diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a5714c2c..ae7ce509 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt index ec42ff89..1038c981 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt @@ -6,4 +6,5 @@ import no.nordicsemi.android.service.BleManagerResult internal sealed class HRSViewState internal data class WorkingState(val result: BleManagerResult) : HRSViewState() + internal object NoDeviceState : HRSViewState() diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt index f848c979..0fc71fc2 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSManager.kt @@ -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() { diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt index b49780ea..2743f982 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt @@ -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.* diff --git a/profile_prx/build.gradle b/profile_prx/build.gradle index d669dc22..2aeedf8a 100644 --- a/profile_prx/build.gradle +++ b/profile_prx/build.gradle @@ -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 diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt index 6085e7ea..f6a8b2d4 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXRepository.kt @@ -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 = _data + private var manager: PRXManager? = null - private val _command = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - val command = _command.asSharedFlow() + private val _data = MutableStateFlow>(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) - fun setLocalAlarmLevel(alarmLevel: AlarmLevel) { - _data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel)) - } + manager.dataHolder.status.onEach { + _data.value = it + handleLocalAlarm(it) + }.launchIn(scope) - 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) - } else { - _status.tryEmit(BleManagerStatus.DISCONNECTED) + scope.launch { + manager.start(device) } } - fun setNewStatus(status: BleManagerStatus) { - _status.value = status + 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 clear() { - _status.value = BleManagerStatus.CONNECTING - _data.tryEmit(PRXData()) + private fun handleLocalAlarm(result: BleManagerResult) { + (result as? SuccessResult)?.let { + if (it.data.localAlarmLevel != AlarmLevel.NONE) { + alarmHandler.playAlarm(it.data.localAlarmLevel) + } else { + alarmHandler.pauseAlarm() + } + } + } + + fun enableAlarm() { + manager?.writeImmediateAlert(true) + } + + fun disableAlarm() { + manager?.writeImmediateAlert(false) + } + + fun release() { + serviceManager.stopService(PRXService::class.java) + manager?.disconnect()?.enqueue() + manager = null + _isRunning.value = false } } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt index 1d420e6f..dbc34045 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXManager.kt @@ -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() + + 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() - } + writeCharacteristic( + linkLossCharacteristic, + AlertLevelData.highAlert(), + BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT + ).enqueue() + + setNotificationCallback(batteryLevelCharacteristic) + .asValidResponseFlow() + .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() } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt index 53761751..98b65b31 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt @@ -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(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 } } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/ProximityServerManager.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/ProximityServerManager.kt index 756b6256..cb4dea63 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/ProximityServerManager.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/ProximityServerManager.kt @@ -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) } @@ -59,4 +64,4 @@ internal class ProximityServerManager(context: Context) : BleServerManager(conte ) return services } -} \ No newline at end of file +} diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXLinkLossView.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXLinkLossView.kt new file mode 100644 index 00000000..5d8af23d --- /dev/null +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXLinkLossView.kt @@ -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)) + } + } +} diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt index b619b2d7..2332dacf 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt @@ -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 } } } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreenViewEvent.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreenViewEvent.kt index ccd49648..e9030ee9 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreenViewEvent.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreenViewEvent.kt @@ -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() diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXState.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXState.kt index 91bda9ee..c074b176 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXState.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXState.kt @@ -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) : PRXViewState() -internal data class DisplayDataState(val data: PRXData) : PRXViewState() +internal object NoDeviceState : PRXViewState() diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt index 0af78647..ec4a28cf 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt @@ -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(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() } } diff --git a/profile_prx/src/main/res/values/strings.xml b/profile_prx/src/main/res/values/strings.xml index f0825f8c..b6114218 100644 --- a/profile_prx/src/main/res/values/strings.xml +++ b/profile_prx/src/main/res/values/strings.xml @@ -14,4 +14,7 @@ Remote alarm Local alarm level + + Device out of range + Device is out of range and it has disconnected. It should reconnect automatically after device is in range again.