diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/AncGlanceWidget.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/AncGlanceWidget.kt index ef7664b2..a6a87fc0 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/AncGlanceWidget.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/AncGlanceWidget.kt @@ -158,7 +158,7 @@ class AncGlanceWidget : GlanceAppWidget() { private fun getCellsForSize(size: Int): Int { var n = 2 - while (70 * n - 30 < size) { + while (70 * n - 30 <= size) { ++n } return n - 1 diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt index 3fedd139..1b3f662e 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt @@ -57,12 +57,14 @@ class BatteryGlanceWidget : GlanceAppWidget() { } catch (e: Exception) { log(TAG, ERROR) { "provideGlance setup failed: ${e.asLog()}" } provideContent { + val layout = BatteryLayout.forCells(getCellsForSize(LocalSize.current.width.value.toInt())) GlanceWidgetContent( state = WidgetRenderState.Message( theme = WidgetTheme.DEFAULT, resolvedBgColor = WidgetRenderStateMapper.resolvedBgColor(context, WidgetTheme.DEFAULT), resolvedTextColor = WidgetRenderStateMapper.resolvedTextColor(context, WidgetTheme.DEFAULT), resolvedIconColor = WidgetRenderStateMapper.resolvedIconColor(context, WidgetTheme.DEFAULT), + layout = layout, primaryText = context.getString(eu.darken.capod.R.string.widget_error_loading_label), ), context = context, @@ -77,6 +79,7 @@ class BatteryGlanceWidget : GlanceAppWidget() { val profiles by ep.deviceProfilesRepo().profiles.collectAsState(initial = emptyList()) val upgradeInfo by ep.upgradeRepo().upgradeInfo.collectAsState(initial = null) val widthDp = LocalSize.current.width + val layout = BatteryLayout.forCells(getCellsForSize(widthDp.value.toInt())) val state = try { val profileId = ep.widgetSettings().getWidgetProfile(appWidgetId) @@ -93,8 +96,6 @@ class BatteryGlanceWidget : GlanceAppWidget() { profiles.firstOrNull { it.id == pid }?.label } - val isWide = getCellsForSize(widthDp.value.toInt()) >= 5 - WidgetRenderStateMapper.map( context = context, device = device, @@ -102,7 +103,7 @@ class BatteryGlanceWidget : GlanceAppWidget() { isPro = isPro, hasConfiguredProfile = profileId != null, profileLabel = profileLabel, - isWide = isWide, + layout = layout, ) } catch (e: Exception) { log(TAG, ERROR) { "provideGlance failed: ${e.asLog()}" } @@ -111,6 +112,7 @@ class BatteryGlanceWidget : GlanceAppWidget() { resolvedBgColor = WidgetRenderStateMapper.resolvedBgColor(context, WidgetTheme.DEFAULT), resolvedTextColor = WidgetRenderStateMapper.resolvedTextColor(context, WidgetTheme.DEFAULT), resolvedIconColor = WidgetRenderStateMapper.resolvedIconColor(context, WidgetTheme.DEFAULT), + layout = layout, primaryText = context.getString(eu.darken.capod.R.string.widget_error_loading_label), ) } @@ -137,7 +139,7 @@ class BatteryGlanceWidget : GlanceAppWidget() { */ private fun getCellsForSize(size: Int): Int { var n = 2 - while (70 * n - 30 < size) { + while (70 * n - 30 <= size) { ++n } return n - 1 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 b043893c..b471b4b4 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 @@ -25,6 +25,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import eu.darken.capod.R @@ -87,86 +88,51 @@ private fun DualPodPreview( val iconColor = Color(state.resolvedIconColor) val iconTint = ColorFilter.tint(iconColor) - if (state.isWide) { - // Wide layout: left | case | right in a horizontal row - WidgetContainer(bgColor = bgColor, modifier = modifier) { - Row( - modifier = Modifier.padding(top = 8.dp, bottom = 4.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - // Left pod - PodItemRow( - icon = state.leftIcon, - percent = state.leftPercent, - charging = state.leftCharging, - inEar = state.leftInEar, + when (state.layout) { + BatteryLayout.WIDE -> { + WidgetContainer(bgColor = bgColor, modifier = modifier) { + Row( + modifier = Modifier.padding(top = 8.dp, bottom = 4.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + PodItemRow(state.leftIcon, state.leftPercent, state.leftCharging, state.leftInEar, textColor, iconTint, iconSize = 40, modifier = Modifier.padding(end = 12.dp)) + PodItemRow(state.caseIcon, state.casePercent, state.caseCharging, false, textColor, iconTint, iconSize = 40, modifier = Modifier.padding(end = 12.dp)) + PodItemRow(state.rightIcon, state.rightPercent, state.rightCharging, state.rightInEar, textColor, iconTint, iconSize = 40) + } + DeviceLabel( + label = state.deviceLabel, + visible = state.theme.showDeviceLabel, textColor = textColor, - iconTint = iconTint, - iconSize = 40, - modifier = Modifier.padding(end = 12.dp), - ) - // Case - PodItemRow( - icon = state.caseIcon, - percent = state.casePercent, - charging = state.caseCharging, - inEar = false, - textColor = textColor, - iconTint = iconTint, - iconSize = 40, - modifier = Modifier.padding(end = 12.dp), - ) - // Right pod - PodItemRow( - icon = state.rightIcon, - percent = state.rightPercent, - charging = state.rightCharging, - inEar = state.rightInEar, - textColor = textColor, - iconTint = iconTint, - iconSize = 40, + modifier = Modifier.padding(top = 4.dp, bottom = 8.dp), ) } - DeviceLabel( - label = state.deviceLabel, - visible = state.theme.showDeviceLabel, - textColor = textColor, - modifier = Modifier.padding(top = 4.dp, bottom = 8.dp), - ) } - } else { - // Compact layout: vertical stack - WidgetContainer(bgColor = bgColor, modifier = modifier) { - PodItemRow( - icon = state.leftIcon, - percent = state.leftPercent, - charging = state.leftCharging, - inEar = state.leftInEar, - textColor = textColor, - iconTint = iconTint, - ) - PodItemRow( - icon = state.rightIcon, - percent = state.rightPercent, - charging = state.rightCharging, - inEar = state.rightInEar, - textColor = textColor, - iconTint = iconTint, - ) - PodItemRow( - icon = state.caseIcon, - percent = state.casePercent, - charging = state.caseCharging, - inEar = false, - textColor = textColor, - iconTint = iconTint, - ) - DeviceLabel( - label = state.deviceLabel, - visible = state.theme.showDeviceLabel, - textColor = textColor, - ) + + BatteryLayout.NARROW -> { + WidgetContainer(bgColor = bgColor, modifier = modifier) { + PodItemRow(state.leftIcon, state.leftPercent, state.leftCharging, state.leftInEar, textColor, iconTint) + PodItemRow(state.rightIcon, state.rightPercent, state.rightCharging, state.rightInEar, textColor, iconTint) + PodItemRow(state.caseIcon, state.casePercent, state.caseCharging, false, textColor, iconTint) + DeviceLabel( + label = state.deviceLabel, + visible = state.theme.showDeviceLabel, + textColor = textColor, + ) + } + } + + BatteryLayout.TINY_COLUMN -> { + WidgetContainer( + bgColor = bgColor, + modifier = modifier, + horizontalPadding = 4.dp, + verticalPadding = 0.dp, + ) { + TinyPodItem(state.leftIcon, state.leftPercent, textColor, iconTint) + TinyPodItem(state.rightIcon, state.rightPercent, textColor, iconTint) + TinyPodItem(state.caseIcon, state.casePercent, textColor, iconTint) + } } } } @@ -180,45 +146,60 @@ private fun SinglePodPreview( val textColor = Color(state.resolvedTextColor) val iconTint = ColorFilter.tint(Color(state.resolvedIconColor)) - WidgetContainer(bgColor = bgColor, modifier = modifier) { - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - Image( - painter = painterResource(state.batteryIcon), - contentDescription = null, - modifier = Modifier.size(20.dp), - colorFilter = iconTint, - ) - Text( - text = formatPercent(state.percent.toBatteryOrNull()), - fontSize = 12.sp, - color = textColor, - modifier = Modifier.padding(horizontal = 8.dp), - ) - if (state.charging) { - Image( - painter = painterResource(R.drawable.ic_baseline_power_24), - contentDescription = null, - modifier = Modifier.size(20.dp), - colorFilter = iconTint, - ) - } - if (state.worn) { - Image( - painter = painterResource(R.drawable.ic_baseline_hearing_24), - contentDescription = null, - modifier = Modifier.size(20.dp), - colorFilter = iconTint, + when (state.layout) { + BatteryLayout.WIDE, BatteryLayout.NARROW -> { + WidgetContainer(bgColor = bgColor, modifier = modifier) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(state.batteryIcon), + contentDescription = null, + modifier = Modifier.size(20.dp), + colorFilter = iconTint, + ) + Text( + text = formatPercent(state.percent.toBatteryOrNull()), + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(horizontal = 8.dp), + ) + if (state.charging) { + Image( + painter = painterResource(R.drawable.ic_baseline_power_24), + contentDescription = null, + modifier = Modifier.size(20.dp), + colorFilter = iconTint, + ) + } + if (state.worn) { + Image( + painter = painterResource(R.drawable.ic_baseline_hearing_24), + contentDescription = null, + modifier = Modifier.size(20.dp), + colorFilter = iconTint, + ) + } + } + DeviceLabel( + label = state.deviceLabel, + visible = state.theme.showDeviceLabel, + textColor = textColor, ) } } - DeviceLabel( - label = state.deviceLabel, - visible = state.theme.showDeviceLabel, - textColor = textColor, - ) + + BatteryLayout.TINY_COLUMN -> { + WidgetContainer( + bgColor = bgColor, + modifier = modifier, + horizontalPadding = 4.dp, + verticalPadding = 0.dp, + ) { + TinyPodItem(state.batteryIcon, state.percent, textColor, iconTint) + } + } } } @@ -229,17 +210,25 @@ private fun MessagePreview( ) { val bgColor = Color(state.resolvedBgColor) val textColor = Color(state.resolvedTextColor) + val isCompact = state.layout == BatteryLayout.TINY_COLUMN - WidgetContainer(bgColor = bgColor, modifier = modifier) { + WidgetContainer( + bgColor = bgColor, + modifier = modifier, + horizontalPadding = if (isCompact) 4.dp else 16.dp, + verticalPadding = if (isCompact) 0.dp else 4.dp, + ) { Text( text = state.primaryText, - fontSize = 12.sp, + fontSize = if (isCompact) 10.sp else 12.sp, fontWeight = FontWeight.Bold, color = textColor, textAlign = TextAlign.Center, + maxLines = if (isCompact) 2 else Int.MAX_VALUE, + overflow = TextOverflow.Ellipsis, modifier = Modifier.fillMaxWidth(), ) - if (state.secondaryText != null) { + if (!isCompact && state.secondaryText != null) { Text( text = state.secondaryText, fontSize = 12.sp, @@ -260,11 +249,17 @@ private fun LoadingPreview( ) { val bgColor = Color(state.resolvedBgColor) val textColor = Color(state.resolvedTextColor) + val isCompact = state.layout == BatteryLayout.TINY_COLUMN - WidgetContainer(bgColor = bgColor, modifier = modifier) { + WidgetContainer( + bgColor = bgColor, + modifier = modifier, + horizontalPadding = if (isCompact) 4.dp else 16.dp, + verticalPadding = if (isCompact) 0.dp else 4.dp, + ) { Text( text = "…", - fontSize = 12.sp, + fontSize = if (isCompact) 10.sp else 12.sp, fontWeight = FontWeight.Bold, color = textColor, textAlign = TextAlign.Center, @@ -277,13 +272,15 @@ private fun LoadingPreview( private fun WidgetContainer( bgColor: Color, modifier: Modifier = Modifier, + horizontalPadding: Dp = 16.dp, + verticalPadding: Dp = 4.dp, content: @Composable () -> Unit, ) { Column( modifier = modifier .clip(RoundedCornerShape(16.dp)) .background(bgColor) - .padding(horizontal = 16.dp, vertical = 4.dp), + .padding(horizontal = horizontalPadding, vertical = verticalPadding), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { @@ -338,6 +335,34 @@ private fun PodItemRow( } } +@Composable +private fun TinyPodItem( + icon: Int, + percent: Float, + textColor: Color, + iconTint: ColorFilter, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(icon), + contentDescription = null, + modifier = Modifier.size(14.dp), + colorFilter = iconTint, + ) + Text( + text = formatPercent(percent.toBatteryOrNull()), + fontSize = 12.sp, + color = textColor, + maxLines = 1, + modifier = Modifier.padding(start = 2.dp), + ) + } +} + @Composable private fun DeviceLabel( label: String?, @@ -363,12 +388,30 @@ private fun formatPercent(percent: Float?): String { @Preview2 @Composable -private fun PreviewDualCompact() = PreviewWrapper { - ComposeWidgetPreview(state = WidgetRenderState.previewDualPod()) +private fun PreviewDualTiny11() = PreviewWrapper { + ComposeWidgetPreview( + state = WidgetRenderState.previewDualPod(layout = BatteryLayout.TINY_COLUMN), + modifier = Modifier.size(40.dp), + ) +} + +@Preview2 +@Composable +private fun PreviewDualTinyTall() = PreviewWrapper { + ComposeWidgetPreview( + state = WidgetRenderState.previewDualPod(layout = BatteryLayout.TINY_COLUMN), + modifier = Modifier.size(width = 40.dp, height = 110.dp), + ) +} + +@Preview2 +@Composable +private fun PreviewDualNarrow() = PreviewWrapper { + ComposeWidgetPreview(state = WidgetRenderState.previewDualPod(layout = BatteryLayout.NARROW)) } @Preview2 @Composable private fun PreviewDualWide() = PreviewWrapper { - ComposeWidgetPreview(state = WidgetRenderState.previewDualPod(isWide = true)) + ComposeWidgetPreview(state = WidgetRenderState.previewDualPod(layout = BatteryLayout.WIDE)) } 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 b000c510..c731081e 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 @@ -3,6 +3,7 @@ package eu.darken.capod.main.ui.widget import android.content.Intent import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.glance.ColorFilter @@ -53,31 +54,47 @@ private fun GlanceDualPod( state: WidgetRenderState.DualPod, clickModifier: GlanceModifier, ) { - val textStyle = TextStyle( - color = fixedColor(state.resolvedTextColor), - fontSize = 12.sp, - ) val iconTint = ColorFilter.tint(fixedColor(state.resolvedIconColor)) - if (state.isWide) { - GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { - Row( - modifier = GlanceModifier.fillMaxWidth().padding(top = 8.dp, bottom = 4.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalAlignment = Alignment.CenterVertically, - ) { - GlancePodItem(state.leftIcon, state.leftPercent, state.leftCharging, state.leftInEar, textStyle, iconTint, iconSize = 40, modifier = GlanceModifier.padding(end = 12.dp)) - GlancePodItem(state.caseIcon, state.casePercent, state.caseCharging, false, textStyle, iconTint, iconSize = 40, modifier = GlanceModifier.padding(end = 12.dp)) - GlancePodItem(state.rightIcon, state.rightPercent, state.rightCharging, state.rightInEar, textStyle, iconTint, iconSize = 40) + when (state.layout) { + BatteryLayout.WIDE -> { + val textStyle = TextStyle(color = fixedColor(state.resolvedTextColor), fontSize = 12.sp) + GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { + Row( + modifier = GlanceModifier.fillMaxWidth().padding(top = 8.dp, bottom = 4.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalAlignment = Alignment.CenterVertically, + ) { + GlancePodItem(state.leftIcon, state.leftPercent, state.leftCharging, state.leftInEar, textStyle, iconTint, iconSize = 40, modifier = GlanceModifier.padding(end = 12.dp)) + GlancePodItem(state.caseIcon, state.casePercent, state.caseCharging, false, textStyle, iconTint, iconSize = 40, modifier = GlanceModifier.padding(end = 12.dp)) + GlancePodItem(state.rightIcon, state.rightPercent, state.rightCharging, state.rightInEar, textStyle, iconTint, iconSize = 40) + } + GlanceDeviceLabel(state.deviceLabel, state.theme.showDeviceLabel, state.resolvedTextColor) } - GlanceDeviceLabel(state.deviceLabel, state.theme.showDeviceLabel, state.resolvedTextColor) } - } else { - GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { - GlancePodItem(state.leftIcon, state.leftPercent, state.leftCharging, state.leftInEar, textStyle, iconTint) - GlancePodItem(state.rightIcon, state.rightPercent, state.rightCharging, state.rightInEar, textStyle, iconTint) - GlancePodItem(state.caseIcon, state.casePercent, state.caseCharging, false, textStyle, iconTint) - GlanceDeviceLabel(state.deviceLabel, state.theme.showDeviceLabel, state.resolvedTextColor) + + BatteryLayout.NARROW -> { + val textStyle = TextStyle(color = fixedColor(state.resolvedTextColor), fontSize = 12.sp) + GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { + GlancePodItem(state.leftIcon, state.leftPercent, state.leftCharging, state.leftInEar, textStyle, iconTint) + GlancePodItem(state.rightIcon, state.rightPercent, state.rightCharging, state.rightInEar, textStyle, iconTint) + GlancePodItem(state.caseIcon, state.casePercent, state.caseCharging, false, textStyle, iconTint) + GlanceDeviceLabel(state.deviceLabel, state.theme.showDeviceLabel, state.resolvedTextColor) + } + } + + BatteryLayout.TINY_COLUMN -> { + val textStyle = TextStyle(color = fixedColor(state.resolvedTextColor), fontSize = 12.sp) + GlanceWidgetRoot( + bgColor = state.resolvedBgColor, + clickModifier = clickModifier, + horizontalPadding = 4.dp, + verticalPadding = 0.dp, + ) { + GlanceTinyPodItem(state.leftIcon, state.leftPercent, textStyle, iconTint) + GlanceTinyPodItem(state.rightIcon, state.rightPercent, textStyle, iconTint) + GlanceTinyPodItem(state.caseIcon, state.casePercent, textStyle, iconTint) + } } } } @@ -87,47 +104,60 @@ private fun GlanceSinglePod( state: WidgetRenderState.SinglePod, clickModifier: GlanceModifier, ) { - val textStyle = TextStyle( - color = fixedColor(state.resolvedTextColor), - fontSize = 12.sp, - ) val iconTint = ColorFilter.tint(fixedColor(state.resolvedIconColor)) - GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { - Row( - modifier = GlanceModifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalAlignment = Alignment.CenterVertically, - ) { - Image( - provider = ImageProvider(state.batteryIcon), - contentDescription = null, - modifier = GlanceModifier.size(20.dp), - colorFilter = iconTint, - ) - Text( - text = formatGlancePercent(state.percent.toBatteryOrNull()), - style = textStyle, - modifier = GlanceModifier.padding(horizontal = 8.dp), - ) - if (state.charging) { - Image( - provider = ImageProvider(R.drawable.ic_baseline_power_24), - contentDescription = null, - modifier = GlanceModifier.size(20.dp), - colorFilter = iconTint, - ) + when (state.layout) { + BatteryLayout.WIDE, BatteryLayout.NARROW -> { + val textStyle = TextStyle(color = fixedColor(state.resolvedTextColor), fontSize = 12.sp) + GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { + Row( + modifier = GlanceModifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + provider = ImageProvider(state.batteryIcon), + contentDescription = null, + modifier = GlanceModifier.size(20.dp), + colorFilter = iconTint, + ) + Text( + text = formatGlancePercent(state.percent.toBatteryOrNull()), + style = textStyle, + modifier = GlanceModifier.padding(horizontal = 8.dp), + ) + if (state.charging) { + Image( + provider = ImageProvider(R.drawable.ic_baseline_power_24), + contentDescription = null, + modifier = GlanceModifier.size(20.dp), + colorFilter = iconTint, + ) + } + if (state.worn) { + Image( + provider = ImageProvider(R.drawable.ic_baseline_hearing_24), + contentDescription = null, + modifier = GlanceModifier.size(20.dp), + colorFilter = iconTint, + ) + } + } + GlanceDeviceLabel(state.deviceLabel, state.theme.showDeviceLabel, state.resolvedTextColor) } - if (state.worn) { - Image( - provider = ImageProvider(R.drawable.ic_baseline_hearing_24), - contentDescription = null, - modifier = GlanceModifier.size(20.dp), - colorFilter = iconTint, - ) + } + + BatteryLayout.TINY_COLUMN -> { + val textStyle = TextStyle(color = fixedColor(state.resolvedTextColor), fontSize = 12.sp) + GlanceWidgetRoot( + bgColor = state.resolvedBgColor, + clickModifier = clickModifier, + horizontalPadding = 4.dp, + verticalPadding = 0.dp, + ) { + GlanceTinyPodItem(state.batteryIcon, state.percent, textStyle, iconTint) } } - GlanceDeviceLabel(state.deviceLabel, state.theme.showDeviceLabel, state.resolvedTextColor) } } @@ -136,20 +166,29 @@ private fun GlanceMessage( state: WidgetRenderState.Message, clickModifier: GlanceModifier, ) { - val textStyle = TextStyle( - color = fixedColor(state.resolvedTextColor), - fontSize = 12.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - ) + val isCompact = state.layout == BatteryLayout.TINY_COLUMN + val fontSize = if (isCompact) 10.sp else 12.sp + val hPad = if (isCompact) 4.dp else 16.dp + val vPad = if (isCompact) 0.dp else 4.dp - GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { + GlanceWidgetRoot( + bgColor = state.resolvedBgColor, + clickModifier = clickModifier, + horizontalPadding = hPad, + verticalPadding = vPad, + ) { Text( text = state.primaryText, - style = textStyle, + style = TextStyle( + color = fixedColor(state.resolvedTextColor), + fontSize = fontSize, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + ), modifier = GlanceModifier.fillMaxWidth(), + maxLines = if (isCompact) 2 else Int.MAX_VALUE, ) - if (state.secondaryText != null) { + if (!isCompact && state.secondaryText != null) { Text( text = state.secondaryText, style = TextStyle( @@ -169,12 +208,18 @@ private fun GlanceLoading( state: WidgetRenderState.Loading, clickModifier: GlanceModifier, ) { - GlanceWidgetRoot(state.resolvedBgColor, clickModifier) { + val isCompact = state.layout == BatteryLayout.TINY_COLUMN + GlanceWidgetRoot( + bgColor = state.resolvedBgColor, + clickModifier = clickModifier, + horizontalPadding = if (isCompact) 4.dp else 16.dp, + verticalPadding = if (isCompact) 0.dp else 4.dp, + ) { Text( text = "…", style = TextStyle( color = fixedColor(state.resolvedTextColor), - fontSize = 12.sp, + fontSize = if (isCompact) 10.sp else 12.sp, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, ), @@ -187,13 +232,15 @@ private fun GlanceLoading( private fun GlanceWidgetRoot( bgColor: Int, clickModifier: GlanceModifier, + horizontalPadding: Dp = 16.dp, + verticalPadding: Dp = 4.dp, content: @Composable () -> Unit, ) { Column( modifier = clickModifier .fillMaxSize() .background(fixedColor(bgColor)) - .padding(horizontal = 16.dp, vertical = 4.dp), + .padding(horizontal = horizontalPadding, vertical = verticalPadding), horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically, ) { @@ -246,6 +293,33 @@ private fun GlancePodItem( } } +@Composable +private fun GlanceTinyPodItem( + icon: Int, + percent: Float, + textStyle: TextStyle, + iconTint: ColorFilter, + modifier: GlanceModifier = GlanceModifier, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + provider = ImageProvider(icon), + contentDescription = null, + modifier = GlanceModifier.size(14.dp), + colorFilter = iconTint, + ) + Text( + text = formatGlancePercent(percent.toBatteryOrNull()), + style = textStyle, + maxLines = 1, + modifier = GlanceModifier.padding(start = 4.dp), + ) + } +} + @Composable private fun GlanceDeviceLabel( label: String?, 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 65d5d637..c4f76ee3 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 @@ -4,18 +4,33 @@ import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import eu.darken.capod.R +enum class BatteryLayout { + TINY_COLUMN, + NARROW, + WIDE; + + companion object { + fun forCells(widthCells: Int): BatteryLayout = when { + widthCells <= 1 -> TINY_COLUMN + widthCells >= 5 -> WIDE + else -> NARROW + } + } +} + sealed class WidgetRenderState { abstract val theme: WidgetTheme @get:ColorInt abstract val resolvedBgColor: Int @get:ColorInt abstract val resolvedTextColor: Int @get:ColorInt abstract val resolvedIconColor: Int + abstract val layout: BatteryLayout data class DualPod( override val theme: WidgetTheme, @ColorInt override val resolvedBgColor: Int, @ColorInt override val resolvedTextColor: Int, @ColorInt override val resolvedIconColor: Int, - val isWide: Boolean, + override val layout: BatteryLayout, val deviceLabel: String?, @DrawableRes val leftIcon: Int, val leftPercent: Float, @@ -35,6 +50,7 @@ sealed class WidgetRenderState { @ColorInt override val resolvedBgColor: Int, @ColorInt override val resolvedTextColor: Int, @ColorInt override val resolvedIconColor: Int, + override val layout: BatteryLayout, val deviceLabel: String?, @DrawableRes val headsetIcon: Int, val percent: Float, @@ -48,6 +64,7 @@ sealed class WidgetRenderState { @ColorInt override val resolvedBgColor: Int, @ColorInt override val resolvedTextColor: Int, @ColorInt override val resolvedIconColor: Int, + override val layout: BatteryLayout, val primaryText: String, val secondaryText: String? = null, ) : WidgetRenderState() @@ -57,6 +74,7 @@ sealed class WidgetRenderState { @ColorInt override val resolvedBgColor: Int, @ColorInt override val resolvedTextColor: Int, @ColorInt override val resolvedIconColor: Int, + override val layout: BatteryLayout, ) : WidgetRenderState() companion object { @@ -65,13 +83,13 @@ sealed class WidgetRenderState { @ColorInt bgColor: Int = 0xFFFFFFFF.toInt(), @ColorInt textColor: Int = 0xFF1E1E1E.toInt(), @ColorInt iconColor: Int = 0xFF1E1E1E.toInt(), - isWide: Boolean = false, + layout: BatteryLayout = BatteryLayout.NARROW, ): DualPod = DualPod( theme = theme, resolvedBgColor = bgColor, resolvedTextColor = textColor, resolvedIconColor = iconColor, - isWide = isWide, + layout = layout, deviceLabel = "My AirPods Pro", leftIcon = R.drawable.device_airpods_pro2_left, leftPercent = 0.85f, @@ -85,5 +103,25 @@ sealed class WidgetRenderState { casePercent = 1.0f, caseCharging = false, ) + + fun previewSinglePod( + theme: WidgetTheme = WidgetTheme.DEFAULT, + @ColorInt bgColor: Int = 0xFFFFFFFF.toInt(), + @ColorInt textColor: Int = 0xFF1E1E1E.toInt(), + @ColorInt iconColor: Int = 0xFF1E1E1E.toInt(), + layout: BatteryLayout = BatteryLayout.NARROW, + ): SinglePod = SinglePod( + theme = theme, + resolvedBgColor = bgColor, + resolvedTextColor = textColor, + resolvedIconColor = iconColor, + layout = layout, + deviceLabel = "My AirPods Max", + headsetIcon = R.drawable.device_airpods_max, + percent = 0.72f, + batteryIcon = R.drawable.ic_baseline_battery_3_bar_24, + charging = false, + worn = true, + ) } } 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 0973e2e1..2d1f84e3 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 @@ -18,7 +18,7 @@ object WidgetRenderStateMapper { isPro: Boolean, hasConfiguredProfile: Boolean, profileLabel: String?, - isWide: Boolean = false, + layout: BatteryLayout = BatteryLayout.NARROW, ): WidgetRenderState { val bgColor = resolvedBgColor(context, theme) val textColor = resolvedTextColor(context, theme) @@ -30,6 +30,7 @@ object WidgetRenderStateMapper { resolvedBgColor = bgColor, resolvedTextColor = textColor, resolvedIconColor = iconColor, + layout = layout, primaryText = context.getString(R.string.upgrade_capod_label), secondaryText = context.getString(R.string.upgrade_capod_description), ) @@ -39,7 +40,7 @@ object WidgetRenderStateMapper { resolvedBgColor = bgColor, resolvedTextColor = textColor, resolvedIconColor = iconColor, - isWide = isWide, + layout = layout, deviceLabel = profileLabel ?: device.getLabel(context), leftIcon = device.leftPodIcon, leftPercent = device.batteryLeft.toBatteryFloat(), @@ -59,6 +60,7 @@ object WidgetRenderStateMapper { resolvedBgColor = bgColor, resolvedTextColor = textColor, resolvedIconColor = iconColor, + layout = layout, deviceLabel = profileLabel ?: device.getLabel(context), headsetIcon = device.iconRes, percent = device.batteryHeadset.toBatteryFloat(), @@ -72,6 +74,7 @@ object WidgetRenderStateMapper { resolvedBgColor = bgColor, resolvedTextColor = textColor, resolvedIconColor = iconColor, + layout = layout, primaryText = context.getString(R.string.pods_unknown_label), ) @@ -86,6 +89,7 @@ object WidgetRenderStateMapper { resolvedBgColor = bgColor, resolvedTextColor = textColor, resolvedIconColor = iconColor, + layout = layout, primaryText = context.getString(messageRes), ) } diff --git a/app/src/main/res/xml/battery_widget_info.xml b/app/src/main/res/xml/battery_widget_info.xml index fa101795..149aa06c 100644 --- a/app/src/main/res/xml/battery_widget_info.xml +++ b/app/src/main/res/xml/battery_widget_info.xml @@ -1,7 +1,7 @@