mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-21 00:14:24 +01:00
Change PRX module
This commit is contained in:
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Wed Sep 08 10:36:20 CEST 2021
|
||||
#Mon Feb 14 14:46:55 CET 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
|
||||
|
||||
@@ -6,4 +6,5 @@ import no.nordicsemi.android.service.BleManagerResult
|
||||
internal sealed class HRSViewState
|
||||
|
||||
internal data class WorkingState(val result: BleManagerResult<HRSData>) : HRSViewState()
|
||||
|
||||
internal object NoDeviceState : HRSViewState()
|
||||
|
||||
@@ -89,7 +89,7 @@ internal class HTSManager internal constructor(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return htCharacteristic != null
|
||||
return htCharacteristic != null && batteryLevelCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
|
||||
@@ -3,9 +3,11 @@ package no.nordicsemi.android.hts.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.hts.repository.HTSService
|
||||
import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID
|
||||
import no.nordicsemi.android.hts.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
|
||||
@@ -13,6 +13,7 @@ dependencies {
|
||||
implementation libs.nordic.ui.scanner
|
||||
implementation libs.nordic.navigation
|
||||
|
||||
implementation libs.bundles.icons
|
||||
implementation libs.bundles.compose
|
||||
implementation libs.androidx.core
|
||||
implementation libs.material
|
||||
|
||||
@@ -1,62 +1,97 @@
|
||||
package no.nordicsemi.android.prx.data
|
||||
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
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.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.prx.repository.AlarmHandler
|
||||
import no.nordicsemi.android.prx.repository.PRXManager
|
||||
import no.nordicsemi.android.prx.repository.PRXService
|
||||
import no.nordicsemi.android.prx.repository.ProximityServerManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.service.SuccessResult
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class PRXRepository @Inject constructor() {
|
||||
internal class PRXRepository @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val proximityServerManager: ProximityServerManager,
|
||||
private val alarmHandler: AlarmHandler
|
||||
) {
|
||||
|
||||
private val _data = MutableStateFlow(PRXData())
|
||||
val data: StateFlow<PRXData> = _data
|
||||
private var manager: PRXManager? = null
|
||||
|
||||
private val _command = MutableSharedFlow<PRXCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
val command = _command.asSharedFlow()
|
||||
private val _data = MutableStateFlow<BleManagerResult<PRXData>>(ConnectingResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
||||
val status = _status.asStateFlow()
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning = _isRunning.asStateFlow()
|
||||
|
||||
fun setBatteryLevel(batteryLevel: Int) {
|
||||
_data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(PRXService::class.java, device)
|
||||
proximityServerManager.open()
|
||||
}
|
||||
|
||||
fun setLocalAlarmLevel(value: Int) {
|
||||
val alarmLevel = AlarmLevel.create(value)
|
||||
_data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel))
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val manager = PRXManager(context, scope)
|
||||
this.manager = manager
|
||||
manager.useServer(proximityServerManager)
|
||||
|
||||
manager.dataHolder.status.onEach {
|
||||
_data.value = it
|
||||
handleLocalAlarm(it)
|
||||
}.launchIn(scope)
|
||||
|
||||
scope.launch {
|
||||
manager.start(device)
|
||||
}
|
||||
}
|
||||
|
||||
fun setLocalAlarmLevel(alarmLevel: AlarmLevel) {
|
||||
_data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel))
|
||||
private suspend fun PRXManager.start(device: BluetoothDevice) {
|
||||
try {
|
||||
connect(device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
_isRunning.value = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setLinkLossLevel(value: Int) {
|
||||
val alarmLevel = AlarmLevel.create(value)
|
||||
_data.tryEmit(_data.value.copy(linkLossAlarmLevel = alarmLevel))
|
||||
}
|
||||
|
||||
fun setRemoteAlarmLevel(isOn: Boolean) {
|
||||
_data.tryEmit(_data.value.copy(isRemoteAlarm = isOn))
|
||||
}
|
||||
|
||||
fun invokeCommand(command: PRXCommand) {
|
||||
if (command == Disconnect) {
|
||||
_command.tryEmit(command)
|
||||
_status.tryEmit(BleManagerStatus.DISCONNECTED)
|
||||
} else if (_command.subscriptionCount.value > 0) {
|
||||
_command.tryEmit(command)
|
||||
private fun handleLocalAlarm(result: BleManagerResult<PRXData>) {
|
||||
(result as? SuccessResult<PRXData>)?.let {
|
||||
if (it.data.localAlarmLevel != AlarmLevel.NONE) {
|
||||
alarmHandler.playAlarm(it.data.localAlarmLevel)
|
||||
} else {
|
||||
_status.tryEmit(BleManagerStatus.DISCONNECTED)
|
||||
alarmHandler.pauseAlarm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setNewStatus(status: BleManagerStatus) {
|
||||
_status.value = status
|
||||
fun enableAlarm() {
|
||||
manager?.writeImmediateAlert(true)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_status.value = BleManagerStatus.CONNECTING
|
||||
_data.tryEmit(PRXData())
|
||||
fun disableAlarm() {
|
||||
manager?.writeImmediateAlert(false)
|
||||
}
|
||||
|
||||
fun release() {
|
||||
serviceManager.stopService(PRXService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,65 +26,84 @@ import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattServer
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
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.alert.AlertLevelDataCallback
|
||||
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
|
||||
import no.nordicsemi.android.ble.common.data.alert.AlertLevelData
|
||||
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import no.nordicsemi.android.prx.data.AlarmLevel
|
||||
import no.nordicsemi.android.prx.data.PRXData
|
||||
import no.nordicsemi.android.service.ConnectionObserverAdapter
|
||||
import no.nordicsemi.android.utils.launchWithCatch
|
||||
import java.util.*
|
||||
|
||||
val LINK_LOSS_SERVICE_UUID = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb")
|
||||
val PRX_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb")
|
||||
val LINK_LOSS_SERVICE_UUID = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb")
|
||||
val ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||
private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
internal class PRXManager(
|
||||
context: Context,
|
||||
scope: CoroutineScope,
|
||||
private val dataHolder: PRXRepository
|
||||
) : BatteryManager(context, scope) {
|
||||
private val scope: CoroutineScope,
|
||||
) : BleManager(context) {
|
||||
|
||||
private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var alertLevelCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var linkLossCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
private var localAlertLevelCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var linkLossServerCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, t->
|
||||
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
|
||||
private var isAlertEnabled = false
|
||||
|
||||
private val data = MutableStateFlow(PRXData())
|
||||
val dataHolder = ConnectionObserverAdapter<PRXData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
var isAlertEnabled = false
|
||||
private set
|
||||
|
||||
private inner class ProximityManagerGattCallback : BatteryManagerGattCallback() {
|
||||
private inner class ProximityManagerGattCallback : BleManagerGattCallback() {
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
|
||||
setWriteCallback(localAlertLevelCharacteristic)
|
||||
.with(object : AlertLevelDataCallback() {
|
||||
override fun onAlertLevelChanged(device: BluetoothDevice, level: Int) {
|
||||
dataHolder.setLocalAlarmLevel(level)
|
||||
data.value = data.value.copy(localAlarmLevel = AlarmLevel.create(level))
|
||||
}
|
||||
})
|
||||
|
||||
setWriteCallback(linkLossServerCharacteristic)
|
||||
.with(object : AlertLevelDataCallback() {
|
||||
override fun onAlertLevelChanged(device: BluetoothDevice, level: Int) {
|
||||
dataHolder.setLinkLossLevel(level)
|
||||
data.value = data.value.copy(linkLossAlarmLevel = AlarmLevel.create(level))
|
||||
}
|
||||
})
|
||||
|
||||
scope.launch(exceptionHandler) {
|
||||
writeCharacteristic(
|
||||
linkLossCharacteristic,
|
||||
AlertLevelData.highAlert(),
|
||||
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
).suspend()
|
||||
}
|
||||
).enqueue()
|
||||
|
||||
setNotificationCallback(batteryLevelCharacteristic)
|
||||
.asValidResponseFlow<BatteryLevelResponse>()
|
||||
.onEach {
|
||||
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||
}.launchIn(scope)
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
}
|
||||
|
||||
override fun onServerReady(server: BluetoothGattServer) {
|
||||
@@ -98,29 +117,26 @@ internal class PRXManager(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() { }
|
||||
|
||||
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
val llService = gatt.getService(LINK_LOSS_SERVICE_UUID)
|
||||
if (llService != null) {
|
||||
linkLossCharacteristic = llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
|
||||
gatt.getService(LINK_LOSS_SERVICE_UUID)?.run {
|
||||
linkLossCharacteristic = getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return linkLossCharacteristic != null
|
||||
}
|
||||
|
||||
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
super.isOptionalServiceSupported(gatt)
|
||||
val iaService = gatt.getService(PRX_SERVICE_UUID)
|
||||
if (iaService != null) {
|
||||
alertLevelCharacteristic = iaService.getCharacteristic(
|
||||
ALERT_LEVEL_CHARACTERISTIC_UUID
|
||||
)
|
||||
gatt.getService(PRX_SERVICE_UUID)?.run {
|
||||
alertLevelCharacteristic = getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return alertLevelCharacteristic != null
|
||||
return alertLevelCharacteristic != null && batteryLevelCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onDeviceDisconnected() {
|
||||
super.onDeviceDisconnected()
|
||||
override fun onServicesInvalidated() {
|
||||
batteryLevelCharacteristic = null
|
||||
alertLevelCharacteristic = null
|
||||
linkLossCharacteristic = null
|
||||
localAlertLevelCharacteristic = null
|
||||
@@ -131,7 +147,7 @@ internal class PRXManager(
|
||||
|
||||
fun writeImmediateAlert(on: Boolean) {
|
||||
if (!isConnected) return
|
||||
scope.launch(exceptionHandler) {
|
||||
scope.launchWithCatch {
|
||||
writeCharacteristic(
|
||||
alertLevelCharacteristic,
|
||||
if (on) AlertLevelData.highAlert() else AlertLevelData.noAlert(),
|
||||
@@ -139,14 +155,10 @@ internal class PRXManager(
|
||||
).suspend()
|
||||
|
||||
isAlertEnabled = on
|
||||
dataHolder.setRemoteAlarmLevel(on)
|
||||
data.value = data.value.copy(isRemoteAlarm = on)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBatteryLevelChanged(batteryLevel: Int) {
|
||||
dataHolder.setBatteryLevel(batteryLevel)
|
||||
}
|
||||
|
||||
override fun getGattCallback(): BleManagerGattCallback {
|
||||
return ProximityManagerGattCallback()
|
||||
}
|
||||
|
||||
@@ -1,77 +1,27 @@
|
||||
package no.nordicsemi.android.prx.repository
|
||||
|
||||
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.*
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class PRXService : ForegroundBleService() {
|
||||
internal class PRXService : NotificationService() {
|
||||
|
||||
@Inject
|
||||
lateinit var repository: PRXRepository
|
||||
|
||||
@Inject
|
||||
lateinit var alarmHandler: AlarmHandler
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
private var serverManager: ProximityServerManager = ProximityServerManager(this)
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
override val manager: PRXManager by lazy {
|
||||
PRXManager(this, scope, repository).apply {
|
||||
useServer(serverManager)
|
||||
}
|
||||
}
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
serverManager.open()
|
||||
|
||||
// status.onEach {
|
||||
// val bleStatus = when (it) {
|
||||
// BleServiceStatus.CONNECTING -> BleManagerStatus.CONNECTING
|
||||
// BleServiceStatus.OK -> BleManagerStatus.OK
|
||||
// BleServiceStatus.DISCONNECTED -> {
|
||||
// scope.close()
|
||||
// stopSelf()
|
||||
// BleManagerStatus.DISCONNECTED
|
||||
// }
|
||||
// BleServiceStatus.LINK_LOSS -> null
|
||||
// }.exhaustive
|
||||
// bleStatus?.let { repository.setNewStatus(it) }
|
||||
//
|
||||
// if (BleServiceStatus.LINK_LOSS == it) {
|
||||
// repository.setLocalAlarmLevel(repository.data.value.linkLossAlarmLevel)
|
||||
// }
|
||||
// }.launchIn(scope)
|
||||
|
||||
repository.command.onEach {
|
||||
when (it) {
|
||||
DisableAlarm -> manager.writeImmediateAlert(false)
|
||||
EnableAlarm -> manager.writeImmediateAlert(true)
|
||||
Disconnect -> stopSelf()
|
||||
}.exhaustive
|
||||
}.launchIn(scope)
|
||||
|
||||
repository.data.onEach {
|
||||
if (it.localAlarmLevel != AlarmLevel.NONE) {
|
||||
alarmHandler.playAlarm(it.localAlarmLevel)
|
||||
} else {
|
||||
alarmHandler.pauseAlarm()
|
||||
}
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun shouldAutoConnect(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
alarmHandler.releaseAlarm()
|
||||
serverManager.close()
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,16 @@ import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import no.nordicsemi.android.ble.BleServerManager
|
||||
import no.nordicsemi.android.ble.common.data.alert.AlertLevelData
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ProximityServerManager @Inject constructor(
|
||||
@ApplicationContext
|
||||
context: Context
|
||||
) : BleServerManager(context) {
|
||||
|
||||
internal class ProximityServerManager(context: Context) : BleServerManager(context) {
|
||||
override fun log(priority: Int, message: String) {
|
||||
Log.println(priority, "BleManager", message)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package no.nordicsemi.android.prx.view
|
||||
|
||||
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.material3.Button
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.prx.R
|
||||
import no.nordicsemi.android.theme.R as themeR
|
||||
import no.nordicsemi.android.theme.view.ScreenSection
|
||||
import androidx.compose.material.icons.filled.HighlightOff
|
||||
|
||||
@Composable
|
||||
fun DeviceOutOfRangeView(navigateUp: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
ScreenSection {
|
||||
Icon(
|
||||
imageVector = Icons.Default.HighlightOff,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSecondary,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
shape = CircleShape
|
||||
)
|
||||
.padding(8.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.prx_device_out_of_range),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.prx_device_out_of_range_reason),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Button(onClick = { navigateUp() }) {
|
||||
Text(text = stringResource(id = themeR.string.disconnect))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,13 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.prx.R
|
||||
import no.nordicsemi.android.prx.viewmodel.PRXViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.scanner.DeviceConnectingView
|
||||
import no.nordicsemi.android.theme.view.scanner.DeviceDisconnectedView
|
||||
import no.nordicsemi.android.theme.view.scanner.NoDeviceView
|
||||
import no.nordicsemi.android.theme.view.scanner.Reason
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@Composable
|
||||
fun PRXScreen() {
|
||||
@@ -19,15 +25,22 @@ fun PRXScreen() {
|
||||
val state = viewModel.state.collectAsState().value
|
||||
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
BackIconAppBar(stringResource(id = R.string.prx_title)) {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}
|
||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||
|
||||
BackIconAppBar(stringResource(id = R.string.prx_title), navigateUp)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
// when (state) {
|
||||
// is DisplayDataState -> ContentView(state.data) { viewModel.onEvent(it) }
|
||||
// LoadingState -> DeviceConnectingView()
|
||||
// }.exhaustive
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult,
|
||||
is ReadyResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) }
|
||||
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
|
||||
is SuccessResult -> ContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package no.nordicsemi.android.prx.view
|
||||
|
||||
internal sealed class PRXScreenViewEvent
|
||||
|
||||
internal object NavigateUpEvent : PRXScreenViewEvent()
|
||||
|
||||
internal object TurnOnAlert : PRXScreenViewEvent()
|
||||
|
||||
internal object TurnOffAlert : PRXScreenViewEvent()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package no.nordicsemi.android.prx.view
|
||||
|
||||
import no.nordicsemi.android.prx.data.PRXData
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
|
||||
internal sealed class PRXViewState
|
||||
|
||||
internal object LoadingState : PRXViewState()
|
||||
internal data class WorkingState(val result: BleManagerResult<PRXData>) : PRXViewState()
|
||||
|
||||
internal data class DisplayDataState(val data: PRXData) : PRXViewState()
|
||||
internal object NoDeviceState : PRXViewState()
|
||||
|
||||
@@ -3,17 +3,14 @@ package no.nordicsemi.android.prx.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.prx.data.DisableAlarm
|
||||
import no.nordicsemi.android.prx.data.Disconnect
|
||||
import no.nordicsemi.android.prx.data.EnableAlarm
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.prx.repository.PRXService
|
||||
import no.nordicsemi.android.prx.repository.PRX_SERVICE_UUID
|
||||
import no.nordicsemi.android.prx.view.*
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.utils.getDevice
|
||||
import no.nordicsemi.ui.scanner.ScannerDestinationId
|
||||
@@ -22,19 +19,23 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
internal class PRXViewModel @Inject constructor(
|
||||
private val repository: PRXRepository,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val navigationManager: NavigationManager
|
||||
) : ViewModel() {
|
||||
|
||||
val state = repository.data.combine(repository.status) { data, status ->
|
||||
// when (status) {
|
||||
// BleManagerStatus.CONNECTING -> LoadingState
|
||||
// BleManagerStatus.OK,
|
||||
// BleManagerStatus.DISCONNECTED -> DisplayDataState(data)
|
||||
// }
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
|
||||
private val _state = MutableStateFlow<PRXViewState>(NoDeviceState)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
if (!repository.isRunning.value) {
|
||||
requestBluetoothDevice()
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
_state.value = WorkingState(it)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun requestBluetoothDevice() {
|
||||
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(PRX_SERVICE_UUID))
|
||||
|
||||
navigationManager.recentResult.onEach {
|
||||
@@ -42,36 +43,26 @@ internal class PRXViewModel @Inject constructor(
|
||||
handleArgs(it)
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
repository.status.onEach {
|
||||
if (it == BleManagerStatus.DISCONNECTED) {
|
||||
navigationManager.navigateUp()
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> serviceManager.startService(PRXService::class.java, args.getDevice())
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
fun onEvent(event: PRXScreenViewEvent) {
|
||||
when (event) {
|
||||
DisconnectEvent -> onDisconnectButtonClick()
|
||||
TurnOffAlert -> repository.invokeCommand(DisableAlarm)
|
||||
TurnOnAlert -> repository.invokeCommand(EnableAlarm)
|
||||
DisconnectEvent -> disconnect()
|
||||
TurnOffAlert -> repository.disableAlarm()
|
||||
TurnOnAlert -> repository.enableAlarm()
|
||||
NavigateUpEvent -> navigationManager.navigateUp()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun onDisconnectButtonClick() {
|
||||
repository.invokeCommand(Disconnect)
|
||||
repository.clear()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
repository.clear()
|
||||
private fun disconnect() {
|
||||
repository.release()
|
||||
navigationManager.navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,7 @@
|
||||
|
||||
<string name="prx_is_remote_alarm">Remote alarm</string>
|
||||
<string name="prx_local_alarm_level">Local alarm level</string>
|
||||
|
||||
<string name="prx_device_out_of_range">Device out of range</string>
|
||||
<string name="prx_device_out_of_range_reason">Device is out of range and it has disconnected. It should reconnect automatically after device is in range again.</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user