diff --git a/app/src/androidTest/java/com/geeksville/mesh/compose/ScannedQrCodeDialogTest.kt b/app/src/androidTest/java/com/geeksville/mesh/compose/ScannedQrCodeDialogTest.kt index b9f40878e..4219d6561 100644 --- a/app/src/androidTest/java/com/geeksville/mesh/compose/ScannedQrCodeDialogTest.kt +++ b/app/src/androidTest/java/com/geeksville/mesh/compose/ScannedQrCodeDialogTest.kt @@ -68,16 +68,37 @@ class ScannedQrCodeDialogTest { } @Test - fun testScannedQrCodeDialog_showsCancelAddAndReplaceButtons() { + fun testScannedQrCodeDialog_showsDialogTitle() { composeTestRule.apply { testScannedQrCodeDialog() - onNodeWithText(getString(R.string.cancel)).assertIsDisplayed() + // Verify that the dialog title is displayed + onNodeWithText(getString(R.string.new_channel_rcvd)).assertIsDisplayed() + } + } + + @Test + fun testScannedQrCodeDialog_showsAddAndReplaceButtons() { + composeTestRule.apply { + testScannedQrCodeDialog() + + // Verify that the "Add" and "Replace" buttons are displayed onNodeWithText(getString(R.string.add)).assertIsDisplayed() onNodeWithText(getString(R.string.replace)).assertIsDisplayed() } } + @Test + fun testScannedQrCodeDialog_showsCancelAndAcceptButtons() { + composeTestRule.apply { + testScannedQrCodeDialog() + + // Verify the "Cancel" and "Accept" buttons are displayed + onNodeWithText(getString(R.string.cancel)).assertIsDisplayed() + onNodeWithText(getString(R.string.accept)).assertIsDisplayed() + } + } + @Test fun testScannedQrCodeDialog_clickCancelButton() { var onDismissClicked = false @@ -98,8 +119,8 @@ class ScannedQrCodeDialogTest { composeTestRule.apply { testScannedQrCodeDialog(onConfirm = { actualChannelSet = it }) - // Click the "Replace" button - onNodeWithText(getString(R.string.replace)).performClick() + // Click the "Accept" button + onNodeWithText(getString(R.string.accept)).performClick() } // Verify onConfirm is called with the correct ChannelSet @@ -112,13 +133,16 @@ class ScannedQrCodeDialogTest { composeTestRule.apply { testScannedQrCodeDialog(onConfirm = { actualChannelSet = it }) - // Click the "Add" button + // Click the "Add" button then the "Accept" button onNodeWithText(getString(R.string.add)).performClick() + onNodeWithText(getString(R.string.accept)).performClick() } // Verify onConfirm is called with the correct ChannelSet val expectedChannelSet = channels.copy { - settings.addAll(incoming.settingsList) + val list = LinkedHashSet(settings + incoming.settingsList) + settings.clear() + settings.addAll(list) } Assert.assertEquals(expectedChannelSet, actualChannelSet) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index 0387e0fae..1810b6ef6 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -525,9 +525,12 @@ private fun ChannelListView( ) } OutlinedButton( - modifier = Modifier.fillMaxWidth(), onClick = onClick, + modifier = Modifier.fillMaxWidth(), enabled = enabled, + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colors.onSurface, + ), ) { Text(text = stringResource(R.string.edit)) } }, second = { diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/ScannedQrCodeDialog.kt b/app/src/main/java/com/geeksville/mesh/ui/components/ScannedQrCodeDialog.kt index c475759c3..7fcc173c9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/ScannedQrCodeDialog.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/ScannedQrCodeDialog.kt @@ -1,19 +1,23 @@ package com.geeksville.mesh.ui.components import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedButton import androidx.compose.material.Surface import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf @@ -23,6 +27,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -32,12 +37,12 @@ import com.geeksville.mesh.R import com.geeksville.mesh.channelSet import com.geeksville.mesh.copy import com.geeksville.mesh.model.Channel -import com.geeksville.mesh.ui.components.config.ChannelCard import com.geeksville.mesh.ui.components.config.ChannelSelection /** * Enables the user to select which channels to accept after scanning a QR code. */ +@OptIn(ExperimentalLayoutApi::class) @Suppress("LongMethod") @Composable fun ScannedQrCodeDialog( @@ -46,19 +51,31 @@ fun ScannedQrCodeDialog( onDismiss: () -> Unit, onConfirm: (ChannelSet) -> Unit ) { - var currentChannelSet by remember(channels) { mutableStateOf(channels) } - val modemPresetName = Channel(loraConfig = currentChannelSet.loraConfig).name + var shouldReplace by remember { mutableStateOf(true) } + + val channelSet = remember(shouldReplace) { + if (shouldReplace) { + incoming + } else { + channels.copy { + val result = LinkedHashSet(settings + incoming.settingsList) + settings.clear() + settings.addAll(result) + } + } + } + + val modemPresetName = Channel(loraConfig = channelSet.loraConfig).name /* Holds selections made by the user */ - val channelSelections = remember { mutableStateListOf(elements = Array(size = 8, init = { true })) } + val channelSelections = remember(channelSet) { + mutableStateListOf(elements = Array(size = channelSet.settingsCount, init = { true })) + } - /* The save button is enabled based on this count */ - var totalCount = currentChannelSet.settingsList.size - for ((index, isSelected) in channelSelections.withIndex()) { - if (index >= incoming.settingsList.size) - break - if (isSelected) - totalCount++ + val selectedChannelSet = channelSet.copy { + val result = settings.filterIndexed { i, _ -> channelSelections.getOrNull(i) == true } + settings.clear() + settings.addAll(result) } Dialog( @@ -66,7 +83,7 @@ fun ScannedQrCodeDialog( properties = DialogProperties(usePlatformDefaultWidth = false, dismissOnBackPress = true) ) { Surface( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.widthIn(max = 600.dp), shape = RoundedCornerShape(16.dp), color = MaterialTheme.colors.background ) { @@ -74,108 +91,89 @@ fun ScannedQrCodeDialog( contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - /* Incoming ChannelSet */ item { Text( - style = MaterialTheme.typography.body1, - text = stringResource(id = R.string.scanned_channels) + text = stringResource(id = R.string.new_channel_rcvd), + modifier = Modifier.padding(20.dp), + style = MaterialTheme.typography.h6, ) } - itemsIndexed(incoming.settingsList) { index, channel -> + itemsIndexed(channelSet.settingsList) { index, channel -> ChannelSelection( index = index, title = channel.name.ifEmpty { modemPresetName }, enabled = true, isSelected = channelSelections[index], - onSelected = { channelSelections[index] = it } + onSelected = { + if (it || selectedChannelSet.settingsCount > 1) { + channelSelections[index] = it + } + }, ) } - /* Current ChannelSet */ item { - Text( - style = MaterialTheme.typography.body1, - text = stringResource(id = R.string.current_channels) - ) - } - itemsIndexed(currentChannelSet.settingsList) { index, channel -> - ChannelCard( - index = index, - title = channel.name.ifEmpty { modemPresetName }, - enabled = true, - onEditClick = { /* Currently we don't enable editing from this dialog. */ }, - onDeleteClick = { - val list = currentChannelSet.settingsList.toMutableList() - list.removeAt(index) - currentChannelSet = currentChannelSet.copy { - settings.clear() - settings.addAll(list) - } - } - ) + Row( + modifier = Modifier.padding(vertical = 20.dp), + ) { + val selectedColors = ButtonDefaults.buttonColors() + val unselectedColors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colors.onSurface, + ) + + OutlinedButton( + onClick = { shouldReplace = false }, + modifier = Modifier + .height(48.dp) + .weight(1f), + colors = if (!shouldReplace) selectedColors else unselectedColors, + ) { Text(text = stringResource(R.string.add)) } + + OutlinedButton( + onClick = { shouldReplace = true }, + modifier = Modifier + .height(48.dp) + .weight(1f), + colors = if (shouldReplace) selectedColors else unselectedColors, + ) { Text(text = stringResource(R.string.replace)) } + } } /* User Actions via buttons */ item { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly + FlowRow( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) ) { - /* Cancel */ - Button( - onClick = onDismiss, - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - .weight(1f) - .padding(3.dp) + TextButton( + onClick = { + onDismiss() + }, ) { Text( + text = stringResource(id = R.string.cancel), + color = MaterialTheme.colors.onSurface, + overflow = TextOverflow.Ellipsis, + maxLines = 1, style = MaterialTheme.typography.body1, - text = stringResource(id = R.string.cancel) ) } - /* Add - Appends incoming selected channels to the current set */ - Button( - enabled = totalCount <= 8, + TextButton( onClick = { - val appended = currentChannelSet.copy { - val result = incoming.settingsList.filterIndexed { i, _ -> - channelSelections.getOrNull(i) == true - } - settings.addAll(result) - } - onDismiss.invoke() - onConfirm.invoke(appended) + onDismiss() + onConfirm(selectedChannelSet) }, - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - .weight(1f) - .padding(3.dp) + enabled = selectedChannelSet.settingsCount in 1..8, ) { Text( + text = stringResource(id = R.string.accept), + color = MaterialTheme.colors.onSurface, + overflow = TextOverflow.Ellipsis, + maxLines = 1, style = MaterialTheme.typography.body1, - text = stringResource(id = R.string.add) - ) - } - - /* Replace - Replaces the previous set with the scanned channel set */ - Button( - onClick = { - onDismiss.invoke() - onConfirm.invoke(incoming) - }, - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - .weight(1f) - .padding(3.dp) - ) { - Text( - style = MaterialTheme.typography.body1, - text = stringResource(id = R.string.replace) ) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt index cec29c0a6..1e999fe29 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -79,11 +80,18 @@ private fun ChannelItem( val textColor = if (enabled) Color.Unspecified else MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) - Chip(onClick = onClick) { Text("$index") } + Chip(onClick = onClick) { + Text( + text = "$index", + color = textColor, + ) + } Text( text = title, modifier = Modifier.weight(1f), color = textColor, + overflow = TextOverflow.Ellipsis, + maxLines = 1, style = MaterialTheme.typography.body1, ) content() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f889f67c6..83970e728 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -211,7 +211,5 @@ Do you want to add a new channel? Do you want to add %d new channels? - Scanned Channels - Current Channels Replace