leafittome/lib/features/household/data/household_providers.dart
cschlaefke 1b2c880c89 V2: Haushalt teilen – Einladungscodes, Mitglieder- und Sitter-Rolle, sichtbare Bestätigungen
- Einladen per einmaligem 6-stelligem Code (7 Tage gültig), wahlweise als Mitglied oder Pflanzen-Sitter
- Beitritt über Cloud Function joinHousehold (Transaktion, Admin-Rechte)
- Security Rules: Sitter dürfen an Pflanzen nur Bestätigungs-Felder ändern; invites nur erstellen, nie lesen
- Haushalts-Screen: Mitgliederliste mit Rollen, Einladen, Beitreten mit Wechsel-Warnung
- UI-Gating: Sitter sehen keine Anlegen-/Bearbeiten-/Löschen-Aktionen
- Bestätigungen speichern den Namen (lastWateredBy/lastFertilizedBy), Anzeige im Profil
- Tests: Members-Map im Seed, neuer Sitter-Test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:14:34 +02:00

115 lines
3.8 KiB
Dart

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 Haushalts-ID des angemeldeten Nutzers (aus users/{uid}.householdId).
///
/// null, solange niemand angemeldet ist oder das Profil noch lädt.
/// Alle Daten-Provider (Pflanzen, Stellplätze) hängen hieran.
final householdIdProvider = StreamProvider<String?>((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?);
});
/// Der komplette Haushalt inkl. Mitgliedern und Rollen, live aus Firestore.
final householdProvider = StreamProvider<Household?>((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;
final membersMap = (data['members'] as Map<String, dynamic>?) ?? {};
final memberUids = (data['memberUids'] as List<dynamic>?) ?? [];
return Household(
id: snapshot.id,
name: data['name'] as String? ?? 'Haushalt',
members: [
for (final uid in memberUids.cast<String>())
HouseholdMember(
uid: uid,
role: ((membersMap[uid]
as Map<String, dynamic>?)?['role'] as String?) ==
'sitter'
? HouseholdRole.sitter
: HouseholdRole.member,
email: (membersMap[uid] as Map<String, dynamic>?)?['email']
as String? ??
'',
),
],
);
});
});
/// Rolle des angemeldeten Nutzers im aktuellen Haushalt.
final myRoleProvider = Provider<HouseholdRole>((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._functions, this._uidGetter);
final FirebaseFirestore _firestore;
final FirebaseFunctions _functions;
final String? Function() _uidGetter;
static const _codeAlphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
/// Erzeugt einen einmaligen Einladungscode (7 Tage gültig).
Future<String> 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;
}
/// Löst einen Code ein und wechselt in den zugehörigen Haushalt.
/// Gibt den Namen des neuen Haushalts zurück.
Future<String> joinHousehold(String code) async {
final callable = _functions.httpsCallable('joinHousehold');
final response =
await callable.call<Map<String, dynamic>>({'code': code.trim()});
return response.data['householdName'] as String? ?? 'Haushalt';
}
}
final householdRepositoryProvider = Provider<HouseholdRepository>((ref) {
return HouseholdRepository(
ref.watch(firestoreProvider),
FirebaseFunctions.instanceFor(region: 'europe-west3'),
() => ref.read(firebaseAuthProvider).currentUser?.uid,
);
});