refactor: extract ScannedQrCodeDialog from ChannelFragment

This commit is contained in:
andrekir 2024-07-30 08:32:20 -03:00
parent ce5643a3ae
commit b4221c7db0
2 changed files with 211 additions and 167 deletions

View file

@ -11,19 +11,14 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
@ -39,8 +34,6 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.Check
import androidx.compose.material.icons.twotone.Close
import androidx.compose.material.Button
import androidx.compose.material.Surface
import androidx.compose.material.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -54,7 +47,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
@ -73,12 +65,10 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.geeksville.mesh.AppOnlyProtos
import com.geeksville.mesh.AppOnlyProtos.ChannelSet
import com.geeksville.mesh.analytics.DataPair
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
@ -102,6 +92,7 @@ import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.ui.components.AdaptiveTwoPane
import com.geeksville.mesh.ui.components.DropDownPreference
import com.geeksville.mesh.ui.components.PreferenceFooter
import com.geeksville.mesh.ui.components.ScannedQrCodeDialog
import com.geeksville.mesh.ui.components.config.ChannelCard
import com.geeksville.mesh.ui.components.config.ChannelSelection
import com.geeksville.mesh.ui.components.config.EditChannelDialog
@ -179,7 +170,7 @@ fun ChannelScreen(
val channelUrl = channelSet.getChannelUrl()
val modemPresetName = Channel(loraConfig = channelSet.loraConfig).name
var scannedChannelSet by remember { mutableStateOf<AppOnlyProtos.ChannelSet?>(null) }
var scannedChannelSet by remember { mutableStateOf<ChannelSet?>(null) }
val barcodeLauncher = rememberLauncherForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
try {
@ -234,7 +225,7 @@ fun ChannelScreen(
/// Send new channel settings to the device
fun installSettings(
newChannelSet: AppOnlyProtos.ChannelSet
newChannelSet: ChannelSet
) {
// Try to change the radio, if it fails, tell the user why and throw away their edits
try {
@ -491,7 +482,7 @@ fun ChannelScreen(
@Composable
private fun QrCodeImage(
enabled: Boolean,
channelSet: AppOnlyProtos.ChannelSet,
channelSet: ChannelSet,
modifier: Modifier = Modifier,
) = Image(
painter = channelSet.qrCode
@ -507,7 +498,7 @@ private fun QrCodeImage(
@Composable
private fun ChannelListView(
enabled: Boolean,
channelSet: AppOnlyProtos.ChannelSet,
channelSet: ChannelSet,
modemPresetName: String,
channelSelections: SnapshotStateList<Boolean>,
onClick: () -> Unit = {},
@ -551,158 +542,6 @@ private fun ChannelListView(
)
}
/**
* Enables the user to select which channels to accept after scanning a QR code.
*/
@Suppress("LongMethod")
@Composable
fun ScannedQrCodeDialog(
channels: AppOnlyProtos.ChannelSet,
incoming: AppOnlyProtos.ChannelSet,
onDismiss: () -> Unit,
onConfirm: (AppOnlyProtos.ChannelSet) -> Unit
) {
var currentChannelSet by remember(channels) { mutableStateOf(channels) }
val modemPresetName = Channel(loraConfig = currentChannelSet.loraConfig).name
/* Holds selections made by the user */
val channelSelections = remember { mutableStateListOf(elements = Array(size = 8, 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++
}
Dialog(
onDismissRequest = { onDismiss() },
properties = DialogProperties(usePlatformDefaultWidth = false, dismissOnBackPress = true)
) {
Surface(
modifier = Modifier.fillMaxSize(),
shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colors.background
) {
LazyColumn(
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)
)
}
itemsIndexed(incoming.settingsList) { index, channel ->
ChannelSelection(
index = index,
title = channel.name.ifEmpty { modemPresetName },
enabled = true,
isSelected = channelSelections[index],
onSelected = { 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)
}
}
)
}
/* User Actions via buttons */
item {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
/* Cancel */
Button(
onClick = onDismiss,
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.weight(1f)
.padding(3.dp)
) {
Text(
style = MaterialTheme.typography.body1,
text = stringResource(id = R.string.cancel)
)
}
/* Add - Appends incoming selected channels to the current set */
Button(
enabled = totalCount <= 8,
onClick = {
val appended = incoming.copy {
val result = settings.filterIndexed { i, _ ->
channelSelections.getOrNull(i) == true
}
settings.clear()
settings.addAll(currentChannelSet.settingsList)
settings.addAll(result)
}
onDismiss.invoke()
onConfirm.invoke(appended)
},
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.weight(1f)
.padding(3.dp)
) {
Text(
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)
)
}
}
}
}
}
}
}
@PreviewScreenSizes
@Composable
private fun ChannelScreenPreview() {

View file

@ -0,0 +1,205 @@
package com.geeksville.mesh.ui.components
import androidx.compose.foundation.layout.Arrangement
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.tooling.preview.PreviewScreenSizes
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.geeksville.mesh.AppOnlyProtos.ChannelSet
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.
*/
@Suppress("LongMethod")
@Composable
fun ScannedQrCodeDialog(
channels: ChannelSet,
incoming: ChannelSet,
onDismiss: () -> Unit,
onConfirm: (ChannelSet) -> Unit
) {
var currentChannelSet by remember(channels) { mutableStateOf(channels) }
val modemPresetName = Channel(loraConfig = currentChannelSet.loraConfig).name
/* Holds selections made by the user */
val channelSelections = remember { mutableStateListOf(elements = Array(size = 8, 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++
}
Dialog(
onDismissRequest = { onDismiss() },
properties = DialogProperties(usePlatformDefaultWidth = false, dismissOnBackPress = true)
) {
Surface(
modifier = Modifier.fillMaxSize(),
shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colors.background
) {
LazyColumn(
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)
)
}
itemsIndexed(incoming.settingsList) { index, channel ->
ChannelSelection(
index = index,
title = channel.name.ifEmpty { modemPresetName },
enabled = true,
isSelected = channelSelections[index],
onSelected = { 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)
}
}
)
}
/* User Actions via buttons */
item {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
/* Cancel */
Button(
onClick = onDismiss,
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.weight(1f)
.padding(3.dp)
) {
Text(
style = MaterialTheme.typography.body1,
text = stringResource(id = R.string.cancel)
)
}
/* Add - Appends incoming selected channels to the current set */
Button(
enabled = totalCount <= 8,
onClick = {
val appended = incoming.copy {
val result = settings.filterIndexed { i, _ ->
channelSelections.getOrNull(i) == true
}
settings.clear()
settings.addAll(currentChannelSet.settingsList)
settings.addAll(result)
}
onDismiss.invoke()
onConfirm.invoke(appended)
},
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.weight(1f)
.padding(3.dp)
) {
Text(
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)
)
}
}
}
}
}
}
}
@PreviewScreenSizes
@Composable
private fun ScannedQrCodeDialogPreview() {
ScannedQrCodeDialog(
channels = channelSet {
settings.add(Channel.default.settings)
loraConfig = Channel.default.loraConfig
},
incoming = channelSet {
settings.add(Channel.default.settings)
loraConfig = Channel.default.loraConfig
},
onDismiss = {},
onConfirm = {},
)
}