import 'package:commons_core/commons_core.dart'; import 'package:flutter/material.dart'; import '../data/variety_repository.dart'; import '../db/enums.dart'; import '../i18n/strings.g.dart'; import 'currency_quick_picks.dart'; import 'harvest_date_picker.dart'; import 'quantity_kind_l10n.dart'; import 'quantity_picker.dart'; import 'theme.dart'; /// Opens the hand-over sheet — the ONE place to note that seeds changed hands /// (a gift, a swap, a sale…), optionally with money and/or a return promise. /// It records the Movement and spawns the Sale/Plantare rows in one save /// (data-model §2.8), so the person never juggles three separate forms. /// /// [lots] are the variety's live batches: one → preselected; several → a /// chip picker; none → the quantity section hides and only the optional /// records are saved. [initialLot] preselects a batch (entry from a lot row). Future showHandoverSheet( BuildContext context, { required VarietyRepository repository, required String varietyId, List lots = const [], VarietyLot? initialLot, }) { return showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => _HandoverSheet( repository: repository, varietyId: varietyId, lots: lots, initialLot: initialLot ?? (lots.length == 1 ? lots.single : null), ), ); } class _HandoverSheet extends StatefulWidget { const _HandoverSheet({ required this.repository, required this.varietyId, required this.lots, this.initialLot, }); final VarietyRepository repository; final String varietyId; final List lots; final VarietyLot? initialLot; @override State<_HandoverSheet> createState() => _HandoverSheetState(); } class _HandoverSheetState extends State<_HandoverSheet> { HandoverDirection _direction = HandoverDirection.iGave; VarietyLot? _lot; bool _gaveAll = true; Quantity? _partialQuantity; final _counterparty = TextEditingController(); bool _withPayment = false; final _amount = TextEditingController(); final _currency = TextEditingController(); bool _withPromise = false; final _owed = TextEditingController(); bool _saving = false; @override void initState() { super.initState(); _lot = widget.initialLot; } @override void dispose() { _counterparty.dispose(); _amount.dispose(); _currency.dispose(); _owed.dispose(); super.dispose(); } String? _nullIfBlank(String s) => s.trim().isEmpty ? null : s.trim(); String _lotLabel(Translations t, VarietyLot lot) { final parts = [ if (lot.harvestYear != null) harvestDateLabel(t, year: lot.harvestYear!, month: lot.harvestMonth) else t.detail.noYear, if (lot.quantity != null) quantityDisplay(t, lot.quantity!), ]; return parts.join(' · '); } Future _save() async { setState(() => _saving = true); final counterparty = _nullIfBlank(_counterparty.text); final amount = double.tryParse(_amount.text.trim().replaceAll(',', '.')); final currency = _nullIfBlank(_currency.text); final owed = _nullIfBlank(_owed.text); final lot = _lot; if (lot != null) { await widget.repository.recordHandover( lotId: lot.id, direction: _direction, gaveAll: _gaveAll, quantity: _gaveAll ? null : _partialQuantity, counterparty: counterparty, withPayment: _withPayment, paymentAmount: amount, paymentCurrency: currency, withPromise: _withPromise, promiseOwedDescription: owed, ); } else { // No batch to move — record just the ledgers the person asked for. if (_withPayment) { await widget.repository.createSale( direction: _direction == HandoverDirection.iGave ? SaleDirection.iSold : SaleDirection.iBought, varietyId: widget.varietyId, counterparty: counterparty, amount: amount, currency: currency, ); } if (_withPromise) { await widget.repository.createPlantare( direction: _direction == HandoverDirection.iGave ? PlantareDirection.owedToMe : PlantareDirection.iReturn, varietyId: widget.varietyId, counterparty: counterparty, owedDescription: owed, ); } } if (mounted) Navigator.of(context).pop(); } @override Widget build(BuildContext context) { final t = context.t; final gave = _direction == HandoverDirection.iGave; return Padding( padding: EdgeInsets.only( left: 16, right: 16, top: 16, bottom: MediaQuery.of(context).viewInsets.bottom + 16, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( t.handover.title, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 4), Text( t.handover.help, style: const TextStyle(color: seedMuted, fontSize: 13), ), const SizedBox(height: 12), // Two big, human choices — same idiom as the plantare sheet. for (final d in HandoverDirection.values) ListTile( key: Key('handover.direction.${d.name}'), contentPadding: EdgeInsets.zero, leading: Icon( _direction == d ? Icons.radio_button_checked : Icons.radio_button_unchecked, color: _direction == d ? seedGreen : seedMuted, ), title: Text( d == HandoverDirection.iGave ? t.handover.iGave : t.handover.iReceived, ), onTap: () => setState(() => _direction = d), ), if (widget.lots.length > 1) ...[ const SizedBox(height: 8), Text( t.handover.whichLot, style: Theme.of(context).textTheme.labelLarge, ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 4, children: [ for (final lot in widget.lots) ChoiceChip( key: Key('handover.lot.${lot.id}'), label: Text(_lotLabel(t, lot)), selected: _lot?.id == lot.id, onSelected: (sel) => setState(() => _lot = sel ? lot : null), ), ], ), ], if (_lot != null && gave) ...[ const SizedBox(height: 8), Text( t.handover.howMuch, style: Theme.of(context).textTheme.labelLarge, ), const SizedBox(height: 8), Wrap( spacing: 8, children: [ ChoiceChip( key: const Key('handover.allOfIt'), label: Text(t.handover.allOfIt), selected: _gaveAll, onSelected: (sel) => setState(() => _gaveAll = true), ), ChoiceChip( key: const Key('handover.partOfIt'), label: Text(t.handover.partOfIt), selected: !_gaveAll, onSelected: (sel) => setState(() => _gaveAll = false), ), ], ), if (!_gaveAll) ...[ const SizedBox(height: 8), QuantityPicker( type: _lot!.type, value: _partialQuantity, onChanged: (q) => _partialQuantity = q, ), ], ], const SizedBox(height: 12), TextField( key: const Key('handover.counterparty'), controller: _counterparty, textCapitalization: TextCapitalization.words, decoration: InputDecoration( labelText: t.sale.counterparty, helperText: t.sale.counterpartyHint, border: const OutlineInputBorder(), ), ), const SizedBox(height: 12), // Money and promise stay one tap away — a plain gift needs neither. Wrap( spacing: 8, children: [ if (!_withPayment) ActionChip( key: const Key('handover.addPayment'), avatar: const Icon(Icons.sell_outlined, size: 18), label: Text(t.handover.paymentChip), onPressed: () => setState(() => _withPayment = true), ), if (!_withPromise) ActionChip( key: const Key('handover.addPromise'), avatar: const Icon( Icons.volunteer_activism_outlined, size: 18, ), label: Text( gave ? t.handover.promiseGave : t.handover.promiseReceived, ), onPressed: () => setState(() => _withPromise = true), ), ], ), if (_withPayment) ...[ const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: TextField( key: const Key('handover.amount'), controller: _amount, keyboardType: const TextInputType.numberWithOptions( decimal: true, ), decoration: InputDecoration( labelText: t.sale.amount, border: const OutlineInputBorder(), ), ), ), const SizedBox(width: 12), Expanded( child: TextField( key: const Key('handover.currency'), controller: _currency, decoration: InputDecoration( labelText: t.sale.currency, border: const OutlineInputBorder(), ), onChanged: (_) => setState(() {}), ), ), ], ), const SizedBox(height: 8), CurrencyQuickPicks( controller: _currency, keyPrefix: 'handover', onChanged: () => setState(() {}), ), ], if (_withPromise) ...[ const SizedBox(height: 12), TextField( key: const Key('handover.owed'), controller: _owed, decoration: InputDecoration( labelText: t.plantare.owed, helperText: t.plantare.owedHint, helperMaxLines: 2, border: const OutlineInputBorder(), ), ), ], const SizedBox(height: 20), FilledButton( key: const Key('handover.save'), onPressed: _saving ? null : _save, child: Text(t.common.save), ), ], ), ), ); } }