Fix HRS profile

This commit is contained in:
Sylwester Zielinski
2023-03-20 13:49:58 +01:00
parent 488104c3d5
commit 33bc7cba6d
7 changed files with 28 additions and 68 deletions

View File

@@ -38,7 +38,9 @@ internal data class HRSServiceData(
val data: List<HRSData> = emptyList(), val data: List<HRSData> = emptyList(),
val bodySensorLocation: Int? = null, val bodySensorLocation: Int? = null,
val batteryLevel: Int? = null, val batteryLevel: Int? = null,
val connectionState: GattConnectionState? = null val connectionState: GattConnectionState? = null,
val zoomIn: Boolean = false,
val deviceName: String? = null
) { ) {
val heartRates = data.map { it.heartRate } val heartRates = data.map { it.heartRate }
} }

View File

@@ -68,6 +68,14 @@ class HRSRepository @Inject constructor(
serviceManager.startService(HRSService::class.java, device) serviceManager.startService(HRSService::class.java, device)
} }
fun onInitComplete(device: ServerDevice) {
_data.value = _data.value.copy(deviceName = device.name)
}
fun switchZoomIn() {
_data.value = _data.value.copy(zoomIn = !_data.value.zoomIn)
}
fun onConnectionStateChanged(connectionState: GattConnectionState?) { fun onConnectionStateChanged(connectionState: GattConnectionState?) {
_data.value = _data.value.copy(connectionState = connectionState) _data.value = _data.value.copy(connectionState = connectionState)
} }

View File

