/// Bottom sheet to add or edit a priced line item. library; import 'package:flutter/material.dart'; import '../models/line_item.dart'; import '../utils/money.dart'; Future showItemEditSheet(BuildContext context, {LineItem? item}) { return showModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, builder: (context) => ItemEditSheet(item: item), ); } class ItemEditSheet extends StatefulWidget { const ItemEditSheet({super.key, this.item}); final LineItem? item; @override State createState() => _ItemEditSheetState(); } class _ItemEditSheetState extends State { late final TextEditingController _nameController; late final TextEditingController _priceController; String? _error; @override void initState() { super.initState(); final item = widget.item; _nameController = TextEditingController(text: item?.name ?? ''); _priceController = TextEditingController( text: item == null ? '' : item.price.toStringAsFixed(2), ); } @override void dispose() { _nameController.dispose(); _priceController.dispose(); super.dispose(); } void _save() { final name = _nameController.text.trim(); final price = tryParsePrice(_priceController.text); if (name.isEmpty || price == null) { setState(() => _error = 'Enter a product name and a price.'); return; } final rounded = roundMoney(price); final quantity = widget.item?.quantity; Navigator.of(context).pop( LineItem( id: widget.item?.id ?? 'manual-${DateTime.now().microsecondsSinceEpoch}', name: name, price: rounded, quantity: quantity, unitPrice: quantity != null && quantity > 0 ? roundMoney(rounded / quantity) : widget.item?.unitPrice, ), ); } @override Widget build(BuildContext context) { final bottom = MediaQuery.viewInsetsOf(context).bottom; return Padding( padding: EdgeInsets.fromLTRB(16, 0, 16, 16 + bottom), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( widget.item == null ? 'Add item' : 'Edit item', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), TextField( controller: _nameController, textCapitalization: TextCapitalization.sentences, decoration: const InputDecoration(labelText: 'Product name'), autofocus: widget.item == null, ), const SizedBox(height: 12), TextField( controller: _priceController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Price'), ), if (_error != null) ...[ const SizedBox(height: 8), Text( _error!, style: TextStyle(color: Theme.of(context).colorScheme.error), ), ], const SizedBox(height: 16), FilledButton(onPressed: _save, child: const Text('Save')), ], ), ); } }