import 'package:cloud_functions/cloud_functions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import '../../../core/router/app_router.dart'; import '../../../l10n/generated/app_localizations.dart'; import '../../household/data/household_providers.dart'; import '../../household/domain/household.dart'; import '../../locations/data/locations_provider.dart'; import '../../locations/domain/plant_location.dart'; import '../../today/application/due_tasks_provider.dart'; import '../data/plant_diagnosis_service.dart'; import '../data/plant_fit_service.dart'; import '../data/plants_provider.dart'; import '../domain/plant.dart'; import 'diagnosis_sheet.dart'; /// Kompakte Sterne-/Status-Anzeige für die Stellplatz-Zeile. String _fitStatusText(AppLocalizations l10n, Plant plant, PlantLocation location) { if (plant.fitStars == null) return l10n.fitCheckNotDone; if (isFitStale(plant, location)) return l10n.fitCheckStale; return '★' * plant.fitStars! + '☆' * (5 - plant.fitStars!); } class PlantDetailScreen extends ConsumerWidget { const PlantDetailScreen({super.key, required this.plantId}); final String plantId; @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context); final plant = ref.watch(plantByIdProvider(plantId)); if (plant == null) { // Pflanze wurde gelöscht, während der Screen offen war. return Scaffold(appBar: AppBar(), body: const SizedBox.shrink()); } final location = ref.watch(locationByIdProvider(plant.locationId)); final locale = Localizations.localeOf(context).toString(); final dateFormat = DateFormat('d. MMMM y', locale); String formatDate(DateTime? date) => date != null ? dateFormat.format(date) : l10n.never; final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); String formatNextDue(DateTime date) => date.isAfter(today) ? dateFormat.format(date) : l10n.dueToday; final canEdit = ref.watch(myRoleProvider) == HouseholdRole.member; return Scaffold( appBar: AppBar( title: Text(plant.nickname), actions: [ if (canEdit) ...[ IconButton( icon: const Icon(Icons.edit), tooltip: l10n.plantDetailTitle, onPressed: () => context.push(AppRoutes.plantEdit, extra: plant), ), IconButton( icon: const Icon(Icons.delete_outline), tooltip: l10n.delete, onPressed: () async { final confirmed = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: Text(l10n.deletePlantQuestion(plant.nickname)), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: Text(l10n.cancel), ), FilledButton( onPressed: () => Navigator.pop(dialogContext, true), child: Text(l10n.delete), ), ], ), ); if (confirmed == true && context.mounted) { ref.read(plantRepositoryProvider).removePlant(plant.id); context.pop(); } }, ), ], ], ), body: ListView( padding: const EdgeInsets.all(16), children: [ if (plant.photoUrl != null) ...[ ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( plant.photoUrl!, height: 220, fit: BoxFit.cover, ), ), const SizedBox(height: 16), ], _InfoTile( icon: Icons.local_florist, label: l10n.speciesLabel, value: plant.species, ), _InfoTile( icon: Icons.place, label: l10n.locationLabel, value: location == null ? l10n.locationNone : '${location.name} · ${_fitStatusText(l10n, plant, location)}', onTap: location == null ? null : () => context.push(AppRoutes.locationDetail(location.id)), ), const Divider(height: 32), _InfoTile( icon: Icons.water_drop, label: l10n.wateringEvery(plant.wateringIntervalDays), value: '${l10n.lastWatered(formatDate(plant.lastWatered))}' '${plant.lastWateredBy != null ? l10n.doneBy(plant.lastWateredBy!) : ''}' '\n${l10n.nextWatering(formatNextDue(nextWateringDue(plant, today)!))}', ), _InfoTile( icon: Icons.compost, label: l10n.fertilizingEvery(plant.fertilizingIntervalDays), value: '${l10n.lastFertilized(formatDate(plant.lastFertilized))}' '${plant.lastFertilizedBy != null ? l10n.doneBy(plant.lastFertilizedBy!) : ''}' '\n${l10n.nextFertilizing(formatNextDue(nextFertilizingDue(plant, today)!))}', ), if (plant.repottingIntervalMonths != null) _InfoTile( icon: Icons.yard, label: l10n.repottingEvery(plant.repottingIntervalMonths!), value: '${l10n.lastRepotted(formatDate(plant.lastRepotted))}' '${plant.lastRepottedBy != null ? l10n.doneBy(plant.lastRepottedBy!) : ''}' '\n${l10n.nextRepotting(formatNextDue(nextRepottingDue(plant, today)!))}', ), if (plant.description.isNotEmpty) ...[ const Divider(height: 32), Text( plant.description, style: Theme.of(context).textTheme.bodyLarge, ), ], if (plant.careNotes.isNotEmpty) ...[ const SizedBox(height: 12), Card( child: Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( Icons.tips_and_updates, color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 12), Expanded( child: Text( plant.careNotes, style: Theme.of(context).textTheme.bodyLarge, ), ), ], ), ), ), ], const Divider(height: 32), _DiagnosisSection(plant: plant), ], ), ); } } /// Krankheits-Check (V3): Foto aufnehmen → Claude beurteilt den Zustand /// und gibt Behandlungstipps. Auch für Sitter nutzbar (nur lesend). class _DiagnosisSection extends ConsumerStatefulWidget { const _DiagnosisSection({required this.plant}); final Plant plant; @override ConsumerState<_DiagnosisSection> createState() => _DiagnosisSectionState(); } class _DiagnosisSectionState extends ConsumerState<_DiagnosisSection> { final _picker = ImagePicker(); bool _busy = false; Future _start() async { final l10n = AppLocalizations.of(context); final source = await showModalBottomSheet( context: context, builder: (sheetContext) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.photo_camera), title: Text(l10n.takePhoto), onTap: () => Navigator.pop(sheetContext, ImageSource.camera), ), ListTile( leading: const Icon(Icons.photo_library), title: Text(l10n.fromGallery), onTap: () => Navigator.pop(sheetContext, ImageSource.gallery), ), ], ), ), ); if (source == null || !mounted) return; final picked = await _picker.pickImage( source: source, maxWidth: 1280, imageQuality: 80, ); if (picked == null || !mounted) return; final bytes = await picked.readAsBytes(); setState(() => _busy = true); try { final service = ref.read(plantDiagnosisServiceProvider); final result = await service.diagnose( bytes, speciesHint: widget.plant.species, ); // In der Historie ablegen — ein Speicherfehler soll die Anzeige des // Ergebnisses aber nicht verhindern. try { await service.saveDiagnosis(widget.plant.id, result); } catch (error) { debugPrint('Untersuchung speichern fehlgeschlagen: $error'); } if (!mounted) return; await showDiagnosisSheet( context, result: result, plantNickname: widget.plant.nickname, ); } on FirebaseFunctionsException catch (e) { if (!mounted) return; // Die Function liefert deutsche Meldungen (z. B. „keine Pflanze // erkannt") – die zeigen wir direkt an. ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar(SnackBar( content: Text(e.message ?? l10n.diagnosisFailed), )); } catch (_) { if (!mounted) return; ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar(SnackBar(content: Text(l10n.diagnosisFailed))); } finally { if (mounted) setState(() => _busy = false); } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); // Für den Historie-Button: deaktiviert, solange keine Untersuchungen // gespeichert sind (oder die Liste noch lädt). final hasHistory = (ref .watch(plantDiagnosesProvider(widget.plant.id)) .value ?? const []) .isNotEmpty; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ OutlinedButton.icon( onPressed: _busy ? null : _start, icon: _busy ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.health_and_safety_outlined), label: Text(_busy ? l10n.diagnosing : l10n.diagnoseButton), ), const SizedBox(height: 4), Text( l10n.diagnoseHint, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: hasHistory ? () => context .push(AppRoutes.plantDiagnoses(widget.plant.id)) : null, icon: const Icon(Icons.history), label: Text(l10n.diagnosisHistoryButton), ), ], ); } } class _InfoTile extends StatelessWidget { const _InfoTile({ required this.icon, required this.label, required this.value, this.onTap, }); final IconData icon; final String label; final String value; final VoidCallback? onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); final content = Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, color: theme.colorScheme.primary), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: theme.textTheme.titleMedium), const SizedBox(height: 2), Text( value, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), if (onTap != null) Icon(Icons.chevron_right, color: theme.colorScheme.onSurfaceVariant), ], ), ); if (onTap == null) return content; return InkWell(onTap: onTap, child: content); } }