@@ -60,13 +60,38 @@ private val WIDE_SIZE = DpSize(340.dp, 110.dp)
6060// Row measurements are unreliable right at a boundary on some launchers, so
6161// the switch-over threshold sits well below WIDE_SIZE's width.
6262private val WIDE_THRESHOLD = 260 .dp
63- // visible (not private) so the config activities can reuse them for previews
64- fun modeLabel (context : Context , mode : String ): String = when (mode) {
65- " off" -> context.getString(R .string.widget_mode_off)
66- " pv" -> context.getString(R .string.widget_mode_pv)
67- " minpv" -> context.getString(R .string.widget_mode_minpv)
68- " now" -> context.getString(R .string.widget_mode_now)
69- else -> mode
63+ // visible (not private) so the config activities can reuse them for previews.
64+ // mirrors Loadpoint.swift's `item()` closure: on smart-mode servers (detected
65+ // by the presence of `alwaysCharge`) the same raw mode can carry a
66+ // device-class-specific label (off->Normal, now->Boost/On) for continuous
67+ // heat pumps / switchable devices.
68+ fun modeLabel (context : Context , lp : Loadpoint , mode : String ): String {
69+ val smartModeServer = lp.alwaysCharge != null
70+ if (smartModeServer) {
71+ if (mode == " off" && lp.chargerFeatureContinuous) return context.getString(R .string.widget_mode_normal)
72+ if (mode == " now" ) {
73+ if (lp.chargerFeatureContinuous) return context.getString(R .string.widget_mode_boost)
74+ if (lp.chargerFeatureSwitchDevice) return context.getString(R .string.widget_mode_on)
75+ }
76+ }
77+ return when (mode) {
78+ " off" -> context.getString(R .string.widget_mode_off)
79+ " smart" -> context.getString(R .string.widget_mode_smart)
80+ " pv" -> context.getString(R .string.widget_mode_pv)
81+ " minpv" -> context.getString(R .string.widget_mode_minpv)
82+ " now" -> context.getString(R .string.widget_mode_now)
83+ else -> mode
84+ }
85+ }
86+
87+ fun alwaysChargeActive (lp : Loadpoint ): Boolean = lp.alwaysCharge == " on" || lp.alwaysCharge == " once"
88+
89+ // label plus a read-only "∞" marker on the Smart chip when Always charge is
90+ // on/once (mirrors the SF Symbol "infinity" shown next to Smart in
91+ // LoadpointViews.swift; no toggle in the widget for now, matches iOS)
92+ fun modeChipLabel (context : Context , lp : Loadpoint , mode : String ): String {
93+ val label = modeLabel(context, lp, mode)
94+ return if (mode == " smart" && alwaysChargeActive(lp)) " $label ∞" else label
7095}
7196
7297enum class LpStatus (val active : Boolean ) {
@@ -133,8 +158,11 @@ fun title(context: Context, lp: Loadpoint): String {
133158 return vt.ifEmpty { lp.title ? : context.getString(R .string.widget_loadpoint_name) }
134159}
135160
136- fun modes (lp : Loadpoint ): List <String > =
137- if (lp.chargerFeatureSwitchDevice) listOf (" off" , " pv" , " now" ) else listOf (" off" , " pv" , " minpv" , " now" )
161+ fun modes (lp : Loadpoint ): List <String > = when {
162+ lp.alwaysCharge != null -> listOf (" off" , " smart" , " now" )
163+ lp.chargerFeatureSwitchDevice -> listOf (" off" , " pv" , " now" )
164+ else -> listOf (" off" , " pv" , " minpv" , " now" )
165+ }
138166
139167class LoadpointWidget : GlanceAppWidget () {
140168 override val sizeMode = SizeMode .Responsive (setOf (SMALL_SIZE , WIDE_SIZE ))
@@ -266,7 +294,7 @@ class LoadpointWidget : GlanceAppWidget() {
266294 Row {
267295 modes(state.lp).forEachIndexed { i, mode ->
268296 if (i > 0 ) Spacer (GlanceModifier .width(4 .dp))
269- ModeChip (context, mode = mode, current = state.lp. mode, serverId = state.serverId, lpIndex = state.lpIndex)
297+ ModeChip (context, lp = state.lp, mode = mode, serverId = state.serverId, lpIndex = state.lpIndex)
270298 }
271299 }
272300 }
@@ -278,7 +306,7 @@ class LoadpointWidget : GlanceAppWidget() {
278306 modes(state.lp).forEachIndexed { i, mode ->
279307 if (i > 0 ) Spacer (GlanceModifier .height(6 .dp))
280308 ModeChip (
281- context, mode = mode, current = state.lp. mode, serverId = state.serverId, lpIndex = state.lpIndex,
309+ context, lp = state.lp, mode = mode, serverId = state.serverId, lpIndex = state.lpIndex,
282310 modifier = GlanceModifier .fillMaxWidth().defaultWeight(),
283311 )
284312 }
@@ -287,13 +315,13 @@ class LoadpointWidget : GlanceAppWidget() {
287315 @Composable
288316 private fun ModeChip (
289317 context : Context ,
318+ lp : Loadpoint ,
290319 mode : String ,
291- current : String? ,
292320 serverId : String ,
293321 lpIndex : Int ,
294322 modifier : GlanceModifier = GlanceModifier ,
295323 ) {
296- val selected = mode == current
324+ val selected = mode == lp.mode
297325 Box (
298326 modifier = modifier
299327 .background(if (selected) modeSelectedBackground else modeUnselectedBackground)
@@ -311,7 +339,7 @@ class LoadpointWidget : GlanceAppWidget() {
311339 contentAlignment = Alignment .Center ,
312340 ) {
313341 Text (
314- text = modeLabel (context, mode),
342+ text = modeChipLabel (context, lp , mode),
315343 style = modeChipStyle.copy(color = if (selected) modeSelectedText else modeUnselectedText),
316344 )
317345 }
0 commit comments