diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt index 5aee6542..5cc9f817 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt @@ -260,7 +260,6 @@ fun HomeView(callback: (NavDestination) -> Unit) { R.string.uart_module_full ) { callback(NavDestination.UART) } } - Spacer(modifier = Modifier.width(16.dp)) } } } 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 e7b0a0d9..857b15ff 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,5 +4,6 @@ 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/UARTDataHolder.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTDataHolder.kt index 30551168..93949c91 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTDataHolder.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTDataHolder.kt @@ -1,6 +1,9 @@ package no.nordicsemi.android.uart.data +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject import javax.inject.Singleton @@ -11,6 +14,20 @@ internal class UARTDataHolder @Inject constructor() { private val _data = MutableStateFlow(UARTData()) val data = _data.asStateFlow() + private val _command = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST) + val command = _command.asSharedFlow() + + fun addNewMacro(macro: UARTMacro) { + _data.tryEmit(_data.value.copy(macros = _data.value.macros + macro)) + } + + fun deleteMacro(macro: UARTMacro) { + val macros = _data.value.macros.toMutableList().apply { + remove(macro) + } + _data.tryEmit(_data.value.copy(macros = macros)) + } + fun emitNewMessage(message: String) { _data.tryEmit(_data.value.copy(text = message)) } @@ -18,4 +35,8 @@ internal class UARTDataHolder @Inject constructor() { fun emitNewBatteryLevel(batteryLevel: Int) { _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel)) } + + fun sendNewCommand(command: UARTServiceCommand) { + _command.tryEmit(command) + } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTMacro.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTMacro.kt new file mode 100644 index 00000000..ae42f710 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTMacro.kt @@ -0,0 +1,6 @@ +package no.nordicsemi.android.uart.data + +data class UARTMacro( + val alias: String, + val command: String, +) diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTServiceCommand.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTServiceCommand.kt new file mode 100644 index 00000000..eb6fb906 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTServiceCommand.kt @@ -0,0 +1,3 @@ +package no.nordicsemi.android.uart.data + +data class UARTServiceCommand(val command: String) 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 762f2c7f..07953057 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,6 +1,9 @@ package no.nordicsemi.android.uart.repository +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.UARTDataHolder import javax.inject.Inject @@ -12,4 +15,12 @@ internal class UARTService : ForegroundBleService() { lateinit var dataHolder: UARTDataHolder override val manager: UARTManager by lazy { UARTManager(this, dataHolder) } + + override fun onCreate() { + super.onCreate() + + dataHolder.command.onEach { + manager.send(it.command) + }.launchIn(lifecycleScope) + } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddMacroDialog.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddMacroDialog.kt new file mode 100644 index 00000000..acd3d3ae --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddMacroDialog.kt @@ -0,0 +1,61 @@ +package no.nordicsemi.android.uart.view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.material.you.TextField +import no.nordicsemi.android.uart.R +import no.nordicsemi.android.uart.data.UARTMacro +import no.nordicsemi.android.utils.EMPTY + +@Composable +internal fun UARTAddMacroDialog(onDismiss: () -> Unit, onEvent: (UARTViewEvent) -> Unit) { + val alias = remember { mutableStateOf(String.EMPTY) } + val command = remember { mutableStateOf(String.EMPTY) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text(text = stringResource(id = R.string.uart_macro_dialog_title)) + }, + text = { + Column { + TextField(text = alias.value, hint = stringResource(id = R.string.uart_macro_dialog_alias)) { + alias.value = it + } + + Spacer(modifier = Modifier.padding(16.dp)) + + TextField(text = command.value, hint = stringResource(id = R.string.uart_macro_dialog_command)) { + command.value = it + } + } + }, + confirmButton = { + TextButton( + onClick = { + onDismiss() + onEvent(OnCreateMacro(UARTMacro(alias.value, command.value))) + } + ) { + Text(stringResource(id = R.string.uart_macro_dialog_confirm)) + } + }, + dismissButton = { + TextButton( + onClick = { onDismiss() } + ) { + Text(stringResource(id = R.string.uart_macro_dialog_dismiss)) + } + } + ) +} 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 9085220b..2abf4566 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 @@ -1,4 +1,95 @@ package no.nordicsemi.android.uart.view -class UARTContentView { -} \ No newline at end of file +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +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 + +@Composable +internal fun UARTContentView(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + Spacer(modifier = Modifier.height(16.dp)) + + InputSection(state, onEvent) + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { onEvent(OnDisconnectButtonClick) } + ) { + Text(text = stringResource(id = R.string.disconnect)) + } + } +} + +@Composable +private fun InputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { + val showSearchDialog = remember { mutableStateOf(false) } + + if (showSearchDialog.value) { + UARTAddMacroDialog(onDismiss = { showSearchDialog.value = false }, onEvent = onEvent) + } + + ScreenSection { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_input)) + + Spacer(modifier = Modifier.height(16.dp)) + + state.macros.forEach { + MacroItem(macro = it, onEvent = onEvent) + + Spacer(modifier = Modifier.height(16.dp)) + } + + if (state.macros.isEmpty()) { + Text( + text = stringResource(id = R.string.uart_no_macros_info), + style = MaterialTheme.typography.bodyMedium + ) + + Spacer(modifier = Modifier.height(16.dp)) + } + + Button( + onClick = { showSearchDialog.value = true } + ) { + Text(text = stringResource(id = R.string.uart_add_macro)) + } + } + } +} + +@Composable +private fun OutputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { + ScreenSection { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + SectionTitle(resId = R.drawable.ic_output, title = stringResource(R.string.uart_output)) + + Spacer(modifier = Modifier.height(16.dp)) + + + } + } +} 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 d02d4760..ddbddeff 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 @@ -1,7 +1,54 @@ package no.nordicsemi.android.uart.view +import android.content.Intent +import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.hilt.navigation.compose.hiltViewModel +import no.nordicsemi.android.theme.view.BackIconAppBar +import no.nordicsemi.android.uart.R +import no.nordicsemi.android.uart.data.UARTData +import no.nordicsemi.android.uart.repository.UARTService +import no.nordicsemi.android.uart.viewmodel.UARTViewModel +import no.nordicsemi.android.utils.isServiceRunning @Composable fun UARTScreen(finishAction: () -> Unit) { + val viewModel: UARTViewModel = hiltViewModel() + val state = viewModel.state.collectAsState().value + val isScreenActive = viewModel.isActive.collectAsState().value + + val context = LocalContext.current + LaunchedEffect(isScreenActive) { + if (!isScreenActive) { + finishAction() + } + if (context.isServiceRunning(UARTService::class.java.name)) { + val intent = Intent(context, UARTService::class.java) + context.stopService(intent) + } + } + + LaunchedEffect("start-service") { + if (!context.isServiceRunning(UARTService::class.java.name)) { + val intent = Intent(context, UARTService::class.java) + context.startService(intent) + } + } + + UARTView(state) { viewModel.onEvent(it) } +} + +@Composable +private fun UARTView(state: UARTData, onEvent: (UARTViewEvent) -> Unit) { + Column { + BackIconAppBar(stringResource(id = R.string.uart_title)) { + onEvent(OnDisconnectButtonClick) + } + + UARTContentView(state) { onEvent(it) } + } } 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 new file mode 100644 index 00000000..28f9adf8 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt @@ -0,0 +1,12 @@ +package no.nordicsemi.android.uart.view + +import no.nordicsemi.android.uart.data.UARTMacro + +internal sealed class UARTViewEvent + +internal data class OnCreateMacro(val macro: UARTMacro) : UARTViewEvent() +internal data class OnDeleteMacro(val macro: UARTMacro) : UARTViewEvent() + +internal data class OnRunMacro(val macro: UARTMacro) : UARTViewEvent() + +internal object OnDisconnectButtonClick : UARTViewEvent() diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViews.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViews.kt new file mode 100644 index 00000000..76b637b8 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViews.kt @@ -0,0 +1,60 @@ +package no.nordicsemi.android.uart.view + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.material.you.Card +import no.nordicsemi.android.uart.R +import no.nordicsemi.android.uart.data.UARTMacro + +@Composable +internal fun MacroItem(macro: UARTMacro, onEvent: (UARTViewEvent) -> Unit) { + Card(backgroundColor = MaterialTheme.colorScheme.primaryContainer) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(16.dp) + ) { + Icon( + imageVector = Icons.Default.PlayArrow, + contentDescription = stringResource(id = R.string.uart_run_macro_description), + modifier = Modifier + .size(40.dp) + .clickable { onEvent(OnRunMacro(macro)) } + ) + + Spacer(modifier = Modifier.padding(16.dp)) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = macro.alias, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + Text( + text = stringResource(id = R.string.uart_command_field, macro.command), + style = MaterialTheme.typography.bodyMedium + ) + } + + Spacer(modifier = Modifier.padding(16.dp)) + + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(id = R.string.uart_delete_macro_description), + modifier = Modifier + .size(32.dp) + .clickable { onEvent(OnDeleteMacro(macro)) } + ) + } + } +} 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 98c05eca..a3fcc75d 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 @@ -1,6 +1,26 @@ package no.nordicsemi.android.uart.viewmodel +import dagger.hilt.android.lifecycle.HiltViewModel import no.nordicsemi.android.theme.viewmodel.CloseableViewModel +import no.nordicsemi.android.uart.data.UARTDataHolder +import no.nordicsemi.android.uart.data.UARTServiceCommand +import no.nordicsemi.android.uart.view.* +import no.nordicsemi.android.utils.exhaustive +import javax.inject.Inject -class UARTViewModel : CloseableViewModel() { +@HiltViewModel +internal class UARTViewModel @Inject constructor( + private val dataHolder: UARTDataHolder +) : CloseableViewModel() { + + val state = dataHolder.data + + fun onEvent(event: UARTViewEvent) { + when (event) { + is OnCreateMacro -> dataHolder.addNewMacro(event.macro) + is OnDeleteMacro -> dataHolder.deleteMacro(event.macro) + OnDisconnectButtonClick -> finish() + is OnRunMacro -> dataHolder.sendNewCommand(UARTServiceCommand(event.macro.command)) + }.exhaustive + } } diff --git a/profile_uart/src/main/res/drawable/ic_input.xml b/profile_uart/src/main/res/drawable/ic_input.xml new file mode 100644 index 00000000..72187e63 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_input.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_output.xml b/profile_uart/src/main/res/drawable/ic_output.xml new file mode 100644 index 00000000..e9ea11c0 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_output.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/values/strings.xml b/profile_uart/src/main/res/values/strings.xml new file mode 100644 index 00000000..d5e9a1e8 --- /dev/null +++ b/profile_uart/src/main/res/values/strings.xml @@ -0,0 +1,23 @@ + + + UART + + Please define a macro to send command to the device. + + Macros + Output + + Command: %s + + Run macro + Delete macro + + Add macro + Here will be displayed read value from GATT characteristic. + + Add macro + Alias + Command + Confirm + Dismiss +