Skip to content

feat(widget): Android forecast widgets (Solar/Price/CO₂/Feed-in) - #264

Draft
webalexeu wants to merge 7 commits into
evcc-io:mainfrom
webalexeu:feat/android-widgets-forecast
Draft

feat(widget): Android forecast widgets (Solar/Price/CO₂/Feed-in)#264
webalexeu wants to merge 7 commits into
evcc-io:mainfrom
webalexeu:feat/android-widgets-forecast

Conversation

@webalexeu

Copy link
Copy Markdown

Follow-up to #255 (split out per naltatis's review: #255 (comment))

#255 now covers the Android Loadpoint widget only. This branch is a snapshot of #255 from just before that split - it still has the from-scratch Solar/Price/CO₂/Feed-in forecast widgets, kept here for whenever the charting approach is settled (Android has no first-party charting API comparable to iOS's Swift Charts; the current implementation hand-rolls each chart to a Canvas bitmap in ChartRenderer.kt rather than relying on a library).

  • ForecastWidget.kt / ChartRenderer.kt - the four forecast widgets, Y-axis + step/step-area/area chart modes, per-type color
  • ForecastWidgetConfigActivity.kt - server picker + live preview, Solar's "adjust to real production" toggle
  • Shares ApiClient.kt/SharedStore.kt/Theme.kt/WidgetConfig.kt with the Loadpoint widget in feat: Android home-screen widgets (Jetpack Glance) #255

TODO

  • Rebase onto feat: Android home-screen widgets (Jetpack Glance) #255 once merged, dropping the now-duplicated Loadpoint-widget history
  • Decide on the charting approach - hand-rolled ChartRenderer.kt (already working) vs. a library (e.g. Vico's compose-glance module, currently pre-release) - before this is ready for review

webalexeu and others added 7 commits August 17, 2026 05:46
Adds loadpoint and forecast home-screen widgets built with Jetpack
Glance: a widget configuration activity for picking server + loadpoint,
real chart rendering for forecast data, per-instance widget config with
immediate refresh on server change, and widget package name derived
from app config so fork builds can coexist with the official app
install.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
lint was failing on 3 @typescript-eslint/no-explicit-any errors in
withAndroidWidget.ts; typed the receiver/activity manifest nodes against
AndroidConfig.Manifest instead. Also brings the README status section up to
date - it still described the pipeline-spike state (one widget, no config
Activity, no instant refresh) even though all 5 widgets, per-instance config,
and instant refresh are implemented.
Ports LoadpointVM's full status/metric logic (heating, finished/
waitForVehicle, kWh fallback), status dot + color coding, a canvas-rendered
progress bar (Glance has no fractional-width modifier), chip-style mode
buttons, a two-column forecast header, a chart Y-axis + step-vs-area modes +
per-type color (previously always a flat green line), bold/colored footer
stats, and day/night theming throughout - all mirrored from LoadpointViews.swift
/ Views.swift / Theme.swift.

Both widget config Activities now fetch real data for the tapped choice and
show an actual preview of the widget (plain Views reusing the same chart/
progress-bar bitmaps, since a live Glance render would need pulling in the
full Compose UI stack) before committing via a new "Use this" button, instead
of committing immediately on tap with no preview.

Verified with expo prebuild + local assembleDebug/assembleRelease builds.
The Android widgets and their config Activities had hardcoded English text.
Extend build-widget-strings.mts (already generating the iOS .xcstrings
catalog from evcc's + this app's Weblate translations) to also emit Android
string resources under targets/android-widget/res/values(-b+<locale>)/, and
have withAndroidWidget.ts merge them into the prebuilt project's
values/strings.xml (which already has app_name etc.) instead of overwriting
it.

Replaced every hardcoded string in the widget/config-Activity Kotlin with
R.string.* lookups. The config Activities' picker/live-preview flow has no
iOS equivalent, so those strings are new additions to this app's own i18n
(en.json source + de.json, matching this repo's i18n conventions) rather
than reuses of existing evcc/app keys.

Also generalized the config plugin's package-rewrite from a single `package`
line replace to a whole-file regex, since the widget Kotlin now needs
`import io.evcc.android.R` to resolve correctly for the LOCAL-ONLY dev fork
build too (io.evcc.android.dev).

Verified with expo prebuild + local assembleDebug (confirmed the manifest
merge keeps app_name, and R resolves for the fork package).
Adds the last open follow-up from the parity work: a second widget size,
mirroring LoadpointCard's HStack { left; modeSelector } on iOS. Declares
SizeMode.Responsive(setOf(SMALL_SIZE, WIDE_SIZE)) and reads LocalSize to
branch layout - compact keeps the inline mode-chip row (deliberately kept
interactive, unlike iOS's compact size which drops to a plain-text mode
label), wide adds a vertical mode-selector column alongside. No manifest
change needed since the widget was already resizable; this just makes the
wider size render differently once a user resizes it.

Forecast widgets have no iOS size-variant precedent, so they stay single-size.

Verified with expo prebuild + local assembleDebug.
pairs with evcc-io/evcc#32490 and mirrors targets/widget/Loadpoint.swift's
handling from evcc-io#246, which this branch picked up via a rebase onto
main. Detects smart-mode servers via loadpoints[].alwaysCharge, switching the
selector to off/smart/now (with device-class labels for continuous heat pumps
and switchable devices) while old servers keep off/pv/minpv/now unchanged. A
read-only "∞" marks the Smart chip when Always charge is on/once, no toggle in
the widget yet, matching iOS.

Also fixes a bug the rebase's auto-merge introduced: the frozen pv/minpv
legacy-label loop wrote straight into `strings`, a variable that in this
branch is now built later from an intermediate `translations` map (added here
to share resolved strings between the iOS and Android generators) - it was
referencing `strings` before its declaration.
Addresses evcc-io#255#issuecomment-5317470240 (Maschga's PR review):

- LoadpointWidget gets a reload button in the title row (res/drawable/ic_reload.xml
  tinted via Glance's ColorFilter.tint(), wired to a new ReloadAction), mirroring
  iOS's ReloadIntent.
- Both widget families are now tappable end-to-end, deep-linking to the right
  app screen (evcc://loadpoint?server=&lp=, evcc://forecast?server=,
  evcc://server when unconfigured) via a shared deepLinkAction() helper -
  mirrors widgetURL in LoadpointViews.swift/Views.swift, including the same
  query param semantics the app's own router (AppContext.tsx) expects.
- loadpoint_widget_info.xml's resize bounds are now pinned to
  SizeMode.Responsive's two declared breakpoints (180-340dp wide, height
  locked at 110dp, resizeMode="horizontal" only) instead of open-ended
  horizontal|vertical; forecast_widget_info.xml drops resizeMode entirely
  since those widgets have no size-variant layout. Closes the gap where a
  launcher could hand the widget a real container bigger than any size Glance
  was told to lay content out for, leaving unfillable blank space - the
  likely cause of the "strange spacing" in Maschga's screenshot, pending
  on-device confirmation.

The mode-button highlight bug and widget-reconfigure check from the same
review are held for a live-device pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant