Wie Apple über den eingebauten GoogleAuthProvider (Browser-Flow über den Firebase-Auth-Handler, kein Zusatz-Paket). Gemeinsamer Provider-Handler im Login-Screen, l10n-Strings, iOS-URL-Scheme (Encoded App ID) für den Rückweg aus dem Browser, Doku Schritt 8. Der reaktive Haushalts-Bootstrap deckt den ersten Google-Login automatisch ab. Offen: Google-Provider in der Firebase-Konsole aktivieren + Gerätetest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
112 lines
4.1 KiB
Dart
112 lines
4.1 KiB
Dart
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<void> 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<void> 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<void> 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<void> signInWithGoogle() async {
|
|
await _auth.signInWithProvider(GoogleAuthProvider());
|
|
}
|
|
|
|
Future<void>? _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<void> 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<void> _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<void> signOut() => _auth.signOut();
|
|
}
|
|
|
|
final authRepositoryProvider = Provider<AuthRepository>((ref) {
|
|
return AuthRepository(
|
|
ref.watch(firebaseAuthProvider),
|
|
ref.watch(firestoreProvider),
|
|
);
|
|
});
|