/// Manage labels: create, nest, and delete. library; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/transaction_meta.dart'; import '../providers/tx_meta_provider.dart'; import '../widgets/label_parent_picker.dart'; import '../widgets/responsive_body.dart'; class LabelsScreen extends StatelessWidget { const LabelsScreen({super.key}); Future _create( BuildContext context, { String? parentId, }) async { final meta = context.read(); final result = await showCreateLabelDialog( context, parents: meta.allLabels, initialParentId: parentId, ); if (result == null || !context.mounted) return; await meta.createLabel(result.name, parentId: result.parentId); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Created “${result.name}”')), ); } Future _delete(BuildContext context, TxLabel label) async { final ok = await showDialog( context: context, builder: (dialogContext) { return AlertDialog( title: Text('Remove “${label.name}”?'), content: const Text( 'This deletes the label. Transactions keep any other labels. ' 'Sub-labels move up one level.', ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: const Text('Cancel'), ), FilledButton( onPressed: () => Navigator.pop(dialogContext, true), child: const Text('Remove'), ), ], ); }, ); if (ok != true || !context.mounted) return; await context.read().removeLabel(label.id); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Removed “${label.name}”')), ); } @override Widget build(BuildContext context) { final meta = context.watch(); final theme = Theme.of(context); final colors = theme.colorScheme; final entries = meta.labelsInTreeOrder(); return Scaffold( appBar: AppBar( title: const Text('Labels'), actions: [ IconButton( tooltip: 'New label', onPressed: () => _create(context), icon: const Icon(Icons.add), ), ], ), floatingActionButton: FloatingActionButton.extended( onPressed: () => _create(context), icon: const Icon(Icons.add), label: const Text('New label'), ), body: ResponsiveBody( child: entries.isEmpty ? ListView( children: [ const SizedBox(height: 48), Icon( Icons.label_outline, size: 48, color: colors.outline, ), const SizedBox(height: 12), Text( 'No labels yet', textAlign: TextAlign.center, style: theme.textTheme.titleMedium, ), const SizedBox(height: 8), Text( 'Create labels to categorize spending.', textAlign: TextAlign.center, style: theme.textTheme.bodyMedium, ), ], ) : ListView.separated( padding: const EdgeInsets.fromLTRB(0, 8, 0, 88), itemCount: entries.length + 1, separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (context, index) { if (index == 0) { return Padding( padding: const EdgeInsets.fromLTRB(4, 4, 4, 12), child: Text( 'Create, nest under another label, or delete custom ' 'ones. Built-in labels can be moved but not deleted.', style: theme.textTheme.bodyMedium, ), ); } final entry = entries[index - 1]; final label = entry.label; final parentId = meta.parentIdOf(label.id); final parent = parentId == null ? null : meta.labelById(parentId); return ListTile( contentPadding: EdgeInsets.fromLTRB( 8 + entry.depth * 16.0, 0, 4, 0, ), leading: Icon(label.icon, color: label.color), title: Text(label.name), subtitle: Text( [ if (label.custom) 'Custom' else 'Built-in', if (parent != null) 'under ${parent.name}', ].join(' · '), ), trailing: PopupMenuButton( tooltip: 'Actions', onSelected: (value) async { switch (value) { case 'sub': await _create(context, parentId: label.id); case 'move': await showMoveLabelDialog(context, label: label); case 'delete': await _delete(context, label); } }, itemBuilder: (context) => [ if (meta.depthOf(label.id) < kMaxLabelDepth) const PopupMenuItem( value: 'sub', child: ListTile( dense: true, contentPadding: EdgeInsets.zero, leading: Icon(Icons.subdirectory_arrow_right), title: Text('Add sub-label'), ), ), const PopupMenuItem( value: 'move', child: ListTile( dense: true, contentPadding: EdgeInsets.zero, leading: Icon(Icons.account_tree_outlined), title: Text('Move under…'), ), ), if (label.custom) PopupMenuItem( value: 'delete', child: ListTile( dense: true, contentPadding: EdgeInsets.zero, leading: Icon( Icons.delete_outline, color: colors.error, ), title: Text( 'Delete', style: TextStyle(color: colors.error), ), ), ), ], ), ); }, ), ), ); } }