@@ -93,11 +93,11 @@ internal class HRSService : NotificationService() {
client.services client.services
.filterNotNull() .filterNotNull()
.onEach { configureGatt(it) } .onEach { configureGatt(it, device) }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
} }
private suspend fun configureGatt(services: BleGattServices) { private suspend fun configureGatt(services: BleGattServices, device: ServerDevice) {
val htsService = services.findService(HRS_SERVICE_UUID)!! val htsService = services.findService(HRS_SERVICE_UUID)!!
val htsMeasurementCharacteristic = htsService.findCharacteristic(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID)!! val htsMeasurementCharacteristic = htsService.findCharacteristic(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID)!!
val bodySensorLocationCharacteristic = htsService.findCharacteristic(BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID)!! val bodySensorLocationCharacteristic = htsService.findCharacteristic(BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID)!!
@@ -116,6 +116,8 @@ internal class HRSService : NotificationService() {
.mapNotNull { HRSDataParser.parse(it) } .mapNotNull { HRSDataParser.parse(it) }
.onEach { repository.onHRSDataChanged(it) } .onEach { repository.onHRSDataChanged(it) }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
repository.onInitComplete(device)
} }
private fun stopIfDisconnected(connectionState: GattConnectionState) { private fun stopIfDisconnected(connectionState: GattConnectionState) {

View File

@@ -52,7 +52,7 @@ import no.nordicsemi.android.ui.view.ScreenSection
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
@Composable @Composable
internal fun HRSContentView(state: HRSServiceData, zoomIn: Boolean, onEvent: (HRSScreenViewEvent) -> Unit) { internal fun HRSContentView(state: HRSServiceData, onEvent: (HRSScreenViewEvent) -> Unit) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
@@ -61,12 +61,12 @@ internal fun HRSContentView(state: HRSServiceData, zoomIn: Boolean, onEvent: (HR
SectionTitle( SectionTitle(
resId = R.drawable.ic_chart_line, resId = R.drawable.ic_chart_line,
title = stringResource(id = R.string.hrs_section_data), title = stringResource(id = R.string.hrs_section_data),
menu = { Menu(zoomIn, onEvent) } menu = { Menu(state.zoomIn, onEvent) }
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
LineChartView(state, zoomIn) LineChartView(state, state.zoomIn)
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -102,5 +102,5 @@ private fun Menu(zoomIn: Boolean, onEvent: (HRSScreenViewEvent) -> Unit) {
@Preview @Preview
@Composable @Composable
private fun Preview() { private fun Preview() {
HRSContentView(state = HRSServiceData(), zoomIn = false) { } HRSContentView(state = HRSServiceData()) { }
} }

View File

@@ -47,6 +47,7 @@ import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.hrs.R import no.nordicsemi.android.hrs.R
import no.nordicsemi.android.hrs.data.HRSServiceData
import no.nordicsemi.android.hrs.viewmodel.HRSViewModel import no.nordicsemi.android.hrs.viewmodel.HRSViewModel
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
@@ -70,14 +71,15 @@ fun HRSScreen() {
.padding(16.dp) .padding(16.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
when (state.hrsManagerState) { if (state.deviceName == null) {
NoDeviceState -> DeviceConnectingView() DeviceConnectingView()
is WorkingState -> when (state.hrsManagerState.result.connectionState) { } else {
when (state.connectionState) {
null, null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) } GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_DISCONNECTED, GattConnectionState.STATE_DISCONNECTED,
GattConnectionState.STATE_DISCONNECTING -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) } GattConnectionState.STATE_DISCONNECTING -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_CONNECTED -> HRSContentView(state.hrsManagerState.result, state.zoomIn) { viewModel.onEvent(it) } GattConnectionState.STATE_CONNECTED -> HRSContentView(state) { viewModel.onEvent(it) }
} }
} }
} }
@@ -85,7 +87,7 @@ fun HRSScreen() {
} }
@Composable @Composable
private fun AppBar(state: HRSViewState, navigateUp: () -> Unit, viewModel: HRSViewModel) { private fun AppBar(state: HRSServiceData, navigateUp: () -> Unit, viewModel: HRSViewModel) {
if (state.deviceName?.isNotBlank() == true) { if (state.deviceName?.isNotBlank() == true) {
LoggerIconAppBar(state.deviceName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) { LoggerIconAppBar(state.deviceName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
viewModel.onEvent(OpenLoggerEvent) viewModel.onEvent(OpenLoggerEvent)

View File

@@ -1,46 +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.hrs.view
import no.nordicsemi.android.hrs.data.HRSServiceData
internal data class HRSViewState(
val zoomIn: Boolean = false,
val hrsManagerState: HRSManagerState = NoDeviceState,
val deviceName: String? = null
)
internal sealed class HRSManagerState
internal data class WorkingState(val result: HRSServiceData) : HRSManagerState()
internal object NoDeviceState : HRSManagerState()

View File

@@ -35,8 +35,6 @@ import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -50,11 +48,9 @@ import no.nordicsemi.android.hrs.service.HRSRepository
import no.nordicsemi.android.hrs.service.HRS_SERVICE_UUID import no.nordicsemi.android.hrs.service.HRS_SERVICE_UUID
import no.nordicsemi.android.hrs.view.DisconnectEvent import no.nordicsemi.android.hrs.view.DisconnectEvent
import no.nordicsemi.android.hrs.view.HRSScreenViewEvent import no.nordicsemi.android.hrs.view.HRSScreenViewEvent
import no.nordicsemi.android.hrs.view.HRSViewState
import no.nordicsemi.android.hrs.view.NavigateUpEvent import no.nordicsemi.android.hrs.view.NavigateUpEvent
import no.nordicsemi.android.hrs.view.OpenLoggerEvent import no.nordicsemi.android.hrs.view.OpenLoggerEvent
import no.nordicsemi.android.hrs.view.SwitchZoomEvent import no.nordicsemi.android.hrs.view.SwitchZoomEvent
import no.nordicsemi.android.hrs.view.WorkingState
import no.nordicsemi.android.kotlin.ble.core.ServerDevice import no.nordicsemi.android.kotlin.ble.core.ServerDevice
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
@@ -67,8 +63,7 @@ internal class HRSViewModel @Inject constructor(
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
private val _state = MutableStateFlow(HRSViewState()) val state = repository.data
val state = _state.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch {
@@ -78,8 +73,6 @@ internal class HRSViewModel @Inject constructor(
} }
repository.data.onEach { repository.data.onEach {
_state.value = _state.value.copy(hrsManagerState = WorkingState(it))
if (it.connectionState == GattConnectionState.STATE_CONNECTED) { if (it.connectionState == GattConnectionState.STATE_CONNECTED) {
analytics.logEvent(ProfileConnectedEvent(Profile.HRS)) analytics.logEvent(ProfileConnectedEvent(Profile.HRS))
} }
@@ -102,7 +95,6 @@ internal class HRSViewModel @Inject constructor(
} }
private fun onDeviceSelected(device: ServerDevice) { private fun onDeviceSelected(device: ServerDevice) {
_state.value = _state.value.copy(deviceName = device.name)
repository.launch(device) repository.launch(device)
} }
@@ -116,7 +108,7 @@ internal class HRSViewModel @Inject constructor(
} }
private fun onZoomButtonClicked() { private fun onZoomButtonClicked() {
_state.value = _state.value.copy(zoomIn = !_state.value.zoomIn) repository.switchZoomIn()
} }
private fun disconnect() { private fun disconnect() {