From 01ed437d45fd60232632795f79c04420e4d49469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylwester=20Zieli=C5=84ski?= Date: Fri, 11 Feb 2022 14:03:59 +0100 Subject: [PATCH] Change CSC module --- .../nrftoolbox/viewmodel/HomeViewModel.kt | 8 +- .../android/cgms/data/CGMRepository.kt | 1 + .../cgms/viewmodel/CGMScreenViewModel.kt | 7 +- .../no/nordicsemi/android/csc/data/CSCData.kt | 78 +------------- .../android/csc/data/CSCRepository.kt | 102 +++++++++--------- .../android/csc/repository/CSCManager.kt | 40 +------ .../android/csc/repository/CSCService.kt | 28 +++-- .../android/csc/view/CSCContentView.kt | 13 +-- .../nordicsemi/android/csc/view/CSCMappers.kt | 79 ++++++++++++++ .../nordicsemi/android/csc/view/CSCScreen.kt | 27 +++-- .../nordicsemi/android/csc/view/CSCState.kt | 12 ++- .../android/csc/view/CSCViewEvent.kt | 2 + .../android/csc/view/SensorsReadingView.kt | 10 +- .../android/csc/viewmodel/CSCViewModel.kt | 64 +++++------ 14 files changed, 228 insertions(+), 243 deletions(-) create mode 100644 profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCMappers.kt 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 index 3aaabb4a..5a3f9161 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt @@ -8,6 +8,7 @@ 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.csc.data.CSCRepository import no.nordicsemi.android.navigation.NavigationManager import no.nordicsemi.android.nrftoolbox.ProfileDestination import no.nordicsemi.android.nrftoolbox.view.HomeViewState @@ -16,7 +17,8 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( private val navigationManager: NavigationManager, - private val cgmRepository: CGMRepository + cgmRepository: CGMRepository, + cscRepository: CSCRepository ) : ViewModel() { private val _state = MutableStateFlow(HomeViewState()) @@ -26,6 +28,10 @@ class HomeViewModel @Inject constructor( cgmRepository.isRunning.onEach { _state.value = _state.value.copy(isCGMModuleRunning = it) }.launchIn(viewModelScope) + + cscRepository.isRunning.onEach { + _state.value = _state.value.copy(isCSCModuleRunning = it) + }.launchIn(viewModelScope) } fun openProfile(destination: ProfileDestination) { 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 bed4fef6..b42a83b9 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 @@ -39,6 +39,7 @@ class CGMRepository @Inject constructor( fun start(device: BluetoothDevice, scope: CoroutineScope) { val manager = CGMManager(context, scope) + this.manager = manager manager.dataHolder.status.onEach { _data.value = it 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 a5be007e..301daf2c 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 @@ -14,7 +14,6 @@ import no.nordicsemi.android.cgms.view.* import no.nordicsemi.android.navigation.* import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.utils.getDevice -import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import no.nordicsemi.ui.scanner.ScannerDestinationId import javax.inject.Inject @@ -58,7 +57,7 @@ internal class CGMScreenViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> connectDevice(args.getDevice()) + is SuccessDestinationResult -> repository.launch(args.getDevice().device) }.exhaustive } @@ -71,10 +70,6 @@ internal class CGMScreenViewModel @Inject constructor( }.exhaustive } - private fun connectDevice(deviceHolder: DiscoveredBluetoothDevice) { - repository.launch(deviceHolder.device) - } - private fun disconnect() { repository.release() navigationManager.navigateUp() diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt index cb58fc3a..21e4919f 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt @@ -1,17 +1,7 @@ package no.nordicsemi.android.csc.data -import no.nordicsemi.android.csc.view.SpeedUnit -import no.nordicsemi.android.material.you.RadioButtonItem -import no.nordicsemi.android.material.you.RadioGroupViewEntity -import java.util.* - -private const val DISPLAY_M_S = "m/s" -private const val DISPLAY_KM_H = "km/h" -private const val DISPLAY_MPH = "mph" - internal data class CSCData( val scanDevices: Boolean = false, - val selectedSpeedUnit: SpeedUnit = SpeedUnit.M_S, val speed: Float = 0f, val cadence: Float = 0f, val distance: Float = 0f, @@ -19,70 +9,4 @@ internal data class CSCData( val gearRatio: Float = 0f, val batteryLevel: Int = 0, val wheelSize: WheelSize = WheelSize() -) { - - private val speedWithUnit = when (selectedSpeedUnit) { - SpeedUnit.M_S -> speed - SpeedUnit.KM_H -> speed * 3.6f - SpeedUnit.MPH -> speed * 2.2369f - } - - fun displaySpeed(): String { - return when (selectedSpeedUnit) { - SpeedUnit.M_S -> String.format("%.1f m/s", speedWithUnit) - SpeedUnit.KM_H -> String.format("%.1f km/h", speedWithUnit) - SpeedUnit.MPH -> String.format("%.1f mph", speedWithUnit) - } - } - - fun displayCadence(): String { - return String.format("%.0f RPM", cadence) - } - - fun displayDistance(): String { - return when (selectedSpeedUnit) { - SpeedUnit.M_S -> String.format("%.0f m", distance) - SpeedUnit.KM_H -> String.format("%.0f m", distance) - SpeedUnit.MPH -> String.format("%.0f yd", distance) - } - } - - fun displayTotalDistance(): String { - return when (selectedSpeedUnit) { - SpeedUnit.M_S -> String.format("%.2f km", totalDistance) - SpeedUnit.KM_H -> String.format("%.2f km", totalDistance) - SpeedUnit.MPH -> String.format("%.2f mile", totalDistance) - } - } - - fun displayGearRatio(): String { - return String.format(Locale.US, "%.1f", gearRatio) - } - - fun getSpeedUnit(label: String): SpeedUnit { - return when (label) { - DISPLAY_KM_H -> SpeedUnit.KM_H - DISPLAY_M_S -> SpeedUnit.M_S - DISPLAY_MPH -> SpeedUnit.MPH - else -> throw IllegalArgumentException("Can't create SpeedUnit from this label: $label") - } - } - - fun temperatureSettingsItems(): RadioGroupViewEntity { - return RadioGroupViewEntity( - SpeedUnit.values().map { createRadioButtonItem(it) } - ) - } - - private fun createRadioButtonItem(unit: SpeedUnit): RadioButtonItem { - return RadioButtonItem(displayTemperature(unit), unit == selectedSpeedUnit) - } - - private fun displayTemperature(unit: SpeedUnit): String { - return when (unit) { - SpeedUnit.KM_H -> DISPLAY_KM_H - SpeedUnit.M_S -> DISPLAY_M_S - SpeedUnit.MPH -> DISPLAY_MPH - } - } -} +) 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 c451cd5f..86439fc8 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,68 +1,74 @@ package no.nordicsemi.android.csc.data -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.* -import no.nordicsemi.android.csc.view.SpeedUnit -import no.nordicsemi.android.service.BleManagerStatus +import android.bluetooth.BluetoothDevice +import android.content.Context +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.csc.repository.CSCManager +import no.nordicsemi.android.csc.repository.CSCService +import no.nordicsemi.android.service.BleManagerResult +import no.nordicsemi.android.service.ConnectingResult +import no.nordicsemi.android.service.ServiceManager import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class CSCRepository @Inject constructor() { +class CSCRepository @Inject constructor( + @ApplicationContext + private val context: Context, + private val serviceManager: ServiceManager, +) { + private var manager: CSCManager? = null - private val _data = MutableStateFlow(CSCData()) - val data: StateFlow = _data.asStateFlow() + private val _data = MutableStateFlow>(ConnectingResult()) + internal val data = _data.asStateFlow() - private val _command = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST) - val command = _command.asSharedFlow() + private val _isRunning = MutableStateFlow(false) + val isRunning = _isRunning.asStateFlow() - private val _status = MutableStateFlow(BleManagerStatus.CONNECTING) - val status = _status.asStateFlow() - - fun setSpeedUnit(selectedSpeedUnit: SpeedUnit) { - _data.tryEmit(_data.value.copy(selectedSpeedUnit = selectedSpeedUnit)) + fun launch(device: BluetoothDevice) { + serviceManager.startService(CSCService::class.java, device) } - fun setNewDistance( - totalDistance: Float, - distance: Float, - speed: Float, - wheelSize: WheelSize - ) { - _data.tryEmit(_data.value.copy( - totalDistance = totalDistance, - distance = distance, - speed = speed, - wheelSize = wheelSize - )) - } + fun start(device: BluetoothDevice, scope: CoroutineScope) { + val manager = CSCManager(context, scope) + this.manager = manager - fun setNewCrankCadence( - crankCadence: Float, - gearRatio: Float, - wheelSize: WheelSize - ) { - _data.tryEmit(_data.value.copy(cadence = crankCadence, gearRatio = gearRatio, wheelSize = wheelSize)) - } + manager.dataHolder.status.onEach { + _data.value = it + }.launchIn(scope) - fun setBatteryLevel(batteryLevel: Int) { - _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel)) - } - - fun sendNewServiceCommand(workingMode: CSCServiceCommand) { - if (_command.subscriptionCount.value > 0) { - _command.tryEmit(workingMode) - } else { - _status.tryEmit(BleManagerStatus.DISCONNECTED) + scope.launch { + manager.start(device) } } - fun setNewStatus(status: BleManagerStatus) { - _status.value = status + fun setWheelSize(wheelSize: WheelSize) { + manager?.setWheelSize(wheelSize) } - fun clear() { - _status.value = BleManagerStatus.CONNECTING - _data.tryEmit(CSCData()) + private suspend fun CSCManager.start(device: BluetoothDevice) { + try { + connect(device) + .useAutoConnect(false) + .retry(3, 100) + .suspend() + _isRunning.value = true + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun release() { + serviceManager.stopService(CSCService::class.java) + manager?.disconnect()?.enqueue() + manager = null + _isRunning.value = false } } 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 cd75aa8a..504cd402 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 @@ -21,17 +21,15 @@ */ package no.nordicsemi.android.csc.repository -import android.bluetooth.BluetoothDevice 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.CoroutineScope -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.* +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 @@ -40,9 +38,8 @@ import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.csc.data.CSCData import no.nordicsemi.android.csc.data.WheelSize -import no.nordicsemi.android.service.* +import no.nordicsemi.android.service.ConnectionObserverAdapter import java.util.* -import javax.inject.Inject val CSC_SERVICE_UUID: UUID = UUID.fromString("00001816-0000-1000-8000-00805f9b34fb") private val CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-0000-1000-8000-00805f9b34fb") @@ -50,35 +47,6 @@ private val CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-0000 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 CSCRepo @Inject constructor( - @ApplicationContext - private val context: Context, -) { - - suspend fun downloadData(device: BluetoothDevice): Flow> = callbackFlow { - val scope = CoroutineScope(coroutineContext) - val manager = CSCManager(context, scope) - - manager.dataHolder.status.onEach { - trySend(it) - }.launchIn(scope) - - scope.launch { - manager.connect(device) - .useAutoConnect(false) - .retry(3, 100) - .suspend() - } - - awaitClose { - scope.launch { - manager.disconnect().suspend() - } - scope.cancel() - } - } -} - internal class CSCManager( context: Context, private val scope: CoroutineScope, 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 acd6a010..e36cd86a 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 @@ -1,31 +1,27 @@ package no.nordicsemi.android.csc.repository +import android.bluetooth.BluetoothDevice +import android.content.Intent +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.csc.data.CSCRepository -import no.nordicsemi.android.csc.data.DisconnectCommand -import no.nordicsemi.android.csc.data.SetWheelSizeCommand -import no.nordicsemi.android.service.ForegroundBleService -import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.android.service.DEVICE_DATA +import no.nordicsemi.android.service.NotificationService import javax.inject.Inject @AndroidEntryPoint -internal class CSCService : ForegroundBleService() { +internal class CSCService : NotificationService() { @Inject lateinit var repository: CSCRepository - override val manager: CSCManager by lazy { CSCManager(this, scope) } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + super.onStartCommand(intent, flags, startId) - override fun onCreate() { - super.onCreate() + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! - repository.command.onEach { - when (it) { - DisconnectCommand -> stopSelf() - is SetWheelSizeCommand -> manager.setWheelSize(it.wheelSize) - }.exhaustive - }.launchIn(scope) + repository.start(device, lifecycleScope) + + return START_REDELIVER_INTENT } } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCContentView.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCContentView.kt index 0bae7678..54ebf9e7 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCContentView.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCContentView.kt @@ -28,7 +28,7 @@ import no.nordicsemi.android.theme.view.dialog.ItemSelectedResult import no.nordicsemi.android.utils.exhaustive @Composable -internal fun CSCContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) { +internal fun CSCContentView(state: CSCData, speedUnit: SpeedUnit, onEvent: (CSCViewEvent) -> Unit) { val showDialog = rememberSaveable { mutableStateOf(false) } if (showDialog.value) { @@ -51,11 +51,11 @@ internal fun CSCContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) { horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { - SettingsSection(state, onEvent) { showDialog.value = true } + SettingsSection(state, speedUnit, onEvent) { showDialog.value = true } Spacer(modifier = Modifier.height(16.dp)) - SensorsReadingView(state = state) + SensorsReadingView(state = state, speedUnit) Spacer(modifier = Modifier.height(16.dp)) @@ -70,6 +70,7 @@ internal fun CSCContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) { @Composable private fun SettingsSection( state: CSCData, + speedUnit: SpeedUnit, onEvent: (CSCViewEvent) -> Unit, onWheelButtonClick: () -> Unit, ) { @@ -86,8 +87,8 @@ private fun SettingsSection( Spacer(modifier = Modifier.height(16.dp)) - RadioButtonGroup(viewEntity = state.temperatureSettingsItems()) { - onEvent(OnSelectedSpeedUnitSelected(state.getSpeedUnit(it.label))) + RadioButtonGroup(viewEntity = speedUnit.temperatureSettingsItems()) { + onEvent(OnSelectedSpeedUnitSelected(it.label.toSpeedUnit())) } } } @@ -96,5 +97,5 @@ private fun SettingsSection( @Preview @Composable private fun ConnectedPreview() { - CSCContentView(CSCData()) { } + CSCContentView(CSCData(), SpeedUnit.KM_H) { } } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCMappers.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCMappers.kt new file mode 100644 index 00000000..4295cd36 --- /dev/null +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCMappers.kt @@ -0,0 +1,79 @@ +package no.nordicsemi.android.csc.view + +import no.nordicsemi.android.csc.data.CSCData +import no.nordicsemi.android.material.you.RadioButtonItem +import no.nordicsemi.android.material.you.RadioGroupViewEntity +import java.util.* + +private const val DISPLAY_M_S = "m/s" +private const val DISPLAY_KM_H = "km/h" +private const val DISPLAY_MPH = "mph" + +internal fun CSCData.speedWithSpeedUnit(speedUnit: SpeedUnit): Float { + return when (speedUnit) { + SpeedUnit.M_S -> speed + SpeedUnit.KM_H -> speed * 3.6f + SpeedUnit.MPH -> speed * 2.2369f + } +} + +internal fun CSCData.displaySpeed(speedUnit: SpeedUnit): String { + val speedWithUnit = speedWithSpeedUnit(speedUnit) + return when (speedUnit) { + SpeedUnit.M_S -> String.format("%.1f m/s", speedWithUnit) + SpeedUnit.KM_H -> String.format("%.1f km/h", speedWithUnit) + SpeedUnit.MPH -> String.format("%.1f mph", speedWithUnit) + } +} + +internal fun CSCData.displayCadence(): String { + return String.format("%.0f RPM", cadence) +} + +internal fun CSCData.displayDistance(speedUnit: SpeedUnit): String { + return when (speedUnit) { + SpeedUnit.M_S -> String.format("%.0f m", distance) + SpeedUnit.KM_H -> String.format("%.0f m", distance) + SpeedUnit.MPH -> String.format("%.0f yd", distance) + } +} + +internal fun CSCData.displayTotalDistance(speedUnit: SpeedUnit): String { + return when (speedUnit) { + SpeedUnit.M_S -> String.format("%.2f km", totalDistance) + SpeedUnit.KM_H -> String.format("%.2f km", totalDistance) + SpeedUnit.MPH -> String.format("%.2f mile", totalDistance) + } +} + +internal fun CSCData.displayGearRatio(): String { + return String.format(Locale.US, "%.1f", gearRatio) +} + +internal fun String.toSpeedUnit(): SpeedUnit { + return when (this) { + DISPLAY_KM_H -> SpeedUnit.KM_H + DISPLAY_M_S -> SpeedUnit.M_S + DISPLAY_MPH -> SpeedUnit.MPH + else -> throw IllegalArgumentException("Can't create SpeedUnit from this label: $this") + } +} + +internal fun SpeedUnit.temperatureSettingsItems(): RadioGroupViewEntity { + return RadioGroupViewEntity( + SpeedUnit.values().map { createRadioButtonItem(it, this) } + ) +} + +private fun createRadioButtonItem(unit: SpeedUnit, selectedSpeedUnit: SpeedUnit): RadioButtonItem { + return RadioButtonItem(displayTemperature(unit), unit == selectedSpeedUnit) +} + +private fun displayTemperature(unit: SpeedUnit): String { + return when (unit) { + SpeedUnit.KM_H -> DISPLAY_KM_H + SpeedUnit.M_S -> DISPLAY_M_S + SpeedUnit.MPH -> DISPLAY_MPH + } +} + 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 027b1095..6feb3b3b 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 @@ -10,7 +10,13 @@ import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.csc.R import no.nordicsemi.android.csc.viewmodel.CSCViewModel +import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar +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 fun CSCScreen() { @@ -18,15 +24,22 @@ fun CSCScreen() { val state = viewModel.state.collectAsState().value Column { - BackIconAppBar(stringResource(id = R.string.csc_title)) { - viewModel.onEvent(OnDisconnectButtonClick) - } + val navigateUp = { viewModel.onEvent(NavigateUp) } + + BackIconAppBar(stringResource(id = R.string.csc_title), navigateUp) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { -// when (state) { -// is DisplayDataState -> CSCContentView(state.data) { viewModel.onEvent(it) } -// LoadingState -> DeviceConnectingView() -// }.exhaustive + when (state.cscManagerState) { + NoDeviceState -> NoDeviceView() + is WorkingState -> when (state.cscManagerState.result) { + is ConnectingResult, + is ReadyResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) } + is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) + is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) + is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) + is SuccessResult -> CSCContentView(state.cscManagerState.result.data, state.speedUnit) { viewModel.onEvent(it) } + } + }.exhaustive } } } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCState.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCState.kt index a43fda58..cc4a60d9 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCState.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCState.kt @@ -1,9 +1,15 @@ package no.nordicsemi.android.csc.view import no.nordicsemi.android.csc.data.CSCData +import no.nordicsemi.android.service.BleManagerResult -internal sealed class CSCViewState +internal data class CSCViewState( + val speedUnit: SpeedUnit = SpeedUnit.M_S, + val cscManagerState: CSCMangerState = NoDeviceState +) -internal object LoadingState : CSCViewState() +internal sealed class CSCMangerState -internal data class DisplayDataState(val data: CSCData) : CSCViewState() +internal data class WorkingState(val result: BleManagerResult) : CSCMangerState() + +internal object NoDeviceState : CSCMangerState() diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt index 77edd51d..0a2bb233 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt @@ -9,3 +9,5 @@ internal data class OnWheelSizeSelected(val wheelSize: WheelSize) : CSCViewEvent internal data class OnSelectedSpeedUnitSelected(val selectedSpeedUnit: SpeedUnit) : CSCViewEvent() internal object OnDisconnectButtonClick : CSCViewEvent() + +internal object NavigateUp : CSCViewEvent() diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt index a79012cd..2b45a407 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt @@ -16,22 +16,22 @@ import no.nordicsemi.android.theme.view.ScreenSection import no.nordicsemi.android.theme.view.SectionTitle @Composable -internal fun SensorsReadingView(state: CSCData) { +internal fun SensorsReadingView(state: CSCData, speedUnit: SpeedUnit) { ScreenSection { SectionTitle(resId = R.drawable.ic_records, title = "Records") Spacer(modifier = Modifier.height(16.dp)) Column { - KeyValueField(stringResource(id = R.string.csc_field_speed), state.displaySpeed()) + KeyValueField(stringResource(id = R.string.csc_field_speed), state.displaySpeed(speedUnit)) Spacer(modifier = Modifier.height(4.dp)) KeyValueField(stringResource(id = R.string.csc_field_cadence), state.displayCadence()) Spacer(modifier = Modifier.height(4.dp)) - KeyValueField(stringResource(id = R.string.csc_field_distance), state.displayDistance()) + KeyValueField(stringResource(id = R.string.csc_field_distance), state.displayDistance(speedUnit)) Spacer(modifier = Modifier.height(4.dp)) KeyValueField( stringResource(id = R.string.csc_field_total_distance), - state.displayTotalDistance() + state.displayTotalDistance(speedUnit) ) Spacer(modifier = Modifier.height(4.dp)) KeyValueField(stringResource(id = R.string.csc_field_gear_ratio), state.displayGearRatio()) @@ -46,5 +46,5 @@ internal fun SensorsReadingView(state: CSCData) { @Preview @Composable private fun Preview() { - SensorsReadingView(CSCData()) + SensorsReadingView(CSCData(), SpeedUnit.KM_H) } 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 0f9d6437..205f8fa5 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 @@ -3,16 +3,14 @@ package no.nordicsemi.android.csc.viewmodel 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.csc.data.CSCRepository -import no.nordicsemi.android.csc.data.DisconnectCommand -import no.nordicsemi.android.csc.data.SetWheelSizeCommand -import no.nordicsemi.android.csc.repository.CSCService import no.nordicsemi.android.csc.repository.CSC_SERVICE_UUID import no.nordicsemi.android.csc.view.* import no.nordicsemi.android.navigation.* -import no.nordicsemi.android.service.BleManagerStatus -import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.utils.getDevice import no.nordicsemi.ui.scanner.ScannerDestinationId @@ -21,19 +19,23 @@ import javax.inject.Inject @HiltViewModel internal class CSCViewModel @Inject constructor( private val repository: CSCRepository, - private val serviceManager: ServiceManager, 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(CSCViewState()) + val state = _state.asStateFlow() init { + if (!repository.isRunning.value) { + requestBluetoothDevice() + } + + repository.data.onEach { + _state.value = _state.value.copy(cscManagerState = WorkingState(it)) + }.launchIn(viewModelScope) + } + + private fun requestBluetoothDevice() { navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CSC_SERVICE_UUID)) navigationManager.recentResult.onEach { @@ -41,44 +43,30 @@ internal class CSCViewModel @Inject constructor( handleArgs(it) } }.launchIn(viewModelScope) - - repository.status.onEach { - if (it == BleManagerStatus.DISCONNECTED) { - navigationManager.navigateUp() - } - }.launchIn(viewModelScope) } private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> serviceManager.startService(CSCService::class.java, args.getDevice()) + is SuccessDestinationResult -> repository.launch(args.getDevice().device) }.exhaustive } fun onEvent(event: CSCViewEvent) { when (event) { - is OnSelectedSpeedUnitSelected -> onSelectedSpeedUnit(event) - is OnWheelSizeSelected -> onWheelSizeChanged(event) - OnDisconnectButtonClick -> onDisconnectButtonClick() + is OnSelectedSpeedUnitSelected -> setSpeedUnit(event.selectedSpeedUnit) + is OnWheelSizeSelected -> repository.setWheelSize(event.wheelSize) + OnDisconnectButtonClick -> disconnect() + NavigateUp -> navigationManager.navigateUp() }.exhaustive } - private fun onSelectedSpeedUnit(event: OnSelectedSpeedUnitSelected) { - repository.setSpeedUnit(event.selectedSpeedUnit) + private fun setSpeedUnit(speedUnit: SpeedUnit) { + _state.value = _state.value.copy(speedUnit = speedUnit) } - private fun onWheelSizeChanged(event: OnWheelSizeSelected) { - repository.sendNewServiceCommand(SetWheelSizeCommand(event.wheelSize)) - } - - private fun onDisconnectButtonClick() { - repository.sendNewServiceCommand(DisconnectCommand) - repository.clear() - } - - override fun onCleared() { - super.onCleared() - repository.clear() + private fun disconnect() { + repository.release() + navigationManager.navigateUp() } }