import 'dart:math'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_functions/cloud_functions.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/firebase/firebase_providers.dart'; import '../domain/household.dart'; /// Die ID des **aktiven** Haushalts (aus users/{uid}.householdId). /// /// Man kann Mitglied mehrerer Haushalte sein ([myHouseholdsProvider]); /// dieser Zeiger bestimmt, welcher davon gerade in der App angezeigt wird. /// null, solange niemand angemeldet ist oder das Profil noch lädt. /// Alle Daten-Provider (Pflanzen, Stellplätze) hängen hieran. final householdIdProvider = StreamProvider((ref) { final user = ref.watch(authStateProvider).value; if (user == null) return Stream.value(null); return ref .watch(firestoreProvider) .collection('users') .doc(user.uid) .snapshots() .map((snapshot) => snapshot.data()?['householdId'] as String?); }); Household _householdFromData(String id, Map data) { final membersMap = (data['members'] as Map?) ?? {}; final memberUids = (data['memberUids'] as List?) ?? []; return Household( id: id, name: data['name'] as String? ?? 'Haushalt', members: [ for (final uid in memberUids.cast()) HouseholdMember( uid: uid, role: ((membersMap[uid] as Map?)?['role'] as String?) == 'sitter' ? HouseholdRole.sitter : HouseholdRole.member, email: (membersMap[uid] as Map?)?['email'] as String? ?? '', ), ], ); } /// Der aktive Haushalt inkl. Mitgliedern und Rollen, live aus Firestore. final householdProvider = StreamProvider((ref) { final householdId = ref.watch(householdIdProvider).value; if (householdId == null) return Stream.value(null); return ref .watch(firestoreProvider) .collection('households') .doc(householdId) .snapshots() .map((snapshot) { final data = snapshot.data(); if (data == null) return null; return _householdFromData(snapshot.id, data); }); }); /// Alle Haushalte, in denen der angemeldete Nutzer Mitglied ist. /// /// Quelle der Wahrheit ist das Feld `memberUids` der Haushalte selbst – /// wer per Einladung beitritt, bleibt in seinen bisherigen Haushalten /// Mitglied und kann jederzeit zurückwechseln. final myHouseholdsProvider = StreamProvider>((ref) { final user = ref.watch(authStateProvider).value; if (user == null) return Stream.value(const []); return ref .watch(firestoreProvider) .collection('households') .where('memberUids', arrayContains: user.uid) .snapshots() .map((snapshot) { final households = [ for (final doc in snapshot.docs) _householdFromData(doc.id, doc.data()), ]..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); return households; }); }); /// Rolle des angemeldeten Nutzers im aktuellen Haushalt. final myRoleProvider = Provider((ref) { final household = ref.watch(householdProvider).value; final uid = ref.watch(authStateProvider).value?.uid; if (household == null || uid == null) return HouseholdRole.member; return household.roleOf(uid); }); class HouseholdRepository { HouseholdRepository(this._firestore, this._functionsGetter, this._uidGetter); final FirebaseFirestore _firestore; // Lazy, damit Widget-Tests ohne initialisiertes Firebase auskommen – // FirebaseFunctions.instanceFor wirft sonst schon beim Erzeugen. final FirebaseFunctions Function() _functionsGetter; final String? Function() _uidGetter; static const _codeAlphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; /// Erzeugt einen einmaligen Einladungscode (7 Tage gültig). Future createInvite({ required String householdId, required HouseholdRole role, }) async { final random = Random.secure(); final code = List.generate( 6, (_) => _codeAlphabet[random.nextInt(_codeAlphabet.length)], ).join(); await _firestore.collection('invites').doc(code).set({ 'householdId': householdId, 'role': role.name, 'createdBy': _uidGetter(), 'createdAt': FieldValue.serverTimestamp(), }); return code; } /// Macht einen anderen Haushalt zum aktiven (users/{uid}.householdId). /// Die Mitgliedschaft in allen anderen Haushalten bleibt bestehen. Future switchHousehold(String householdId) async { final uid = _uidGetter(); if (uid == null) return; await _firestore .collection('users') .doc(uid) .set({'householdId': householdId}, SetOptions(merge: true)); } /// Löst einen Code ein und wechselt in den zugehörigen Haushalt. /// Gibt den Namen des neuen Haushalts zurück. Future joinHousehold(String code) async { final callable = _functionsGetter().httpsCallable('joinHousehold'); final response = await callable.call>({'code': code.trim()}); return response.data['householdName'] as String? ?? 'Haushalt'; } } final householdRepositoryProvider = Provider((ref) { return HouseholdRepository( ref.watch(firestoreProvider), () => FirebaseFunctions.instanceFor(region: 'europe-west3'), () => ref.read(firebaseAuthProvider).currentUser?.uid, ); });