import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/firebase/firebase_providers.dart'; /// Anmeldung, Registrierung und der Haushalts-Bootstrap. class AuthRepository { AuthRepository(this._auth, this._firestore); final FirebaseAuth _auth; final FirebaseFirestore _firestore; Future signIn({required String email, required String password}) async { await _auth.signInWithEmailAndPassword(email: email, password: password); } /// Registrierung: legt den Auth-Nutzer an und danach Profil + Haushalt. Future signUp({required String email, required String password}) async { final credential = await _auth.createUserWithEmailAndPassword( email: email, password: password, ); await ensureHouseholdBootstrap(credential.user!.uid, email); } /// Anmeldung mit Apple. Nativer Dialog auf iOS, Web-Flow (über den /// Firebase-Auth-Handler) auf Android — beides über den eingebauten /// [AppleAuthProvider], daher ohne Zusatz-Paket. /// /// Der Haushalts-Bootstrap hängt bewusst NICHT mehr hier dran: sobald /// signInWithProvider zurückkehrt, feuert authStateChanges und der Router /// leitet in die App weiter — ein danach geworfener Bootstrap-Fehler wäre /// auf dem Login-Screen unsichtbar und ließe den Nutzer ohne Haushalt /// zurück. Stattdessen übernimmt der reaktive householdBootstrapProvider. Future signInWithApple() async { final provider = AppleAuthProvider() ..addScope('email') ..addScope('name'); await _auth.signInWithProvider(provider); } /// Anmeldung mit Google — wie bei Apple über den eingebauten /// [GoogleAuthProvider] (Browser-Flow über den Firebase-Auth-Handler, /// kein Zusatz-Paket). Der Haushalts-Bootstrap läuft auch hier reaktiv /// über den householdBootstrapProvider. Future signInWithGoogle() async { await _auth.signInWithProvider(GoogleAuthProvider()); } Future? _bootstrapInFlight; /// Legt Profil-Dokument und einen eigenen Haushalt an, falls für [uid] /// noch kein Haushalt existiert. Idempotent und gegen parallele Aufrufe geschützt /// (Registrierung und reaktiver Bootstrap können sich überlappen) — /// ein bestehender Haushalt wird nie überschrieben. Future ensureHouseholdBootstrap(String uid, String? email) { return _bootstrapInFlight ??= _bootstrapHouseholdIfNeeded(uid, email).whenComplete(() { _bootstrapInFlight = null; }); } /// [email] kann bei „E-Mail verbergen“ die Apple-Relay-Adresse sein. /// /// Maßstab ist das Feld `householdId`, nicht die bloße Existenz des /// users-Dokuments: Die Push-Registrierung legt das Dokument nebenbei an /// (fcmTokens/timezone, merge), ohne dass ein Haushalt existiert. Future _bootstrapHouseholdIfNeeded(String uid, String? email) async { final userRef = _firestore.collection('users').doc(uid); final existing = await userRef.get(); if (existing.data()?['householdId'] != null) return; final safeEmail = email ?? ''; final householdRef = _firestore.collection('households').doc(); final batch = _firestore.batch(); batch.set(householdRef, { 'name': 'Mein Haushalt', 'ownerUid': uid, 'memberUids': [uid], 'members': { uid: { 'role': 'member', 'email': safeEmail, 'joinedAt': FieldValue.serverTimestamp(), }, }, 'createdAt': FieldValue.serverTimestamp(), }); // merge: von der Push-Registrierung bereits geschriebene Felder // (fcmTokens, timezone, reminderTime) bleiben erhalten. batch.set( userRef, { 'email': safeEmail, 'householdId': householdRef.id, 'createdAt': FieldValue.serverTimestamp(), }, SetOptions(merge: true), ); await batch.commit(); } Future signOut() => _auth.signOut(); } final authRepositoryProvider = Provider((ref) { return AuthRepository( ref.watch(firebaseAuthProvider), ref.watch(firestoreProvider), ); });