mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 02:36:12 -04:00
fix: Add Compose stability annotations and fix lazy list keys
Add @Stable to PodDevice to enable Compose referential equality checks, reducing unnecessary recompositions from the 3-second update ticker. Make icon properties non-null with built-in defaults and fix lazy list keys to use stable string identifiers.
This commit is contained in:
@@ -225,7 +225,7 @@ fun OverviewScreen(
|
||||
if (!state.isScanBlocked && state.isBluetoothEnabled) {
|
||||
items(
|
||||
items = state.profiledDevices,
|
||||
key = { it.identifier.hashCode() },
|
||||
key = { it.identifier?.toString() ?: it.hashCode() },
|
||||
) { device ->
|
||||
PodDeviceCard(
|
||||
device = device,
|
||||
@@ -256,7 +256,7 @@ fun OverviewScreen(
|
||||
if (state.showUnmatchedDevices) {
|
||||
items(
|
||||
items = state.unmatchedDevices,
|
||||
key = { "unmatched_${it.identifier.hashCode()}" },
|
||||
key = { "unmatched_${it.identifier?.toString() ?: it.hashCode()}" },
|
||||
) { device ->
|
||||
PodDeviceCard(
|
||||
device = device,
|
||||
|
||||
@@ -157,7 +157,7 @@ fun DualPodsCard(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
PodGauge(
|
||||
iconRes = device.leftPodIcon ?: R.drawable.device_airpods_gen1_left,
|
||||
iconRes = device.leftPodIcon,
|
||||
batteryPercent = device.batteryLeft.toBatteryFloat(),
|
||||
isCharging = device.isLeftPodCharging ?: false,
|
||||
isInEar = device.isLeftInEar ?: false,
|
||||
@@ -168,7 +168,7 @@ fun DualPodsCard(
|
||||
)
|
||||
|
||||
PodGauge(
|
||||
iconRes = device.rightPodIcon ?: R.drawable.device_airpods_gen1_right,
|
||||
iconRes = device.rightPodIcon,
|
||||
batteryPercent = device.batteryRight.toBatteryFloat(),
|
||||
isCharging = device.isRightPodCharging ?: false,
|
||||
isInEar = device.isRightInEar ?: false,
|
||||
@@ -338,7 +338,7 @@ private fun CaseRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(device.caseIcon ?: R.drawable.device_airpods_gen1_case),
|
||||
painter = painterResource(device.caseIcon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(28.dp),
|
||||
)
|
||||
|
||||
@@ -41,15 +41,15 @@ object WidgetRenderStateMapper {
|
||||
resolvedIconColor = iconColor,
|
||||
isWide = isWide,
|
||||
deviceLabel = profileLabel ?: device.getLabel(context),
|
||||
leftIcon = device.leftPodIcon ?: R.drawable.device_airpods_gen1_left,
|
||||
leftIcon = device.leftPodIcon,
|
||||
leftPercent = device.batteryLeft.toBatteryFloat(),
|
||||
leftCharging = device.isLeftPodCharging == true,
|
||||
leftInEar = device.isLeftInEar == true,
|
||||
rightIcon = device.rightPodIcon ?: R.drawable.device_airpods_gen1_right,
|
||||
rightIcon = device.rightPodIcon,
|
||||
rightPercent = device.batteryRight.toBatteryFloat(),
|
||||
rightCharging = device.isRightPodCharging == true,
|
||||
rightInEar = device.isRightInEar == true,
|
||||
caseIcon = device.caseIcon ?: R.drawable.device_airpods_gen1_case,
|
||||
caseIcon = device.caseIcon,
|
||||
casePercent = device.batteryCase.toBatteryFloat(),
|
||||
caseCharging = device.isCaseCharging == true,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package eu.darken.capod.monitor.core
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Stable
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BluetoothAddress
|
||||
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
|
||||
import eu.darken.capod.pods.core.apple.ble.DualBlePodSnapshot
|
||||
@@ -24,6 +26,7 @@ import java.time.Instant
|
||||
* All properties are dynamically resolved from the best available source.
|
||||
* Consumers don't need to know whether data came from BLE or AAP.
|
||||
*/
|
||||
@Stable
|
||||
data class PodDevice(
|
||||
internal val ble: BlePodSnapshot?,
|
||||
internal val aap: AapPodState?,
|
||||
@@ -164,14 +167,14 @@ data class PodDevice(
|
||||
// Icons / labels
|
||||
val iconRes: Int get() = ble?.iconRes ?: model.iconRes
|
||||
|
||||
val leftPodIcon: Int?
|
||||
get() = (ble as? DualBlePodSnapshot)?.leftPodIcon
|
||||
val leftPodIcon: Int
|
||||
get() = (ble as? DualBlePodSnapshot)?.leftPodIcon ?: R.drawable.device_airpods_gen1_left
|
||||
|
||||
val rightPodIcon: Int?
|
||||
get() = (ble as? DualBlePodSnapshot)?.rightPodIcon
|
||||
val rightPodIcon: Int
|
||||
get() = (ble as? DualBlePodSnapshot)?.rightPodIcon ?: R.drawable.device_airpods_gen1_right
|
||||
|
||||
val caseIcon: Int?
|
||||
get() = (ble as? HasCase)?.caseIcon
|
||||
val caseIcon: Int
|
||||
get() = (ble as? HasCase)?.caseIcon ?: R.drawable.device_airpods_gen1_case
|
||||
|
||||
fun getLabel(context: Context): String = ble?.getLabel(context) ?: model.label
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class MonitorNotificationViewFactory @Inject constructor(
|
||||
).apply {
|
||||
// Left
|
||||
val leftPercent = device.batteryLeft
|
||||
setImageViewResource(R.id.pod_left_icon, device.leftPodIcon ?: R.drawable.device_airpods_gen1_left)
|
||||
setImageViewResource(R.id.pod_left_icon, device.leftPodIcon)
|
||||
setTextViewText(R.id.pod_left_label, formatBatteryPercent(context, leftPercent))
|
||||
val isLeftPodCharging = device.isLeftPodCharging ?: false
|
||||
setViewVisibility(R.id.pod_left_charging, if (isLeftPodCharging) View.VISIBLE else View.GONE)
|
||||
@@ -39,7 +39,7 @@ class MonitorNotificationViewFactory @Inject constructor(
|
||||
// Case
|
||||
setViewVisibility(R.id.pod_case_charging, if (device.hasCase) View.VISIBLE else View.GONE)
|
||||
if (device.hasCase) {
|
||||
setImageViewResource(R.id.pod_case_icon, device.caseIcon ?: R.drawable.device_airpods_gen1_case)
|
||||
setImageViewResource(R.id.pod_case_icon, device.caseIcon)
|
||||
val casePercent = device.batteryCase
|
||||
setTextViewText(R.id.pod_case_label, formatBatteryPercent(context, casePercent))
|
||||
setViewVisibility(R.id.pod_case_charging, if (device.isCaseCharging == true) View.VISIBLE else View.GONE)
|
||||
@@ -47,7 +47,7 @@ class MonitorNotificationViewFactory @Inject constructor(
|
||||
|
||||
// Right
|
||||
val rightPercent = device.batteryRight
|
||||
setImageViewResource(R.id.pod_right_icon, device.rightPodIcon ?: R.drawable.device_airpods_gen1_right)
|
||||
setImageViewResource(R.id.pod_right_icon, device.rightPodIcon)
|
||||
setTextViewText(R.id.pod_right_label, formatBatteryPercent(context, rightPercent))
|
||||
val isRightPodCharging = device.isRightPodCharging ?: false
|
||||
setViewVisibility(R.id.pod_right_charging, if (isRightPodCharging) View.VISIBLE else View.GONE)
|
||||
@@ -94,7 +94,7 @@ class MonitorNotificationViewFactory @Inject constructor(
|
||||
).apply {
|
||||
// Left
|
||||
val leftPercent = device.batteryLeft
|
||||
setImageViewResource(R.id.pod_left_icon, device.leftPodIcon ?: R.drawable.device_airpods_gen1_left)
|
||||
setImageViewResource(R.id.pod_left_icon, device.leftPodIcon)
|
||||
setProgressBar(R.id.pod_left_progress, 100, percentToInt(leftPercent), false)
|
||||
setTextViewText(R.id.pod_left_label, formatBatteryPercent(context, leftPercent))
|
||||
val isLeftPodCharging = device.isLeftPodCharging ?: false
|
||||
@@ -105,7 +105,7 @@ class MonitorNotificationViewFactory @Inject constructor(
|
||||
// Case
|
||||
setViewVisibility(R.id.pod_case_container, if (device.hasCase) View.VISIBLE else View.GONE)
|
||||
if (device.hasCase) {
|
||||
setImageViewResource(R.id.pod_case_icon, device.caseIcon ?: R.drawable.device_airpods_gen1_case)
|
||||
setImageViewResource(R.id.pod_case_icon, device.caseIcon)
|
||||
val casePercent = device.batteryCase
|
||||
setProgressBar(R.id.pod_case_progress, 100, percentToInt(casePercent), false)
|
||||
setTextViewText(R.id.pod_case_label, formatBatteryPercent(context, casePercent))
|
||||
@@ -114,7 +114,7 @@ class MonitorNotificationViewFactory @Inject constructor(
|
||||
|
||||
// Right
|
||||
val rightPercent = device.batteryRight
|
||||
setImageViewResource(R.id.pod_right_icon, device.rightPodIcon ?: R.drawable.device_airpods_gen1_right)
|
||||
setImageViewResource(R.id.pod_right_icon, device.rightPodIcon)
|
||||
setProgressBar(R.id.pod_right_progress, 100, percentToInt(rightPercent), false)
|
||||
setTextViewText(R.id.pod_right_label, formatBatteryPercent(context, rightPercent))
|
||||
val isRightPodCharging = device.isRightPodCharging ?: false
|
||||
|
||||
@@ -124,7 +124,7 @@ private fun DualPodContent(device: PodDevice) {
|
||||
) {
|
||||
// Left pod
|
||||
BatteryColumn(
|
||||
iconRes = device.leftPodIcon ?: R.drawable.device_airpods_gen1_left,
|
||||
iconRes = device.leftPodIcon,
|
||||
batteryPercent = device.batteryLeft.toBatteryFloat(),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
@@ -132,7 +132,7 @@ private fun DualPodContent(device: PodDevice) {
|
||||
// Case (only if device has one)
|
||||
if (device.hasCase) {
|
||||
BatteryColumn(
|
||||
iconRes = device.caseIcon ?: R.drawable.device_airpods_gen1_case,
|
||||
iconRes = device.caseIcon,
|
||||
batteryPercent = device.batteryCase.toBatteryFloat(),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
@@ -140,7 +140,7 @@ private fun DualPodContent(device: PodDevice) {
|
||||
|
||||
// Right pod
|
||||
BatteryColumn(
|
||||
iconRes = device.rightPodIcon ?: R.drawable.device_airpods_gen1_right,
|
||||
iconRes = device.rightPodIcon,
|
||||
batteryPercent = device.batteryRight.toBatteryFloat(),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user