From ded529410ff2329c4d768c8f3d84c65ac6d03ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylwester=20Zieli=C5=84ski?= Date: Tue, 22 Feb 2022 11:03:03 +0100 Subject: [PATCH] Add UI for handling configurations for UART profile --- .../nordicsemi/android/bps/data/BPSManager.kt | 4 - profile_uart/build.gradle | 2 +- .../android/uart/data/UARTConfiguration.kt | 15 +- .../uart/data/UARTPersistentDataSource.kt | 19 ++- .../android/uart/db/Configuration.kt | 4 +- .../android/uart/db/ConfigurationsDao.kt | 3 + .../android/uart/db/XmlConfiguration.kt | 12 +- .../uart/view/UARTAddConfigurationDialog.kt | 81 +++++++++ .../android/uart/view/UARTAddMacroDialog.kt | 160 ++++++++++++------ .../uart/view/UARTConfigurationPicker.kt | 83 +++++++++ .../android/uart/view/UARTContentView.kt | 59 ++++--- .../android/uart/view/UARTMacroView.kt | 125 ++++++++++++++ .../android/uart/view/UARTMapper.kt | 28 +++ .../android/uart/view/UARTScreen.kt | 8 +- .../nordicsemi/android/uart/view/UARTState.kt | 11 +- .../android/uart/view/UARTViewEvent.kt | 7 + .../android/uart/viewmodel/UARTViewModel.kt | 57 ++++++- .../src/main/res/drawable/ic_uart_1.xml | 9 + .../src/main/res/drawable/ic_uart_2.xml | 9 + .../src/main/res/drawable/ic_uart_3.xml | 9 + .../src/main/res/drawable/ic_uart_4.xml | 9 + .../src/main/res/drawable/ic_uart_5.xml | 9 + .../src/main/res/drawable/ic_uart_6.xml | 9 + .../src/main/res/drawable/ic_uart_7.xml | 9 + .../src/main/res/drawable/ic_uart_8.xml | 9 + .../src/main/res/drawable/ic_uart_9.xml | 9 + .../src/main/res/drawable/ic_uart_about.xml | 9 + .../src/main/res/drawable/ic_uart_down.xml | 9 + .../src/main/res/drawable/ic_uart_forward.xml | 9 + .../src/main/res/drawable/ic_uart_left.xml | 9 + .../src/main/res/drawable/ic_uart_pause.xml | 9 + .../src/main/res/drawable/ic_uart_play.xml | 9 + .../src/main/res/drawable/ic_uart_rewind.xml | 9 + .../src/main/res/drawable/ic_uart_right.xml | 9 + .../main/res/drawable/ic_uart_settings.xml | 9 + .../src/main/res/drawable/ic_uart_stop.xml | 9 + .../src/main/res/drawable/ic_uart_up.xml | 9 + .../src/main/res/drawable/uart_button.xml | 44 +++++ profile_uart/src/main/res/values/strings.xml | 12 ++ 39 files changed, 810 insertions(+), 104 deletions(-) create mode 100644 profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt create mode 100644 profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTConfigurationPicker.kt create mode 100644 profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt create mode 100644 profile_uart/src/main/res/drawable/ic_uart_1.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_2.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_3.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_4.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_5.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_6.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_7.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_8.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_9.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_about.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_down.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_forward.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_left.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_pause.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_play.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_rewind.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_right.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_settings.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_stop.xml create mode 100644 profile_uart/src/main/res/drawable/ic_uart_up.xml create mode 100644 profile_uart/src/main/res/drawable/uart_button.xml diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt index 5ae31938..d5286f41 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt @@ -72,10 +72,6 @@ internal class BPSManager( return Log.VERBOSE } - override fun log(priority: Int, message: String) { - Log.println(priority, "AAA", message) - } - override fun getGattCallback(): BleManagerGattCallback { return BloodPressureManagerGattCallback() } diff --git a/profile_uart/build.gradle b/profile_uart/build.gradle index 0f8532fe..b4d95a07 100644 --- a/profile_uart/build.gradle +++ b/profile_uart/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'com.google.protobuf' protobuf { protoc { - artifact = 'com.google.protobuf:protoc:3.14.0:osx-x86_64' + artifact = 'com.google.protobuf:protoc:3.14.0' } // Generates the java Protobuf-lite code for the Protobufs in this project. See diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTConfiguration.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTConfiguration.kt index e3ed3c10..b3df12f9 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTConfiguration.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTConfiguration.kt @@ -1,6 +1,17 @@ package no.nordicsemi.android.uart.data +import no.nordicsemi.android.uart.db.XmlCommand + +private const val MACROS_SIZES = 9 + data class UARTConfiguration( val name: String, - val macros: List -) + val macros: List = List(9) { null } +) { + + init { + if (macros.size < 9) { + throw IllegalArgumentException("Macros should always have 9 positions.") + } + } +} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTPersistentDataSource.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTPersistentDataSource.kt index 8f6295c9..e03a5cb9 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTPersistentDataSource.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTPersistentDataSource.kt @@ -1,6 +1,6 @@ package no.nordicsemi.android.uart.data -import dagger.hilt.android.qualifiers.ApplicationContext +import android.util.Log import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import no.nordicsemi.android.uart.db.* @@ -16,7 +16,6 @@ import javax.inject.Singleton @Singleton internal class UARTPersistentDataSource @Inject constructor( - @ApplicationContext private val configurationsDao: ConfigurationsDao, ) { @@ -31,10 +30,14 @@ internal class UARTPersistentDataSource @Inject constructor( } } - private fun createMacro(macros: Array): List { - return macros.filterNotNull().mapNotNull { - val icon = MacroIcon.create(it.iconIndex) - it.command?.let { c -> UARTMacro(icon, c, it.eol) } + private fun createMacro(macros: Array): List { + return macros.map { + if (it == null) { + null + } else { + val icon = MacroIcon.create(it.iconIndex) + it.command?.let { c -> UARTMacro(icon, c, it.eol) } + } } } @@ -48,4 +51,8 @@ internal class UARTPersistentDataSource @Inject constructor( configurationsDao.insert(Configuration(0, configuration.name, xml, 0)) } + + suspend fun deleteConfiguration(configuration: UARTConfiguration) { + configurationsDao.delete(configuration.name) + } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/db/Configuration.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/db/Configuration.kt index 84c2976b..6d4fd0b2 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/db/Configuration.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/db/Configuration.kt @@ -7,8 +7,8 @@ import androidx.room.PrimaryKey @Entity(tableName = "configurations") internal data class Configuration( @PrimaryKey(autoGenerate = true) - @ColumnInfo(name = "id") val id: Int, + @ColumnInfo(name = "_id") val _id: Int?, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "xml") val xml: String, - @ColumnInfo(name = "deleted") val deleted: Int + @ColumnInfo(name = "deleted", defaultValue = "0") val deleted: Int ) diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/db/ConfigurationsDao.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/db/ConfigurationsDao.kt index 5683aa87..fb0e8f8e 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/db/ConfigurationsDao.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/db/ConfigurationsDao.kt @@ -14,4 +14,7 @@ internal interface ConfigurationsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(configuration: Configuration) + + @Query("DELETE FROM configurations WHERE name = :name") + suspend fun delete(name: String) } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/db/XmlConfiguration.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/db/XmlConfiguration.kt index 2ae5c8be..20c3c80d 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/db/XmlConfiguration.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/db/XmlConfiguration.kt @@ -28,13 +28,13 @@ import org.simpleframework.xml.core.PersistenceException import org.simpleframework.xml.core.Validate @Root -internal class XmlConfiguration { +internal class XmlConfiguration @JvmOverloads constructor( + @field:Attribute(required = false, empty = "Unnamed") + var name: String? = "", - @Attribute(required = false, empty = "Unnamed") - var name: String? = null - - @ElementArray - val commands: Array = arrayOfNulls(COMMANDS_COUNT) + @field:ElementArray + var commands: Array = arrayOfNulls(COMMANDS_COUNT) +) { @Validate @Throws(PersistenceException::class) diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt new file mode 100644 index 00000000..d1f39092 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt @@ -0,0 +1,81 @@ +package no.nordicsemi.android.uart.view + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +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.utils.EMPTY + +@Composable +internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit) { + val name = remember { mutableStateOf(String.EMPTY) } + val isError = remember { mutableStateOf(false) } + + Column { + NameInput(name, isError) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = { onEvent(OnEditFinish) }) { + Text(stringResource(id = R.string.uart_macro_dialog_dismiss)) + } + + Spacer(modifier = Modifier.size(16.dp)) + + TextButton(onClick = { + if (isNameValid(name.value)) { + onEvent(OnEditFinish) + onEvent(OnAddConfiguration(name.value)) + } else { + isError.value = true + } + }) { + Text(stringResource(id = R.string.uart_macro_dialog_confirm)) + } + } + } + +} + +@Composable +private fun NameInput( + name: MutableState, + isError: MutableState +) { + Column { + TextField( + text = name.value, + hint = stringResource(id = R.string.uart_macro_dialog_command) + ) { + isError.value = false + name.value = it + } + + if (isError.value) { + Text( + text = stringResource(id = R.string.uart_name_empty), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.error + ) + } + + Spacer(modifier = Modifier.size(16.dp)) + } +} + +private fun isNameValid(name: String): Boolean { + return name.isNotBlank() +} 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 index 4afb5e7e..6a852c95 100644 --- 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 @@ -1,18 +1,26 @@ package no.nordicsemi.android.uart.view +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.lazy.GridCells +import androidx.compose.foundation.lazy.GridItemSpan +import androidx.compose.foundation.lazy.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -26,13 +34,17 @@ import no.nordicsemi.android.uart.data.MacroIcon import no.nordicsemi.android.uart.data.UARTMacro import no.nordicsemi.android.utils.EMPTY -@Composable -internal fun UARTAddMacroDialog(onDismiss: () -> Unit, onEvent: (UARTViewEvent) -> Unit) { - val command = remember { mutableStateOf(String.EMPTY) } - val newLineChar = remember { mutableStateOf(MacroEol.LF) } - val isError = remember { mutableStateOf(false) } +private const val GRID_SIZE = 5 - Dialog(onDismissRequest = { onDismiss() }) { +@OptIn(ExperimentalFoundationApi::class) +@Composable +internal fun UARTAddMacroDialog(onEvent: (UARTViewEvent) -> Unit) { + val newLineChar = remember { mutableStateOf(MacroEol.LF) } + val command = remember { mutableStateOf(String.EMPTY) } + val isError = remember { mutableStateOf(false) } + val selectedIcon = remember { mutableStateOf(MacroIcon.values()[0]) } + + Dialog(onDismissRequest = { onEvent(OnEditFinish) }) { Surface( color = MaterialTheme.colorScheme.background, shape = RoundedCornerShape(10.dp), @@ -47,59 +59,105 @@ internal fun UARTAddMacroDialog(onDismiss: () -> Unit, onEvent: (UARTViewEvent) .padding(16.dp) ) - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - Column(modifier = Modifier.padding(bottom = 8.dp, start = 16.dp, end = 16.dp)) { - - NewLineCharSection(newLineChar.value) { newLineChar.value = it } - - Spacer(modifier = Modifier.size(16.dp)) - - TextField( - text = command.value, - hint = stringResource(id = R.string.uart_macro_dialog_command) - ) { - isError.value = false - command.value = it - } - - if (isError.value) { - Text( - text = stringResource(id = R.string.uart_macro_error), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.error - ) - } - - Spacer(modifier = Modifier.size(16.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - TextButton(onClick = { onDismiss() }) { - Text(stringResource(id = R.string.uart_macro_dialog_dismiss)) - } + LazyVerticalGrid( + cells = GridCells.Fixed(GRID_SIZE), + modifier = Modifier + .padding(horizontal = 16.dp) + .wrapContentHeight() + ) { + item(span = { GridItemSpan(GRID_SIZE) }) { + Column { + NewLineCharSection(newLineChar.value) { newLineChar.value = it } Spacer(modifier = Modifier.size(16.dp)) - - TextButton(onClick = { - if (isCommandValid(command.value)) { - onDismiss() - onEvent(OnCreateMacro(UARTMacro(MacroIcon.DOWN, command.value, newLineChar.value))) - } else { - isError.value = true - } - }) { - Text(stringResource(id = R.string.uart_macro_dialog_confirm)) - } } } + + item(span = { GridItemSpan(GRID_SIZE) }) { + CommandInput(command, isError) + } + + items(20) { item -> + val icon = MacroIcon.create(item) + val background = if (selectedIcon.value == icon) { + MaterialTheme.colorScheme.primaryContainer + } else { + MaterialTheme.colorScheme.secondaryContainer + } + + Image( + painter = painterResource(id = icon.toResId()), + contentDescription = stringResource(id = R.string.uart_macro_icon), + modifier = Modifier + .size(40.dp) + .clip(RoundedCornerShape(10.dp)) + .clickable { selectedIcon.value = icon } + .background(background) + ) + } + } + + Spacer(modifier = Modifier.size(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = { onEvent(OnEditFinish) }) { + Text(stringResource(id = R.string.uart_macro_dialog_dismiss)) + } + + Spacer(modifier = Modifier.size(16.dp)) + + TextButton(onClick = { + if (isCommandValid(command.value)) { + onEvent( + OnCreateMacro( + UARTMacro( + selectedIcon.value, + command.value, + newLineChar.value + ) + ) + ) + } else { + isError.value = true + } + }) { + Text(stringResource(id = R.string.uart_macro_dialog_confirm)) + } } } } } } +@Composable +private fun CommandInput( + command: MutableState, + isError: MutableState +) { + Column { + TextField( + text = command.value, + hint = stringResource(id = R.string.uart_macro_dialog_command) + ) { + isError.value = false + command.value = it + } + + if (isError.value) { + Text( + text = stringResource(id = R.string.uart_macro_error), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.error + ) + } + + Spacer(modifier = Modifier.size(16.dp)) + } +} + @Composable private fun NewLineCharSection(checkedItem: MacroEol, onItemClick: (MacroEol) -> Unit) { val items = MacroEol.values().map { diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTConfigurationPicker.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTConfigurationPicker.kt new file mode 100644 index 00000000..9eb462df --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTConfigurationPicker.kt @@ -0,0 +1,83 @@ +package no.nordicsemi.android.uart.view + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import no.nordicsemi.android.theme.view.dialog.* +import no.nordicsemi.android.uart.R +import no.nordicsemi.android.uart.data.UARTConfiguration +import no.nordicsemi.android.utils.exhaustive + +@Composable +internal fun UARTConfigurationPicker(state: UARTViewState, onEvent: (UARTViewEvent) -> Unit) { + val showDialog = rememberSaveable { mutableStateOf(false) } + + UARTConfigurationButton(state.selectedConfiguration) { + showDialog.value = true + } + + if (showDialog.value) { + SelectWheelSizeDialog(state) { + when (it) { + FlowCanceled -> showDialog.value = false + is ItemSelectedResult -> { + onEvent(OnConfigurationSelected(state.configurations[it.index])) + showDialog.value = false + } + }.exhaustive + } + } +} + +@Composable +internal fun SelectWheelSizeDialog(state: UARTViewState, onEvent: (StringListDialogResult) -> Unit) { + val wheelEntries = state.configurations.map { it.name } + + StringListDialog(createConfig(wheelEntries) { + onEvent(it) + }) +} + +@Composable +private fun createConfig(entries: List, onResult: (StringListDialogResult) -> Unit): StringListDialogConfig { + return StringListDialogConfig( + title = stringResource(id = R.string.uart_configuration_picker_dialog).toAnnotatedString(), + items = entries, + onResult = onResult + ) +} + +@Composable +internal fun UARTConfigurationButton(configuration: UARTConfiguration?, onClick: () -> Unit) { + OutlinedButton(onClick = { onClick() }) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ){ + Column { + Text( + text = stringResource(id = R.string.uart_configuration_picker_hint), + style = MaterialTheme.typography.labelSmall + ) + val text = configuration?.name ?: stringResource(id = R.string.uart_configuration_picker_not_selected) + Text(text = text, style = MaterialTheme.typography.bodyMedium) + } + + Icon(Icons.Default.ArrowDropDown, contentDescription = "") + } + } +} 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 845dacc6..84943c80 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,11 +1,13 @@ package no.nordicsemi.android.uart.view -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.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -18,10 +20,9 @@ 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, macros: List, onEvent: (UARTViewEvent) -> Unit) { +internal fun UARTContentView(state: UARTData, viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) @@ -30,7 +31,7 @@ internal fun UARTContentView(state: UARTData, macros: List, onEvent: Spacer(modifier = Modifier.height(16.dp)) - InputSection(macros, onEvent) + InputSection(viewState, onEvent) Spacer(modifier = Modifier.height(16.dp)) @@ -43,11 +44,11 @@ internal fun UARTContentView(state: UARTData, macros: List, onEvent: } @Composable -private fun InputSection(macros: List, onEvent: (UARTViewEvent) -> Unit) { - val showSearchDialog = remember { mutableStateOf(false) } +private fun InputSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) { + val showDialog = remember { mutableStateOf(false) } - if (showSearchDialog.value) { - UARTAddMacroDialog(onDismiss = { showSearchDialog.value = false }, onEvent = onEvent) + if (showDialog.value) { + UARTAddConfigurationDialog(onEvent) } ScreenSection { @@ -58,25 +59,31 @@ private fun InputSection(macros: List, onEvent: (UARTViewEvent) -> Un Spacer(modifier = Modifier.height(16.dp)) - macros.forEach { - MacroItem(macro = it, onEvent = onEvent) + Row { + Box(modifier = Modifier.weight(1f)) { + UARTConfigurationPicker(viewState, onEvent) + } + + IconButton(onClick = { showDialog.value = true }) { + Icon(Icons.Default.Add, stringResource(id = R.string.uart_configuration_add)) + } - Spacer(modifier = Modifier.height(16.dp)) + viewState.selectedConfiguration?.let { + + IconButton(onClick = { onEvent(OnEditConfiguration) }) { + Icon(Icons.Default.Edit, stringResource(id = R.string.uart_configuration_edit)) + } + + IconButton(onClick = { onEvent(OnDeleteConfiguration) }) { + Icon(Icons.Default.Delete, stringResource(id = R.string.uart_configuration_delete)) + } + } } - if (macros.isEmpty()) { - Text( - text = stringResource(id = R.string.uart_no_macros_info), - style = MaterialTheme.typography.bodyMedium - ) - + viewState.selectedConfiguration?.let { Spacer(modifier = Modifier.height(16.dp)) - } - Button( - onClick = { showSearchDialog.value = true } - ) { - Text(text = stringResource(id = R.string.uart_add_macro)) + UARTMacroView(it, viewState.isConfigurationEdited, onEvent) } } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt new file mode 100644 index 00000000..15747af4 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt @@ -0,0 +1,125 @@ +package no.nordicsemi.android.uart.view + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.uart.R +import no.nordicsemi.android.uart.data.UARTConfiguration +import no.nordicsemi.android.uart.data.UARTMacro + +private val divider = 4.dp +private val buttonSize = 80.dp + +@Composable +internal fun UARTMacroView( + configuration: UARTConfiguration, + isEdited: Boolean, + onEvent: (UARTViewEvent) -> Unit +) { + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + + Row { + Item(configuration, isEdited, 0, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 1, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 2, onEvent) + } + + Spacer(modifier = Modifier.size(divider)) + + Row { + Item(configuration, isEdited, 3, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 4, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 5, onEvent) + } + + Spacer(modifier = Modifier.size(divider)) + + Row { + Item(configuration, isEdited, 6, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 7, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 8, onEvent) + } + } +} + +@Composable +private fun Item( + configuration: UARTConfiguration, + isEdited: Boolean, + position: Int, + onEvent: (UARTViewEvent) -> Unit +) { + val macro = configuration.macros.getOrNull(position) + + if (macro == null) { + EmptyButton(isEdited, position, onEvent) + } else { + MacroButton(macro, position, isEdited, onEvent) + } +} + +@Composable +private fun MacroButton( + macro: UARTMacro, + position: Int, + isEdited: Boolean, + onEvent: (UARTViewEvent) -> Unit +) { + Image( + painter = painterResource(id = macro.icon.toResId()), + contentDescription = stringResource(id = R.string.uart_macro_icon), + modifier = Modifier + .size(buttonSize) + .clip(RoundedCornerShape(10.dp)) + .clickable { + if (isEdited) { + onEvent(OnEditMacro(position)) + } else { + onEvent(OnRunMacro(macro)) + } + } + .background(getBackground(isEdited)) + ) +} + +@Composable +private fun EmptyButton( + isEdited: Boolean, + position: Int, + onEvent: (UARTViewEvent) -> Unit +) { + Box(modifier = Modifier + .size(buttonSize) + .clip(RoundedCornerShape(10.dp)) + .clickable { + if (isEdited) { + onEvent(OnEditMacro(position)) + } + } + .background(getBackground(isEdited))) +} + +@Composable +private fun getBackground(isEdited: Boolean): Color { + return if (!isEdited) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.secondary + } +} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMapper.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMapper.kt index aad6b72b..9aded5cc 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMapper.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMapper.kt @@ -1,9 +1,11 @@ package no.nordicsemi.android.uart.view +import androidx.annotation.DrawableRes import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.data.MacroEol +import no.nordicsemi.android.uart.data.MacroIcon @Composable fun MacroEol.toDisplayString(): String { @@ -13,3 +15,29 @@ fun MacroEol.toDisplayString(): String { MacroEol.CR -> stringResource(id = R.string.uart_macro_dialog_cr) } } + +@DrawableRes +fun MacroIcon.toResId(): Int { + return when (this) { + MacroIcon.LEFT -> R.drawable.ic_uart_left + MacroIcon.UP -> R.drawable.ic_uart_up + MacroIcon.RIGHT -> R.drawable.ic_uart_right + MacroIcon.DOWN -> R.drawable.ic_uart_down + MacroIcon.SETTINGS -> R.drawable.ic_uart_settings + MacroIcon.REW -> R.drawable.ic_uart_rewind + MacroIcon.PLAY -> R.drawable.ic_uart_play + MacroIcon.PAUSE -> R.drawable.ic_uart_pause + MacroIcon.STOP -> R.drawable.ic_uart_stop + MacroIcon.FWD -> R.drawable.ic_uart_forward + MacroIcon.INFO -> R.drawable.ic_uart_about + MacroIcon.NUMBER_1 -> R.drawable.ic_uart_1 + MacroIcon.NUMBER_2 -> R.drawable.ic_uart_2 + MacroIcon.NUMBER_3 -> R.drawable.ic_uart_3 + MacroIcon.NUMBER_4 -> R.drawable.ic_uart_4 + MacroIcon.NUMBER_5 -> R.drawable.ic_uart_5 + MacroIcon.NUMBER_6 -> R.drawable.ic_uart_6 + MacroIcon.NUMBER_7 -> R.drawable.ic_uart_7 + MacroIcon.NUMBER_8 -> R.drawable.ic_uart_8 + MacroIcon.NUMBER_9 -> R.drawable.ic_uart_9 + } +} 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 82babbc1..a20f724e 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 @@ -5,6 +5,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel @@ -23,6 +25,10 @@ fun UARTScreen() { val viewModel: UARTViewModel = hiltViewModel() val state = viewModel.state.collectAsState().value + if (state.showEditDialog) { + UARTAddMacroDialog { viewModel.onEvent(it) } + } + Column { val navigateUp = { viewModel.onEvent(NavigateUp) } @@ -37,7 +43,7 @@ fun UARTScreen() { is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) - is SuccessResult -> UARTContentView(state.uartManagerState.result.data, state.configuration) { viewModel.onEvent(it) } + is SuccessResult -> UARTContentView(state.uartManagerState.result.data, state) { 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 de402d2d..7f6d51c9 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 @@ -5,9 +5,16 @@ import no.nordicsemi.android.uart.data.UARTConfiguration import no.nordicsemi.android.uart.data.UARTData internal data class UARTViewState( - val configuration: List = emptyList(), + val editedPosition: Int? = null, + val selectedConfigurationIndex: Int? = null, + val isConfigurationEdited: Boolean = false, + val configurations: List = emptyList(), val uartManagerState: HTSManagerState = NoDeviceState -) +) { + val showEditDialog: Boolean = editedPosition != null + + val selectedConfiguration: UARTConfiguration? = selectedConfigurationIndex?.let { configurations[it] } +} internal sealed class 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 fecd9266..c95a79bb 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 @@ -1,12 +1,19 @@ package no.nordicsemi.android.uart.view +import no.nordicsemi.android.uart.data.UARTConfiguration import no.nordicsemi.android.uart.data.UARTMacro internal sealed class UARTViewEvent +internal data class OnEditMacro(val position: Int) : UARTViewEvent() internal data class OnCreateMacro(val macro: UARTMacro) : UARTViewEvent() internal data class OnDeleteMacro(val macro: UARTMacro) : UARTViewEvent() +internal object OnEditFinish : UARTViewEvent() +internal data class OnConfigurationSelected(val configuration: UARTConfiguration) : UARTViewEvent() +internal data class OnAddConfiguration(val name: String) : UARTViewEvent() +internal object OnEditConfiguration : UARTViewEvent() +internal object OnDeleteConfiguration : UARTViewEvent() internal data class OnRunMacro(val macro: UARTMacro) : UARTViewEvent() internal object DisconnectEvent : 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 aeb0a0f2..fe202335 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 @@ -7,6 +7,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.navigation.* +import no.nordicsemi.android.uart.data.UARTConfiguration import no.nordicsemi.android.uart.data.UARTMacro import no.nordicsemi.android.uart.data.UARTPersistentDataSource import no.nordicsemi.android.uart.data.UART_SERVICE_UUID @@ -39,7 +40,7 @@ internal class UARTViewModel @Inject constructor( }.launchIn(viewModelScope) dataSource.getConfigurations().onEach { - _state.value = _state.value.copy(configuration = it) + _state.value = _state.value.copy(configurations = it) }.launchIn(viewModelScope) } @@ -67,18 +68,68 @@ internal class UARTViewModel @Inject constructor( DisconnectEvent -> disconnect() is OnRunMacro -> repository.runMacro(event.macro) NavigateUp -> navigationManager.navigateUp() + is OnEditMacro -> onEditMacro(event) + OnEditFinish -> onEditFinish() + is OnConfigurationSelected -> onConfigurationSelected(event) + is OnAddConfiguration -> onAddConfiguration(event) + OnDeleteConfiguration -> deleteConfiguration() + OnEditConfiguration -> onEditConfiguration() }.exhaustive } + private fun onEditConfiguration() { + _state.value = _state.value.copy(isConfigurationEdited = true) + } + + private fun onAddConfiguration(event: OnAddConfiguration) { + viewModelScope.launch(Dispatchers.IO) { + dataSource.saveConfiguration(UARTConfiguration(event.name)) + } + } + + private fun onEditMacro(event: OnEditMacro) { + _state.value = _state.value.copy(editedPosition = event.position) + } + + private fun onEditFinish() { + _state.value = _state.value.copy(editedPosition = null) + } + + private fun onConfigurationSelected(event: OnConfigurationSelected) { + _state.value = _state.value.copy(selectedConfigurationIndex = _state.value.configurations.indexOf(event.configuration)) + } + private fun addNewMacro(macro: UARTMacro) { viewModelScope.launch(Dispatchers.IO) { - dataSource.addNewMacro(macro) + _state.value.selectedConfiguration?.let { + val macros = it.macros.toMutableList().apply { + set(_state.value.editedPosition!!, macro) + } + val newConf = it.copy(macros = macros) + val newConfs = _state.value.configurations.map { + if (it.name == newConf.name) { + newConf + } else { + it + } + } + dataSource.saveConfiguration(newConf) + _state.value = _state.value.copy(editedPosition = null) + } + } + } + + private fun deleteConfiguration() { + viewModelScope.launch(Dispatchers.IO) { + _state.value.selectedConfiguration?.let { + dataSource.deleteConfiguration(it) + } } } private fun deleteMacro(macro: UARTMacro) { viewModelScope.launch(Dispatchers.IO) { - dataSource.deleteMacro(macro) +// dataSource.deleteMacro(macro) } } diff --git a/profile_uart/src/main/res/drawable/ic_uart_1.xml b/profile_uart/src/main/res/drawable/ic_uart_1.xml new file mode 100644 index 00000000..fddf0dbf --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_1.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_2.xml b/profile_uart/src/main/res/drawable/ic_uart_2.xml new file mode 100644 index 00000000..e4f435aa --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_2.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_3.xml b/profile_uart/src/main/res/drawable/ic_uart_3.xml new file mode 100644 index 00000000..add4bb2f --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_3.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_4.xml b/profile_uart/src/main/res/drawable/ic_uart_4.xml new file mode 100644 index 00000000..98a8e26d --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_4.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_5.xml b/profile_uart/src/main/res/drawable/ic_uart_5.xml new file mode 100644 index 00000000..39f651ba --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_5.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_6.xml b/profile_uart/src/main/res/drawable/ic_uart_6.xml new file mode 100644 index 00000000..39f651ba --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_6.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_7.xml b/profile_uart/src/main/res/drawable/ic_uart_7.xml new file mode 100644 index 00000000..39f651ba --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_7.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_8.xml b/profile_uart/src/main/res/drawable/ic_uart_8.xml new file mode 100644 index 00000000..39f651ba --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_8.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_9.xml b/profile_uart/src/main/res/drawable/ic_uart_9.xml new file mode 100644 index 00000000..39f651ba --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_9.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_about.xml b/profile_uart/src/main/res/drawable/ic_uart_about.xml new file mode 100644 index 00000000..3b028ec6 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_about.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_down.xml b/profile_uart/src/main/res/drawable/ic_uart_down.xml new file mode 100644 index 00000000..f4ae791c --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_forward.xml b/profile_uart/src/main/res/drawable/ic_uart_forward.xml new file mode 100644 index 00000000..f210b6a7 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_forward.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_left.xml b/profile_uart/src/main/res/drawable/ic_uart_left.xml new file mode 100644 index 00000000..53abfa3a --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_pause.xml b/profile_uart/src/main/res/drawable/ic_uart_pause.xml new file mode 100644 index 00000000..f0a1e107 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_pause.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_play.xml b/profile_uart/src/main/res/drawable/ic_uart_play.xml new file mode 100644 index 00000000..4dae2144 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_play.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_rewind.xml b/profile_uart/src/main/res/drawable/ic_uart_rewind.xml new file mode 100644 index 00000000..1a0c063b --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_rewind.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_right.xml b/profile_uart/src/main/res/drawable/ic_uart_right.xml new file mode 100644 index 00000000..97e9e0ff --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_settings.xml b/profile_uart/src/main/res/drawable/ic_uart_settings.xml new file mode 100644 index 00000000..a0616ccf --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_stop.xml b/profile_uart/src/main/res/drawable/ic_uart_stop.xml new file mode 100644 index 00000000..037c2cdc --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_stop.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_uart_up.xml b/profile_uart/src/main/res/drawable/ic_uart_up.xml new file mode 100644 index 00000000..b02ccfaf --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_uart_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/profile_uart/src/main/res/drawable/uart_button.xml b/profile_uart/src/main/res/drawable/uart_button.xml new file mode 100644 index 00000000..0abfeb58 --- /dev/null +++ b/profile_uart/src/main/res/drawable/uart_button.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profile_uart/src/main/res/values/strings.xml b/profile_uart/src/main/res/values/strings.xml index 90ec249d..4735fbf2 100644 --- a/profile_uart/src/main/res/values/strings.xml +++ b/profile_uart/src/main/res/values/strings.xml @@ -4,8 +4,15 @@ Please define a macro to send command to the device. + Add selected configuration. + Delete selected configuration. + Edit selected configuration. + Macros Output + Select configuration + Not selected. + Select configuration Command: %s @@ -29,4 +36,9 @@ The incoming messages will be displayed here. Provided command cannot be empty. + + Name + Provided name cannot be empty. + + Icon representing defined command.