- 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>
115 lines
3.8 KiB
Dart
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,
|
|
);
|
|
});
|