Add first working example of CGM service in new approach

This commit is contained in:
Sylwester Zieliński
2022-02-11 11:02:06 +01:00
parent d12409cffd
commit 8d21c591ee
20 changed files with 267 additions and 118 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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() {

View File

@@ -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)
}

View File

@@ -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
)

View File

@@ -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)
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#000000"/>
</vector>

View File

@@ -20,6 +20,9 @@
<string name="dfu_module">DFU</string>
<string name="dfu_module_full">Available in separate application.</string>
<string name="bluetooth_services">Bluetooth services</string>
<string name="viewmodel_profiles">ViewModel profiles</string>
<string name="service_profiles">Service profiles</string>
<string name="utils_services">Utils services</string>
<string name="running_profile_icon">Icon indicating if the profile is running</string>
</resources>

View File

@@ -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 { }
}

View File

@@ -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 { }
}

View File

@@ -70,5 +70,5 @@ fun NoDeviceView() {
@Preview
@Composable
fun NoDeviceView_Preview() {
DeviceConnectingView()
DeviceConnectingView { }
}

View File

@@ -5,10 +5,12 @@
<string name="dialog">Dialog</string>
<string name="cancel">CANCEL</string>
<string name="go_up">Go up</string>
<string name="close_app">Close the application.</string>
<string name="back_screen">Close the current screen.</string>
<string name="disconnect">DISCONNECT</string>
<string name="disconnect">Disconnect</string>
<string name="field_battery">Battery</string>
<string name="device_disconnected">Disconnected</string>

View File

@@ -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

View File

@@ -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<BleManagerResult<CGMData>>(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
}
}

View File

@@ -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<BatteryLevelResponse>()
.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
}
}

View File

@@ -20,7 +20,7 @@ internal class CGMService : NotificationService() {
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
repository.startManager(device, lifecycleScope)
repository.start(device, lifecycleScope)
return START_REDELIVER_INTENT
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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