Files
ingtrakapp/lib/models/transaction_meta.dart
T
2026-09-06 21:58:59 +02:00

136 lines
3.9 KiB
Dart

/// 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<String, dynamic> toJson() => {
'category': category.name,
'recurring': recurring,
'notes': notes,
};
factory TxMeta.fromJson(Map<String, dynamic> 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<String, dynamic> toJson() => {
'notes': notes,
'recurring': recurring,
};
factory TxExactTemplate.fromJson(Map<String, dynamic> json) {
return TxExactTemplate(
notes: json['notes'] as String? ?? '',
recurring: json['recurring'] as bool? ?? false,
);
}
}