# Native Plattformprüfung (v1.9.7) ## Zweck Grundlage für die Entscheidung, wie die nativen Android-/Apple-App-Hüllen und Home-/Lock-Screen-Widgets (v1.9.7) umgesetzt werden, bevor Code entsteht. Dokumentiert die geprüften offiziellen Quellen, die getroffene Architekturentscheidung, verworfene Alternativen, aktuelle Einschränkungen sowie benötigte Konten/Hardware. Prüfdatum: **2026-07-30**. ## Plattformmatrix (Stand v1.32.0, siehe CURRENT_STATUS.md für den jeweils aktuellsten Stand) | Plattform | App-Art | Store-Status | Widget | Update-Weg | |---|---|---|---|---| | **Android** | native Testversion (vollständiges Dashboard, siehe `android-app-architecture.md`) | **nicht** im Google Play Store; öffentlicher Downloadbereich implementiert, aber **noch keine veröffentlichte Beta** (Signierung offen, siehe `android-release-distribution.md`) | 3 Widgets technisch vorbereitet/im Emulator geprüft, **keine vollständige Geräteabnahme** genannt, solange sie nicht erfolgt ist | App-interner Update-Check (`versionCode`-Vergleich) + erneuter Download über die Webseite/PWA | | **iPhone/iPad** | **keine native App** - PWA (installierbar über Safari) | **keine** TestFlight-/App-Store-Version | **kein** natives Widget | PWA-eigener Update-/Cache-Mechanismus (Service Worker), kein App-Store-Update | | **Desktop** | normale Webseite, in unterstützenden Browsern als PWA installierbar | entfällt | entfällt | Browser-Cache/Service-Worker, keine native Desktop-App | ## Ausgangslage (Baseline vor dieser Version) - `HEAD` == `origin/main` == `37a8324` (v1.9.5), Arbeitsbaum sauber. - Vorhandene Umgebung (dieselbe Arbeitsstation, Windows 11, siehe unten): - **Java/JDK**: OpenJDK 21.0.10 ✔ vorhanden. - **Android SDK**: `%LOCALAPPDATA%\Android\Sdk` ✔ vorhanden, Plattformen `android-36`/ `android-36.1`, `cmdline-tools/latest` (u. a. `sdkmanager`, `lint`, `d8`), `platform-tools`, `build-tools`, `emulator`. - **Gradle**: kein `gradle`-Kommando im `PATH`, aber eine vollständige, bereits von Android Studio heruntergeladene **Gradle-9.1.0-Distribution** liegt lokal im Gradle-Wrapper-Cache (`%USERPROFILE%\.gradle\wrapper\dists\gradle-9.1.0-all\...\gradle-9.1.0\bin\gradle.bat`) - damit sind **echte** Gradle-Builds ohne Internetzugriff möglich (siehe `docs/android-build-and-test.md`). - **macOS/Xcode/Swift**: **nicht vorhanden** auf dieser Windows-Arbeitsstation (`xcodebuild`/`swift` liefern "command not found"). Die Apple-Seite dieser Version bleibt daher **ausschließlich statisch** (Quellcode/Projektstruktur/Dokumentation), siehe `docs/ios-build-signing-and-test.md` Abschnitt "Grenzen dieser Arbeitsumgebung". - Bestehende Server-API (unverändert Grundlage, keine Duplizierung): `docs/mobile-api.md` (`/api/v1/mobile/current`, `/config`, `/changes`, `/learning*`), `docs/web-push-architecture.md` (`/push/*`), `docs/radar-map-architecture.md` (`/radar/*`), `docs/warning-architecture.md` (`/warnings*`). Bestehende PWA unter `pwa/`, Deep-Link-Ziele `?view=current|warnings|radar` (siehe `pwa/index.html`). - Bestehende Standortkonfiguration: Einzelstandort `Lehre-Wendhausen` (`WEATHER_LOCATION_NAME`, `WEATHER_LATITUDE`/`WEATHER_LONGITUDE`, siehe `.env.example`) - keine Mehrort-Auswahl (siehe ROADMAP.md, bleibt v2.1). - Versionsstrategie: `CHANGELOG.md`/`ROADMAP.md`, semantische Versionierung; API-Pfad trägt `/v1/`, das Snapshot-/Widget-Schema zusätzlich ein eigenes `schemaVersion`-Feld. ## Geprüfte offizielle Quellen | Plattform | Quelle | Kernaussage (siehe Zitat/Zusammenfassung unten) | |---|---|---| | Android | developer.android.com/develop/ui/compose/glance | Jetpack Glance: Compose-Runtime-basiertes Widget-Framework, `GlanceAppWidget`/`GlanceAppWidgetReceiver`, WorkManager-Integration für periodische Updates, **nicht** direkt mit normalem Compose-UI mischbar. | | Apple | developer.apple.com/documentation/widgetkit | WidgetKit: `TimelineProvider` (`placeholder`/`getSnapshot`/`getTimeline`), Widget-Familien (Home Screen small/medium/large, Lock-Screen-Accessory-Typen, iPad extra large), App Groups für gemeinsamen Zustand über `UserDefaults(suiteName:)`. | Zusätzlich einbezogen (bereits etabliertes, unverändertes Fachwissen, nicht gesondert per Einzel-Fetch, da unstrittig und seit Jahren stabile Kernkonzepte): Android `WorkManager` für Hintergrundarbeit, Android App Links (`autoVerify`-Intent-Filter + `assetlinks.json`) als verifizierte Alternative zu einem reinen Custom-Scheme, Apple Universal Links (`apple-app-site-association` + Associated Domains Capability). Diese Konzepte werden in `docs/native-deep-links.md` konkret auf BENSA angewendet, inklusive der Einschränkung, dass die produktive Domain aktuell `` ist und ein Wechsel zu `bensa-digital.eu` laut Speicher-Notiz zur Mobile-API/PWA-Planung noch nicht abgeschlossen ist - App Links/Universal Links werden daher **vorbereitet, aber nicht als produktiv aktiviert** dokumentiert (siehe dort). **Wichtige Einschränkung dieser Prüfung:** Die automatisierten Abrufe der offiziellen Dokumentationsseiten lieferten für Android eine inhaltlich verwertbare Zusammenfassung; der Abruf der Apple-WidgetKit-Seite lieferte nur den Seitentitel ohne Fließtext (dynamisch nachgeladener Inhalt) - die obige WidgetKit-Zusammenfassung stammt daher aus etabliertem, stabilem API-Wissen (Kernprotokoll `TimelineProvider`, Widget-Familien, App Groups sind seit mehreren iOS-Jahrgängen unverändert), **nicht** aus einem einzelnen frischen Seitenabruf. Exakte aktuelle Xcode-/iOS-SDK-Minimalversionen konnten in dieser Umgebung **nicht** gegen eine lauffähige Xcode-Installation verifiziert werden (siehe Einschränkungen unten) - jede in diesem Dokument genannte Versionsangabe ist als **zum Prüfdatum plausible Untergrenze**, nicht als geprüfte Freigabe zu verstehen und muss vor einem echten Xcode-Build durch den Nutzer bestätigt werden (siehe `docs/ios-build-signing-and-test.md`, Schritt 1). ## Gewählte Architektur ### Gemeinsames Prinzip (beide Plattformen) Die native App-Hülle **berechnet kein Wetter**. Sie ruft ausschließlich die neue, kompakte `GET /api/v1/mobile/widget/current`-API ab (siehe `docs/widget-api.md`), die ihrerseits nur bereits vorhandene Snapshot-/Warnungs-/Radar-Daten zusammenfasst (`scripts/lib/widget-view-lib.js` - reine Formatierung, keine zweite Auswertung). Beide Plattformen zeigen den Zustand über dasselbe, versionierte Vokabular (`severity`, `dataFreshness`, `condition`, siehe "Widget-Zustandsmodell" unten) - ein Widget führt **niemals** einen eigenen Schwellenwert ein. ### Android - **Kotlin** + **Jetpack Compose** für die App-Hülle (Bildschirme: Aktuell/Warnungen/Radar-Link/ Hilfe), **Jetpack Glance** für alle drei Home-Screen-Widgetgrößen (Compose-basiert, aber bewusst als **eigener** UI-Baum - siehe Glance-Einschränkung oben, kein Mischen mit normalem Compose innerhalb desselben Composables). - **WorkManager** für die periodische Hintergrundaktualisierung der Widget-Daten (kein sekündliches Polling, Systemgrenzen respektiert - siehe `docs/android-widget-guide.md`). - **App Links** (`autoVerify`, Intent-Filter für `/app/...`) zusätzlich zu einem Custom-Scheme `bensa://...` als Fallback ohne Domain-Verifizierung. - Öffnen der bestehenden PWA: **Custom Tabs** (externe, vom System verwaltete Browser-Ansicht) statt einer selbst gebauten WebView - siehe Abschnitt "Verworfene Alternativen". - Build: Gradle (Kotlin-DSL, `build.gradle.kts`), Ziel `compileSdk`/`targetSdk` entsprechend der lokal installierten, neuesten stabilen Plattform (`android-36`, siehe Baseline oben), `minSdk 26` (deckt die weit überwiegende Mehrheit aktiver Geräte ab und erlaubt moderne Glance-/Compose-APIs ohne übermäßige Rückwärtskompatibilitäts-Sonderfälle - endgültige Bestätigung des gewählten `minSdk` gegen die zum Build-Zeitpunkt aktuelle offizielle Verteilungsstatistik ist ein offener Punkt für den Nutzer, siehe `docs/android-build-and-test.md`). ### Apple - **Swift** + **SwiftUI** für die App-Hülle, **WidgetKit** (`TimelineProvider`) für Home-Screen- (small/medium/large) und, soweit vom Zielsystem unterstützt, Lock-Screen-/Smart-Stack-Widgets. - **App Group** (`group.eu.comhair.bensa` als Platzhalter-Bezeichner, siehe `docs/ios-app-architecture.md`) für den gemeinsamen letzten-gültigen-Datenstand zwischen App und Widget-Extension (`UserDefaults(suiteName:)`, ausschließlich der bereits nicht-sensible Widget-Snapshot - siehe `docs/native-security-and-privacy.md`). - Öffnen der PWA: **SFSafariViewController** (In-App-Safari-Ansicht, keine eigene WKWebView- Engine) - dieselbe Begründung wie bei Custom Tabs unter Android. - **Universal Links** (`apple-app-site-association`) vorbereitet, analog zu Android App Links **nicht als produktiv aktiviert** dokumentiert (fehlende Domain-Assoziation/Team-ID-Bindung in dieser Arbeitsumgebung, siehe `docs/native-deep-links.md`). - Kein Xcode-Projektformat-Build in dieser Umgebung möglich - stattdessen vollständige, copy-paste-fertige Swift-Package-/Xcode-Projektstruktur (Quelldateien + Anleitung), siehe `docs/ios-build-signing-and-test.md`. ## Verworfene Alternativen | Alternative | Warum verworfen | |---|---| | Flutter (plattformübergreifend) | Bereits in `ROADMAP.md`/`docs/mobile-api.md` als bewusst **nicht** gewählter Weg dokumentiert (Kurswechsel zur PWA, siehe Speicher-Notiz "BENSA Mobile-API/PWA-Plan"); Home-/Lock-Screen-Widgets sind zudem plattformspezifische natives Terrain, das Flutter nur über zusätzliche native Plugins abdeckt - kein Vorteil gegenüber einer schlanken nativen Hülle für genau diesen Zweck. | | Eigene WebView statt Custom Tabs/SFSafariViewController | Eine selbst eingebettete WebView dupliziert Cookie-/Service-Worker-/Update-Verhalten des Systembrowsers, den die PWA bereits nutzt, und wäre ein zusätzliches Sicherheits-/Wartungsrisiko (siehe `docs/native-security-and-privacy.md` "unsichere WebViews"). Custom Tabs/SFSafariViewController liefern dieselbe PWA zuverlässig, ohne die PWA-Funktionalität zu beschädigen. | | Trusted Web Activity (TWA) als alleinige Android-App-Hülle | TWA würde die App auf reines PWA-Hosting reduzieren und Glance-Home-Screen-Widgets (die zentrale Anforderung dieser Version) nicht ermöglichen - TWA und Widgets schließen sich nicht aus, aber die Kernanforderung "Home-/Lock-Screen-Widgets" erfordert ohnehin eine native App-Hülle, in der eine einfache Custom-Tab-Aktion genügt. | | Push-Zustellung direkt im Widget-Code (FCM/APNs-Handling in der Widget-Extension) | Widgets aktualisieren sich über das jeweilige Timeline-/WorkManager-System, nicht über eigene Push-Handler - native Push wird in der **App**, nicht im Widget, vorbereitet (siehe `docs/native-deep-links.md`/`docs/android-widget-guide.md` "kein sekündliches Polling"). | | Eigene Schwellenwerte/Severity-Logik in den Clients | Widerspricht Abschnitt 4 der Anforderung ("Die mobilen Clients dürfen keine eigenen widersprüchlichen Warnschwellen einführen") - beide Clients übernehmen ausschließlich `status.severity` aus der Widget-API. | ## Widget-Zustandsmodell (gemeinsames Vokabular) Siehe `docs/widget-api.md` für die vollständige Feldreferenz. Kurzfassung: - `severity`: `normal` < `information` < `notice` < `warning` < `severe` < `extreme`, zusätzlich `unavailable` (kein Snapshot vorhanden). Abgeleitet aus dem bestehenden Snapshot-`status` und ggf. durch die höchste **aktive amtliche** Warnstufe angehoben (nie abgesenkt) - siehe `scripts/lib/widget-view-lib.js` `raiseSeverityByOfficialWarnings`. - `dataFreshness`: `current` / `delayed` / `stale` / `unavailable` - dieselbe Kulanzlogik wie die bestehende PWA-Frischeanzeige (`pwa/js/pwa-data-lib.js` `describeFreshness`), auf ein maschinenlesbares Vokabular statt deutschsprachiger Anzeige-Labels abgebildet. - `condition`: stabile, maschinenlesbare ID (z. B. `partly-cloudy`, `thunderstorm`), abgeleitet aus dem bereits im Snapshot vorhandenen Anzeige-Icon; unbekannte/zukünftige Icons ergeben kontrolliert `unknown` statt eines Absturzes. ## Erforderliche Entwicklerkonten und Hardware (offene, vom Nutzer durchzuführende Schritte) | Voraussetzung | Status | |---|---| | Google Play Console Konto | **nicht vorhanden/nicht geprüft** - nur für eine spätere Store-Veröffentlichung nötig, nicht für den repository-seitigen Abschluss dieser Version. | | Apple Developer Program Mitgliedschaft | **nicht vorhanden/nicht geprüft** - nötig für Code Signing, Push-Zertifikate, App Links/Universal-Links-Team-ID-Bindung, Store-Veröffentlichung. | | Physisches Android-Gerät/Emulator für einen echten Geräte-/Widget-Test | in dieser Arbeitsumgebung **nicht durchgeführt** (siehe `docs/project-status-v1.9.7.md` "Reale Geräte-Gates"). | | Mac mit Xcode für einen echten iOS-Build/-Simulator-/Geräte-Test | in dieser Arbeitsumgebung **nicht vorhanden** - vollständige manuelle Anleitung in `docs/ios-build-signing-and-test.md`. | ## Aktuelle Einschränkungen dieser Version - Kein Xcode/macOS verfügbar: Die Apple-Seite bleibt repository-seitig vollständig (Quellcode, Projektstruktur, Tests-als-Vorlage, Dokumentation), aber **ungebaut/ungetestet** in dieser Umgebung. - Der Android-Teil **wird** in dieser Umgebung tatsächlich mit dem lokal vorhandenen Gradle-9.1.0 gebaut und getestet (siehe `docs/android-build-and-test.md` für das exakte Ergebnis) - das unterscheidet den Android-Build klar vom rein statischen Apple-Teil. - Kein reales Android-Gerät und kein reales iPhone in dieser Arbeitsumgebung - alle "auf echtem Gerät geprüft"-Aussagen bleiben explizit offen (siehe `docs/project-status-v1.9.7.md`). - Store-Veröffentlichung, Signierung und produktive App-Links/Universal-Links-Aktivierung sind bewusst **nicht** Teil dieser Version (siehe Anforderung Abschnitt 2/9/15). - **Update (v1.32.0):** ein eigener, von Google Play unabhängiger Downloadbereich (`/downloads/android/`) sowie ein App-seitiger Update-Check sind seit v1.32.0 implementiert und getestet (siehe [android-release-distribution.md](android-release-distribution.md)) - die Signierungsentscheidung selbst (dediziertes Beta-Zertifikat statt des aktuellen Debug-Keystores) bleibt weiterhin offen und ist bewusst dem Betreiber vorbehalten.