new widget types
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user