Add switch between macro and text input

This commit is contained in:
Sylwester Zieliński
2022-05-03 16:21:14 +02:00
parent be04c26485
commit 718ef0f44a
10 changed files with 382 additions and 294 deletions

View File

@@ -71,6 +71,7 @@ fun BackIconAppBar(text: String, onClick: () -> Unit) {
IconButton(onClick = { onClick() }) {
Icon(
Icons.Default.ArrowBack,
tint = MaterialTheme.colorScheme.onPrimary,
contentDescription = stringResource(id = R.string.back_screen),
)
}
@@ -103,6 +104,7 @@ fun LoggerIconAppBar(text: String, onClick: () -> Unit, onLoggerClick: () -> Uni
IconButton(onClick = { onClick() }) {
Icon(
Icons.Default.ArrowBack,
tint = MaterialTheme.colorScheme.onPrimary,
contentDescription = stringResource(id = R.string.back_screen),
)
}

View File

@@ -0,0 +1,81 @@
package no.nordicsemi.android.uart.view
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.material.you.*
import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.MacroEol
import no.nordicsemi.android.utils.EMPTY
@Composable
internal fun InputSection(onEvent: (UARTViewEvent) -> Unit) {
val text = rememberSaveable { mutableStateOf(String.EMPTY) }
val hint = stringResource(id = R.string.uart_input_hint)
val checkedItem = rememberSaveable { mutableStateOf(MacroEol.values()[0]) }
val items = MacroEol.values().map {
RadioButtonItem(it.toDisplayString(), it == checkedItem.value)
}
val viewEntity = RadioGroupViewEntity(items)
ScreenSection {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
SectionTitle(
resId = R.drawable.ic_input,
title = stringResource(R.string.uart_input),
menu = {
IconButton(onClick = { onEvent(MacroInputSwitchClick) }) {
Icon(
painterResource(id = R.drawable.ic_macro),
contentDescription = stringResource(id = R.string.uart_input_macro),
)
}
}
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(id = R.string.uart_macro_dialog_eol),
style = MaterialTheme.typography.labelLarge
)
RadioButtonGroup(viewEntity) {
val i = items.indexOf(it)
checkedItem.value = MacroEol.values()[i]
}
}
Spacer(modifier = Modifier.size(16.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Box(modifier = Modifier.weight(1f)) {
TextField(text = text.value, hint = hint) {
text.value = it
}
}
Spacer(modifier = Modifier.size(16.dp))
Button(
onClick = { onEvent(OnRunInput(text.value, checkedItem.value)) },
modifier = Modifier.padding(top = 6.dp)
) {
Text(text = stringResource(id = R.string.uart_send))
}
}
}
}
}

View File

@@ -0,0 +1,125 @@
package no.nordicsemi.android.uart.view
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.*
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.material.you.ScreenSection
import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.android.uart.R
@Composable
internal fun MacroSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
val showAddDialog = rememberSaveable { mutableStateOf(false) }
val showDeleteDialog = rememberSaveable { mutableStateOf(false) }
if (showAddDialog.value) {
UARTAddConfigurationDialog(onEvent) { showAddDialog.value = false }
}
if (showDeleteDialog.value) {
DeleteConfigurationDialog(onEvent) { showDeleteDialog.value = false }
}
ScreenSection {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
SectionTitle(
resId = R.drawable.ic_macro,
title = stringResource(R.string.uart_macros),
menu = {
IconButton(onClick = { onEvent(MacroInputSwitchClick) }) {
Icon(
painterResource(id = R.drawable.ic_input),
contentDescription = stringResource(id = R.string.uart_input_macro),
)
}
}
)
Spacer(modifier = Modifier.height(16.dp))
Row {
Box(modifier = Modifier.weight(1f)) {
UARTConfigurationPicker(viewState, onEvent)
}
IconButton(onClick = { showAddDialog.value = true }) {
Icon(Icons.Default.Add, stringResource(id = R.string.uart_configuration_add))
}
viewState.selectedConfiguration?.let {
if (!viewState.isConfigurationEdited) {
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
Icon(
Icons.Default.Edit,
stringResource(id = R.string.uart_configuration_edit)
)
}
} else {
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
Icon(
painterResource(id = R.drawable.ic_pencil_off),
stringResource(id = R.string.uart_configuration_edit)
)
}
}
IconButton(onClick = { showDeleteDialog.value = true }) {
Icon(
Icons.Default.Delete,
stringResource(id = R.string.uart_configuration_delete)
)
}
}
}
viewState.selectedConfiguration?.let {
Spacer(modifier = Modifier.height(16.dp))
UARTMacroView(it, viewState.isConfigurationEdited, onEvent)
}
}
}
}
@Composable
private fun DeleteConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = stringResource(id = R.string.uart_delete_dialog_title),
style = MaterialTheme.typography.headlineSmall
)
},
text = {
Text(text = stringResource(id = R.string.uart_delete_dialog_info))
},
confirmButton = {
Button(onClick = {
onDismiss()
onEvent(OnDeleteConfiguration)
}) {
Text(text = stringResource(id = R.string.uart_delete_dialog_confirm))
}
},
dismissButton = {
Button(onClick = onDismiss) {
Text(text = stringResource(id = R.string.uart_delete_dialog_cancel))
}
}
)
}

