Letzter Roadmap-Punkt von V3. Foto-basierte Lichtverhältnis-Analyse pro Stellplatz (Cloud Function analyzeLocation) und darauf aufbauende, rein textbasierte Eignungs-Bewertung einer Pflanze (assessPlantFit) — beides ausschließlich auf Abruf, nicht automatisch (Kostenkontrolle, eigener Anthropic-Key). Neue Stellplatz-Detailseite, Verlinkung im Pflanzen-Detail, Erkennung veralteter Bewertungen bei Umzug/Neuanalyse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
98 lines
3 KiB
Dart
98 lines
3 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 '../domain/plant_location.dart';
|
||
|
||
/// Firestore-Anbindung der Stellplätze:
|
||
/// households/{householdId}/locations/{locationId}
|
||
|
||
PlantLocation _locationFromDoc(String id, Map<String, dynamic> data) {
|
||
return PlantLocation(
|
||
id: id,
|
||
name: data['name'] as String? ?? '',
|
||
photoUrl: data['photoUrl'] as String?,
|
||
lightCategory: data['lightCategory'] as String?,
|
||
lightAssessment: data['lightAssessment'] as String?,
|
||
analyzedAt: (data['analyzedAt'] as Timestamp?)?.toDate(),
|
||
);
|
||
}
|
||
|
||
/// Alle Stellplätze des Haushalts, live aus Firestore.
|
||
final locationsProvider = StreamProvider<List<PlantLocation>>((ref) {
|
||
final householdId = ref.watch(householdIdProvider).value;
|
||
if (householdId == null) return Stream.value(const []);
|
||
|
||
return ref
|
||
.watch(firestoreProvider)
|
||
.collection('households')
|
||
.doc(householdId)
|
||
.collection('locations')
|
||
.orderBy('name')
|
||
.snapshots()
|
||
.map((snapshot) => [
|
||
for (final doc in snapshot.docs)
|
||
_locationFromDoc(doc.id, doc.data()),
|
||
]);
|
||
});
|
||
|
||
final locationByIdProvider =
|
||
Provider.family<PlantLocation?, String?>((ref, id) {
|
||
if (id == null) return null;
|
||
final locations = ref.watch(locationsProvider).value ?? const [];
|
||
for (final location in locations) {
|
||
if (location.id == id) return location;
|
||
}
|
||
return null;
|
||
});
|
||
|
||
class LocationRepository {
|
||
LocationRepository(this._firestore, this._householdIdGetter);
|
||
|
||
final FirebaseFirestore _firestore;
|
||
final String? Function() _householdIdGetter;
|
||
|
||
Future<void> addLocation(String name) {
|
||
final householdId = _householdIdGetter();
|
||
if (householdId == null) {
|
||
throw StateError('Kein Haushalt geladen – Aktion nicht möglich.');
|
||
}
|
||
return _firestore
|
||
.collection('households')
|
||
.doc(householdId)
|
||
.collection('locations')
|
||
.add({'name': name});
|
||
}
|
||
|
||
/// Speichert das Ergebnis der Standort-Analyse (Foto + Claude-Einschätzung).
|
||
Future<void> updateAnalysis(
|
||
String locationId, {
|
||
required String photoUrl,
|
||
required String lightCategory,
|
||
required String lightAssessment,
|
||
}) {
|
||
final householdId = _householdIdGetter();
|
||
if (householdId == null) {
|
||
throw StateError('Kein Haushalt geladen – Aktion nicht möglich.');
|
||
}
|
||
return _firestore
|
||
.collection('households')
|
||
.doc(householdId)
|
||
.collection('locations')
|
||
.doc(locationId)
|
||
.update({
|
||
'photoUrl': photoUrl,
|
||
'lightCategory': lightCategory,
|
||
'lightAssessment': lightAssessment,
|
||
'analyzedAt': FieldValue.serverTimestamp(),
|
||
});
|
||
}
|
||
}
|
||
|
||
final locationRepositoryProvider = Provider<LocationRepository>((ref) {
|
||
return LocationRepository(
|
||
ref.watch(firestoreProvider),
|
||
() => ref.read(householdIdProvider).value,
|
||
);
|
||
});
|