Skip to content

feat: Android home-screen widgets (Jetpack Glance) - #255

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

feat: Android home-screen widgets (Jetpack Glance)#255
webalexeu wants to merge 8 commits into
evcc-io:mainfrom
webalexeu:feat/android-widgets

Conversation

@webalexeu

Copy link
Copy Markdown

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.

@webalexeu

Copy link
Copy Markdown
Author

Pushed a follow-up commit (d9a9d77) to get this review-ready:

  • npm run lint was failing on 3 @typescript-eslint/no-explicit-any errors in withAndroidWidget.ts (the manifest <receiver>/<activity> injection) — typed against AndroidConfig.Manifest instead of loosening to any.
  • Updated targets/android-widget/README.md's status section, which still described the original spike (one widget, no config Activity, no instant refresh) — all 5 widgets, per-instance config, and instant refresh are actually implemented already; only localization and full visual parity with iOS remain outstanding.

Verified locally: npm run lint clean, expo prebuild --platform android --clean + ./gradlew assembleDebug build successfully.

@Maschga

Maschga commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

I get an error when opening the app:
grafik

@webalexeu

Copy link
Copy Markdown
Author

I get an error when opening the app:
grafik

I don't have error on my side but I test directly on a phone and not inside emulator. Which Android version did you emulate ?

@Maschga

Maschga commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

The error was on my side. Did a complete reinstall of the repo. Now I can build the app.

@Maschga

Maschga commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

I was able to test the widgets.
So far, everything is working.
The widgets are still very basic, but the PR is also a draft. In the end, the widgets should look like the ones on iOS.
It would also be nice to have a preview when configuring the widgets.

@webalexeu

Copy link
Copy Markdown
Author

I was able to test the widgets. So far, everything is working. The widgets are still very basic, but the PR is also a draft. In the end, the widgets should look like the ones on iOS. It would also be nice to have a preview when configuring the widgets.

As it is time consuming, I'm waiting to see if you agree to go (CC: @naltatis ) and then I can continue the work.
Indeed, idea is to be aligned as much as possible with the IOS one

@Maschga

Maschga commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

To me, this looks like a good approach. The implementation of Android widgets is definitely wanted.

@webalexeu
webalexeu force-pushed the feat/android-widgets branch from d9a9d77 to 52f7788 Compare August 16, 2026 10:59
@webalexeu

Copy link
Copy Markdown
Author

Pushed a follow-up addressing both points from @Maschga's feedback above:

  • Visual parity with iOS: status dot + color-coded status text, a real progress bar (rounded, striped while charging - Glance has no fractional-width layout modifier, so it's a small canvas bitmap like the chart), chip-style mode buttons with a selected-state fill, and the full status/metric logic ported from LoadpointVM (heating devices, finished/waiting-for-vehicle states, kWh fallback when SoC isn't available). Forecast widgets get a two-column header, a chart Y-axis, and step-vs-area chart modes with per-type color - price/CO₂ used to render identically to solar (a flat green area line) regardless of type; now they're correctly stepped and colored. Day/night theming throughout (previously always dark).
  • Live preview when configuring: tapping a server/loadpoint/toggle in either config screen now fetches real data and shows an actual preview of the widget before you commit, via a new "Use this" button - it used to commit immediately on tap with no feedback.

Still not attempted: widget size variants (iOS's medium-size mode-selector column) and localization (still hardcoded English) - both already called out as follow-ups.

Verified with expo prebuild + local assembleDebug/assembleRelease builds.

@webalexeu

Copy link
Copy Markdown
Author

Pushed both remaining follow-ups called out earlier:

  • Localization: every widget/config-Activity string now reads from Android string resources instead of being hardcoded English. build-widget-strings.mts (already generating the iOS .xcstrings catalog) now also emits res/values(-b+<locale>)/strings.xml for all 27 supported languages, from the same evcc-daemon + app Weblate translations. The config Activities' picker/live-preview flow has no iOS equivalent, so those strings are new additions to this app's own en.json/de.json rather than reuses.
  • Size variant: the Loadpoint widget now supports two sizes via SizeMode.Responsive + LocalSize - resized wider, it shows a vertical mode-selector column alongside the status/metric info, mirroring iOS's systemMedium layout (LoadpointCard's HStack { left; modeSelector }). Compact size keeps the interactive inline chip row (a deliberate difference from iOS, which drops to a plain-text mode label at its smallest size - dropping interactivity felt like a regression). Forecast widgets have no iOS size-variant precedent, so they stay single-size.

