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>
73 lines
2.3 KiB
Dart
73 lines
2.3 KiB
Dart
import 'dart:convert';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:cloud_functions/cloud_functions.dart';
|
||
import 'package:firebase_storage/firebase_storage.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
import '../../household/data/household_providers.dart';
|
||
|
||
/// Ergebnis der Standort-Analyse (Cloud Function analyzeLocation).
|
||
class LocationAnalysisResult {
|
||
const LocationAnalysisResult({
|
||
required this.lightCategory,
|
||
required this.description,
|
||
});
|
||
|
||
final String lightCategory;
|
||
final String description;
|
||
}
|
||
|
||
class LocationAnalysisService {
|
||
LocationAnalysisService(
|
||
this._functionsGetter,
|
||
this._storage,
|
||
this._householdIdGetter,
|
||
);
|
||
|
||
// Lazy, damit Widget-Tests ohne initialisiertes Firebase auskommen —
|
||
// FirebaseFunctions.instanceFor wirft sonst schon beim Erzeugen.
|
||
final FirebaseFunctions Function() _functionsGetter;
|
||
final FirebaseStorage _storage;
|
||
final String? Function() _householdIdGetter;
|
||
|
||
/// Lädt das Stellplatz-Foto in den Haushalts-Ordner in Storage und
|
||
/// liefert die URL.
|
||
Future<String> uploadPhoto(Uint8List imageBytes) async {
|
||
final householdId = _householdIdGetter();
|
||
if (householdId == null) {
|
||
throw StateError('Kein Haushalt geladen – Aktion nicht möglich.');
|
||
}
|
||
final ref = _storage.ref(
|
||
'households/$householdId/location-photos/'
|
||
'${DateTime.now().millisecondsSinceEpoch}.jpg',
|
||
);
|
||
await ref.putData(
|
||
imageBytes,
|
||
SettableMetadata(contentType: 'image/jpeg'),
|
||
);
|
||
return ref.getDownloadURL();
|
||
}
|
||
|
||
/// Schickt das Foto an die Cloud Function (Claude) – dauert einige Sekunden.
|
||
Future<LocationAnalysisResult> analyze(Uint8List imageBytes) async {
|
||
final callable = _functionsGetter().httpsCallable('analyzeLocation');
|
||
final response = await callable.call<Map<String, dynamic>>({
|
||
'imageBase64': base64Encode(imageBytes),
|
||
});
|
||
final data = response.data;
|
||
return LocationAnalysisResult(
|
||
lightCategory: data['lightCategory'] as String? ?? '',
|
||
description: data['description'] as String? ?? '',
|
||
);
|
||
}
|
||
}
|
||
|
||
final locationAnalysisServiceProvider =
|
||
Provider<LocationAnalysisService>((ref) {
|
||
return LocationAnalysisService(
|
||
() => FirebaseFunctions.instanceFor(region: 'europe-west3'),
|
||
FirebaseStorage.instance,
|
||
() => ref.read(householdIdProvider).value,
|
||
);
|
||
});
|