leafittome/lib/features/plants/presentation/plants_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

105 lines
3.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/router/app_router.dart';
import '../../../core/widgets/app_drawer.dart';
import '../../../l10n/generated/app_localizations.dart';
import '../../locations/data/locations_provider.dart';
import '../data/plants_provider.dart';
import '../domain/plant.dart';
class PlantsScreen extends ConsumerWidget {
const PlantsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final plantsAsync = ref.watch(plantsProvider);
final plants = plantsAsync.value ?? const [];
final Widget body;
if (plantsAsync.isLoading && !plantsAsync.hasValue) {
body = const Center(child: CircularProgressIndicator());
} else if (plants.isEmpty) {
body = _EmptyView(l10n: l10n);
} else {
body = _plantList(context, ref, plants);
}
return Scaffold(
appBar: AppBar(title: Text(l10n.plantsTitle)),
drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => context.push(AppRoutes.plantAdd),
icon: const Icon(Icons.add),
label: Text(l10n.addPlant),
),
body: body,
);
}
Widget _plantList(BuildContext context, WidgetRef ref, List<Plant> plants) {
return ListView.separated(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 96),
itemCount: plants.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final plant = plants[index];
final location =
ref.watch(locationByIdProvider(plant.locationId));
return ListTile(
leading: CircleAvatar(
radius: 24,
child: const Icon(Icons.local_florist),
),
title: Text(
plant.nickname,
style: Theme.of(context).textTheme.titleMedium,
),
subtitle: Text(
location != null
? '${plant.species}\n${location.name}'
: plant.species,
),
isThreeLine: location != null,
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push('/plants/${plant.id}'),
);
},
);
}
}
class _EmptyView extends StatelessWidget {
const _EmptyView({required this.l10n});
final AppLocalizations l10n;
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.local_florist, size: 72),
const SizedBox(height: 16),
Text(
l10n.plantsEmpty,
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
l10n.plantsEmptyHint,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
],
),
),
);
}
}