/// Local transaction categories and per-transaction annotations. library; import 'package:flutter/material.dart'; /// User-facing spend / income categories (stored locally, not on the bank API). enum TxCategory { uncategorized, groceries, energy, salary, mortgage, transport, dining, shopping, health, transfer, other, } extension TxCategoryX on TxCategory { String get label => switch (this) { TxCategory.uncategorized => 'Uncategorized', TxCategory.groceries => 'Groceries', TxCategory.energy => 'Energy', TxCategory.salary => 'Salary', TxCategory.mortgage => 'Mortgage', TxCategory.transport => 'Transport', TxCategory.dining => 'Dining', TxCategory.shopping => 'Shopping', TxCategory.health => 'Health', TxCategory.transfer => 'Transfer', TxCategory.other => 'Other', }; IconData get icon => switch (this) { TxCategory.uncategorized => Icons.label_outline, TxCategory.groceries => Icons.shopping_cart_outlined, TxCategory.energy => Icons.bolt_outlined, TxCategory.salary => Icons.payments_outlined, TxCategory.mortgage => Icons.home_work_outlined, TxCategory.transport => Icons.directions_bus_outlined, TxCategory.dining => Icons.restaurant_outlined, TxCategory.shopping => Icons.shopping_bag_outlined, TxCategory.health => Icons.local_hospital_outlined, TxCategory.transfer => Icons.swap_horiz, TxCategory.other => Icons.more_horiz, }; Color get color => switch (this) { TxCategory.uncategorized => const Color(0xFF64748B), TxCategory.groceries => const Color(0xFF16A34A), TxCategory.energy => const Color(0xFFEAB308), TxCategory.salary => const Color(0xFF0F766E), TxCategory.mortgage => const Color(0xFF7C3AED), TxCategory.transport => const Color(0xFF2563EB), TxCategory.dining => const Color(0xFFEA580C), TxCategory.shopping => const Color(0xFFDB2777), TxCategory.health => const Color(0xFFDC2626), TxCategory.transfer => const Color(0xFF0891B2), TxCategory.other => const Color(0xFF57534E), }; static TxCategory fromName(String? name) { if (name == null || name.isEmpty) return TxCategory.uncategorized; return TxCategory.values.firstWhere( (c) => c.name == name, orElse: () => TxCategory.uncategorized, ); } } /// Local overlay for a bank transaction. class TxMeta { const TxMeta({ this.category = TxCategory.uncategorized, this.recurring = false, this.notes = '', }); final TxCategory category; final bool recurring; final String notes; static const empty = TxMeta(); bool get hasCategory => category != TxCategory.uncategorized; bool get hasNotes => notes.trim().isNotEmpty; TxMeta copyWith({ TxCategory? category, bool? recurring, String? notes, }) { return TxMeta( category: category ?? this.category, recurring: recurring ?? this.recurring, notes: notes ?? this.notes, ); } Map toJson() => { 'category': category.name, 'recurring': recurring, 'notes': notes, }; factory TxMeta.fromJson(Map json) { return TxMeta( category: TxCategoryX.fromName(json['category'] as String?), recurring: json['recurring'] as bool? ?? false, notes: json['notes'] as String? ?? '', ); } } /// Notes / recurring template keyed by exact merchant+amount match. class TxExactTemplate { const TxExactTemplate({this.notes = '', this.recurring = false}); final String notes; final bool recurring; Map toJson() => { 'notes': notes, 'recurring': recurring, }; factory TxExactTemplate.fromJson(Map json) { return TxExactTemplate( notes: json['notes'] as String? ?? '', recurring: json['recurring'] as bool? ?? false, ); } }