mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: extract ScannedQrCodeDialog from ChannelFragment
This commit is contained in:
parent
ce5643a3ae
commit
b4221c7db0
2 changed files with 211 additions and 167 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue