leafittome/lib/features/plants/presentation/plant_detail_screen.dart
cschlaefke 53ee30c598 Firestore und Auth: echte Daten statt Demo, Login mit E-Mail+Passwort, Security Rules
- 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>
2026-07-18 22:53:09 +02:00

170 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../../core/router/app_router.dart';
import '../../../l10n/generated/app_localizations.dart';
import '../../locations/data/locations_provider.dart';
import '../data/plants_provider.dart';
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;
return Scaffold(
appBar: AppBar(
title: Text(plant.nickname),
actions: [
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<bool>(
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: [
_InfoTile(
icon: Icons.local_florist,
label: l10n.speciesLabel,
value: plant.species,
),
_InfoTile(
icon: Icons.place,
label: l10n.locationLabel,
value: location?.name ?? l10n.locationNone,
),
const Divider(height: 32),
_InfoTile(
icon: Icons.water_drop,
label: l10n.wateringEvery(plant.wateringIntervalDays),
value: l10n.lastWatered(formatDate(plant.lastWatered)),
),
_InfoTile(
icon: Icons.compost,
label: l10n.fertilizingEvery(plant.fertilizingIntervalDays),
value: l10n.lastFertilized(formatDate(plant.lastFertilized)),
),
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,
),
),
],
),
),
),
],
],
),
);
}
}
class _InfoTile extends StatelessWidget {
const _InfoTile({
required this.icon,
required this.label,
required this.value,
});
final IconData icon;
final String label;
final String value;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return 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,
),
),
],
),
),
],
),
);
}
}