iOS/TestFlight: App-Check-Release-Provider, App-Icon, Datenschutz-Hosting

- main.dart: App-Check-Provider per kReleaseMode umgeschaltet (Release =
  PlayIntegrity/AppAttest, Debug = Debug-Provider). Nötig, da die Functions
  App Check erzwingen — ein Release-Build mit Debug-Provider würde auf
  fremden Geräten bei jedem Function-Aufruf scheitern.
- App-Icon aus assets/icon/icon.png via flutter_launcher_icons generiert
  (iOS-Set + Android-Mipmaps).
- docs/legal/datenschutz.html: Platzhalter ausgefüllt, interner Hinweis raus.
- firebase.json: hosting-Block (public: docs/legal, Redirect / -> /datenschutz).
  URL: https://leaf-it-to-me-app.web.app/datenschutz
- Doku: firebase-einrichtung.md Schritt 9/10 + Hosting fortgeschrieben,
  handoff.md aktualisiert (iOS/TestFlight abgeschlossen, nur noch Android offen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMRhL16hB14xcfef1qez7N
This commit is contained in:
cschlaefke 2026-09-08 20:37:49 +02:00
parent 8d2b10b9ee
commit 9aa49c641d
35 changed files with 104 additions and 168 deletions

3
.gitignore vendored
View file

@ -33,6 +33,9 @@ migrate_working_dir/
/build/ /build/
/coverage/ /coverage/
# Firebase CLI deploy cache
.firebase/
# Symbolication related # Symbolication related
app.*.symbols app.*.symbols

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 7 KiB

BIN
assets/icon/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -196,22 +196,30 @@ Zwei getrennte Schritte, **nacheinander**, nicht gleichzeitig (sonst sperrt man
- **Metriken abwarten:** In der Console unter *App Check → APIs* prüfen, dass für die genutzten Functions „Verified requests" ankommen (kann nach einem frischen Build ein paar Minuten dauern). - **Metriken abwarten:** In der Console unter *App Check → APIs* prüfen, dass für die genutzten Functions „Verified requests" ankommen (kann nach einem frischen Build ein paar Minuten dauern).
2. **Erst wenn Schritt 1 bestätigt ist:** In `functions/src/index.ts` und `functions/src/household.ts` bei den `onCall(...)`-Funktionen (`identifyPlant`, `diagnosePlant`, `analyzeLocation`, `assessPlantFit`, `joinHousehold`, `leaveHousehold`, `removeMember`) die Option `enforceAppCheck: true` ergänzen und neu deployen. Danach lehnen die Functions Aufrufe ohne gültiges App-Check-Token ab. 2. **Erst wenn Schritt 1 bestätigt ist:** In `functions/src/index.ts` und `functions/src/household.ts` bei den `onCall(...)`-Funktionen (`identifyPlant`, `diagnosePlant`, `analyzeLocation`, `assessPlantFit`, `joinHousehold`, `leaveHousehold`, `removeMember`) die Option `enforceAppCheck: true` ergänzen und neu deployen. Danach lehnen die Functions Aufrufe ohne gültiges App-Check-Token ab.
Für einen echten Release-Build sollte `main.dart` auf die Produktiv-Provider umgestellt werden (`AndroidProvider.playIntegrity` / `AppleProvider.appAttest` statt der Debug-Provider) — bis dahin reicht der Debug-Provider für eigene Testgeräte. **Erledigt (2026-09-08):** `main.dart` schaltet die App-Check-Provider jetzt per `kReleaseMode` um — Release-Builds (TestFlight/Play) nutzen `AndroidPlayIntegrityProvider` / `AppleAppAttestProvider`, Debug-Builds weiter die Debug-Provider mit den hinterlegten Debug-Tokens. Wichtig, weil die Functions App Check **erzwingen**: Ein Release-Build mit Debug-Provider würde auf fremden Geräten bei jedem Function-Aufruf (Pflanze anlegen, Diagnose, Haushalt beitreten) scheitern. Auf dem ersten TestFlight-Build bestätigt.
## Schritt 10: TestFlight einrichten (iOS) ## Schritt 10: TestFlight einrichten (iOS) — abgeschlossen 2026-09-08
Ziel: die App auf eigenen/Familien-Geräten installierbar machen, **ohne** öffentlichen Store-Eintrag oder Apple-Review (interne Tester werden nicht geprüft). Ziel: die App auf eigenen/Familien-Geräten installierbar machen, **ohne** öffentlichen Store-Eintrag oder Apple-Review (interne Tester werden nicht geprüft). Erster Build (1.0.0 (1)) am 2026-09-08 hochgeladen, in TestFlight „Bereit zum Testen", auf dem iPhone installiert und nutzbar.
1. **App Store Connect** (<https://appstoreconnect.apple.com>, mit dem aktiven Apple-Developer-Konto) → *Meine Apps* → „+" → **Neue App**: 1. **App Store Connect** (<https://appstoreconnect.apple.com>, mit dem aktiven Apple-Developer-Konto) → *Apps* → „+" → **Neue App**:
- Plattform iOS, Name „LeafItToMe" (falls der Name schon vergeben ist, z. B. „LeafItToMe Pflanzenpflege" versuchen), Bundle-ID `dev.leafittome.app` auswählen (aus der Liste, die Xcode/das Developer-Portal bereits kennt), SKU frei wählbar (z. B. `leafittome-001`). - Plattform iOS, Name „LeafItToMe" (falls vergeben: „LeafItToMe Pflanzenpflege"), primäre Sprache Deutsch, Bundle-ID `dev.leafittome.app` aus der Liste wählen, SKU frei (z. B. `leafittome-001`), voller Zugriff.
2. **App-Informationen → Datenschutzerklärung-URL:** die gehostete Datenschutzerklärung eintragen (Entwurf liegt in `docs/legal/datenschutz.html`, Hosting-Vorschlag s. u.). - Screenshots, Beschreibung, Preis, Altersfreigabe sind für **interne** TestFlight-Tests nicht nötig — nur die Datenschutz-Angaben unten.
3. **App-Datenschutz (Privacy-Fragebogen):** Apple fragt nach Datentypen. Antworten passend zu `docs/legal/datenschutz.html`: 2. **App-Informationen → Datenschutzrichtlinie-URL:** `https://leaf-it-to-me-app.web.app/datenschutz` (Hosting via Firebase Hosting, s. u.).
- *Kontaktdaten* → E-Mail-Adresse: ja, verknüpft mit Nutzer, für App-Funktionalität (Konto). 3. **App-Datenschutz (Privacy-Fragebogen):** Apple fragt Datentyp für Datentyp ab. Antworten passend zu `docs/legal/datenschutz.html` — jeweils *erhoben: ja*, *mit Nutzer verknüpft: ja*, *Tracking: nein*, Zweck *App-Funktionalität*:
- *Fotos oder Videos* → ja, verknüpft mit Nutzer, für App-Funktionalität (Pflanzen-/Standort-Erkennung). - *Kontaktdaten → E-Mail-Adresse* (Konto).
- *Nutzerinhalte* (sonstige, z. B. Pflanzendaten) → ja, verknüpft mit Nutzer, für App-Funktionalität. - *Benutzerinhalte → Fotos oder Videos* (Pflanzen-/Standort-Erkennung).
- Kein Tracking, keine Daten zu Werbezwecken, keine Analyse-/Diagnosedaten (kein Analytics/Crashlytics im Projekt). - *Benutzerinhalte → Andere Benutzerinhalte* (Pflanzen-/Haushaltsdaten).
4. **Build hochladen:** `flutter build ipa` (Release-Signing über Xcode „Automatically manage signing"), dann per Xcode Organizer oder Transporter-App hochladen. - *Kennungen → Nutzer-ID* (Apple/Google-Login-Konto-ID).
5. **TestFlight → Interne Tests:** neue Testgruppe anlegen (z. B. „Haushalt"), Tester per Apple-ID-E-Mail hinzufügen — die genannten Personen müssen als *Nutzer* im App-Store-Connect-Team stehen (bis zu 100 interne Tester, kein Review nötig, Build ist nach ein paar Minuten Verarbeitungszeit verfügbar). - Alles andere: nicht erhoben. Kein Tracking, keine Werbedaten, keine Analyse-/Diagnosedaten (kein Analytics/Crashlytics im Projekt).
4. **Distribution-Zertifikat anlegen (einmalig, sonst scheitert der Upload!):** Xcode → *Settings → Accounts* → Apple-ID + Team markieren → **„Manage Certificates…"** → „+" → **„Apple Distribution"**. Ohne dieses Zertifikat gibt es im Schlüsselbund nur „Apple Development", und der Upload wird mit *„Invalid Signature … not signed with a distribution certificate"* (Code 90035) für jede Framework-Datei abgelehnt. Kontrolle: `security find-identity -v -p codesigning` muss eine Zeile „Apple Distribution: …" zeigen.
5. **Build als Archiv aus Xcode bauen** (nicht `flutter build ipa` über die CLI — die konnte das Distribution-Zertifikat headless nicht ziehen und fiel auf Development-Signing zurück):
- `flutter build ios --config-only --release`
- `ios/Runner.xcworkspace` in Xcode öffnen → Geräte-Dropdown **„Any iOS Device (arm64)"** → Menü **Product → Archive**.
- Der Organizer öffnet sich → **„Distribute App" → App Store Connect → Upload** → Optionen auf Standard.
- **Harmlose Meldungen beim Upload:** „Upload Symbols Failed … did not include a dSYM for FirebaseFirestoreInternal / grpc / absl / RecaptchaEnterpriseSDK …". Firebase/gRPC liefern ihre Frameworks vorkompiliert ohne Debug-Symbole; das betrifft nur die Crash-Symbolisierung (ohne Crashlytics irrelevant). Der Build gilt trotzdem als angenommen.
6. **TestFlight → Interne Tests:** neue Testgruppe (z. B. „Haushalt"), Tester per Apple-ID-E-Mail — die Personen müssen unter *Benutzer und Zugriff* im Team stehen (Rolle „Entwickler"/„Kundenservice" reicht). Bis zu 100 interne Tester, kein Review, Build nach wenigen Minuten Verarbeitung verfügbar.
7. **Export-Compliance:** taucht dank `ITSAppUsesNonExemptEncryption = false` in `Info.plist` nicht als Nachfrage auf.
## Schritt 11: Play Internal Testing einrichten (Android) ## Schritt 11: Play Internal Testing einrichten (Android)
@ -234,7 +242,29 @@ Ziel: dieselbe „ohne öffentlichen Store-Eintrag"-Stufe für Android.
## Hosting: Datenschutzerklärung öffentlich erreichbar machen ## Hosting: Datenschutzerklärung öffentlich erreichbar machen
`docs/legal/datenschutz.html` ist bewusst eine einzelne, eigenständige HTML-Datei ohne Build-Schritt — sie kann 1:1 ausgeliefert werden. Chris nutzt bereits Cloudflare vor einer eigenen Domain, daher läuft das Hosting so: `docs/legal/datenschutz.html` ist bewusst eine einzelne, eigenständige HTML-Datei ohne Build-Schritt — sie kann 1:1 ausgeliefert werden.
### Genutzte Lösung: Firebase Hosting (2026-09-08)
Kein Zusatz-Setup, kostenlos (die ~4-KB-Seite bleibt weit im Gratis-Kontingent: 10 GB Speicher + 10 GB Transfer/Monat). In `firebase.json` steht ein `hosting`-Block:
```json
"hosting": {
"public": "docs/legal",
"cleanUrls": true,
"ignore": ["firebase.json", "**/.*"],
"redirects": [{ "source": "/", "destination": "/datenschutz", "type": 302 }]
}
```
- `public: "docs/legal"` deployt die Seite direkt aus dem Repo — keine Kopie zum Synchronhalten. (Vorsicht: alles, was künftig in `docs/legal/` landet, wäre damit öffentlich.)
- Deploy: `firebase deploy --only hosting --project leaf-it-to-me-app`
- URL für App Store Connect / Play Console: **`https://leaf-it-to-me-app.web.app/datenschutz`** (die nackte Domain leitet per Redirect dorthin).
- Nach jeder Änderung an `docs/legal/datenschutz.html` erneut `firebase deploy --only hosting`.
### Alternative: eigene Domain über Cloudflare + Hetzner-VPS
Falls die Datenschutzerklärung später unter eigener Domain laufen soll — Chris nutzt bereits Cloudflare vor einer eigenen Domain:
1. **Cloudflare-DNS:** Subdomain anlegen, z. B. `datenschutz.<domain>.tld` → A-Record auf die IP des Hetzner-VPS. Proxy (orange Wolke) aktiviert lassen. 1. **Cloudflare-DNS:** Subdomain anlegen, z. B. `datenschutz.<domain>.tld` → A-Record auf die IP des Hetzner-VPS. Proxy (orange Wolke) aktiviert lassen.
2. **SSL/TLS-Modus auf „Full (strict)"** stellen (Cloudflare → SSL/TLS → Übersicht) — **nicht** „Flexible", sonst ist die Strecke Cloudflare↔Server unverschlüsselt. Dafür ein **Origin Certificate** erzeugen (Cloudflare → SSL/TLS → Origin Server → Zertifikat erstellen; 15 Jahre gültig, kein Renewal wie bei Let's Encrypt nötig) und auf dem Server ablegen. 2. **SSL/TLS-Modus auf „Full (strict)"** stellen (Cloudflare → SSL/TLS → Übersicht) — **nicht** „Flexible", sonst ist die Strecke Cloudflare↔Server unverschlüsselt. Dafür ein **Origin Certificate** erzeugen (Cloudflare → SSL/TLS → Origin Server → Zertifikat erstellen; 15 Jahre gültig, kein Renewal wie bei Let's Encrypt nötig) und auf dem Server ablegen.

View file

@ -1,6 +1,6 @@
# Handoff: LeafItToMe (Pflanzenpflege-App) # Handoff: LeafItToMe (Pflanzenpflege-App)
Stand: 2026-08-20 (Session-Ende). Sprache mit dem Nutzer (Chris): **Deutsch**. Stand: 2026-09-08 (Session-Ende). Sprache mit dem Nutzer (Chris): **Deutsch**.
## Projekt in einem Satz ## Projekt in einem Satz
@ -30,10 +30,20 @@ Private Flutter-App (iOS+Android) zur Pflanzenpflege für Haushalt + Pflanzen-Si
## Offen / Nächstes ## Offen / Nächstes
### 1. App-Store-Vorbereitung (läuft, 2026-08-20 begonnen) ### 1. App-Store-Vorbereitung (iOS/TestFlight abgeschlossen 2026-09-08; Android offen)
Ziel fürs Erste: **nur TestFlight + Play-Internal-Testing**, kein öffentlicher Store-Eintrag (mit Chris abgestimmt — spart Screenshots/Store-Texte/Altersfreigabe für später). Bundle-ID bleibt bewusst `dev.leafittome.app` (eigene Domain steht noch aus, ist aber kein Blocker für TestFlight/Internal-Testing). Ziel fürs Erste: **nur TestFlight + Play-Internal-Testing**, kein öffentlicher Store-Eintrag (mit Chris abgestimmt — spart Screenshots/Store-Texte/Altersfreigabe für später). Bundle-ID bleibt bewusst `dev.leafittome.app` (eigene Domain steht noch aus, ist aber kein Blocker für TestFlight/Internal-Testing).
**iOS / TestFlight: fertig (2026-09-08).** Erster Build `1.0.0 (1)` hochgeladen, in TestFlight „Bereit zum Testen", auf dem iPhone installiert und nutzbar. Dabei erledigt:
- `lib/main.dart`: App-Check-Provider per `kReleaseMode` umgeschaltet — Release nutzt `AndroidPlayIntegrityProvider` / `AppleAppAttestProvider`, Debug weiter Debug-Provider. Nötig, weil die Functions App Check erzwingen.
- App-Icon: `assets/icon/icon.png` (1024×1024, ohne Alpha) + `dart run flutter_launcher_icons` → iOS-/Android-Icon-Sätze generiert. (Tool-Macke v0.14.4: verhunzt `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS` in `project.pbxproj` von `YES` auf `AppIcon` — danach zurücksetzen.)
- Datenschutzerklärung: Platzhalter in `docs/legal/datenschutz.html` ausgefüllt (Christopher Schläfke / cschlaefke@live.com / 8. September 2026), interner Hinweiskasten raus. Gehostet via **Firebase Hosting** (`hosting`-Block in `firebase.json`, `public: "docs/legal"`), URL **`https://leaf-it-to-me-app.web.app/datenschutz`** — in App Store Connect eingetragen. Deploy: `firebase deploy --only hosting`.
- App Store Connect: App-Eintrag + App-Datenschutz-Fragebogen ausgefüllt (E-Mail, Fotos, Nutzerinhalte, Nutzer-ID — alle verknüpft, kein Tracking).
- **Stolperfalle (teuer erkauft):** `flutter build ipa` über die CLI signiert headless nur mit „Apple Development" → App-Store-Upload lehnt mit „Invalid Signature / Code 90035" jede Framework-Datei ab. Fix: in Xcode → Settings → Accounts → Manage Certificates → „+" → **Apple Distribution** anlegen, dann **aus Xcode archivieren** (Product → Archive) statt CLI. Details in `firebase-einrichtung.md` Schritt 10.
- Die „Upload Symbols Failed / dSYM"-Meldungen für Firebase/gRPC/absl/Recaptcha beim Upload sind **harmlos** (vorkompilierte Frameworks ohne Debug-Symbole, nur Crash-Symbolik).
**Offene Kleinigkeit iOS:** Launch-Screen ist noch der weiße Flutter-Platzhalter (Warnung beim Build, kein Blocker) → Backlog.
**App Check: vollständig abgeschlossen** (2026-08-20). Play Integrity (Android) + App Attest (iOS) in der Firebase-Console registriert, Debug-Token hinterlegt, „Verified requests" für Cloud Functions auf beiden Plattformen bestätigt, `enforceAppCheck: true` auf allen 7 `onCall`-Functions deployt (Commit `49b7f44`) und per Gerätetest bestätigt (Pflanze anlegen/Diagnose funktionieren normal). **Hinweis für später:** Unverified Requests bei Firestore/Storage in der Console sind erwartet/harmlos — dafür wurde nie Enforcement eingerichtet, nur für die Functions. Ebenso die einmalige iOS-Kaltstart-Meldung „App not registered … exchangeDeviceCheckToken" — bekannte, folgenlose Plugin-Race, siehe `firebase-einrichtung.md` Schritt 9. **App Check: vollständig abgeschlossen** (2026-08-20). Play Integrity (Android) + App Attest (iOS) in der Firebase-Console registriert, Debug-Token hinterlegt, „Verified requests" für Cloud Functions auf beiden Plattformen bestätigt, `enforceAppCheck: true` auf allen 7 `onCall`-Functions deployt (Commit `49b7f44`) und per Gerätetest bestätigt (Pflanze anlegen/Diagnose funktionieren normal). **Hinweis für später:** Unverified Requests bei Firestore/Storage in der Console sind erwartet/harmlos — dafür wurde nie Enforcement eingerichtet, nur für die Functions. Ebenso die einmalige iOS-Kaltstart-Meldung „App not registered … exchangeDeviceCheckToken" — bekannte, folgenlose Plugin-Race, siehe `firebase-einrichtung.md` Schritt 9.
**Weiteres Code-seitig erledigt:** **Weiteres Code-seitig erledigt:**
@ -44,12 +54,12 @@ Ziel fürs Erste: **nur TestFlight + Play-Internal-Testing**, kein öffentlicher
- Nebenbei behoben: SPM-Versionskonflikt durch `firebase_app_check`-Einführung (inkonsistente Firebase-Paket-Minor-Versionen verhinderten den iOS-Build) — Commit `248f3be`. - Nebenbei behoben: SPM-Versionskonflikt durch `firebase_app_check`-Einführung (inkonsistente Firebase-Paket-Minor-Versionen verhinderten den iOS-Build) — Commit `248f3be`.
- Neue Doku-Schritte in `firebase-einrichtung.md`: **Schritt 9** (App Check, jetzt komplett abgeschlossen), **Schritt 10** (TestFlight einrichten), **Schritt 11** (Play Internal Testing einrichten) — inkl. Textvorlagen für Apples App-Privacy-Fragebogen und Googles Data-Safety-Formular. - Neue Doku-Schritte in `firebase-einrichtung.md`: **Schritt 9** (App Check, jetzt komplett abgeschlossen), **Schritt 10** (TestFlight einrichten), **Schritt 11** (Play Internal Testing einrichten) — inkl. Textvorlagen für Apples App-Privacy-Fragebogen und Googles Data-Safety-Formular.
**Noch offen (Chris' nächste Schritte, alle in `firebase-einrichtung.md` Schritt 1011 dokumentiert):** **Noch offen — nur noch Android (Play Internal Testing), dokumentiert in `firebase-einrichtung.md` Schritt 11:**
- App-Icon: 1024×1024-Design liefern → unter `assets/icon/icon.png` ablegen → `dart run flutter_launcher_icons`. - Android-Upload-Key erzeugen (`keytool`, Schritt 11) und sofort sichern (Passwort-Manager + Backup außerhalb des Repos).
- Datenschutzerklärung: Platzhalter ausfüllen + über Cloudflare/Hetzner hosten (Anleitung s. o.). - `android/key.properties` lokal anlegen (Vorlage `android/key.properties.example`).
- Android-Upload-Key erzeugen (`keytool`, Schritt 11) und sichern. - Release-Fingerabdruck (SHA-1/SHA-256 des Upload-Keys) in Firebase hinterlegen (`firebase apps:android:sha:create`) — für App Check / Play Integrity im Release.
- App Store Connect: App-Eintrag anlegen, Privacy-URL + App-Privacy-Fragebogen ausfüllen, Build hochladen, interne TestFlight-Gruppe + Tester. - `flutter build appbundle --release` → Play Console: App anlegen, Datenschutz-URL (`https://leaf-it-to-me-app.web.app/datenschutz`) + Data-Safety-Formular + Content-Rating, internen Test-Track + Tester.
- Play Console: App anlegen, Datenschutz-URL + Data-Safety-Formular + Content-Rating ausfüllen, internen Test-Track + Tester. - App-Icon ist bereits generiert (auch Android-Mipmaps), Datenschutzerklärung ist bereits gehostet — beides gilt für Android mit.
### 2. Backlog ### 2. Backlog
@ -69,6 +79,8 @@ Button „KI-Zweitmeinung" bei unbefriedigendem PlantNet-Ergebnis; Push-Anzeige
- iOS-Fehler „requires minimum platform version 15.0 … but this target supports 13.0": nach `flutter clean` oder beim Bauen direkt aus Xcode einmal `flutter build ios --config-only` ausführen (SwiftPM-Paket wird bei `pub get` mit iOS 13 erzeugt). - iOS-Fehler „requires minimum platform version 15.0 … but this target supports 13.0": nach `flutter clean` oder beim Bauen direkt aus Xcode einmal `flutter build ios --config-only` ausführen (SwiftPM-Paket wird bei `pub get` mit iOS 13 erzeugt).
- In `.arb`-Dateien UND in deutschen Texten innerhalb von TS-Strings (Function-Prompts!) **typografische Anführungszeichen „…“** verwenden — gerade `"` brechen das JSON bzw. den TS-String. - In `.arb`-Dateien UND in deutschen Texten innerhalb von TS-Strings (Function-Prompts!) **typografische Anführungszeichen „…“** verwenden — gerade `"` brechen das JSON bzw. den TS-String.
- Die Bash-Session wechselt das Arbeitsverzeichnis **nicht zurück**: nach `cd functions && npm run build` vor Flutter-Kommandos wieder explizit ins Projekt-Root. - Die Bash-Session wechselt das Arbeitsverzeichnis **nicht zurück**: nach `cd functions && npm run build` vor Flutter-Kommandos wieder explizit ins Projekt-Root.
- **iOS-App-Store-Upload braucht ein „Apple Distribution"-Zertifikat.** `flutter build ipa` über die CLI zieht es headless nicht und signiert nur mit „Apple Development" → Upload scheitert mit „Invalid Signature / Code 90035". Zuerst in Xcode → Settings → Accounts → Manage Certificates → „+" → Apple Distribution anlegen (`security find-identity -v -p codesigning` muss die Zeile zeigen), dann **aus Xcode archivieren** (Product → Archive), nicht per CLI.
- `flutter_launcher_icons` v0.14.4 schreibt beim Generieren `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon` (statt `YES`) in `ios/Runner.xcodeproj/project.pbxproj` — danach `git checkout` auf die Datei, die generierten Icons bleiben.
- Nutzer wünscht zu jedem Backend-Baustein **begleitende deutsche Doku** in `docs/` (keine Firebase-Vorerfahrung). - Nutzer wünscht zu jedem Backend-Baustein **begleitende deutsche Doku** in `docs/` (keine Firebase-Vorerfahrung).
## Arbeitsabläufe ## Arbeitsabläufe

View file

@ -27,20 +27,12 @@
<body> <body>
<h1>Datenschutzerklärung LeafItToMe</h1> <h1>Datenschutzerklärung LeafItToMe</h1>
<p><em>Stand: [Datum einsetzen]</em></p> <p><em>Stand: 8. September 2026</em></p>
<p class="hint">
<strong>Hinweis für Chris (vor Veröffentlichung entfernen):</strong>
Platzhalter <code>[Name]</code>, <code>[E-Mail-Adresse]</code> und <code>[Datum]</code>
ausfüllen. Diese Seite ist als öffentlich erreichbare, eigenständige HTML-Datei gedacht
(Hosting-Vorschlag in <code>docs/firebase-einrichtung.md</code>), auf die im Apple/Google-
Account bei „Datenschutzerklärung" verlinkt wird.
</p>
<h2>Verantwortlicher</h2> <h2>Verantwortlicher</h2>
<p> <p>
[Name]<br> Christopher Schläfke<br>
E-Mail: [E-Mail-Adresse] E-Mail: cschlaefke@live.com
</p> </p>
<p> <p>
LeafItToMe ist eine private, nicht kommerzielle App zur Pflanzenpflege für einen LeafItToMe ist eine private, nicht kommerzielle App zur Pflanzenpflege für einen
@ -105,7 +97,7 @@
<p> <p>
Daten werden gespeichert, solange dein Konto bzw. der Haushalt besteht. Daten werden gespeichert, solange dein Konto bzw. der Haushalt besteht.
Zur Löschung deines Kontos oder einzelner Daten wende dich an Zur Löschung deines Kontos oder einzelner Daten wende dich an
[E-Mail-Adresse]. cschlaefke@live.com.
</p> </p>
<h2>Deine Rechte</h2> <h2>Deine Rechte</h2>
@ -113,7 +105,7 @@
Du hast das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Du hast das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der
Verarbeitung, Datenübertragbarkeit und Widerspruch gemäß Art. 1521 DSGVO Verarbeitung, Datenübertragbarkeit und Widerspruch gemäß Art. 1521 DSGVO
sowie ein Beschwerderecht bei einer Datenschutzaufsichtsbehörde. Wende dich sowie ein Beschwerderecht bei einer Datenschutzaufsichtsbehörde. Wende dich
dazu an [E-Mail-Adresse]. dazu an cschlaefke@live.com.
</p> </p>
</body> </body>

View file

@ -10,6 +10,18 @@
"source": "functions", "source": "functions",
"predeploy": ["npm --prefix functions run build"] "predeploy": ["npm --prefix functions run build"]
}, },
"hosting": {
"public": "docs/legal",
"cleanUrls": true,
"ignore": ["firebase.json", "**/.*"],
"redirects": [
{
"source": "/",
"destination": "/datenschutz",
"type": 302
}
]
},
"flutter": { "flutter": {
"platforms": { "platforms": {
"android": { "android": {

View file

@ -50,8 +50,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git", "location" : "https://github.com/google/GoogleDataTransport.git",
"state" : { "state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9", "revision" : "ba3358d3c3dbae8ef230b58a46b97ad65e84e974",
"version" : "10.1.0" "version" : "10.1.1"
} }
}, },
{ {
@ -59,8 +59,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git", "location" : "https://github.com/google/GoogleUtilities.git",
"state" : { "state" : {
"revision" : "9f183ae842be978784f2963a343682e0c46d8fb3", "revision" : "92c8f6dc3ac375d6febdfcb3db68bc3d10633db3",
"version" : "8.1.2" "version" : "8.1.3"
} }
}, },
{ {
@ -77,8 +77,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git", "location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : { "state" : {
"revision" : "c0ac7575d70050c2973ba2318bd5af47f8e8153a", "revision" : "724a52eea6329b7e12d3ad8300d76ca9f3895fcc",
"version" : "5.3.0" "version" : "5.3.1"
} }
}, },
{ {

View file

@ -1,122 +1 @@
{ {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -1,5 +1,6 @@
import 'package:firebase_app_check/firebase_app_check.dart'; import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -13,12 +14,19 @@ Future<void> main() async {
await Firebase.initializeApp( await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform, options: DefaultFirebaseOptions.currentPlatform,
); );
// Debug-Provider, bis Play Integrity (Android) / App Attest (iOS) in der // Release-Builds (TestFlight/Play): echte Attestierung über Play Integrity
// Firebase-Console registriert sind (docs/firebase-einrichtung.md Schritt 9). // (Android) bzw. App Attest (iOS). Debug-Builds nutzen weiter den
// Die Functions erzwingen App Check noch nicht reines Vorbereiten. // Debug-Provider mit den in der Firebase-Console hinterlegten Debug-Tokens.
// Die onCall-Functions erzwingen App Check (enforceAppCheck: true), deshalb
// muss ein Release-Build zwingend einen echten Provider verwenden.
// Siehe docs/firebase-einrichtung.md Schritt 9.
await FirebaseAppCheck.instance.activate( await FirebaseAppCheck.instance.activate(
providerAndroid: AndroidDebugProvider(), providerAndroid: kReleaseMode
providerApple: AppleDebugProvider(), ? const AndroidPlayIntegrityProvider()
: AndroidDebugProvider(),
providerApple: kReleaseMode
? const AppleAppAttestProvider()
: AppleDebugProvider(),
); );
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
runApp( runApp(