Files
NoGooleApps/replica/wetter/architecture.md
T
2026-10-17 14:56:22 +02:00

8.6 KiB
Raw Permalink Blame History

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