import 'package:flutter/material.dart'; import '../i18n/strings.g.dart'; import 'theme.dart'; /// How many years back the picker offers. A freshly harvested lot is the common /// case, but old seed still needs a home. const _yearsBack = 40; /// A harvest date: a required [year] plus an optional [month] (1..12). class HarvestDate { const HarvestDate(this.year, [this.month]); final int year; final int? month; } /// Formats a harvest date for display: "September 2021" when a month is set, /// otherwise just the localized "Year 2021". String harvestDateLabel(Translations t, {required int year, int? month}) { if (month == null) return t.detail.year(year: year); return '${t.harvest.monthNames[month - 1]} $year'; } /// An inline, tappable field showing the current harvest date. Tapping opens a /// themed month/year dialog (see [showHarvestDatePicker]). Month is optional, so /// the field can read "September 2021" or just "2021". class HarvestDateField extends StatelessWidget { const HarvestDateField({ required this.year, required this.onChanged, this.month, super.key, }); final int year; final int? month; final void Function(int year, int? month) onChanged; @override Widget build(BuildContext context) { final t = context.t; return OutlinedButton.icon( key: const Key('addLot.year'), onPressed: () async { final picked = await showHarvestDatePicker( context, initial: HarvestDate(year, month), ); if (picked != null) onChanged(picked.year, picked.month); }, icon: const Icon(Icons.event_outlined, size: 20), label: Align( alignment: AlignmentDirectional.centerStart, child: Text(harvestDateLabel(t, year: year, month: month)), ), style: OutlinedButton.styleFrom( foregroundColor: seedGreenDark, alignment: AlignmentDirectional.centerStart, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), minimumSize: const Size.fromHeight(0), ), ); } } /// Opens the themed month/year dialog. Returns the chosen [HarvestDate], or null /// if the user cancels. Future showHarvestDatePicker( BuildContext context, { required HarvestDate initial, }) { return showDialog( context: context, builder: (_) => _HarvestPickerDialog(initial: initial), ); } enum _Mode { months, years } class _HarvestPickerDialog extends StatefulWidget { const _HarvestPickerDialog({required this.initial}); final HarvestDate initial; @override State<_HarvestPickerDialog> createState() => _HarvestPickerDialogState(); } class _HarvestPickerDialogState extends State<_HarvestPickerDialog> { late int _year = widget.initial.year; late int? _month = widget.initial.month; var _mode = _Mode.months; int get _maxYear => DateTime.now().year; int get _minYear => _maxYear - _yearsBack; void _setYear(int year) => setState(() { _year = year.clamp(_minYear, _maxYear); _mode = _Mode.months; }); void _toggleMonth(int month) => setState(() => _month = _month == month ? null : month); @override Widget build(BuildContext context) { final t = context.t; return Dialog( clipBehavior: Clip.antiAlias, child: SizedBox( width: 360, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _Header( title: t.harvest.pickTitle, selection: harvestDateLabel(t, year: _year, month: _month), ), Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: _NavRow( year: _year, mode: _mode, onToggleMode: () => setState( () => _mode = _mode == _Mode.years ? _Mode.months : _Mode.years, ), onStep: (delta) { if (_mode == _Mode.months) { _setYear(_year + delta); } else { // Page the year grid by a screenful (12 years). _setYearGridPage(delta); } }, ), ), AnimatedSize( duration: const Duration(milliseconds: 150), child: _mode == _Mode.months ? _MonthGrid( selected: _month, onSelect: _toggleMonth, onClear: () => setState(() => _month = null), ) : _YearGrid( years: _yearPage, selected: _year, onSelect: _setYear, ), ), Padding( padding: const EdgeInsets.fromLTRB(8, 4, 8, 8), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( key: const Key('harvestPicker.cancel'), onPressed: () => Navigator.of(context).pop(), child: Text(t.common.cancel), ), TextButton( key: const Key('harvestPicker.ok'), onPressed: () => Navigator.of(context).pop(HarvestDate(_year, _month)), child: Text(t.common.save), ), ], ), ), ], ), ), ); } // --- Year grid paging ----------------------------------------------------- // Top-left year of the visible page in years mode. Anchored to the current // year so recent harvests show first; older years are one ‹ page away. late int _yearPageAnchor = _maxYear; List get _yearPage { // A page of up to 12 years, newest at the top-left, clamped to the range. final start = _yearPageAnchor; return [for (var y = start; y > start - 12 && y >= _minYear; y--) y]; } void _setYearGridPage(int delta) => setState(() { // delta -1 (‹) → older page, +1 (›) → newer page. _yearPageAnchor = (_yearPageAnchor + delta * 12).clamp( _minYear + 11, _maxYear, ); }); } class _Header extends StatelessWidget { const _Header({required this.title, required this.selection}); final String title; final String selection; @override Widget build(BuildContext context) { return Container( width: double.infinity, color: seedGreen, padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title.toUpperCase(), style: const TextStyle( color: Colors.white, fontSize: 12, letterSpacing: 1.2, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), Text( selection, style: const TextStyle( color: Colors.white, fontSize: 30, fontWeight: FontWeight.w400, ), ), ], ), ); } } class _NavRow extends StatelessWidget { const _NavRow({ required this.year, required this.mode, required this.onToggleMode, required this.onStep, }); final int year; final _Mode mode; final VoidCallback onToggleMode; final ValueChanged onStep; @override Widget build(BuildContext context) { return Row( children: [ TextButton.icon( key: const Key('harvestPicker.yearToggle'), onPressed: onToggleMode, label: Text('$year'), icon: Icon( mode == _Mode.years ? Icons.arrow_drop_up : Icons.arrow_drop_down, ), iconAlignment: IconAlignment.end, style: TextButton.styleFrom(foregroundColor: seedGreenDark), ), const Spacer(), IconButton( key: const Key('harvestPicker.prev'), onPressed: () => onStep(-1), icon: const Icon(Icons.chevron_left), ), IconButton( key: const Key('harvestPicker.next'), onPressed: () => onStep(1), icon: const Icon(Icons.chevron_right), ), ], ); } } class _MonthGrid extends StatelessWidget { const _MonthGrid({ required this.selected, required this.onSelect, required this.onClear, }); final int? selected; final ValueChanged onSelect; final VoidCallback onClear; @override Widget build(BuildContext context) { final t = context.t; final names = t.harvest.monthNames; return Column( children: [ GridView.count( crossAxisCount: 3, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 12), childAspectRatio: 2.4, children: [ for (var m = 1; m <= 12; m++) _PickCell( key: Key('harvestPicker.month.$m'), label: names[m - 1], selected: selected == m, onTap: () => onSelect(m), ), ], ), TextButton( key: const Key('harvestPicker.anyMonth'), onPressed: selected == null ? null : onClear, child: Text(t.harvest.anyMonth), ), ], ); } } class _YearGrid extends StatelessWidget { const _YearGrid({ required this.years, required this.selected, required this.onSelect, }); final List years; final int selected; final ValueChanged onSelect; @override Widget build(BuildContext context) { return GridView.count( crossAxisCount: 4, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), childAspectRatio: 1.8, children: [ for (final y in years) _PickCell( key: Key('harvestPicker.year.$y'), label: '$y', selected: y == selected, onTap: () => onSelect(y), ), ], ); } } /// A round-highlight cell used for both months and years, matching the seed /// theme (green pill when selected). class _PickCell extends StatelessWidget { const _PickCell({ required this.label, required this.selected, required this.onTap, super.key, }); final String label; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(4), child: Material( color: selected ? seedGreen : Colors.transparent, shape: const StadiumBorder(), child: InkWell( customBorder: const StadiumBorder(), onTap: onTap, child: Center( child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: selected ? Colors.white : null, fontWeight: selected ? FontWeight.w600 : null, ), ), ), ), ), ); } }