Files
ingtrakapp/lib/models/overview_favourite.dart
2026-09-11 12:31:07 +02:00

158 lines
4.6 KiB
Dart

/// Named Overview chart filter snapshots (local favourites).
library;
import '../models/transaction_meta.dart';
import '../widgets/label_filter_dialog.dart';
/// Persisted Overview lens: period, metric, and Labels-chart options.
class OverviewFavourite {
const OverviewFavourite({
required this.id,
required this.name,
this.display = true,
this.metric = OverviewMetricMode.flow,
this.period = OverviewPeriodMode.monthly,
this.labelIds = const [],
this.flow = TxFlowFilter.expense,
this.barStyle = LabelChartBarStyle.parallel,
this.invertExpensePerspective = false,
this.updatedAt = 0,
this.deleted = false,
});
final String id;
final String name;
/// When true, this favourite appears on the Favourites overview view.
final bool display;
final OverviewMetricMode metric;
final OverviewPeriodMode period;
/// Empty = all labels (Labels metric only).
final List<String> labelIds;
final TxFlowFilter flow;
final LabelChartBarStyle barStyle;
final bool invertExpensePerspective;
/// Epoch ms; used for last-write-wins sync across devices.
final int updatedAt;
/// Soft-delete so removals propagate via sync instead of being recreated.
final bool deleted;
OverviewFavourite copyWith({
String? id,
String? name,
bool? display,
OverviewMetricMode? metric,
OverviewPeriodMode? period,
List<String>? labelIds,
TxFlowFilter? flow,
LabelChartBarStyle? barStyle,
bool? invertExpensePerspective,
int? updatedAt,
bool? deleted,
bool touchUpdatedAt = false,
}) {
return OverviewFavourite(
id: id ?? this.id,
name: name ?? this.name,
display: display ?? this.display,
metric: metric ?? this.metric,
period: period ?? this.period,
labelIds: labelIds ?? this.labelIds,
flow: flow ?? this.flow,
barStyle: barStyle ?? this.barStyle,
invertExpensePerspective:
invertExpensePerspective ?? this.invertExpensePerspective,
updatedAt: touchUpdatedAt
? DateTime.now().millisecondsSinceEpoch
: (updatedAt ?? this.updatedAt),
deleted: deleted ?? this.deleted,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'display': display,
'metric': metric.name,
'period': period.name,
'labelIds': labelIds,
'flow': flow.name,
'barStyle': barStyle.name,
'invertExpensePerspective': invertExpensePerspective,
'updatedAt': updatedAt,
'deleted': deleted,
};
factory OverviewFavourite.fromJson(Map<String, dynamic> json) {
final rawUpdated = json['updatedAt'] ?? json['updated_at'];
final updatedAt = switch (rawUpdated) {
int n => n,
num n => n.round(),
String s => int.tryParse(s) ?? 0,
_ => 0,
};
return OverviewFavourite(
id: json['id'] as String? ?? 'fav_${DateTime.now().millisecondsSinceEpoch}',
name: (json['name'] as String?)?.trim().isNotEmpty == true
? (json['name'] as String).trim()
: 'Favourite',
display: json['display'] as bool? ?? true,
metric: OverviewMetricMode.values.firstWhere(
(e) => e.name == json['metric'],
orElse: () => OverviewMetricMode.flow,
),
period: OverviewPeriodMode.values.firstWhere(
(e) => e.name == json['period'],
orElse: () => OverviewPeriodMode.monthly,
),
labelIds: [
for (final id in (json['labelIds'] as List? ?? const []))
if (id is String && id.isNotEmpty) id,
],
flow: TxFlowFilter.values.firstWhere(
(e) => e.name == json['flow'],
orElse: () => TxFlowFilter.expense,
),
barStyle: LabelChartBarStyle.values.firstWhere(
(e) => e.name == json['barStyle'],
orElse: () => LabelChartBarStyle.parallel,
),
invertExpensePerspective:
json['invertExpensePerspective'] as bool? ?? false,
updatedAt: updatedAt,
deleted: json['deleted'] as bool? ?? false,
);
}
}
enum OverviewMetricMode { flow, labels }
enum OverviewPeriodMode { monthly, yearly }
/// Union favourites by id with last-write-wins on [OverviewFavourite.updatedAt].
List<OverviewFavourite> mergeOverviewFavourites(
List<OverviewFavourite> local,
List<OverviewFavourite> remote,
) {
final byId = <String, OverviewFavourite>{};
void put(OverviewFavourite fav) {
final existing = byId[fav.id];
if (existing == null || fav.updatedAt >= existing.updatedAt) {
byId[fav.id] = fav;
}
}
for (final fav in remote) {
put(fav);
}
for (final fav in local) {
put(fav);
}
return byId.values.toList(growable: false);
}