mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
block creation or sending of duplicate channels. (#3913)
This commit is contained in:
parent
499ed58311
commit
7db7f61386
5 changed files with 108 additions and 9 deletions
|
|
@ -96,6 +96,7 @@ import kotlinx.coroutines.launch
|
|||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.util.getChannelUrl
|
||||
import org.meshtastic.core.model.util.hasDuplicateKeys
|
||||
import org.meshtastic.core.model.util.qrCode
|
||||
import org.meshtastic.core.model.util.toChannelSet
|
||||
import org.meshtastic.core.navigation.Route
|
||||
|
|
@ -107,6 +108,7 @@ import org.meshtastic.core.strings.are_you_sure_change_default
|
|||
import org.meshtastic.core.strings.cancel
|
||||
import org.meshtastic.core.strings.cant_change_no_radio
|
||||
import org.meshtastic.core.strings.channel_invalid
|
||||
import org.meshtastic.core.strings.channel_key_already_in_use
|
||||
import org.meshtastic.core.strings.copy
|
||||
import org.meshtastic.core.strings.edit
|
||||
import org.meshtastic.core.strings.modem_preset
|
||||
|
|
@ -231,6 +233,12 @@ fun ChannelScreen(
|
|||
|
||||
// Send new channel settings to the device
|
||||
fun installSettings(newChannelSet: ChannelSet) {
|
||||
// Check for duplicate keys before installing
|
||||
if (newChannelSet.hasDuplicateKeys()) {
|
||||
scope.launch { context.showToast(Res.string.channel_key_already_in_use) }
|
||||
return
|
||||
}
|
||||
|
||||
// Try to change the radio, if it fails, tell the user why and throw away their edits
|
||||
try {
|
||||
viewModel.setChannels(newChannelSet)
|
||||
|
|
|
|||
|
|
@ -89,3 +89,21 @@ fun ChannelSet.qrCode(shouldAdd: Boolean): Bitmap? = try {
|
|||
Timber.e("URL was too complex to render as barcode")
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the ChannelSet contains any duplicate PSKs.
|
||||
*
|
||||
* @return true if there are duplicate PSKs, false otherwise
|
||||
*/
|
||||
fun ChannelSet.hasDuplicateKeys(): Boolean {
|
||||
val pskList = mutableListOf<ByteArray>()
|
||||
for (setting in settingsList) {
|
||||
val channel = Channel(setting, loraConfig)
|
||||
val pskBytes = channel.psk.toByteArray()
|
||||
if (pskList.any { it contentEquals pskBytes }) {
|
||||
return true
|
||||
}
|
||||
pskList.add(pskBytes)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,6 +218,7 @@
|
|||
<string name="meshtastic_service_notifications">Service notifications</string>
|
||||
<string name="about">About</string>
|
||||
<string name="channel_invalid">This Channel URL is invalid and can not be used</string>
|
||||
<string name="channel_key_already_in_use">A channel with this key is already in use</string>
|
||||
<string name="contact_invalid">This contact is invalid and can not be added</string>
|
||||
<string name="debug_panel">Debug Panel</string>
|
||||
<string name="debug_decoded_payload">Decoded Payload:</string>
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -50,15 +52,19 @@ import androidx.compose.ui.window.Dialog
|
|||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.util.hasDuplicateKeys
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.accept
|
||||
import org.meshtastic.core.strings.add
|
||||
import org.meshtastic.core.strings.cancel
|
||||
import org.meshtastic.core.strings.channel_key_already_in_use
|
||||
import org.meshtastic.core.strings.new_channel_rcvd
|
||||
import org.meshtastic.core.strings.replace
|
||||
import org.meshtastic.core.ui.component.ChannelSelection
|
||||
import org.meshtastic.core.ui.util.showToast
|
||||
import org.meshtastic.proto.AppOnlyProtos.ChannelSet
|
||||
import org.meshtastic.proto.ConfigProtos.Config.LoRaConfig.ModemPreset
|
||||
import org.meshtastic.proto.channelSet
|
||||
|
|
@ -290,10 +296,16 @@ fun ScannedQrCodeDialog(
|
|||
)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
onConfirm(selectedChannelSet)
|
||||
if (selectedChannelSet.hasDuplicateKeys()) {
|
||||
coroutineScope.launch { context.showToast(Res.string.channel_key_already_in_use) }
|
||||
} else {
|
||||
onDismiss()
|
||||
onConfirm(selectedChannelSet)
|
||||
}
|
||||
},
|
||||
enabled = selectedChannelSet.settingsCount in 1..8,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -37,27 +37,33 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.listSaver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.model.util.hasDuplicateKeys
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.add
|
||||
import org.meshtastic.core.strings.cancel
|
||||
import org.meshtastic.core.strings.channel_key_already_in_use
|
||||
import org.meshtastic.core.strings.channel_name
|
||||
import org.meshtastic.core.strings.channels
|
||||
import org.meshtastic.core.strings.press_and_drag
|
||||
|
|
@ -67,6 +73,7 @@ import org.meshtastic.core.ui.component.PreferenceFooter
|
|||
import org.meshtastic.core.ui.component.dragContainer
|
||||
import org.meshtastic.core.ui.component.dragDropItemsIndexed
|
||||
import org.meshtastic.core.ui.component.rememberDragDropState
|
||||
import org.meshtastic.core.ui.util.showToast
|
||||
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.feature.settings.radio.channel.component.ChannelCard
|
||||
import org.meshtastic.feature.settings.radio.channel.component.ChannelConfigHeader
|
||||
|
|
@ -75,6 +82,7 @@ import org.meshtastic.feature.settings.radio.channel.component.ChannelLegendDial
|
|||
import org.meshtastic.feature.settings.radio.channel.component.EditChannelDialog
|
||||
import org.meshtastic.feature.settings.radio.channel.component.SECONDARY_CHANNEL_EPOCH
|
||||
import org.meshtastic.feature.settings.radio.component.PacketResponseStateDialog
|
||||
import org.meshtastic.proto.AppOnlyProtos
|
||||
import org.meshtastic.proto.ChannelProtos.ChannelSettings
|
||||
import org.meshtastic.proto.ConfigProtos.Config.LoRaConfig
|
||||
import org.meshtastic.proto.channelSettings
|
||||
|
|
@ -135,21 +143,69 @@ private fun ChannelConfigScreen(
|
|||
settingsList.size != settingsListInput.size ||
|
||||
settingsList.zip(settingsListInput).any { (item1, item2) -> item1 != item2 }
|
||||
|
||||
// Check if the current channel list has duplicate PSKs - recompute when list changes
|
||||
var hasDuplicateKeys by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(settingsListInput.size, settingsListInput.toList(), loraConfig) {
|
||||
val channelSet =
|
||||
AppOnlyProtos.ChannelSet.newBuilder()
|
||||
.apply {
|
||||
addAllSettings(settingsListInput.toList())
|
||||
setLoraConfig(loraConfig)
|
||||
}
|
||||
.build()
|
||||
hasDuplicateKeys = channelSet.hasDuplicateKeys()
|
||||
}
|
||||
|
||||
var showEditChannelDialog: Int? by rememberSaveable { mutableStateOf(null) }
|
||||
var showChannelLegendDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
if (showEditChannelDialog != null) {
|
||||
val index = showEditChannelDialog ?: return
|
||||
EditChannelDialog(
|
||||
channelSettings = with(settingsListInput) { if (size > index) get(index) else channelSettings {} },
|
||||
modemPresetName = modemPresetName,
|
||||
onAddClick = {
|
||||
if (settingsListInput.size > index) {
|
||||
settingsListInput[index] = it
|
||||
onAddClick = { newChannelSettings ->
|
||||
val isEditing = index < settingsListInput.size
|
||||
|
||||
// Build a temporary list to check for duplicates
|
||||
val tempList = settingsListInput.toMutableList()
|
||||
if (isEditing) {
|
||||
tempList[index] = newChannelSettings
|
||||
} else {
|
||||
settingsListInput.add(it)
|
||||
tempList.add(newChannelSettings)
|
||||
}
|
||||
|
||||
// Check for duplicates in the temporary list
|
||||
val tempChannelSet =
|
||||
AppOnlyProtos.ChannelSet.newBuilder()
|
||||
.apply {
|
||||
addAllSettings(tempList)
|
||||
setLoraConfig(loraConfig)
|
||||
}
|
||||
.build()
|
||||
|
||||
val hasDuplicate = tempChannelSet.hasDuplicateKeys()
|
||||
|
||||
if (hasDuplicate) {
|
||||
coroutineScope.launch { context.showToast(Res.string.channel_key_already_in_use) }
|
||||
// If this was a new channel added by FAB (at the end), remove it
|
||||
// FAB adds channel then opens dialog, so index will be lastIndex
|
||||
if (index == settingsListInput.size - 1 && index >= 0) {
|
||||
settingsListInput.removeAt(index)
|
||||
showEditChannelDialog = null
|
||||
}
|
||||
// If editing existing channel, don't update - keep original and dialog open
|
||||
} else {
|
||||
if (isEditing) {
|
||||
settingsListInput[index] = newChannelSettings
|
||||
} else {
|
||||
settingsListInput.add(newChannelSettings)
|
||||
}
|
||||
showEditChannelDialog = null
|
||||
}
|
||||
showEditChannelDialog = null
|
||||
},
|
||||
onDismissRequest = { showEditChannelDialog = null },
|
||||
)
|
||||
|
|
@ -238,7 +294,7 @@ private fun ChannelConfigScreen(
|
|||
item { Spacer(modifier = Modifier.weight(1f)) }
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && isEditing,
|
||||
enabled = enabled && isEditing && !hasDuplicateKeys,
|
||||
negativeText = stringResource(Res.string.cancel),
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
|
|
@ -248,7 +304,11 @@ private fun ChannelConfigScreen(
|
|||
positiveText = stringResource(Res.string.send),
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onPositiveClicked(settingsListInput)
|
||||
if (hasDuplicateKeys) {
|
||||
coroutineScope.launch { context.showToast(Res.string.channel_key_already_in_use) }
|
||||
} else {
|
||||
onPositiveClicked(settingsListInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue