Skip to content

Commit 52f7788

Browse files
committed
fix: type the manifest config plugin without any, update widget README
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.
1 parent dff75ae commit 52f7788

2 files changed

Lines changed: 40 additions & 19 deletions

File tree

scripts/androidWidget/withAndroidWidget.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,25 @@ const FORECAST_RECEIVERS = [
8686
{ name: "EvccFeedinWidgetReceiver", label: "Feed-in" },
8787
];
8888

89-
const pushWidgetReceiver = (app: any, shortName: string, infoResource: string, label: string) => {
89+
// The manifest types don't model android:label or <meta-data> on <receiver>
90+
// (Expo's typings only add them for activities/applications), though the
91+
// manifest XML writer accepts both fine.
92+
type ManifestReceiver = NonNullable<AndroidConfig.Manifest.ManifestApplication["receiver"]>[number];
93+
type LabeledManifestReceiver = ManifestReceiver & {
94+
$: ManifestReceiver["$"] & { "android:label"?: string };
95+
"meta-data"?: AndroidConfig.Manifest.ManifestMetaData[];
96+
};
97+
98+
const pushWidgetReceiver = (
99+
app: AndroidConfig.Manifest.ManifestApplication,
100+
shortName: string,
101+
infoResource: string,
102+
label: string,
103+
) => {
90104
app.receiver = app.receiver ?? [];
91105
const name = `.${WIDGET_SUBDIR}.${shortName}`;
92-
if (app.receiver.some((r: any) => r.$["android:name"] === name)) return;
93-
app.receiver.push({
106+
if (app.receiver.some((r) => r.$["android:name"] === name)) return;
107+
const receiver: LabeledManifestReceiver = {
94108
$: { "android:name": name, "android:exported": "false", "android:label": label },
95109
"intent-filter": [
96110
{ action: [{ $: { "android:name": "android.appwidget.action.APPWIDGET_UPDATE" } }] },
@@ -103,7 +117,8 @@ const pushWidgetReceiver = (app: any, shortName: string, infoResource: string, l
103117
},
104118
},
105119
],
106-
});
120+
};
121+
app.receiver.push(receiver);
107122
};
108123

109124
const withWidgetReceiver: ConfigPlugin = (config) =>
@@ -127,7 +142,7 @@ const withWidgetReceiver: ConfigPlugin = (config) =>
127142
action: [{ $: { "android:name": "android.appwidget.action.APPWIDGET_CONFIGURE" } }],
128143
},
129144
],
130-
} as any);
145+
});
131146
}
132147
return config;
133148
});

targets/android-widget/README.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ this is a Kotlin/Glance reimplementation of the same contracts.
66

77
## Status
88

9-
This is a **pipeline spike**: one interactive **Loadpoint** widget, end-to-end.
10-
It has **not been compiled** yet — it needs `expo prebuild` + a real Android
11-
build to verify (see below). Treat it as a foundation to iterate on.
9+
Five interactive home-screen widgets, end-to-end, with per-instance
10+
configuration and instant refresh: **Loadpoint**, and forecast widgets for
11+
**Solar / Price / CO₂ / Feed-in**. Verified with `expo prebuild` + a real local
12+
Android build (`./gradlew assembleDebug` / `assembleRelease`).
1213

1314
Done:
1415

@@ -19,22 +20,27 @@ Done:
1920
- `kotlin/ApiClient.kt` — GET `/api/state?jq=…` + basic auth + POST actions, plus
2021
the `Loadpoint` model (mirrors `ApiClient.swift` / `Loadpoint.swift`).
2122
- `kotlin/LoadpointWidget.kt` — Glance widget + interactive mode buttons.
23+
- `kotlin/ForecastWidget.kt` / `ChartRenderer.kt` — the four forecast widgets,
24+
with a Canvas-drawn chart (mirrors `ForecastWidget.swift`).
25+
- **Per-instance config**: `LoadpointWidgetConfigActivity.kt` (pick server, then
26+
loadpoint) and `ForecastWidgetConfigActivity.kt` (pick server; Solar also gets
27+
an "adjust to real production" toggle). Selections persist per `appWidgetId` in
28+
`WidgetConfig.kt`, including a fallback queue for launchers (e.g. MIUI) that
29+
hand the configure Activity a different id than the one the widget binds with.
30+
- **Immediate refresh on config/server change**: `modules/evcc-widget` (a small
31+
local Expo native module) exposes `refresh()`, called from
32+
`utils/widgetRefresh.ts` after `widgetSync.ts` writes the file — no need to
33+
wait for the periodic `updatePeriodMillis` tick.
2234
- `kotlin/Theme.kt` — brand colors / text styles.
2335
- `scripts/androidWidget/withAndroidWidget.ts` — Expo config plugin: injects the
24-
Kotlin, the `res/xml` widget info, the manifest `<receiver>`, and the
25-
Glance/Compose gradle wiring. Registered in `app.config.ts`.
36+
Kotlin, the `res/xml` widget info, the manifest `<receiver>`/`<activity>`
37+
entries, and the Glance/Compose gradle wiring. Registered in `app.config.ts`.
2638

2739
Not done yet (follow-ups for parity with iOS):
2840

29-
- **Per-instance config** (pick server + loadpoint). iOS uses App Intents; Android
30-
needs a widget **configuration Activity**. The spike uses the default server and
31-
`loadpoints[0]`.
32-
- **The other 5 widgets** (Solar / Price / CO₂ / Feed-in forecasts).
33-
- **Immediate refresh on config change**`widgetSync.ts` only writes the file;
34-
pushing an instant update from RN needs a tiny native module calling
35-
`LoadpointWidget().updateAll(context)`. Today the widget refreshes on its own
36-
schedule (`updatePeriodMillis`, 30 min floor) / after a mode change.
37-
- Localization (`.xcstrings``strings.xml`), size variants, full visual parity.
41+
- Localization (`.xcstrings` → Android string resources) — widget text is
42+
currently hardcoded English in the Kotlin.
43+
- Size variants, full visual parity with the iOS widgets.
3844

3945
## Build / test
4046

0 commit comments

Comments
 (0)