mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
fix(overview): Prevent crash from duplicate LazyList keys
This commit is contained in:
@@ -67,6 +67,22 @@ import eu.darken.capod.pods.core.apple.PodModel
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import java.time.Instant
|
||||
|
||||
internal fun profiledDeviceKey(
|
||||
device: PodDevice,
|
||||
index: Int,
|
||||
duplicateProfileIds: Set<String>,
|
||||
): String {
|
||||
val pid = requireNotNull(device.profileId)
|
||||
return if (pid !in duplicateProfileIds) {
|
||||
"profiled:$pid"
|
||||
} else {
|
||||
"profiled:$pid:${device.identifier ?: "idx:$index"}"
|
||||
}
|
||||
}
|
||||
|
||||
internal fun unmatchedDeviceKey(device: PodDevice, index: Int): String =
|
||||
"unmatched:${device.identifier ?: "idx:$index"}"
|
||||
|
||||
@Composable
|
||||
fun OverviewScreenHost(vm: OverviewViewModel = hiltViewModel()) {
|
||||
ErrorEventHandler(vm)
|
||||
@@ -286,9 +302,15 @@ fun OverviewScreen(
|
||||
|
||||
// 4. Profiled device cards (limited to 1 for free users)
|
||||
if (!state.isScanBlocked && state.isBluetoothEnabled) {
|
||||
val duplicateProfileIds = state.visibleProfiledDevices
|
||||
.mapNotNull { it.profileId }
|
||||
.groupingBy { it }
|
||||
.eachCount()
|
||||
.filterValues { it > 1 }
|
||||
.keys
|
||||
itemsIndexed(
|
||||
items = state.visibleProfiledDevices,
|
||||
key = { _, device -> requireNotNull(device.profileId) },
|
||||
key = { index, device -> profiledDeviceKey(device, index, duplicateProfileIds) },
|
||||
) { index, device ->
|
||||
val isCollapsed = !state.isExpanded(device, index)
|
||||
val isToggleable = state.isToggleable(device, index)
|
||||
@@ -337,10 +359,10 @@ fun OverviewScreen(
|
||||
}
|
||||
|
||||
if (state.showUnmatchedDevices) {
|
||||
items(
|
||||
itemsIndexed(
|
||||
items = state.unmatchedDevices,
|
||||
key = { "unmatched_${it.identifier?.toString() ?: it.hashCode()}" },
|
||||
) { device ->
|
||||
key = { index, device -> unmatchedDeviceKey(device, index) },
|
||||
) { _, device ->
|
||||
PodDeviceCard(
|
||||
device = device,
|
||||
isPro = state.upgradeInfo.isPro,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package eu.darken.capod.main.ui.overview
|
||||
|
||||
import eu.darken.capod.monitor.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import testhelpers.BaseTest
|
||||
import java.util.UUID
|
||||
|
||||
class OverviewKeysTest : BaseTest() {
|
||||
|
||||
private fun device(profileId: String?, identifier: BlePodSnapshot.Id? = null): PodDevice {
|
||||
val bleSnapshot: BlePodSnapshot? = identifier?.let { id ->
|
||||
mockk(relaxed = true) {
|
||||
every { this@mockk.identifier } returns id
|
||||
}
|
||||
}
|
||||
return PodDevice(profileId = profileId, ble = bleSnapshot, aap = null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `profiledDeviceKey returns plain key for non-duplicate profile`() {
|
||||
val d = device(profileId = "p1", identifier = BlePodSnapshot.Id(UUID.randomUUID()))
|
||||
profiledDeviceKey(d, index = 0, duplicateProfileIds = emptySet()) shouldBe "profiled:p1"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `profiledDeviceKey disambiguates duplicates with identifier`() {
|
||||
val id1 = BlePodSnapshot.Id(UUID.fromString("00000000-0000-0000-0000-000000000001"))
|
||||
val id2 = BlePodSnapshot.Id(UUID.fromString("00000000-0000-0000-0000-000000000002"))
|
||||
val d1 = device(profileId = "p1", identifier = id1)
|
||||
val d2 = device(profileId = "p1", identifier = id2)
|
||||
|
||||
val k1 = profiledDeviceKey(d1, index = 0, duplicateProfileIds = setOf("p1"))
|
||||
val k2 = profiledDeviceKey(d2, index = 1, duplicateProfileIds = setOf("p1"))
|
||||
|
||||
k1 shouldBe "profiled:p1:$id1"
|
||||
k2 shouldBe "profiled:p1:$id2"
|
||||
(k1 == k2) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `profiledDeviceKey falls back to index when no BLE snapshot is present`() {
|
||||
val d1 = device(profileId = "p1")
|
||||
val d2 = device(profileId = "p1")
|
||||
val k1 = profiledDeviceKey(d1, index = 0, duplicateProfileIds = setOf("p1"))
|
||||
val k2 = profiledDeviceKey(d2, index = 1, duplicateProfileIds = setOf("p1"))
|
||||
|
||||
k1 shouldBe "profiled:p1:idx:0"
|
||||
k2 shouldBe "profiled:p1:idx:1"
|
||||
(k1 == k2) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unmatchedDeviceKey uses identifier when present`() {
|
||||
val id = BlePodSnapshot.Id(UUID.fromString("00000000-0000-0000-0000-0000000000aa"))
|
||||
val d = device(profileId = null, identifier = id)
|
||||
unmatchedDeviceKey(d, index = 5) shouldBe "unmatched:$id"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unmatchedDeviceKey falls back to index when no BLE snapshot is present`() {
|
||||
val d1 = device(profileId = null)
|
||||
val d2 = device(profileId = null)
|
||||
val k1 = unmatchedDeviceKey(d1, index = 0)
|
||||
val k2 = unmatchedDeviceKey(d2, index = 1)
|
||||
|
||||
k1 shouldBe "unmatched:idx:0"
|
||||
k2 shouldBe "unmatched:idx:1"
|
||||
(k1 == k2) shouldBe false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user