block creation or sending of duplicate channels. (#3913)

This commit is contained in:
Dane Evans 2025-12-06 23:47:33 +11:00 committed by GitHub
parent 499ed58311
commit 7db7f61386
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 108 additions and 9 deletions

View file

@ -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)
}
},
)
}