- Datenmodell: users/{uid}, households/{id} mit memberUids, plants/locations als Subcollections
- StreamProvider liefern Live-Daten, Repositories kapseln Schreibzugriffe
- Login-Screen mit Registrierung (legt Haushalt automatisch an), Logout in Einstellungen
- Router-Redirect bei Login/Logout, firestore.rules mit Haushalts-Prinzip
- Tests auf firebase_auth_mocks + fake_cloud_firestore umgestellt
- Verifiziert: analyze/test grün, Android-Debug-Build erfolgreich
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
111 lines
3.7 KiB
Dart
111 lines
3.7 KiB
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
import '../../../core/firebase/firebase_providers.dart';
|
||
import '../../household/data/household_providers.dart';
|
||
import '../../today/domain/due_task.dart';
|
||
import '../domain/plant.dart';
|
||
|
||
/// Firestore-Anbindung der Pflanzen:
|
||
/// households/{householdId}/plants/{plantId}
|
||
|
||
Plant _plantFromDoc(String id, Map<String, dynamic> data) {
|
||
return Plant(
|
||
id: id,
|
||
nickname: data['nickname'] as String? ?? '',
|
||
species: data['species'] as String? ?? '',
|
||
locationId: data['locationId'] as String?,
|
||
description: data['description'] as String? ?? '',
|
||
careNotes: data['careNotes'] as String? ?? '',
|
||
wateringIntervalDays: (data['wateringIntervalDays'] as num?)?.toInt() ?? 7,
|
||
fertilizingIntervalDays:
|
||
(data['fertilizingIntervalDays'] as num?)?.toInt() ?? 28,
|
||
lastWatered: (data['lastWatered'] as Timestamp?)?.toDate(),
|
||
lastFertilized: (data['lastFertilized'] as Timestamp?)?.toDate(),
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> _plantToMap(Plant plant) {
|
||
return {
|
||
'nickname': plant.nickname,
|
||
'species': plant.species,
|
||
'locationId': plant.locationId,
|
||
'description': plant.description,
|
||
'careNotes': plant.careNotes,
|
||
'wateringIntervalDays': plant.wateringIntervalDays,
|
||
'fertilizingIntervalDays': plant.fertilizingIntervalDays,
|
||
'lastWatered':
|
||
plant.lastWatered != null ? Timestamp.fromDate(plant.lastWatered!) : null,
|
||
'lastFertilized': plant.lastFertilized != null
|
||
? Timestamp.fromDate(plant.lastFertilized!)
|
||
: null,
|
||
};
|
||
}
|
||
|
||
/// Alle Pflanzen des Haushalts, live aus Firestore.
|
||
/// Leere Liste, solange kein Haushalt geladen ist (z. B. direkt nach Login).
|
||
final plantsProvider = StreamProvider<List<Plant>>((ref) {
|
||
final householdId = ref.watch(householdIdProvider).value;
|
||
if (householdId == null) return Stream.value(const []);
|
||
|
||
return ref
|
||
.watch(firestoreProvider)
|
||
.collection('households')
|
||
.doc(householdId)
|
||
.collection('plants')
|
||
.orderBy('nickname')
|
||
.snapshots()
|
||
.map((snapshot) => [
|
||
for (final doc in snapshot.docs) _plantFromDoc(doc.id, doc.data()),
|
||
]);
|
||
});
|
||
|
||
final plantByIdProvider = Provider.family<Plant?, String>((ref, id) {
|
||
final plants = ref.watch(plantsProvider).value ?? const [];
|
||
for (final plant in plants) {
|
||
if (plant.id == id) return plant;
|
||
}
|
||
return null;
|
||
});
|
||
|
||
class PlantRepository {
|
||
PlantRepository(this._firestore, this._householdIdGetter);
|
||
|
||
final FirebaseFirestore _firestore;
|
||
final String? Function() _householdIdGetter;
|
||
|
||
CollectionReference<Map<String, dynamic>> get _plants {
|
||
final householdId = _householdIdGetter();
|
||
if (householdId == null) {
|
||
throw StateError('Kein Haushalt geladen – Aktion nicht möglich.');
|
||
}
|
||
return _firestore
|
||
.collection('households')
|
||
.doc(householdId)
|
||
.collection('plants');
|
||
}
|
||
|
||
Future<void> addPlant(Plant plant) => _plants.add(_plantToMap(plant));
|
||
|
||
Future<void> updatePlant(Plant plant) =>
|
||
_plants.doc(plant.id).update(_plantToMap(plant));
|
||
|
||
Future<void> removePlant(String plantId) => _plants.doc(plantId).delete();
|
||
|
||
/// Bestätigt eine Aufgabe: setzt das Erledigungsdatum, wodurch sich die
|
||
/// nächste Fälligkeit automatisch neu berechnet (Intervall-Modell).
|
||
Future<void> confirmTask(String plantId, CareTaskType type) {
|
||
final field = switch (type) {
|
||
CareTaskType.watering => 'lastWatered',
|
||
CareTaskType.fertilizing => 'lastFertilized',
|
||
};
|
||
return _plants.doc(plantId).update({field: Timestamp.now()});
|
||
}
|
||
}
|
||
|
||
final plantRepositoryProvider = Provider<PlantRepository>((ref) {
|
||
return PlantRepository(
|
||
ref.watch(firestoreProvider),
|
||
() => ref.read(householdIdProvider).value,
|
||
);
|
||
});
|