import 'package:flutter/material.dart'; import '../data/variety_repository.dart'; import '../db/enums.dart'; import '../i18n/strings.g.dart'; import 'theme.dart'; /// Opens the "record a sale" sheet. Optionally pre-attached to [varietyId] /// (when opened from a variety's detail). Future showSaleSheet( BuildContext context, { required VarietyRepository repository, String? varietyId, }) { return showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => _SaleSheet(repository: repository, varietyId: varietyId), ); } class _SaleSheet extends StatefulWidget { const _SaleSheet({required this.repository, this.varietyId}); final VarietyRepository repository; final String? varietyId; @override State<_SaleSheet> createState() => _SaleSheetState(); } class _SaleSheetState extends State<_SaleSheet> { SaleDirection _direction = SaleDirection.iSold; final _counterparty = TextEditingController(); final _amount = TextEditingController(); final _currency = TextEditingController(); final _note = TextEditingController(); bool _saving = false; @override void dispose() { _counterparty.dispose(); _amount.dispose(); _currency.dispose(); _note.dispose(); super.dispose(); } String? _nullIfBlank(String s) => s.trim().isEmpty ? null : s.trim(); Future _save() async { setState(() => _saving = true); await widget.repository.createSale( direction: _direction, varietyId: widget.varietyId, counterparty: _nullIfBlank(_counterparty.text), amount: double.tryParse(_amount.text.trim().replaceAll(',', '.')), currency: _nullIfBlank(_currency.text), note: _nullIfBlank(_note.text), ); if (mounted) Navigator.of(context).pop(); } @override Widget build(BuildContext context) { final t = context.t; 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.sale.add, style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 4), Text(t.sale.help, style: const TextStyle(color: seedMuted, fontSize: 13)), const SizedBox(height: 16), Text(t.sale.direction, style: Theme.of(context).textTheme.labelLarge), const SizedBox(height: 6), for (final d in SaleDirection.values) ListTile( key: Key('sale.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 == SaleDirection.iSold ? t.sale.iSold : t.sale.iBought), onTap: () => setState(() => _direction = d), ), const SizedBox(height: 8), TextField( key: const Key('sale.counterparty'), controller: _counterparty, textCapitalization: TextCapitalization.words, decoration: InputDecoration( labelText: t.sale.counterparty, helperText: t.sale.counterpartyHint, border: const OutlineInputBorder(), ), ), const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: TextField( key: const Key('sale.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('sale.currency'), controller: _currency, decoration: InputDecoration( labelText: t.sale.currency, border: const OutlineInputBorder(), ), onChanged: (_) => setState(() {}), ), ), ], ), const SizedBox(height: 8), // Quick picks so a currency is one tap. Ğ1 sits quietly among the // familiar ones — offered, never imposed — and free text still takes // anything else. Wrap( spacing: 8, children: [ for (final c in ['€', 'Ğ1', t.sale.hours]) ChoiceChip( key: Key('sale.currencyChip.$c'), label: Text(c), selected: _currency.text.trim() == c, onSelected: (sel) => setState(() { _currency.text = sel ? c : ''; }), ), ], ), const SizedBox(height: 12), TextField( key: const Key('sale.note'), controller: _note, minLines: 1, maxLines: 3, decoration: InputDecoration( labelText: t.sale.note, border: const OutlineInputBorder(), ), ), const SizedBox(height: 20), FilledButton( key: const Key('sale.save'), onPressed: _saving ? null : _save, child: Text(t.sale.save), ), ], ), ), ); } }