mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-19 07:24:22 +01:00
Fix manager in progress indicator
This commit is contained in:
@@ -6,16 +6,12 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.colorResource
|
||||
@@ -41,6 +37,12 @@ fun FeatureButton(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
val color = if (isRunning == true) {
|
||||
colorResource(id = R.color.nordicGrass)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = stringResource(id = name),
|
||||
@@ -49,7 +51,7 @@ fun FeatureButton(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.secondary)
|
||||
.background(color)
|
||||
.padding(16.dp)
|
||||
)
|
||||
|
||||
@@ -61,35 +63,10 @@ fun FeatureButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
// Text(
|
||||
// text = stringResource(id = nameCode),
|
||||
// style = MaterialTheme.typography.headlineSmall,
|
||||
// textAlign = TextAlign.Center
|
||||
// )
|
||||
|
||||
isRunning?.let {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_running_indicator),
|
||||
contentDescription = stringResource(id = R.string.running_profile_icon),
|
||||
tint = getRunningIndicatorColor(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getRunningIndicatorColor(isRunning: Boolean): Color {
|
||||
return if (isRunning) {
|
||||
colorResource(id = R.color.nordicGrass)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun FeatureButtonPreview() {
|
||||
|
||||
@@ -4,14 +4,21 @@ enum class BleManagerStatus {
|
||||
CONNECTING, OK, LINK_LOSS, DISCONNECTED, MISSING_SERVICE
|
||||
}
|
||||
|
||||
sealed class BleManagerResult <T>
|
||||
sealed class BleManagerResult <T> {
|
||||
|
||||
fun isRunning(): Boolean {
|
||||
return this is SuccessResult
|
||||
}
|
||||
|
||||
fun hasBeenDisconnected(): Boolean {
|
||||
return this is LinkLossResult || this is DisconnectedResult || this is MissingServiceResult
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectingResult<T> : BleManagerResult<T>()
|
||||
class ReadyResult<T> : BleManagerResult<T>()
|
||||
|
||||
data class SuccessResult<T>(val data: T) : BleManagerResult<T>()
|
||||
|
||||
class LinkLossResult<T> : BleManagerResult<T>()
|
||||
class DisconnectedResult<T> : BleManagerResult<T>()
|
||||
class UnknownErrorResult<T> : BleManagerResult<T>()
|
||||
class MissingServiceResult<T> : BleManagerResult<T>()
|
||||
|
||||
|
||||
@@ -13,18 +13,20 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
||||
private val _status = MutableStateFlow<BleManagerResult<T>>(ConnectingResult())
|
||||
val status = _status.asStateFlow()
|
||||
|
||||
private var lastValue: T? = null
|
||||
|
||||
override fun onDeviceConnecting(device: BluetoothDevice) {
|
||||
Log.d(TAG, "onDeviceConnecting()")
|
||||
}
|
||||
|
||||
override fun onDeviceConnected(device: BluetoothDevice) {
|
||||
Log.d(TAG, "onDeviceConnected()")
|
||||
_status.value = ReadyResult()
|
||||
_status.value = SuccessResult(lastValue!!)
|
||||
}
|
||||
|
||||
override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) {
|
||||
Log.d(TAG, "onDeviceFailedToConnect()")
|
||||
_status.value = DisconnectedResult()
|
||||
Log.d(TAG, "onDeviceFailedToConnect(), reason: $reason")
|
||||
_status.value = MissingServiceResult()
|
||||
}
|
||||
|
||||
override fun onDeviceReady(device: BluetoothDevice) {
|
||||
@@ -36,15 +38,19 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
||||
}
|
||||
|
||||
override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) {
|
||||
Log.d(TAG, "onDeviceDisconnected()")
|
||||
Log.d(TAG, "onDeviceDisconnected(), reason: $reason")
|
||||
_status.value = when (reason) {
|
||||
ConnectionObserver.REASON_NOT_SUPPORTED -> MissingServiceResult()
|
||||
ConnectionObserver.REASON_LINK_LOSS -> LinkLossResult()
|
||||
else -> DisconnectedResult()
|
||||
ConnectionObserver.REASON_SUCCESS -> DisconnectedResult()
|
||||
else -> UnknownErrorResult()
|
||||
}
|
||||
}
|
||||
|
||||
fun setValue(value: T) {
|
||||
_status.value = SuccessResult(value)
|
||||
lastValue = value
|
||||
if (_status.value.isRunning()) {
|
||||
_status.value = SuccessResult(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import no.nordicsemi.android.theme.R
|
||||
import no.nordicsemi.android.theme.view.ScreenSection
|
||||
|
||||
enum class Reason {
|
||||
USER, LINK_LOSS, MISSING_SERVICE
|
||||
USER, UNKNOWN, LINK_LOSS, MISSING_SERVICE
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -57,6 +57,7 @@ fun DeviceDisconnectedView(reason: Reason, navigateUp: () -> Unit) {
|
||||
Reason.USER -> stringResource(id = R.string.device_reason_user)
|
||||
Reason.LINK_LOSS -> stringResource(id = R.string.device_reason_link_loss)
|
||||
Reason.MISSING_SERVICE -> stringResource(id = R.string.device_reason_missing_service)
|
||||
Reason.UNKNOWN -> stringResource(id = R.string.device_reason_unknown)
|
||||
}
|
||||
|
||||
Text(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="dialog">Dialog</string>
|
||||
<string name="cancel">CANCEL</string>
|
||||
|
||||
<string name="go_up">Go up</string>
|
||||
<string name="go_up">Back</string>
|
||||
|
||||
<string name="close_app">Close the application.</string>
|
||||
<string name="back_screen">Close the current screen.</string>
|
||||
@@ -15,10 +15,11 @@
|
||||
|
||||
<string name="device_disconnected">Disconnected</string>
|
||||
<string name="device_reason_user">Device disconnected successfully.</string>
|
||||
<string name="device_reason_unknown">Device disconnected with unknown reason.</string>
|
||||
<string name="device_reason_link_loss">Device signal has been lost.</string>
|
||||
<string name="device_reason_missing_service">Device was disconnected, because required services are missing.</string>
|
||||
|
||||
<string name="device_connecting">Connecting</string>
|
||||
<string name="device_connecting">Connecting...</string>
|
||||
<string name="device_explanation">The mobile is trying to connect to peripheral device.</string>
|
||||
<string name="device_please_wait">Please wait...</string>
|
||||
</resources>
|
||||
@@ -9,6 +9,9 @@ import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.ParcelableArgument
|
||||
import no.nordicsemi.android.navigation.SuccessDestinationResult
|
||||
|
||||
@@ -34,11 +34,11 @@ fun BPSScreen() {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> BPSContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources>
|
||||
<string name="bps_title">Blood pressure</string>
|
||||
|
||||
<string name="bps_records">Records</string>
|
||||
<string name="bps_records">Data</string>
|
||||
|
||||
<string name="bps_systolic">Systolic</string>
|
||||
<string name="bps_diastolic">Diastolic</string>
|
||||
|
||||
@@ -4,10 +4,7 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.cgms.repository.CGMManager
|
||||
@@ -30,8 +27,8 @@ class CGMRepository @Inject constructor(
|
||||
private val _data = MutableStateFlow<BleManagerResult<CGMData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(CGMService::class.java, device)
|
||||
@@ -56,7 +53,6 @@ class CGMRepository @Inject constructor(
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
_isRunning.value = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -75,9 +71,7 @@ class CGMRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun release() {
|
||||
serviceManager.stopService(CGMService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.util.SparseArray
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -48,6 +48,7 @@ import no.nordicsemi.android.cgms.data.CGMData
|
||||
import no.nordicsemi.android.cgms.data.CGMRecord
|
||||
import no.nordicsemi.android.cgms.data.RequestStatus
|
||||
import no.nordicsemi.android.service.ConnectionObserverAdapter
|
||||
import no.nordicsemi.android.utils.launchWithCatch
|
||||
import java.util.*
|
||||
|
||||
val CGMS_SERVICE_UUID: UUID = UUID.fromString("0000181F-0000-1000-8000-00805f9b34fb")
|
||||
@@ -80,10 +81,6 @@ internal class CGMManager(
|
||||
|
||||
private var sessionStartTime: Long = 0
|
||||
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, t ->
|
||||
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
|
||||
}
|
||||
|
||||
private val data = MutableStateFlow(CGMData())
|
||||
val dataHolder = ConnectionObserverAdapter<CGMData>()
|
||||
|
||||
@@ -101,32 +98,17 @@ internal class CGMManager(
|
||||
|
||||
override fun log(priority: Int, message: String) {
|
||||
super.log(priority, message)
|
||||
Log.d("COROUTINE-EXCEPTION", message)
|
||||
Log.d("CGM-PROFILE", message)
|
||||
}
|
||||
|
||||
private inner class CGMManagerGattCallback : BleManagerGattCallback() {
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
|
||||
scope.launch(exceptionHandler) {
|
||||
val response =
|
||||
readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse<CGMFeatureResponse>()
|
||||
this@CGMManager.secured = response.features.e2eCrcSupported
|
||||
}
|
||||
|
||||
scope.launch(exceptionHandler) {
|
||||
val response =
|
||||
readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse<CGMFeatureResponse>()
|
||||
this@CGMManager.secured = response.features.e2eCrcSupported
|
||||
}
|
||||
|
||||
scope.launch(exceptionHandler) {
|
||||
val response =
|
||||
readCharacteristic(cgmStatusCharacteristic).suspendForValidResponse<CGMStatusResponse>()
|
||||
if (response.status?.sessionStopped == false) {
|
||||
sessionStartTime = System.currentTimeMillis() - response.timeOffset * 60000L
|
||||
}
|
||||
}
|
||||
enableNotifications(cgmMeasurementCharacteristic).enqueue()
|
||||
enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue()
|
||||
enableIndications(recordAccessControlPointCharacteristic).enqueue()
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
|
||||
setNotificationCallback(cgmMeasurementCharacteristic).asValidResponseFlow<ContinuousGlucoseMeasurementResponse>()
|
||||
.onEach {
|
||||
@@ -164,7 +146,7 @@ internal class CGMManager(
|
||||
|
||||
setIndicationCallback(recordAccessControlPointCharacteristic).asValidResponseFlow<RecordAccessControlPointResponse>()
|
||||
.onEach {
|
||||
if (it.isOperationCompleted && !it.wereRecordsFound() && it.numberOfRecords > 0) {
|
||||
if (it.isOperationCompleted && it.wereRecordsFound() && it.numberOfRecords > 0) {
|
||||
onRecordsReceived(it)
|
||||
} else if (it.isOperationCompleted && !it.wereRecordsFound()) {
|
||||
onNoRecordsFound()
|
||||
@@ -180,13 +162,16 @@ internal class CGMManager(
|
||||
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||
}.launchIn(scope)
|
||||
|
||||
enableNotifications(cgmMeasurementCharacteristic).enqueue()
|
||||
enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue()
|
||||
enableIndications(recordAccessControlPointCharacteristic).enqueue()
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
scope.launchWithCatch {
|
||||
val cgmResponse = readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse<CGMFeatureResponse>()
|
||||
this@CGMManager.secured = cgmResponse.features.e2eCrcSupported
|
||||
|
||||
if (sessionStartTime == 0L) {
|
||||
scope.launch(exceptionHandler) {
|
||||
val response = readCharacteristic(cgmStatusCharacteristic).suspendForValidResponse<CGMStatusResponse>()
|
||||
if (response.status?.sessionStopped == false) {
|
||||
sessionStartTime = System.currentTimeMillis() - response.timeOffset * 60000L
|
||||
}
|
||||
|
||||
if (sessionStartTime == 0L) {
|
||||
writeCharacteristic(
|
||||
cgmSpecificOpsControlPointCharacteristic,
|
||||
CGMSpecificOpsControlPointData.startSession(secured),
|
||||
@@ -282,7 +267,7 @@ internal class CGMManager(
|
||||
clear()
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
scope.launch(exceptionHandler) {
|
||||
scope.launchWithCatch {
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportLastStoredRecord(),
|
||||
@@ -296,7 +281,7 @@ internal class CGMManager(
|
||||
clear()
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
scope.launch(exceptionHandler) {
|
||||
scope.launchWithCatch {
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportFirstStoredRecord(),
|
||||
@@ -310,7 +295,7 @@ internal class CGMManager(
|
||||
clear()
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
scope.launch(exceptionHandler) {
|
||||
scope.launchWithCatch {
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportNumberOfAllStoredRecords(),
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
@@ -22,6 +24,10 @@ internal class CGMService : NotificationService() {
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
repository.hasBeenDisconnected.onEach {
|
||||
if (it) stopSelf()
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ fun CGMScreen() {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> CGMContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -3,10 +3,8 @@ package no.nordicsemi.android.cgms.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||
import no.nordicsemi.android.cgms.repository.CGMS_SERVICE_UUID
|
||||
@@ -27,8 +25,10 @@ internal class CGMScreenViewModel @Inject constructor(
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
viewModelScope.launch {
|
||||
if (repository.isRunning.firstOrNull() == false) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
|
||||
@@ -4,10 +4,7 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.csc.repository.CSCManager
|
||||
@@ -29,8 +26,8 @@ class CSCRepository @Inject constructor(
|
||||
private val _data = MutableStateFlow<BleManagerResult<CSCData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(CSCService::class.java, device)
|
||||
@@ -59,16 +56,13 @@ class CSCRepository @Inject constructor(
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
@@ -22,6 +24,10 @@ internal class CSCService : NotificationService() {
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
repository.hasBeenDisconnected.onEach {
|
||||
if (it) stopSelf()
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ fun CSCScreen() {
|
||||
when (state.cscManagerState) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.cscManagerState.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> CSCContentView(state.cscManagerState.result.data, state.speedUnit) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -3,10 +3,8 @@ package no.nordicsemi.android.csc.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.csc.repository.CSC_SERVICE_UUID
|
||||
import no.nordicsemi.android.csc.view.*
|
||||
@@ -26,8 +24,10 @@ internal class CSCViewModel @Inject constructor(
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
viewModelScope.launch {
|
||||
if (repository.isRunning.firstOrNull() == false) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
|
||||
@@ -175,7 +175,7 @@ internal class GLSManager @Inject constructor(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null && glucoseMeasurementContextCharacteristic != null && batteryLevelCharacteristic != null
|
||||
return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null && batteryLevelCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {}
|
||||
|
||||
@@ -34,11 +34,11 @@ fun GLSScreen() {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> GLSContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -4,10 +4,7 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.hrs.service.HRSManager
|
||||
@@ -29,8 +26,8 @@ class HRSRepository @Inject constructor(
|
||||
private val _data = MutableStateFlow<BleManagerResult<HRSData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(HRSService::class.java, device)
|
||||
@@ -55,16 +52,13 @@ class HRSRepository @Inject constructor(
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
_isRunning.value = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
serviceManager.stopService(HRSService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,10 @@ package no.nordicsemi.android.hrs.service
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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
|
||||
import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationResponse
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
@@ -22,6 +24,10 @@ internal class HRSService : NotificationService() {
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
repository.hasBeenDisconnected.onEach {
|
||||
if (it) stopSelf()
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ fun HRSScreen() {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> HRSContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -3,10 +3,8 @@ package no.nordicsemi.android.hrs.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.hrs.service.HRS_SERVICE_UUID
|
||||
import no.nordicsemi.android.hrs.view.*
|
||||
@@ -26,8 +24,10 @@ internal class HRSViewModel @Inject constructor(
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
viewModelScope.launch {
|
||||
if (repository.isRunning.firstOrNull() == false) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
|
||||
@@ -4,10 +4,7 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.hts.repository.HTSManager
|
||||
@@ -29,8 +26,8 @@ class HTSRepository @Inject constructor(
|
||||
private val _data = MutableStateFlow<BleManagerResult<HTSData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(HTSService::class.java, device)
|
||||
@@ -55,16 +52,13 @@ class HTSRepository @Inject constructor(
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
_isRunning.value = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
serviceManager.stopService(HTSService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
@@ -22,6 +24,10 @@ internal class HTSService : NotificationService() {
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
repository.hasBeenDisconnected.onEach {
|
||||
if (it) stopSelf()
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ fun HTSScreen() {
|
||||
when (state.htsManagerState) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.htsManagerState.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> HTSContentView(state.htsManagerState.result.data, state.temperatureUnit) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -3,15 +3,12 @@ package no.nordicsemi.android.hts.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID
|
||||
import no.nordicsemi.android.hts.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.utils.getDevice
|
||||
import no.nordicsemi.ui.scanner.ScannerDestinationId
|
||||
@@ -20,7 +17,6 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
internal class HTSViewModel @Inject constructor(
|
||||
private val repository: HTSRepository,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val navigationManager: NavigationManager
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -28,8 +24,10 @@ internal class HTSViewModel @Inject constructor(
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
viewModelScope.launch {
|
||||
if (repository.isRunning.firstOrNull() == false) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
<string name="hts_kelvin">%.1f °K</string>
|
||||
|
||||
<string name="hts_temperature">Temperature</string>
|
||||
<string name="hts_records_section">Records</string>
|
||||
<string name="hts_records_section">Data</string>
|
||||
</resources>
|
||||
|
||||
@@ -4,10 +4,7 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.prx.repository.AlarmHandler
|
||||
@@ -35,8 +32,8 @@ class PRXRepository @Inject internal constructor(
|
||||
private val _data = MutableStateFlow<BleManagerResult<PRXData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(PRXService::class.java, device)
|
||||
@@ -64,7 +61,6 @@ class PRXRepository @Inject internal constructor(
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
_isRunning.value = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -89,9 +85,7 @@ class PRXRepository @Inject internal constructor(
|
||||
}
|
||||
|
||||
fun release() {
|
||||
serviceManager.stopService(PRXService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
@@ -22,6 +24,10 @@ internal class PRXService : NotificationService() {
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
repository.hasBeenDisconnected.onEach {
|
||||
if (it) stopSelf()
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ fun PRXScreen() {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) }
|
||||
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
|
||||
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> ContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -3,10 +3,8 @@ package no.nordicsemi.android.prx.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.prx.repository.PRX_SERVICE_UUID
|
||||
@@ -26,8 +24,10 @@ internal class PRXViewModel @Inject constructor(
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
viewModelScope.launch {
|
||||
if (repository.isRunning.firstOrNull() == false) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="prx_silent_me">SILENT ME</string>
|
||||
<string name="prx_find_me">FIND ME</string>
|
||||
|
||||
<string name="prx_records">Records</string>
|
||||
<string name="prx_records">Data</string>
|
||||
<string name="prx_settings">Settings</string>
|
||||
|
||||
<string name="prx_alarm_level_none">none</string>
|
||||
|
||||
@@ -4,10 +4,7 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.rscs.repository.RSCSManager
|
||||
@@ -29,8 +26,8 @@ class RSCSRepository @Inject constructor(
|
||||
private val _data = MutableStateFlow<BleManagerResult<RSCSData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(RSCSService::class.java, device)
|
||||
@@ -55,16 +52,13 @@ class RSCSRepository @Inject constructor(
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
_isRunning.value = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
serviceManager.stopService(RSCSService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ 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.rsc.RunningSpeedAndCadenceMeasurementResponse
|
||||
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
@@ -76,10 +77,14 @@ internal class RSCSManager internal constructor(
|
||||
totalDistance = it.totalDistance
|
||||
))
|
||||
}.launchIn(scope)
|
||||
enableNotifications(rscMeasurementCharacteristic).enqueue()
|
||||
|
||||
scope.launchWithCatch {
|
||||
enableNotifications(rscMeasurementCharacteristic).suspend()
|
||||
}
|
||||
setNotificationCallback(batteryLevelCharacteristic)
|
||||
.asValidResponseFlow<BatteryLevelResponse>()
|
||||
.onEach {
|
||||
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||
}.launchIn(scope)
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
}
|
||||
|
||||
public override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
|
||||
@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -25,6 +24,10 @@ internal class RSCSService : NotificationService() {
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
repository.hasBeenDisconnected.onEach {
|
||||
if (it) stopSelf()
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,20 +26,17 @@ fun RSCSScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||
|
||||
BackIconAppBar(stringResource(id = R.string.rscs_title)) {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}
|
||||
BackIconAppBar(stringResource(id = R.string.rscs_title), navigateUp)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult
|
||||
-> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> RSCSContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -3,10 +3,8 @@ package no.nordicsemi.android.rscs.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.rscs.repository.RSCS_SERVICE_UUID
|
||||
@@ -26,8 +24,10 @@ internal class RSCSViewModel @Inject constructor(
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
viewModelScope.launch {
|
||||
if (repository.isRunning.firstOrNull() == false) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
|
||||
@@ -4,10 +4,7 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
@@ -29,8 +26,8 @@ class UARTRepository @Inject constructor(
|
||||
private val _data = MutableStateFlow<BleManagerResult<UARTData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(UARTService::class.java, device)
|
||||
@@ -59,16 +56,13 @@ class UARTRepository @Inject constructor(
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
_isRunning.value = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
serviceManager.stopService(UARTService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import no.nordicsemi.android.utils.EMPTY
|
||||
import no.nordicsemi.android.utils.launchWithCatch
|
||||
import java.util.*
|
||||
|
||||
val UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
val UART_SERVICE_UUID: UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
private val UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
private val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
|
||||
@@ -116,7 +116,7 @@ internal class UARTManager(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return rxCharacteristic != null && txCharacteristic != null && batteryLevelCharacteristic != null && (writeRequest || writeCommand)
|
||||
return rxCharacteristic != null && txCharacteristic != null && (writeRequest || writeCommand)
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
@@ -22,6 +24,10 @@ internal class UARTService : NotificationService() {
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
repository.hasBeenDisconnected.onEach {
|
||||
if (it) stopSelf()
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,20 +26,17 @@ fun UARTScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||
|
||||
BackIconAppBar(stringResource(id = R.string.uart_title)) {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}
|
||||
BackIconAppBar(stringResource(id = R.string.uart_title), navigateUp)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state.uartManagerState) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.uartManagerState.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult
|
||||
-> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is ConnectingResult -> 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 UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> UARTContentView(state.uartManagerState.result.data, state.macros) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
@@ -3,10 +3,8 @@ package no.nordicsemi.android.uart.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.uart.data.UARTMacro
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
@@ -27,8 +25,10 @@ internal class UARTViewModel @Inject constructor(
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
viewModelScope.launch {
|
||||
if (repository.isRunning.firstOrNull() == false) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
|
||||
Reference in New Issue
Block a user