>(
private fun assertParent() = parent.get().also { assert(it != null) }
}
+
+// Seems to be a bug on commonizer using new targets sourcesets which is preventing to infer the correct type
+expect fun MFMessageComposeViewController.setMessageWorkAround(message: platform.Messages.MSMessage?)
diff --git a/architecture/src/iosSimulatorArm64Main/kotlin/SetMessageWorkAround.kt b/architecture/src/iosSimulatorArm64Main/kotlin/SetMessageWorkAround.kt
new file mode 100644
index 000000000..177103bdb
--- /dev/null
+++ b/architecture/src/iosSimulatorArm64Main/kotlin/SetMessageWorkAround.kt
@@ -0,0 +1,22 @@
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+package com.splendo.kaluga.architecture.navigation
+
+import platform.MessageUI.MFMessageComposeViewController
+
+actual fun MFMessageComposeViewController.setMessageWorkAround(message: platform.Messages.MSMessage?) = this.setMessage(message)
diff --git a/architecture/src/iosX64Main/kotlin/SetMessageWorkAround.kt b/architecture/src/iosX64Main/kotlin/SetMessageWorkAround.kt
new file mode 100644
index 000000000..177103bdb
--- /dev/null
+++ b/architecture/src/iosX64Main/kotlin/SetMessageWorkAround.kt
@@ -0,0 +1,22 @@
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+package com.splendo.kaluga.architecture.navigation
+
+import platform.MessageUI.MFMessageComposeViewController
+
+actual fun MFMessageComposeViewController.setMessageWorkAround(message: platform.Messages.MSMessage?) = this.setMessage(message)
diff --git a/base-permissions/src/commonMain/kotlin/PermissionState.kt b/base-permissions/src/commonMain/kotlin/PermissionState.kt
index caf484ea6..a9650b38b 100644
--- a/base-permissions/src/commonMain/kotlin/PermissionState.kt
+++ b/base-permissions/src/commonMain/kotlin/PermissionState.kt
@@ -91,12 +91,12 @@ abstract class PermissionStateRepo(
initChangeStateWithRepo = { state, repo ->
val pm = (repo as PermissionStateRepo
).permissionManager
pm.startMonitoring(monitoringInterval)
- if (state == null || state is PermissionState.Unknown
)
+ if (state is PermissionState.Unknown
)
suspend { pm.initializeState() }
else
suspend { state }
},
- deinitChangeStateWithRepo = { state, repo ->
+ deinitChangeStateWithRepo = { _, repo ->
(repo as PermissionStateRepo
).permissionManager.stopMonitoring() // TODO: could also be replaced by a state
null
},
diff --git a/base/src/commonMain/kotlin/state/State.kt b/base/src/commonMain/kotlin/state/State.kt
index 9776b5805..ad8f01510 100644
--- a/base/src/commonMain/kotlin/state/State.kt
+++ b/base/src/commonMain/kotlin/state/State.kt
@@ -414,9 +414,9 @@ abstract class BaseColdStateRepo>(
*/
open class ColdStateFlowRepo(
coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
- val initChangeStateWithRepo: suspend (S?, ColdStateFlowRepo) -> (suspend () -> S),
+ val initChangeStateWithRepo: suspend (S, ColdStateFlowRepo) -> (suspend () -> S),
val deinitChangeStateWithRepo: suspend (S, ColdStateFlowRepo) -> (suspend () -> S)?,
- val firstState: (suspend() -> S)? = null
+ val firstState: () -> S
) : StateFlowRepo,
BaseColdStateRepo>(
context = coroutineContext,
@@ -425,12 +425,12 @@ open class ColdStateFlowRepo(
constructor(
coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
// order is different than below because here firstState is mandatory, and to avoid JVM signature clashes
- firstState: suspend() -> S,
+ firstState: () -> S,
initChangeState: suspend (S) -> (suspend () -> S),
deinitChangeState: suspend (S) -> (suspend () -> S)
) : this(
coroutineContext,
- initChangeStateWithRepo = { state, _ -> state?.let { initChangeState(state) } ?: firstState },
+ initChangeStateWithRepo = { state, _ -> initChangeState(state) },
deinitChangeStateWithRepo = { state, _ -> deinitChangeState(state) },
firstState = firstState
)
@@ -439,7 +439,7 @@ open class ColdStateFlowRepo(
coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
init: suspend (ColdStateFlowRepo) -> S,
deinit: suspend (ColdStateFlowRepo) -> S?,
- firstState: (suspend() -> S)? = null
+ firstState: () -> S
) : this(
coroutineContext,
initChangeStateWithRepo = { _, repo -> { init(repo) } },
@@ -459,11 +459,9 @@ open class ColdStateFlowRepo(
override val lazyMutableFlow: Lazy> =
lazy {
- runBlocking {
- MutableStateFlow(
- firstState?.invoke() ?: initChangeStateWithRepo(null, this@ColdStateFlowRepo)()
- )
- }
+ MutableStateFlow(
+ firstState()
+ )
}
override suspend fun firstCollection() {
diff --git a/base/src/iosMain/kotlin/text/DateFormatter.kt b/base/src/iosMain/kotlin/text/DateFormatter.kt
index 196ea1479..a53671092 100644
--- a/base/src/iosMain/kotlin/text/DateFormatter.kt
+++ b/base/src/iosMain/kotlin/text/DateFormatter.kt
@@ -76,11 +76,21 @@ actual class DateFormatter private constructor(private val format: NSDateFormatt
}
)
- private fun defaultDate(timeZone: TimeZone) = Date.now(timeZone = timeZone).apply {
+ // Due to a problem related to the commonizer we need to supply all the
+ // default arguments expected from the method signature
+ private fun defaultDate(timeZone: TimeZone) = Date.now(
+ offsetInMilliseconds = 0L,
+ timeZone = timeZone,
+ locale = Locale.defaultLocale
+ ).apply {
// Cannot use .utc since it may not be available when this method is called
// This is likely caused by https://youtrack.jetbrains.com/issue/KT-38181
// TODO When moving Date and Date formatter to separate modules, this should be updated to use .utc
- val epoch = Date.epoch(timeZone = TimeZone.get("UTC")!!)
+ val epoch = Date.epoch(
+ offsetInMilliseconds = 0L,
+ timeZone = TimeZone.get("UTC")!!,
+ locale = Locale.defaultLocale
+ )
this.era = epoch.era
this.year = epoch.year
this.month = epoch.month
diff --git a/beacons/src/commonMain/kotlin/Beacons.kt b/beacons/src/commonMain/kotlin/Beacons.kt
index 3f9bbd795..1ca30efce 100644
--- a/beacons/src/commonMain/kotlin/Beacons.kt
+++ b/beacons/src/commonMain/kotlin/Beacons.kt
@@ -95,7 +95,7 @@ class Beacons(
}
cache[beacon.beaconID] = beacon to coroutineScope.launch {
debug(TAG, "[Added] $beacon")
- delay(timeoutMs)
+ delay(beacon.lastSeen.millisecondSinceEpoch + timeoutMs - Date.now().millisecondSinceEpoch)
debug(TAG, "[Lost] $beacon")
cache.remove(beacon.beaconID)
updateList()
diff --git a/bluetooth/src/androidLibMain/kotlin/Characteristic.kt b/bluetooth/src/androidLibMain/kotlin/Characteristic.kt
index fc83be72a..e04094edd 100644
--- a/bluetooth/src/androidLibMain/kotlin/Characteristic.kt
+++ b/bluetooth/src/androidLibMain/kotlin/Characteristic.kt
@@ -28,7 +28,7 @@ actual interface CharacteristicWrapper {
val service: ServiceWrapper
actual val descriptors: List
val permissions: Int
- val properties: Int
+ actual val properties: Int
var writeType: Int
fun setValue(newValue: String): Boolean
@@ -40,12 +40,6 @@ actual interface CharacteristicWrapper {
fun intValue(formatType: Int, offset: Int): Int
}
-fun CharacteristicWrapper.containsAnyOf(vararg property: Int) =
- if (property.count() > 0)
- properties and property.reduce { acc, i -> acc.or(i) } != 0
- else
- false
-
class DefaultCharacteristicWrapper(private val gattCharacteristic: BluetoothGattCharacteristic) : CharacteristicWrapper {
override val uuid: java.util.UUID
diff --git a/bluetooth/src/androidLibMain/kotlin/device/BluetoothGattWrapper.kt b/bluetooth/src/androidLibMain/kotlin/device/BluetoothGattWrapper.kt
index ade1ac28c..bcf13dbab 100644
--- a/bluetooth/src/androidLibMain/kotlin/device/BluetoothGattWrapper.kt
+++ b/bluetooth/src/androidLibMain/kotlin/device/BluetoothGattWrapper.kt
@@ -29,6 +29,8 @@ interface BluetoothGattWrapper {
fun disconnect()
fun close()
fun readRemoteRssi(): Boolean
+ /** Request MTU, returns `true` if the new MTU value has been requested successfully */
+ fun requestMtu(mtu: Int): Boolean
fun readCharacteristic(wrapper: CharacteristicWrapper): Boolean
fun readDescriptor(wrapper: DescriptorWrapper): Boolean
@@ -59,6 +61,10 @@ class DefaultBluetoothGattWrapper(private val gatt: BluetoothGatt) : BluetoothGa
return gatt.readRemoteRssi()
}
+ override fun requestMtu(mtu: Int): Boolean {
+ return gatt.requestMtu(mtu)
+ }
+
override fun readCharacteristic(wrapper: CharacteristicWrapper): Boolean {
val characteristic = getCharacteristic(wrapper) ?: return false
return gatt.readCharacteristic(characteristic)
diff --git a/bluetooth/src/androidLibMain/kotlin/device/DeviceConnectionManager.kt b/bluetooth/src/androidLibMain/kotlin/device/DeviceConnectionManager.kt
index 76e473976..425e0f581 100644
--- a/bluetooth/src/androidLibMain/kotlin/device/DeviceConnectionManager.kt
+++ b/bluetooth/src/androidLibMain/kotlin/device/DeviceConnectionManager.kt
@@ -35,6 +35,7 @@ import com.splendo.kaluga.bluetooth.Service
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.containsAnyOf
import com.splendo.kaluga.bluetooth.uuidString
+import com.splendo.kaluga.logging.debug
import com.splendo.kaluga.logging.warn
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
@@ -78,6 +79,13 @@ internal actual class DeviceConnectionManager(
}
}
+ override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
+ debug(TAG) { "onMtuChanged($gatt, $mtu, $status)" }
+ if (status == GATT_SUCCESS) {
+ handleNewMtu(mtu)
+ }
+ }
+
override fun onCharacteristicRead(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
characteristic ?: return
updateCharacteristic(characteristic, status)
@@ -178,6 +186,10 @@ internal actual class DeviceConnectionManager(
gatt.await().readRemoteRssi()
}
+ override suspend fun requestMtu(mtu: Int): Boolean {
+ return gatt.await().requestMtu(mtu)
+ }
+
override suspend fun performAction(action: DeviceAction) {
currentAction = action
val succeeded = when (action) {
diff --git a/bluetooth/src/commonMain/kotlin/Bluetooth.kt b/bluetooth/src/commonMain/kotlin/Bluetooth.kt
index 4cacee952..0b7d61927 100644
--- a/bluetooth/src/commonMain/kotlin/Bluetooth.kt
+++ b/bluetooth/src/commonMain/kotlin/Bluetooth.kt
@@ -228,6 +228,8 @@ fun Flow.rssi(): Flow {
return this.info().map { it.rssi }.distinctUntilChanged()
}
+fun Flow.mtu() = state().mapLatest { it.connectionManager.mtu }
+
fun Flow.distance(environmentalFactor: Double = 2.0, averageOver: Int = 5): Flow {
val lastNResults = sharedMutableListOf()
return this.info().map { deviceInfo ->
@@ -256,6 +258,17 @@ suspend fun Flow.updateRssi() {
}.first()
}
+suspend fun Flow.requestMtu(mtu: Int): Boolean {
+ return state().transformLatest { deviceState ->
+ when (deviceState) {
+ is Connected -> {
+ emit(deviceState.requestMtu(mtu))
+ }
+ else -> {}
+ }
+ }.first()
+}
+
@JvmName("getService")
operator fun Flow>.get(uuid: UUID): Flow {
return this.map { services ->
diff --git a/bluetooth/src/commonMain/kotlin/Characteristic.kt b/bluetooth/src/commonMain/kotlin/Characteristic.kt
index bad4e495b..f39ba43ac 100644
--- a/bluetooth/src/commonMain/kotlin/Characteristic.kt
+++ b/bluetooth/src/commonMain/kotlin/Characteristic.kt
@@ -109,10 +109,33 @@ open class Characteristic(val wrapper: CharacteristicWrapper, initialValue: Byte
override fun getUpdatedValue(): ByteArray? {
return wrapper.value?.asBytes
}
+
+ fun hasProperty(property: CharacteristicProperties) = hasProperties(listOf(property))
+
+ private fun hasProperties(properties: List) = wrapper
+ .containsAnyOf(*properties.map(CharacteristicProperties::rawValue).toIntArray())
}
expect interface CharacteristicWrapper {
val uuid: UUID
val descriptors: List
val value: Value?
+ val properties: Int
+}
+
+fun CharacteristicWrapper.containsAnyOf(vararg property: Int) = if (property.isNotEmpty()) {
+ properties and property.reduce { acc, i -> acc.or(i) } != 0
+} else { false }
+
+sealed class CharacteristicProperties(val rawValue: Int) {
+ object Broadcast : CharacteristicProperties(0x01)
+ object Read : CharacteristicProperties(0x02)
+ object WriteWithoutResponse : CharacteristicProperties(0x04)
+ object Write : CharacteristicProperties(0x08)
+ object Notify : CharacteristicProperties(0x10)
+ object Indicate : CharacteristicProperties(0x20)
+ object SignedWrite : CharacteristicProperties(0x40)
+ object ExtendedProperties : CharacteristicProperties(0x80)
+
+ infix fun or(other: CharacteristicProperties) = rawValue or other.rawValue
}
diff --git a/bluetooth/src/commonMain/kotlin/device/DeviceConnectionManager.kt b/bluetooth/src/commonMain/kotlin/device/DeviceConnectionManager.kt
index 613216989..98ae282cd 100644
--- a/bluetooth/src/commonMain/kotlin/device/DeviceConnectionManager.kt
+++ b/bluetooth/src/commonMain/kotlin/device/DeviceConnectionManager.kt
@@ -18,7 +18,9 @@
package com.splendo.kaluga.bluetooth.device
import co.touchlab.stately.collections.sharedMutableMapOf
+import co.touchlab.stately.concurrency.AtomicInt
import co.touchlab.stately.concurrency.AtomicReference
+import co.touchlab.stately.concurrency.value
import com.splendo.kaluga.bluetooth.Characteristic
import com.splendo.kaluga.bluetooth.Descriptor
import com.splendo.kaluga.bluetooth.Service
@@ -51,10 +53,14 @@ abstract class BaseDeviceConnectionManager(
set(value) { _currentAction.set(value) }
protected val notifyingCharacteristics = sharedMutableMapOf()
+ private val _mtu = AtomicInt(-1)
+ val mtu get() = _mtu.value
+
abstract suspend fun connect()
abstract suspend fun discoverServices()
abstract suspend fun disconnect()
abstract suspend fun readRssi()
+ abstract suspend fun requestMtu(mtu: Int): Boolean
abstract suspend fun performAction(action: DeviceAction)
suspend fun handleNewRssi(rssi: Int) {
@@ -63,6 +69,8 @@ abstract class BaseDeviceConnectionManager(
}
}
+ fun handleNewMtu(mtu: Int) = _mtu.set(mtu)
+
suspend fun handleConnect() {
stateRepo.takeAndChangeState { state ->
when (state) {
diff --git a/bluetooth/src/commonMain/kotlin/device/DeviceState.kt b/bluetooth/src/commonMain/kotlin/device/DeviceState.kt
index c7896f2a1..715a3ea11 100644
--- a/bluetooth/src/commonMain/kotlin/device/DeviceState.kt
+++ b/bluetooth/src/commonMain/kotlin/device/DeviceState.kt
@@ -163,6 +163,10 @@ sealed class DeviceState(
connectionManager.readRssi()
}
+ suspend fun requestMtu(mtu: Int): Boolean {
+ return connectionManager.requestMtu(mtu)
+ }
+
override suspend fun finalState() {
super.finalState()
diff --git a/bluetooth/src/commonTest/kotlin/BluetoothCharacteristicPropertiesTest.kt b/bluetooth/src/commonTest/kotlin/BluetoothCharacteristicPropertiesTest.kt
new file mode 100644
index 000000000..ad3c8fe73
--- /dev/null
+++ b/bluetooth/src/commonTest/kotlin/BluetoothCharacteristicPropertiesTest.kt
@@ -0,0 +1,70 @@
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+package com.splendo.kaluga.bluetooth
+
+import com.splendo.kaluga.test.mock.bluetooth.characteristic
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class BluetoothCharacteristicPropertiesTest : BluetoothFlowTest() {
+
+ override val flow = suspend {
+ setup(Setup.CHARACTERISTIC) {
+ characteristics {
+ characteristic {
+ properties =
+ CharacteristicProperties.Read or
+ CharacteristicProperties.WriteWithoutResponse
+ }
+ }
+ }
+ bluetooth.devices()[device.identifier]
+ .services()[service.uuid]
+ .characteristics()[characteristic.uuid]
+ }
+
+ @Test
+ fun testProperties() = testWithFlow {
+ scanDevice()
+ bluetooth.startScanning()
+
+ test {
+ assertNull(it)
+ }
+
+ action {
+ connectDevice(device)
+ discoverService(service, device)
+ }
+ val characteristic = characteristic
+ test {
+ assertEquals(characteristic, it)
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Broadcast))
+ assertTrue(characteristic.hasProperty(CharacteristicProperties.Read))
+ assertTrue(characteristic.hasProperty(CharacteristicProperties.WriteWithoutResponse))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Write))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Notify))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Indicate))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.SignedWrite))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.ExtendedProperties))
+ }
+ }
+}
diff --git a/bluetooth/src/commonTest/kotlin/BluetoothCharactertisticTest.kt b/bluetooth/src/commonTest/kotlin/BluetoothCharacteristicTest.kt
similarity index 58%
rename from bluetooth/src/commonTest/kotlin/BluetoothCharactertisticTest.kt
rename to bluetooth/src/commonTest/kotlin/BluetoothCharacteristicTest.kt
index 1b87565f7..d70ae8ba7 100644
--- a/bluetooth/src/commonTest/kotlin/BluetoothCharactertisticTest.kt
+++ b/bluetooth/src/commonTest/kotlin/BluetoothCharacteristicTest.kt
@@ -17,14 +17,22 @@
package com.splendo.kaluga.bluetooth
+import com.splendo.kaluga.test.mock.bluetooth.characteristic
import kotlin.test.Test
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
import kotlin.test.assertNull
class BluetoothCharacteristicTest : BluetoothFlowTest() {
override val flow = suspend {
- setup(Setup.CHARACTERISTIC)
+ setup(Setup.CHARACTERISTIC) {
+ characteristics {
+ characteristic {
+ properties = 0
+ }
+ }
+ }
bluetooth.devices()[device.identifier].services()[service.uuid].characteristics()[characteristic.uuid]
}
@@ -44,6 +52,14 @@ class BluetoothCharacteristicTest : BluetoothFlowTest() {
val characteristic = characteristic
test {
assertEquals(characteristic, it)
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Broadcast))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Read))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.WriteWithoutResponse))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Write))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Notify))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.Indicate))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.SignedWrite))
+ assertFalse(characteristic.hasProperty(CharacteristicProperties.ExtendedProperties))
}
}
}
diff --git a/bluetooth/src/commonTest/kotlin/BluetoothCharactertisticsTest.kt b/bluetooth/src/commonTest/kotlin/BluetoothCharacteristicsTest.kt
similarity index 100%
rename from bluetooth/src/commonTest/kotlin/BluetoothCharactertisticsTest.kt
rename to bluetooth/src/commonTest/kotlin/BluetoothCharacteristicsTest.kt
diff --git a/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt b/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt
index 217a7fb68..8ed583d18 100644
--- a/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt
+++ b/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt
@@ -41,8 +41,11 @@ import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission
import com.splendo.kaluga.test.FlowTestBlock
import com.splendo.kaluga.test.MockPermissionManager
import com.splendo.kaluga.test.SimpleFlowTest
+import com.splendo.kaluga.test.mock.bluetooth.ServiceWrapperBuilder
+import com.splendo.kaluga.test.mock.bluetooth.characteristic
import com.splendo.kaluga.test.mock.bluetooth.createDeviceWrapper
import com.splendo.kaluga.test.mock.bluetooth.createServiceWrapper
+import com.splendo.kaluga.test.mock.bluetooth.descriptor
import com.splendo.kaluga.test.mock.bluetooth.device.MockAdvertisementData
import com.splendo.kaluga.test.mock.bluetooth.device.MockDeviceConnectionManager
import com.splendo.kaluga.test.mock.bluetooth.scanner.MockBaseScanner
@@ -60,6 +63,18 @@ import kotlinx.coroutines.launch
abstract class BluetoothFlowTest : SimpleFlowTest() {
companion object {
+ fun defaultService(): ServiceWrapperBuilder.() -> Unit = {
+ uuid = randomUUID()
+ characteristics {
+ characteristic {
+ uuid = randomUUID()
+ properties = 0
+ descriptors {
+ descriptor(uuid = randomUUID())
+ }
+ }
+ }
+ }
}
var rssi = -100
@@ -161,6 +176,7 @@ abstract class BluetoothFlowTest : SimpleFlowTest() {
}
protected fun setup(
setup: Setup,
+ serviceWrapperBuilder: ServiceWrapperBuilder.() -> Unit = defaultService()
) {
setupPermissions()
setupBluetooth(autoRequestPermission, autoEnableBluetooth, isEnabled, permissionState)
@@ -171,7 +187,7 @@ abstract class BluetoothFlowTest : SimpleFlowTest() {
connectionManager = device.peekState().connectionManager as MockDeviceConnectionManager
if (setup == DEVICE) return
- serviceWrapper = createServiceWrapper(connectionManager.stateRepo)
+ serviceWrapper = createServiceWrapper(serviceWrapperBuilder)
service = Service(serviceWrapper, connectionManager.stateRepo)
if (setup == SERVICE) return
diff --git a/bluetooth/src/commonTest/kotlin/BluetoothRequestMtuTest.kt b/bluetooth/src/commonTest/kotlin/BluetoothRequestMtuTest.kt
new file mode 100644
index 000000000..4787e1de2
--- /dev/null
+++ b/bluetooth/src/commonTest/kotlin/BluetoothRequestMtuTest.kt
@@ -0,0 +1,56 @@
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+package com.splendo.kaluga.bluetooth
+
+import com.splendo.kaluga.bluetooth.device.DeviceState
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.first
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class BluetoothRequestMtuTest : BluetoothFlowTest() {
+
+ override val flow = suspend {
+ setup(Setup.DEVICE)
+ bluetooth.devices()[device.identifier].mtu()
+ }
+
+ @Test
+ fun testRequestMtu() = testWithFlow {
+ val newMtu = 512
+
+ assertEquals(-1, connectionManager.mtu)
+
+ scanDevice()
+ bluetooth.startScanning()
+ action {
+ connectDevice(device)
+ bluetooth.devices()[device.identifier].requestMtu(newMtu)
+ connectionManager.requestMtuCompleted.get().await()
+ device.filterIsInstance().first()
+ connectionManager.handleNewMtu(newMtu)
+ }
+
+ assertEquals(newMtu, connectionManager.mtu)
+
+ resetFlow()
+
+ permissionManager.hasStoppedMonitoring.await()
+ mockBaseScanner().stopMonitoringPermissionsCompleted.get().await()
+ }
+}
diff --git a/bluetooth/src/commonTest/kotlin/device/DeviceTest.kt b/bluetooth/src/commonTest/kotlin/device/DeviceTest.kt
index 945b27077..73c696240 100644
--- a/bluetooth/src/commonTest/kotlin/device/DeviceTest.kt
+++ b/bluetooth/src/commonTest/kotlin/device/DeviceTest.kt
@@ -235,6 +235,29 @@ class DeviceTest : BluetoothFlowTest() {
}
}
+ @Test
+ fun testRequestMtu() = testWithFlow {
+ getDisconnectedState()
+ connecting()
+ connect()
+
+ action {
+ deviceStateRepo.useState { deviceState ->
+ when (deviceState) {
+ is DeviceState.Connected -> {
+ assertTrue(deviceState.requestMtu(23))
+ connectionManager.handleNewMtu(23)
+ }
+ else -> {}
+ }
+ }
+ }
+
+ val requestMtuCompleted = connectionManager.requestMtuCompleted.get()
+ assertTrue(requestMtuCompleted.isCompleted)
+ assertEquals(23, connectionManager.mtu)
+ }
+
@Test
fun testDiscoverDevices() = testWithFlow {
getDisconnectedState()
diff --git a/bluetooth/src/iosMain/kotlin/Characteristic.kt b/bluetooth/src/iosMain/kotlin/Characteristic.kt
index e4585d64b..22b9a441a 100644
--- a/bluetooth/src/iosMain/kotlin/Characteristic.kt
+++ b/bluetooth/src/iosMain/kotlin/Characteristic.kt
@@ -29,6 +29,7 @@ actual interface CharacteristicWrapper {
actual val uuid: CBUUID
actual val descriptors: List
actual val value: NSData?
+ actual val properties: Int
fun readValue(peripheral: CBPeripheral)
fun writeValue(value: NSData, peripheral: CBPeripheral)
@@ -40,6 +41,7 @@ class DefaultCharacteristicWrapper(private val characteristic: CBCharacteristic)
override val uuid: CBUUID get() { return characteristic.UUID }
override val descriptors: List = characteristic.descriptors?.typedList()?.map { DefaultDescriptorWrapper(it) } ?: emptyList()
override val value: NSData? get() { return characteristic.value }
+ override val properties get() = characteristic.properties.toInt()
override fun readValue(peripheral: CBPeripheral) {
peripheral.readValueForCharacteristic(characteristic)
diff --git a/bluetooth/src/iosMain/kotlin/device/DeviceConnectionManager.kt b/bluetooth/src/iosMain/kotlin/device/DeviceConnectionManager.kt
index 9eb42f165..5c37d4d01 100644
--- a/bluetooth/src/iosMain/kotlin/device/DeviceConnectionManager.kt
+++ b/bluetooth/src/iosMain/kotlin/device/DeviceConnectionManager.kt
@@ -23,9 +23,11 @@ import com.splendo.kaluga.base.typedList
import com.splendo.kaluga.bluetooth.DefaultServiceWrapper
import com.splendo.kaluga.bluetooth.Service
import com.splendo.kaluga.bluetooth.uuidString
+import com.splendo.kaluga.logging.debug
import kotlinx.coroutines.launch
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBCharacteristic
+import platform.CoreBluetooth.CBCharacteristicWriteWithResponse
import platform.CoreBluetooth.CBDescriptor
import platform.CoreBluetooth.CBPeripheral
import platform.CoreBluetooth.CBPeripheralDelegateProtocol
@@ -138,6 +140,17 @@ internal actual class DeviceConnectionManager(
peripheral.readRSSI()
}
+ override suspend fun requestMtu(mtu: Int): Boolean {
+ val max = peripheral.maximumWriteValueLengthForType(CBCharacteristicWriteWithResponse)
+ debug(TAG) {
+ "maximumWriteValueLengthForType(CBCharacteristicWriteWithResponse) = $max"
+ }
+ // Update MTU to current known value
+ handleNewMtu(max.toInt())
+ // Return false, because we can't request MTU change from iOS
+ return false
+ }
+
override suspend fun performAction(action: DeviceAction) {
currentAction = action
when (action) {
diff --git a/bluetooth/src/jsMain/kotlin/device/DeviceConnectionManager.kt b/bluetooth/src/jsMain/kotlin/device/DeviceConnectionManager.kt
index fff370f22..5443cb031 100644
--- a/bluetooth/src/jsMain/kotlin/device/DeviceConnectionManager.kt
+++ b/bluetooth/src/jsMain/kotlin/device/DeviceConnectionManager.kt
@@ -38,5 +38,7 @@ internal actual class DeviceConnectionManager(connectionSettings: ConnectionSett
override suspend fun readRssi() {}
+ override suspend fun requestMtu(mtu: Int) = false
+
override suspend fun performAction(action: DeviceAction) {}
}
diff --git a/bluetooth/src/jsMain/kotlin/wrappers.kt b/bluetooth/src/jsMain/kotlin/wrappers.kt
index 2f8966461..c2113cc92 100644
--- a/bluetooth/src/jsMain/kotlin/wrappers.kt
+++ b/bluetooth/src/jsMain/kotlin/wrappers.kt
@@ -21,6 +21,7 @@ actual interface CharacteristicWrapper {
actual val uuid: UUID
actual val descriptors: List
actual val value: Value?
+ actual val properties: Int
}
actual interface DescriptorWrapper {
diff --git a/bluetooth/src/jvmMain/kotlin/device/DeviceConnectionManager.kt b/bluetooth/src/jvmMain/kotlin/device/DeviceConnectionManager.kt
index 31178c3ee..64c01e851 100644
--- a/bluetooth/src/jvmMain/kotlin/device/DeviceConnectionManager.kt
+++ b/bluetooth/src/jvmMain/kotlin/device/DeviceConnectionManager.kt
@@ -34,5 +34,7 @@ internal actual class DeviceConnectionManager(connectionSettings: ConnectionSett
override suspend fun readRssi() {}
+ override suspend fun requestMtu(mtu: Int) = false
+
override suspend fun performAction(action: DeviceAction) {}
}
diff --git a/bluetooth/src/jvmMain/kotlin/wrappers.kt b/bluetooth/src/jvmMain/kotlin/wrappers.kt
index 2f8966461..c2113cc92 100644
--- a/bluetooth/src/jvmMain/kotlin/wrappers.kt
+++ b/bluetooth/src/jvmMain/kotlin/wrappers.kt
@@ -21,6 +21,7 @@ actual interface CharacteristicWrapper {
actual val uuid: UUID
actual val descriptors: List
actual val value: Value?
+ actual val properties: Int
}
actual interface DescriptorWrapper {
diff --git a/build.gradle b/build.gradle
index 4f563c7e6..1aeedcee5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,6 +18,12 @@ plugins {
id 'org.jetbrains.kotlin.multiplatform' apply false
}
+// TODO: To be removed once we will migrate to kotlin version 1.6.20
+// https://youtrack.jetbrains.com/issue/KT-49109#focus=Comments-27-5667134.0-0
+rootProject.plugins.withType(Class.forName("org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin")) {
+ rootProject.extensions.getByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.class).nodeVersion = "16.13.2"
+}
+
allprojects {
repositories {
mavenCentral()
diff --git a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt
index 40ba2a877..440d46f49 100644
--- a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt
+++ b/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt
@@ -52,7 +52,7 @@ class BeaconsActivity : KalugaViewModelActivity() {
}
}
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.bluetooth_menu, menu)
return true
}
diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt
index 60dbe44b3..dd0957254 100644
--- a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt
+++ b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt
@@ -54,7 +54,7 @@ class BluetoothActivity : KalugaViewModelActivity() {
viewModel.title.observe(::setTitle)
}
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.bluetooth_menu, menu)
return true
}
diff --git a/example/ios/Demo.xcodeproj/project.pbxproj b/example/ios/Demo.xcodeproj/project.pbxproj
index e4af6895d..6d30214da 100644
--- a/example/ios/Demo.xcodeproj/project.pbxproj
+++ b/example/ios/Demo.xcodeproj/project.pbxproj
@@ -33,9 +33,7 @@
641CC0DC25CACAAD001E1CD1 /* LinksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641CC0DB25CACAAD001E1CD1 /* LinksViewController.swift */; };
649979D325CC638C00348419 /* SystemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649979D225CC638C00348419 /* SystemViewController.swift */; };
649979E725CCD37200348419 /* NetworkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649979E625CCD37200348419 /* NetworkViewController.swift */; };
- 74167996D52193D5DECB517A /* KotlinNativeFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 746885BADB9F6661211345DA /* KotlinNativeFramework.framework */; };
7425815FF35195035A8991B0 /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C710A1E2E41AF0F4A9FFC5 /* LocationViewController.swift */; };
- 7450B266B7AD1FE0B79C10EA /* KotlinNativeFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 746885BADB9F6661211345DA /* KotlinNativeFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74671D1F8F6A31F1DD3F7912 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BBBF2C57521E71982179E0 /* AppDelegate.swift */; };
7478297FCF44ED1D49525D4E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74FDC8922442294AF0776058 /* LaunchScreen.storyboard */; };
749B42F84CA72A5D4A0F3AD9 /* DemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741DDADC0C8034E32EA9C3D6 /* DemoUITests.swift */; };
@@ -47,13 +45,6 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
- 740E6C673EE16791117B2FD4 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 7477FD4305C126F92DB837CC /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 74269CF2B44D65F5DA990E74;
- remoteInfo = KotlinNativeFramework;
- };
74798B41F78378ECDB0E4BB7 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7477FD4305C126F92DB837CC /* Project object */;
@@ -76,7 +67,6 @@
buildActionMask = 0;
dstSubfolderSpec = 10;
files = (
- 7450B266B7AD1FE0B79C10EA /* KotlinNativeFramework.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -117,13 +107,11 @@
741DDADC0C8034E32EA9C3D6 /* DemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoUITests.swift; sourceTree = ""; };
741E82A1DDEE399E80A02B54 /* gradle-wrapper.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = "gradle-wrapper.jar"; sourceTree = ""; };
7428D8755D288FD7EA2E6537 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; };
- 742C6ACDA3C877A6128316ED /* KotlinNativeFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KotlinNativeFramework.framework; path = Library/Frameworks/KotlinNativeFramework.framework; sourceTree = DEVELOPER_DIR; };
742CCE9033CEC8C8004D624F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
7433F67D6FEF093F4C827A5A /* build.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = build.gradle.kts; sourceTree = ""; };
744CF99B70DA1947AA98CA3B /* DemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoTests.swift; sourceTree = ""; };
744F12775216AF134CBAF4E9 /* build.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = build.gradle.kts; sourceTree = ""; };
746834742E284396FD5452F4 /* DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 746885BADB9F6661211345DA /* KotlinNativeFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KotlinNativeFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
746E133CAEC5C7B1D516B11F /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
74A0718952D0CC604E49709B /* DemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
74B0C3AB70AAB7878B48F6B1 /* gradle-wrapper.properties */ = {isa = PBXFileReference; lastKnownFileType = file.properties; path = "gradle-wrapper.properties"; sourceTree = ""; };
@@ -157,7 +145,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 74167996D52193D5DECB517A /* KotlinNativeFramework.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -323,7 +310,6 @@
children = (
205AF6542488F017001BD99E /* Localizable.strings */,
747A549D9FF0D1B49E65E190 /* Products */,
- 747C47F96366E8C0FE896BF1 /* Frameworks */,
746CAD9D15ECB24F4ED84E11 /* Demo */,
7492DE0914511F3DC96A30A8 /* DemoTests */,
74D6698E6E24A4F93E6B289A /* DemoUITests */,
@@ -404,19 +390,10 @@
746E133CAEC5C7B1D516B11F /* Demo.app */,
746834742E284396FD5452F4 /* DemoTests.xctest */,
74A0718952D0CC604E49709B /* DemoUITests.xctest */,
- 746885BADB9F6661211345DA /* KotlinNativeFramework.framework */,
);
name = Products;
sourceTree = "";
};
- 747C47F96366E8C0FE896BF1 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 742C6ACDA3C877A6128316ED /* KotlinNativeFramework.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
747F646D72854D37D5686909 /* KotlinNativeFramework */ = {
isa = PBXGroup;
children = (
@@ -483,25 +460,11 @@
productReference = 746834742E284396FD5452F4 /* DemoTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
- 74269CF2B44D65F5DA990E74 /* KotlinNativeFramework */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 7455FEDF902D83B941B78621 /* Build configuration list for PBXNativeTarget "KotlinNativeFramework" */;
- buildPhases = (
- 74EB9E9C533BEEA0282341AE /* Compile Kotlin/Native */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = KotlinNativeFramework;
- productName = KotlinNativeFramework;
- productReference = 746885BADB9F6661211345DA /* KotlinNativeFramework.framework */;
- productType = "com.apple.product-type.framework";
- };
74555136E1B2BB715E50195E /* Demo */ = {
isa = PBXNativeTarget;
buildConfigurationList = 74C754F7F747073B076C3A91 /* Build configuration list for PBXNativeTarget "Demo" */;
buildPhases = (
+ 95D0860F27D2540B00838E57 /* Compile Kotlin/Native */,
744156D82DB12C1F9CC3C68F /* Sources */,
74B635A880CB81844D31A213 /* Frameworks */,
746C535FD7586B544C684D34 /* Resources */,
@@ -510,7 +473,6 @@
buildRules = (
);
dependencies = (
- 7408BEE716C49FFB32C030AC /* PBXTargetDependency */,
);
name = Demo;
productName = Demo;
@@ -566,7 +528,6 @@
74555136E1B2BB715E50195E /* Demo */,
74074B293DEEEE5CB9495BC2 /* DemoTests */,
74F1ACA722488B68F78605D9 /* DemoUITests */,
- 74269CF2B44D65F5DA990E74 /* KotlinNativeFramework */,
);
};
/* End PBXProject section */
@@ -603,19 +564,23 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 74EB9E9C533BEEA0282341AE /* Compile Kotlin/Native */ = {
+ 95D0860F27D2540B00838E57 /* Compile Kotlin/Native */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
+ inputFileListPaths = (
+ );
inputPaths = (
);
name = "Compile Kotlin/Native";
+ outputFileListPaths = (
+ );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ -z \"$KOTLIN_NATIVE_BUILD_CAPABLE\" ]; then\necho \"arch: $ARCHS\"\ncd \"$SRCROOT/Supporting Files\"\nsh -c \". gradlew buildForXcode\"\nfi\n \n";
+ shellScript = "cd \"$SRCROOT/Supporting Files\"\n./gradlew :KotlinNativeFramework:embedAndSignAppleFrameworkForXcode\n";
};
/* End PBXShellScriptBuildPhase section */
@@ -673,11 +638,6 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
- 7408BEE716C49FFB32C030AC /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 74269CF2B44D65F5DA990E74 /* KotlinNativeFramework */;
- targetProxy = 740E6C673EE16791117B2FD4 /* PBXContainerItemProxy */;
- };
744683563AC18D9D78E89D91 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 74555136E1B2BB715E50195E /* Demo */;
@@ -718,23 +678,6 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
- 74129CD46D0024B61630054C /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CODE_SIGN_IDENTITY = "";
- DYLIB_INSTALL_NAME_BASE = "@rpath";
- INFOPLIST_FILE = KotlinNativeFramework/Info.plist;
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- PRODUCT_BUNDLE_IDENTIFIER = com.splendo.mpp.demo.KotlinNativeFramework;
- PRODUCT_MODULE_NAME = "_$(PRODUCT_NAME:c99extidentifier)";
- PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
- SKIP_INSTALL = YES;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALID_ARCHS = arm64;
- };
- name = Debug;
- };
7450045FC3F0FAB4E9E0751D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -789,27 +732,11 @@
};
name = Release;
};
- 74556A21FFE52626ECDED0EB /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CODE_SIGN_IDENTITY = "";
- DYLIB_INSTALL_NAME_BASE = "@rpath";
- INFOPLIST_FILE = KotlinNativeFramework/Info.plist;
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- PRODUCT_BUNDLE_IDENTIFIER = com.splendo.mpp.demo.KotlinNativeFramework;
- PRODUCT_MODULE_NAME = "_$(PRODUCT_NAME:c99extidentifier)";
- PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
- SKIP_INSTALL = YES;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALID_ARCHS = arm64;
- };
- name = Release;
- };
74B72DFF132E49F15B2DF935 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo";
+ FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = DemoTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.splendo.mpp.demo.DemoTests;
@@ -827,7 +754,10 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = 2732HQPVJP;
- FRAMEWORK_SEARCH_PATHS = $BUILT_PRODUCTS_DIR;
+ FRAMEWORK_SEARCH_PATHS = (
+ $BUILT_PRODUCTS_DIR,
+ "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
+ );
INFOPLIST_FILE = Demo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.splendo.kaluga.example;
@@ -841,6 +771,7 @@
74C4C9BEEE5FE949D448C58D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = DemoUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.splendo.mpp.demo.DemoUITests;
@@ -916,6 +847,7 @@
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo";
+ FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = DemoTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.splendo.mpp.demo.DemoTests;
@@ -929,6 +861,7 @@
74F864CC21F489CA494FD856 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = DemoUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.splendo.mpp.demo.DemoUITests;
@@ -947,7 +880,10 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = 2732HQPVJP;
- FRAMEWORK_SEARCH_PATHS = $BUILT_PRODUCTS_DIR;
+ FRAMEWORK_SEARCH_PATHS = (
+ $BUILT_PRODUCTS_DIR,
+ "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
+ );
INFOPLIST_FILE = Demo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.splendo.kaluga.example;
@@ -979,15 +915,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
- 7455FEDF902D83B941B78621 /* Build configuration list for PBXNativeTarget "KotlinNativeFramework" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 74129CD46D0024B61630054C /* Debug */,
- 74556A21FFE52626ECDED0EB /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
7493E4639A804158022EC4C4 /* Build configuration list for PBXNativeTarget "DemoUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/example/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoTests.xcscheme b/example/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoTests.xcscheme
new file mode 100644
index 000000000..df330714c
--- /dev/null
+++ b/example/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoTests.xcscheme
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoUITests.xcscheme b/example/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoUITests.xcscheme
new file mode 100644
index 000000000..f5b5d4dc4
--- /dev/null
+++ b/example/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoUITests.xcscheme
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/ios/KotlinNativeFramework/build.gradle.kts b/example/ios/KotlinNativeFramework/build.gradle.kts
index ad0bc2071..af1919ff5 100644
--- a/example/ios/KotlinNativeFramework/build.gradle.kts
+++ b/example/ios/KotlinNativeFramework/build.gradle.kts
@@ -9,27 +9,39 @@ buildscript {
plugins {
kotlin("multiplatform")
- kotlin("xcode-compat") version "0.2.5"
id("org.jlleitschuh.gradle.ktlint")
}
kotlin {
- xcode {
- setupFramework("KotlinNativeFramework") {
-
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64()
+ ).forEach {
+ it.binaries.framework {
+ baseName = "KotlinNativeFramework"
export(project(":shared"))
-
transitiveExport = true
}
}
sourceSets {
- getByName("KotlinNativeFrameworkMain") {
+ val commonMain by getting {
dependencies {
api(project(":shared"))
}
}
+
+ val iosX64Main by getting
+ val iosArm64Main by getting
+ val iosSimulatorArm64Main by getting
+ val iosMain by creating {
+ dependsOn(commonMain)
+ iosX64Main.dependsOn(this)
+ iosArm64Main.dependsOn(this)
+ iosSimulatorArm64Main.dependsOn(this)
+ }
}
}
diff --git a/example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/KotlinNativeFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt
similarity index 84%
rename from example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/KotlinNativeFramework.kt
rename to example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt
index 9c027fa0c..51cb090c9 100644
--- a/example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/KotlinNativeFramework.kt
+++ b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt
@@ -16,12 +16,13 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands
*/
-import ru.pocketbyte.kydra.log.KydraLog
+import io.github.aakira.napier.DebugAntilog
+import io.github.aakira.napier.Antilog as NapierLog
class KotlinNativeFramework {
fun hello() = com.splendo.kaluga.example.shared.helloCommon()
// expose a dependency to Swift as an example
- fun logger(): ru.pocketbyte.kydra.log.Logger = KydraLog.logger
+ fun logger(): NapierLog = DebugAntilog()
}
diff --git a/example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/architecture/KNArchitectureFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt
similarity index 100%
rename from example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/architecture/KNArchitectureFramework.kt
rename to example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt
diff --git a/example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/beacons/KNBeaconsFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt
similarity index 100%
rename from example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/beacons/KNBeaconsFramework.kt
rename to example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt
diff --git a/example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/bluetooth/KNBluetoothFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt
similarity index 100%
rename from example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/bluetooth/KNBluetoothFramework.kt
rename to example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt
diff --git a/example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/location/KNLocationFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/location/KNLocationFramework.kt
similarity index 100%
rename from example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/location/KNLocationFramework.kt
rename to example/ios/KotlinNativeFramework/src/commonMain/kotlin/location/KNLocationFramework.kt
diff --git a/example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/permissions/KNPermissionsFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/permissions/KNPermissionsFramework.kt
similarity index 100%
rename from example/ios/KotlinNativeFramework/src/KotlinNativeFrameworkMain/kotlin/permissions/KNPermissionsFramework.kt
rename to example/ios/KotlinNativeFramework/src/commonMain/kotlin/permissions/KNPermissionsFramework.kt
diff --git a/example/ios/Supporting Files/build.gradle.kts b/example/ios/Supporting Files/build.gradle.kts
index 7dbf413ba..126204c81 100644
--- a/example/ios/Supporting Files/build.gradle.kts
+++ b/example/ios/Supporting Files/build.gradle.kts
@@ -1,3 +1,9 @@
+// TODO: To be removed once we will migrate to kotlin version 1.6.20
+// https://youtrack.jetbrains.com/issue/KT-49109#focus=Comments-27-5667134.0-0
+rootProject.plugins.withType {
+ rootProject.the().nodeVersion = "16.13.2"
+}
+
buildscript {
repositories {
mavenCentral()
diff --git a/example/ios/Supporting Files/gradle.properties b/example/ios/Supporting Files/gradle.properties
index feb4187a8..6cddc7024 100644
--- a/example/ios/Supporting Files/gradle.properties
+++ b/example/ios/Supporting Files/gradle.properties
@@ -4,12 +4,17 @@ kotlin.code.style=official
kotlin.mpp.stability.nowarn=true
android.useAndroidX=true
-kaluga.androidGradlePluginVersion=7.0.1
+kaluga.androidGradlePluginVersion=7.0.4
kaluga.kotlinVersion=1.6.10
kaluga.googleServicesGradlePluginVersion=4.3.10
kaluga.ktLintGradlePluginVersion=10.1.0
org.gradle.jvmargs=-Xmx3048M -Dkotlin.daemon.jvm.options\="-Xmx3048M"
+#MPP
+kotlin.mpp.enableGranularSourceSetsMetadata=true
+kotlin.native.enableDependencyPropagation=false
+kotlin.mpp.enableCInteropCommonization=true
+
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
## extra properties only for example
diff --git a/example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.properties b/example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.properties
index 29e413457..a0f7639f7 100644
--- a/example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.properties
+++ b/example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradle.properties b/gradle.properties
index e2da5e188..9dcd637ab 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,4 +10,9 @@ kaluga.googleServicesGradlePluginVersion=4.3.10
kaluga.ktLintGradlePluginVersion=10.1.0
org.gradle.jvmargs=-Xmx3048M -Dkotlin.daemon.jvm.options\="-Xmx3048M"
+#MPP
+kotlin.mpp.enableGranularSourceSetsMetadata=true
+kotlin.native.enableDependencyPropagation=false
+kotlin.mpp.enableCInteropCommonization=true
+
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
diff --git a/gradle/component.gradle b/gradle/component.gradle
index 11eebe2b3..af991a57e 100644
--- a/gradle/component.gradle
+++ b/gradle/component.gradle
@@ -17,29 +17,17 @@ kotlin {
}
android("androidLib") {
-
publishAllLibraryVariants()
}
- if (gradle.ios_primary_arch == "iosx64")
- targetFromPreset(presets.iosX64, gradle.ios_primary_arch) {
+ gradle.ext.ios_targets.each {
+ "$it" {
binaries {
// Use this entry point to turn the thread tests are run to a background thread instead of the main thread
// This better allows testing the main dispatcher, and testing cross thread access
binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "com.splendo.kaluga.test.mainBackground"]
}
-
}
-
- if (gradle.ios_primary_arch == "iosarm64")
- targetFromPreset(presets.iosArm64, gradle.ios_primary_arch)
-
- if (gradle.ios_primary_arch == "iosarm32")
- targetFromPreset(presets.iosArm32, gradle.ios_primary_arch)
-
- if (!gradle.ext.ios_one_sourceset) {
- targetFromPreset(presets.iosArm32, 'iosarm32')
- targetFromPreset(gradle.ios_secondary_arch == "iosx64" ? presets.iosX64 : presets.iosArm64, gradle.ios_secondary_arch)
}
jvm()
@@ -66,6 +54,7 @@ kotlin {
commonTest {
dependencies {
+ implementation kotlin('test')
implementation kotlin("test-common")
implementation kotlin("test-annotations-common")
}
@@ -77,6 +66,7 @@ kotlin {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-swing:$gradle.kotlinx_coroutines_version"
}
}
+
jvmTest {
dependsOn commonTest
dependencies {
@@ -84,52 +74,34 @@ kotlin {
implementation kotlin("test-junit")
}
}
+
jsMain {
dependencies {
implementation kotlin('stdlib-js')
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$gradle.kotlinx_coroutines_version"
}
}
+
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
- "${gradle.ios_primary_arch}Main" {
- kotlin.srcDirs("src/iosMain/kotlin")
- dependencies {
- // none for now
- }
+ iosMain {
+ dependsOn(commonMain)
}
- "${gradle.ios_primary_arch}Test" {
- kotlin.srcDirs("src/iosTest/kotlin")
- dependencies {
- implementation kotlin('test')
- }
+ iosTest {
+ dependsOn(commonTest)
}
-
- if (!gradle.ext.ios_one_sourceset) {
-
- iosarm32Main {
- kotlin.srcDirs("src/iosMain/kotlin")
+ gradle.ext.ios_targets.each {
+ "${it}Main" {
+ dependsOn(iosMain)
}
- iosarm32Test {
- kotlin.srcDirs("src/iosTest/kotlin")
- dependencies {
- implementation kotlin('test')
- }
- }
- "${gradle.ios_secondary_arch}Main" {
- kotlin.srcDirs("src/iosMain/kotlin")
- }
- "${gradle.ios_secondary_arch}Test" {
- kotlin.srcDirs("src/iosTest/kotlin")
- dependencies {
- implementation kotlin('test')
- }
+ "${it}Test" {
+ dependsOn(iosTest)
}
}
@@ -181,16 +153,20 @@ task printConfigurations {
}
}
-task copyResources(type: Copy) {
- from file('src/iosTest/res/.')
- into file("$buildDir/bin/iosx64/debugTest")
-}
-
afterEvaluate {
- if (tasks.getNames().contains("linkDebugTestIosx64"))
- tasks.named("linkDebugTestIosx64").configure {
- dependsOn copyResources
- }
+ gradle.ext.ios_targets.each { target ->
+ if (tasks.getNames().contains("linkDebugTest${target.capitalize()}"))
+ // creating copy task for the target
+ tasks.create(name: "copy${target.capitalize()}TestResources", type: Copy) {
+ from file('src/iosTest/resources/.')
+ into file("$buildDir/bin/$target/debugTest")
+ }
+
+ // apply copy task to the target
+ tasks.named("linkDebugTest${target.capitalize()}").configure {
+ dependsOn("copy${target.capitalize()}TestResources")
+ }
+ }
}
ktlint {
diff --git a/gradle/ext.gradle b/gradle/ext.gradle
index 9d595b745..06e77a09e 100644
--- a/gradle/ext.gradle
+++ b/gradle/ext.gradle
@@ -24,13 +24,13 @@ String kotlinVersion = getProperty("kaluga.kotlinVersion")
gradle.ext {
kotlin_version = kotlinVersion
kotlinx_coroutines_version = '1.6.0-native-mt'
- stately_version = '1.2.0'
- stately_isolate_version = '1.2.0'
- koin_version = '3.1.2'
+ stately_version = '1.2.1'
+ stately_isolate_version = '1.2.1'
+ koin_version = '3.2.0-beta-1'
serialization_version = '1.3.2'
- kydra_log_version = '1.1.6'
+ napier_version = '2.4.0'
android_ble_scanner_version = '1.5.0'
- library_version_base = '0.2.3'
+ library_version_base = '0.3.0'
library_version = libraryVersionLocalProperties ?: "$library_version_base${System.properties.kaluga_branch_postfix}"
android_min_sdk_version = 21
android_compile_sdk_version = 31
@@ -105,22 +105,32 @@ if (System.env.containsKey("EXAMPLE_MAVEN_REPO")) {
// based on https://github.com/Kotlin/xcode-compat/blob/d677a43edc46c50888bca0a7890a81f976a42809/xcode-compat/src/main/kotlin/org/jetbrains/kotlin/xcodecompat/XcodeCompatPlugin.kt#L16
def sdkName = System.getenv("SDK_NAME") ?: "unknown"
-if (sdkName.startsWith("iphoneos")) {
- gradle.ext.ios_primary_arch = "iosarm64"
- gradle.ext.ios_secondary_arch = "iosx64"
-}
-else {
- gradle.ext.ios_primary_arch = "iosx64"
- gradle.ext.ios_secondary_arch = "iosarm64"
+gradle.ext.is_real_ios_device = sdkName.startsWith("iphoneos")
+logger.lifecycle "Run on real ios device: $gradle.ext.is_real_ios_device from sdk: $sdkName"
+
+// Run on IntelliJ
+def ideaActive = System.getProperty("idea.active") == "true"
+gradle.ext.idea_active = ideaActive
+logger.lifecycle "Run on IntelliJ: $gradle.ext.idea_active"
+
+// Run on apple silicon
+def isAppleSilicon = System.getProperty("os.arch") == "aarch64"
+gradle.ext.is_apple_silicon = isAppleSilicon
+logger.lifecycle "Run on apple silicon: $gradle.ext.is_apple_silicon"
+
+def iosX64 = "iosX64"
+def iosArm64 = "iosArm64"
+def iosSimulatorArm64 = "iosSimulatorArm64"
+def allIosTargets = [iosX64, iosArm64, iosSimulatorArm64]
+if (!gradle.ext.idea_active) {
+ gradle.ext.ios_targets = allIosTargets
+} else {
+ if (gradle.ext.is_real_ios_device) {
+ gradle.ext.ios_targets = [iosArm64]
+ } else if (gradle.ext.is_apple_silicon) {
+ gradle.ext.ios_targets = [iosSimulatorArm64]
+ } else {
+ gradle.ext.ios_targets = [iosX64]
+ }
}
-
-println("Detected primary arch from sdk $sdkName: $gradle.ext.ios_primary_arch")
-
-
-// set global variable to decide how many iOS source sets to use. More than one sourceset can create problems for project dependencies and gives false errors in the IDE
-// currently the xcodecompat plugin also supports only arm64/iosx64 so this is off by default
-// it is automatically enabled when publishing, in order to generate multiple artifacts
-def envFlag = System.env.IOS_ONE_SOURCESET
-boolean isPublishTask = gradle.startParameter.taskNames.any { it.startsWith("publish") }
-gradle.ext.ios_one_sourceset = envFlag == null ? !isPublishTask : Boolean.parseBoolean(envFlag)
-println "Using a single iOS sourceset (env IOS_ONE_SOURCESET=$envFlag, isPublishTask=$isPublishTask): $gradle.ext.ios_one_sourceset"
+logger.lifecycle "Run on ios targets: $gradle.ext.ios_targets"
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 141c0f521..ab3e503f8 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -9,7 +9,7 @@ afterEvaluate {
release(MavenPublication) {
from components.release
- artifactId = module.name
+ artifactId = project.name
groupId = "com.splendo.kaluga"
version = gradle.ext.library_version
}
@@ -17,13 +17,13 @@ afterEvaluate {
debug(MavenPublication) {
from components.debug
- artifactId = module.name
+ artifactId = project.name
groupId = "com.splendo.kaluga"
version = gradle.ext.library_version
}
} else {
kotlinMultiplatform { publication ->
- artifactId = module.name
+ artifactId = project.name
groupId = "com.splendo.kaluga"
version = gradle.ext.library_version
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 29e413457..a0f7639f7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/logging/README.md b/logging/README.md
index 1fc711e57..670a096a0 100644
--- a/logging/README.md
+++ b/logging/README.md
@@ -1,6 +1,6 @@
## Logging
-This module provided simplified access to logging capabilities of [Kotlin Kydra Log](https://github.com/PocketByte/kotlin-kydra-log).
+This module provided simplified access to logging capabilities of [Kotlin Napier Log](https://github.com/AAkira/Napier).
## Installing
This library is available on Maven Central. You can import Kaluga Logging as follows:
@@ -19,10 +19,6 @@ dependencies {
### How to
-#### NOTE
-* Because of Kydra Log limitation logger can be initialized only once. All subsequent calls to initialize will return first logger used for initialization.
-* If `initLogger(logger: Logger)` was not called before any logging calls, then default logger will be instantiated.
-
#### Initialization
*Optionally* call `initLogger(logger: Logger): Logger` with instance of `Logger`.
diff --git a/logging/build.gradle.kts b/logging/build.gradle.kts
index f6148bffd..8a9f3e8fb 100644
--- a/logging/build.gradle.kts
+++ b/logging/build.gradle.kts
@@ -22,7 +22,7 @@ kotlin {
commonMain {
val ext = (gradle as ExtensionAware).extra
dependencies {
- implementation("ru.pocketbyte.kydra:kydra-log:${ext["kydra_log_version"]}")
+ implementation("io.github.aakira:napier:${ext["napier_version"]}")
implementation("co.touchlab:stately-concurrency:${ext["stately_version"]}")
}
}
diff --git a/logging/src/androidLibMain/kotlin/log.kt b/logging/src/androidLibMain/kotlin/log.kt
index 05a51d400..7768d926c 100644
--- a/logging/src/androidLibMain/kotlin/log.kt
+++ b/logging/src/androidLibMain/kotlin/log.kt
@@ -19,13 +19,13 @@
package com.splendo.kaluga.logging
import android.os.Build
-import ru.pocketbyte.kydra.log.AndroidLogger
+import io.github.aakira.napier.DebugAntilog
import java.lang.Integer.min
actual val defaultLogger: Logger =
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M)
TransformLogger(
- KydraLogger(AndroidLogger()),
+ NapierLogger(DebugAntilog()),
transformTag = { tag ->
tag?.let {
val maxSize = min(23, tag.length - 1)
@@ -38,6 +38,6 @@ actual val defaultLogger: Logger =
}
)
else
- KydraLogger(AndroidLogger())
+ NapierLogger(DebugAntilog())
actual var logger: Logger = defaultLogger
diff --git a/logging/src/commonMain/kotlin/NapierLogger.kt b/logging/src/commonMain/kotlin/NapierLogger.kt
new file mode 100644
index 000000000..9590c9432
--- /dev/null
+++ b/logging/src/commonMain/kotlin/NapierLogger.kt
@@ -0,0 +1,42 @@
+/*
+ Copyright 2020 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package com.splendo.kaluga.logging
+
+import kotlin.native.concurrent.SharedImmutable
+import com.splendo.kaluga.logging.LogLevel as KalugaLogLevel
+import io.github.aakira.napier.Antilog as NapierLog
+import io.github.aakira.napier.LogLevel as NapierLogLevel
+
+@SharedImmutable
+val logLevel = arrayOf(
+ NapierLogLevel.VERBOSE,
+ NapierLogLevel.DEBUG,
+ NapierLogLevel.INFO,
+ NapierLogLevel.WARNING,
+ NapierLogLevel.ERROR,
+ NapierLogLevel.ASSERT
+)
+
+fun KalugaLogLevel.logLevel(): NapierLogLevel {
+ return logLevel[this.ordinal]
+}
+
+class NapierLogger(val logger: NapierLog) : Logger {
+ override fun log(level: KalugaLogLevel, tag: String?, throwable: Throwable?, message: (() -> String)?) {
+ logger.log(priority = level.logLevel(), tag = tag, throwable = throwable, message = message?.invoke())
+ }
+}
diff --git a/logging/src/commonMain/kotlin/kydraLogger.kt b/logging/src/commonMain/kotlin/kydraLogger.kt
deleted file mode 100644
index c203f450c..000000000
--- a/logging/src/commonMain/kotlin/kydraLogger.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- Copyright 2020 Splendo Consulting B.V. The Netherlands
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- */
-package com.splendo.kaluga.logging
-
-import kotlin.native.concurrent.SharedImmutable
-
-@SharedImmutable
-val logLevel = arrayOf(
- ru.pocketbyte.kydra.log.LogLevel.DEBUG,
- ru.pocketbyte.kydra.log.LogLevel.INFO,
- ru.pocketbyte.kydra.log.LogLevel.WARNING,
- ru.pocketbyte.kydra.log.LogLevel.ERROR
-)
-
-fun LogLevel.logLevel(): ru.pocketbyte.kydra.log.LogLevel {
- return logLevel[this.ordinal]
- // optimization for:
- // return when(this) {
- // LogLevel.DEBUG -> ru.pocketbyte.kydra.log.LogLevel.DEBUG
- // LogLevel.INFO -> ru.pocketbyte.kydra.log.LogLevel.INFO
- // LogLevel.WARNING -> ru.pocketbyte.kydra.log.LogLevel.WARNING
- // LogLevel.ERROR -> ru.pocketbyte.kydra.log.LogLevel.ERROR
- // }
-}
-
-class KydraLogger(val kydraLogger: ru.pocketbyte.kydra.log.Logger) : Logger {
- override fun log(level: LogLevel, tag: String?, throwable: Throwable?, message: (() -> String)?) {
- if (message != null)
- kydraLogger.log(level.logLevel(), tag, message())
- if (throwable != null)
- kydraLogger.log(level.logLevel(), tag, throwable)
- }
-}
diff --git a/logging/src/iosMain/kotlin/log.kt b/logging/src/iosMain/kotlin/log.kt
index 581f576d1..d7707a47c 100644
--- a/logging/src/iosMain/kotlin/log.kt
+++ b/logging/src/iosMain/kotlin/log.kt
@@ -18,17 +18,11 @@
package com.splendo.kaluga.logging
import co.touchlab.stately.concurrency.value
-import ru.pocketbyte.kydra.log.NSLogger
+import io.github.aakira.napier.DebugAntilog
import kotlin.native.concurrent.SharedImmutable
@SharedImmutable
-actual val defaultLogger: Logger = TransformLogger(
- KydraLogger(NSLogger()),
- transformMessage = {
- // Since on iOS Kydra uses NSLog, the messages is formatted. % signs should therefore be escaped
- it?.replace("%", "%%")
- }
-)
+actual val defaultLogger: Logger = NapierLogger(DebugAntilog())
@SharedImmutable
private val _logger = co.touchlab.stately.concurrency.AtomicReference(defaultLogger)
diff --git a/logging/src/jsMain/kotlin/log.kt b/logging/src/jsMain/kotlin/log.kt
index c4f3cb02f..c0f515a18 100644
--- a/logging/src/jsMain/kotlin/log.kt
+++ b/logging/src/jsMain/kotlin/log.kt
@@ -17,7 +17,7 @@
package com.splendo.kaluga.logging
-import ru.pocketbyte.kydra.log.JsLogger
+import io.github.aakira.napier.DebugAntilog
-actual val defaultLogger: Logger = KydraLogger(JsLogger())
+actual val defaultLogger: Logger = NapierLogger(DebugAntilog())
actual var logger = defaultLogger
diff --git a/logging/src/jvmMain/kotlin/log.kt b/logging/src/jvmMain/kotlin/log.kt
index 5cbe7d111..b1b4e30e1 100644
--- a/logging/src/jvmMain/kotlin/log.kt
+++ b/logging/src/jvmMain/kotlin/log.kt
@@ -17,7 +17,7 @@
@file:JvmName("JvmLogKt")
package com.splendo.kaluga.logging
-import ru.pocketbyte.kydra.log.PrintLogger
+import io.github.aakira.napier.DebugAntilog
-actual val defaultLogger: Logger = KydraLogger(PrintLogger())
+actual val defaultLogger: Logger = NapierLogger(DebugAntilog())
actual var logger = defaultLogger
diff --git a/resources/src/iosTest/res/Localizable.stringsdict b/resources/src/iosTest/resources/Localizable.stringsdict
similarity index 74%
rename from resources/src/iosTest/res/Localizable.stringsdict
rename to resources/src/iosTest/resources/Localizable.stringsdict
index bcbe1526a..fa296406d 100644
--- a/resources/src/iosTest/res/Localizable.stringsdict
+++ b/resources/src/iosTest/resources/Localizable.stringsdict
@@ -12,16 +12,8 @@
NSStringPluralRuleType
NSStringFormatValueTypeKey
d
- zero
- %d hours
one
%d hour
- two
-
- few
-
- many
-
other
%d hours
diff --git a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockBluetoothGattWrapper.kt b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockBluetoothGattWrapper.kt
index d7ce4d651..a44cbfe36 100644
--- a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockBluetoothGattWrapper.kt
+++ b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockBluetoothGattWrapper.kt
@@ -31,6 +31,7 @@ class MockBluetoothGattWrapper : BluetoothGattWrapper {
val disconnectCompleted = EmptyCompletableDeferred()
val closeCompleted = EmptyCompletableDeferred()
val readRemoteRssiCompleted = EmptyCompletableDeferred()
+ val requestMtuCompleted = CompletableDeferred()
val readCharacteristicCompleted = CompletableDeferred()
val readDescriptorCompleted = CompletableDeferred()
val writeCharacteristicCompleted = CompletableDeferred()
@@ -60,6 +61,11 @@ class MockBluetoothGattWrapper : BluetoothGattWrapper {
return true
}
+ override fun requestMtu(mtu: Int): Boolean {
+ requestMtuCompleted.complete(mtu)
+ return true
+ }
+
override fun readCharacteristic(wrapper: CharacteristicWrapper): Boolean {
readCharacteristicCompleted.complete(wrapper)
return true
diff --git a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockCharacteristic.kt b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockCharacteristic.kt
index e050befe8..2f1808a4c 100644
--- a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockCharacteristic.kt
+++ b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockCharacteristic.kt
@@ -23,8 +23,9 @@ import java.util.UUID
class AndroidMockCharacteristicWrapper(
override val uuid: UUID = UUID.randomUUID(),
- descriptorUuids: List = emptyList(),
- override val service: ServiceWrapper
+ descriptorUUIDs: List = emptyList(),
+ override val service: ServiceWrapper,
+ override val properties: Int = 0
) : MockCharacteristicWrapper {
override var value: ByteArray? = null
@@ -32,11 +33,9 @@ class AndroidMockCharacteristicWrapper(
this.value = value
}
- override val descriptors: List = descriptorUuids.map { AndroidMockDescriptorWrapper(it, this) }
+ override val descriptors: List = descriptorUUIDs.map { AndroidMockDescriptorWrapper(it, this) }
override val permissions: Int
get() = 0
- override val properties: Int
- get() = 0
override var writeType = 0
override fun setValue(newValue: String): Boolean {
diff --git a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockServiceWrapper.kt b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockServiceWrapper.kt
index 8eb7a563d..1e24c31cb 100644
--- a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockServiceWrapper.kt
+++ b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/MockServiceWrapper.kt
@@ -25,11 +25,28 @@ import com.splendo.kaluga.bluetooth.DefaultGattServiceWrapper
import com.splendo.kaluga.bluetooth.ServiceWrapper
import java.util.UUID
-class MockServiceWrapper(override val uuid: UUID = UUID.randomUUID(), characteristicUuids: List>> = emptyList()) : ServiceWrapper {
+class MockServiceWrapper(
+ override val uuid: UUID = UUID.randomUUID(),
+ initialCharacteristics: List = emptyList()
+) : ServiceWrapper {
+
+ constructor(builder: ServiceWrapperBuilder) : this(
+ builder.uuid,
+ builder.characteristics
+ )
override val type: Int = 0
override val instanceId: Int = 0
- private val mutableCharacteristics = mutableListOf(*characteristicUuids.map { AndroidMockCharacteristicWrapper(it.first, it.second, this) }.toTypedArray())
+ private val mutableCharacteristics = mutableListOf(
+ *initialCharacteristics.map {
+ AndroidMockCharacteristicWrapper(
+ uuid = it.uuid,
+ descriptorUUIDs = it.descriptorUUIDs,
+ properties = it.properties,
+ service = this
+ )
+ }.toTypedArray()
+ )
override val characteristics: List
get() = mutableCharacteristics
private val mutableIncludedServices = mutableListOf()
diff --git a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/android_mocks.kt b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/android_mocks.kt
index 47974bb73..09e3ab0bf 100644
--- a/test-utils/src/androidLibMain/kotlin/mock/bluetooth/android_mocks.kt
+++ b/test-utils/src/androidLibMain/kotlin/mock/bluetooth/android_mocks.kt
@@ -19,8 +19,6 @@ package com.splendo.kaluga.test.mock.bluetooth
import android.bluetooth.BluetoothDevice
import com.splendo.kaluga.bluetooth.ServiceWrapper
-import com.splendo.kaluga.bluetooth.UUID
-import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
const val deviceName = "name"
@@ -31,12 +29,4 @@ actual fun createDeviceWrapper(
deviceName: String?
): DeviceWrapper = MockDeviceWrapper(deviceName, address, bondState)
-actual fun createServiceWrapper(
- stateRepo: DeviceStateFlowRepo,
- uuid: UUID,
- characteristics: List>>
-): ServiceWrapper =
- MockServiceWrapper(
- uuid,
- characteristics
- )
+actual fun ServiceWrapperBuilder.build(): ServiceWrapper = MockServiceWrapper(builder = this)
diff --git a/test-utils/src/androidLibUnitTest/kotlin/TestMockBluetoothGattWrapper.kt b/test-utils/src/androidLibUnitTest/kotlin/TestMockBluetoothGattWrapper.kt
new file mode 100644
index 000000000..1966b2381
--- /dev/null
+++ b/test-utils/src/androidLibUnitTest/kotlin/TestMockBluetoothGattWrapper.kt
@@ -0,0 +1,35 @@
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+package com.splendo.kaluga.test
+
+import com.splendo.kaluga.base.runBlocking
+import com.splendo.kaluga.test.mock.bluetooth.MockBluetoothGattWrapper
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class TestMockBluetoothGattWrapper {
+
+ @Test
+ fun testRequestMtu() = runBlocking {
+ val mtu = 54
+ val mock = MockBluetoothGattWrapper()
+ assertTrue(mock.requestMtu(mtu))
+ assertEquals(mtu, mock.requestMtuCompleted.await())
+ }
+}
diff --git a/test-utils/src/commonMain/kotlin/mock/bluetooth/MockDeviceInfoBuilder.kt b/test-utils/src/commonMain/kotlin/mock/bluetooth/MockDeviceInfoBuilder.kt
new file mode 100644
index 000000000..413f94d5f
--- /dev/null
+++ b/test-utils/src/commonMain/kotlin/mock/bluetooth/MockDeviceInfoBuilder.kt
@@ -0,0 +1,87 @@
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+package com.splendo.kaluga.test.mock.bluetooth
+
+import com.splendo.kaluga.bluetooth.UUID
+import com.splendo.kaluga.bluetooth.device.BaseDeviceConnectionManager
+import com.splendo.kaluga.bluetooth.device.ConnectionSettings
+import com.splendo.kaluga.bluetooth.device.Device
+import com.splendo.kaluga.bluetooth.device.DeviceInfoImpl
+import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
+import com.splendo.kaluga.bluetooth.device.DeviceWrapper
+import com.splendo.kaluga.bluetooth.uuidFrom
+import com.splendo.kaluga.test.mock.bluetooth.device.MockAdvertisementData
+import com.splendo.kaluga.test.mock.bluetooth.device.MockDeviceConnectionManager
+import kotlin.coroutines.CoroutineContext
+
+@MockBuilderDsl
+typealias ServiceUUIDsList = ArrayList
+
+fun ServiceUUIDsList.uuid(uuidString: String) = add(uuidFrom(uuidString))
+
+@MockBuilderDsl
+class MockDeviceInfoBuilder {
+
+ var deviceName: String? = null
+ var rssi: Int = 0
+ var manufacturerId: Int? = null
+ var manufacturerData: ByteArray? = null
+ private val _serviceUUIDs = ArrayList()
+ private val serviceUUIDs get() = _serviceUUIDs.toList()
+ var serviceData: Map = emptyMap()
+ var txPowerLevel: Int = Int.MIN_VALUE
+
+ fun services(builder: ServiceUUIDsList.() -> Unit) = builder(_serviceUUIDs)
+
+ fun build() = DeviceInfoImpl(
+ createDeviceWrapper(deviceName),
+ rssi,
+ MockAdvertisementData(
+ deviceName,
+ manufacturerId,
+ manufacturerData,
+ serviceUUIDs,
+ serviceData,
+ txPowerLevel
+ )
+ )
+}
+
+fun createMockDevice(
+ coroutineContext: CoroutineContext,
+ connectionSettings: ConnectionSettings = ConnectionSettings(
+ ConnectionSettings.ReconnectionSettings.Never
+ ),
+ connectionBuilder: BaseDeviceConnectionManager.Builder = object : BaseDeviceConnectionManager.Builder {
+ override fun create(
+ connectionSettings: ConnectionSettings,
+ deviceWrapper: DeviceWrapper,
+ stateRepo: DeviceStateFlowRepo
+ ) = MockDeviceConnectionManager(
+ connectionSettings,
+ deviceWrapper,
+ stateRepo
+ )
+ },
+ builder: MockDeviceInfoBuilder.() -> Unit
+) = Device(
+ connectionSettings = connectionSettings,
+ connectionBuilder = connectionBuilder,
+ initialDeviceInfo = MockDeviceInfoBuilder().apply(builder).build(),
+ coroutineContext = coroutineContext
+)
diff --git a/test-utils/src/commonMain/kotlin/mock/bluetooth/ServiceWrapperBuilder.kt b/test-utils/src/commonMain/kotlin/mock/bluetooth/ServiceWrapperBuilder.kt
new file mode 100644
index 000000000..0a80ab356
--- /dev/null
+++ b/test-utils/src/commonMain/kotlin/mock/bluetooth/ServiceWrapperBuilder.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 Splendo Consulting B.V. The Netherlands
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.splendo.kaluga.test.mock.bluetooth
+
+import com.splendo.kaluga.bluetooth.ServiceWrapper
+import com.splendo.kaluga.bluetooth.UUID
+import com.splendo.kaluga.bluetooth.randomUUID
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS)
+@DslMarker annotation class MockBuilderDsl
+
+@MockBuilderDsl
+typealias CharacteristicList = ArrayList
+
+fun CharacteristicList.characteristic(
+ builder: ServiceWrapperBuilder.Characteristic.Builder.() -> Unit
+) = add(
+ ServiceWrapperBuilder.Characteristic.Builder().apply(builder).build()
+)
+
+@MockBuilderDsl
+typealias DescriptorList = ArrayList
+
+fun DescriptorList.descriptor(uuid: UUID) = add(
+ ServiceWrapperBuilder.Descriptor(uuid)
+)
+
+@MockBuilderDsl
+class ServiceWrapperBuilder {
+
+ data class Descriptor(
+ val uuid: UUID = randomUUID()
+ )
+
+ data class Characteristic(
+ val uuid: UUID = randomUUID(),
+ val descriptors: List = listOf(Descriptor()),
+ val properties: Int = 0
+ ) {
+
+ @MockBuilderDsl
+ class Builder {
+ var uuid: UUID = randomUUID()
+ var properties: Int = 0
+ private var _descriptors = arrayListOf()
+ val descriptors: List get() = _descriptors.toList()
+
+ fun descriptors(builder: DescriptorList.() -> Unit) = builder(_descriptors)
+ fun build() = Characteristic(uuid, descriptors, properties)
+ }
+
+ val descriptorUUIDs get() = descriptors.map(Descriptor::uuid)
+ }
+
+ var uuid = randomUUID()
+ private val _characteristics = ArrayList()
+ val characteristics: List get() = _characteristics.toList()
+
+ fun characteristics(builder: CharacteristicList.() -> Unit) = builder(_characteristics)
+}
+
+expect fun ServiceWrapperBuilder.build(): ServiceWrapper
diff --git a/test-utils/src/commonMain/kotlin/mock/bluetooth/device/MockDeviceConnectionManager.kt b/test-utils/src/commonMain/kotlin/mock/bluetooth/device/MockDeviceConnectionManager.kt
index d3b881f52..71ffd2840 100644
--- a/test-utils/src/commonMain/kotlin/mock/bluetooth/device/MockDeviceConnectionManager.kt
+++ b/test-utils/src/commonMain/kotlin/mock/bluetooth/device/MockDeviceConnectionManager.kt
@@ -50,6 +50,7 @@ class MockDeviceConnectionManager(
val discoverServicesCompleted = AtomicReference(EmptyCompletableDeferred())
val disconnectCompleted = AtomicReference(EmptyCompletableDeferred())
val readRssiCompleted = AtomicReference(EmptyCompletableDeferred())
+ val requestMtuCompleted = AtomicReference(CompletableDeferred())
val performActionCompleted = AtomicReference(CompletableDeferred())
val performActionStarted = AtomicReference(CompletableDeferred())
private val _handledAction = MutableSharedFlow(replay = 16, onBufferOverflow = BufferOverflow.DROP_OLDEST)
@@ -82,6 +83,12 @@ class MockDeviceConnectionManager(
readRssiCompleted.get().complete()
}
+ override suspend fun requestMtu(mtu: Int): Boolean {
+ requestMtuCompleted.get().complete(mtu)
+ handleNewMtu(mtu)
+ return true
+ }
+
var waitAfterHandlingAction: MutableMap, EmptyCompletableDeferred> = sharedMutableMapOf()
override suspend fun performAction(action: DeviceAction) {
diff --git a/test-utils/src/commonMain/kotlin/mock/bluetooth/mocks.kt b/test-utils/src/commonMain/kotlin/mock/bluetooth/mocks.kt
index 3edc58c4e..d39465f7d 100644
--- a/test-utils/src/commonMain/kotlin/mock/bluetooth/mocks.kt
+++ b/test-utils/src/commonMain/kotlin/mock/bluetooth/mocks.kt
@@ -18,18 +18,13 @@
package com.splendo.kaluga.test.mock.bluetooth
import com.splendo.kaluga.bluetooth.ServiceWrapper
-import com.splendo.kaluga.bluetooth.UUID
-import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
-import com.splendo.kaluga.bluetooth.randomUUID
expect fun createDeviceWrapper(deviceName: String? = null): DeviceWrapper
-expect fun createServiceWrapper(
- stateRepo: DeviceStateFlowRepo,
- uuid: UUID = randomUUID(),
- characteristics: List>> = listOf(randomUUID() to listOf(randomUUID()))
-): ServiceWrapper
+fun createServiceWrapper(
+ builder: ServiceWrapperBuilder.() -> Unit
+): ServiceWrapper = ServiceWrapperBuilder().apply(builder).build()
interface CanUpdateMockValue {
fun updateMockValue(value: ByteArray?)
diff --git a/test-utils/src/commonTest/kotlin/KoinUIThreadViewModelTestTest.kt b/test-utils/src/commonTest/kotlin/KoinUIThreadViewModelTestTest.kt
index f3d19c1a9..1f88728ff 100644
--- a/test-utils/src/commonTest/kotlin/KoinUIThreadViewModelTestTest.kt
+++ b/test-utils/src/commonTest/kotlin/KoinUIThreadViewModelTestTest.kt
@@ -26,7 +26,6 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.logger.Level
import org.koin.dsl.module
-import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -60,7 +59,6 @@ class KoinUIThreadViewModelTestTest :
{ MyKoinViewModelTestContext() }
@Test
- @Ignore // Ignored due to a bug in Koin library using Kotlin version >= 1.6.0 https://github.com/InsertKoinIO/koin/issues/1188
fun testKoinViewModelTestContext() = testOnUIThread {
assertEquals("S", viewModel.s)
assertTrue(builder is MockAlertPresenter.Builder)
diff --git a/test-utils/src/commonTest/kotlin/MockDeviceConnectionManagerTest.kt b/test-utils/src/commonTest/kotlin/MockDeviceConnectionManagerTest.kt
new file mode 100644
index 000000000..f96dcf5cf
--- /dev/null
+++ b/test-utils/src/commonTest/kotlin/MockDeviceConnectionManagerTest.kt
@@ -0,0 +1,47 @@
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+import com.splendo.kaluga.base.runBlocking
+import com.splendo.kaluga.bluetooth.device.ConnectionSettings
+import com.splendo.kaluga.test.mock.bluetooth.createDeviceWrapper
+import com.splendo.kaluga.test.mock.bluetooth.createMockDevice
+import com.splendo.kaluga.test.mock.bluetooth.device.MockDeviceConnectionManager
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class MockDeviceConnectionManagerTest {
+
+ @Test
+ fun testRequestMtu() = runBlocking {
+ val device = createMockDevice(coroutineContext) {
+ deviceName = "foo"
+ }
+ val settings = ConnectionSettings(
+ ConnectionSettings.ReconnectionSettings.Never
+ )
+ val mock = MockDeviceConnectionManager(
+ connectionSettings = settings,
+ deviceWrapper = createDeviceWrapper("foo"),
+ stateRepo = device
+ )
+ assertEquals(-1, mock.mtu)
+ assertTrue(mock.requestMtu(42))
+ assertEquals(42, mock.requestMtuCompleted.get().await())
+ assertEquals(42, mock.mtu)
+ }
+}
diff --git a/test-utils/src/commonTest/kotlin/MockDeviceInfoBuilderTest.kt b/test-utils/src/commonTest/kotlin/MockDeviceInfoBuilderTest.kt
new file mode 100644
index 000000000..bbe4c751c
--- /dev/null
+++ b/test-utils/src/commonTest/kotlin/MockDeviceInfoBuilderTest.kt
@@ -0,0 +1,65 @@
+
+/*
+ Copyright 2022 Splendo Consulting B.V. The Netherlands
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+import com.splendo.kaluga.base.runBlocking
+import com.splendo.kaluga.bluetooth.uuidFrom
+import com.splendo.kaluga.test.mock.bluetooth.createMockDevice
+import com.splendo.kaluga.test.mock.bluetooth.uuid
+import kotlinx.coroutines.flow.first
+import kotlin.test.Test
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+
+class MockDeviceInfoBuilderTest {
+
+ @Test
+ fun testBuilder() = runBlocking {
+
+ val device = createMockDevice(coroutineContext) {
+ deviceName = "foo"
+ rssi = -43
+ manufacturerId = 0x1234
+ manufacturerData = byteArrayOf(1, 2, 3)
+ txPowerLevel = 10
+ serviceData = mapOf(
+ uuidFrom("180a") to byteArrayOf(4, 5, 6)
+ )
+ services {
+ uuid("180d")
+ }
+ }.first()
+
+ assertEquals("foo", device.name)
+ assertEquals(-43, device.rssi)
+ assertEquals(0x1234, device.advertisementData.manufacturerId)
+ assertContentEquals(
+ byteArrayOf(1, 2, 3),
+ device.advertisementData.manufacturerData
+ )
+ assertEquals(10, device.advertisementData.txPowerLevel)
+ assertContentEquals(
+ listOf(uuidFrom("180d")),
+ device.advertisementData.serviceUUIDs
+ )
+ assertEquals(1, device.advertisementData.serviceData.size)
+ assertContentEquals(
+ byteArrayOf(4, 5, 6),
+ device.advertisementData.serviceData[uuidFrom("180a")]
+ )
+ }
+}
diff --git a/test-utils/src/commonTest/kotlin/ServiceWrapperBuilderTest.kt b/test-utils/src/commonTest/kotlin/ServiceWrapperBuilderTest.kt
new file mode 100644
index 000000000..33cfbc554
--- /dev/null
+++ b/test-utils/src/commonTest/kotlin/ServiceWrapperBuilderTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 Splendo Consulting B.V. The Netherlands
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.splendo.kaluga.test
+
+import com.splendo.kaluga.bluetooth.uuidFrom
+import com.splendo.kaluga.test.mock.bluetooth.ServiceWrapperBuilder
+import com.splendo.kaluga.test.mock.bluetooth.characteristic
+import com.splendo.kaluga.test.mock.bluetooth.createServiceWrapper
+import com.splendo.kaluga.test.mock.bluetooth.descriptor
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ServiceWrapperBuilderTest {
+
+ @Test
+ fun testBuilder() {
+ val serviceUUID = uuidFrom("180A")
+ val characteristicUUID = uuidFrom("180B")
+ val descriptorUUID = uuidFrom("180C")
+
+ val builder: ServiceWrapperBuilder.() -> Unit = {
+ uuid = serviceUUID
+ characteristics {
+ characteristic {
+ uuid = characteristicUUID
+ properties = 0x42
+ descriptors {
+ descriptor(descriptorUUID)
+ }
+ }
+ }
+ }
+
+ val wrapper = createServiceWrapper(builder)
+
+ assertEquals(serviceUUID, wrapper.uuid)
+ assertEquals(expected = 1, wrapper.characteristics.size)
+ val characteristic = wrapper.characteristics.first()
+ assertEquals(characteristicUUID, characteristic.uuid)
+ assertEquals(expected = 0x42, characteristic.properties)
+ val descriptor = characteristic.descriptors.first()
+ assertEquals(descriptorUUID, descriptor.uuid)
+ }
+}
diff --git a/test-utils/src/iosMain/kotlin/mock/bluetooth/IOSMockCharacteristicWrapper.kt b/test-utils/src/iosMain/kotlin/mock/bluetooth/IOSMockCharacteristicWrapper.kt
index db2143573..8fc962a20 100644
--- a/test-utils/src/iosMain/kotlin/mock/bluetooth/IOSMockCharacteristicWrapper.kt
+++ b/test-utils/src/iosMain/kotlin/mock/bluetooth/IOSMockCharacteristicWrapper.kt
@@ -29,7 +29,8 @@ import platform.Foundation.NSData
class IOSMockCharacteristicWrapper(
override val uuid: CBUUID = CBUUID(),
- descriptorUuids: List = emptyList()
+ override val properties: Int = 0,
+ descriptorUUIDs: List = emptyList()
) : MockCharacteristicWrapper {
val isReadCompleted = EmptyCompletableDeferred()
@@ -41,11 +42,8 @@ class IOSMockCharacteristicWrapper(
get() = _value.get()
set(value) { _value.set(value) }
- override val descriptors: List = descriptorUuids.map {
- IOSMockDescriptorWrapper(
- it
- )
- }
+ override val descriptors: List = descriptorUUIDs
+ .map(::IOSMockDescriptorWrapper)
override fun readValue(peripheral: CBPeripheral) {
isReadCompleted.complete()
diff --git a/test-utils/src/iosMain/kotlin/mock/bluetooth/MockServiceWrapper.kt b/test-utils/src/iosMain/kotlin/mock/bluetooth/MockServiceWrapper.kt
index 0a4926d32..1da44316e 100644
--- a/test-utils/src/iosMain/kotlin/mock/bluetooth/MockServiceWrapper.kt
+++ b/test-utils/src/iosMain/kotlin/mock/bluetooth/MockServiceWrapper.kt
@@ -23,13 +23,16 @@ import com.splendo.kaluga.bluetooth.UUID
class MockServiceWrapper(
override val uuid: UUID = UUID(),
- characteristicUuids: List>> = emptyList()
+ override val characteristics: List = emptyList()
) : ServiceWrapper {
-
- override val characteristics: List = characteristicUuids.map {
- IOSMockCharacteristicWrapper(
- it.first,
- it.second
- )
- }
+ constructor(builder: ServiceWrapperBuilder) : this(
+ builder.uuid,
+ builder.characteristics.map {
+ IOSMockCharacteristicWrapper(
+ uuid = it.uuid,
+ properties = it.properties,
+ descriptorUUIDs = it.descriptorUUIDs
+ )
+ }
+ )
}
diff --git a/test-utils/src/iosMain/kotlin/mock/bluetooth/ios_mocks.kt b/test-utils/src/iosMain/kotlin/mock/bluetooth/ios_mocks.kt
index 5bd7de74f..6924652de 100644
--- a/test-utils/src/iosMain/kotlin/mock/bluetooth/ios_mocks.kt
+++ b/test-utils/src/iosMain/kotlin/mock/bluetooth/ios_mocks.kt
@@ -18,18 +18,10 @@
package com.splendo.kaluga.test.mock.bluetooth
import com.splendo.kaluga.bluetooth.ServiceWrapper
-import com.splendo.kaluga.bluetooth.UUID
-import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
-import platform.CoreBluetooth.CBUUID
actual fun createDeviceWrapper(
deviceName: String?
): DeviceWrapper = MockCBPeripheralWrapper(name = deviceName)
-actual fun createServiceWrapper(
- stateRepo: DeviceStateFlowRepo,
- uuid: UUID,
- characteristics: List>>
-): ServiceWrapper =
- MockServiceWrapper(uuid, characteristics)
+actual fun ServiceWrapperBuilder.build(): ServiceWrapper = MockServiceWrapper(builder = this)
diff --git a/test-utils/src/jsMain/kotlin/mock/bluetooth/js_mocks.kt b/test-utils/src/jsMain/kotlin/mock/bluetooth/js_mocks.kt
index 6f1d68a3b..0873f2141 100644
--- a/test-utils/src/jsMain/kotlin/mock/bluetooth/js_mocks.kt
+++ b/test-utils/src/jsMain/kotlin/mock/bluetooth/js_mocks.kt
@@ -18,16 +18,7 @@
package com.splendo.kaluga.test.mock.bluetooth
import com.splendo.kaluga.bluetooth.ServiceWrapper
-import com.splendo.kaluga.bluetooth.UUID
-import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
-actual fun createDeviceWrapper(deviceName: String?): DeviceWrapper {
- TODO()
-}
-
-actual fun createServiceWrapper(
- stateRepo: DeviceStateFlowRepo,
- uuid: UUID,
- characteristics: List>>
-): ServiceWrapper = TODO()
+actual fun createDeviceWrapper(deviceName: String?): DeviceWrapper = TODO()
+actual fun ServiceWrapperBuilder.build(): ServiceWrapper = TODO()
diff --git a/test-utils/src/jvmMain/kotlin/mock/bluetooth/jvm_mocks.kt b/test-utils/src/jvmMain/kotlin/mock/bluetooth/jvm_mocks.kt
index afbdc7bf8..e05cfd2e7 100644
--- a/test-utils/src/jvmMain/kotlin/mock/bluetooth/jvm_mocks.kt
+++ b/test-utils/src/jvmMain/kotlin/mock/bluetooth/jvm_mocks.kt
@@ -18,16 +18,10 @@
package com.splendo.kaluga.test.mock.bluetooth
import com.splendo.kaluga.bluetooth.ServiceWrapper
-import com.splendo.kaluga.bluetooth.UUID
-import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import com.splendo.kaluga.bluetooth.randomUUID
actual fun createDeviceWrapper(deviceName: String?): DeviceWrapper =
MockDeviceWrapper(deviceName, randomUUID())
-actual fun createServiceWrapper(
- stateRepo: DeviceStateFlowRepo,
- uuid: UUID,
- characteristics: List>>
-): ServiceWrapper = TODO()
+actual fun ServiceWrapperBuilder.build(): ServiceWrapper = TODO()