Verified with expo prebuild + local assembleDebug/assembleRelease builds throughout.

@Maschga

Maschga commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Just to make sure this topic isn't forgotten: The widgets will need to support Smart Mode once they're ready. (see #246)

The current widgets already look promising!

webalexeu and others added 6 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.
@webalexeu
webalexeu force-pushed the feat/android-widgets branch from 1aac0fb to 223ae76 Compare August 17, 2026 05:53
@webalexeu

Copy link
Copy Markdown
Author

Still not attempted: widget size variants (iOS's medium-size mode-selector column) and localization (still hardcoded English) - both already called out as follow-ups.

I've implemented it, thanks for noticing it. Can you do a check again and provide a feedback?
Thanks

@Maschga

Maschga commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator
  • Sometimes, when clicking on another mode than currently selected (for example off), the mode switches at evcc level but it doesn't switch the active button to off at widget level
  • As far as I can tell you can't reconfigure the widgets
  • The widgets need to look exactly like the iOS ones. Currently, there is some strange spacing, the reload button is missing and the overall style doesn't follow the style from the iOS widgets.
  • When clicking on a widget the app should open the correct evcc instance in the app and deep link to the correct tab (either the correct loadpoint or the forecast tab). See https://docs.evcc.io/de/reference/app for more information
  • It would be very nice to have a pipeline which automatically tests these widgets on Android. I don't know if this is possible, but if so, I can help you getting this pipeline working
  • The widgets do not have to support every widget size. AFAIK the iOS widgets are restricted too. Feel free to limit the widgets size to a something quadratic and rectangular. Have a look at the videos and images at this PR iOS: add forecast & loadpoint widgets #212 for this. Something like 2x2 and 2x4 should be enough.
grafik

The buttons, text, and graphics are all on the widget up there. Now it's just a matter of arranging them properly so they look like the iOS widgets.

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.
@naltatis

Copy link
Copy Markdown
Member

The widgets need to look exactly like the iOS ones. Currently, there is some strange spacing, the reload button is missing and the overall style doesn't follow the style from the iOS widgets.

If charting is non-trivial for Android (iOS has builtin charting for widgets), we could also simplify and start with just the loadpoint widgets.

Per naltatis's review (evcc-io#255#issuecomment-5327087002): Android has
no first-party charting API comparable to iOS's Swift Charts, and even
third-party Compose chart libraries generally can't render inside a Glance
widget's RemoteViews surface - the four forecast widgets carry real
hand-rolled-canvas risk this PR doesn't need to also resolve. Splits them out:

- Removes ForecastWidget.kt, ForecastWidgetConfigActivity.kt, ChartRenderer.kt
  and their manifest receiver/config-activity/widget-info-xml/preview
  registration in the config plugin.
- Trims the now-unused forecast-only pieces from the files shared between
  both widget families: WidgetConfig.kt (saveForecast/forecastServerId/
  forecastAdjust), Theme.kt (per-forecast-type Palette + header/footer
  styles), WidgetPreview.kt (forecast()/footerSide()).
- build-widget-strings.mts is untouched - it also feeds iOS's already-shipped
  forecast widgets, so its KEYS table still has forecast entries; only unused
  on the Android side now.
- README rewritten for loadpoint-only scope, pointing at the
  feat/android-widgets-forecast branch (a snapshot of this branch pre-split)
  for the deferred forecast-widget follow-up.

Verified with expo prebuild + a full local Android build
(assembleDebug) and npm run lint.
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.

3 participants