diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt index eab8e072..ac204f16 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt @@ -1,32 +1,68 @@ package no.nordicsemi.android.service +import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCharacteristic import android.content.Context import android.util.Log +import androidx.annotation.IntRange import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import no.nordicsemi.android.ble.BleManager +import no.nordicsemi.android.ble.callback.DataReceivedCallback +import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelDataCallback +import no.nordicsemi.android.ble.data.Data import java.util.* private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb") private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb") -abstract class BatteryManager( - context: Context, - protected val scope: CoroutineScope, -) : BleManager(context) { +abstract class BatteryManager(context: Context, protected val scope: CoroutineScope) : BleManager(context) { private val TAG = "BLE-MANAGER" private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null + private val batteryLevelDataCallback: DataReceivedCallback = + object : BatteryLevelDataCallback() { + override fun onBatteryLevelChanged( + device: BluetoothDevice, + @IntRange(from = 0, to = 100) batteryLevel: Int + ) { + onBatteryLevelChanged(batteryLevel) + } + override fun onInvalidDataReceived(device: BluetoothDevice, data: Data) { + log(Log.WARN, "Invalid Battery Level data received: $data") + } + } + protected abstract fun onBatteryLevelChanged(batteryLevel: Int) + + fun readBatteryLevelCharacteristic() { + if (isConnected) { + readCharacteristic(batteryLevelCharacteristic) + .with(batteryLevelDataCallback) + .fail { device: BluetoothDevice?, status: Int -> + log(Log.WARN, "Battery Level characteristic not found") + } + .enqueue() + } + } fun enableBatteryLevelCharacteristicNotifications() { if (isConnected) { - + // If the Battery Level characteristic is null, the request will be ignored + setNotificationCallback(batteryLevelCharacteristic) + .with(batteryLevelDataCallback) + enableNotifications(batteryLevelCharacteristic) + .done { device: BluetoothDevice? -> + log(Log.INFO, "Battery Level notifications enabled") + } + .fail { device: BluetoothDevice?, status: Int -> + log(Log.WARN, "Battery Level characteristic not found") + } + .enqueue() } } @@ -37,6 +73,7 @@ abstract class BatteryManager( protected abstract inner class BatteryManagerGattCallback : BleManagerGattCallback() { override fun initialize() { + readBatteryLevelCharacteristic() enableBatteryLevelCharacteristicNotifications() } @@ -48,12 +85,13 @@ abstract class BatteryManager( return batteryLevelCharacteristic != null } - override fun onServicesInvalidated() { + override fun onDeviceDisconnected() { batteryLevelCharacteristic = null + onBatteryLevelChanged(0) } } - fun releaseScope() { + fun release() { scope.cancel() } } diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt index 074f85cb..76343ab8 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt @@ -45,6 +45,7 @@ class ConnectionObserverAdapter : ConnectionObserver { } fun setValue(value: T) { - _status.tryEmit(SuccessResult(value)) + Log.d("AAATESTAAA", "setValue()") + _status.value = SuccessResult(value) } } diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/DeviceConnectingView.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceConnectingView.kt similarity index 95% rename from lib_theme/src/main/java/no/nordicsemi/android/theme/view/DeviceConnectingView.kt rename to lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceConnectingView.kt index cb498fa7..03a2e645 100644 --- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/DeviceConnectingView.kt +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceConnectingView.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.theme.view +package no.nordicsemi.android.theme.view.scanner import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -19,6 +19,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import no.nordicsemi.android.theme.R +import no.nordicsemi.android.theme.view.ScreenSection @Composable fun DeviceConnectingView() { diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceDisconnectedView.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceDisconnectedView.kt new file mode 100644 index 00000000..aef83bc1 --- /dev/null +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceDisconnectedView.kt @@ -0,0 +1,72 @@ +package no.nordicsemi.android.theme.view.scanner + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HighlightOff +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.theme.R +import no.nordicsemi.android.theme.view.ScreenSection + +enum class Reason { + USER, LINK_LOSS, MISSING_SERVICE +} + +@Composable +fun DeviceDisconnectedView(reason: Reason) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + ScreenSection { + Icon( + imageVector = Icons.Default.HighlightOff, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondary, + modifier = Modifier + .background( + color = MaterialTheme.colorScheme.secondary, + shape = CircleShape + ) + .padding(8.dp) + ) + + Spacer(modifier = Modifier.size(16.dp)) + + Text( + text = stringResource(id = R.string.device_disconnected), + style = MaterialTheme.typography.titleMedium + ) + + Spacer(modifier = Modifier.size(16.dp)) + + val text = when (reason) { + Reason.USER -> stringResource(id = R.string.device_reason_user) + Reason.LINK_LOSS -> stringResource(id = R.string.device_reason_link_loss) + Reason.MISSING_SERVICE -> stringResource(id = R.string.device_reason_missing_service) + } + + Text( + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } + } +} + +@Preview +@Composable +fun DeviceDisconnectedView_Preview() { + DeviceConnectingView() +} diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/NoDeviceView.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/NoDeviceView.kt new file mode 100644 index 00000000..cad618cd --- /dev/null +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/NoDeviceView.kt @@ -0,0 +1,74 @@ +package no.nordicsemi.android.theme.view.scanner + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HourglassTop +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.theme.R +import no.nordicsemi.android.theme.view.ScreenSection + +@Composable +fun NoDeviceView() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + ScreenSection { + Icon( + imageVector = Icons.Default.HourglassTop, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondary, + modifier = Modifier + .background( + color = MaterialTheme.colorScheme.secondary, + shape = CircleShape + ) + .padding(8.dp) + ) + + Spacer(modifier = Modifier.size(16.dp)) + + Text( + text = stringResource(id = R.string.device_connecting), + style = MaterialTheme.typography.titleMedium + ) + + Spacer(modifier = Modifier.size(16.dp)) + + Text( + text = stringResource(id = R.string.device_explanation), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + + Spacer(modifier = Modifier.size(16.dp)) + + Text( + text = stringResource(id = R.string.device_please_wait), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge + ) + } + } +} + +@Preview +@Composable +fun NoDeviceView_Preview() { + DeviceConnectingView() +} diff --git a/lib_theme/src/main/res/values/strings.xml b/lib_theme/src/main/res/values/strings.xml index dfccbcf1..a9b27fe4 100644 --- a/lib_theme/src/main/res/values/strings.xml +++ b/lib_theme/src/main/res/values/strings.xml @@ -11,6 +11,11 @@ DISCONNECT Battery + Disconnected + Device disconnected successfully. + Device signal has been lost. + Device was disconnected, because required services are missing. + Connecting The mobile is trying to connect to peripheral device. Please wait... diff --git a/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt b/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt index b10f575f..ca3239b8 100644 --- a/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt +++ b/lib_utils/src/main/java/no/nordicsemi/android/utils/Ext.kt @@ -2,9 +2,14 @@ package no.nordicsemi.android.utils import android.app.ActivityManager import android.content.Context +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import androidx.navigation.NavController +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import no.nordicsemi.android.navigation.ParcelableArgument import no.nordicsemi.android.navigation.SuccessDestinationResult import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice @@ -38,3 +43,12 @@ fun NavController.consumeResult(value: String): T? { ?.set(value, null) } } + +private val exceptionHandler = CoroutineExceptionHandler { _, t -> + Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t) +} + +fun CoroutineScope.launchWithCatch(block: suspend CoroutineScope.() -> Unit) = + launch(Job() + exceptionHandler) { + block() + } diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSRepository.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSRepository.kt index fe82569b..4f0ef555 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSRepository.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSRepository.kt @@ -1,73 +1,50 @@ package no.nordicsemi.android.bps.data -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import no.nordicsemi.android.ble.common.profile.bp.BloodPressureTypes -import no.nordicsemi.android.service.BleManagerStatus -import java.util.* +import android.bluetooth.BluetoothDevice +import android.content.Context +import android.util.Log +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import no.nordicsemi.android.ble.ktx.suspend +import no.nordicsemi.android.bps.repository.BPSManager +import no.nordicsemi.android.service.BleManagerResult import javax.inject.Inject -import javax.inject.Singleton -@Singleton -internal class BPSRepository @Inject constructor() { +@ViewModelScoped +internal class BPSRepository @Inject constructor( + @ApplicationContext + private val context: Context, +) { - private val _data = MutableStateFlow(BPSData()) - val data: StateFlow = _data + fun downloadData(device: BluetoothDevice): Flow> = callbackFlow { + val scope = this + val manager = BPSManager(context, scope) - private val _status = MutableStateFlow(BleManagerStatus.CONNECTING) - val status = _status.asStateFlow() + manager.dataHolder.status.onEach { + trySend(it) + }.launchIn(scope) - fun setIntermediateCuffPressure( - cuffPressure: Float, - unit: Int, - pulseRate: Float?, - userID: Int?, - status: BloodPressureTypes.BPMStatus?, - calendar: Calendar? - ) { - _data.tryEmit(_data.value.copy( - cuffPressure = cuffPressure, - unit = unit, - pulseRate = pulseRate, - userID = userID, - status = status, - calendar = calendar - )) - } + try { + manager.connect(device) + .useAutoConnect(false) + .retry(3, 100) + .suspend() + } catch (e: Exception) { + e.printStackTrace() + } - fun setBloodPressureMeasurement( - systolic: Float, - diastolic: Float, - meanArterialPressure: Float, - unit: Int, - pulseRate: Float?, - userID: Int?, - status: BloodPressureTypes.BPMStatus?, - calendar: Calendar? - ) { - _data.tryEmit(_data.value.copy( - systolic = systolic, - diastolic = diastolic, - meanArterialPressure = meanArterialPressure, - unit = unit, - pulseRate = pulseRate, - userID = userID, - status = status, - calendar = calendar - )) - } - - fun setBatteryLevel(batteryLevel: Int) { - _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel)) - } - - fun clear() { - _status.value = BleManagerStatus.CONNECTING - _data.tryEmit(BPSData()) - } - - fun setNewStatus(status: BleManagerStatus) { - _status.value = status + awaitClose { + launch { + manager.disconnect().suspend() + } + } } } diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/data/DataMapper.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/DataMapper.kt new file mode 100644 index 00000000..a7efb03a --- /dev/null +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/DataMapper.kt @@ -0,0 +1,31 @@ +package no.nordicsemi.android.bps.data + +import no.nordicsemi.android.ble.common.callback.bps.BloodPressureMeasurementResponse +import no.nordicsemi.android.ble.common.callback.bps.IntermediateCuffPressureResponse + +internal fun BPSData.copyWithNewResponse(response: IntermediateCuffPressureResponse): BPSData { + return with (response) { + copy( + cuffPressure = cuffPressure, + unit = unit, + pulseRate = pulseRate, + userID = userID, + status = status, + calendar = timestamp + ) + } +} + +internal fun BPSData.copyWithNewResponse(response: BloodPressureMeasurementResponse): BPSData { + return with (response) { + copy( + systolic = systolic, + diastolic = diastolic, + meanArterialPressure = meanArterialPressure, + unit = unit, + pulseRate = pulseRate, + userID = userID, + status = status, + ) + } +} diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt index b7b2623f..56856a7e 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt @@ -26,109 +26,104 @@ import android.bluetooth.BluetoothGattCharacteristic import android.content.Context import android.util.Log import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.android.scopes.ViewModelScoped -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch +import no.nordicsemi.android.ble.BleManager +import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.bps.BloodPressureMeasurementResponse import no.nordicsemi.android.ble.common.callback.bps.IntermediateCuffPressureResponse import no.nordicsemi.android.ble.ktx.asValidResponseFlow -import no.nordicsemi.android.ble.ktx.suspend -import no.nordicsemi.android.bps.data.BPSRepository -import no.nordicsemi.android.service.BatteryManager -import no.nordicsemi.android.service.CloseableCoroutineScope +import no.nordicsemi.android.bps.data.BPSData +import no.nordicsemi.android.bps.data.copyWithNewResponse +import no.nordicsemi.android.service.ConnectionObserverAdapter import java.util.* -import javax.inject.Inject val BPS_SERVICE_UUID: UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb") - private val BPM_CHARACTERISTIC_UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb") - private val ICP_CHARACTERISTIC_UUID = UUID.fromString("00002A36-0000-1000-8000-00805f9b34fb") -@ViewModelScoped -internal class BPSManager @Inject constructor( - @ApplicationContext context: Context, - private val dataHolder: BPSRepository -) : BatteryManager(context, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) { +private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb") +private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb") +internal class BPSManager( + @ApplicationContext context: Context, + private val scope: CoroutineScope +) : BleManager(context) { + + private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null private var bpmCharacteristic: BluetoothGattCharacteristic? = null private var icpCharacteristic: BluetoothGattCharacteristic? = null - private val exceptionHandler = CoroutineExceptionHandler { _, t-> - Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t) + private val data = MutableStateFlow(BPSData()) + val dataHolder = ConnectionObserverAdapter() + + init { + setConnectionObserver(dataHolder) + + data.onEach { + dataHolder.setValue(it) + }.launchIn(scope) } - override fun onBatteryLevelChanged(batteryLevel: Int) { - dataHolder.setBatteryLevel(batteryLevel) + override fun getMinLogPriority(): Int { + return Log.VERBOSE } - private inner class BloodPressureManagerGattCallback : BatteryManagerGattCallback() { + override fun log(priority: Int, message: String) { + Log.println(priority, "AAA", message) + } + override fun getGattCallback(): BleManagerGattCallback { + return BloodPressureManagerGattCallback() + } + + private inner class BloodPressureManagerGattCallback : BleManagerGattCallback() { + + @OptIn(ExperimentalCoroutinesApi::class) override fun initialize() { super.initialize() setNotificationCallback(icpCharacteristic).asValidResponseFlow() - .onEach { - dataHolder.setIntermediateCuffPressure( - it.cuffPressure, - it.unit, - it.pulseRate, - it.userID, - it.status, - it.timestamp - ) - }.launchIn(scope) + .onEach { data.tryEmit(data.value.copyWithNewResponse(it)) } + .launchIn(scope) setIndicationCallback(bpmCharacteristic).asValidResponseFlow() + .onEach { data.tryEmit(data.value.copyWithNewResponse(it)) } + .launchIn(scope) + + setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow() .onEach { - dataHolder.setBloodPressureMeasurement( - it.systolic, - it.diastolic, - it.meanArterialPressure, - it.unit, - it.pulseRate, - it.userID, - it.status, - it.timestamp - ) + data.value = data.value.copy(batteryLevel = it.batteryLevel) }.launchIn(scope) - scope.launch(exceptionHandler) { - enableNotifications(icpCharacteristic).suspend() - } - - scope.launch(exceptionHandler) { - enableIndications(bpmCharacteristic).suspend() - } + enableNotifications(icpCharacteristic).enqueue() + enableIndications(bpmCharacteristic).enqueue() + enableNotifications(batteryLevelCharacteristic).enqueue() } override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { - val service = gatt.getService(BPS_SERVICE_UUID) - if (service != null) { - bpmCharacteristic = service.getCharacteristic(BPM_CHARACTERISTIC_UUID) - icpCharacteristic = service.getCharacteristic(ICP_CHARACTERISTIC_UUID) + gatt.getService(BPS_SERVICE_UUID)?.run { + bpmCharacteristic = getCharacteristic(BPM_CHARACTERISTIC_UUID) + icpCharacteristic = getCharacteristic(ICP_CHARACTERISTIC_UUID) } - return bpmCharacteristic != null && icpCharacteristic != null + gatt.getService(BATTERY_SERVICE_UUID)?.run { + batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) + } + return bpmCharacteristic != null && batteryLevelCharacteristic != null } - override fun onServicesInvalidated() {} - override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean { super.isOptionalServiceSupported(gatt) // ignore the result of this return icpCharacteristic != null } - override fun onDeviceDisconnected() { + override fun onServicesInvalidated() { icpCharacteristic = null bpmCharacteristic = null + batteryLevelCharacteristic = null } } - - override fun getGattCallback(): BleManagerGattCallback { - return BloodPressureManagerGattCallback() - } } diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt index fe601318..8a1488fa 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt @@ -1,5 +1,6 @@ package no.nordicsemi.android.bps.view +import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -10,8 +11,12 @@ import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.bps.R import no.nordicsemi.android.bps.viewmodel.BPSViewModel +import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView +import no.nordicsemi.android.theme.view.scanner.DeviceConnectingView +import no.nordicsemi.android.theme.view.scanner.DeviceDisconnectedView +import no.nordicsemi.android.theme.view.scanner.NoDeviceView +import no.nordicsemi.android.theme.view.scanner.Reason import no.nordicsemi.android.utils.exhaustive @Composable @@ -24,10 +29,19 @@ fun BPSScreen() { viewModel.onEvent(DisconnectEvent) } + Log.d("AAATESTAAA", "state: $state") + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { - is DisplayDataState -> BPSContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() + NoDeviceState -> NoDeviceView() + is WorkingState -> when (state.result) { + is ConnectingResult -> DeviceConnectingView() + is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) + is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) + is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) + is ReadyResult -> DeviceConnectingView() + is SuccessResult -> BPSContentView(state.result.data) { viewModel.onEvent(it) } + } }.exhaustive } } diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSState.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSState.kt deleted file mode 100644 index b66ee096..00000000 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package no.nordicsemi.android.bps.view - -import no.nordicsemi.android.bps.data.BPSData - -internal sealed class BPSViewState - -internal object LoadingState : BPSViewState() - -internal data class DisplayDataState(val data: BPSData) : BPSViewState() diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewState.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewState.kt new file mode 100644 index 00000000..36826f5e --- /dev/null +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewState.kt @@ -0,0 +1,9 @@ +package no.nordicsemi.android.bps.view + +import no.nordicsemi.android.bps.data.BPSData +import no.nordicsemi.android.service.BleManagerResult + +internal sealed class BPSViewState + +internal data class WorkingState(val result: BleManagerResult) : BPSViewState() +internal object NoDeviceState : BPSViewState() diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt index a74ba16c..5c64d9e3 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt @@ -1,20 +1,16 @@ package no.nordicsemi.android.bps.viewmodel -import android.bluetooth.BluetoothDevice import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.bps.data.BPSRepository -import no.nordicsemi.android.bps.repository.BPSManager import no.nordicsemi.android.bps.repository.BPS_SERVICE_UUID -import no.nordicsemi.android.bps.view.BPSScreenViewEvent -import no.nordicsemi.android.bps.view.DisconnectEvent -import no.nordicsemi.android.bps.view.DisplayDataState -import no.nordicsemi.android.bps.view.LoadingState +import no.nordicsemi.android.bps.view.* import no.nordicsemi.android.navigation.* -import no.nordicsemi.android.service.BleManagerStatus -import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.utils.getDevice import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice @@ -23,18 +19,12 @@ import javax.inject.Inject @HiltViewModel internal class BPSViewModel @Inject constructor( - private val bpsManager: BPSManager, private val repository: BPSRepository, private val navigationManager: NavigationManager ) : ViewModel() { - val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } - }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) + private val _state = MutableStateFlow(NoDeviceState) + val state = _state.asStateFlow() init { navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(BPS_SERVICE_UUID)) @@ -44,29 +34,6 @@ internal class BPSViewModel @Inject constructor( handleArgs(it) } }.launchIn(viewModelScope) - - bpsManager.setConnectionObserver(object : ConnectionObserverAdapter() { - override fun onDeviceConnected(device: BluetoothDevice) { - super.onDeviceConnected(device) - repository.setNewStatus(BleManagerStatus.OK) - } - - override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) { - super.onDeviceFailedToConnect(device, reason) - repository.setNewStatus(BleManagerStatus.DISCONNECTED) - } - - override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) { - super.onDeviceDisconnected(device, reason) - repository.setNewStatus(BleManagerStatus.DISCONNECTED) - } - }) - - repository.status.onEach { - if (it == BleManagerStatus.DISCONNECTED) { - navigationManager.navigateUp() - } - }.launchIn(viewModelScope) } private fun handleArgs(args: DestinationResult) { @@ -78,28 +45,13 @@ internal class BPSViewModel @Inject constructor( fun onEvent(event: BPSScreenViewEvent) { when (event) { - DisconnectEvent -> onDisconnectButtonClick() + DisconnectEvent -> navigationManager.navigateUp() }.exhaustive } private fun connectDevice(device: DiscoveredBluetoothDevice) { - bpsManager.connect(device.device) - .useAutoConnect(false) - .retry(3, 100) - .enqueue() - } - - private fun onDisconnectButtonClick() { - if (bpsManager.isConnected) { - bpsManager.disconnect().enqueue() - } else { - repository.setNewStatus(BleManagerStatus.DISCONNECTED) - } - } - - override fun onCleared() { - super.onCleared() - repository.clear() - bpsManager.release() + repository.downloadData(device.device).onEach { + _state.value = WorkingState(it) + }.launchIn(viewModelScope) } } diff --git a/profile_bps/src/test/java/no/nordicsemi/android/bps/BPSViewModelTest.kt b/profile_bps/src/test/java/no/nordicsemi/android/bps/BPSViewModelTest.kt index 9d84cf8c..70e222cb 100644 --- a/profile_bps/src/test/java/no/nordicsemi/android/bps/BPSViewModelTest.kt +++ b/profile_bps/src/test/java/no/nordicsemi/android/bps/BPSViewModelTest.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain -import no.nordicsemi.android.bps.data.BPSRepository import no.nordicsemi.android.bps.repository.BPSManager import no.nordicsemi.android.bps.view.DisconnectEvent import no.nordicsemi.android.bps.viewmodel.BPSViewModel diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt index aa684bb8..2793e313 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.cgms.data.CGMRepository import no.nordicsemi.android.cgms.data.CGMServiceCommand -import no.nordicsemi.android.service.BleManagerStatus import no.nordicsemi.android.service.ForegroundBleService import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @@ -21,11 +20,11 @@ internal class CGMService : ForegroundBleService() { override fun onCreate() { super.onCreate() - status.onEach { - val status = it.mapToSimpleManagerStatus() - repository.setNewStatus(status) - stopIfDisconnected(status) - }.launchIn(scope) +// status.onEach { +// val status = it.mapToSimpleManagerStatus() +// repository.setNewStatus(status) +// stopIfDisconnected(status) +// }.launchIn(scope) repository.command.onEach { when (it) { diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt index c3c57397..88413141 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt @@ -11,8 +11,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.cgms.R import no.nordicsemi.android.cgms.viewmodel.CGMScreenViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView -import no.nordicsemi.android.utils.exhaustive @Composable fun CGMScreen() { @@ -25,10 +23,10 @@ fun CGMScreen() { } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state) { - is DisplayDataState -> CGMContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> CGMContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } } diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt index 5c0d3135..74c5367c 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt @@ -25,11 +25,11 @@ internal class CGMScreenViewModel @Inject constructor( ) : ViewModel() { val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt index 2e7c8878..c451cd5f 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt @@ -1,6 +1,5 @@ package no.nordicsemi.android.csc.data -import dagger.hilt.android.scopes.ServiceScoped import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.* import no.nordicsemi.android.csc.view.SpeedUnit @@ -8,7 +7,7 @@ import no.nordicsemi.android.service.BleManagerStatus import javax.inject.Inject import javax.inject.Singleton -@ServiceScoped +@Singleton internal class CSCRepository @Inject constructor() { private val _data = MutableStateFlow(CSCData()) diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt index 82165cd4..cd75aa8a 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt @@ -30,13 +30,10 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import no.nordicsemi.android.ble.BleManager import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.csc.CyclingSpeedAndCadenceMeasurementResponse import no.nordicsemi.android.ble.ktx.asValidResponseFlow @@ -58,7 +55,7 @@ internal class CSCRepo @Inject constructor( private val context: Context, ) { - suspend fun downloadData(device: BluetoothDevice) = callbackFlow> { + suspend fun downloadData(device: BluetoothDevice): Flow> = callbackFlow { val scope = CoroutineScope(coroutineContext) val manager = CSCManager(context, scope) @@ -84,9 +81,8 @@ internal class CSCRepo @Inject constructor( internal class CSCManager( context: Context, - scope: CoroutineScope, -// private val channel: SendChannel> -) : BatteryManager(context, scope) { + private val scope: CoroutineScope, +) : BleManager(context) { private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null private var cscMeasurementCharacteristic: BluetoothGattCharacteristic? = null @@ -97,7 +93,7 @@ internal class CSCManager( private val data = MutableStateFlow(CSCData()) val dataHolder = ConnectionObserverAdapter() - private val exceptionHandler = CoroutineExceptionHandler { context, t -> + private val exceptionHandler = CoroutineExceptionHandler { _, t -> Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t) } @@ -109,7 +105,7 @@ internal class CSCManager( }.launchIn(scope) } - override fun getGattCallback(): BatteryManagerGattCallback { + override fun getGattCallback(): BleManagerGattCallback { return CSCManagerGattCallback() } @@ -117,7 +113,7 @@ internal class CSCManager( wheelSize = value } - private inner class CSCManagerGattCallback : BatteryManagerGattCallback() { + private inner class CSCManagerGattCallback : BleManagerGattCallback() { override fun initialize() { super.initialize() @@ -128,14 +124,17 @@ internal class CSCManager( val totalDistance = it.getTotalDistance(wheelSize.value.toFloat()) val distance = it.getDistance(wheelCircumference, previousResponse) val speed = it.getSpeed(wheelCircumference, previousResponse) - - //todo - data.value.copy(totalDistance, ) - repository.setNewDistance(totalDistance, distance, speed, wheelSize) - val crankCadence = it.getCrankCadence(previousResponse) val gearRatio = it.getGearRatio(previousResponse) - repository.setNewCrankCadence(crankCadence, gearRatio, wheelSize) + + data.tryEmit(data.value.copy( + totalDistance = totalDistance, + distance = distance, + speed = speed, + wheelSize = wheelSize, + cadence = crankCadence, + gearRatio = gearRatio, + )) } previousResponse = it @@ -155,16 +154,16 @@ internal class CSCManager( } public override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { - val service = gatt.getService(CSC_SERVICE_UUID) - if (service != null) { - cscMeasurementCharacteristic = service.getCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID) - batteryLevelCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) + gatt.getService(CSC_SERVICE_UUID)?.run { + cscMeasurementCharacteristic = getCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID) } - return cscMeasurementCharacteristic != null + gatt.getService(BATTERY_SERVICE_UUID)?.run { + batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) + } + return cscMeasurementCharacteristic != null && batteryLevelCharacteristic != null } override fun onServicesInvalidated() { - super.onServicesInvalidated() cscMeasurementCharacteristic = null batteryLevelCharacteristic = null } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt index 6cf4b723..acd6a010 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt @@ -16,17 +16,11 @@ internal class CSCService : ForegroundBleService() { @Inject lateinit var repository: CSCRepository - override val manager: CSCManager by lazy { CSCManager(this, scope, repository) } + override val manager: CSCManager by lazy { CSCManager(this, scope) } override fun onCreate() { super.onCreate() - status.onEach { - val status = it.mapToSimpleManagerStatus() - repository.setNewStatus(status) - stopIfDisconnected(status) - }.launchIn(scope) - repository.command.onEach { when (it) { DisconnectCommand -> stopSelf() diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt index c880d74e..027b1095 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt @@ -11,8 +11,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.csc.R import no.nordicsemi.android.csc.viewmodel.CSCViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView -import no.nordicsemi.android.utils.exhaustive @Composable fun CSCScreen() { @@ -25,10 +23,10 @@ fun CSCScreen() { } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state) { - is DisplayDataState -> CSCContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> CSCContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt index ea15901e..0f9d6437 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt @@ -26,11 +26,11 @@ internal class CSCViewModel @Inject constructor( ) : ViewModel() { val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt index f34eb4a1..cef26cf3 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt @@ -6,8 +6,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView -import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.dfu.R import no.nordicsemi.dfu.viewmodel.DFUViewModel @@ -21,9 +19,9 @@ fun DFUScreen() { viewModel.onEvent(OnDisconnectButtonClick) } - when (state) { - is DisplayDataState -> DFUContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> DFUContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt index 2d300ab3..4e44e20e 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt @@ -37,11 +37,11 @@ internal class DFUViewModel @Inject constructor( ?.run { createInstallingStateWithNewStatus(state, status) } ?: state }.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/DataMapper.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/DataMapper.kt new file mode 100644 index 00000000..84d178db --- /dev/null +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/DataMapper.kt @@ -0,0 +1,43 @@ +package no.nordicsemi.android.gls.data + +import no.nordicsemi.android.ble.common.callback.glucose.GlucoseMeasurementContextResponse +import no.nordicsemi.android.ble.common.callback.glucose.GlucoseMeasurementResponse + +internal fun GlucoseMeasurementResponse.toRecord(): GLSRecord { + return this.let { + GLSRecord( + sequenceNumber = it.sequenceNumber, + time = it.time, + glucoseConcentration = it.glucoseConcentration ?: 0f, + unit = it.unit?.let { ConcentrationUnit.create(it) } + ?: ConcentrationUnit.UNIT_KGPL, + type = RecordType.createOrNull(it.type), + sampleLocation = SampleLocation.createOrNull(it.sampleLocation), + status = it.status + ) + } +} + +internal fun GlucoseMeasurementContextResponse.toMeasurementContext(): MeasurementContext { + return this.let { + MeasurementContext( + sequenceNumber = it.sequenceNumber, + carbohydrate = it.carbohydrate, + carbohydrateAmount = it.carbohydrateAmount ?: 0f, + meal = it.meal, + tester = it.tester, + health = it.health, + exerciseDuration = it.exerciseDuration ?: 0, + exerciseIntensity = it.exerciseIntensity ?: 0, + medication = it.medication, + medicationQuantity = it.medicationAmount ?: 0f, + medicationUnit = it.medicationUnit?.let { MedicationUnit.create(it) } + ?: MedicationUnit.UNIT_KG, + HbA1c = it.hbA1c ?: 0f + ) + } +} + +internal fun GLSRecord.copyWithNewContext(response: GlucoseMeasurementContextResponse): GLSRecord { + return copy(context = context) +} diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt similarity index 64% rename from profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt rename to profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt index e98eff16..92cf4768 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt @@ -19,29 +19,28 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package no.nordicsemi.android.gls.repository +package no.nordicsemi.android.gls.data import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCharacteristic import android.content.Context import android.util.Log import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch +import no.nordicsemi.android.ble.BleManager import no.nordicsemi.android.ble.common.callback.RecordAccessControlPointDataCallback import no.nordicsemi.android.ble.common.callback.RecordAccessControlPointResponse +import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.glucose.GlucoseMeasurementContextResponse import no.nordicsemi.android.ble.common.callback.glucose.GlucoseMeasurementResponse import no.nordicsemi.android.ble.common.data.RecordAccessControlPointData import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.suspend -import no.nordicsemi.android.gls.data.* -import no.nordicsemi.android.service.BatteryManager -import no.nordicsemi.android.service.CloseableCoroutineScope +import no.nordicsemi.android.service.ConnectionObserverAdapter +import no.nordicsemi.android.utils.launchWithCatch import java.util.* import javax.inject.Inject @@ -52,75 +51,58 @@ private val GM_CONTEXT_CHARACTERISTIC = UUID.fromString("00002A34-0000-1000-8000 private val GF_CHARACTERISTIC = UUID.fromString("00002A51-0000-1000-8000-00805f9b34fb") private val RACP_CHARACTERISTIC = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb") +private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb") +private val BATTERY_LEVEL_CHARACTERISTIC_UUID = + UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb") + internal class GLSManager @Inject constructor( - @ApplicationContext context: Context, - private val repository: GLSRepository -) : BatteryManager(context) { - - private val scope = CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + @ApplicationContext + context: Context, + private val scope: CoroutineScope +) : BleManager(context) { + private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null private var glucoseMeasurementCharacteristic: BluetoothGattCharacteristic? = null private var glucoseMeasurementContextCharacteristic: BluetoothGattCharacteristic? = null private var recordAccessControlPointCharacteristic: BluetoothGattCharacteristic? = null - private val exceptionHandler = CoroutineExceptionHandler { _, t-> - Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t) + private val data = MutableStateFlow(GLSData()) + val dataHolder = ConnectionObserverAdapter() + + init { + setConnectionObserver(dataHolder) + + data.onEach { + dataHolder.setValue(it) + }.launchIn(scope) } - override fun onBatteryLevelChanged(batteryLevel: Int) { - repository.setNewBatteryLevel(batteryLevel) - } - - override fun getGattCallback(): BatteryManagerGattCallback { + override fun getGattCallback(): BleManagerGattCallback { return GlucoseManagerGattCallback() } - private inner class GlucoseManagerGattCallback : BatteryManagerGattCallback() { + private inner class GlucoseManagerGattCallback : BleManagerGattCallback() { override fun initialize() { super.initialize() setNotificationCallback(glucoseMeasurementCharacteristic).asValidResponseFlow() - .onEach { - val record = GLSRecord( - sequenceNumber = it.sequenceNumber, - time = it.time, - glucoseConcentration = it.glucoseConcentration ?: 0f, - unit = it.unit?.let { ConcentrationUnit.create(it) } - ?: ConcentrationUnit.UNIT_KGPL, - type = RecordType.createOrNull(it.type), - sampleLocation = SampleLocation.createOrNull(it.sampleLocation), - status = it.status - ) - - repository.addNewRecord(record) - }.launchIn(scope) + .onEach { data.tryEmit(data.value.copy(records = data.value.records + it.toRecord())) } + .launchIn(scope) setNotificationCallback(glucoseMeasurementContextCharacteristic).asValidResponseFlow() .onEach { - val context = MeasurementContext( - sequenceNumber = it.sequenceNumber, - carbohydrate = it.carbohydrate, - carbohydrateAmount = it.carbohydrateAmount ?: 0f, - meal = it.meal, - tester = it.tester, - health = it.health, - exerciseDuration = it.exerciseDuration ?: 0, - exerciseIntensity = it.exerciseIntensity ?: 0, - medication = it.medication, - medicationQuantity = it.medicationAmount ?: 0f, - medicationUnit = it.medicationUnit?.let { MedicationUnit.create(it) } - ?: MedicationUnit.UNIT_KG, - HbA1c = it.hbA1c ?: 0f - ) - - repository.addNewContext(context) + val context = it.toMeasurementContext() + data.value.records.find { context.sequenceNumber == it.sequenceNumber }?.let { + it.context = context + } + data.tryEmit(data.value) }.launchIn(scope) setIndicationCallback(recordAccessControlPointCharacteristic).asValidResponseFlow() .onEach { if (it.isOperationCompleted && it.wereRecordsFound() && it.numberOfRecords > 0) { onNumberOfRecordsReceived(it) - } else if(it.isOperationCompleted && it.wereRecordsFound() && it.numberOfRecords == 0) { + } else if (it.isOperationCompleted && it.wereRecordsFound() && it.numberOfRecords == 0) { onRecordAccessOperationCompletedWithNoRecordsFound(it) } else if (it.isOperationCompleted && it.wereRecordsFound()) { onRecordAccessOperationCompleted(it) @@ -129,15 +111,15 @@ internal class GLSManager @Inject constructor( } }.launchIn(scope) - scope.launch(exceptionHandler) { - enableNotifications(glucoseMeasurementCharacteristic).suspend() - } - scope.launch(exceptionHandler) { - enableNotifications(glucoseMeasurementContextCharacteristic).suspend() - } - scope.launch(exceptionHandler) { - enableIndications(recordAccessControlPointCharacteristic).suspend() - } + setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow() + .onEach { + data.value = data.value.copy(batteryLevel = it.batteryLevel) + }.launchIn(scope) + + enableNotifications(glucoseMeasurementCharacteristic).enqueue() + enableNotifications(glucoseMeasurementContextCharacteristic).enqueue() + enableIndications(recordAccessControlPointCharacteristic).enqueue() + enableNotifications(batteryLevelCharacteristic).enqueue() } private fun onRecordAccessOperationCompleted(response: RecordAccessControlPointResponse) { @@ -145,21 +127,23 @@ internal class GLSManager @Inject constructor( RecordAccessControlPointDataCallback.RACP_OP_CODE_ABORT_OPERATION -> RequestStatus.ABORTED else -> RequestStatus.SUCCESS } - repository.setRequestStatus(status) + data.tryEmit(data.value.copy(requestStatus = status)) } private fun onRecordAccessOperationCompletedWithNoRecordsFound(response: RecordAccessControlPointResponse) { - repository.setRequestStatus(RequestStatus.SUCCESS) + data.tryEmit(data.value.copy(requestStatus = RequestStatus.SUCCESS)) } private suspend fun onNumberOfRecordsReceived(response: RecordAccessControlPointResponse) { if (response.numberOfRecords > 0) { - if (repository.records().isNotEmpty()) { - val sequenceNumber = repository.records() - .last().sequenceNumber + 1 //TODO check if correct + if (data.value.records.isNotEmpty()) { + val sequenceNumber = data.value.records + .last().sequenceNumber + 1 writeCharacteristic( recordAccessControlPointCharacteristic, - RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(sequenceNumber), + RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo( + sequenceNumber + ), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT ).suspend() } else { @@ -170,26 +154,28 @@ internal class GLSManager @Inject constructor( ).suspend() } } - repository.setRequestStatus(RequestStatus.SUCCESS) + data.tryEmit(data.value.copy(requestStatus = RequestStatus.SUCCESS)) } private fun onRecordAccessOperationError(response: RecordAccessControlPointResponse) { log(Log.WARN, "Record Access operation failed (error ${response.errorCode})") if (response.errorCode == RecordAccessControlPointDataCallback.RACP_ERROR_OP_CODE_NOT_SUPPORTED) { - repository.setRequestStatus(RequestStatus.NOT_SUPPORTED) + data.tryEmit(data.value.copy(requestStatus = RequestStatus.NOT_SUPPORTED)) } else { - repository.setRequestStatus(RequestStatus.FAILED) + data.tryEmit(data.value.copy(requestStatus = RequestStatus.FAILED)) } } public override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { - val service = gatt.getService(GLS_SERVICE_UUID) - if (service != null) { - glucoseMeasurementCharacteristic = service.getCharacteristic(GM_CHARACTERISTIC) - glucoseMeasurementContextCharacteristic = service.getCharacteristic(GM_CONTEXT_CHARACTERISTIC) - recordAccessControlPointCharacteristic = service.getCharacteristic(RACP_CHARACTERISTIC) + gatt.getService(GLS_SERVICE_UUID)?.run { + glucoseMeasurementCharacteristic = getCharacteristic(GM_CHARACTERISTIC) + glucoseMeasurementContextCharacteristic = getCharacteristic(GM_CONTEXT_CHARACTERISTIC) + recordAccessControlPointCharacteristic = getCharacteristic(RACP_CHARACTERISTIC) } - return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null && glucoseMeasurementContextCharacteristic != null + gatt.getService(BATTERY_SERVICE_UUID)?.run { + batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) + } + return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null && glucoseMeasurementContextCharacteristic != null && batteryLevelCharacteristic != null } override fun onServicesInvalidated() {} @@ -207,10 +193,10 @@ internal class GLSManager @Inject constructor( } private fun clear() { - repository.clearRecords() + data.tryEmit(data.value.copy(records = emptyList())) val target = bluetoothDevice if (target != null) { - repository.setRequestStatus(RequestStatus.SUCCESS) + data.tryEmit(data.value.copy(requestStatus = RequestStatus.SUCCESS)) } } @@ -218,8 +204,8 @@ internal class GLSManager @Inject constructor( if (recordAccessControlPointCharacteristic == null) return val target = bluetoothDevice ?: return clear() - repository.setRequestStatus(RequestStatus.PENDING) - scope.launch(exceptionHandler) { + data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING)) + scope.launchWithCatch { writeCharacteristic( recordAccessControlPointCharacteristic, RecordAccessControlPointData.reportLastStoredRecord(), @@ -231,8 +217,8 @@ internal class GLSManager @Inject constructor( fun requestFirstRecord() { if (recordAccessControlPointCharacteristic == null) return clear() - repository.setRequestStatus(RequestStatus.PENDING) - scope.launch(exceptionHandler) { + data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING)) + scope.launchWithCatch { writeCharacteristic( recordAccessControlPointCharacteristic, RecordAccessControlPointData.reportFirstStoredRecord(), @@ -244,8 +230,8 @@ internal class GLSManager @Inject constructor( fun requestAllRecords() { if (recordAccessControlPointCharacteristic == null) return clear() - repository.setRequestStatus(RequestStatus.PENDING) - scope.launch(exceptionHandler) { + data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING)) + scope.launchWithCatch { writeCharacteristic( recordAccessControlPointCharacteristic, RecordAccessControlPointData.reportNumberOfAllStoredRecords(), @@ -253,8 +239,4 @@ internal class GLSManager @Inject constructor( ).suspend() } } - - fun release() { - scope.close() - } } diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRepository.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRepository.kt index ed0651b1..80fcb5f2 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRepository.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRepository.kt @@ -1,55 +1,59 @@ package no.nordicsemi.android.gls.data -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import no.nordicsemi.android.service.BleManagerStatus +import android.bluetooth.BluetoothDevice +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import no.nordicsemi.android.ble.ktx.suspend +import no.nordicsemi.android.service.BleManagerResult +import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject -import javax.inject.Singleton -@Singleton -internal class GLSRepository @Inject constructor() { +@ViewModelScoped +internal class GLSRepository @Inject constructor( + @ApplicationContext + private val context: Context, +) { - private val _data = MutableStateFlow(GLSData()) - val data: StateFlow = _data.asStateFlow() + private var manager: GLSManager? = null - private val _status = MutableStateFlow(BleManagerStatus.CONNECTING) - val status = _status.asStateFlow() - - fun addNewRecord(record: GLSRecord) { - val newRecords = _data.value.records.toMutableList().apply { - add(record) + fun downloadData(device: BluetoothDevice): Flow> = callbackFlow { + val scope = this + val managerInstance = manager ?: GLSManager(context, scope).apply { + try { + connect(device) + .useAutoConnect(false) + .retry(3, 100) + .suspend() + } catch (e: Exception) { + e.printStackTrace() + } } - _data.tryEmit(_data.value.copy(records = newRecords)) - } + manager = managerInstance - fun addNewContext(context: MeasurementContext) { - _data.value.records.find { context.sequenceNumber == it.sequenceNumber }?.let { - it.context = context + managerInstance.dataHolder.status.onEach { + trySend(it) + }.launchIn(scope) + + awaitClose { + launch { + manager?.disconnect()?.suspend() + manager = null + } } - _data.tryEmit(_data.value) } - fun setRequestStatus(requestStatus: RequestStatus) { - _data.tryEmit(_data.value.copy(requestStatus = requestStatus)) - } - - fun records() = _data.value.records - - fun clearRecords() { - _data.tryEmit(_data.value.copy(records = emptyList())) - } - - fun setNewBatteryLevel(batteryLevel: Int) { - _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel)) - } - - fun setNewStatus(status: BleManagerStatus) { - _status.value = status - } - - fun clear() { - _status.value = BleManagerStatus.CONNECTING - _data.tryEmit(GLSData()) + fun requestMode(workingMode: WorkingMode) { + when (workingMode) { + WorkingMode.ALL -> manager?.requestAllRecords() + WorkingMode.LAST -> manager?.requestLastRecord() + WorkingMode.FIRST -> manager?.requestFirstRecord() + }.exhaustive } } diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/BPSState.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/BPSState.kt deleted file mode 100644 index b5726979..00000000 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/BPSState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package no.nordicsemi.android.gls.main.view - -import no.nordicsemi.android.gls.data.GLSData - -internal sealed class GLSViewState - -internal object LoadingState : GLSViewState() - -internal data class DisplayDataState(val data: GLSData) : GLSViewState() diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt index 47b91bd7..7f9d1d3b 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt @@ -1,5 +1,6 @@ package no.nordicsemi.android.gls.main.view +import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -10,8 +11,12 @@ import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.gls.R import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel +import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView +import no.nordicsemi.android.theme.view.scanner.DeviceConnectingView +import no.nordicsemi.android.theme.view.scanner.DeviceDisconnectedView +import no.nordicsemi.android.theme.view.scanner.NoDeviceView +import no.nordicsemi.android.theme.view.scanner.Reason import no.nordicsemi.android.utils.exhaustive @Composable @@ -24,10 +29,19 @@ fun GLSScreen() { viewModel.onEvent(DisconnectEvent) } + Log.d("AAATESTAAA", "state: $state") + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { - is DisplayDataState -> GLSContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() + NoDeviceState -> NoDeviceView() + is WorkingState -> when (state.result) { + is ConnectingResult -> DeviceConnectingView() + is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) + is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) + is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) + is ReadyResult -> DeviceConnectingView() + is SuccessResult -> GLSContentView(state.result.data) { viewModel.onEvent(it) } + } }.exhaustive } } diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSState.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSState.kt new file mode 100644 index 00000000..63839047 --- /dev/null +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSState.kt @@ -0,0 +1,9 @@ +package no.nordicsemi.android.gls.main.view + +import no.nordicsemi.android.gls.data.GLSData +import no.nordicsemi.android.service.BleManagerResult + +internal sealed class BPSViewState + +internal data class WorkingState(val result: BleManagerResult) : BPSViewState() +internal object NoDeviceState : BPSViewState() diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt index 181165ad..1ccca985 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt @@ -1,19 +1,14 @@ package no.nordicsemi.android.gls.main.viewmodel -import android.bluetooth.BluetoothDevice import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import no.nordicsemi.android.gls.GlsDetailsDestinationId import no.nordicsemi.android.gls.data.GLSRepository -import no.nordicsemi.android.gls.data.WorkingMode +import no.nordicsemi.android.gls.data.GLS_SERVICE_UUID import no.nordicsemi.android.gls.main.view.* -import no.nordicsemi.android.gls.repository.GLSManager -import no.nordicsemi.android.gls.repository.GLS_SERVICE_UUID import no.nordicsemi.android.navigation.* -import no.nordicsemi.android.service.BleManagerStatus -import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.utils.getDevice import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice @@ -22,18 +17,12 @@ import javax.inject.Inject @HiltViewModel internal class GLSViewModel @Inject constructor( - private val glsManager: GLSManager, private val repository: GLSRepository, private val navigationManager: NavigationManager ) : ViewModel() { - val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } - }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) + private val _state = MutableStateFlow(NoDeviceState) + val state = _state.asStateFlow() init { navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(GLS_SERVICE_UUID)) @@ -43,29 +32,6 @@ internal class GLSViewModel @Inject constructor( handleArgs(it) } }.launchIn(viewModelScope) - - glsManager.setConnectionObserver(object : ConnectionObserverAdapter() { - override fun onDeviceConnected(device: BluetoothDevice) { - super.onDeviceConnected(device) - repository.setNewStatus(BleManagerStatus.OK) - } - - override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) { - super.onDeviceFailedToConnect(device, reason) - repository.setNewStatus(BleManagerStatus.DISCONNECTED) - } - - override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) { - super.onDeviceDisconnected(device, reason) - repository.setNewStatus(BleManagerStatus.DISCONNECTED) - } - }) - - repository.status.onEach { - if (it == BleManagerStatus.DISCONNECTED) { - navigationManager.navigateUp() - } - }.launchIn(viewModelScope) } private fun handleArgs(args: DestinationResult) { @@ -77,38 +43,16 @@ internal class GLSViewModel @Inject constructor( fun onEvent(event: GLSScreenViewEvent) { when (event) { - DisconnectEvent -> disconnect() - is OnWorkingModeSelected -> requestData(event.workingMode) + DisconnectEvent -> navigationManager.navigateUp() + is OnWorkingModeSelected -> repository.requestMode(event.workingMode) is OnGLSRecordClick -> navigationManager.navigateTo(GlsDetailsDestinationId, AnyArgument(event.record)) + DisconnectEvent -> navigationManager.navigateUp() }.exhaustive } private fun connectDevice(device: DiscoveredBluetoothDevice) { - glsManager.connect(device.device) - .useAutoConnect(false) - .retry(3, 100) - .enqueue() - } - - private fun requestData(mode: WorkingMode) { - when (mode) { - WorkingMode.ALL -> glsManager.requestAllRecords() - WorkingMode.LAST -> glsManager.requestLastRecord() - WorkingMode.FIRST -> glsManager.requestFirstRecord() - }.exhaustive - } - - private fun disconnect() { - if (glsManager.isConnected) { - glsManager.disconnect().enqueue() - } else { - repository.setNewStatus(BleManagerStatus.DISCONNECTED) - } - } - - override fun onCleared() { - super.onCleared() - repository.clear() - glsManager.release() + repository.downloadData(device.device).onEach { + _state.value = WorkingState(it) + }.launchIn(viewModelScope) } } diff --git a/profile_hrs/build.gradle b/profile_hrs/build.gradle index c7950cf7..bc0e2727 100644 --- a/profile_hrs/build.gradle +++ b/profile_hrs/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation libs.chart implementation libs.nordic.ble.common + implementation libs.nordic.ble.ktx implementation libs.nordic.navigation implementation libs.nordic.ui.scanner diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt index 2907e2fc..ba9b5284 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt @@ -21,15 +21,18 @@ */ package no.nordicsemi.android.hrs.service -import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCharacteristic import android.content.Context -import android.util.Log -import androidx.annotation.IntRange -import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationDataCallback -import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementDataCallback -import no.nordicsemi.android.ble.common.profile.hr.BodySensorLocation +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationResponse +import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementResponse +import no.nordicsemi.android.ble.ktx.asValidResponseFlow +import no.nordicsemi.android.ble.ktx.suspend +import no.nordicsemi.android.ble.ktx.suspendForValidResponse import no.nordicsemi.android.hrs.data.HRSRepository import no.nordicsemi.android.service.BatteryManager import java.util.* @@ -38,40 +41,15 @@ val HRS_SERVICE_UUID: UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34 private val BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb") private val HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb") -/** - * HRSManager class performs BluetoothGatt operations for connection, service discovery, - * enabling notification and reading characteristics. - * All operations required to connect to device with BLE Heart Rate Service and reading - * heart rate values are performed here. - */ -internal class HRSManager(context: Context, private val dataHolder: HRSRepository) : BatteryManager(context) { +internal class HRSManager( + context: Context, + scope: CoroutineScope, + private val dataHolder: HRSRepository +) : BatteryManager(context, scope) { private var heartRateCharacteristic: BluetoothGattCharacteristic? = null private var bodySensorLocationCharacteristic: BluetoothGattCharacteristic? = null - private val bodySensorLocationDataCallback = object : BodySensorLocationDataCallback() { - - override fun onBodySensorLocationReceived( - device: BluetoothDevice, - @BodySensorLocation sensorLocation: Int - ) { - dataHolder.setSensorLocation(sensorLocation) - } - } - - private val heartRateMeasurementDataCallback = object : HeartRateMeasurementDataCallback() { - - override fun onHeartRateMeasurementReceived( - device: BluetoothDevice, - @IntRange(from = 0) heartRate: Int, - contactDetected: Boolean?, - @IntRange(from = 0) energyExpanded: Int?, - rrIntervals: List? - ) { - dataHolder.addNewHeartRate(heartRate) - } - } - override fun onBatteryLevelChanged(batteryLevel: Int) { dataHolder.setBatteryLevel(batteryLevel) } @@ -80,23 +58,25 @@ internal class HRSManager(context: Context, private val dataHolder: HRSRepositor return HeartRateManagerCallback() } - /** - * BluetoothGatt callbacks for connection/disconnection, service discovery, - * receiving notification, etc. - */ private inner class HeartRateManagerCallback : BatteryManagerGattCallback() { override fun initialize() { super.initialize() - readCharacteristic(bodySensorLocationCharacteristic) - .with(bodySensorLocationDataCallback) - .fail { device: BluetoothDevice?, status: Int -> - log(Log.WARN, "Body Sensor Location characteristic not found") - } - .enqueue() - setNotificationCallback(heartRateCharacteristic) - .with(heartRateMeasurementDataCallback) - enableNotifications(heartRateCharacteristic).enqueue() + scope.launch { + val data = readCharacteristic(bodySensorLocationCharacteristic) + .suspendForValidResponse() + + dataHolder.setSensorLocation(data.sensorLocation) + } + + setNotificationCallback(heartRateCharacteristic).asValidResponseFlow() + .onEach { + dataHolder.addNewHeartRate(it.heartRate) + }.launchIn(scope) + + scope.launch { + enableNotifications(heartRateCharacteristic).suspend() + } } override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { @@ -111,19 +91,14 @@ internal class HRSManager(context: Context, private val dataHolder: HRSRepositor super.isOptionalServiceSupported(gatt) val service = gatt.getService(HRS_SERVICE_UUID) if (service != null) { - bodySensorLocationCharacteristic = service.getCharacteristic( - BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID - ) + bodySensorLocationCharacteristic = service.getCharacteristic(BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID) } return bodySensorLocationCharacteristic != null } - override fun onDeviceDisconnected() { - super.onDeviceDisconnected() + override fun onServicesInvalidated() { bodySensorLocationCharacteristic = null heartRateCharacteristic = null } - - override fun onServicesInvalidated() {} } } diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt index e686240c..504272df 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt @@ -13,17 +13,11 @@ internal class HRSService : ForegroundBleService() { @Inject lateinit var repository: HRSRepository - override val manager: HRSManager by lazy { HRSManager(this, repository) } + override val manager: HRSManager by lazy { HRSManager(this, scope, repository) } override fun onCreate() { super.onCreate() - status.onEach { - val status = it.mapToSimpleManagerStatus() - repository.setNewStatus(status) - stopIfDisconnected(status) - }.launchIn(scope) - repository.command.onEach { stopSelf() }.launchIn(scope) diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt index c69d86fc..7e9393fb 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt @@ -11,8 +11,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.hrs.R import no.nordicsemi.android.hrs.viewmodel.HRSViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView -import no.nordicsemi.android.utils.exhaustive @Composable fun HRSScreen() { @@ -25,10 +23,10 @@ fun HRSScreen() { } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state) { - is DisplayDataState -> HRSContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> HRSContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } } diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt index 228cc955..58110b44 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt @@ -24,11 +24,11 @@ internal class HRSViewModel @Inject constructor( ) : ViewModel() { val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt index 34890245..90fd7cef 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt @@ -40,16 +40,11 @@ import java.util.* val HTS_SERVICE_UUID: UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb") private val HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb") -/** - * [HTSManager] class performs [BluetoothGatt] operations for connection, service discovery, - * enabling indication and reading characteristics. All operations required to connect to device - * with BLE HT Service and reading health thermometer values are performed here. - */ internal class HTSManager internal constructor( context: Context, - private val scope: CoroutineScope, + scope: CoroutineScope, private val dataHolder: HTSRepository -) : BatteryManager(context) { +) : BatteryManager(context, scope) { private var htCharacteristic: BluetoothGattCharacteristic? = null @@ -65,10 +60,6 @@ internal class HTSManager internal constructor( return HTManagerGattCallback() } - /** - * BluetoothGatt callbacks for connection/disconnection, service discovery, - * receiving indication, etc.. - */ private inner class HTManagerGattCallback : BatteryManagerGattCallback() { override fun initialize() { super.initialize() diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt index ccaae41e..6e01a750 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt @@ -18,12 +18,6 @@ internal class HTSService : ForegroundBleService() { override fun onCreate() { super.onCreate() - status.onEach { - val status = it.mapToSimpleManagerStatus() - repository.setNewStatus(status) - stopIfDisconnected(status) - }.launchIn(scope) - repository.command.onEach { stopSelf() }.launchIn(scope) diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt index ded13285..876aa523 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt @@ -11,8 +11,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.hts.R import no.nordicsemi.android.hts.viewmodel.HTSViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView -import no.nordicsemi.android.utils.exhaustive @Composable fun HTSScreen() { @@ -25,10 +23,10 @@ fun HTSScreen() { } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state) { - is DisplayDataState -> HTSContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> HTSContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } } diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt index cad40c64..68a67dac 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt @@ -24,11 +24,11 @@ internal class HTSViewModel @Inject constructor( ) : ViewModel() { val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt index dabaf4cd..1d420e6f 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt @@ -43,9 +43,9 @@ val ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-0 internal class PRXManager( context: Context, - private val scope: CoroutineScope, + scope: CoroutineScope, private val dataHolder: PRXRepository -) : BatteryManager(context) { +) : BatteryManager(context, scope) { private var alertLevelCharacteristic: BluetoothGattCharacteristic? = null private var linkLossCharacteristic: BluetoothGattCharacteristic? = null diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt index c05f324e..53761751 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt @@ -1,12 +1,9 @@ package no.nordicsemi.android.prx.repository -import android.util.Log import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.prx.data.* -import no.nordicsemi.android.service.BleManagerStatus -import no.nordicsemi.android.service.BleServiceStatus import no.nordicsemi.android.service.ForegroundBleService import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @@ -33,23 +30,23 @@ internal class PRXService : ForegroundBleService() { serverManager.open() - status.onEach { - val bleStatus = when (it) { - BleServiceStatus.CONNECTING -> BleManagerStatus.CONNECTING - BleServiceStatus.OK -> BleManagerStatus.OK - BleServiceStatus.DISCONNECTED -> { - scope.close() - stopSelf() - BleManagerStatus.DISCONNECTED - } - BleServiceStatus.LINK_LOSS -> null - }.exhaustive - bleStatus?.let { repository.setNewStatus(it) } - - if (BleServiceStatus.LINK_LOSS == it) { - repository.setLocalAlarmLevel(repository.data.value.linkLossAlarmLevel) - } - }.launchIn(scope) +// status.onEach { +// val bleStatus = when (it) { +// BleServiceStatus.CONNECTING -> BleManagerStatus.CONNECTING +// BleServiceStatus.OK -> BleManagerStatus.OK +// BleServiceStatus.DISCONNECTED -> { +// scope.close() +// stopSelf() +// BleManagerStatus.DISCONNECTED +// } +// BleServiceStatus.LINK_LOSS -> null +// }.exhaustive +// bleStatus?.let { repository.setNewStatus(it) } +// +// if (BleServiceStatus.LINK_LOSS == it) { +// repository.setLocalAlarmLevel(repository.data.value.linkLossAlarmLevel) +// } +// }.launchIn(scope) repository.command.onEach { when (it) { diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt index 68b18f62..b619b2d7 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt @@ -12,8 +12,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.prx.R import no.nordicsemi.android.prx.viewmodel.PRXViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView -import no.nordicsemi.android.utils.exhaustive @Composable fun PRXScreen() { @@ -26,10 +24,10 @@ fun PRXScreen() { } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state) { - is DisplayDataState -> ContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> ContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt index 9b1c7b2a..0af78647 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt @@ -27,11 +27,11 @@ internal class PRXViewModel @Inject constructor( ) : ViewModel() { val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt index 42451f36..ff2121bd 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSManager.kt @@ -42,9 +42,9 @@ private val RSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A53-0000 internal class RSCSManager internal constructor( context: Context, - private val scope: CoroutineScope, + scope: CoroutineScope, private val dataHolder: RSCSRepository -) : BatteryManager(context) { +) : BatteryManager(context, scope) { private var rscMeasurementCharacteristic: BluetoothGattCharacteristic? = null @@ -83,11 +83,6 @@ internal class RSCSManager internal constructor( } override fun onServicesInvalidated() { - - } - - override fun onDeviceDisconnected() { - super.onDeviceDisconnected() rscMeasurementCharacteristic = null } } diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt index 20a932a5..cda439fd 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt @@ -18,11 +18,11 @@ internal class RSCSService : ForegroundBleService() { override fun onCreate() { super.onCreate() - status.onEach { - val status = it.mapToSimpleManagerStatus() - repository.setNewStatus(status) - stopIfDisconnected(status) - }.launchIn(scope) +// status.onEach { +// val status = it.mapToSimpleManagerStatus() +// repository.setNewStatus(status) +// stopIfDisconnected(status) +// }.launchIn(scope) repository.command.onEach { stopSelf() diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt index 406bfa8e..0fa24c45 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt @@ -11,8 +11,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.rscs.R import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView -import no.nordicsemi.android.utils.exhaustive @Composable fun RSCSScreen() { @@ -25,10 +23,10 @@ fun RSCSScreen() { } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state) { - is DisplayDataState -> RSCSContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> RSCSContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } } diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt index 7a97e43a..5230c6b4 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt @@ -27,11 +27,11 @@ internal class RSCSViewModel @Inject constructor( ) : ViewModel() { val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt index fddde2a6..e45ad9b0 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt @@ -45,9 +45,9 @@ private val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0 internal class UARTManager( context: Context, - private val scope: CoroutineScope, + scope: CoroutineScope, private val dataHolder: UARTRepository -) : BatteryManager(context) { +) : BatteryManager(context, scope) { private var rxCharacteristic: BluetoothGattCharacteristic? = null private var txCharacteristic: BluetoothGattCharacteristic? = null diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt index a561add3..765b8bad 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt @@ -21,11 +21,11 @@ internal class UARTService : ForegroundBleService() { override fun onCreate() { super.onCreate() - status.onEach { - val status = it.mapToSimpleManagerStatus() - repository.setNewStatus(status) - stopIfDisconnected(status) - }.launchIn(scope) +// status.onEach { +// val status = it.mapToSimpleManagerStatus() +// repository.setNewStatus(status) +// stopIfDisconnected(status) +// }.launchIn(scope) repository.command.onEach { when (it) { diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt index 8d5208dc..3ecc390e 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt @@ -9,10 +9,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.theme.view.BackIconAppBar -import no.nordicsemi.android.theme.view.DeviceConnectingView import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.viewmodel.UARTViewModel -import no.nordicsemi.android.utils.exhaustive @Composable fun UARTScreen() { @@ -25,10 +23,10 @@ fun UARTScreen() { } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state) { - is DisplayDataState -> UARTContentView(state.data) { viewModel.onEvent(it) } - LoadingState -> DeviceConnectingView() - }.exhaustive +// when (state) { +// is DisplayDataState -> UARTContentView(state.data) { viewModel.onEvent(it) } +// LoadingState -> DeviceConnectingView() +// }.exhaustive } } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt index 816c314c..1b87f209 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt @@ -26,11 +26,11 @@ internal class UARTViewModel @Inject constructor( ) : ViewModel() { val state = repository.data.combine(repository.status) { data, status -> - when (status) { - BleManagerStatus.CONNECTING -> LoadingState - BleManagerStatus.OK, - BleManagerStatus.DISCONNECTED -> DisplayDataState(data) - } +// when (status) { +// BleManagerStatus.CONNECTING -> LoadingState +// BleManagerStatus.OK, +// BleManagerStatus.DISCONNECTED -> DisplayDataState(data) +// } }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { diff --git a/settings.gradle b/settings.gradle index bfe0b342..745384b1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,10 +13,14 @@ dependencyResolutionManagement { libs { alias('nordic-ble-common').to('no.nordicsemi.android:ble-common:2.4.0-beta01') alias('nordic-ble-ktx').to('no.nordicsemi.android:ble-ktx:2.4.0-beta01') - alias('nordic-log').to('no.nordicsemi.android:log:2.3.0') alias('nordic-scanner').to('no.nordicsemi.android.support.v18:scanner:1.6.0') alias('nordic-dfu').to('no.nordicsemi.android:dfu:1.12.1-beta01') + alias('nordic-log').to('no.nordicsemi.android:log-timber:2.3.0') + alias('timber-main').to('com.jakewharton.timber:timber:5.0.1') + alias('timber-arcao').to('com.arcao:slf4j-timber:3.1') + bundle('icons', ['nordic-log', 'timber-main', 'timber-arcao']) + version('commonlibraries', '1.0.1') alias('nordic-ui-scanner').to('no.nordicsemi.android.common', 'uiscanner').versionRef('commonlibraries') alias('nordic-navigation').to('no.nordicsemi.android.common', 'navigation').versionRef('commonlibraries')