diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index a3bdc638d..4d32bff5a 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -20,9 +20,9 @@ final Settings settings = Settings._private(); class Settings extends ChangeNotifier { final EventChannel _platformSettingsChangeChannel = const EventChannel('deckers.thibault/aves/settings_change'); - final StreamController _updateStreamController = StreamController.broadcast(); + final StreamController _updateStreamController = StreamController.broadcast(); - Stream get updateStream => _updateStreamController.stream; + Stream get updateStream => _updateStreamController.stream; Settings._private(); @@ -356,6 +356,17 @@ class Settings extends ChangeNotifier { set hiddenFilters(Set newValue) => setAndNotify(hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList()); + void changeFilterVisibility(Set filters, bool visible) { + final _hiddenFilters = hiddenFilters; + if (visible) { + _hiddenFilters.removeAll(filters); + } else { + _hiddenFilters.addAll(filters); + searchHistory = searchHistory..removeWhere(filters.contains); + } + hiddenFilters = _hiddenFilters; + } + // viewer List get viewerQuickActions => getEnumListOrDefault(viewerQuickActionsKey, SettingsDefaults.viewerQuickActions, EntryAction.values); @@ -540,7 +551,7 @@ class Settings extends ChangeNotifier { settingsStore.setBool(key, newValue); } if (oldValue != newValue) { - _updateStreamController.add(key); + _updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue)); notifyListeners(); } } @@ -583,37 +594,39 @@ class Settings extends ChangeNotifier { await reset(includeInternalKeys: false); // apply user modifications - jsonMap.forEach((key, value) { - if (value == null) { + jsonMap.forEach((key, newValue) { + final oldValue = settingsStore.get(key); + + if (newValue == null) { settingsStore.remove(key); } else if (key.startsWith(tileExtentPrefixKey)) { - if (value is double) { - settingsStore.setDouble(key, value); + if (newValue is double) { + settingsStore.setDouble(key, newValue); } else { - debugPrint('failed to import key=$key, value=$value is not a double'); + debugPrint('failed to import key=$key, value=$newValue is not a double'); } } else if (key.startsWith(tileLayoutPrefixKey)) { - if (value is String) { - settingsStore.setString(key, value); + if (newValue is String) { + settingsStore.setString(key, newValue); } else { - debugPrint('failed to import key=$key, value=$value is not a string'); + debugPrint('failed to import key=$key, value=$newValue is not a string'); } } else { switch (key) { case subtitleTextColorKey: case subtitleBackgroundColorKey: - if (value is int) { - settingsStore.setInt(key, value); + if (newValue is int) { + settingsStore.setInt(key, newValue); } else { - debugPrint('failed to import key=$key, value=$value is not an int'); + debugPrint('failed to import key=$key, value=$newValue is not an int'); } break; case subtitleFontSizeKey: case infoMapZoomKey: - if (value is double) { - settingsStore.setDouble(key, value); + if (newValue is double) { + settingsStore.setDouble(key, newValue); } else { - debugPrint('failed to import key=$key, value=$value is not a double'); + debugPrint('failed to import key=$key, value=$newValue is not a double'); } break; case isInstalledAppAccessAllowedKey: @@ -638,10 +651,10 @@ class Settings extends ChangeNotifier { case subtitleShowOutlineKey: case saveSearchHistoryKey: case filePickerShowHiddenFilesKey: - if (value is bool) { - settingsStore.setBool(key, value); + if (newValue is bool) { + settingsStore.setBool(key, newValue); } else { - debugPrint('failed to import key=$key, value=$value is not a bool'); + debugPrint('failed to import key=$key, value=$newValue is not a bool'); } break; case localeKey: @@ -661,10 +674,10 @@ class Settings extends ChangeNotifier { case unitSystemKey: case accessibilityAnimationsKey: case timeToTakeActionKey: - if (value is String) { - settingsStore.setString(key, value); + if (newValue is String) { + settingsStore.setString(key, newValue); } else { - debugPrint('failed to import key=$key, value=$value is not a string'); + debugPrint('failed to import key=$key, value=$newValue is not a string'); } break; case confirmationDialogsKey: @@ -677,17 +690,29 @@ class Settings extends ChangeNotifier { case collectionSelectionQuickActionsKey: case viewerQuickActionsKey: case videoQuickActionsKey: - if (value is List) { - settingsStore.setStringList(key, value.cast()); + if (newValue is List) { + settingsStore.setStringList(key, newValue.cast()); } else { - debugPrint('failed to import key=$key, value=$value is not a list'); + debugPrint('failed to import key=$key, value=$newValue is not a list'); } break; } } - _updateStreamController.add(key); + if (oldValue != newValue) { + _updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue)); + } }); notifyListeners(); } } } + +@immutable +class SettingsChangedEvent { + final String key; + final dynamic oldValue; + final dynamic newValue; + + // old and new values as stored, e.g. `List` for collections + const SettingsChangedEvent(this.key, this.oldValue, this.newValue); +} diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 3f1d2fddb..ece584e15 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -79,10 +79,10 @@ class CollectionLens with ChangeNotifier { favourites.addListener(_onFavouritesChanged); } _subscriptions.add(settings.updateStream - .where([ - Settings.collectionSortFactorKey, - Settings.collectionGroupFactorKey, - ].contains) + .where((event) => [ + Settings.collectionSortFactorKey, + Settings.collectionGroupFactorKey, + ].contains(event.key)) .listen((_) => _onSettingsChanged())); _refresh(); } diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index 0d753ebbc..6a391eb55 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -45,7 +45,14 @@ mixin SourceBase { abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin, TrashMixin { CollectionSource() { - settings.updateStream.where((key) => key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames()); + settings.updateStream.where((event) => event.key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames()); + settings.updateStream.where((event) => event.key == Settings.hiddenFiltersKey).listen((event) { + final oldValue = event.oldValue; + if (oldValue is List?) { + final oldHiddenFilters = (oldValue ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); + _onFilterVisibilityChanged(oldHiddenFilters, settings.hiddenFilters); + } + }); } final EventBus _eventBus = EventBus(); @@ -441,20 +448,13 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM return recentEntry(filter); } - void changeFilterVisibility(Set filters, bool visible) { - final hiddenFilters = settings.hiddenFilters; - if (visible) { - hiddenFilters.removeAll(filters); - } else { - hiddenFilters.addAll(filters); - settings.searchHistory = settings.searchHistory..removeWhere(filters.contains); - } - settings.hiddenFilters = hiddenFilters; + void _onFilterVisibilityChanged(Set oldHiddenFilters, Set currentHiddenFilters) { updateDerivedFilters(); - eventBus.fire(FilterVisibilityChangedEvent(filters, visible)); + eventBus.fire(const FilterVisibilityChangedEvent()); - if (visible) { - final candidateEntries = visibleEntries.where((entry) => filters.any((f) => f.test(entry))).toSet(); + final newlyVisibleFilters = oldHiddenFilters.whereNot(currentHiddenFilters.contains).toSet(); + if (newlyVisibleFilters.isNotEmpty) { + final candidateEntries = visibleEntries.where((entry) => newlyVisibleFilters.any((f) => f.test(entry))).toSet(); analyze(null, entries: candidateEntries); } } diff --git a/lib/model/source/events.dart b/lib/model/source/events.dart index 582a86c2f..eed59df63 100644 --- a/lib/model/source/events.dart +++ b/lib/model/source/events.dart @@ -1,6 +1,5 @@ import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/entry.dart'; -import 'package:aves/model/filters/filters.dart'; import 'package:flutter/foundation.dart'; @immutable @@ -34,10 +33,7 @@ class EntryRefreshedEvent { @immutable class FilterVisibilityChangedEvent { - final Set filters; - final bool visible; - - const FilterVisibilityChangedEvent(this.filters, this.visible); + const FilterVisibilityChangedEvent(); } @immutable diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 1d7647990..3d34f4f6a 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -229,9 +229,9 @@ class _AvesAppState extends State with WidgetsBindingObserver { } } - settings.updateStream.where((key) => key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed()); - settings.updateStream.where((key) => key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn()); - settings.updateStream.where((key) => key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked()); + settings.updateStream.where((event) => event.key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed()); + settings.updateStream.where((event) => event.key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn()); + settings.updateStream.where((event) => event.key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked()); applyKeepScreenOn(); applyIsRotationLocked(); @@ -239,7 +239,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { Future _setupErrorReporting() async { await reportService.init(); - settings.updateStream.where((key) => key == Settings.isErrorReportingAllowedKey).listen( + settings.updateStream.where((event) => event.key == Settings.isErrorReportingAllowedKey).listen( (_) => reportService.setCollectionEnabled(settings.isErrorReportingAllowed), ); await reportService.setCollectionEnabled(settings.isErrorReportingAllowed); diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart index 4267bc1b1..2f3b4cdca 100644 --- a/lib/widgets/collection/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -40,7 +40,7 @@ class _CollectionPageState extends State { @override void initState() { super.initState(); - _subscriptions.add(settings.updateStream.where((key) => key == Settings.enableBinKey).listen((_) { + _subscriptions.add(settings.updateStream.where((event) => event.key == Settings.enableBinKey).listen((_) { if (!settings.enableBin) { collection.removeFilter(TrashFilter.instance); } diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart index 3ad653c3f..e2da8b4b6 100644 --- a/lib/widgets/debug/app_debug_page.dart +++ b/lib/widgets/debug/app_debug_page.dart @@ -132,7 +132,6 @@ class _AppDebugPageState extends State { ), ElevatedButton( onPressed: () async { - final source = context.read(); await source.init(); await source.refresh(); }, @@ -163,18 +162,16 @@ class _AppDebugPageState extends State { Future _onActionSelected(AppDebugAction action) async { switch (action) { case AppDebugAction.prepScreenshotThumbnails: - final source = context.read(); - source.changeFilterVisibility(settings.hiddenFilters, true); - source.changeFilterVisibility({ + settings.changeFilterVisibility(settings.hiddenFilters, true); + settings.changeFilterVisibility({ TagFilter('aves-thumbnail', not: true), }, false); await favourites.clear(); await favourites.add(source.visibleEntries); break; case AppDebugAction.prepScreenshotStats: - final source = context.read(); - source.changeFilterVisibility(settings.hiddenFilters, true); - source.changeFilterVisibility({ + settings.changeFilterVisibility(settings.hiddenFilters, true); + settings.changeFilterVisibility({ PathFilter('/storage/emulated/0/Pictures/Dev'), }, false); break; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip.dart b/lib/widgets/filter_grids/common/action_delegates/chip.dart index bc5c1de97..3e86c12db 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip.dart @@ -1,7 +1,7 @@ import 'package:aves/model/actions/chip_actions.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; -import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; @@ -51,8 +51,7 @@ class ChipActionDelegate { ); if (confirmed == null || !confirmed) return; - final source = context.read(); - source.changeFilterVisibility({filter}, false); + settings.changeFilterVisibility({filter}, false); } void _goTo( diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index ebfd59b05..f20f02796 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -271,8 +271,7 @@ abstract class ChipSetActionDelegate with FeedbackMi ); if (confirmed == null || !confirmed) return; - final source = context.read(); - source.changeFilterVisibility(filters, false); + settings.changeFilterVisibility(filters, false); _browse(context); } diff --git a/lib/widgets/settings/privacy/hidden_items.dart b/lib/widgets/settings/privacy/hidden_items.dart index 0c1b0f17b..7073702d1 100644 --- a/lib/widgets/settings/privacy/hidden_items.dart +++ b/lib/widgets/settings/privacy/hidden_items.dart @@ -1,7 +1,6 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/path.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -116,7 +115,7 @@ class _HiddenFilters extends StatelessWidget { .map((filter) => AvesFilterChip( filter: filter, removable: true, - onTap: (filter) => context.read().changeFilterVisibility({filter}, true), + onTap: (filter) => settings.changeFilterVisibility({filter}, true), onLongPress: null, )) .toList(), @@ -152,7 +151,7 @@ class _HiddenPaths extends StatelessWidget { trailing: IconButton( icon: const Icon(AIcons.clear), onPressed: () { - context.read().changeFilterVisibility({pathFilter}, true); + settings.changeFilterVisibility({pathFilter}, true); }, tooltip: context.l10n.actionRemove, ), @@ -176,7 +175,7 @@ class _HiddenPaths extends StatelessWidget { // wait for the dialog to hide as applying the change may block the UI await Future.delayed(Durations.pageTransitionAnimation * timeDilation); if (path != null && path.isNotEmpty) { - context.read().changeFilterVisibility({PathFilter(path)}, false); + settings.changeFilterVisibility({PathFilter(path)}, false); } }, ), diff --git a/lib/widgets/settings/video/video.dart b/lib/widgets/settings/video/video.dart index 168a94154..800ac3def 100644 --- a/lib/widgets/settings/video/video.dart +++ b/lib/widgets/settings/video/video.dart @@ -2,7 +2,6 @@ import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/video_loop_mode.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -36,7 +35,7 @@ class VideoSection extends StatelessWidget { if (!standalonePage) SwitchListTile( value: currentShowVideos, - onChanged: (v) => context.read().changeFilterVisibility({MimeFilter.video}, v), + onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v), title: Text(context.l10n.settingsVideoShowVideos), ), const VideoActionsTile(),