diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt index fd0a2337..eb55a42a 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt @@ -56,6 +56,8 @@ import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePods.LidState import eu.darken.capod.pods.core.firstSeenFormatted import eu.darken.capod.pods.core.formatBatteryPercent +import eu.darken.capod.pods.core.toBatteryFloat +import eu.darken.capod.pods.core.toBatteryOrNull import eu.darken.capod.pods.core.getSignalQuality import eu.darken.capod.pods.core.lastSeenFormatted import java.time.Duration @@ -164,7 +166,7 @@ fun DualPodsCard( ) { PodGauge( iconRes = device.leftPodIcon, - batteryPercent = device.batteryLeftPodPercent, + batteryPercent = device.batteryLeftPodPercent.toBatteryFloat(), isCharging = (device as? HasChargeDetectionDual)?.isLeftPodCharging ?: false, isInEar = (device as? HasEarDetectionDual)?.isLeftPodInEar ?: false, showEarDetection = device is HasEarDetectionDual, @@ -175,7 +177,7 @@ fun DualPodsCard( PodGauge( iconRes = device.rightPodIcon, - batteryPercent = device.batteryRightPodPercent, + batteryPercent = device.batteryRightPodPercent.toBatteryFloat(), isCharging = (device as? HasChargeDetectionDual)?.isRightPodCharging ?: false, isInEar = (device as? HasEarDetectionDual)?.isRightPodInEar ?: false, showEarDetection = device is HasEarDetectionDual, @@ -218,7 +220,7 @@ fun DualPodsCard( @Composable private fun PodGauge( iconRes: Int, - batteryPercent: Float?, + batteryPercent: Float, isCharging: Boolean, isInEar: Boolean, showEarDetection: Boolean, @@ -227,15 +229,15 @@ private fun PodGauge( modifier: Modifier = Modifier, ) { val context = LocalContext.current - val clamped = batteryPercent?.coerceIn(0f, 1f) + val clamped = if (batteryPercent >= 0f) batteryPercent.coerceIn(0f, 1f) else -1f val animatedProgress by animateFloatAsState( - targetValue = clamped ?: 0f, + targetValue = if (clamped >= 0f) clamped else 0f, animationSpec = tween(600, easing = FastOutSlowInEasing), label = "gaugeProgress", ) val ringColor = when { - clamped == null -> MaterialTheme.colorScheme.surfaceVariant + clamped < 0f -> MaterialTheme.colorScheme.surfaceVariant clamped > 0.30f -> MaterialTheme.colorScheme.primary clamped >= 0.15f -> MaterialTheme.colorScheme.tertiary else -> MaterialTheme.colorScheme.error @@ -261,7 +263,7 @@ private fun PodGauge( ) // Progress ring - if (clamped != null) { + if (clamped >= 0f) { CircularProgressIndicator( progress = { animatedProgress }, modifier = Modifier.size(80.dp), @@ -284,9 +286,9 @@ private fun PodGauge( // Battery percentage Text( - text = formatBatteryPercent(context, batteryPercent), + text = formatBatteryPercent(context, batteryPercent.toBatteryOrNull()), style = MaterialTheme.typography.titleMedium, - color = if (batteryPercent != null) { + color = if (batteryPercent >= 0f) { MaterialTheme.colorScheme.onSurface } else { MaterialTheme.colorScheme.onSurfaceVariant @@ -335,7 +337,7 @@ private fun CaseRow( ) BatteryCapsule( - percent = device.batteryCasePercent, + percent = device.batteryCasePercent.toBatteryFloat(), modifier = Modifier .weight(1f) .height(8.dp), diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/PodCardComponents.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/PodCardComponents.kt index fd4fe9f1..de176ff8 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/PodCardComponents.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/PodCardComponents.kt @@ -40,18 +40,18 @@ private val CapsuleShape = RoundedCornerShape(6.dp) @Composable fun BatteryCapsule( - percent: Float?, + percent: Float, modifier: Modifier = Modifier, ) { - val clamped = percent?.coerceIn(0f, 1f) + val clamped = if (percent >= 0f) percent.coerceIn(0f, 1f) else -1f val animatedFraction by animateFloatAsState( - targetValue = clamped ?: 0f, + targetValue = if (clamped >= 0f) clamped else 0f, animationSpec = tween(600, easing = FastOutSlowInEasing), label = "batteryFill", ) val barColor = when { - clamped == null -> MaterialTheme.colorScheme.surfaceVariant + clamped < 0f -> MaterialTheme.colorScheme.surfaceVariant clamped > 0.30f -> MaterialTheme.colorScheme.primary clamped >= 0.15f -> MaterialTheme.colorScheme.tertiary else -> MaterialTheme.colorScheme.error @@ -62,7 +62,7 @@ fun BatteryCapsule( .clip(CapsuleShape) .background(MaterialTheme.colorScheme.surfaceVariant), ) { - if (clamped != null) { + if (clamped >= 0f) { Box( modifier = Modifier .fillMaxHeight() diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/ComposeWidgetPreview.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/ComposeWidgetPreview.kt index 4dd27090..46fa5c80 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/ComposeWidgetPreview.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/ComposeWidgetPreview.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.unit.sp import eu.darken.capod.R import eu.darken.capod.common.compose.Preview2 import eu.darken.capod.common.compose.PreviewWrapper +import eu.darken.capod.pods.core.toBatteryOrNull import kotlin.math.roundToInt @Composable @@ -191,7 +192,7 @@ private fun SinglePodPreview( colorFilter = iconTint, ) Text( - text = formatPercent(state.percent), + text = formatPercent(state.percent.toBatteryOrNull()), fontSize = 12.sp, color = textColor, modifier = Modifier.padding(horizontal = 8.dp), @@ -293,7 +294,7 @@ private fun WidgetContainer( @Composable private fun PodItemRow( icon: Int, - percent: Float?, + percent: Float, charging: Boolean, inEar: Boolean, textColor: Color, @@ -313,7 +314,7 @@ private fun PodItemRow( colorFilter = iconTint, ) Text( - text = formatPercent(percent), + text = formatPercent(percent.toBatteryOrNull()), fontSize = 12.sp, color = textColor, modifier = Modifier.padding(horizontal = 4.dp), diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/GlanceWidgetContent.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/GlanceWidgetContent.kt index f3f7c60e..9952ef96 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/GlanceWidgetContent.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/GlanceWidgetContent.kt @@ -26,6 +26,7 @@ import androidx.glance.text.TextAlign import androidx.glance.text.TextStyle import eu.darken.capod.R import eu.darken.capod.main.ui.MainActivity +import eu.darken.capod.pods.core.toBatteryOrNull import kotlin.math.roundToInt @Composable @@ -105,7 +106,7 @@ private fun GlanceSinglePod( colorFilter = iconTint, ) Text( - text = formatGlancePercent(state.percent), + text = formatGlancePercent(state.percent.toBatteryOrNull()), style = textStyle, modifier = GlanceModifier.padding(horizontal = 8.dp), ) @@ -203,7 +204,7 @@ private fun GlanceWidgetRoot( @Composable private fun GlancePodItem( icon: Int, - percent: Float?, + percent: Float, charging: Boolean, inEar: Boolean, textStyle: TextStyle, @@ -222,7 +223,7 @@ private fun GlancePodItem( colorFilter = iconTint, ) Text( - text = formatGlancePercent(percent), + text = formatGlancePercent(percent.toBatteryOrNull()), style = textStyle, modifier = GlanceModifier.padding(horizontal = 4.dp), ) diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderState.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderState.kt index 2f954dc8..65d5d637 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderState.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderState.kt @@ -18,15 +18,15 @@ sealed class WidgetRenderState { val isWide: Boolean, val deviceLabel: String?, @DrawableRes val leftIcon: Int, - val leftPercent: Float?, + val leftPercent: Float, val leftCharging: Boolean, val leftInEar: Boolean, @DrawableRes val rightIcon: Int, - val rightPercent: Float?, + val rightPercent: Float, val rightCharging: Boolean, val rightInEar: Boolean, @DrawableRes val caseIcon: Int, - val casePercent: Float?, + val casePercent: Float, val caseCharging: Boolean, ) : WidgetRenderState() @@ -37,7 +37,7 @@ sealed class WidgetRenderState { @ColorInt override val resolvedIconColor: Int, val deviceLabel: String?, @DrawableRes val headsetIcon: Int, - val percent: Float?, + val percent: Float, @DrawableRes val batteryIcon: Int, val charging: Boolean, val worn: Boolean, diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt index 8d587078..b58cf041 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt @@ -12,6 +12,7 @@ import eu.darken.capod.pods.core.HasEarDetectionDual import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.SinglePodDevice import eu.darken.capod.pods.core.getBatteryDrawable +import eu.darken.capod.pods.core.toBatteryFloat object WidgetRenderStateMapper { @@ -46,15 +47,15 @@ object WidgetRenderStateMapper { isWide = isWide, deviceLabel = profileLabel ?: device.getLabel(context), leftIcon = device.leftPodIcon, - leftPercent = device.batteryLeftPodPercent, + leftPercent = device.batteryLeftPodPercent.toBatteryFloat(), leftCharging = device is HasChargeDetectionDual && device.isLeftPodCharging, leftInEar = device is HasEarDetectionDual && device.isLeftPodInEar, rightIcon = device.rightPodIcon, - rightPercent = device.batteryRightPodPercent, + rightPercent = device.batteryRightPodPercent.toBatteryFloat(), rightCharging = device is HasChargeDetectionDual && device.isRightPodCharging, rightInEar = device is HasEarDetectionDual && device.isRightPodInEar, caseIcon = (device as? HasCase)?.caseIcon ?: R.drawable.device_airpods_gen1_case, - casePercent = (device as? HasCase)?.batteryCasePercent, + casePercent = (device as? HasCase)?.batteryCasePercent.toBatteryFloat(), caseCharging = device is HasCase && device.isCaseCharging, ) @@ -65,7 +66,7 @@ object WidgetRenderStateMapper { resolvedIconColor = iconColor, deviceLabel = profileLabel ?: device.getLabel(context), headsetIcon = device.iconRes, - percent = device.batteryHeadsetPercent, + percent = device.batteryHeadsetPercent.toBatteryFloat(), batteryIcon = getBatteryDrawable(device.batteryHeadsetPercent), charging = device is HasChargeDetectionDual && device.isHeadsetBeingCharged, worn = device is HasEarDetection && device.isBeingWorn, diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt b/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt index c7336c63..a99c4095 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt @@ -8,6 +8,12 @@ import java.time.Duration import java.time.Instant import kotlin.math.roundToInt +const val BATTERY_UNKNOWN = -1f + +fun Float?.toBatteryFloat(): Float = this ?: BATTERY_UNKNOWN + +fun Float.toBatteryOrNull(): Float? = takeIf { it >= 0f } + fun formatBatteryPercent(context: Context, percent: Float?): String = percent?.let { "${(it * 100).roundToInt()}%" } ?: context.getString(R.string.general_value_not_available_label) diff --git a/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt b/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt index 825a9f1b..e16c0fce 100644 --- a/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt +++ b/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt @@ -40,6 +40,8 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.SinglePodDevice import eu.darken.capod.pods.core.formatBatteryPercent import eu.darken.capod.pods.core.getBatteryDrawable +import eu.darken.capod.pods.core.toBatteryFloat +import eu.darken.capod.pods.core.toBatteryOrNull import eu.darken.capod.pods.core.getSignalQuality @Composable @@ -127,7 +129,7 @@ private fun DualPodContent(device: DualPodDevice) { // Left pod BatteryColumn( iconRes = device.leftPodIcon, - batteryPercent = device.batteryLeftPodPercent, + batteryPercent = device.batteryLeftPodPercent.toBatteryFloat(), modifier = Modifier.weight(1f), ) @@ -135,7 +137,7 @@ private fun DualPodContent(device: DualPodDevice) { if (hasCase != null) { BatteryColumn( iconRes = hasCase.caseIcon, - batteryPercent = hasCase.batteryCasePercent, + batteryPercent = hasCase.batteryCasePercent.toBatteryFloat(), modifier = Modifier.weight(1f), ) } @@ -143,7 +145,7 @@ private fun DualPodContent(device: DualPodDevice) { // Right pod BatteryColumn( iconRes = device.rightPodIcon, - batteryPercent = device.batteryRightPodPercent, + batteryPercent = device.batteryRightPodPercent.toBatteryFloat(), modifier = Modifier.weight(1f), ) } @@ -153,17 +155,18 @@ private fun DualPodContent(device: DualPodDevice) { private fun SinglePodContent(device: SinglePodDevice) { BatteryColumn( iconRes = device.iconRes, - batteryPercent = device.batteryHeadsetPercent, + batteryPercent = device.batteryHeadsetPercent.toBatteryFloat(), ) } @Composable private fun BatteryColumn( iconRes: Int, - batteryPercent: Float?, + batteryPercent: Float, modifier: Modifier = Modifier, ) { val context = LocalContext.current + val nullablePercent = batteryPercent.toBatteryOrNull() Column( modifier = modifier, @@ -183,13 +186,13 @@ private fun BatteryColumn( horizontalArrangement = Arrangement.Center, ) { Icon( - painter = painterResource(getBatteryDrawable(batteryPercent)), + painter = painterResource(getBatteryDrawable(nullablePercent)), contentDescription = null, modifier = Modifier.size(16.dp), ) Spacer(modifier = Modifier.width(2.dp)) Text( - text = formatBatteryPercent(context, batteryPercent), + text = formatBatteryPercent(context, nullablePercent), style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis,