mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-30 16:35:51 -04:00
fix(qr): Preserve incoming channels when adding from QR (#6013)
This commit is contained in:
@@ -63,6 +63,7 @@ import org.meshtastic.core.resources.replace
|
||||
import org.meshtastic.core.resources.replace_channels_and_settings_description
|
||||
import org.meshtastic.core.ui.component.ChannelSelection
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.util.mergeChannelSettingsForAdd
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
|
||||
@Composable
|
||||
@@ -107,9 +108,7 @@ fun ScannedQrCodeDialog(
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// To guarantee consistent ordering, using a LinkedHashSet which iterates through
|
||||
// its entries according to the order an item was *first* inserted.
|
||||
val result = (channels.settings + incoming.settings).distinct()
|
||||
val result = mergeChannelSettingsForAdd(channels.settings, incoming.settings)
|
||||
channels.copy(settings = result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,3 +154,20 @@ suspend fun applyReplacementChannelSet(
|
||||
}
|
||||
radioConfigRepository.replaceAllSettings(channelSet.settings)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the ADD-mode preview list for QR import. Existing channels are preserved at their positions; every incoming
|
||||
* channel is appended in order without deduplication.
|
||||
*
|
||||
* Structural `.distinct()` was previously used here, but it silently dropped incoming channels that matched existing
|
||||
* entries, shifting later channels to wrong indices and hiding them from the user. The caller (UI) lets the user select
|
||||
* which incoming channels to keep.
|
||||
*
|
||||
* @param existing The current [ChannelSettings] list on the radio. Preserved in order.
|
||||
* @param incoming The imported [ChannelSettings] list. Appended in order.
|
||||
* @return The concatenated list `[existing..., incoming...]`.
|
||||
*/
|
||||
fun mergeChannelSettingsForAdd(
|
||||
existing: List<ChannelSettings>,
|
||||
incoming: List<ChannelSettings>,
|
||||
): List<ChannelSettings> = existing + incoming
|
||||
|
||||
@@ -141,4 +141,75 @@ class ProtoExtensionsTest {
|
||||
assertEquals(2, result[2].index)
|
||||
assertEquals(secondaryB, result[2].settings)
|
||||
}
|
||||
|
||||
// --- mergeChannelSettingsForAdd tests ---
|
||||
|
||||
@Test
|
||||
fun merge_preserves_all_existing_channels_in_order() {
|
||||
val existing = listOf(ChannelSettings(name = "A"), ChannelSettings(name = "B"))
|
||||
|
||||
val result = mergeChannelSettingsForAdd(existing, incoming = emptyList())
|
||||
|
||||
assertEquals(2, result.size)
|
||||
assertEquals("A", result[0].name)
|
||||
assertEquals("B", result[1].name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_appends_all_incoming_channels_in_order() {
|
||||
val incoming = listOf(ChannelSettings(name = "C"), ChannelSettings(name = "D"))
|
||||
|
||||
val result = mergeChannelSettingsForAdd(existing = emptyList(), incoming)
|
||||
|
||||
assertEquals(2, result.size)
|
||||
assertEquals("C", result[0].name)
|
||||
assertEquals("D", result[1].name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_preserves_structurally_equal_channels() {
|
||||
val channel = ChannelSettings(name = "LongFast", psk = byteArrayOf(1).toByteString())
|
||||
|
||||
val result = mergeChannelSettingsForAdd(existing = listOf(channel), incoming = listOf(channel))
|
||||
|
||||
assertEquals(2, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_preserves_same_name_different_psk() {
|
||||
val existingChan = ChannelSettings(name = "A", psk = byteArrayOf(1).toByteString())
|
||||
val incomingChan = ChannelSettings(name = "A", psk = byteArrayOf(2).toByteString())
|
||||
|
||||
val result = mergeChannelSettingsForAdd(listOf(existingChan), listOf(incomingChan))
|
||||
|
||||
assertEquals(2, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_preserves_same_psk_different_name() {
|
||||
val psk = byteArrayOf(1, 2).toByteString()
|
||||
val existingChan = ChannelSettings(name = "A", psk = psk)
|
||||
val incomingChan = ChannelSettings(name = "B", psk = psk)
|
||||
|
||||
val result = mergeChannelSettingsForAdd(listOf(existingChan), listOf(incomingChan))
|
||||
|
||||
assertEquals(2, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_preserves_duplicate_inside_incoming() {
|
||||
val a = ChannelSettings(name = "A", psk = byteArrayOf(1).toByteString())
|
||||
val b = ChannelSettings(name = "B", psk = byteArrayOf(2).toByteString())
|
||||
|
||||
val result = mergeChannelSettingsForAdd(existing = emptyList(), incoming = listOf(a, a, b))
|
||||
|
||||
assertEquals(3, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_both_empty_produces_empty_list() {
|
||||
val result = mergeChannelSettingsForAdd(existing = emptyList(), incoming = emptyList())
|
||||
|
||||
assertTrue(result.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user