mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 02:36:12 -04:00
feat(widget): Add 1x1 size support to battery widget
Introduce BatteryLayout enum with size-driven dispatch. Drops minWidth from 80dp to 40dp so the battery widget can be placed at one cell. At 1-cell-wide placements render a compact icon+percent stack; wider sizes keep the current NARROW/WIDE layouts.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:minWidth="80dp"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:targetCellWidth="2"
|
||||
android:targetCellHeight="1"
|
||||
|
||||
Reference in New Issue
Block a user