From c07dc36d26d95f67dce962c5306b48bfb9b8768e Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 3 Oct 2024 23:00:19 +0200 Subject: [PATCH] #1216 settings: hidden path filters are merged with others and can be toggled --- lib/l10n/app_en.arb | 5 - lib/widgets/common/search/page.dart | 1 - lib/widgets/common/search/route.dart | 8 + .../settings/privacy/hidden_items_page.dart | 246 ++++++------------ 4 files changed, 83 insertions(+), 177 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 94dc5179e..70284e0c1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -946,14 +946,9 @@ "settingsHiddenItemsTile": "Hidden items", "settingsHiddenItemsPageTitle": "Hidden Items", - "settingsHiddenItemsTabFilters": "Hidden Filters", "settingsHiddenFiltersBanner": "Photos and videos matching hidden filters will not appear in your collection.", "settingsHiddenFiltersEmpty": "No hidden filters", - "settingsHiddenItemsTabPaths": "Hidden Paths", - "settingsHiddenPathsBanner": "Photos and videos in these folders, or any of their subfolders, will not appear in your collection.", - "addPathTooltip": "Add path", - "settingsStorageAccessTile": "Storage access", "settingsStorageAccessPageTitle": "Storage Access", "settingsStorageAccessBanner": "Some directories require an explicit access grant to modify files in them. You can review here directories to which you previously gave access.", diff --git a/lib/widgets/common/search/page.dart b/lib/widgets/common/search/page.dart index 59990c644..7f218e62e 100644 --- a/lib/widgets/common/search/page.dart +++ b/lib/widgets/common/search/page.dart @@ -55,7 +55,6 @@ class _SearchPageState extends State { _unregisterWidget(widget); widget.animation.removeStatusListener(_onAnimationStatusChanged); _searchFieldFocusNode.dispose(); - widget.delegate.dispose(); super.dispose(); } diff --git a/lib/widgets/common/search/route.dart b/lib/widgets/common/search/route.dart index 6813b6892..e69a1b8cc 100644 --- a/lib/widgets/common/search/route.dart +++ b/lib/widgets/common/search/route.dart @@ -21,6 +21,14 @@ class SearchPageRoute extends PageRoute { delegate.route = this; } + @override + void dispose() { + // `delegate` is always created by the caller at route creation time, + // so it should always be disposed when the route is disposed + delegate.dispose(); + super.dispose(); + } + final AvesSearchDelegate delegate; @override diff --git a/lib/widgets/settings/privacy/hidden_items_page.dart b/lib/widgets/settings/privacy/hidden_items_page.dart index 35b8762d1..9337f62c0 100644 --- a/lib/widgets/settings/privacy/hidden_items_page.dart +++ b/lib/widgets/settings/privacy/hidden_items_page.dart @@ -1,17 +1,12 @@ import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/path.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/font_size_icon_theme.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; -import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/common/identity/empty.dart'; -import 'package:aves/widgets/settings/privacy/file_picker/file_picker_page.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; class HiddenItemsPage extends StatelessWidget { @@ -22,178 +17,87 @@ class HiddenItemsPage extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - final tabs = <(Tab, Widget)>[ - ( - Tab(text: l10n.settingsHiddenItemsTabFilters), - const _HiddenFilters(), + return AvesScaffold( + appBar: AppBar( + automaticallyImplyLeading: !settings.useTvLayout, + title: Text(l10n.settingsHiddenItemsPageTitle), ), - ( - Tab(text: l10n.settingsHiddenItemsTabPaths), - const _HiddenPaths(), - ), - ]; - - return DefaultTabController( - length: tabs.length, - child: AvesScaffold( - appBar: AppBar( - automaticallyImplyLeading: !settings.useTvLayout, - title: Text(l10n.settingsHiddenItemsPageTitle), - bottom: TabBar( - tabs: tabs.map((t) => t.$1).toList(), - ), - ), - body: SafeArea( - child: TabBarView( - children: tabs.map((t) => t.$2).toList(), - ), - ), - ), - ); - } -} - -class _HiddenFilters extends StatelessWidget { - const _HiddenFilters(); - - @override - Widget build(BuildContext context) { - bool filterPredicate(CollectionFilter v) => v is! PathFilter; - return Selector>( - selector: (context, s) => settings.hiddenFilters.where(filterPredicate).toSet(), - builder: (context, activatedHiddenFilters, child) { - return Selector>( - selector: (context, s) => settings.deactivatedHiddenFilters.where(filterPredicate).toSet(), - builder: (context, deactivatedHiddenFilters, child) { - final allHiddenFilters = { - ...activatedHiddenFilters, - ...deactivatedHiddenFilters, - }; - if (allHiddenFilters.isEmpty) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _Banner(bannerText: context.l10n.settingsHiddenFiltersBanner), - const Divider(height: 0), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8), - child: EmptyContent( - icon: AIcons.hide, - text: context.l10n.settingsHiddenFiltersEmpty, - ), - ), - ), - ], - ); - } - - final filterList = allHiddenFilters.toList()..sort(); - return ListView( - children: [ - _Banner(bannerText: context.l10n.settingsHiddenFiltersBanner), - const Divider(height: 0), - const SizedBox(height: 8), - ...filterList.map((filter) { - void onRemove(CollectionFilter filter) => settings.changeFilterVisibility({filter}, true); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Row( - children: [ - Expanded( - child: LayoutBuilder( - builder: (context, constraints) { - return Row( - children: [ - AvesFilterChip( - filter: filter, - maxWidth: constraints.maxWidth, - onTap: onRemove, - onRemove: onRemove, - onLongPress: null, - ), - const Spacer(), - ], - ); - }, + body: SafeArea( + child: Selector>( + selector: (context, s) => settings.hiddenFilters.toSet(), + builder: (context, activatedHiddenFilters, child) { + return Selector>( + selector: (context, s) => settings.deactivatedHiddenFilters.toSet(), + builder: (context, deactivatedHiddenFilters, child) { + final allHiddenFilters = { + ...activatedHiddenFilters, + ...deactivatedHiddenFilters, + }; + if (allHiddenFilters.isEmpty) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _Banner(bannerText: context.l10n.settingsHiddenFiltersBanner), + const Divider(height: 0), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8), + child: EmptyContent( + icon: AIcons.hide, + text: context.l10n.settingsHiddenFiltersEmpty, ), ), - const SizedBox(width: 8), - Switch( - value: activatedHiddenFilters.contains(filter), - onChanged: (v) => settings.activateHiddenFilter(filter, v), - ), - ], - ), + ), + ], ); - }), - ], + } + + final filterList = allHiddenFilters.toList()..sort(); + return ListView( + children: [ + _Banner(bannerText: context.l10n.settingsHiddenFiltersBanner), + const Divider(height: 0), + const SizedBox(height: 8), + ...filterList.map((filter) { + void onRemove(CollectionFilter filter) => settings.changeFilterVisibility({filter}, true); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + children: [ + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + return Row( + children: [ + AvesFilterChip( + filter: filter, + maxWidth: constraints.maxWidth, + onTap: onRemove, + onRemove: onRemove, + onLongPress: null, + ), + const Spacer(), + ], + ); + }, + ), + ), + const SizedBox(width: 8), + Switch( + value: activatedHiddenFilters.contains(filter), + onChanged: (v) => settings.activateHiddenFilter(filter, v), + ), + ], + ), + ); + }), + ], + ); + }, ); }, - ); - }, - ); - } -} - -class _HiddenPaths extends StatelessWidget { - const _HiddenPaths(); - - @override - Widget build(BuildContext context) { - return Selector>( - selector: (context, s) => { - ...settings.hiddenFilters, - ...settings.deactivatedHiddenFilters, - }.whereType().toSet(), - builder: (context, hiddenPaths, child) { - final pathList = hiddenPaths.toList()..sort(); - return Column( - children: [ - _Banner(bannerText: context.l10n.settingsHiddenPathsBanner), - const Divider(height: 0), - Flexible( - child: ListView( - shrinkWrap: true, - children: [ - ...pathList.map((pathFilter) { - void onPressed() => settings.changeFilterVisibility({pathFilter}, true); - return ListTile( - title: Text(pathFilter.path), - dense: true, - trailing: IconButton( - icon: const Icon(AIcons.clear), - onPressed: onPressed, - tooltip: context.l10n.actionRemove, - ), - onTap: settings.useTvLayout ? onPressed : null, - ); - }), - ], - ), - ), - const Divider(height: 0), - const SizedBox(height: 8), - AvesOutlinedButton( - icon: const Icon(AIcons.add), - label: context.l10n.addPathTooltip, - onPressed: () async { - final path = await Navigator.maybeOf(context)?.push( - MaterialPageRoute( - settings: const RouteSettings(name: FilePickerPage.routeName), - builder: (context) => const FilePickerPage(), - ), - ); - // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(ADurations.pageTransitionLoose * timeDilation); - if (path != null && path.isNotEmpty) { - settings.changeFilterVisibility({PathFilter(path)}, false); - } - }, - ), - ], - ); - }, + ), + ), ); } }