kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Channel scan select (#1141)
rodzic
81297c46e9
commit
ed17ae0734
|
@ -16,12 +16,14 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.ButtonDefaults
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material.Checkbox
|
import androidx.compose.material.Checkbox
|
||||||
import androidx.compose.material.Chip
|
import androidx.compose.material.Chip
|
||||||
|
@ -41,6 +43,9 @@ import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.twotone.Check
|
import androidx.compose.material.icons.twotone.Check
|
||||||
import androidx.compose.material.icons.twotone.Close
|
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.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
@ -72,6 +77,8 @@ import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
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.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
@ -180,9 +187,12 @@ fun ChannelScreen(
|
||||||
val channelUrl = channelSet.getChannelUrl()
|
val channelUrl = channelSet.getChannelUrl()
|
||||||
val modemPresetName = Channel(loraConfig = channelSet.loraConfig).name
|
val modemPresetName = Channel(loraConfig = channelSet.loraConfig).name
|
||||||
|
|
||||||
|
val userScannedQrCode = remember { mutableStateOf(false) }
|
||||||
|
val scannedQR = remember { mutableStateOf("") }
|
||||||
val barcodeLauncher = rememberLauncherForActivityResult(ScanContract()) { result ->
|
val barcodeLauncher = rememberLauncherForActivityResult(ScanContract()) { result ->
|
||||||
if (result.contents != null) {
|
if (result.contents != null) {
|
||||||
viewModel.setRequestChannelUrl(Uri.parse(result.contents))
|
scannedQR.value = result.contents
|
||||||
|
userScannedQrCode.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,6 +303,15 @@ fun ChannelScreen(
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userScannedQrCode.value)
|
||||||
|
/* Prompt the user to modify channels after scanning a QR code. */
|
||||||
|
ScannedQrCodeDialog(
|
||||||
|
channels = channels,
|
||||||
|
incoming = Uri.parse(scannedQR.value).toChannelSet(),
|
||||||
|
onDismiss = { userScannedQrCode.value = false },
|
||||||
|
onConfirm = { newChannelSet -> installSettings(newChannelSet) }
|
||||||
|
)
|
||||||
|
|
||||||
var showEditChannelDialog: Int? by remember { mutableStateOf(null) }
|
var showEditChannelDialog: Int? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
if (showEditChannelDialog != null) {
|
if (showEditChannelDialog != null) {
|
||||||
|
@ -547,6 +566,158 @@ private fun ChannelSelection(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChannelScreenPreview() {
|
private fun ChannelScreenPreview() {
|
||||||
|
|
|
@ -206,4 +206,7 @@
|
||||||
<string name="mute_8_hours">8 hours</string>
|
<string name="mute_8_hours">8 hours</string>
|
||||||
<string name="mute_1_week">1 week</string>
|
<string name="mute_1_week">1 week</string>
|
||||||
<string name="mute_always">Always</string>
|
<string name="mute_always">Always</string>
|
||||||
|
<string name="scanned_channels">Scanned Channels</string>
|
||||||
|
<string name="current_channels">Current Channels</string>
|
||||||
|
<string name="replace">Replace</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Ładowanie…
Reference in New Issue