Files
ingtrakapp/lib/providers/preferences_provider.dart
T
2026-09-11 12:31:07 +02:00

283 lines
9.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// App preferences beyond theme (Look ahead, Overview favourites, Home widgets).
library;
import 'package:flutter/foundation.dart';
import '../models/app_cloud_prefs.dart';
import '../models/home_built_in_sections.dart';
import '../models/home_filter_widget.dart';
import '../models/overview_favourite.dart';
import '../services/storage_service.dart';
class PreferencesController extends ChangeNotifier {
PreferencesController(this._storage) {
_lookAheadMaxYears = _storage.lookAheadMaxYears;
_overviewFavourites = _storage.loadOverviewFavourites();
_homeFilterWidgets = _storage.loadHomeFilterWidgets();
_homeBuiltInSections = _storage.loadHomeBuiltInSections();
}
final StorageService _storage;
late int _lookAheadMaxYears;
late List<OverviewFavourite> _overviewFavourites;
late List<HomeFilterWidget> _homeFilterWidgets;
late HomeBuiltInSections _homeBuiltInSections;
/// Fired after favourites are persisted (for cloud push debounce).
VoidCallback? onFavouritesPersisted;
/// Fired after Home widgets are persisted (for cloud push debounce).
VoidCallback? onHomeWidgetsPersisted;
/// Fired after Home sections / Look ahead are persisted.
VoidCallback? onAppPrefsPersisted;
/// Years ahead Look ahead can navigate (1–30, default 5).
int get lookAheadMaxYears => _lookAheadMaxYears;
/// Snapshot used for PocketBase `app_prefs` sync.
AppCloudPrefs get cloudPrefs => AppCloudPrefs(
homeBuiltInSections: _homeBuiltInSections,
lookAheadMaxYears: _lookAheadMaxYears,
);
List<OverviewFavourite> get overviewFavourites =>
List.unmodifiable([
for (final fav in _overviewFavourites)
if (!fav.deleted) fav,
]);
/// Full list including soft-deleted rows (for PocketBase push).
List<OverviewFavourite> get overviewFavouritesForSync =>
List.unmodifiable(_overviewFavourites);
List<OverviewFavourite> get displayedOverviewFavourites => [
for (final fav in _overviewFavourites)
if (!fav.deleted && fav.display) fav,
];
bool get hasDisplayedOverviewFavourites =>
_overviewFavourites.any((f) => !f.deleted && f.display);
List<HomeFilterWidget> get homeFilterWidgets =>
List.unmodifiable([
for (final w in _homeFilterWidgets)
if (!w.deleted) w,
]);
/// Full list including soft-deleted rows (for PocketBase push).
List<HomeFilterWidget> get homeFilterWidgetsForSync =>
List.unmodifiable(_homeFilterWidgets);
List<HomeFilterWidget> get displayedHomeFilterWidgets => [
for (final w in _homeFilterWidgets)
if (!w.deleted && w.display) w,
];
bool get hasDisplayedHomeFilterWidgets =>
_homeFilterWidgets.any((w) => !w.deleted && w.display);
HomeBuiltInSections get homeBuiltInSections => _homeBuiltInSections;
Future<void> setHomeBuiltInSections(
HomeBuiltInSections sections, {
bool schedulePush = true,
}) async {
if (sections.showTransactions == _homeBuiltInSections.showTransactions &&
sections.showCategorized == _homeBuiltInSections.showCategorized &&
sections.showRecent == _homeBuiltInSections.showRecent) {
return;
}
_homeBuiltInSections = sections;
notifyListeners();
await _storage.saveHomeBuiltInSections(sections);
if (schedulePush) onAppPrefsPersisted?.call();
}
Future<void> setHomeShowTransactions(bool value) => setHomeBuiltInSections(
_homeBuiltInSections.copyWith(showTransactions: value),
);
Future<void> setHomeShowCategorized(bool value) => setHomeBuiltInSections(
_homeBuiltInSections.copyWith(showCategorized: value),
);
Future<void> setHomeShowRecent(bool value) => setHomeBuiltInSections(
_homeBuiltInSections.copyWith(showRecent: value),
);
Future<void> setLookAheadMaxYears(
int years, {
bool schedulePush = true,
}) async {
final next = years.clamp(1, 30);
if (next == _lookAheadMaxYears) return;
_lookAheadMaxYears = next;
notifyListeners();
await _storage.setLookAheadMaxYears(next);
if (schedulePush) onAppPrefsPersisted?.call();
}
Future<void> replaceCloudPrefs(
AppCloudPrefs prefs, {
bool notify = true,
bool schedulePush = false,
}) async {
_homeBuiltInSections = prefs.homeBuiltInSections;
_lookAheadMaxYears = prefs.lookAheadMaxYears.clamp(1, 30);
if (notify) notifyListeners();
await _storage.saveHomeBuiltInSections(_homeBuiltInSections);
await _storage.setLookAheadMaxYears(_lookAheadMaxYears);
if (schedulePush) onAppPrefsPersisted?.call();
}
Future<void> addOverviewFavourite(OverviewFavourite favourite) async {
final stamped = favourite.updatedAt == 0
? favourite.copyWith(touchUpdatedAt: true)
: favourite;
_overviewFavourites = [..._overviewFavourites, stamped];
notifyListeners();
await _persistFavourites();
}
Future<void> updateOverviewFavourite(OverviewFavourite favourite) async {
final index = _overviewFavourites.indexWhere((f) => f.id == favourite.id);
if (index < 0) return;
final next = [..._overviewFavourites];
next[index] = favourite.copyWith(touchUpdatedAt: true);
_overviewFavourites = next;
notifyListeners();
await _persistFavourites();
}
Future<void> removeOverviewFavourite(String id) async {
final index = _overviewFavourites.indexWhere((f) => f.id == id);
if (index < 0) return;
final current = _overviewFavourites[index];
if (current.deleted) return;
final next = [..._overviewFavourites];
next[index] = current.copyWith(deleted: true, touchUpdatedAt: true);
_overviewFavourites = next;
notifyListeners();
await _persistFavourites();
}
Future<void> setOverviewFavouriteDisplay(String id, bool display) async {
final index = _overviewFavourites.indexWhere((f) => f.id == id);
if (index < 0) return;
final current = _overviewFavourites[index];
if (current.deleted || current.display == display) return;
await updateOverviewFavourite(current.copyWith(display: display));
}
Future<void> replaceOverviewFavourites(
List<OverviewFavourite> favourites, {
bool notify = true,
bool schedulePush = false,
}) async {
_overviewFavourites = List.of(favourites);
if (notify) notifyListeners();
await _storage.saveOverviewFavourites(_overviewFavourites);
if (schedulePush) onFavouritesPersisted?.call();
}
Future<void> addHomeFilterWidget(HomeFilterWidget widget) async {
final order = _homeFilterWidgets.where((w) => !w.deleted).isEmpty
? 0
: _homeFilterWidgets
.where((w) => !w.deleted)
.map((w) => w.sortOrder)
.reduce((a, b) => a > b ? a : b) +
1;
final stamped = (widget.updatedAt == 0
? widget.copyWith(touchUpdatedAt: true)
: widget)
.copyWith(
sortOrder: widget.sortOrder == 0 ? order : widget.sortOrder,
);
_homeFilterWidgets = [..._homeFilterWidgets, stamped];
notifyListeners();
await _persistHomeWidgets();
}
Future<void> updateHomeFilterWidget(HomeFilterWidget widget) async {
final index = _homeFilterWidgets.indexWhere((w) => w.id == widget.id);
if (index < 0) return;
final next = [..._homeFilterWidgets];
next[index] = widget.copyWith(touchUpdatedAt: true);
next.sort((a, b) => a.sortOrder.compareTo(b.sortOrder));
_homeFilterWidgets = next;
notifyListeners();
await _persistHomeWidgets();
}
Future<void> removeHomeFilterWidget(String id) async {
final index = _homeFilterWidgets.indexWhere((w) => w.id == id);
if (index < 0) return;
final current = _homeFilterWidgets[index];
if (current.deleted) return;
final next = [..._homeFilterWidgets];
next[index] = current.copyWith(deleted: true, touchUpdatedAt: true);
_homeFilterWidgets = next;
notifyListeners();
await _persistHomeWidgets();
}
Future<void> setHomeFilterWidgetDisplay(String id, bool display) async {
final index = _homeFilterWidgets.indexWhere((w) => w.id == id);
if (index < 0) return;
final current = _homeFilterWidgets[index];
if (current.deleted || current.display == display) return;
await updateHomeFilterWidget(current.copyWith(display: display));
}
Future<void> reorderHomeFilterWidgets(int oldIndex, int newIndex) async {
if (oldIndex == newIndex) return;
final visible = [
for (final w in _homeFilterWidgets)
if (!w.deleted) w,
];
if (oldIndex < 0 || oldIndex >= visible.length) return;
final nextVisible = [...visible];
final item = nextVisible.removeAt(oldIndex);
final target = newIndex.clamp(0, nextVisible.length);
nextVisible.insert(target, item);
final orderById = <String, int>{
for (var i = 0; i < nextVisible.length; i++) nextVisible[i].id: i,
};
final now = DateTime.now().millisecondsSinceEpoch;
_homeFilterWidgets = [
for (final w in _homeFilterWidgets)
if (w.deleted)
w
else
w.copyWith(sortOrder: orderById[w.id] ?? w.sortOrder, updatedAt: now),
]..sort((a, b) => a.sortOrder.compareTo(b.sortOrder));
notifyListeners();
await _persistHomeWidgets();
}
Future<void> replaceHomeFilterWidgets(
List<HomeFilterWidget> widgets, {
bool notify = true,
bool schedulePush = false,
}) async {
final next = List.of(widgets)
..sort((a, b) => a.sortOrder.compareTo(b.sortOrder));
_homeFilterWidgets = next;
if (notify) notifyListeners();
await _storage.saveHomeFilterWidgets(_homeFilterWidgets);
if (schedulePush) onHomeWidgetsPersisted?.call();
}
Future<void> _persistFavourites() async {
await _storage.saveOverviewFavourites(_overviewFavourites);
onFavouritesPersisted?.call();
}
Future<void> _persistHomeWidgets() async {
await _storage.saveHomeFilterWidgets(_homeFilterWidgets);
onHomeWidgetsPersisted?.call();
}
}