diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeView.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeView.kt index 4eebebee..f536a13e 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeView.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeView.kt @@ -19,6 +19,8 @@ import no.nordicsemi.android.nrftoolbox.R import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel import no.nordicsemi.android.theme.view.TitleAppBar +private const val DFU_LINK = "https://github.com/NordicSemiconductor/Android-DFU-Library" + @Composable fun HomeScreen() { val viewModel: HomeViewModel = hiltViewModel() @@ -111,7 +113,7 @@ fun HomeScreen() { Spacer(modifier = Modifier.height(16.dp)) - FeatureButton(R.drawable.ic_uart, R.string.uart_module, R.string.uart_module_full) { + FeatureButton(R.drawable.ic_uart, R.string.uart_module, R.string.uart_module_full, state.isUARTModuleRunning) { viewModel.openProfile(ProfileDestination.UART) } @@ -119,7 +121,7 @@ fun HomeScreen() { val uriHandler = LocalUriHandler.current FeatureButton(R.drawable.ic_dfu, R.string.dfu_module, R.string.dfu_module_full) { - uriHandler.openUri("https://github.com/NordicSemiconductor/Android-DFU-Library") + uriHandler.openUri(DFU_LINK) } Spacer(modifier = Modifier.height(16.dp)) diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeViewState.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeViewState.kt index d8b47492..77e32767 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeViewState.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/view/HomeViewState.kt @@ -6,5 +6,6 @@ data class HomeViewState( val isHTSModuleRunning: Boolean = false, val isRSCSModuleRunning: Boolean = false, val isPRXModuleRunning: Boolean = false, - val isCGMModuleRunning: Boolean = false + val isCGMModuleRunning: Boolean = false, + val isUARTModuleRunning: Boolean = false ) diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt index e78bb3ae..aa556449 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/viewmodel/HomeViewModel.kt @@ -16,6 +16,7 @@ import no.nordicsemi.android.nrftoolbox.ProfileDestination import no.nordicsemi.android.nrftoolbox.view.HomeViewState import no.nordicsemi.android.prx.data.PRXRepository import no.nordicsemi.android.rscs.data.RSCSRepository +import no.nordicsemi.android.uart.data.UARTRepository import javax.inject.Inject @HiltViewModel @@ -26,7 +27,8 @@ class HomeViewModel @Inject constructor( hrsRepository: HRSRepository, htsRepository: HTSRepository, prxRepository: PRXRepository, - rscsRepository: RSCSRepository + rscsRepository: RSCSRepository, + uartRepository: UARTRepository, ) : ViewModel() { private val _state = MutableStateFlow(HomeViewState()) @@ -56,6 +58,10 @@ class HomeViewModel @Inject constructor( rscsRepository.isRunning.onEach { _state.value = _state.value.copy(isRSCSModuleRunning = it) }.launchIn(viewModelScope) + + uartRepository.isRunning.onEach { + _state.value = _state.value.copy(isUARTModuleRunning = it) + }.launchIn(viewModelScope) } fun openProfile(destination: ProfileDestination) { diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt index 857b15ff..e7b0a0d9 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt @@ -4,6 +4,5 @@ import no.nordicsemi.android.utils.EMPTY internal data class UARTData( val text: String = String.EMPTY, - val macros: List = emptyList(), val batteryLevel: Int = 0 ) diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt index a8b75b47..4c64b7c8 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTRepository.kt @@ -1,58 +1,74 @@ package no.nordicsemi.android.uart.data -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow +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.asSharedFlow import kotlinx.coroutines.flow.asStateFlow -import no.nordicsemi.android.service.BleManagerStatus +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.service.BleManagerResult +import no.nordicsemi.android.service.ConnectingResult +import no.nordicsemi.android.service.ServiceManager +import no.nordicsemi.android.uart.repository.UARTManager +import no.nordicsemi.android.uart.repository.UARTService import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class UARTRepository @Inject constructor() { +class UARTRepository @Inject constructor( + @ApplicationContext + private val context: Context, + private val serviceManager: ServiceManager, +) { + private var manager: UARTManager? = null - private val _data = MutableStateFlow(UARTData()) - val data = _data.asStateFlow() + private val _data = MutableStateFlow>(ConnectingResult()) + internal val data = _data.asStateFlow() - private val _command = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST) - val command = _command.asSharedFlow() + private val _isRunning = MutableStateFlow(false) + val isRunning = _isRunning.asStateFlow() - private val _status = MutableStateFlow(BleManagerStatus.CONNECTING) - val status = _status.asStateFlow() - - fun addNewMacro(macro: UARTMacro) { - _data.tryEmit(_data.value.copy(macros = _data.value.macros + macro)) + fun launch(device: BluetoothDevice) { + serviceManager.startService(UARTService::class.java, device) } - fun deleteMacro(macro: UARTMacro) { - val macros = _data.value.macros.toMutableList().apply { - remove(macro) - } - _data.tryEmit(_data.value.copy(macros = macros)) - } + fun start(device: BluetoothDevice, scope: CoroutineScope) { + val manager = UARTManager(context, scope) + this.manager = manager - fun emitNewMessage(message: String) { - _data.tryEmit(_data.value.copy(text = message)) - } + manager.dataHolder.status.onEach { + _data.value = it + }.launchIn(scope) - fun emitNewBatteryLevel(batteryLevel: Int) { - _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel)) - } - - fun sendNewCommand(command: UARTServiceCommand) { - 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 + fun runMacro(macro: UARTMacro) { + manager?.send(macro.command) } - fun clear() { - _status.value = BleManagerStatus.CONNECTING + private suspend fun UARTManager.start(device: BluetoothDevice) { + try { + connect(device) + .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 } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt index e45ad9b0..d86004e1 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTManager.kt @@ -26,53 +26,66 @@ import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattService import android.content.Context import android.text.TextUtils -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.WriteRequest +import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.ktx.asFlow -import no.nordicsemi.android.ble.ktx.suspend -import no.nordicsemi.android.service.BatteryManager -import no.nordicsemi.android.uart.data.UARTRepository +import no.nordicsemi.android.ble.ktx.asValidResponseFlow +import no.nordicsemi.android.service.ConnectionObserverAdapter +import no.nordicsemi.android.uart.data.UARTData 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") 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") +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 UARTManager( context: Context, - scope: CoroutineScope, - private val dataHolder: UARTRepository -) : BatteryManager(context, scope) { + private val scope: CoroutineScope, +) : BleManager(context) { + + private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null private var rxCharacteristic: BluetoothGattCharacteristic? = null private var txCharacteristic: BluetoothGattCharacteristic? = null - private val exceptionHandler = CoroutineExceptionHandler { _, t-> - Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t) - } - private var useLongWrite = true - private inner class UARTManagerGattCallback : BatteryManagerGattCallback() { + private val data = MutableStateFlow(UARTData()) + val dataHolder = ConnectionObserverAdapter() + + init { + setConnectionObserver(dataHolder) + + data.onEach { + dataHolder.setValue(it) + }.launchIn(scope) + } + + private inner class UARTManagerGattCallback : BleManagerGattCallback() { override fun initialize() { setNotificationCallback(txCharacteristic).asFlow().onEach { val text: String = it.getStringValue(0) ?: String.EMPTY - dataHolder.emitNewMessage(text) + data.tryEmit(data.value.copy(text = text)) } - scope.launch(exceptionHandler) { - requestMtu(260).suspend() - } + requestMtu(260).enqueue() + enableNotifications(txCharacteristic).enqueue() - scope.launch(exceptionHandler) { - enableNotifications(txCharacteristic).suspend() - } + setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow().onEach { + data.value = data.value.copy(batteryLevel = it.batteryLevel) + }.launchIn(scope) + enableNotifications(batteryLevelCharacteristic).enqueue() } override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { @@ -100,24 +113,24 @@ internal class UARTManager( useLongWrite = false } } - return rxCharacteristic != null && txCharacteristic != null && (writeRequest || writeCommand) - } - - override fun onDeviceDisconnected() { - rxCharacteristic = null - txCharacteristic = null - useLongWrite = true + gatt.getService(BATTERY_SERVICE_UUID)?.run { + batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID) + } + return rxCharacteristic != null && txCharacteristic != null && batteryLevelCharacteristic != null && (writeRequest || writeCommand) } override fun onServicesInvalidated() { - + batteryLevelCharacteristic = null + rxCharacteristic = null + txCharacteristic = null + useLongWrite = true } } fun send(text: String) { if (rxCharacteristic == null) return if (!TextUtils.isEmpty(text)) { - scope.launch(exceptionHandler) { + scope.launchWithCatch { val request: WriteRequest = writeCharacteristic(rxCharacteristic, text.toByteArray(), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) if (!useLongWrite) { request.split() @@ -127,11 +140,7 @@ internal class UARTManager( } } - override fun onBatteryLevelChanged(batteryLevel: Int) { - dataHolder.emitNewBatteryLevel(batteryLevel) - } - - override fun getGattCallback(): BatteryManagerGattCallback { + override fun getGattCallback(): BleManagerGattCallback { return UARTManagerGattCallback() } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt index 765b8bad..ff8ccd4c 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt @@ -1,37 +1,27 @@ package no.nordicsemi.android.uart.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.service.ForegroundBleService -import no.nordicsemi.android.uart.data.DisconnectCommand -import no.nordicsemi.android.uart.data.SendTextCommand +import no.nordicsemi.android.service.DEVICE_DATA +import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.uart.data.UARTRepository -import no.nordicsemi.android.utils.exhaustive import javax.inject.Inject @AndroidEntryPoint -internal class UARTService : ForegroundBleService() { +internal class UARTService : NotificationService() { @Inject lateinit var repository: UARTRepository - override val manager: UARTManager by lazy { UARTManager(this, scope, repository) } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + super.onStartCommand(intent, flags, startId) - override fun onCreate() { - super.onCreate() + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! -// status.onEach { -// val status = it.mapToSimpleManagerStatus() -// repository.setNewStatus(status) -// stopIfDisconnected(status) -// }.launchIn(scope) + repository.start(device, lifecycleScope) - repository.command.onEach { - when (it) { - DisconnectCommand -> stopSelf() - is SendTextCommand -> manager.send(it.command) - }.exhaustive - }.launchIn(scope) + return START_REDELIVER_INTENT } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt index 65be5e43..9e917411 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt @@ -18,19 +18,26 @@ import no.nordicsemi.android.theme.view.ScreenSection import no.nordicsemi.android.theme.view.SectionTitle import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.data.UARTData +import no.nordicsemi.android.uart.data.UARTMacro @Composable -internal fun UARTContentView(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { +internal fun UARTContentView(state: UARTData, macros: List, onEvent: (UARTViewEvent) -> Unit) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { - InputSection(state, onEvent) + InputSection(macros, onEvent) Spacer(modifier = Modifier.height(16.dp)) + if (state.text.isNotEmpty()) { + OutputSection(state.text) + + Spacer(modifier = Modifier.height(16.dp)) + } + Button( - onClick = { onEvent(OnDisconnectButtonClick) } + onClick = { onEvent(DisconnectEvent) } ) { Text(text = stringResource(id = R.string.disconnect)) } @@ -38,7 +45,7 @@ internal fun UARTContentView(state: UARTData, onEvent: (UARTViewEvent) -> Unit) } @Composable -private fun InputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { +private fun InputSection(macros: List, onEvent: (UARTViewEvent) -> Unit) { val showSearchDialog = remember { mutableStateOf(false) } if (showSearchDialog.value) { @@ -53,13 +60,13 @@ private fun InputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { Spacer(modifier = Modifier.height(16.dp)) - state.macros.forEach { + macros.forEach { MacroItem(macro = it, onEvent = onEvent) Spacer(modifier = Modifier.height(16.dp)) } - if (state.macros.isEmpty()) { + if (macros.isEmpty()) { Text( text = stringResource(id = R.string.uart_no_macros_info), style = MaterialTheme.typography.bodyMedium @@ -78,7 +85,7 @@ private fun InputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { } @Composable -private fun OutputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { +private fun OutputSection(text: String) { ScreenSection { Column( horizontalAlignment = Alignment.CenterHorizontally @@ -87,7 +94,7 @@ private fun OutputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { Spacer(modifier = Modifier.height(16.dp)) - + Text(text = text) } } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt index 3ecc390e..1af31071 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt @@ -8,9 +8,15 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel +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.uart.R import no.nordicsemi.android.uart.viewmodel.UARTViewModel +import no.nordicsemi.android.utils.exhaustive @Composable fun UARTScreen() { @@ -18,15 +24,25 @@ fun UARTScreen() { val state = viewModel.state.collectAsState().value Column { + val navigateUp = { viewModel.onEvent(NavigateUp) } + BackIconAppBar(stringResource(id = R.string.uart_title)) { - viewModel.onEvent(OnDisconnectButtonClick) + viewModel.onEvent(DisconnectEvent) } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { -// when (state) { -// is DisplayDataState -> UARTContentView(state.data) { viewModel.onEvent(it) } -// LoadingState -> DeviceConnectingView() -// }.exhaustive + when (state.uartManagerState) { + NoDeviceState -> NoDeviceView() + is WorkingState -> when (state.uartManagerState.result) { + is ConnectingResult, + is ReadyResult + -> 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 SuccessResult -> UARTContentView(state.uartManagerState.result.data, state.macros) { viewModel.onEvent(it) } + } + }.exhaustive } } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt index 31c34d7e..eaeb2a10 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt @@ -1,9 +1,16 @@ package no.nordicsemi.android.uart.view +import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.uart.data.UARTData +import no.nordicsemi.android.uart.data.UARTMacro -internal sealed class UARTViewState +internal data class UARTViewState( + val macros: List = emptyList(), + val uartManagerState: HTSManagerState = NoDeviceState +) -internal object LoadingState : UARTViewState() +internal sealed class HTSManagerState -internal data class DisplayDataState(val data: UARTData) : UARTViewState() +internal data class WorkingState(val result: BleManagerResult) : HTSManagerState() + +internal object NoDeviceState : HTSManagerState() diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt index 28f9adf8..fecd9266 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt @@ -9,4 +9,6 @@ internal data class OnDeleteMacro(val macro: UARTMacro) : UARTViewEvent() internal data class OnRunMacro(val macro: UARTMacro) : UARTViewEvent() -internal object OnDisconnectButtonClick : UARTViewEvent() +internal object DisconnectEvent : UARTViewEvent() + +internal object NavigateUp : UARTViewEvent() diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt index 1b87f209..f04bcba6 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt @@ -3,14 +3,13 @@ package no.nordicsemi.android.uart.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.service.BleManagerStatus -import no.nordicsemi.android.service.ServiceManager -import no.nordicsemi.android.uart.data.DisconnectCommand -import no.nordicsemi.android.uart.data.SendTextCommand +import no.nordicsemi.android.uart.data.UARTMacro import no.nordicsemi.android.uart.data.UARTRepository -import no.nordicsemi.android.uart.repository.UARTService import no.nordicsemi.android.uart.repository.UART_SERVICE_UUID import no.nordicsemi.android.uart.view.* import no.nordicsemi.android.utils.exhaustive @@ -21,19 +20,23 @@ import javax.inject.Inject @HiltViewModel internal class UARTViewModel @Inject constructor( private val repository: UARTRepository, - 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(UARTViewState()) + val state = _state.asStateFlow() init { + if (!repository.isRunning.value) { + requestBluetoothDevice() + } + + repository.data.onEach { + _state.value = _state.value.copy(uartManagerState = WorkingState(it)) + }.launchIn(viewModelScope) + } + + private fun requestBluetoothDevice() { navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(UART_SERVICE_UUID)) navigationManager.recentResult.onEach { @@ -41,32 +44,38 @@ internal class UARTViewModel @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(UARTService::class.java, args.getDevice()) + is SuccessDestinationResult -> repository.launch(args.getDevice().device) }.exhaustive } fun onEvent(event: UARTViewEvent) { when (event) { - is OnCreateMacro -> repository.addNewMacro(event.macro) - is OnDeleteMacro -> repository.deleteMacro(event.macro) - OnDisconnectButtonClick -> repository.sendNewCommand(DisconnectCommand) - is OnRunMacro -> repository.sendNewCommand(SendTextCommand(event.macro.command)) + is OnCreateMacro -> addNewMacro(event.macro) + is OnDeleteMacro -> deleteMacro(event.macro) + DisconnectEvent -> disconnect() + is OnRunMacro -> repository.runMacro(event.macro) + NavigateUp -> navigationManager.navigateUp() }.exhaustive } - override fun onCleared() { - super.onCleared() - repository.clear() + private fun addNewMacro(macro: UARTMacro) { + _state.tryEmit(_state.value.copy(macros = _state.value.macros + macro)) + } + + private fun deleteMacro(macro: UARTMacro) { + val macros = _state.value.macros.toMutableList().apply { + remove(macro) + } + _state.tryEmit(_state.value.copy(macros = macros)) + } + + private fun disconnect() { + repository.release() + navigationManager.navigateUp() } }