/// Named shopping lists of things to buy. library; import 'package:flutter/material.dart'; import 'models/shopping_list.dart'; import 'services/product_repository.dart'; import 'shopping_list_detail_screen.dart'; import 'utils/dates.dart'; import 'widgets/confirm_dialog.dart'; import 'widgets/name_dialog.dart'; import 'widgets/profile_button.dart'; import 'widgets/settings_button.dart'; class ShoppingListsScreen extends StatefulWidget { const ShoppingListsScreen({super.key, required this.repository}); final ProductRepository repository; @override State createState() => _ShoppingListsScreenState(); } class _ShoppingListsScreenState extends State { List? _lists; Object? _error; bool _editing = false; int _loadToken = 0; bool get _hasLists => _lists != null && _lists!.isNotEmpty; @override void initState() { super.initState(); widget.repository.addListener(_load); _load(); } @override void dispose() { widget.repository.removeListener(_load); super.dispose(); } Future _load() async { final token = ++_loadToken; try { final lists = await widget.repository.getShoppingLists(); if (!mounted || token != _loadToken) return; setState(() { _lists = lists; _error = null; if (lists.isEmpty) _editing = false; }); } catch (error) { if (!mounted || token != _loadToken) return; setState(() => _error = error); } } Future _create() async { final name = await showNameDialog( context, title: 'New shopping list', label: 'List name', confirmLabel: 'Create', ); if (name == null || !mounted) return; final id = await widget.repository.createShoppingList(name); if (!mounted) return; await _openList(id); } Future _openList(int id) async { await Navigator.of(context).push( MaterialPageRoute( builder: (_) => ShoppingListDetailScreen(repository: widget.repository, listId: id), ), ); if (mounted) _load(); } Future _removeList(ShoppingList list) async { final confirmed = await confirmAction( context, title: 'Delete shopping list?', message: '"${list.name}" and its items will be deleted.', ); if (!confirmed || !mounted) return; setState(() { _lists = [ for (final item in _lists ?? const []) if (item.id != list.id) item, ]; if (_lists!.isEmpty) _editing = false; }); try { await widget.repository.deleteShoppingList(list.id); } catch (_) { if (mounted) _load(); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: const Text('Shopping lists'), actions: [ if (_hasLists) IconButton( tooltip: _editing ? 'Done' : 'Edit', onPressed: () => setState(() => _editing = !_editing), icon: Icon(_editing ? Icons.check : Icons.edit_outlined), ), const ProfileButton(), const SettingsButton(), ], ), floatingActionButton: FloatingActionButton( onPressed: _create, tooltip: 'New list', child: const Icon(Icons.add), ), body: _error != null && _lists == null ? Center( child: Text( 'Could not load shopping lists.', style: theme.textTheme.bodyLarge, ), ) : _lists == null ? const Center(child: CircularProgressIndicator()) : _lists!.isEmpty ? Center( child: Text( 'Create a list for your next shop.', textAlign: TextAlign.center, style: theme.textTheme.bodyLarge, ), ) : ListView.separated( padding: const EdgeInsets.fromLTRB(8, 8, 8, 88), itemCount: _lists!.length, separatorBuilder: (_, _) => const SizedBox(height: 4), itemBuilder: (context, index) { final list = _lists![index]; final remaining = list.itemCount - list.checkedCount; return ListTile( leading: CircleAvatar(child: Text('${list.itemCount}')), title: Text('${list.name} (${formatDate(list.createdAt)})'), subtitle: Text( list.itemCount == 0 ? 'Empty' : remaining == 0 ? 'All done' : remaining == 1 ? '1 item left' : '$remaining items left', ), trailing: _editing ? IconButton( tooltip: 'Delete', onPressed: () => _removeList(list), icon: const Icon(Icons.delete_outline), ) : const Icon(Icons.chevron_right), onTap: _editing ? null : () => _openList(list.id), ); }, ), ); } }