View File

@@ -0,0 +1,143 @@
package no.nordicsemi.android.uart.view
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTRecord
import no.nordicsemi.android.uart.data.UARTRecordType
import java.text.SimpleDateFormat
import java.util.*
@Composable
internal fun OutputSection(records: List<UARTRecord>, onEvent: (UARTViewEvent) -> Unit) {
val scrollDown = remember { mutableStateOf(true) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
SectionTitle(
resId = R.drawable.ic_output,
title = stringResource(R.string.uart_output),
modifier = Modifier,
menu = { Menu(scrollDown, onEvent) }
)
}
Spacer(modifier = Modifier.size(16.dp))
val listState = rememberLazyListState()
LazyColumn(
userScrollEnabled = !scrollDown.value,
modifier = Modifier
.fillMaxWidth(),
state = listState
) {
if (records.isEmpty()) {
item { Text(text = stringResource(id = R.string.uart_output_placeholder)) }
} else {
records.forEach {
item {
MessageItem(record = it)
Spacer(modifier = Modifier.height(16.dp))
}
}
}
}
LaunchedEffect(records, scrollDown.value) {
if (!scrollDown.value || records.isEmpty()) {
return@LaunchedEffect
}
launch {
listState.scrollToItem(records.lastIndex)
}
}
}
}
@Composable
private fun MessageItem(record: UARTRecord) {
Column {
Text(
text = record.timeToString(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = createRecordText(record = record),
style = MaterialTheme.typography.bodyMedium,
color = createRecordColor(record = record)
)
}
}
@Composable
private fun Menu(scrollDown: MutableState<Boolean>, onEvent: (UARTViewEvent) -> Unit) {
val icon = when (scrollDown.value) {
true -> R.drawable.ic_sync_down_off
false -> R.drawable.ic_sync_down
}
Row {
IconButton(onClick = { scrollDown.value = !scrollDown.value }) {
Icon(
painter = painterResource(id = icon),
contentDescription = stringResource(id = R.string.uart_scroll_down)
)
}
IconButton(onClick = { onEvent(ClearOutputItems) }) {
Icon(
Icons.Default.Delete,
contentDescription = stringResource(id = R.string.uart_clear_items),
)
}
}
}
@Composable
private fun createRecordText(record: UARTRecord): String {
return when (record.type) {
UARTRecordType.INPUT -> stringResource(id = R.string.uart_input_log, record.text)
UARTRecordType.OUTPUT -> stringResource(id = R.string.uart_output_log, record.text)
}
}
@Composable
private fun createRecordColor(record: UARTRecord): Color {
return when (record.type) {
UARTRecordType.INPUT -> colorResource(id = R.color.nordicGrass)
UARTRecordType.OUTPUT -> MaterialTheme.colorScheme.onBackground
}
}
private val datFormatter = SimpleDateFormat("dd MMMM yyyy, HH:mm:ss", Locale.ENGLISH)
private fun UARTRecord.timeToString(): String {
return datFormatter.format(timestamp)
}

View File

@@ -1,37 +1,18 @@
package no.nordicsemi.android.uart.view
import android.util.Log
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.material3.Button
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.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import no.nordicsemi.android.material.you.*
import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.android.material.you.ScreenSection
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.MacroEol
import no.nordicsemi.android.uart.data.UARTData
import no.nordicsemi.android.uart.data.UARTRecord
import no.nordicsemi.android.uart.data.UARTRecordType
import no.nordicsemi.android.utils.EMPTY
import java.text.SimpleDateFormat
import java.util.*
@Composable
internal fun UARTContentView(
@@ -52,11 +33,11 @@ internal fun UARTContentView(
Spacer(modifier = Modifier.size(16.dp))
// MacroSection(viewState, onEvent)
//
// Spacer(modifier = Modifier.size(16.dp))
InputSection(onEvent = onEvent)
if (viewState.isInputVisible) {
InputSection(onEvent = onEvent)
} else {
MacroSection(viewState, onEvent)
}
Spacer(modifier = Modifier.size(16.dp))
@@ -67,266 +48,3 @@ internal fun UARTContentView(
}
}
}
@Composable
private fun InputSection(onEvent: (UARTViewEvent) -> Unit) {
val text = rememberSaveable { mutableStateOf(String.EMPTY) }
val hint = stringResource(id = R.string.uart_input_hint)
val checkedItem = rememberSaveable { mutableStateOf(MacroEol.values()[0]) }
val items = MacroEol.values().map {
RadioButtonItem(it.toDisplayString(), it == checkedItem.value)
}
val viewEntity = RadioGroupViewEntity(items)
ScreenSection {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_input))
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(id = R.string.uart_macro_dialog_eol),
style = MaterialTheme.typography.labelLarge
)
RadioButtonGroup(viewEntity) {
val i = items.indexOf(it)
checkedItem.value = MacroEol.values()[i]
}
}
Spacer(modifier = Modifier.size(16.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Box(modifier = Modifier.weight(1f)) {
TextField(text = text.value, hint = hint) {
text.value = it
}
}
Spacer(modifier = Modifier.size(16.dp))
Button(
onClick = { onEvent(OnRunInput(text.value, checkedItem.value)) },
modifier = Modifier.padding(top = 6.dp)
) {
Text(text = stringResource(id = R.string.uart_send))
}
}
}
}
}
@Composable
private fun MacroSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
val showAddDialog = rememberSaveable { mutableStateOf(false) }
val showDeleteDialog = rememberSaveable { mutableStateOf(false) }
if (showAddDialog.value) {
UARTAddConfigurationDialog(onEvent) { showAddDialog.value = false }
}
if (showDeleteDialog.value) {
DeleteConfigurationDialog(onEvent) { showDeleteDialog.value = false }
}
ScreenSection {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_macros))
Spacer(modifier = Modifier.height(16.dp))
Row {
Box(modifier = Modifier.weight(1f)) {
UARTConfigurationPicker(viewState, onEvent)
}
IconButton(onClick = { showAddDialog.value = true }) {
Icon(Icons.Default.Add, stringResource(id = R.string.uart_configuration_add))
}
viewState.selectedConfiguration?.let {
if (!viewState.isConfigurationEdited) {
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
Icon(
Icons.Default.Edit,
stringResource(id = R.string.uart_configuration_edit)
)
}
} else {
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
Icon(
painterResource(id = R.drawable.ic_pencil_off),
stringResource(id = R.string.uart_configuration_edit)
)
}
}
IconButton(onClick = { showDeleteDialog.value = true }) {
Icon(
Icons.Default.Delete,
stringResource(id = R.string.uart_configuration_delete)
)
}
}
}
viewState.selectedConfiguration?.let {
Spacer(modifier = Modifier.height(16.dp))
UARTMacroView(it, viewState.isConfigurationEdited, onEvent)
}
}
}
}
@Composable
private fun DeleteConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = stringResource(id = R.string.uart_delete_dialog_title),
style = MaterialTheme.typography.headlineSmall
)
},
text = {
Text(text = stringResource(id = R.string.uart_delete_dialog_info))
},
confirmButton = {
Button(onClick = {
onDismiss()
onEvent(OnDeleteConfiguration)
}) {
Text(text = stringResource(id = R.string.uart_delete_dialog_confirm))
}
},
dismissButton = {
Button(onClick = onDismiss) {
Text(text = stringResource(id = R.string.uart_delete_dialog_cancel))
}
}
)
}
@Composable
private fun OutputSection(records: List<UARTRecord>, onEvent: (UARTViewEvent) -> Unit) {
val scrollDown = remember { mutableStateOf(true) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
SectionTitle(
resId = R.drawable.ic_output,
title = stringResource(R.string.uart_output),
modifier = Modifier,
menu = { Menu(scrollDown, onEvent) }
)
}
Spacer(modifier = Modifier.size(16.dp))
val listState = rememberLazyListState()
LazyColumn(
userScrollEnabled = !scrollDown.value,
modifier = Modifier
.fillMaxWidth(),
state = listState
) {
if (records.isEmpty()) {
item { Text(text = stringResource(id = R.string.uart_output_placeholder)) }
} else {
records.forEach {
item {
MessageItem(record = it)
Spacer(modifier = Modifier.height(16.dp))
}
}
}
}
LaunchedEffect(records) {
if (!scrollDown.value || records.isEmpty()) {
return@LaunchedEffect
}
launch {
listState.scrollToItem(records.lastIndex)
}
}
}
}
@Composable
private fun Menu(scrollDown: MutableState<Boolean>, onEvent: (UARTViewEvent) -> Unit) {
val icon = when (scrollDown.value) {
true -> R.drawable.ic_sync_down_off
false -> R.drawable.ic_sync_down
}
Row {
IconButton(onClick = { scrollDown.value = !scrollDown.value }) {
Icon(
painter = painterResource(id = icon),
contentDescription = stringResource(id = R.string.uart_scroll_down)
)
}
IconButton(onClick = { onEvent(ClearOutputItems) }) {
Icon(
Icons.Default.Delete,
contentDescription = stringResource(id = R.string.uart_clear_items),
)
}
}
}
@Composable
private fun MessageItem(record: UARTRecord) {
Column {
Text(
text = record.timeToString(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = createRecordText(record = record),
style = MaterialTheme.typography.bodyMedium,
color = createRecordColor(record = record)
)
}
}
@Composable
private fun createRecordText(record: UARTRecord): String {
return when (record.type) {
UARTRecordType.INPUT -> stringResource(id = R.string.uart_input_log, record.text)
UARTRecordType.OUTPUT -> stringResource(id = R.string.uart_output_log, record.text)
}
}
@Composable
private fun createRecordColor(record: UARTRecord): Color {
return when (record.type) {
UARTRecordType.INPUT -> colorResource(id = R.color.nordicGrass)
UARTRecordType.OUTPUT -> MaterialTheme.colorScheme.onBackground
}
}
private val datFormatter = SimpleDateFormat("dd MMMM yyyy, HH:mm:ss", Locale.ENGLISH)
private fun UARTRecord.timeToString(): String {
return datFormatter.format(timestamp)
}

View File

@@ -10,7 +10,8 @@ internal data class UARTViewState(
val selectedConfigurationName: String? = null,
val isConfigurationEdited: Boolean = false,
val configurations: List<UARTConfiguration> = emptyList(),
val uartManagerState: HTSManagerState = NoDeviceState
val uartManagerState: HTSManagerState = NoDeviceState,
val isInputVisible: Boolean = true
) {
val showEditDialog: Boolean = editedPosition != null

View File

@@ -23,3 +23,5 @@ internal object DisconnectEvent : UARTViewEvent()
internal object NavigateUp : UARTViewEvent()
internal object OpenLogger : UARTViewEvent()
internal object MacroInputSwitchClick : UARTViewEvent()

View File

@@ -83,9 +83,14 @@ internal class UARTViewModel @Inject constructor(
ClearOutputItems -> repository.clearItems()
OpenLogger -> repository.openLogger()
is OnRunInput -> repository.sendText(event.text, event.newLineChar)
MacroInputSwitchClick -> onMacroInputSwitch()
}.exhaustive
}
private fun onMacroInputSwitch() {
_state.value = _state.value.copy(isInputVisible = !state.value.isInputVisible)
}
private fun onEditConfiguration() {
val isEdited = _state.value.isConfigurationEdited
_state.value = _state.value.copy(isConfigurationEdited = !isEdited)

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z" />
</vector>

View File

@@ -53,6 +53,7 @@
<string name="uart_delete_dialog_confirm">Confirm</string>
<string name="uart_delete_dialog_cancel">Cancel</string>
<string name="uart_input_macro">Click to switch between text input and macro input.</string>
<string name="uart_clear_items">Clear items.</string>
<string name="uart_scroll_down">Click to constantly scroll view to the latest available log.</string>
<string name="uart_input_log">--&gt; %s</string>