replica: recon, architecture, design tokens, build log, reference and clone screenshots
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# Architecture: Wetter-Klon (Android, ohne Google)
|
||||
|
||||
Basis: `recon.md`, `features.csv`. Schwerpunkt: **modernes Design und Homescreen-Widgets**.
|
||||
Es gibt keinen Server, keine Accounts und keine Zahlungen. Die App läuft komplett auf dem Gerät und spricht nur öffentliche, schlüsselfreie APIs an.
|
||||
|
||||
## Stack
|
||||
|
||||
| Schicht | Wahl | warum |
|
||||
| --- | --- | --- |
|
||||
| Sprache/UI | Kotlin + Jetpack Compose, **Material 3 Expressive** | native Animationen, Shape-Morphing, Dynamic Color: genau der Look des Originals |
|
||||
| Hintergründe | Compose Canvas + **AGSL Runtime Shaders** (API 33+), Fallback auf Canvas-Partikel | flüssiger Regen/Schnee/Wolken/Sonne bei 60–120 fps, eigene Grafik statt Google-Assets |
|
||||
| Widgets | **Jetpack Glance** (+ RemoteViews nur wo nötig) | Compose-artige Widget-API, responsive Größen, Dynamic Color |
|
||||
| Persistenz | Room (SQLite) + DataStore | eine lokale DB, offline-first |
|
||||
| Netzwerk | Ktor Client + kotlinx.serialization | leicht, keine Google-Abhängigkeit |
|
||||
| Hintergrundjobs | WorkManager | Refresh, Warnungen, Widget-Updates ohne Play Services |
|
||||
| Standort | `android.location.LocationManager` (kein Fused Provider) | funktioniert ohne GMS (GrapheneOS, /e/, LineageOS) |
|
||||
| Karte | MapLibre Native + OSM-/OpenFreeMap-Tiles, RainViewer-Radarkacheln | Open Source, keine Google Maps |
|
||||
| DI | Hilt oder manuell (Koin) | Koin: weniger Magie, reicht für eine App |
|
||||
| Build/Verteilung | Gradle KTS, minSdk 29, targetSdk aktuell, F-Droid-tauglich (keine proprietären Libs) | |
|
||||
|
||||
## Datenquellen (offiziell, öffentlich)
|
||||
|
||||
| Zweck | API | Hinweise |
|
||||
| --- | --- | --- |
|
||||
| Vorhersage aktuell/stündlich/10 Tage | Open-Meteo `/v1/forecast` (`models=best_match`, für DE `icon_seamless`) | kostenlos nichtkommerziell, CC BY 4.0, Attribution in den Einstellungen |
|
||||
| Minutengenauer Niederschlag | Open-Meteo `minutely_15` | Basis für „Regen in 20 Min“ |
|
||||
| Luftqualität + Pollen | Open-Meteo Air Quality API | Pollen nur in Europa |
|
||||
| Ortssuche | Open-Meteo Geocoding API | Name/PLZ |
|
||||
| Reverse Geocoding (GPS → Name) | Nominatim (OSM), max. 1 Req/s, eigener User-Agent | nur beim Standortwechsel |
|
||||
| Warnungen DE | DWD Warnungen (`warnings.json`/CAP) über Bright Sky API | |
|
||||
| Warnungen EU | MeteoAlarm CAP/Atom Feeds | |
|
||||
| Radar | RainViewer Weather Maps API (Vergangenheit + Nowcast) | Attribution Pflicht |
|
||||
|
||||
## Schema
|
||||
|
||||
Siehe `schema.sql`: 6 Tabellen (`locations`, `card_layout`, `forecast_cache`, `alerts`, `widget_config`, `notification_pref`), die Einstellungen liegen in DataStore.
|
||||
Zugriffsregeln: Es gibt keinen Mehrbenutzerbetrieb, Row Level Security entfällt. Die Daten liegen app-privat im Sandbox-Speicher, Backup über Auto Backup ist optional (ausschaltbar).
|
||||
Harte Constraints: nur ein `is_current`-Ort (partieller Unique-Index), keine doppelten Orte (gerundete Koordinaten), Warnungen werden über die Provider-ID dedupliziert.
|
||||
|
||||
## Interne „API“ (Repositories / Use Cases)
|
||||
|
||||
| Aufruf | macht | Input | Output | Flow |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `ForecastRepo.observe(locId)` | Flow aus Cache, löst Refresh aus, wenn älter als 30 min | locId | `Forecast` + Status (fresh/stale/offline) | F01 |
|
||||
| `ForecastRepo.refresh(locId, force)` | Open-Meteo forecast + air laden, Cache schreiben | locId | Result | F01 |
|
||||
| `LocationRepo.updateCurrent()` | GPS → Reverse Geocoding → `is_current`-Zeile | – | Location | F01 |
|
||||
| `LocationRepo.search(q)` | Geocoding, entprellt 300 ms | query | List<Place> | F02 |
|
||||
| `LocationRepo.add/delete/reorder` | CRUD + Standard-`card_layout` anlegen | Place / id / order | – | F02, F03, F04 |
|
||||
| `LayoutRepo.move(locId, card, toPos)` | Kartenreihenfolge pro Stadt | – | – | F07 |
|
||||
| `AlertRepo.sync(locId)` | DWD/MeteoAlarm abrufen, abgelaufene löschen | locId | List<Alert> | F08 |
|
||||
| `RadarRepo.frames()` | RainViewer-Frame-Liste (−2 h … +30 min) | – | Frames | F05 |
|
||||
| `BriefGenerator.build(forecast, alerts)` | regelbasierter Satz („Ab 15 Uhr Regen, morgen 8° kühler“) | Forecast | String | S01 |
|
||||
| `SettingsRepo` | Einheiten, Theme, Animationen | – | Flow<Settings> | F06 |
|
||||
|
||||
### Hintergrundjobs (WorkManager)
|
||||
|
||||
| Job | Takt | macht |
|
||||
| --- | --- | --- |
|
||||
| `RefreshWorker` | periodisch 30 min (Netz vorhanden), Batterie nicht niedrig | alle Orte + Widgets aktualisieren |
|
||||
| `AlertWorker` | 15 min | Warnungen abrufen, neue severe/extreme → Notification |
|
||||
| `RainSoonWorker` | 15 min, nur Orte mit `rain_alerts` | `minutely_15` prüfen → „Regen in ~X Min“ |
|
||||
| `TomorrowWorker` | täglich zur eingestellten Uhrzeit | Morgen-Vorhersage als Notification |
|
||||
| Widget-Update | nach jedem Refresh + `updatePeriodMillis` als Fallback | `GlanceAppWidget.updateAll()` |
|
||||
|
||||
## Design-Architektur (Priorität)
|
||||
|
||||
- **Theme:** Material 3 Expressive, Dynamic Color (Monet) **plus** eine eigene, zur Wetterlage passende Farbpalette (z. B. klar/Tag → Blau-Gelb, Gewitter → Violett-Grau). `WeatherTheme` liefert Tokens aus `replica-design`.
|
||||
- **Hintergrund-Engine:** `WeatherScene(condition, isDay, intensity, windDir)` → AGSL-Shader-Layer (Himmelsverlauf, Wolken, Niederschlagspartikel, Blitz, Nebel), mit Parallax beim Scrollen. Bei aktiviertem Battery Saver bzw. „Animationen reduzieren“ wird ein statischer Verlauf gezeigt.
|
||||
- **Karten:** wiederverwendbare `MetricCard` mit expressiven Formen (Cookie-/Pill-Shapes), eigene Gauges (UV-Bogen, Windkompass, Sonnenbogen, AQI-Skala) in Canvas, alles eigene Icons (z. B. Material Symbols, Apache 2.0, plus eigene Wettericons).
|
||||
- **Motion:** Shared-Element-Übergang von der Städteliste zur Stadt, Kartendrag mit Spring-Animationen, Haptik beim Einrasten.
|
||||
- **Edge-to-Edge**, prädiktive Zurück-Geste, Two-Pane-Layout ab Breite „medium“ (Tablet/Foldable).
|
||||
|
||||
## Widgets (Priorität)
|
||||
|
||||
| Widget | Größen | Inhalt |
|
||||
| --- | --- | --- |
|
||||
| Jetzt | 2×1, 2×2 | Icon, Temperatur, H/T, Ort |
|
||||
| Stündlich | 4×2 | Jetzt + 5–6 Stunden |
|
||||
| Täglich | 4×2, 4×3 | Jetzt + 5 Tage mit Temperaturbalken |
|
||||
| Groß/Komplett | 4×4 | Jetzt, stündlich, täglich, Warnhinweis |
|
||||
| Pille/Glance | 4×1 | Datum + Wetter in einer Zeile (eigener At-a-Glance-Ersatz) |
|
||||
|
||||
- Responsive `SizeMode.Responsive`, ein Widget-Typ pro Inhalt mit Größen-Breakpoints.
|
||||
- Stile: Dynamic Color, Wetterlage als Hintergrund (vorgerenderte Bitmaps, weil Widgets keine Shader können), transparent.
|
||||
- Konfigurations-Activity: Ort wählen (oder „aktueller Standort“) und den Stil, mit Live-Vorschau.
|
||||
- Tippen öffnet direkt die jeweilige Stadt, das Refresh-Icon stößt `RefreshWorker` an.
|
||||
|
||||
## Was beißt
|
||||
|
||||
- **Zeitzonen/DST:** Stunden und Tage immer in der Zeitzone des Orts (`timezone=auto`) zeigen, nicht in der des Geräts.
|
||||
- **Doze/Akkuoptimierung:** WorkManager-Takte sind nicht garantiert. Bei OEMs mit aggressivem Energiesparen auf die Ausnahme hinweisen.
|
||||
- **Ohne Play Services:** Kein Fused Location. Für schnelle Ergebnisse `getCurrentLocation` des Netzwerk-Providers mit Timeout nutzen, sonst die letzte bekannte Position.
|
||||
- **Rate Limits:** Open-Meteo erlaubt 10k Aufrufe am Tag, kein Problem. Nominatim erlaubt 1 Req/s, also cachen. RainViewer-Tiles müssen gecacht werden.
|
||||
- **Widget-Grenzen:** RemoteViews können keine Animationen und keine Shader, daher Hintergründe vorrendern. Updates sind limitiert, also ereignisgesteuert aktualisieren.
|
||||
- **Offline:** Der Cache wird immer angezeigt, mit dem Stand „vor X Min“. Ab 3 h wird er als veraltet markiert.
|
||||
- **Lizenz/Attribution:** Open-Meteo, DWD, RainViewer und OSM kommen in die About-Seite und ins Kartenoverlay.
|
||||
- **Performance:** Shader auf alten Geräten drosseln (adaptive Qualität), Hintergrund pausieren, wenn er nicht sichtbar ist.
|
||||
- **DSGVO:** Es gibt kein Tracking und kein Analytics. Koordinaten gehen nur an die Wetter-APIs, auf 2 Nachkommastellen gerundet.
|
||||
|
||||
## Build-Reihenfolge
|
||||
|
||||
1. **Vertical Slice** (S01, S14): GPS → Open-Meteo → Room → Home mit Hero, stündlicher Leiste und 10-Tage-Liste, schlicht gestaltet. Tabellen `locations`, `forecast_cache`. Beweist den Stack ohne GMS.
|
||||
2. **Design-Fundament** (S01): Theme, `WeatherScene`-Shader für die 6 Grundlagen (klar, bewölkt, Regen, Schnee, Gewitter, Nebel × Tag/Nacht), `MetricCard` und alle Must-Karten.
|
||||
3. **Widgets** (S12): Jetzt, Stündlich, Täglich, Konfiguration, `RefreshWorker`. Tabelle `widget_config`.
|
||||
4. **Orte** (S02, S03): Suche, Hinzufügen, Swipe-to-Delete, Pager zwischen Städten.
|
||||
5. **Warnungen** (S11, S13): DWD/MeteoAlarm, Banner, `AlertWorker`. Tabelle `alerts`.
|
||||
6. **Einstellungen** (S07–S09): Einheiten, Theme, Animationen.
|
||||
7. **Should:** Radar (S06), Kartenreihenfolge (S10, `card_layout`), AQI, Druck, Sicht, Detailansichten (S04, S05), großes Widget und Pillen-Widget, Regen-Benachrichtigung.
|
||||
8. **Could:** Brief, Pollen, Morgen-Benachrichtigung, Vibrationen, Tablet-Layout.
|
||||
9. **Fixes** aus `/replica-entrepreneur`.
|
||||
Reference in New Issue
Block a user