Migrate HTS profile to the new BLE library

This commit is contained in:
Sylwester Zielinski
2023-03-08 16:19:19 +01:00
parent 7630a3113b
commit 9d74000a03
17 changed files with 168 additions and 221 deletions

View File

@@ -0,0 +1,3 @@
package no.nordicsemi.android.service
class DisconnectAndStopEvent

View File

@@ -55,6 +55,7 @@ dependencies {
implementation(libs.nordic.theme)
implementation(libs.nordic.navigation)
implementation(libs.nordic.uiscanner)
implementation(libs.nordic.core)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended)

View File

@@ -6,5 +6,5 @@ import no.nordicsemi.android.kotlin.ble.profile.csc.CSCData
data class CSCServicesData(
val data: CSCData = CSCData(),
val batteryLevel: Int? = null,
val connectionState: GattConnectionState = GattConnectionState.STATE_DISCONNECTED
val connectionState: GattConnectionState? = null
)

View File

@@ -34,8 +34,10 @@ package no.nordicsemi.android.csc.repository
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import no.nordicsemi.android.common.core.simpleSharedFlow
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.csc.data.CSCServicesData
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
@@ -43,6 +45,7 @@ import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.profile.csc.CSCData
import no.nordicsemi.android.kotlin.ble.profile.csc.WheelSize
import no.nordicsemi.android.kotlin.ble.profile.csc.WheelSizes
import no.nordicsemi.android.service.DisconnectAndStopEvent
import no.nordicsemi.android.service.ServiceManager
import javax.inject.Inject
import javax.inject.Singleton
@@ -61,6 +64,9 @@ class CSCRepository @Inject constructor(
private val _data = MutableStateFlow(CSCServicesData())
internal val data = _data.asStateFlow()
private val _stopEvent = simpleSharedFlow<DisconnectAndStopEvent>()
internal val stopEvent = _stopEvent.asSharedFlow()
val isRunning = data.map { it.connectionState == GattConnectionState.STATE_CONNECTED }
fun launch(device: ServerDevice) {
@@ -71,7 +77,7 @@ class CSCRepository @Inject constructor(
_wheelSize.value = wheelSize
}
fun onConnectionStateChanged(connectionState: GattConnectionState) {
fun onConnectionStateChanged(connectionState: GattConnectionState?) {
_data.value = _data.value.copy(connectionState = connectionState)
}
@@ -89,6 +95,6 @@ class CSCRepository @Inject constructor(
fun release() {
logger = null
serviceManager.stopService(CSCService::class.java)
_stopEvent.tryEmit(DisconnectAndStopEvent())
}
}

View File

@@ -41,7 +41,9 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
import no.nordicsemi.android.kotlin.ble.core.client.callback.BleGattClient
import no.nordicsemi.android.kotlin.ble.core.client.service.BleGattServices
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.profile.battery.BatteryLevelParser
import no.nordicsemi.android.kotlin.ble.profile.csc.CSCDataParser
import no.nordicsemi.android.service.DEVICE_DATA
@@ -62,6 +64,8 @@ internal class CSCService : NotificationService() {
@Inject
lateinit var repository: CSCRepository
private lateinit var client: BleGattClient
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
@@ -69,14 +73,20 @@ internal class CSCService : NotificationService() {
startGattClient(device)
repository.stopEvent
.onEach { disconnect() }
.launchIn(lifecycleScope)
return START_REDELIVER_INTENT
}
private fun startGattClient(blinkyDevice: ServerDevice) = lifecycleScope.launch {
val client = blinkyDevice.connect(this@CSCService)
client = blinkyDevice.connect(this@CSCService)
client.connectionState
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
.launchIn(lifecycleScope)
client.services
@@ -102,4 +112,14 @@ internal class CSCService : NotificationService() {
.onEach { repository.onCSCDataChanged(it) }
.launchIn(lifecycleScope)
}
private fun stopIfDisconnected(connectionState: GattConnectionState) {
if (connectionState == GattConnectionState.STATE_DISCONNECTED) {
stopSelf()
}
}
private fun disconnect() {
client.disconnect()
}
}

View File

@@ -49,7 +49,6 @@ import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.csc.R
import no.nordicsemi.android.csc.viewmodel.CSCViewModel
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.ui.view.NavigateUpButton
@@ -74,6 +73,7 @@ fun CSCScreen() {
when (state.cscManagerState) {
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.cscManagerState.result.connectionState) {
null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_DISCONNECTED,
GattConnectionState.STATE_DISCONNECTING -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
@@ -86,15 +86,11 @@ fun CSCScreen() {
@Composable
private fun AppBar(state: CSCViewState, navigateUp: () -> Unit, viewModel: CSCViewModel) {
val toolbarName = (state.cscManagerState as? WorkingState)?.let {
(it.result as? DeviceHolder)?.deviceName()
}
if (toolbarName == null) {
BackIconAppBar(stringResource(id = R.string.csc_title), navigateUp)
} else {
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(OnDisconnectButtonClick) }) {
if (state.deviceName?.isNotBlank() == true) {
LoggerIconAppBar(state.deviceName, navigateUp, { viewModel.onEvent(OnDisconnectButtonClick) }) {
viewModel.onEvent(OpenLogger)
}
} else {
BackIconAppBar(stringResource(id = R.string.csc_title), navigateUp)
}
}

View File

@@ -35,7 +35,8 @@ import no.nordicsemi.android.csc.data.CSCServicesData
internal data class CSCViewState(
val speedUnit: SpeedUnit = SpeedUnit.M_S,
val cscManagerState: CSCMangerState = NoDeviceState
val cscManagerState: CSCMangerState = NoDeviceState,
val deviceName: String? = null
)
internal sealed class CSCMangerState

View File

@@ -99,10 +99,15 @@ internal class CSCViewModel @Inject constructor(
private fun handleResult(result: NavigationResult<ServerDevice>) {
when (result) {
is NavigationResult.Cancelled -> navigationManager.navigateUp()
is NavigationResult.Success -> repository.launch(result.value)
is NavigationResult.Success -> onDeviceSelected(result.value)
}
}
private fun onDeviceSelected(device: ServerDevice) {
_state.value = _state.value.copy(deviceName = device.name)
repository.launch(device)
}
fun onEvent(event: CSCViewEvent) {
when (event) {
is OnSelectedSpeedUnitSelected -> setSpeedUnit(event.selectedSpeedUnit)

View File

@@ -55,6 +55,7 @@ dependencies {
implementation(libs.nordic.uiscanner)
implementation(libs.nordic.navigation)
implementation(libs.nordic.uilogger)
implementation(libs.nordic.core)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended)

View File

@@ -1,119 +0,0 @@
/*
* Copyright (c) 2022, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.hts.data
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
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.ktx.asValidResponseFlow
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter
import java.util.*
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 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(
context: Context,
private val scope: CoroutineScope,
private val logger: NordicLogger
) : BleManager(context) {
private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
private var htCharacteristic: BluetoothGattCharacteristic? = null
private val data = MutableStateFlow(HTSData())
val dataHolder = ConnectionObserverAdapter<HTSData>()
init {
connectionObserver = dataHolder
data.onEach {
dataHolder.setValue(it)
}.launchIn(scope)
}
override fun log(priority: Int, message: String) {
logger.log(priority, message)
}
override fun getMinLogPriority(): Int {
return Log.VERBOSE
}
override fun getGattCallback(): BleManagerGattCallback {
return HTManagerGattCallback()
}
private inner class HTManagerGattCallback : BleManagerGattCallback() {
override fun initialize() {
super.initialize()
setIndicationCallback(htCharacteristic)
.asValidResponseFlow<TemperatureMeasurementResponse>()
.onEach {
data.tryEmit(data.value.copy(temperatureValue = it.temperature))
}.launchIn(scope)
enableIndications(htCharacteristic).enqueue()
setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow<BatteryLevelResponse>().onEach {
data.value = data.value.copy(batteryLevel = it.batteryLevel)
}.launchIn(scope)
enableNotifications(batteryLevelCharacteristic).enqueue()
}
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
gatt.getService(HTS_SERVICE_UUID)?.run {
htCharacteristic = getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID)
}
gatt.getService(BATTERY_SERVICE_UUID)?.run {
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
}
return htCharacteristic != null
}
override fun onServicesInvalidated() {
htCharacteristic = null
batteryLevelCharacteristic = null
}
}
}

View File

@@ -31,7 +31,11 @@
package no.nordicsemi.android.hts.data
internal data class HTSData(
val temperatureValue: Float = 0f,
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.profile.hts.HTSData
internal data class HTSServicesData(
val data: HTSData = HTSData(),
val batteryLevel: Int? = null,
val connectionState: GattConnectionState? = null
)

View File

@@ -33,22 +33,18 @@ package no.nordicsemi.android.hts.repository
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import no.nordicsemi.android.common.core.simpleSharedFlow
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.hts.data.HTSData
import no.nordicsemi.android.hts.data.HTSManager
import no.nordicsemi.android.hts.data.HTSServicesData
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.profile.hts.HTSData
import no.nordicsemi.android.service.DisconnectAndStopEvent
import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst
import javax.inject.Inject
import javax.inject.Singleton
@@ -56,57 +52,40 @@ import javax.inject.Singleton
class HTSRepository @Inject constructor(
@ApplicationContext
private val context: Context,
private val serviceManager: ServiceManager,
private val loggerFactory: NordicLoggerFactory,
private val stringConst: StringConst
private val serviceManager: ServiceManager
) {
private var manager: HTSManager? = null
private var logger: NordicLogger? = null
private val _data = MutableStateFlow<BleManagerResult<HTSData>>(IdleResult())
private val _data = MutableStateFlow(HTSServicesData())
internal val data = _data.asStateFlow()
val isRunning = data.map { it.isRunning() }
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
private val _stopEvent = simpleSharedFlow<DisconnectAndStopEvent>()
internal val stopEvent = _stopEvent.asSharedFlow()
val isRunning = data.map { it.connectionState == GattConnectionState.STATE_CONNECTED }
fun launch(device: ServerDevice) {
serviceManager.startService(HTSService::class.java, device)
}
fun start(device: ServerDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "HTS", device.address).also {
logger = it
}
val manager = HTSManager(context, scope, createdLogger)
this.manager = manager
fun onConnectionStateChanged(connectionState: GattConnectionState?) {
_data.value = _data.value.copy(connectionState = connectionState)
}
manager.dataHolder.status.onEach {
_data.value = it
}.launchIn(scope)
fun onHTSDataChanged(data: HTSData) {
_data.value = _data.value.copy(data = data)
}
scope.launch {
manager.start(device)
}
fun onBatteryLevelChanged(batteryLevel: Int) {
_data.value = _data.value.copy(batteryLevel = batteryLevel)
}
fun openLogger() {
NordicLogger.launch(context, logger)
}
private suspend fun HTSManager.start(device: ServerDevice) {
// try {
// connect(device.device)
// .useAutoConnect(false)
// .retry(3, 100)
// .suspend()
// } catch (e: Exception) {
// e.printStackTrace()
// }
}
fun release() {
manager?.disconnect()?.enqueue()
logger = null
manager = null
_stopEvent.tryEmit(DisconnectAndStopEvent())
}
}

View File

@@ -31,33 +31,94 @@
package no.nordicsemi.android.hts.repository
import android.annotation.SuppressLint
import android.content.Intent
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
import no.nordicsemi.android.kotlin.ble.core.client.callback.BleGattClient
import no.nordicsemi.android.kotlin.ble.core.client.service.BleGattServices
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.profile.battery.BatteryLevelParser
import no.nordicsemi.android.kotlin.ble.profile.hts.HTSDataParser
import no.nordicsemi.android.service.DEVICE_DATA
import no.nordicsemi.android.service.NotificationService
import java.util.*
import javax.inject.Inject
val HTS_SERVICE_UUID: UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb")
private val HTS_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")
@SuppressLint("MissingPermission")
@AndroidEntryPoint
internal class HTSService : NotificationService() {
@Inject
lateinit var repository: HTSRepository
private lateinit var client: BleGattClient
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val device = intent!!.getParcelableExtra<ServerDevice>(DEVICE_DATA)!!
repository.start(device, lifecycleScope)
startGattClient(device)
repository.hasBeenDisconnected.onEach {
if (it) stopSelf()
}.launchIn(lifecycleScope)
repository.stopEvent
.onEach { disconnect() }
.launchIn(lifecycleScope)
return START_REDELIVER_INTENT
}
private fun startGattClient(blinkyDevice: ServerDevice) = lifecycleScope.launch {
client = blinkyDevice.connect(this@HTSService)
client.connectionState
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
.launchIn(lifecycleScope)
client.services
.filterNotNull()
.onEach { configureGatt(it) }
.launchIn(lifecycleScope)
}
private suspend fun configureGatt(services: BleGattServices) {
val htsService = services.findService(HTS_SERVICE_UUID)!!
val htsMeasurementCharacteristic = htsService.findCharacteristic(HTS_MEASUREMENT_CHARACTERISTIC_UUID)!!
val batteryService = services.findService(BATTERY_SERVICE_UUID)!!
val batteryLevelCharacteristic = batteryService.findCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)!!
batteryLevelCharacteristic.getNotifications()
.mapNotNull { BatteryLevelParser.parse(it) }
.onEach { repository.onBatteryLevelChanged(it) }
.launchIn(lifecycleScope)
htsMeasurementCharacteristic.getNotifications()
.mapNotNull { HTSDataParser.parse(it) }
.onEach { repository.onHTSDataChanged(it) }
.launchIn(lifecycleScope)
}
private fun stopIfDisconnected(connectionState: GattConnectionState) {
if (connectionState == GattConnectionState.STATE_DISCONNECTED) {
stopSelf()
}
}
private fun disconnect() {
client.disconnect()
}
}

View File

@@ -45,14 +45,14 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.common.theme.view.RadioButtonGroup
import no.nordicsemi.android.hts.R
import no.nordicsemi.android.hts.data.HTSData
import no.nordicsemi.android.hts.data.HTSServicesData
import no.nordicsemi.android.ui.view.BatteryLevelView
import no.nordicsemi.android.ui.view.KeyValueField
import no.nordicsemi.android.ui.view.ScreenSection
import no.nordicsemi.android.ui.view.SectionTitle
@Composable
internal fun HTSContentView(state: HTSData, temperatureUnit: TemperatureUnit, onEvent: (HTSScreenViewEvent) -> Unit) {
internal fun HTSContentView(state: HTSServicesData, temperatureUnit: TemperatureUnit, onEvent: (HTSScreenViewEvent) -> Unit) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
@@ -76,7 +76,7 @@ internal fun HTSContentView(state: HTSData, temperatureUnit: TemperatureUnit, on
KeyValueField(
stringResource(id = R.string.hts_temperature),
displayTemperature(state.temperatureValue, temperatureUnit)
displayTemperature(state.data.temperature, temperatureUnit)
)
}
@@ -99,5 +99,5 @@ internal fun HTSContentView(state: HTSData, temperatureUnit: TemperatureUnit, on
@Preview
@Composable
private fun Preview() {
HTSContentView(state = HTSData(), TemperatureUnit.CELSIUS) { }
HTSContentView(state = HTSServicesData(), TemperatureUnit.CELSIUS) { }
}

View File

@@ -48,15 +48,7 @@ import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.hts.R
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.ui.view.NavigateUpButton
@@ -80,15 +72,12 @@ fun HTSScreen() {
) {
when (state.htsManagerState) {
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.htsManagerState.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { NavigateUpButton(navigateUp) }
is ConnectedResult -> DeviceConnectingView { NavigateUpButton(navigateUp) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> HTSContentView(state.htsManagerState.result.data, state.temperatureUnit) { viewModel.onEvent(it) }
is WorkingState -> when (state.htsManagerState.result.connectionState) {
null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_DISCONNECTED,
GattConnectionState.STATE_DISCONNECTING -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_CONNECTED -> HTSContentView(state.htsManagerState.result, state.temperatureUnit) { viewModel.onEvent(it) }
}
}
}
@@ -97,16 +86,11 @@ fun HTSScreen() {
@Composable
private fun AppBar(state: HTSViewState, navigateUp: () -> Unit, viewModel: HTSViewModel) {
val toolbarName = (state.htsManagerState as? WorkingState)?.let {
(it.result as? DeviceHolder)?.deviceName()
}
if (toolbarName == null) {
BackIconAppBar(stringResource(id = R.string.hts_title), navigateUp)
} else {
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
if (state.deviceName?.isNotBlank() == true) {
LoggerIconAppBar(state.deviceName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
viewModel.onEvent(OpenLoggerEvent)
}
} else {
BackIconAppBar(stringResource(id = R.string.hts_title), navigateUp)
}
}

View File

@@ -31,16 +31,16 @@
package no.nordicsemi.android.hts.view
import no.nordicsemi.android.hts.data.HTSData
import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.hts.data.HTSServicesData
internal data class HTSViewState(
val temperatureUnit: TemperatureUnit = TemperatureUnit.CELSIUS,
val htsManagerState: HTSManagerState = NoDeviceState
val htsManagerState: HTSManagerState = NoDeviceState,
val deviceName: String? = null
)
internal sealed class HTSManagerState
internal data class WorkingState(val result: BleManagerResult<HTSData>) : HTSManagerState()
internal data class WorkingState(val result: HTSServicesData) : HTSManagerState()
internal object NoDeviceState : HTSManagerState()

View File

@@ -46,8 +46,8 @@ import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.hts.data.HTS_SERVICE_UUID
import no.nordicsemi.android.hts.repository.HTSRepository
import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID
import no.nordicsemi.android.hts.view.DisconnectEvent
import no.nordicsemi.android.hts.view.HTSScreenViewEvent
import no.nordicsemi.android.hts.view.HTSViewState
@@ -56,7 +56,7 @@ import no.nordicsemi.android.hts.view.OnTemperatureUnitSelected
import no.nordicsemi.android.hts.view.OpenLoggerEvent
import no.nordicsemi.android.hts.view.WorkingState
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import javax.inject.Inject
@@ -80,7 +80,7 @@ internal class HTSViewModel @Inject constructor(
repository.data.onEach {
_state.value = _state.value.copy(htsManagerState = WorkingState(it))
(it as? ConnectedResult)?.let {
if (it.connectionState == GattConnectionState.STATE_CONNECTED) {
analytics.logEvent(ProfileConnectedEvent(Profile.HTS))
}
}.launchIn(viewModelScope)
@@ -97,10 +97,15 @@ internal class HTSViewModel @Inject constructor(
private fun handleResult(result: NavigationResult<ServerDevice>) {
when (result) {
is NavigationResult.Cancelled -> navigationManager.navigateUp()
is NavigationResult.Success -> repository.launch(result.value)
is NavigationResult.Success -> onDeviceSelected(result.value)
}
}
private fun onDeviceSelected(device: ServerDevice) {
_state.value = _state.value.copy(deviceName = device.name)
repository.launch(device)
}
fun onEvent(event: HTSScreenViewEvent) {
when (event) {
DisconnectEvent -> disconnect()