#1216 settings: hidden path filters are merged with others and can be toggled
This commit is contained in:
parent
d859887319
commit
c07dc36d26
4 changed files with 83 additions and 177 deletions
|
@ -946,14 +946,9 @@
|
||||||
"settingsHiddenItemsTile": "Hidden items",
|
"settingsHiddenItemsTile": "Hidden items",
|
||||||
"settingsHiddenItemsPageTitle": "Hidden Items",
|
"settingsHiddenItemsPageTitle": "Hidden Items",
|
||||||
|
|
||||||
"settingsHiddenItemsTabFilters": "Hidden Filters",
|
|
||||||
"settingsHiddenFiltersBanner": "Photos and videos matching hidden filters will not appear in your collection.",
|
"settingsHiddenFiltersBanner": "Photos and videos matching hidden filters will not appear in your collection.",
|
||||||
"settingsHiddenFiltersEmpty": "No hidden filters",
|
"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",
|
"settingsStorageAccessTile": "Storage access",
|
||||||
"settingsStorageAccessPageTitle": "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.",
|
"settingsStorageAccessBanner": "Some directories require an explicit access grant to modify files in them. You can review here directories to which you previously gave access.",
|
||||||
|
|
|
@ -55,7 +55,6 @@ class _SearchPageState extends State<SearchPage> {
|
||||||
_unregisterWidget(widget);
|
_unregisterWidget(widget);
|
||||||
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
||||||
_searchFieldFocusNode.dispose();
|
_searchFieldFocusNode.dispose();
|
||||||
widget.delegate.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,14 @@ class SearchPageRoute<T> extends PageRoute<T> {
|
||||||
delegate.route = this;
|
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;
|
final AvesSearchDelegate delegate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
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/settings/settings.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/basic/font_size_icon_theme.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/basic/scaffold.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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/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/common/identity/empty.dart';
|
||||||
import 'package:aves/widgets/settings/privacy/file_picker/file_picker_page.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class HiddenItemsPage extends StatelessWidget {
|
class HiddenItemsPage extends StatelessWidget {
|
||||||
|
@ -22,178 +17,87 @@ class HiddenItemsPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
final tabs = <(Tab, Widget)>[
|
return AvesScaffold(
|
||||||
(
|
appBar: AppBar(
|
||||||
Tab(text: l10n.settingsHiddenItemsTabFilters),
|
automaticallyImplyLeading: !settings.useTvLayout,
|
||||||
const _HiddenFilters(),
|
title: Text(l10n.settingsHiddenItemsPageTitle),
|
||||||
),
|
),
|
||||||
(
|
body: SafeArea(
|
||||||
Tab(text: l10n.settingsHiddenItemsTabPaths),
|
child: Selector<Settings, Set<CollectionFilter>>(
|
||||||
const _HiddenPaths(),
|
selector: (context, s) => settings.hiddenFilters.toSet(),
|
||||||
),
|
builder: (context, activatedHiddenFilters, child) {
|
||||||
];
|
return Selector<Settings, Set<CollectionFilter>>(
|
||||||
|
selector: (context, s) => settings.deactivatedHiddenFilters.toSet(),
|
||||||
return DefaultTabController(
|
builder: (context, deactivatedHiddenFilters, child) {
|
||||||
length: tabs.length,
|
final allHiddenFilters = {
|
||||||
child: AvesScaffold(
|
...activatedHiddenFilters,
|
||||||
appBar: AppBar(
|
...deactivatedHiddenFilters,
|
||||||
automaticallyImplyLeading: !settings.useTvLayout,
|
};
|
||||||
title: Text(l10n.settingsHiddenItemsPageTitle),
|
if (allHiddenFilters.isEmpty) {
|
||||||
bottom: TabBar(
|
return Column(
|
||||||
tabs: tabs.map((t) => t.$1).toList(),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
),
|
_Banner(bannerText: context.l10n.settingsHiddenFiltersBanner),
|
||||||
body: SafeArea(
|
const Divider(height: 0),
|
||||||
child: TabBarView(
|
Expanded(
|
||||||
children: tabs.map((t) => t.$2).toList(),
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.all(8),
|
||||||
),
|
child: EmptyContent(
|
||||||
),
|
icon: AIcons.hide,
|
||||||
);
|
text: context.l10n.settingsHiddenFiltersEmpty,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HiddenFilters extends StatelessWidget {
|
|
||||||
const _HiddenFilters();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
bool filterPredicate(CollectionFilter v) => v is! PathFilter;
|
|
||||||
return Selector<Settings, Set<CollectionFilter>>(
|
|
||||||
selector: (context, s) => settings.hiddenFilters.where(filterPredicate).toSet(),
|
|
||||||
builder: (context, activatedHiddenFilters, child) {
|
|
||||||
return Selector<Settings, Set<CollectionFilter>>(
|
|
||||||
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(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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<Settings, Set<PathFilter>>(
|
|
||||||
selector: (context, s) => {
|
|
||||||
...settings.hiddenFilters,
|
|
||||||
...settings.deactivatedHiddenFilters,
|
|
||||||
}.whereType<PathFilter>().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<String>(
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue