leafittome/lib/features/plants/presentation/plant_form_screen.dart
cschlaefke 6e22d9c3aa Projekt-Gerüst: Flutter-App mit Heute-Checkliste, Pflanzenverwaltung, Stellplätzen und Einstellungen
- Flutter/Dart (iOS + Android), Riverpod, go_router, i18n via ARB (deutsch)
- Feature-Struktur: today, plants, locations, household (V2-Platzhalter), settings
- Intervall-Modell: Aufgaben werden aus lastWatered/lastFertilized + Intervall berechnet
- In-Memory-Demo-Daten, Firebase-Anbindung folgt im nächsten Block
- Doku: Architektur, Firebase-Einrichtung, Forgejo-Git-Hosting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:13:18 +02:00

187 lines
6.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../l10n/generated/app_localizations.dart';
import '../../locations/data/locations_provider.dart';
import '../data/plants_provider.dart';
import '../domain/plant.dart';
/// Manuelles Anlegen und Bearbeiten einer Pflanze.
///
/// Im KI-Block von V1 wird dieser Screen um den Foto-Flow ergänzt:
/// Foto → PlantNet-Erkennung → Claude befüllt Art, Beschreibung und
/// Intervall-Vorschläge vor der Nutzer bestätigt hier nur noch.
class PlantFormScreen extends ConsumerStatefulWidget {
const PlantFormScreen({super.key, this.existing});
final Plant? existing;
@override
ConsumerState<PlantFormScreen> createState() => _PlantFormScreenState();
}
class _PlantFormScreenState extends ConsumerState<PlantFormScreen> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController _nicknameController;
late final TextEditingController _speciesController;
late final TextEditingController _wateringController;
late final TextEditingController _fertilizingController;
String? _locationId;
@override
void initState() {
super.initState();
final existing = widget.existing;
_nicknameController = TextEditingController(text: existing?.nickname ?? '');
_speciesController = TextEditingController(text: existing?.species ?? '');
_wateringController = TextEditingController(
text: existing?.wateringIntervalDays.toString() ?? '7');
_fertilizingController = TextEditingController(
text: existing?.fertilizingIntervalDays.toString() ?? '28');
_locationId = existing?.locationId;
}
@override
void dispose() {
_nicknameController.dispose();
_speciesController.dispose();
_wateringController.dispose();
_fertilizingController.dispose();
super.dispose();
}
void _save() {
if (!_formKey.currentState!.validate()) return;
final notifier = ref.read(plantsProvider.notifier);
final existing = widget.existing;
if (existing != null) {
notifier.updatePlant(existing.copyWith(
nickname: _nicknameController.text.trim(),
species: _speciesController.text.trim(),
locationId: () => _locationId,
wateringIntervalDays: int.parse(_wateringController.text.trim()),
fertilizingIntervalDays: int.parse(_fertilizingController.text.trim()),
));
} else {
notifier.addPlant(Plant(
id: 'plant-${DateTime.now().millisecondsSinceEpoch}',
nickname: _nicknameController.text.trim(),
species: _speciesController.text.trim(),
locationId: _locationId,
wateringIntervalDays: int.parse(_wateringController.text.trim()),
fertilizingIntervalDays: int.parse(_fertilizingController.text.trim()),
));
}
context.pop();
}
String? _validateRequired(String? value) {
final l10n = AppLocalizations.of(context);
if (value == null || value.trim().isEmpty) return l10n.requiredField;
return null;
}
String? _validateInterval(String? value) {
final l10n = AppLocalizations.of(context);
final parsed = int.tryParse(value?.trim() ?? '');
if (parsed == null || parsed <= 0) return l10n.invalidNumber;
return null;
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final locations = ref.watch(locationsProvider);
return Scaffold(
appBar: AppBar(
title: Text(
widget.existing != null ? l10n.plantDetailTitle : l10n.addPlant),
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
controller: _nicknameController,
decoration: InputDecoration(
labelText: l10n.nicknameLabel,
border: const OutlineInputBorder(),
),
textInputAction: TextInputAction.next,
validator: _validateRequired,
),
const SizedBox(height: 16),
TextFormField(
controller: _speciesController,
decoration: InputDecoration(
labelText: l10n.speciesLabel,
border: const OutlineInputBorder(),
),
textInputAction: TextInputAction.next,
validator: _validateRequired,
),
const SizedBox(height: 16),
DropdownButtonFormField<String?>(
initialValue: _locationId,
decoration: InputDecoration(
labelText: l10n.locationLabel,
border: const OutlineInputBorder(),
),
items: [
DropdownMenuItem<String?>(
value: null,
child: Text(l10n.locationNone),
),
for (final location in locations)
DropdownMenuItem<String?>(
value: location.id,
child: Text(location.name),
),
],
onChanged: (value) => setState(() => _locationId = value),
),
const SizedBox(height: 16),
TextFormField(
controller: _wateringController,
decoration: InputDecoration(
labelText: l10n.wateringIntervalLabel,
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.water_drop),
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
validator: _validateInterval,
),
const SizedBox(height: 16),
TextFormField(
controller: _fertilizingController,
decoration: InputDecoration(
labelText: l10n.fertilizingIntervalLabel,
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.compost),
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
validator: _validateInterval,
),
const SizedBox(height: 24),
FilledButton.icon(
onPressed: _save,
icon: const Icon(Icons.check),
label: Text(l10n.save),
),
const SizedBox(height: 8),
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.cancel),
),
],
),
),
);
}
}