Change CSC module

This commit is contained in:
Sylwester Zieliński
2022-02-11 14:03:59 +01:00
parent 8d21c591ee
commit 01ed437d45
14 changed files with 228 additions and 243 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<CSCData> = _data.asStateFlow()
private val _data = MutableStateFlow<BleManagerResult<CSCData>>(ConnectingResult())
internal val data = _data.asStateFlow()
private val _command = MutableSharedFlow<CSCServiceCommand>(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
}
}

View File

@@ -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<BleManagerResult<CSCData>> = 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,

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<CSCData>) : CSCMangerState()
internal object NoDeviceState : CSCMangerState()

View File

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

View File

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

View File

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