Fix manager in progress indicator

This commit is contained in:
Sylwester Zieliński
2022-02-15 09:46:45 +01:00
parent a6f0f58448
commit 1ae259fc65
44 changed files with 195 additions and 224 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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