new widget types

This commit is contained in:
davmar
2026-09-10 17:31:18 +02:00
parent 2727f1baf2
commit 86b5f1e09b
8 changed files with 817 additions and 89 deletions
+129 -2
View File
@@ -1,10 +1,77 @@
/// Named transaction-filter widgets pinned on the Home screen.
library;
import 'package:flutter/material.dart';
import '../widgets/label_filter_dialog.dart';
import 'transaction_meta.dart';
/// A saved [TransactionFilterSelection] plus which metrics to show on Home.
/// What a Home filter widget shows.
enum HomeWidgetType {
/// In / Out / count metrics.
stats,
/// Progress of this month’s net toward a budget cap.
budget,
}
/// How the budget ceiling is chosen.
enum HomeBudgetCapMode {
/// User-entered amount (positive).
fixed,
/// Absolute average of monthly nets over the last N months.
averageMonths,
}
/// Budget progress visualization.
enum HomeWidgetChartStyle {
bar,
pie,
ring,
}
/// Preset icons users can assign to a Home widget.
abstract final class HomeWidgetIcons {
static const List<(String id, IconData icon, String label)> all = [
('dashboard', Icons.dashboard_outlined, 'Dashboard'),
('savings', Icons.savings_outlined, 'Savings'),
('wallet', Icons.account_balance_wallet_outlined, 'Wallet'),
('cart', Icons.shopping_cart_outlined, 'Shopping'),
('bag', Icons.shopping_bag_outlined, 'Bag'),
('food', Icons.restaurant_outlined, 'Food'),
('bolt', Icons.bolt_outlined, 'Utilities'),
('home', Icons.home_work_outlined, 'Home'),
('car', Icons.directions_car_outlined, 'Transport'),
('bus', Icons.directions_bus_outlined, 'Transit'),
('health', Icons.local_hospital_outlined, 'Health'),
('sport', Icons.fitness_center_outlined, 'Sport'),
('travel', Icons.flight_outlined, 'Travel'),
('gift', Icons.card_giftcard_outlined, 'Gifts'),
('pets', Icons.pets_outlined, 'Pets'),
('work', Icons.work_outline, 'Work'),
('pay', Icons.payments_outlined, 'Pay'),
('swap', Icons.swap_horiz, 'Transfers'),
('chart', Icons.pie_chart_outline, 'Chart'),
('flag', Icons.flag_outlined, 'Goal'),
];
static IconData iconFor(String? id) {
for (final entry in all) {
if (entry.$1 == id) return entry.$2;
}
return Icons.dashboard_outlined;
}
static String labelFor(String? id) {
for (final entry in all) {
if (entry.$1 == id) return entry.$3;
}
return 'Dashboard';
}
}
/// A saved [TransactionFilterSelection] plus Home display options.
class HomeFilterWidget {
const HomeFilterWidget({
required this.id,
@@ -12,6 +79,12 @@ class HomeFilterWidget {
this.display = true,
this.sortOrder = 0,
this.halfWidth = false,
this.type = HomeWidgetType.stats,
this.iconId = 'dashboard',
this.chartStyle = HomeWidgetChartStyle.bar,
this.budgetCapMode = HomeBudgetCapMode.fixed,
this.budgetFixedAmount = 0,
this.budgetAverageMonths = 3,
this.labelIds = const [],
this.flow = TxFlowFilter.all,
this.amountRange = const TxAmountRange(),
@@ -30,6 +103,17 @@ class HomeFilterWidget {
/// When true, the Home card uses half the row width.
final bool halfWidth;
final HomeWidgetType type;
final String iconId;
final HomeWidgetChartStyle chartStyle;
final HomeBudgetCapMode budgetCapMode;
/// Positive ceiling when [budgetCapMode] is [HomeBudgetCapMode.fixed].
final double budgetFixedAmount;
/// Months of history when [budgetCapMode] is [HomeBudgetCapMode.averageMonths].
final int budgetAverageMonths;
final List<String> labelIds;
final TxFlowFilter flow;
final TxAmountRange amountRange;
@@ -40,6 +124,8 @@ class HomeFilterWidget {
final bool showAmountOut;
final bool showCount;
IconData get icon => HomeWidgetIcons.iconFor(iconId);
TransactionFilterSelection get filter => TransactionFilterSelection(
labelIds: labelIds.toSet(),
flow: flow,
@@ -54,6 +140,12 @@ class HomeFilterWidget {
bool? display,
int? sortOrder,
bool? halfWidth,
HomeWidgetType? type,
String? iconId,
HomeWidgetChartStyle? chartStyle,
HomeBudgetCapMode? budgetCapMode,
double? budgetFixedAmount,
int? budgetAverageMonths,
List<String>? labelIds,
TxFlowFilter? flow,
TxAmountRange? amountRange,
@@ -69,6 +161,12 @@ class HomeFilterWidget {
display: display ?? this.display,
sortOrder: sortOrder ?? this.sortOrder,
halfWidth: halfWidth ?? this.halfWidth,
type: type ?? this.type,
iconId: iconId ?? this.iconId,
chartStyle: chartStyle ?? this.chartStyle,
budgetCapMode: budgetCapMode ?? this.budgetCapMode,
budgetFixedAmount: budgetFixedAmount ?? this.budgetFixedAmount,
budgetAverageMonths: budgetAverageMonths ?? this.budgetAverageMonths,
labelIds: labelIds ?? this.labelIds,
flow: flow ?? this.flow,
amountRange: amountRange ?? this.amountRange,
@@ -110,6 +208,12 @@ class HomeFilterWidget {
'display': display,
'sortOrder': sortOrder,
'halfWidth': halfWidth,
'type': type.name,
'iconId': iconId,
'chartStyle': chartStyle.name,
'budgetCapMode': budgetCapMode.name,
'budgetFixedAmount': budgetFixedAmount,
'budgetAverageMonths': budgetAverageMonths,
'labelIds': labelIds,
'flow': flow.name,
'amountRange': {
@@ -134,6 +238,12 @@ class HomeFilterWidget {
return null;
}
int asInt(dynamic value, int fallback) {
if (value is int) return value;
if (value is num) return value.round();
return fallback;
}
return HomeFilterWidget(
id: json['id'] as String? ??
'hw_${DateTime.now().millisecondsSinceEpoch}',
@@ -141,8 +251,25 @@ class HomeFilterWidget {
? (json['name'] as String).trim()
: 'Filter',
display: json['display'] as bool? ?? true,
sortOrder: json['sortOrder'] as int? ?? 0,
sortOrder: asInt(json['sortOrder'], 0),
halfWidth: json['halfWidth'] as bool? ?? false,
type: HomeWidgetType.values.firstWhere(
(e) => e.name == json['type'],
orElse: () => HomeWidgetType.stats,
),
iconId: (json['iconId'] as String?)?.trim().isNotEmpty == true
? (json['iconId'] as String).trim()
: 'dashboard',
chartStyle: HomeWidgetChartStyle.values.firstWhere(
(e) => e.name == json['chartStyle'],
orElse: () => HomeWidgetChartStyle.bar,
),
budgetCapMode: HomeBudgetCapMode.values.firstWhere(
(e) => e.name == json['budgetCapMode'],
orElse: () => HomeBudgetCapMode.fixed,
),
budgetFixedAmount: asDouble(json['budgetFixedAmount']) ?? 0,
budgetAverageMonths: asInt(json['budgetAverageMonths'], 3).clamp(1, 36),
labelIds: [
for (final id in (json['labelIds'] as List? ?? const []))
if (id is String && id.isNotEmpty) id,