diff --git a/app/build.gradle b/app/build.gradle index f9e6f36e..02f66864 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -88,6 +88,7 @@ dependencies { implementation libs.bundles.hilt kapt libs.bundles.hiltkapt + implementation libs.bundles.icons implementation libs.bundles.compose implementation libs.androidx.core implementation libs.material diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/AppDestination.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/AppDestination.kt index a4347e6d..fae7779a 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/AppDestination.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/AppDestination.kt @@ -8,6 +8,7 @@ import no.nordicsemi.android.hrs.view.HRSScreen import no.nordicsemi.android.hts.view.HTSScreen import no.nordicsemi.android.navigation.ComposeDestination import no.nordicsemi.android.navigation.ComposeDestinations +import no.nordicsemi.android.nrftoolbox.view.HomeScreen import no.nordicsemi.android.prx.view.PRXScreen import no.nordicsemi.android.rscs.view.RSCSScreen import no.nordicsemi.android.uart.view.UARTScreen diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt deleted file mode 100644 index 4bfcd046..00000000 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt +++ /dev/null @@ -1,16 +0,0 @@ -package no.nordicsemi.android.nrftoolbox - -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import no.nordicsemi.android.navigation.NavigationManager -import javax.inject.Inject - -@HiltViewModel -class HomeViewModel @Inject constructor( - private val navigationManager: NavigationManager -) : ViewModel() { - - fun openProfile(destination: ProfileDestination) { - navigationManager.navigateTo(destination.destination.id) - } -} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeatureButton.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/FeatureButton.kt similarity index 67% rename from app/src/main/java/no/nordicsemi/android/nrftoolbox/FeatureButton.kt rename to app/src/main/java/no/nordicsemi/android/nrftoolbox/view/FeatureButton.kt index 989d598d..811699e2 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeatureButton.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/FeatureButton.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.nrftoolbox +package no.nordicsemi.android.nrftoolbox.view import androidx.annotation.DrawableRes import androidx.annotation.StringRes @@ -6,19 +6,25 @@ import androidx.compose.foundation.Image 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.PlayArrow +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource 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.nrftoolbox.R import no.nordicsemi.android.theme.view.ScreenSection @Composable @@ -26,6 +32,7 @@ fun FeatureButton( @DrawableRes iconId: Int, @StringRes nameCode: Int, @StringRes name: Int, + isRunning: Boolean? = null, onClick: () -> Unit ) { ScreenSection(onClick = onClick) { @@ -57,15 +64,32 @@ fun FeatureButton( Spacer(modifier = Modifier.size(16.dp)) - Text( - text = stringResource(id = nameCode), - style = MaterialTheme.typography.headlineSmall, - textAlign = TextAlign.Center - ) +// Text( +// text = stringResource(id = nameCode), +// style = MaterialTheme.typography.headlineSmall, +// textAlign = TextAlign.Center +// ) + + isRunning?.let { + Icon( + painter = painterResource(id = R.drawable.ic_running_indicator), + contentDescription = stringResource(id = R.string.running_profile_icon), + tint = getRunningIndicatorColor(it) + ) + } } } } +@Composable +private fun getRunningIndicatorColor(isRunning: Boolean): Color { + return if (isRunning) { + colorResource(id = R.color.nordicGrass) + } else { + MaterialTheme.colorScheme.outline + } +} + @Preview @Composable private fun FeatureButtonPreview() { diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeView.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeView.kt similarity index 79% rename from app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeView.kt rename to app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeView.kt index cd32f050..4eebebee 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeView.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeView.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.nrftoolbox +package no.nordicsemi.android.nrftoolbox.view import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -6,17 +6,23 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import no.nordicsemi.android.nrftoolbox.BuildConfig +import no.nordicsemi.android.nrftoolbox.ProfileDestination +import no.nordicsemi.android.nrftoolbox.R +import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel import no.nordicsemi.android.theme.view.TitleAppBar @Composable fun HomeScreen() { val viewModel: HomeViewModel = hiltViewModel() + val state = viewModel.state.collectAsState().value Column { TitleAppBar(stringResource(id = R.string.app_name)) @@ -34,21 +40,15 @@ fun HomeScreen() { Spacer(modifier = Modifier.height(16.dp)) Text( - text = stringResource(id = R.string.bluetooth_services), + text = stringResource(id = R.string.viewmodel_profiles), style = MaterialTheme.typography.titleLarge, modifier = Modifier.fillMaxWidth(), ) Spacer(modifier = Modifier.height(16.dp)) - FeatureButton(R.drawable.ic_csc, R.string.csc_module, R.string.csc_module_full) { - viewModel.openProfile(ProfileDestination.CSC) - } - - Spacer(modifier = Modifier.height(16.dp)) - - FeatureButton(R.drawable.ic_hrs, R.string.hrs_module, R.string.hrs_module_full) { - viewModel.openProfile(ProfileDestination.HRS) + FeatureButton(R.drawable.ic_bps, R.string.bps_module, R.string.bps_module_full) { + viewModel.openProfile(ProfileDestination.BPS) } Spacer(modifier = Modifier.height(16.dp)) @@ -59,31 +59,45 @@ fun HomeScreen() { Spacer(modifier = Modifier.height(16.dp)) - FeatureButton(R.drawable.ic_hts, R.string.hts_module, R.string.hts_module_full) { + Text( + text = stringResource(id = R.string.service_profiles), + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(16.dp)) + + FeatureButton(R.drawable.ic_csc, R.string.csc_module, R.string.csc_module_full, state.isCSCModuleRunning) { + viewModel.openProfile(ProfileDestination.CSC) + } + + Spacer(modifier = Modifier.height(16.dp)) + + FeatureButton(R.drawable.ic_hrs, R.string.hrs_module, R.string.hrs_module_full, state.isHRSModuleRunning) { + viewModel.openProfile(ProfileDestination.HRS) + } + + Spacer(modifier = Modifier.height(16.dp)) + + FeatureButton(R.drawable.ic_hts, R.string.hts_module, R.string.hts_module_full, state.isHTSModuleRunning) { viewModel.openProfile(ProfileDestination.HTS) } Spacer(modifier = Modifier.height(16.dp)) - FeatureButton(R.drawable.ic_bps, R.string.bps_module, R.string.bps_module_full) { - viewModel.openProfile(ProfileDestination.BPS) - } - - Spacer(modifier = Modifier.height(16.dp)) - - FeatureButton(R.drawable.ic_rscs, R.string.rscs_module, R.string.rscs_module_full) { + FeatureButton(R.drawable.ic_rscs, R.string.rscs_module, R.string.rscs_module_full, state.isRSCSModuleRunning) { viewModel.openProfile(ProfileDestination.RSCS) } Spacer(modifier = Modifier.height(16.dp)) - FeatureButton(R.drawable.ic_prx, R.string.prx_module, R.string.prx_module_full) { + FeatureButton(R.drawable.ic_prx, R.string.prx_module, R.string.prx_module_full, state.isPRXModuleRunning) { viewModel.openProfile(ProfileDestination.PRX) } Spacer(modifier = Modifier.height(16.dp)) - FeatureButton(R.drawable.ic_cgm, R.string.cgm_module, R.string.cgm_module_full) { + FeatureButton(R.drawable.ic_cgm, R.string.cgm_module, R.string.cgm_module_full, state.isCGMModuleRunning) { viewModel.openProfile(ProfileDestination.CGMS) } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeViewState.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeViewState.kt new file mode 100644 index 00000000..d8b47492 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeViewState.kt @@ -0,0 +1,10 @@ +package no.nordicsemi.android.nrftoolbox.view + +data class HomeViewState( + val isCSCModuleRunning: Boolean = false, + val isHRSModuleRunning: Boolean = false, + val isHTSModuleRunning: Boolean = false, + val isRSCSModuleRunning: Boolean = false, + val isPRXModuleRunning: Boolean = false, + val isCGMModuleRunning: Boolean = false +) diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt new file mode 100644 index 00000000..3aaabb4a --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt @@ -0,0 +1,34 @@ +package no.nordicsemi.android.nrftoolbox.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import no.nordicsemi.android.cgms.data.CGMRepository +import no.nordicsemi.android.navigation.NavigationManager +import no.nordicsemi.android.nrftoolbox.ProfileDestination +import no.nordicsemi.android.nrftoolbox.view.HomeViewState +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val navigationManager: NavigationManager, + private val cgmRepository: CGMRepository +) : ViewModel() { + + private val _state = MutableStateFlow(HomeViewState()) + val state = _state.asStateFlow() + + init { + cgmRepository.isRunning.onEach { + _state.value = _state.value.copy(isCGMModuleRunning = it) + }.launchIn(viewModelScope) + } + + fun openProfile(destination: ProfileDestination) { + navigationManager.navigateTo(destination.destination.id) + } +} diff --git a/app/src/main/res/drawable/ic_running_indicator.xml b/app/src/main/res/drawable/ic_running_indicator.xml new file mode 100644 index 00000000..9aa87408 --- /dev/null +++ b/app/src/main/res/drawable/ic_running_indicator.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b55da1f..b531a5a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,9 @@ DFU Available in separate application. - Bluetooth services + ViewModel profiles + Service profiles Utils services + + Icon indicating if the profile is running diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceConnectingView.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceConnectingView.kt index 03a2e645..c83e2a98 100644 --- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceConnectingView.kt +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/scanner/DeviceConnectingView.kt @@ -9,10 +9,12 @@ 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.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -22,11 +24,12 @@ import no.nordicsemi.android.theme.R import no.nordicsemi.android.theme.view.ScreenSection @Composable -fun DeviceConnectingView() { +fun DeviceConnectingView(navigateUp: () -> Unit) { Column( modifier = Modifier .fillMaxSize() - .padding(16.dp) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { ScreenSection { Icon( @@ -64,11 +67,17 @@ fun DeviceConnectingView() { style = MaterialTheme.typography.titleLarge ) } + + Spacer(modifier = Modifier.size(16.dp)) + + Button(onClick = { navigateUp() }) { + Text(text = stringResource(id = R.string.disconnect)) + } } } @Preview @Composable fun DeviceConnectingView_Preview() { - DeviceConnectingView() + 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 index aef83bc1..577aac1c 100644 --- 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 @@ -5,10 +5,12 @@ 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.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -22,11 +24,12 @@ enum class Reason { } @Composable -fun DeviceDisconnectedView(reason: Reason) { +fun DeviceDisconnectedView(reason: Reason, navigateUp: () -> Unit) { Column( modifier = Modifier .fillMaxSize() - .padding(16.dp) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { ScreenSection { Icon( @@ -62,11 +65,17 @@ fun DeviceDisconnectedView(reason: Reason) { style = MaterialTheme.typography.bodyMedium ) } + + Spacer(modifier = Modifier.size(16.dp)) + + Button(onClick = { navigateUp() }) { + Text(text = stringResource(id = R.string.go_up)) + } } } @Preview @Composable fun DeviceDisconnectedView_Preview() { - DeviceConnectingView() + 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 index cad618cd..cd027073 100644 --- 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 @@ -70,5 +70,5 @@ fun NoDeviceView() { @Preview @Composable fun NoDeviceView_Preview() { - DeviceConnectingView() + DeviceConnectingView { } } diff --git a/lib_theme/src/main/res/values/strings.xml b/lib_theme/src/main/res/values/strings.xml index a9b27fe4..cee533f9 100644 --- a/lib_theme/src/main/res/values/strings.xml +++ b/lib_theme/src/main/res/values/strings.xml @@ -5,10 +5,12 @@ Dialog CANCEL + Go up + Close the application. Close the current screen. - DISCONNECT + Disconnect Battery Disconnected 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 63400a73..850b75ea 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 @@ -24,6 +24,8 @@ fun BPSScreen() { val state = viewModel.state.collectAsState().value Column { + val navigateUp = { viewModel.onEvent(DisconnectEvent) } + BackIconAppBar(stringResource(id = R.string.bps_title)) { viewModel.onEvent(DisconnectEvent) } @@ -32,11 +34,11 @@ fun BPSScreen() { when (state) { 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 ConnectingResult, + is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) + is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) + is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is SuccessResult -> BPSContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt index bf20a1c6..bed4fef6 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMRepository.kt @@ -2,24 +2,25 @@ package no.nordicsemi.android.cgms.data import android.bluetooth.BluetoothDevice import android.content.Context -import android.util.Log import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow 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.cgms.repository.CGMManager import no.nordicsemi.android.cgms.repository.CGMService import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.ConnectingResult import no.nordicsemi.android.service.ServiceManager -import no.nordicsemi.android.utils.exhaustive +import java.lang.Exception import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class CGMRepository @Inject constructor( +class CGMRepository @Inject constructor( @ApplicationContext private val context: Context, private val serviceManager: ServiceManager, @@ -27,38 +28,55 @@ internal class CGMRepository @Inject constructor( private var manager: CGMManager? = null private val _data = MutableStateFlow>(ConnectingResult()) - val data = _data.asStateFlow() + internal val data = _data.asStateFlow() + + private val _isRunning = MutableStateFlow(false) + val isRunning = _isRunning.asStateFlow() fun launch(device: BluetoothDevice) { serviceManager.startService(CGMService::class.java, device) } - fun startManager(device: BluetoothDevice, scope: CoroutineScope) { + fun start(device: BluetoothDevice, scope: CoroutineScope) { val manager = CGMManager(context, scope) manager.dataHolder.status.onEach { _data.value = it - Log.d("AAATESTAAA", "data: $it") }.launchIn(scope) - manager.connect(device) - .useAutoConnect(false) - .retry(3, 100) - .enqueue() + scope.launch { + manager.start(device) + } } - fun sendNewServiceCommand(workingMode: CGMServiceCommand) { - when (workingMode) { - CGMServiceCommand.REQUEST_ALL_RECORDS -> manager?.requestAllRecords() - CGMServiceCommand.REQUEST_LAST_RECORD -> manager?.requestLastRecord() - CGMServiceCommand.REQUEST_FIRST_RECORD -> manager?.requestFirstRecord() - CGMServiceCommand.DISCONNECT -> release() - }.exhaustive + private suspend fun CGMManager.start(device: BluetoothDevice) { + try { + connect(device) + .useAutoConnect(false) + .retry(3, 100) + .suspend() + _isRunning.value = true + } catch (e: Exception) { + e.printStackTrace() + } } - private fun release() { + fun requestAllRecords() { + manager?.requestAllRecords() + } + + fun requestLastRecord() { + manager?.requestLastRecord() + } + + fun requestFirstRecord() { + manager?.requestFirstRecord() + } + + fun release() { serviceManager.stopService(CGMService::class.java) manager?.disconnect()?.enqueue() manager = null + _isRunning.value = false } } diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt index c22818a3..50779e2f 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.ble.BleManager import no.nordicsemi.android.ble.common.callback.RecordAccessControlPointResponse +import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.cgm.CGMFeatureResponse import no.nordicsemi.android.ble.common.callback.cgm.CGMSpecificOpsControlPointResponse import no.nordicsemi.android.ble.common.callback.cgm.CGMStatusResponse @@ -98,6 +99,11 @@ internal class CGMManager( return CGMManagerGattCallback() } + override fun log(priority: Int, message: String) { + super.log(priority, message) + Log.d("COROUTINE-EXCEPTION", message) + } + private inner class CGMManagerGattCallback : BleManagerGattCallback() { override fun initialize() { super.initialize() @@ -169,6 +175,11 @@ internal class CGMManager( } }.launchIn(scope) + setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow() + .onEach { + data.value = data.value.copy(batteryLevel = it.batteryLevel) + }.launchIn(scope) + enableNotifications(cgmMeasurementCharacteristic).enqueue() enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue() enableIndications(recordAccessControlPointCharacteristic).enqueue() @@ -191,7 +202,9 @@ internal class CGMManager( val sequenceNumber = records.keyAt(records.size() - 1) + 1 writeCharacteristic( recordAccessControlPointCharacteristic, - RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(sequenceNumber), + RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo( + sequenceNumber + ), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT ).suspend() } else { @@ -232,28 +245,31 @@ internal class CGMManager( } override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { - val service = gatt.getService(CGMS_SERVICE_UUID) - if (service != null) { - cgmStatusCharacteristic = service.getCharacteristic(CGM_STATUS_UUID) - cgmFeatureCharacteristic = service.getCharacteristic(CGM_FEATURE_UUID) - cgmMeasurementCharacteristic = service.getCharacteristic(CGM_MEASUREMENT_UUID) - cgmSpecificOpsControlPointCharacteristic = service.getCharacteristic( - CGM_OPS_CONTROL_POINT_UUID - ) - recordAccessControlPointCharacteristic = service.getCharacteristic(RACP_UUID) + gatt.getService(CGMS_SERVICE_UUID)?.run { + cgmStatusCharacteristic = getCharacteristic(CGM_STATUS_UUID) + cgmFeatureCharacteristic = getCharacteristic(CGM_FEATURE_UUID) + cgmMeasurementCharacteristic = getCharacteristic(CGM_MEASUREMENT_UUID) + cgmSpecificOpsControlPointCharacteristic = getCharacteristic(CGM_OPS_CONTROL_POINT_UUID) + recordAccessControlPointCharacteristic = getCharacteristic(RACP_UUID) } - return cgmMeasurementCharacteristic != null && cgmSpecificOpsControlPointCharacteristic != null && recordAccessControlPointCharacteristic != null && cgmStatusCharacteristic != null && cgmFeatureCharacteristic != null + gatt.getService(BATTERY_SERVICE_UUID)?.run { + batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) + } + return batteryLevelCharacteristic != null + && cgmMeasurementCharacteristic != null + && cgmSpecificOpsControlPointCharacteristic != null + && recordAccessControlPointCharacteristic != null + && cgmStatusCharacteristic != null + && cgmFeatureCharacteristic != null } - override fun onServicesInvalidated() {} - - override fun onDeviceDisconnected() { - super.onDeviceDisconnected() + override fun onServicesInvalidated() { cgmStatusCharacteristic = null cgmFeatureCharacteristic = null cgmMeasurementCharacteristic = null cgmSpecificOpsControlPointCharacteristic = null recordAccessControlPointCharacteristic = null + batteryLevelCharacteristic = null } } 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 74eed617..73339fc2 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 @@ -20,7 +20,7 @@ internal class CGMService : NotificationService() { val device = intent!!.getParcelableExtra(DEVICE_DATA)!! - repository.startManager(device, lifecycleScope) + repository.start(device, lifecycleScope) return START_REDELIVER_INTENT } 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 ef18c059..048dd298 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 @@ -24,19 +24,19 @@ fun CGMScreen() { val state = viewModel.state.collectAsState().value Column { - BackIconAppBar(stringResource(id = R.string.cgms_title)) { - viewModel.onEvent(DisconnectEvent) - } + val navigateUp = { viewModel.onEvent(NavigateUp) } + + BackIconAppBar(stringResource(id = R.string.cgms_title), navigateUp) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { 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 ConnectingResult, + is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) + is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) + is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is SuccessResult -> CGMContentView(state.result.data) { viewModel.onEvent(it) } } }.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 cb9f3b7e..a5be007e 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 @@ -1,6 +1,5 @@ package no.nordicsemi.android.cgms.viewmodel -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -29,6 +28,24 @@ internal class CGMScreenViewModel @Inject constructor( val state = _state.asStateFlow() init { + if (!repository.isRunning.value) { + requestBluetoothDevice() + } + + repository.data.onEach { + _state.value = WorkingState(it) + }.launchIn(viewModelScope) + } + + fun onEvent(event: CGMViewEvent) { + when (event) { + DisconnectEvent -> disconnect() + is OnWorkingModeSelected -> onCommandReceived(event.workingMode) + NavigateUp -> navigationManager.navigateUp() + }.exhaustive + } + + private fun requestBluetoothDevice() { navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CGMS_SERVICE_UUID)) navigationManager.recentResult.onEach { @@ -36,11 +53,6 @@ internal class CGMScreenViewModel @Inject constructor( handleArgs(it) } }.launchIn(viewModelScope) - - repository.data.onEach { - _state.value = WorkingState(it) - Log.d("AAATESTAAA", "vm data: $it") - }.launchIn(viewModelScope) } private fun handleArgs(args: DestinationResult) { @@ -50,11 +62,12 @@ internal class CGMScreenViewModel @Inject constructor( }.exhaustive } - fun onEvent(event: CGMViewEvent) { - when (event) { - DisconnectEvent -> disconnect() - is OnWorkingModeSelected -> repository.sendNewServiceCommand(event.workingMode) - NavigateUp -> navigationManager.navigateUp() + private fun onCommandReceived(workingMode: CGMServiceCommand) { + when (workingMode) { + CGMServiceCommand.REQUEST_ALL_RECORDS -> repository.requestAllRecords() + CGMServiceCommand.REQUEST_LAST_RECORD -> repository.requestLastRecord() + CGMServiceCommand.REQUEST_FIRST_RECORD -> repository.requestFirstRecord() + CGMServiceCommand.DISCONNECT -> disconnect() }.exhaustive } @@ -63,6 +76,7 @@ internal class CGMScreenViewModel @Inject constructor( } private fun disconnect() { - repository.sendNewServiceCommand(CGMServiceCommand.DISCONNECT) + repository.release() + navigationManager.navigateUp() } } 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 7f9d1d3b..7871c368 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,6 +1,5 @@ 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 @@ -25,21 +24,21 @@ fun GLSScreen() { val state = viewModel.state.collectAsState().value Column { + val navigateUp = { viewModel.onEvent(DisconnectEvent) } + BackIconAppBar(stringResource(id = R.string.gls_title)) { viewModel.onEvent(DisconnectEvent) } - Log.d("AAATESTAAA", "state: $state") - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { 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 ConnectingResult, + is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } + is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) + is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) + is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is SuccessResult -> GLSContentView(state.result.data) { viewModel.onEvent(it) } } }.exhaustive