mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-22 08:54:21 +01:00
Change HTS module
This commit is contained in:
@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||||
import no.nordicsemi.android.csc.data.CSCRepository
|
import no.nordicsemi.android.csc.data.CSCRepository
|
||||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||||
|
import no.nordicsemi.android.hts.data.HTSRepository
|
||||||
import no.nordicsemi.android.navigation.NavigationManager
|
import no.nordicsemi.android.navigation.NavigationManager
|
||||||
import no.nordicsemi.android.nrftoolbox.ProfileDestination
|
import no.nordicsemi.android.nrftoolbox.ProfileDestination
|
||||||
import no.nordicsemi.android.nrftoolbox.view.HomeViewState
|
import no.nordicsemi.android.nrftoolbox.view.HomeViewState
|
||||||
@@ -20,7 +21,8 @@ class HomeViewModel @Inject constructor(
|
|||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
cgmRepository: CGMRepository,
|
cgmRepository: CGMRepository,
|
||||||
cscRepository: CSCRepository,
|
cscRepository: CSCRepository,
|
||||||
hrsRepository: HRSRepository
|
hrsRepository: HRSRepository,
|
||||||
|
htsRepository: HTSRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _state = MutableStateFlow(HomeViewState())
|
private val _state = MutableStateFlow(HomeViewState())
|
||||||
@@ -38,6 +40,10 @@ class HomeViewModel @Inject constructor(
|
|||||||
hrsRepository.isRunning.onEach {
|
hrsRepository.isRunning.onEach {
|
||||||
_state.value = _state.value.copy(isHRSModuleRunning = it)
|
_state.value = _state.value.copy(isHRSModuleRunning = it)
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
htsRepository.isRunning.onEach {
|
||||||
|
_state.value = _state.value.copy(isHTSModuleRunning = it)
|
||||||
|
}.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openProfile(destination: ProfileDestination) {
|
fun openProfile(destination: ProfileDestination) {
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ internal class HRSManager(
|
|||||||
override fun onServicesInvalidated() {
|
override fun onServicesInvalidated() {
|
||||||
bodySensorLocationCharacteristic = null
|
bodySensorLocationCharacteristic = null
|
||||||
heartRateCharacteristic = null
|
heartRateCharacteristic = null
|
||||||
|
batteryLevelCharacteristic = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,6 @@
|
|||||||
package no.nordicsemi.android.hts.data
|
package no.nordicsemi.android.hts.data
|
||||||
|
|
||||||
import no.nordicsemi.android.material.you.RadioButtonItem
|
|
||||||
import no.nordicsemi.android.material.you.RadioGroupViewEntity
|
|
||||||
|
|
||||||
private const val DISPLAY_FAHRENHEIT = "°F"
|
|
||||||
private const val DISPLAY_CELSIUS = "°C"
|
|
||||||
private const val DISPLAY_KELVIN = "°K"
|
|
||||||
|
|
||||||
internal data class HTSData(
|
internal data class HTSData(
|
||||||
val temperatureValue: Float = 0f,
|
val temperatureValue: Float = 0f,
|
||||||
val temperatureUnit: TemperatureUnit = TemperatureUnit.CELSIUS,
|
|
||||||
val batteryLevel: Int = 0,
|
val batteryLevel: Int = 0,
|
||||||
) {
|
)
|
||||||
|
|
||||||
fun displayTemperature(): String {
|
|
||||||
return when (temperatureUnit) {
|
|
||||||
TemperatureUnit.CELSIUS -> String.format("%.1f °C", temperatureValue)
|
|
||||||
TemperatureUnit.FAHRENHEIT -> String.format("%.1f °F", temperatureValue * 1.8f + 32f)
|
|
||||||
TemperatureUnit.KELVIN -> String.format("%.1f °K", temperatureValue + 273.15f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTemperatureUnit(label: String): TemperatureUnit {
|
|
||||||
return when (label) {
|
|
||||||
DISPLAY_CELSIUS -> TemperatureUnit.CELSIUS
|
|
||||||
DISPLAY_FAHRENHEIT -> TemperatureUnit.FAHRENHEIT
|
|
||||||
DISPLAY_KELVIN -> TemperatureUnit.KELVIN
|
|
||||||
else -> throw IllegalArgumentException("Can't create TemperatureUnit from this label: $label")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun temperatureSettingsItems(): RadioGroupViewEntity {
|
|
||||||
return RadioGroupViewEntity(
|
|
||||||
TemperatureUnit.values().map { createRadioButtonItem(it) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createRadioButtonItem(unit: TemperatureUnit): RadioButtonItem {
|
|
||||||
return RadioButtonItem(displayTemperature(unit), unit == temperatureUnit)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun displayTemperature(unit: TemperatureUnit): String {
|
|
||||||
return when (unit) {
|
|
||||||
TemperatureUnit.CELSIUS -> DISPLAY_CELSIUS
|
|
||||||
TemperatureUnit.FAHRENHEIT -> DISPLAY_FAHRENHEIT
|
|
||||||
TemperatureUnit.KELVIN -> DISPLAY_KELVIN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum class TemperatureUnit {
|
|
||||||
CELSIUS,
|
|
||||||
FAHRENHEIT,
|
|
||||||
KELVIN
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,49 +1,70 @@
|
|||||||
package no.nordicsemi.android.hts.data
|
package no.nordicsemi.android.hts.data
|
||||||
|
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import android.bluetooth.BluetoothDevice
|
||||||
import kotlinx.coroutines.flow.*
|
import android.content.Context
|
||||||
import no.nordicsemi.android.service.BleManagerStatus
|
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.hts.repository.HTSManager
|
||||||
|
import no.nordicsemi.android.hts.repository.HTSService
|
||||||
|
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.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
internal class HTSRepository @Inject constructor() {
|
class HTSRepository @Inject constructor(
|
||||||
|
@ApplicationContext
|
||||||
|
private val context: Context,
|
||||||
|
private val serviceManager: ServiceManager,
|
||||||
|
) {
|
||||||
|
private var manager: HTSManager? = null
|
||||||
|
|
||||||
private val _data = MutableStateFlow(HTSData())
|
private val _data = MutableStateFlow<BleManagerResult<HTSData>>(ConnectingResult())
|
||||||
val data: StateFlow<HTSData> = _data
|
internal val data = _data.asStateFlow()
|
||||||
|
|
||||||
private val _command = MutableSharedFlow<DisconnectCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
private val _isRunning = MutableStateFlow(false)
|
||||||
val command = _command.asSharedFlow()
|
val isRunning = _isRunning.asStateFlow()
|
||||||
|
|
||||||
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
fun launch(device: BluetoothDevice) {
|
||||||
val status = _status.asStateFlow()
|
serviceManager.startService(HTSService::class.java, device)
|
||||||
|
|
||||||
fun setNewTemperature(temperature: Float) {
|
|
||||||
_data.tryEmit(_data.value.copy(temperatureValue = temperature))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBatteryLevel(batteryLevel: Int) {
|
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||||
_data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
|
val manager = HTSManager(context, scope)
|
||||||
}
|
this.manager = manager
|
||||||
|
|
||||||
fun setTemperatureUnit(unit: TemperatureUnit) {
|
manager.dataHolder.status.onEach {
|
||||||
_data.tryEmit(_data.value.copy(temperatureUnit = unit))
|
_data.value = it
|
||||||
}
|
}.launchIn(scope)
|
||||||
|
|
||||||
fun sendDisconnectCommand() {
|
scope.launch {
|
||||||
if (_command.subscriptionCount.value > 0) {
|
manager.start(device)
|
||||||
_command.tryEmit(DisconnectCommand)
|
|
||||||
} else {
|
|
||||||
_status.tryEmit(BleManagerStatus.DISCONNECTED)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNewStatus(status: BleManagerStatus) {
|
private suspend fun HTSManager.start(device: BluetoothDevice) {
|
||||||
_status.value = status
|
try {
|
||||||
|
connect(device)
|
||||||
|
.useAutoConnect(false)
|
||||||
|
.retry(3, 100)
|
||||||
|
.suspend()
|
||||||
|
_isRunning.value = true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun release() {
|
||||||
_status.value = BleManagerStatus.CONNECTING
|
serviceManager.stopService(HTSService::class.java)
|
||||||
_data.tryEmit(HTSData())
|
manager?.disconnect()?.enqueue()
|
||||||
|
manager = null
|
||||||
|
_isRunning.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,70 +24,77 @@ package no.nordicsemi.android.hts.repository
|
|||||||
import android.bluetooth.BluetoothGatt
|
import android.bluetooth.BluetoothGatt
|
||||||
import android.bluetooth.BluetoothGattCharacteristic
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import no.nordicsemi.android.ble.BleManager
|
||||||
|
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
|
||||||
import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementResponse
|
import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementResponse
|
||||||
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
|
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
|
||||||
import no.nordicsemi.android.ble.ktx.suspend
|
import no.nordicsemi.android.hts.data.HTSData
|
||||||
import no.nordicsemi.android.hts.data.HTSRepository
|
import no.nordicsemi.android.service.ConnectionObserverAdapter
|
||||||
import no.nordicsemi.android.service.BatteryManager
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
val HTS_SERVICE_UUID: UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb")
|
val HTS_SERVICE_UUID: UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb")
|
||||||
private val HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb")
|
private val HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb")
|
||||||
|
|
||||||
|
private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||||
|
private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||||
|
|
||||||
internal class HTSManager internal constructor(
|
internal class HTSManager internal constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
private val dataHolder: HTSRepository
|
) : BleManager(context) {
|
||||||
) : BatteryManager(context, scope) {
|
|
||||||
|
|
||||||
|
private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
|
||||||
private var htCharacteristic: BluetoothGattCharacteristic? = null
|
private var htCharacteristic: BluetoothGattCharacteristic? = null
|
||||||
|
|
||||||
private val exceptionHandler = CoroutineExceptionHandler { _, t->
|
private val data = MutableStateFlow(HTSData())
|
||||||
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
|
val dataHolder = ConnectionObserverAdapter<HTSData>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
setConnectionObserver(dataHolder)
|
||||||
|
|
||||||
|
data.onEach {
|
||||||
|
dataHolder.setValue(it)
|
||||||
|
}.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBatteryLevelChanged(batteryLevel: Int) {
|
override fun getGattCallback(): BleManagerGattCallback {
|
||||||
dataHolder.setBatteryLevel(batteryLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getGattCallback(): BatteryManagerGattCallback {
|
|
||||||
return HTManagerGattCallback()
|
return HTManagerGattCallback()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class HTManagerGattCallback : BatteryManagerGattCallback() {
|
private inner class HTManagerGattCallback : BleManagerGattCallback() {
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
|
|
||||||
setIndicationCallback(htCharacteristic)
|
setIndicationCallback(htCharacteristic)
|
||||||
.asValidResponseFlow<TemperatureMeasurementResponse>()
|
.asValidResponseFlow<TemperatureMeasurementResponse>()
|
||||||
.onEach {
|
.onEach {
|
||||||
dataHolder.setNewTemperature(it.temperature)
|
data.tryEmit(data.value.copy(temperatureValue = it.temperature))
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
enableIndications(htCharacteristic).enqueue()
|
||||||
|
|
||||||
scope.launch(exceptionHandler) {
|
setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow<BatteryLevelResponse>().onEach {
|
||||||
enableIndications(htCharacteristic).suspend()
|
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||||
}
|
}.launchIn(scope)
|
||||||
|
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
|
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||||
val service = gatt.getService(HTS_SERVICE_UUID)
|
gatt.getService(HTS_SERVICE_UUID)?.run {
|
||||||
if (service != null) {
|
htCharacteristic = getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID)
|
||||||
htCharacteristic = service.getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID)
|
}
|
||||||
|
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||||
|
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||||
}
|
}
|
||||||
return htCharacteristic != null
|
return htCharacteristic != null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceDisconnected() {
|
override fun onServicesInvalidated() {
|
||||||
super.onDeviceDisconnected()
|
|
||||||
htCharacteristic = null
|
htCharacteristic = null
|
||||||
|
batteryLevelCharacteristic = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServicesInvalidated() {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
package no.nordicsemi.android.hts.repository
|
package no.nordicsemi.android.hts.repository
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import no.nordicsemi.android.hts.data.HTSRepository
|
import no.nordicsemi.android.hts.data.HTSRepository
|
||||||
import no.nordicsemi.android.service.ForegroundBleService
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
|
import no.nordicsemi.android.service.NotificationService
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
internal class HTSService : ForegroundBleService() {
|
internal class HTSService : NotificationService() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var repository: HTSRepository
|
lateinit var repository: HTSRepository
|
||||||
|
|
||||||
override val manager: HTSManager by lazy { HTSManager(this, scope, repository) }
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
override fun onCreate() {
|
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||||
super.onCreate()
|
|
||||||
|
|
||||||
repository.command.onEach {
|
repository.start(device, lifecycleScope)
|
||||||
stopSelf()
|
|
||||||
}.launchIn(scope)
|
return START_REDELIVER_INTENT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import no.nordicsemi.android.theme.view.ScreenSection
|
|||||||
import no.nordicsemi.android.theme.view.SectionTitle
|
import no.nordicsemi.android.theme.view.SectionTitle
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HTSContentView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
|
internal fun HTSContentView(state: HTSData, temperatureUnit: TemperatureUnit, onEvent: (HTSScreenViewEvent) -> Unit) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -30,21 +30,21 @@ internal fun HTSContentView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Uni
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
RadioButtonGroup(viewEntity = state.temperatureSettingsItems()) {
|
RadioButtonGroup(viewEntity = temperatureUnit.temperatureSettingsItems()) {
|
||||||
onEvent(OnTemperatureUnitSelected(state.getTemperatureUnit(it.label)))
|
onEvent(OnTemperatureUnitSelected(it.label.toTemperatureUnit()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
ScreenSection {
|
ScreenSection {
|
||||||
SectionTitle(resId = R.drawable.ic_records, title = "Records")
|
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.hts_records_section))
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
KeyValueField(
|
KeyValueField(
|
||||||
stringResource(id = R.string.hts_temperature),
|
stringResource(id = R.string.hts_temperature),
|
||||||
state.displayTemperature()
|
displayTemperature(state.temperatureValue, temperatureUnit)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,5 +65,5 @@ internal fun HTSContentView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Uni
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun Preview() {
|
private fun Preview() {
|
||||||
HTSContentView(state = HTSData()) { }
|
HTSContentView(state = HTSData(), TemperatureUnit.CELSIUS) { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package no.nordicsemi.android.hts.view
|
||||||
|
|
||||||
|
import no.nordicsemi.android.material.you.RadioButtonItem
|
||||||
|
import no.nordicsemi.android.material.you.RadioGroupViewEntity
|
||||||
|
|
||||||
|
private const val DISPLAY_FAHRENHEIT = "°F"
|
||||||
|
private const val DISPLAY_CELSIUS = "°C"
|
||||||
|
private const val DISPLAY_KELVIN = "°K"
|
||||||
|
|
||||||
|
internal fun displayTemperature(value: Float, temperatureUnit: TemperatureUnit): String {
|
||||||
|
return when (temperatureUnit) {
|
||||||
|
TemperatureUnit.CELSIUS -> String.format("%.1f °C", value)
|
||||||
|
TemperatureUnit.FAHRENHEIT -> String.format("%.1f °F", value * 1.8f + 32f)
|
||||||
|
TemperatureUnit.KELVIN -> String.format("%.1f °K", value + 273.15f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun String.toTemperatureUnit(): TemperatureUnit {
|
||||||
|
return when (this) {
|
||||||
|
DISPLAY_CELSIUS -> TemperatureUnit.CELSIUS
|
||||||
|
DISPLAY_FAHRENHEIT -> TemperatureUnit.FAHRENHEIT
|
||||||
|
DISPLAY_KELVIN -> TemperatureUnit.KELVIN
|
||||||
|
else -> throw IllegalArgumentException("Can't create TemperatureUnit from this label: $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TemperatureUnit.temperatureSettingsItems(): RadioGroupViewEntity {
|
||||||
|
return RadioGroupViewEntity(
|
||||||
|
TemperatureUnit.values().map { createRadioButtonItem(it, this) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRadioButtonItem(unit: TemperatureUnit, selectedTemperatureUnit: TemperatureUnit): RadioButtonItem {
|
||||||
|
return RadioButtonItem(displayTemperature(unit), unit == selectedTemperatureUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displayTemperature(unit: TemperatureUnit): String {
|
||||||
|
return when (unit) {
|
||||||
|
TemperatureUnit.CELSIUS -> DISPLAY_CELSIUS
|
||||||
|
TemperatureUnit.FAHRENHEIT -> DISPLAY_FAHRENHEIT
|
||||||
|
TemperatureUnit.KELVIN -> DISPLAY_KELVIN
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,13 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.hts.R
|
import no.nordicsemi.android.hts.R
|
||||||
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
|
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
|
||||||
|
import no.nordicsemi.android.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
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
|
@Composable
|
||||||
fun HTSScreen() {
|
fun HTSScreen() {
|
||||||
@@ -18,15 +24,22 @@ fun HTSScreen() {
|
|||||||
val state = viewModel.state.collectAsState().value
|
val state = viewModel.state.collectAsState().value
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
BackIconAppBar(stringResource(id = R.string.hts_title)) {
|
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||||
viewModel.onEvent(DisconnectEvent)
|
|
||||||
}
|
BackIconAppBar(stringResource(id = R.string.hts_title), navigateUp)
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
// when (state) {
|
when (state.htsManagerState) {
|
||||||
// is DisplayDataState -> HTSContentView(state.data) { viewModel.onEvent(it) }
|
NoDeviceState -> NoDeviceView()
|
||||||
// LoadingState -> DeviceConnectingView()
|
is WorkingState -> when (state.htsManagerState.result) {
|
||||||
// }.exhaustive
|
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 -> HTSContentView(state.htsManagerState.result.data, state.temperatureUnit) { viewModel.onEvent(it) }
|
||||||
|
}
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package no.nordicsemi.android.hts.view
|
package no.nordicsemi.android.hts.view
|
||||||
|
|
||||||
import no.nordicsemi.android.hts.data.TemperatureUnit
|
|
||||||
|
|
||||||
internal sealed class HTSScreenViewEvent
|
internal sealed class HTSScreenViewEvent
|
||||||
|
|
||||||
internal data class OnTemperatureUnitSelected(val value: TemperatureUnit) : HTSScreenViewEvent()
|
internal data class OnTemperatureUnitSelected(val value: TemperatureUnit) : HTSScreenViewEvent()
|
||||||
|
|
||||||
internal object DisconnectEvent : HTSScreenViewEvent()
|
internal object DisconnectEvent : HTSScreenViewEvent()
|
||||||
|
|
||||||
|
internal object NavigateUp : HTSScreenViewEvent()
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
package no.nordicsemi.android.hts.view
|
package no.nordicsemi.android.hts.view
|
||||||
|
|
||||||
import no.nordicsemi.android.hts.data.HTSData
|
import no.nordicsemi.android.hts.data.HTSData
|
||||||
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
|
|
||||||
internal sealed class HTSViewState
|
internal data class HTSViewState(
|
||||||
|
val temperatureUnit: TemperatureUnit = TemperatureUnit.CELSIUS,
|
||||||
|
val htsManagerState: HTSManagerState = NoDeviceState
|
||||||
|
)
|
||||||
|
|
||||||
internal object LoadingState : HTSViewState()
|
internal sealed class HTSManagerState
|
||||||
|
|
||||||
internal data class DisplayDataState(val data: HTSData) : HTSViewState()
|
internal data class WorkingState(val result: BleManagerResult<HTSData>) : HTSManagerState()
|
||||||
|
|
||||||
|
internal object NoDeviceState : HTSManagerState()
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package no.nordicsemi.android.hts.view
|
||||||
|
|
||||||
|
internal enum class TemperatureUnit {
|
||||||
|
CELSIUS,
|
||||||
|
FAHRENHEIT,
|
||||||
|
KELVIN
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import no.nordicsemi.android.hts.repository.HTSService
|
|||||||
import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID
|
import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID
|
||||||
import no.nordicsemi.android.hts.view.*
|
import no.nordicsemi.android.hts.view.*
|
||||||
import no.nordicsemi.android.navigation.*
|
import no.nordicsemi.android.navigation.*
|
||||||
import no.nordicsemi.android.service.BleManagerStatus
|
|
||||||
import no.nordicsemi.android.service.ServiceManager
|
import no.nordicsemi.android.service.ServiceManager
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import no.nordicsemi.android.utils.getDevice
|
import no.nordicsemi.android.utils.getDevice
|
||||||
@@ -23,15 +22,20 @@ internal class HTSViewModel @Inject constructor(
|
|||||||
private val navigationManager: NavigationManager
|
private val navigationManager: NavigationManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val state = repository.data.combine(repository.status) { data, status ->
|
private val _state = MutableStateFlow(HTSViewState())
|
||||||
// when (status) {
|
val state = _state.asStateFlow()
|
||||||
// BleManagerStatus.CONNECTING -> LoadingState
|
|
||||||
// BleManagerStatus.OK,
|
|
||||||
// BleManagerStatus.DISCONNECTED -> DisplayDataState(data)
|
|
||||||
// }
|
|
||||||
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (!repository.isRunning.value) {
|
||||||
|
requestBluetoothDevice()
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.data.onEach {
|
||||||
|
_state.value = _state.value.copy(htsManagerState = WorkingState(it))
|
||||||
|
}.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestBluetoothDevice() {
|
||||||
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(HTS_SERVICE_UUID))
|
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(HTS_SERVICE_UUID))
|
||||||
|
|
||||||
navigationManager.recentResult.onEach {
|
navigationManager.recentResult.onEach {
|
||||||
@@ -39,39 +43,29 @@ internal class HTSViewModel @Inject constructor(
|
|||||||
handleArgs(it)
|
handleArgs(it)
|
||||||
}
|
}
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
|
|
||||||
repository.status.onEach {
|
|
||||||
if (it == BleManagerStatus.DISCONNECTED) {
|
|
||||||
navigationManager.navigateUp()
|
|
||||||
}
|
|
||||||
}.launchIn(viewModelScope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleArgs(args: DestinationResult) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> serviceManager.startService(HTSService::class.java, args.getDevice())
|
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEvent(event: HTSScreenViewEvent) {
|
fun onEvent(event: HTSScreenViewEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
DisconnectEvent -> onDisconnectButtonClick()
|
DisconnectEvent -> disconnect()
|
||||||
is OnTemperatureUnitSelected -> onTemperatureUnitSelected(event)
|
is OnTemperatureUnitSelected -> onTemperatureUnitSelected(event)
|
||||||
|
NavigateUp -> navigationManager.navigateUp()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDisconnectButtonClick() {
|
private fun disconnect() {
|
||||||
repository.sendDisconnectCommand()
|
repository.release()
|
||||||
repository.clear()
|
navigationManager.navigateUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTemperatureUnitSelected(event: OnTemperatureUnitSelected) {
|
private fun onTemperatureUnitSelected(event: OnTemperatureUnitSelected) {
|
||||||
repository.setTemperatureUnit(event.value)
|
_state.value = _state.value.copy(temperatureUnit = event.value)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
super.onCleared()
|
|
||||||
repository.clear()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,5 @@
|
|||||||
<string name="hts_kelvin">%.1f °K</string>
|
<string name="hts_kelvin">%.1f °K</string>
|
||||||
|
|
||||||
<string name="hts_temperature">Temperature</string>
|
<string name="hts_temperature">Temperature</string>
|
||||||
|
<string name="hts_records_section">Records</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user