Change approach for service & viewmodel state managment for all profiles

This commit is contained in:
Sylwester Zielinski
2023-04-27 16:00:31 +02:00
parent 38438e4af2
commit 24db1bcc2b
37 changed files with 244 additions and 201 deletions

View File

@@ -77,9 +77,7 @@ fun BPSScreen() {
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
if (state.deviceName == null) {
DeviceConnectingView { NavigateUpButton(navigateUp) }
} else when (state.result.connectionState?.state) {
when (state.result.connectionState?.state) {
null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_DISCONNECTED,

View File

@@ -128,11 +128,9 @@ internal class BPSViewModel @Inject constructor(
.onEach { onDataUpdate(it) }
.onEach { stopIfDisconnected(it.state) }
.onEach { logAnalytics(it.state) }
.onEach { unlockUiIfDisconnected(it, device) }
.launchIn(viewModelScope)
if (!client.isConnected) {
_state.value = _state.value.copy(deviceName = device.name)
return@launch
}
@@ -142,12 +140,6 @@ internal class BPSViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun unlockUiIfDisconnected(connectionState: GattConnectionStateWithStatus, device: ServerDevice) {
if (connectionState.state == GattConnectionState.STATE_DISCONNECTED) {
_state.value = _state.value.copy(deviceName = device.name)
}
}
private suspend fun configureGatt(services: BleGattServices) {
val bpsService = services.findService(BPS_SERVICE_UUID)!!
val bpmCharacteristic = bpsService.findCharacteristic(BPM_CHARACTERISTIC_UUID)!!

View File

@@ -8,8 +8,7 @@ internal data class CGMServiceData(
val records: List<CGMRecordWithSequenceNumber> = emptyList(),
val batteryLevel: Int? = null,
val connectionState: GattConnectionStateWithStatus? = null,
val requestStatus: RequestStatus = RequestStatus.IDLE,
val deviceName: String? = null
val requestStatus: RequestStatus = RequestStatus.IDLE
)
data class CGMRecordWithSequenceNumber(

View File

@@ -32,7 +32,6 @@
package no.nordicsemi.android.cgms.repository
import android.content.Context
import android.util.Log
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
@@ -74,6 +73,23 @@ class CGMRepository @Inject constructor(
val hasRecords = data.value.records.isNotEmpty()
val highestSequenceNumber = data.value.records.maxOfOrNull { it.sequenceNumber } ?: -1
private var isOnScreen = false
private var isServiceRunning = false
fun setOnScreen(isOnScreen: Boolean) {
this.isOnScreen = isOnScreen
if (shouldClean()) clean()
}
fun setServiceRunning(serviceRunning: Boolean) {
this.isServiceRunning = serviceRunning
if (shouldClean()) clean()
}
private fun shouldClean() = !isOnScreen && !isServiceRunning
fun launch(device: ServerDevice) {
serviceManager.startService(CGMService::class.java, device)
}
@@ -102,16 +118,15 @@ class CGMRepository @Inject constructor(
_loggerEvent.tryEmit(OpenLoggerEvent())
}
fun onInitComplete(device: ServerDevice) {
_data.value = _data.value.copy(deviceName = device.name)
}
fun clear() {
_data.value = _data.value.copy(records = emptyList())
}
fun release() {
_data.value = CGMServiceData()
fun disconnect() {
_stopEvent.tryEmit(DisconnectAndStopEvent())
}
fun clean() {
_data.value = CGMServiceData()
}
}

View File

@@ -104,11 +104,11 @@ internal class CGMService : NotificationService() {
private lateinit var recordAccessControlPointCharacteristic: BleGattCharacteristic
private var hasBeenInitialized: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
repository.setServiceRunning(true)
val device = intent!!.getParcelableExtra<ServerDevice>(DEVICE_DATA)!!
startGattClient(device)
@@ -146,22 +146,19 @@ internal class CGMService : NotificationService() {
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
.onEach { unlockUiIfDisconnected(it, device) }
.launchIn(lifecycleScope)
if (!client.isConnected) {
hasBeenInitialized = true
repository.onInitComplete(device)
return@launch
}
client.discoverServices()
.filterNotNull()
.onEach { configureGatt(it, device) }
.onEach { configureGatt(it) }
.launchIn(lifecycleScope)
}
private suspend fun configureGatt(services: BleGattServices, device: ServerDevice) {
private suspend fun configureGatt(services: BleGattServices) {
val cgmService = services.findService(CGMS_SERVICE_UUID)!!
val statusCharacteristic = cgmService.findCharacteristic(CGM_STATUS_UUID)!!
val featureCharacteristic = cgmService.findCharacteristic(CGM_FEATURE_UUID)!!
@@ -232,9 +229,6 @@ internal class CGMService : NotificationService() {
if (sessionStartTime == 0L) {
opsControlPointCharacteristic.write(CGMSpecificOpsControlPointData.startSession(secured).value!!)
}
hasBeenInitialized = true
repository.onInitComplete(device)
}
private fun onAccessControlPointDataReceived(data: RecordAccessControlPointData) = lifecycleScope.launch {
@@ -317,13 +311,12 @@ internal class CGMService : NotificationService() {
}
}
private fun unlockUiIfDisconnected(connectionState: GattConnectionStateWithStatus, device: ServerDevice) {
if (connectionState.state == GattConnectionState.STATE_DISCONNECTED && !hasBeenInitialized) {
repository.onInitComplete(device)
}
}
private fun disconnect() {
client.disconnect()
}
override fun onDestroy() {
super.onDestroy()
repository.setServiceRunning(false)
}
}

View File

@@ -55,13 +55,14 @@ import no.nordicsemi.android.ui.view.ProfileAppBar
fun CGMScreen() {
val viewModel: CGMViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
val deviceName = viewModel.deviceName.collectAsState().value
val navigateUp = { viewModel.onEvent(NavigateUp) }
Scaffold(
topBar = {
ProfileAppBar(
deviceName = state.deviceName,
deviceName = deviceName,
connectionState = state.connectionState,
title = R.string.cgms_title,
navigateUp = navigateUp,
@@ -76,9 +77,7 @@ fun CGMScreen() {
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
if (state.deviceName == null) {
DeviceConnectingView { NavigateUpButton(navigateUp) }
} else when (state.connectionState?.state) {
when (state.connectionState?.state) {
null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }

View File

@@ -35,6 +35,8 @@ import android.os.ParcelUuid
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.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -66,7 +68,12 @@ internal class CGMViewModel @Inject constructor(
val state = repository.data
private val _deviceName = MutableStateFlow<String?>(null)
val deviceName = _deviceName.asStateFlow()
init {
repository.setOnScreen(true)
viewModelScope.launch {
if (repository.isRunning.firstOrNull() == false) {
requestBluetoothDevice()
@@ -105,6 +112,7 @@ internal class CGMViewModel @Inject constructor(
}
private fun onDeviceSelected(device: ServerDevice) {
_deviceName.value = device.name
repository.launch(device)
}
@@ -113,6 +121,11 @@ internal class CGMViewModel @Inject constructor(
}
private fun disconnect() {
repository.release()
repository.disconnect()
}
override fun onCleared() {
super.onCleared()
repository.setOnScreen(false)
}
}

View File

@@ -77,14 +77,6 @@ class CSCRepository @Inject constructor(
serviceManager.startService(CSCService::class.java, device)
}
fun onInitComplete(device: ServerDevice) {
Log.d("AAATESTAAA", "onInitComplete: ${data.value}")
if (_data.value.deviceName == null) {
Log.d("AAATESTAAA", "AAA")
_data.value = _data.value.copy(deviceName = device.name)
}
}
internal fun setSpeedUnit(speedUnit: SpeedUnit) {
_data.value = _data.value.copy(speedUnit = speedUnit)
}
@@ -109,8 +101,7 @@ class CSCRepository @Inject constructor(
_loggerEvent.tryEmit(OpenLoggerEvent())
}
fun release() {
Log.d("AAATESTAAA", "release: ${data.value}")
fun disconnect() {
_data.value = CSCServiceData()
_stopEvent.tryEmit(DisconnectAndStopEvent())
}

View File

@@ -73,8 +73,6 @@ internal class CSCService : NotificationService() {
private lateinit var client: BleGattClient
private var hasBeenInitialized: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
@@ -102,22 +100,19 @@ internal class CSCService : NotificationService() {
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
.onEach { unlockUiIfDisconnected(it, device) }
.launchIn(lifecycleScope)
if (!client.isConnected) {
hasBeenInitialized = true
repository.onInitComplete(device)
return@launch
}
client.discoverServices()
.filterNotNull()
.onEach { configureGatt(it, device) }
.onEach { configureGatt(it) }
.launchIn(lifecycleScope)
}
private suspend fun configureGatt(services: BleGattServices, device: ServerDevice) {
private suspend fun configureGatt(services: BleGattServices) {
val cscService = services.findService(CSC_SERVICE_UUID)!!
val cscMeasurementCharacteristic = cscService.findCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID)!!
val batteryService = services.findService(BATTERY_SERVICE_UUID)!!
@@ -133,9 +128,6 @@ internal class CSCService : NotificationService() {
.mapNotNull { cscDataParser.parse(it, repository.wheelSize.value) }
.onEach { repository.onCSCDataChanged(it) }
.launchIn(lifecycleScope)
hasBeenInitialized = true
repository.onInitComplete(device)
}
private fun stopIfDisconnected(connectionState: GattConnectionStateWithStatus) {
@@ -144,13 +136,6 @@ internal class CSCService : NotificationService() {
}
}
private fun unlockUiIfDisconnected(connectionState: GattConnectionStateWithStatus, device: ServerDevice) {
if (connectionState.state == GattConnectionState.STATE_DISCONNECTED && !hasBeenInitialized) {
hasBeenInitialized = true
repository.onInitComplete(device)
}
}
private fun disconnect() {
client.disconnect()
}

View File

@@ -58,8 +58,6 @@ fun CSCScreen() {
val viewModel: CSCViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
Log.d("AAATESTAAA", "State: ${state}")
val navigateUp = { viewModel.onEvent(NavigateUp) }
Scaffold(

View File

@@ -115,7 +115,7 @@ internal class CSCViewModel @Inject constructor(
}
private fun disconnect() {
repository.release()
repository.disconnect()
navigationManager.navigateUp()
}
}

View File

@@ -31,10 +31,9 @@
package no.nordicsemi.android.gls.data
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus
import no.nordicsemi.android.kotlin.ble.profile.gls.data.GLSRecord
import no.nordicsemi.android.kotlin.ble.profile.gls.data.GLSMeasurementContext
import no.nordicsemi.android.kotlin.ble.profile.gls.data.GLSRecord
import no.nordicsemi.android.kotlin.ble.profile.gls.data.RequestStatus
internal data class GLSServiceData(

View File

@@ -163,17 +163,15 @@ internal class GLSViewModel @Inject constructor(
client = device.connect(context, logger = logger)
client.waitForBonding()
client.connectionStateWithStatus
.filterNotNull()
.onEach { _state.value = _state.value.copyWithNewConnectionState(it) }
.onEach { logAnalytics(it) }
.onEach { unlockUiIfDisconnected(it, device) }
.launchIn(viewModelScope)
client.waitForBonding()
if (!client.isConnected) {
_state.value = _state.value.copy(deviceName = device.name)
return@launch
}
@@ -189,12 +187,6 @@ internal class GLSViewModel @Inject constructor(
}
}
private fun unlockUiIfDisconnected(connectionState: GattConnectionStateWithStatus, device: ServerDevice) {
if (connectionState.state == GattConnectionState.STATE_DISCONNECTED) {
_state.value = _state.value.copy(deviceName = device.name)
}
}
private suspend fun configureGatt(services: BleGattServices, device: ServerDevice) {
val glsService = services.findService(GLS_SERVICE_UUID)!!
glucoseMeasurementCharacteristic = glsService.findCharacteristic(GM_CHARACTERISTIC)!!
@@ -221,8 +213,6 @@ internal class GLSViewModel @Inject constructor(
.mapNotNull { RecordAccessControlPointParser.parse(it) }
.onEach { onAccessControlPointDataReceived(it) }
.launchIn(viewModelScope)
_state.value = _state.value.copy(deviceName = device.name) //prevents UI from appearing before BLE connection is set up
}
private fun onAccessControlPointDataReceived(data: RecordAccessControlPointData) = viewModelScope.launch {

View File

@@ -40,8 +40,7 @@ internal data class HRSServiceData(
val bodySensorLocation: Int? = null,
val batteryLevel: Int? = null,
val connectionState: GattConnectionStateWithStatus? = null,
val zoomIn: Boolean = false,
val deviceName: String? = null
val zoomIn: Boolean = false
) {
val heartRates = data.map { it.heartRate }
}

View File

@@ -69,12 +69,25 @@ class HRSRepository @Inject constructor(
val isRunning = data.map { it.connectionState?.state == GattConnectionState.STATE_CONNECTED }
fun launch(device: ServerDevice) {
serviceManager.startService(HRSService::class.java, device)
private var isOnScreen = false
private var isServiceRunning = false
fun setOnScreen(isOnScreen: Boolean) {
this.isOnScreen = isOnScreen
if (shouldClean()) clean()
}
fun onInitComplete(device: ServerDevice) {
_data.value = _data.value.copy(deviceName = device.name)
fun setServiceRunning(serviceRunning: Boolean) {
this.isServiceRunning = serviceRunning
if (shouldClean()) clean()
}
private fun shouldClean() = !isOnScreen && !isServiceRunning
fun launch(device: ServerDevice) {
serviceManager.startService(HRSService::class.java, device)
}
fun switchZoomIn() {
@@ -101,8 +114,11 @@ class HRSRepository @Inject constructor(
_loggerEvent.tryEmit(OpenLoggerEvent())
}
fun release() {
_data.value = HRSServiceData()
fun disconnect() {
_stopEvent.tryEmit(DisconnectAndStopEvent())
}
private fun clean() {
_data.value = HRSServiceData()
}
}

View File

@@ -75,11 +75,11 @@ internal class HRSService : NotificationService() {
private lateinit var client: BleGattClient
private var hasBeenInitialized: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
repository.setServiceRunning(true)
val device = intent!!.getParcelableExtra<ServerDevice>(DEVICE_DATA)!!
startGattClient(device)
@@ -104,14 +104,11 @@ internal class HRSService : NotificationService() {
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
.onEach { unlockUiIfDisconnected(it, device) }
.launchIn(lifecycleScope)
client.waitForBonding()
if (!client.isConnected) {
hasBeenInitialized = true
repository.onInitComplete(device)
return@launch
}
@@ -140,9 +137,6 @@ internal class HRSService : NotificationService() {
.mapNotNull { HRSDataParser.parse(it) }
.onEach { repository.onHRSDataChanged(it) }
.launchIn(lifecycleScope)
hasBeenInitialized = true
repository.onInitComplete(device)
}
private fun stopIfDisconnected(connectionState: GattConnectionStateWithStatus) {
@@ -151,13 +145,12 @@ internal class HRSService : NotificationService() {
}
}
private fun unlockUiIfDisconnected(connectionState: GattConnectionStateWithStatus, device: ServerDevice) {
if (connectionState.state == GattConnectionState.STATE_DISCONNECTED && !hasBeenInitialized) {
repository.onInitComplete(device)
}
}
private fun disconnect() {
client.disconnect()
}
override fun onDestroy() {
super.onDestroy()
repository.setServiceRunning(false)
}
}

View File

@@ -56,13 +56,14 @@ import no.nordicsemi.android.ui.view.ProfileAppBar
fun HRSScreen() {
val viewModel: HRSViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
val deviceName = viewModel.deviceName.collectAsState().value
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
Scaffold(
topBar = {
ProfileAppBar(
deviceName = state.deviceName,
deviceName = deviceName,
connectionState = state.connectionState,
title = R.string.hrs_title,
navigateUp = navigateUp,
@@ -77,9 +78,7 @@ fun HRSScreen() {
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
if (state.deviceName == null) {
DeviceConnectingView { NavigateUpButton(navigateUp) }
} else when (state.connectionState?.state) {
when (state.connectionState?.state) {
null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_DISCONNECTED,

View File

@@ -35,6 +35,8 @@ import android.os.ParcelUuid
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.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -65,7 +67,12 @@ internal class HRSViewModel @Inject constructor(
val state = repository.data
private val _deviceName = MutableStateFlow<String?>(null)
val deviceName = _deviceName.asStateFlow()
init {
repository.setOnScreen(true)
viewModelScope.launch {
if (repository.isRunning.firstOrNull() == false) {
requestBluetoothDevice()
@@ -95,6 +102,7 @@ internal class HRSViewModel @Inject constructor(
}
private fun onDeviceSelected(device: ServerDevice) {
_deviceName.value = device.name
repository.launch(device)
}
@@ -112,7 +120,12 @@ internal class HRSViewModel @Inject constructor(
}
private fun disconnect() {
repository.release()
repository.disconnect()
navigationManager.navigateUp()
}
override fun onCleared() {
super.onCleared()
repository.setOnScreen(false)
}
}

View File

@@ -73,10 +73,6 @@ class HTSRepository @Inject constructor(
private var isOnScreen = false
private var isServiceRunning = false
fun launch(device: ServerDevice) {
serviceManager.startService(HTSService::class.java, device)
}
fun setOnScreen(isOnScreen: Boolean) {
this.isOnScreen = isOnScreen
@@ -91,6 +87,10 @@ class HTSRepository @Inject constructor(
private fun shouldClean() = !isOnScreen && !isServiceRunning
fun launch(device: ServerDevice) {
serviceManager.startService(HTSService::class.java, device)
}
internal fun setTemperatureUnit(temperatureUnit: TemperatureUnit) {
_data.value = _data.value.copy(temperatureUnit = temperatureUnit)
}

View File

@@ -74,8 +74,6 @@ internal class HTSService : NotificationService() {
private lateinit var client: BleGattClient
private var hasBeenInitialized: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
@@ -104,7 +102,6 @@ internal class HTSService : NotificationService() {
.launchIn(lifecycleScope)
client.connectionStateWithStatus
.onEach { Log.d("AAATESTAAA", "Connection state: $it") }
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
@@ -116,11 +113,11 @@ internal class HTSService : NotificationService() {
client.discoverServices()
.filterNotNull()
.onEach { configureGatt(it, device) }
.onEach { configureGatt(it) }
.launchIn(lifecycleScope)
}
private suspend fun configureGatt(services: BleGattServices, device: ServerDevice) {
private suspend fun configureGatt(services: BleGattServices) {
val htsService = services.findService(HTS_SERVICE_UUID)!!
val htsMeasurementCharacteristic = htsService.findCharacteristic(HTS_MEASUREMENT_CHARACTERISTIC_UUID)!!
val batteryService = services.findService(BATTERY_SERVICE_UUID)!!

View File

@@ -58,8 +58,6 @@ fun HTSScreen() {
val state = viewModel.state.collectAsState().value
val deviceName = viewModel.deviceName.collectAsState().value
Log.d("AAATESTAAA", "State: $state")
val navigateUp = { viewModel.onEvent(NavigateUp) }
Scaffold(

View File

@@ -11,8 +11,7 @@ data class PRXServiceData(
val batteryLevel: Int? = null,
val connectionState: GattConnectionStateWithStatus? = null,
val connectionStatus: BleGattConnectionStatus? = null,
val isRemoteAlarm: Boolean = false,
val deviceName: String? = null
val isRemoteAlarm: Boolean = false
) {
val isLinkLossDisconnected = connectionStatus?.isLinkLoss ?: false

View File

@@ -70,12 +70,26 @@ class PRXRepository @Inject internal constructor(
val isRunning = data.map { it.connectionState?.state == GattConnectionState.STATE_CONNECTED }
fun launch(device: ServerDevice) {
serviceManager.startService(PRXService::class.java, device)
private var isOnScreen = false
private var isServiceRunning = false
fun setOnScreen(isOnScreen: Boolean) {
this.isOnScreen = isOnScreen
if (shouldClean()) clean()
}
fun onInitComplete(device: ServerDevice) {
_data.value = _data.value.copy(deviceName = device.name)
fun setServiceRunning(serviceRunning: Boolean) {
this.isServiceRunning = serviceRunning
if (shouldClean()) clean()
}
private fun shouldClean() = !isOnScreen && !isServiceRunning
fun launch(device: ServerDevice) {
serviceManager.startService(PRXService::class.java, device)
}
fun onConnectionStateChanged(connection: GattConnectionStateWithStatus) {
@@ -106,9 +120,12 @@ class PRXRepository @Inject internal constructor(
_loggerEvent.tryEmit(OpenLoggerEvent())
}
fun release() {
_data.value = PRXServiceData()
fun disconnect() {
_remoteAlarmLevel.tryEmit(AlarmLevel.NONE)
_stopEvent.tryEmit(DisconnectAndStopEvent())
}
private fun clean() {
_data.value = PRXServiceData()
}
}

View File

@@ -89,11 +89,11 @@ internal class PRXService : NotificationService() {
private lateinit var alertLevelCharacteristic: BleGattCharacteristic
private var hasBeenInitialized: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
repository.setServiceRunning(true)
val device = intent!!.getParcelableExtra<ServerDevice>(DEVICE_DATA)!!
startServer(device)
@@ -175,8 +175,6 @@ internal class PRXService : NotificationService() {
.launchIn(lifecycleScope)
if (!client.isConnected) {
hasBeenInitialized = true
repository.onInitComplete(device)
return@launch
}
@@ -204,9 +202,6 @@ internal class PRXService : NotificationService() {
.launchIn(lifecycleScope)
linkLossCharacteristic.write(AlertLevelInputParser.parse(AlarmLevel.HIGH))
hasBeenInitialized = true
repository.onInitComplete(device)
}
private suspend fun writeAlertLevel(alarmLevel: AlarmLevel) {
@@ -217,7 +212,7 @@ internal class PRXService : NotificationService() {
private fun stopIfDisconnected(connectionState: GattConnectionState, connectionStatus: BleGattConnectionStatus) {
if (connectionState == GattConnectionState.STATE_DISCONNECTED && !connectionStatus.isLinkLoss) {
server.stopServer()
repository.release()
repository.disconnect()
stopSelf()
}
}
@@ -226,4 +221,9 @@ internal class PRXService : NotificationService() {
client.disconnect()
server.stopServer()
}
override fun onDestroy() {
super.onDestroy()
repository.setServiceRunning(false)
}
}

View File

@@ -56,13 +56,14 @@ import no.nordicsemi.android.ui.view.ProfileAppBar
fun PRXScreen() {
val viewModel: PRXViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
val deviceName = viewModel.deviceName.collectAsState().value
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
Scaffold(
topBar = {
ProfileAppBar(
deviceName = state.deviceName,
deviceName = deviceName,
connectionState = state.connectionState,
title = R.string.prx_title,
navigateUp = navigateUp,
@@ -77,9 +78,7 @@ fun PRXScreen() {
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
if (state.deviceName == null) {
DeviceConnectingView { NavigateUpButton(navigateUp) }
} else when (state.connectionState?.state) {
when (state.connectionState?.state) {
null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_DISCONNECTED,

View File

@@ -35,6 +35,8 @@ import android.os.ParcelUuid
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.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
@@ -71,7 +73,12 @@ internal class PRXViewModel @Inject constructor(
val state = repository.data
private val _deviceName = MutableStateFlow<String?>(null)
val deviceName = _deviceName.asStateFlow()
init {
repository.setOnScreen(true)
viewModelScope.launch {
if (repository.isRunning.firstOrNull() == false) {
requestBluetoothDevice()
@@ -123,11 +130,12 @@ internal class PRXViewModel @Inject constructor(
private fun disconnect() {
alarmHandler.pauseAlarm()
navigationManager.navigateUp()
repository.release()
repository.disconnect()
}
override fun onCleared() {
super.onCleared()
alarmHandler.pauseAlarm()
repository.setOnScreen(false)
}
}

View File

@@ -41,8 +41,7 @@ import no.nordicsemi.android.rscs.R
internal data class RSCSServiceData(
val data: RSCSData = RSCSData(),
val batteryLevel: Int? = null,
val connectionState: GattConnectionStateWithStatus? = null,
val deviceName: String? = null
val connectionState: GattConnectionStateWithStatus? = null
) {
@Composable
fun displayActivity(): String {

View File

@@ -69,12 +69,25 @@ class RSCSRepository @Inject constructor(
val isRunning = data.map { it.connectionState?.state == GattConnectionState.STATE_CONNECTED }
fun launch(device: ServerDevice) {
serviceManager.startService(RSCSService::class.java, device)
private var isOnScreen = false
private var isServiceRunning = false
fun setOnScreen(isOnScreen: Boolean) {
this.isOnScreen = isOnScreen
if (shouldClean()) clean()
}
fun onInitComplete(device: ServerDevice) {
_data.value = _data.value.copy(deviceName = device.name)
fun setServiceRunning(serviceRunning: Boolean) {
this.isServiceRunning = serviceRunning
if (shouldClean()) clean()
}
private fun shouldClean() = !isOnScreen && !isServiceRunning
fun launch(device: ServerDevice) {
serviceManager.startService(RSCSService::class.java, device)
}
fun onConnectionStateChanged(connectionState: GattConnectionStateWithStatus?) {
@@ -93,9 +106,12 @@ class RSCSRepository @Inject constructor(
_loggerEvent.tryEmit(OpenLoggerEvent())
}
fun release() {
logger = null
_data.value = RSCSServiceData()
fun disconnect() {
_stopEvent.tryEmit(DisconnectAndStopEvent())
}
private fun clean() {
logger = null
_data.value = RSCSServiceData()
}
}

View File

@@ -73,11 +73,11 @@ internal class RSCSService : NotificationService() {
private lateinit var client: BleGattClient
private var hasBeenInitialized: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
repository.setServiceRunning(true)
val device = intent!!.getParcelableExtra<ServerDevice>(DEVICE_DATA)!!
startGattClient(device)
@@ -102,12 +102,9 @@ internal class RSCSService : NotificationService() {
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
.onEach { unlockUiIfDisconnected(it, device) }
.launchIn(lifecycleScope)
if (!client.isConnected) {
hasBeenInitialized = true
repository.onInitComplete(device)
return@launch
}
@@ -132,9 +129,6 @@ internal class RSCSService : NotificationService() {
.mapNotNull { RSCSDataParser.parse(it) }
.onEach { repository.onRSCSDataChanged(it) }
.launchIn(lifecycleScope)
hasBeenInitialized = true
repository.onInitComplete(device)
}
private fun stopIfDisconnected(connectionState: GattConnectionStateWithStatus) {
@@ -143,13 +137,12 @@ internal class RSCSService : NotificationService() {
}
}
private fun unlockUiIfDisconnected(connectionState: GattConnectionStateWithStatus, device: ServerDevice) {
if (connectionState.state == GattConnectionState.STATE_DISCONNECTED && !hasBeenInitialized) {
repository.onInitComplete(device)
}
}
private fun disconnect() {
client.disconnect()
}
override fun onDestroy() {
super.onDestroy()
repository.setServiceRunning(false)
}
}

View File

@@ -56,13 +56,14 @@ import no.nordicsemi.android.ui.view.ProfileAppBar
fun RSCSScreen() {
val viewModel: RSCSViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
val deviceName = viewModel.deviceName.collectAsState().value
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
Scaffold(
topBar = {
ProfileAppBar(
deviceName = state.deviceName,
deviceName = deviceName,
connectionState = state.connectionState,
title = R.string.rscs_title,
navigateUp = navigateUp,
@@ -77,9 +78,7 @@ fun RSCSScreen() {
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
if (state.deviceName == null) {
DeviceConnectingView { NavigateUpButton(navigateUp) }
} else when (state.connectionState?.state) {
when (state.connectionState?.state) {
null,
GattConnectionState.STATE_CONNECTING -> DeviceConnectingView { NavigateUpButton(navigateUp) }
GattConnectionState.STATE_DISCONNECTED,

View File

@@ -35,6 +35,8 @@ import android.os.ParcelUuid
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.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -64,6 +66,10 @@ internal class RSCSViewModel @Inject constructor(
val state = repository.data
private val _deviceName = MutableStateFlow<String?>(null)
val deviceName = _deviceName.asStateFlow()
init {
viewModelScope.launch {
if (repository.isRunning.firstOrNull() == false) {
@@ -94,6 +100,7 @@ internal class RSCSViewModel @Inject constructor(
}
private fun onDeviceSelected(device: ServerDevice) {
_deviceName.value = device.name
repository.launch(device)
}
@@ -106,7 +113,7 @@ internal class RSCSViewModel @Inject constructor(
}
private fun disconnect() {
repository.release()
repository.disconnect()
navigationManager.navigateUp()
}
}

View File

@@ -37,8 +37,7 @@ import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus
internal data class UARTServiceData(
val messages: List<UARTRecord> = emptyList(),
val connectionState: GattConnectionStateWithStatus? = null,
val batteryLevel: Int? = null,
val deviceName: String? = null
val batteryLevel: Int? = null
) {
val displayMessages = messages

View File

@@ -77,6 +77,23 @@ class UARTRepository @Inject internal constructor(
val lastConfigurationName = configurationDataSource.lastConfigurationName
private var isOnScreen = false
private var isServiceRunning = false
fun setOnScreen(isOnScreen: Boolean) {
this.isOnScreen = isOnScreen
if (shouldClean()) clean()
}
fun setServiceRunning(serviceRunning: Boolean) {
this.isServiceRunning = serviceRunning
if (shouldClean()) clean()
}
private fun shouldClean() = !isOnScreen && !isServiceRunning
fun launch(device: ServerDevice) {
serviceManager.startService(UARTService::class.java, device)
}
@@ -97,10 +114,6 @@ class UARTRepository @Inject internal constructor(
_data.value = _data.value.copy(messages = _data.value.messages + UARTRecord(value, UARTRecordType.INPUT))
}
fun onInitComplete(device: ServerDevice) {
_data.value = _data.value.copy(deviceName = device.name)
}
fun sendText(text: String, newLineChar: MacroEol) {
_command.tryEmit(text.parseWithNewLineChar(newLineChar))
}
@@ -124,8 +137,11 @@ class UARTRepository @Inject internal constructor(
configurationDataSource.saveConfigurationName(name)
}
fun release() {
_data.value = UARTServiceData()
fun disconnect() {
_stopEvent.tryEmit(DisconnectAndStopEvent())
}
private fun clean() {
_data.value = UARTServiceData()
}
}

View File

@@ -78,11 +78,11 @@ internal class UARTService : NotificationService() {
private lateinit var client: BleGattClient
private var hasBeenInitialized: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
repository.setServiceRunning(true)
val device = intent!!.getParcelableExtra<ServerDevice>(DEVICE_DATA)!!
startGattClient(device)
@@ -109,12 +109,9 @@ internal class UARTService : NotificationService() {
.onEach { repository.onConnectionStateChanged(it) }
.filterNotNull()
.onEach { stopIfDisconnected(it) }
.onEach { unlockUiIfDisconnected(it, device) }
.launchIn(lifecycleScope)
if (!client.isConnected) {
hasBeenInitialized = true
repository.onInitComplete(device)
return@launch
}
@@ -146,9 +143,6 @@ internal class UARTService : NotificationService() {
.onEach { repository.onNewMessageSent(it) }
.onEach { logger.log(10, "Sent: $it") }
.launchIn(lifecycleScope)
hasBeenInitialized = true
repository.onInitComplete(device)
}
private fun getWriteType(characteristic: BleGattCharacteristic): BleWriteType {
@@ -165,13 +159,12 @@ internal class UARTService : NotificationService() {
}
}
private fun unlockUiIfDisconnected(connectionState: GattConnectionStateWithStatus, device: ServerDevice) {
if (connectionState.state == GattConnectionState.STATE_DISCONNECTED && !hasBeenInitialized) {
repository.onInitComplete(device)
}
}
private fun disconnect() {
client.disconnect()
}
override fun onDestroy() {
super.onDestroy()
repository.setServiceRunning(false)
}
}

View File

@@ -68,7 +68,7 @@ fun UARTScreen() {
Scaffold(
topBar = {
ProfileAppBar(
deviceName = state.uartManagerState.deviceName,
deviceName = state.deviceName,
connectionState = state.uartManagerState.connectionState,
title = R.string.uart_title,
navigateUp = navigateUp,
@@ -80,9 +80,7 @@ fun UARTScreen() {
Column(
modifier = Modifier.padding(it)
) {
if (state.uartManagerState.deviceName == null) {
DeviceConnectingView { NavigateUpButton(navigateUp) }
} else when (state.uartManagerState.connectionState?.state) {
when (state.uartManagerState.connectionState?.state) {
null,
GattConnectionState.STATE_CONNECTING -> PaddingBox { DeviceConnectingView { NavigateUpButton(navigateUp) } }
GattConnectionState.STATE_DISCONNECTED,

View File

@@ -41,7 +41,8 @@ internal data class UARTViewState(
val isConfigurationEdited: Boolean = false,
val configurations: List<UARTConfiguration> = emptyList(),
val uartManagerState: UARTServiceData = UARTServiceData(),
val isInputVisible: Boolean = true
val isInputVisible: Boolean = true,
val deviceName: String? = null
) {
val showEditDialog: Boolean = editedPosition != null

View File

@@ -91,6 +91,8 @@ internal class UARTViewModel @Inject constructor(
val state = _state.asStateFlow()
init {
repository.setOnScreen(true)
viewModelScope.launch {
if (repository.isRunning.firstOrNull() == false) {
requestBluetoothDevice()
@@ -127,10 +129,15 @@ internal class UARTViewModel @Inject constructor(
private fun handleResult(result: NavigationResult<ServerDevice>) {
when (result) {
is NavigationResult.Cancelled -> navigationManager.navigateUp()
is NavigationResult.Success -> repository.launch(result.value)
is NavigationResult.Success -> onDeviceSelected(result.value)
}
}
private fun onDeviceSelected(device: ServerDevice) {
_state.value = _state.value.copy(deviceName = device.name)
repository.launch(device)
}
fun onEvent(event: UARTViewEvent) {
when (event) {
is OnCreateMacro -> addNewMacro(event.macro)
@@ -233,7 +240,12 @@ internal class UARTViewModel @Inject constructor(
}
private fun disconnect() {
repository.release()
repository.disconnect()
navigationManager.navigateUp()
}
override fun onCleared() {
super.onCleared()
repository.setOnScreen(false)
}
}