import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../data/variety_repository.dart'; import '../db/enums.dart'; import '../i18n/strings.g.dart'; import 'plantare_sheet.dart'; import 'theme.dart'; /// The Plantares screen — your reproduction commitments (data-model §2.7). /// Lists what's open (still to grow out & return) and what's done, and lets you /// add or settle one. Local-first; reads the encrypted inventory DB. class PlantaresScreen extends StatelessWidget { const PlantaresScreen({super.key}); @override Widget build(BuildContext context) { final t = context.t; final repo = context.read(); return Scaffold( appBar: AppBar(title: Text(t.plantare.title)), floatingActionButton: FloatingActionButton.extended( key: const Key('plantares.add'), onPressed: () => showPlantareSheet(context, repository: repo), icon: const Icon(Icons.add), label: Text(t.plantare.add), ), body: StreamBuilder>( stream: repo.watchPlantareViews(), builder: (context, snapshot) { final all = snapshot.data; if (all == null) { return const Center(child: CircularProgressIndicator()); } if (all.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Text( t.plantare.empty, textAlign: TextAlign.center, style: const TextStyle(color: seedMuted, fontSize: 15), ), ), ); } final open = all.where((v) => v.plantare.status == PlantareStatus.open); final settled = all.where((v) => v.plantare.status != PlantareStatus.open); return ListView( padding: const EdgeInsets.only(bottom: 88), children: [ if (open.isNotEmpty) _SectionHeader(t.plantare.openSection), for (final v in open) _PlantareTile(v, repo), if (settled.isNotEmpty) _SectionHeader(t.plantare.settledSection), for (final v in settled) _PlantareTile(v, repo), ], ); }, ), ); } } class _SectionHeader extends StatelessWidget { const _SectionHeader(this.label); final String label; @override Widget build(BuildContext context) => Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), child: Text( label.toUpperCase(), style: const TextStyle( color: seedMuted, fontSize: 12, fontWeight: FontWeight.w600, letterSpacing: 0.5, ), ), ); } /// One commitment row. An open one shows a big "mark returned"; a settled one /// reads muted. It names the seed and (when linked) taps through to its detail, /// shows when it was made and — for a promise with a return-by date — whether /// it's still ahead or overdue. The overflow menu carries the rest (let go / /// reopen / remove). class _PlantareTile extends StatelessWidget { const _PlantareTile(this.view, this.repo); final PlantareView view; final VarietyRepository repo; @override Widget build(BuildContext context) { final t = context.t; final p = view.plantare; final varietyLabel = view.varietyLabel; final done = p.status != PlantareStatus.open; final iReturn = p.direction == PlantareDirection.iReturn; final title = (p.owedDescription?.trim().isNotEmpty ?? false) ? p.owedDescription!.trim() : (iReturn ? t.plantare.iReturn : t.plantare.owedToMe); final locals = MaterialLocalizations.of(context); final made = locals .formatShortDate(DateTime.fromMillisecondsSinceEpoch(p.madeOn)); final subtitleParts = [ iReturn ? t.plantare.iReturn : t.plantare.owedToMe, ?varietyLabel, if (p.counterparty?.trim().isNotEmpty ?? false) p.counterparty!.trim(), made, if (done) p.status == PlantareStatus.returned ? t.plantare.statusReturned : t.plantare.statusForgiven, ]; // A return-by date is a gentle nudge, not a debt: only an open, past-due // promise reads as overdue. final overdue = !done && p.dueBy != null && p.dueBy! < DateTime.now().millisecondsSinceEpoch; final returnBy = p.dueBy == null ? null : t.plantare.returnBy( date: locals.formatShortDate( DateTime.fromMillisecondsSinceEpoch(p.dueBy!), ), ); final linkedId = varietyLabel == null ? null : p.varietyId; return ListTile( onTap: linkedId == null ? null : () => context.push('/variety/$linkedId'), leading: Icon( iReturn ? Icons.volunteer_activism_outlined : Icons.eco_outlined, color: done ? seedMuted : seedGreen, ), title: Text( title, style: TextStyle( decoration: done ? TextDecoration.lineThrough : null, color: done ? seedMuted : seedOnSurface, ), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(subtitleParts.join(' · '), maxLines: 2, overflow: TextOverflow.ellipsis), if (returnBy != null) Text( overdue ? '$returnBy · ${t.plantare.overdue}' : returnBy, style: TextStyle( color: overdue ? seedWarning : seedMuted, fontWeight: overdue ? FontWeight.w600 : null, ), ), ], ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (!done) IconButton( key: Key('plantare.return.${p.id}'), icon: const Icon(Icons.check_circle_outline), tooltip: t.plantare.markReturned, color: seedGreen, onPressed: () => repo.setPlantareStatus(p.id, PlantareStatus.returned), ), PopupMenuButton( key: Key('plantare.menu.${p.id}'), onSelected: (v) async { switch (v) { case 'forgiven': await repo.setPlantareStatus(p.id, PlantareStatus.forgiven); case 'reopen': await repo.setPlantareStatus(p.id, PlantareStatus.open); case 'delete': await repo.deletePlantare(p.id); } }, itemBuilder: (context) => [ if (!done) PopupMenuItem( value: 'forgiven', child: Text(t.plantare.markForgiven)), if (done) PopupMenuItem( value: 'reopen', child: Text(t.plantare.reopen)), PopupMenuItem(value: 'delete', child: Text(t.plantare.delete)), ], ), ], ), ); } }