custom filters
This commit is contained in:
@@ -3,6 +3,7 @@ library;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../models/home_built_in_sections.dart';
|
||||
import '../models/home_filter_widget.dart';
|
||||
import '../models/overview_favourite.dart';
|
||||
import '../services/storage_service.dart';
|
||||
@@ -12,12 +13,14 @@ class PreferencesController extends ChangeNotifier {
|
||||
_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;
|
||||
@@ -50,6 +53,31 @@ class PreferencesController extends ChangeNotifier {
|
||||
bool get hasDisplayedHomeFilterWidgets =>
|
||||
_homeFilterWidgets.any((w) => w.display);
|
||||
|
||||
HomeBuiltInSections get homeBuiltInSections => _homeBuiltInSections;
|
||||
|
||||
Future<void> setHomeBuiltInSections(HomeBuiltInSections sections) async {
|
||||
if (sections.showTransactions == _homeBuiltInSections.showTransactions &&
|
||||
sections.showCategorized == _homeBuiltInSections.showCategorized &&
|
||||
sections.showRecent == _homeBuiltInSections.showRecent) {
|
||||
return;
|
||||
}
|
||||
_homeBuiltInSections = sections;
|
||||
notifyListeners();
|
||||
await _storage.saveHomeBuiltInSections(sections);
|
||||
}
|
||||
|
||||
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) async {
|
||||
final next = years.clamp(1, 30);
|
||||
if (next == _lookAheadMaxYears) return;
|
||||
@@ -149,6 +177,19 @@ class PreferencesController extends ChangeNotifier {
|
||||
await updateHomeFilterWidget(current.copyWith(display: display));
|
||||
}
|
||||
|
||||
Future<void> reorderHomeFilterWidgets(int oldIndex, int newIndex) async {
|
||||
if (oldIndex == newIndex) return;
|
||||
final next = [..._homeFilterWidgets];
|
||||
final item = next.removeAt(oldIndex);
|
||||
final target = newIndex.clamp(0, next.length);
|
||||
next.insert(target, item);
|
||||
_homeFilterWidgets = [
|
||||
for (var i = 0; i < next.length; i++) next[i].copyWith(sortOrder: i),
|
||||
];
|
||||
notifyListeners();
|
||||
await _persistHomeWidgets();
|
||||
}
|
||||
|
||||
Future<void> replaceHomeFilterWidgets(
|
||||
List<HomeFilterWidget> widgets, {
|
||||
bool notify = true,
|
||||
|
||||
@@ -39,9 +39,9 @@ class SyncController extends ChangeNotifier {
|
||||
_bank = bank,
|
||||
_prefs = prefs {
|
||||
_wasLoggedIn = auth.isLoggedIn;
|
||||
_meta.onPersisted = _schedulePush;
|
||||
_prefs.onFavouritesPersisted = _schedulePush;
|
||||
_prefs.onHomeWidgetsPersisted = _schedulePush;
|
||||
_meta.onPersisted = _scheduleMetaPush;
|
||||
_prefs.onFavouritesPersisted = _scheduleFavouritesPush;
|
||||
_prefs.onHomeWidgetsPersisted = _scheduleHomeWidgetsPush;
|
||||
_auth.addListener(_onAuthChanged);
|
||||
_bank.onAfterSuccessfulLoad = _onBankDataLoaded;
|
||||
if (_auth.isLoggedIn) {
|
||||
@@ -58,6 +58,9 @@ class SyncController extends ChangeNotifier {
|
||||
bool _syncing = false;
|
||||
bool _pushQueued = false;
|
||||
bool _queuedFullSync = false;
|
||||
bool _pushMetaPending = false;
|
||||
bool _pushFavouritesPending = false;
|
||||
bool _pushHomeWidgetsPending = false;
|
||||
Timer? _pushDebounce;
|
||||
Timer? _progressHold;
|
||||
Timer? _uiNotifyThrottle;
|
||||
@@ -187,6 +190,10 @@ class SyncController extends ChangeNotifier {
|
||||
_progressHold?.cancel();
|
||||
_uiNotifyThrottle?.cancel();
|
||||
_uiNotifyQueued = false;
|
||||
_pushQueued = false;
|
||||
_pushMetaPending = false;
|
||||
_pushFavouritesPending = false;
|
||||
_pushHomeWidgetsPending = false;
|
||||
_lastError = null;
|
||||
_statusDetail = '';
|
||||
_showProgressRing = false;
|
||||
@@ -224,8 +231,27 @@ class SyncController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void _schedulePush() {
|
||||
void _scheduleMetaPush() {
|
||||
_pushMetaPending = true;
|
||||
_armPushDebounce();
|
||||
}
|
||||
|
||||
void _scheduleFavouritesPush() {
|
||||
_pushFavouritesPending = true;
|
||||
_armPushDebounce();
|
||||
}
|
||||
|
||||
void _scheduleHomeWidgetsPush() {
|
||||
_pushHomeWidgetsPending = true;
|
||||
_armPushDebounce();
|
||||
}
|
||||
|
||||
void _armPushDebounce() {
|
||||
if (!_auth.isLoggedIn) return;
|
||||
if (_syncing) {
|
||||
_pushQueued = true;
|
||||
return;
|
||||
}
|
||||
_pushDebounce?.cancel();
|
||||
_pushDebounce = Timer(const Duration(seconds: 2), () {
|
||||
unawaited(_pushOnly());
|
||||
@@ -370,53 +396,100 @@ class SyncController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Annotation-only push after local meta edits (avoids re-uploading 4k+ txs).
|
||||
/// Pushes only the stores that changed locally (debounced).
|
||||
///
|
||||
/// Annotation edits push tx_meta; favourite / Home-widget edits do not
|
||||
/// re-upload the full annotation store or bank transactions.
|
||||
Future<void> _pushOnly() async {
|
||||
if (!_auth.isLoggedIn || _syncing) {
|
||||
if (_syncing) _pushQueued = true;
|
||||
if (!_auth.isLoggedIn) return;
|
||||
if (_syncing) {
|
||||
_pushQueued = true;
|
||||
return;
|
||||
}
|
||||
|
||||
final pushMeta = _pushMetaPending;
|
||||
final pushFavourites = _pushFavouritesPending;
|
||||
final pushHomeWidgets = _pushHomeWidgetsPending;
|
||||
_pushMetaPending = false;
|
||||
_pushFavouritesPending = false;
|
||||
_pushHomeWidgetsPending = false;
|
||||
if (!pushMeta && !pushFavourites && !pushHomeWidgets) return;
|
||||
|
||||
_progressHold?.cancel();
|
||||
_syncing = true;
|
||||
_progressSuccess = false;
|
||||
_statusDetail = 'Pushing…';
|
||||
|
||||
final label = switch ((pushMeta, pushFavourites, pushHomeWidgets)) {
|
||||
(false, false, true) => 'Home widgets',
|
||||
(false, true, false) => 'Favourites',
|
||||
(false, true, true) => 'Home layout',
|
||||
(true, false, false) => 'Annotations',
|
||||
_ => 'Changes',
|
||||
};
|
||||
_statusDetail = 'Pushing $label…';
|
||||
_setProgress(0.05);
|
||||
_announce('Syncing…', isProgress: true);
|
||||
_announce('Saving $label…', isProgress: true);
|
||||
|
||||
try {
|
||||
final service = PbSyncService(_auth.client);
|
||||
final summary = await service.push(
|
||||
_meta.exportStore(),
|
||||
progressStart: 0.05,
|
||||
progressEnd: 0.9,
|
||||
onProgress: _setProgress,
|
||||
onStatus: _setStatus,
|
||||
);
|
||||
try {
|
||||
await service.pushOverviewFavourites(
|
||||
_prefs.overviewFavourites,
|
||||
final parts = <String>[];
|
||||
var done = 0;
|
||||
final total = (pushMeta ? 1 : 0) +
|
||||
(pushFavourites ? 1 : 0) +
|
||||
(pushHomeWidgets ? 1 : 0);
|
||||
void bump() {
|
||||
done++;
|
||||
_setProgress(done / total);
|
||||
}
|
||||
|
||||
if (pushMeta) {
|
||||
_setStatus('Pushing annotations…');
|
||||
final summary = await service.push(
|
||||
_meta.exportStore(),
|
||||
progressStart: done / total,
|
||||
progressEnd: (done + 1) / total,
|
||||
onProgress: _setProgress,
|
||||
onStatus: _setStatus,
|
||||
);
|
||||
} catch (error) {
|
||||
debugPrint(
|
||||
'SyncController: overview_favourites push skipped ($error)',
|
||||
);
|
||||
parts.add(summary);
|
||||
bump();
|
||||
}
|
||||
try {
|
||||
await service.pushHomeFilterWidgets(
|
||||
_prefs.homeFilterWidgets,
|
||||
onStatus: _setStatus,
|
||||
);
|
||||
} catch (error) {
|
||||
debugPrint(
|
||||
'SyncController: home_filter_widgets push skipped ($error)',
|
||||
);
|
||||
if (pushFavourites) {
|
||||
_setStatus('Pushing favourites…');
|
||||
try {
|
||||
await service.pushOverviewFavourites(
|
||||
_prefs.overviewFavourites,
|
||||
onStatus: _setStatus,
|
||||
);
|
||||
parts.add('Favourites updated');
|
||||
} catch (error) {
|
||||
debugPrint(
|
||||
'SyncController: overview_favourites push skipped ($error)',
|
||||
);
|
||||
}
|
||||
bump();
|
||||
}
|
||||
if (pushHomeWidgets) {
|
||||
_setStatus('Pushing home widgets…');
|
||||
try {
|
||||
await service.pushHomeFilterWidgets(
|
||||
_prefs.homeFilterWidgets,
|
||||
onStatus: _setStatus,
|
||||
);
|
||||
parts.add('Home widgets updated');
|
||||
} catch (error) {
|
||||
debugPrint(
|
||||
'SyncController: home_filter_widgets push skipped ($error)',
|
||||
);
|
||||
}
|
||||
bump();
|
||||
}
|
||||
|
||||
_setProgress(1.0);
|
||||
_lastSyncedAt = DateTime.now();
|
||||
_lastError = null;
|
||||
_lastSyncSummary = summary;
|
||||
_announce('Sync complete');
|
||||
_lastSyncSummary = parts.isEmpty ? label : parts.join('\n');
|
||||
_announce('Saved $label');
|
||||
_syncing = false;
|
||||
_finishProgress(success: true);
|
||||
} catch (error, stackTrace) {
|
||||
|
||||
Reference in New Issue
Block a user