settings: option to hide videos

This commit is contained in:
Thibault Deckers 2021-03-20 10:12:10 +09:00
parent 6159f5ec25
commit 81c9c8a757
9 changed files with 75 additions and 33 deletions

View file

@ -525,6 +525,11 @@
"settingsViewerShowShootingDetails": "Show shooting details", "settingsViewerShowShootingDetails": "Show shooting details",
"@settingsViewerShowShootingDetails": {}, "@settingsViewerShowShootingDetails": {},
"settingsSectionVideo": "Video",
"@settingsSectionVideo": {},
"settingsVideoShowVideos": "Show videos",
"@settingsVideoShowVideos": {},
"settingsSectionSearch": "Search", "settingsSectionSearch": "Search",
"@settingsSectionSearch": {}, "@settingsSectionSearch": {},
"settingsSaveSearchHistory": "Save search history", "settingsSaveSearchHistory": "Save search history",

View file

@ -245,6 +245,9 @@
"settingsViewerShowInformationSubtitle": "제목, 날짜, 장소 등 표시", "settingsViewerShowInformationSubtitle": "제목, 날짜, 장소 등 표시",
"settingsViewerShowShootingDetails": "촬영 정보 표시", "settingsViewerShowShootingDetails": "촬영 정보 표시",
"settingsSectionVideo": "동영상",
"settingsVideoShowVideos": "미디어에 동영상 표시",
"settingsSectionSearch": "검색", "settingsSectionSearch": "검색",
"settingsSaveSearchHistory": "검색기록", "settingsSaveSearchHistory": "검색기록",

View file

@ -14,6 +14,9 @@ class MimeFilter extends CollectionFilter {
String _label; String _label;
IconData _icon; IconData _icon;
static final image = MimeFilter(MimeTypes.anyImage);
static final video = MimeFilter(MimeTypes.anyVideo);
MimeFilter(this.mime) { MimeFilter(this.mime) {
var lowMime = mime.toLowerCase(); var lowMime = mime.toLowerCase();
if (lowMime.endsWith('/*')) { if (lowMime.endsWith('/*')) {

View file

@ -7,30 +7,35 @@ import 'package:flutter/widgets.dart';
class TypeFilter extends CollectionFilter { class TypeFilter extends CollectionFilter {
static const type = 'type'; static const type = 'type';
static const animated = 'animated'; // subset of `image/gif` and `image/webp` static const _animated = 'animated'; // subset of `image/gif` and `image/webp`
static const geotiff = 'geotiff'; // subset of `image/tiff` static const _geotiff = 'geotiff'; // subset of `image/tiff`
static const panorama = 'panorama'; // subset of images static const _panorama = 'panorama'; // subset of images
static const sphericalVideo = 'spherical_video'; // subset of videos static const _sphericalVideo = 'spherical_video'; // subset of videos
final String itemType; final String itemType;
EntryFilter _test; EntryFilter _test;
IconData _icon; IconData _icon;
TypeFilter(this.itemType) { static final animated = TypeFilter._private(_animated);
static final geotiff = TypeFilter._private(_geotiff);
static final panorama = TypeFilter._private(_panorama);
static final sphericalVideo = TypeFilter._private(_sphericalVideo);
TypeFilter._private(this.itemType) {
switch (itemType) { switch (itemType) {
case animated: case _animated:
_test = (entry) => entry.isAnimated; _test = (entry) => entry.isAnimated;
_icon = AIcons.animated; _icon = AIcons.animated;
break; break;
case panorama: case _panorama:
_test = (entry) => entry.isImage && entry.is360; _test = (entry) => entry.isImage && entry.is360;
_icon = AIcons.threesixty; _icon = AIcons.threesixty;
break; break;
case sphericalVideo: case _sphericalVideo:
_test = (entry) => entry.isVideo && entry.is360; _test = (entry) => entry.isVideo && entry.is360;
_icon = AIcons.threesixty; _icon = AIcons.threesixty;
break; break;
case geotiff: case _geotiff:
_test = (entry) => entry.isGeotiff; _test = (entry) => entry.isGeotiff;
_icon = AIcons.geo; _icon = AIcons.geo;
break; break;
@ -38,7 +43,7 @@ class TypeFilter extends CollectionFilter {
} }
TypeFilter.fromMap(Map<String, dynamic> json) TypeFilter.fromMap(Map<String, dynamic> json)
: this( : this._private(
json['itemType'], json['itemType'],
); );
@ -57,13 +62,13 @@ class TypeFilter extends CollectionFilter {
@override @override
String getLabel(BuildContext context) { String getLabel(BuildContext context) {
switch (itemType) { switch (itemType) {
case animated: case _animated:
return context.l10n.filterTypeAnimatedLabel; return context.l10n.filterTypeAnimatedLabel;
case panorama: case _panorama:
return context.l10n.filterTypePanoramaLabel; return context.l10n.filterTypePanoramaLabel;
case sphericalVideo: case _sphericalVideo:
return context.l10n.filterTypeSphericalVideoLabel; return context.l10n.filterTypeSphericalVideoLabel;
case geotiff: case _geotiff:
return context.l10n.filterTypeGeotiffLabel; return context.l10n.filterTypeGeotiffLabel;
default: default:
return itemType; return itemType;

View file

@ -2,11 +2,11 @@ import 'dart:ui';
import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/album.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/location.dart'; import 'package:aves/model/source/location.dart';
import 'package:aves/model/source/tag.dart'; import 'package:aves/model/source/tag.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/services.dart'; import 'package:aves/services/services.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
@ -45,10 +45,12 @@ class _AppDrawerState extends State<AppDrawer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final hiddenFilters = settings.hiddenFilters;
final showVideos = !hiddenFilters.contains(MimeFilter.video);
final drawerItems = <Widget>[ final drawerItems = <Widget>[
_buildHeader(context), _buildHeader(context),
allCollectionTile, allCollectionTile,
videoTile, if (showVideos) videoTile,
favouriteTile, favouriteTile,
_buildSpecialAlbumSection(), _buildSpecialAlbumSection(),
Divider(), Divider(),
@ -153,7 +155,7 @@ class _AppDrawerState extends State<AppDrawer> {
Widget get videoTile => CollectionNavTile( Widget get videoTile => CollectionNavTile(
leading: Icon(AIcons.video), leading: Icon(AIcons.video),
title: context.l10n.drawerCollectionVideos, title: context.l10n.drawerCollectionVideos,
filter: MimeFilter(MimeTypes.anyVideo), filter: MimeFilter.video,
); );
Widget get favouriteTile => CollectionNavTile( Widget get favouriteTile => CollectionNavTile(

View file

@ -31,13 +31,13 @@ class CollectionSearchDelegate {
static const searchHistoryCount = 10; static const searchHistoryCount = 10;
static final typeFilters = [ static final typeFilters = [
FavouriteFilter(), FavouriteFilter(),
MimeFilter(MimeTypes.anyImage), MimeFilter.image,
MimeFilter(MimeTypes.anyVideo), MimeFilter.video,
TypeFilter.animated,
TypeFilter.panorama,
TypeFilter.sphericalVideo,
TypeFilter.geotiff,
MimeFilter(MimeTypes.svg), MimeFilter(MimeTypes.svg),
TypeFilter(TypeFilter.animated),
TypeFilter(TypeFilter.panorama),
TypeFilter(TypeFilter.sphericalVideo),
TypeFilter(TypeFilter.geotiff),
]; ];
CollectionSearchDelegate({@required this.source, this.parentCollection}); CollectionSearchDelegate({@required this.source, this.parentCollection});
@ -87,7 +87,14 @@ class CollectionSearchDelegate {
selector: (context, s) => s.hiddenFilters, selector: (context, s) => s.hiddenFilters,
builder: (context, hiddenFilters, child) { builder: (context, hiddenFilters, child) {
bool notHidden(CollectionFilter filter) => !hiddenFilters.contains(filter); bool notHidden(CollectionFilter filter) => !hiddenFilters.contains(filter);
final visibleTypeFilters = typeFilters.where(notHidden).toList();
if (hiddenFilters.contains(MimeFilter.video)) {
[MimeFilter.image, TypeFilter.sphericalVideo].forEach(visibleTypeFilters.remove);
}
final history = settings.searchHistory.where(notHidden).toList(); final history = settings.searchHistory.where(notHidden).toList();
return ListView( return ListView(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
children: [ children: [
@ -95,7 +102,7 @@ class CollectionSearchDelegate {
context: context, context: context,
filters: [ filters: [
queryFilter, queryFilter,
...typeFilters.where(notHidden), ...visibleTypeFilters,
].where((f) => f != null && containQuery(f.getLabel(context))).toList(), ].where((f) => f != null && containQuery(f.getLabel(context))).toList(),
// usually perform hero animation only on tapped chips, // usually perform hero animation only on tapped chips,
// but we also need to animate the query chip when it is selected by submitting the search query // but we also need to animate the query chip when it is selected by submitting the search query

View file

@ -1,8 +1,10 @@
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/coordinate_format.dart'; import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/home_page.dart'; import 'package:aves/model/settings/home_page.dart';
import 'package:aves/model/settings/screen_on.dart'; import 'package:aves/model/settings/screen_on.dart';
import 'package:aves/model/settings/settings.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/durations.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
@ -61,6 +63,7 @@ class _SettingsPageState extends State<SettingsPage> {
_buildDisplaySection(context), _buildDisplaySection(context),
_buildThumbnailsSection(context), _buildThumbnailsSection(context),
_buildViewerSection(context), _buildViewerSection(context),
_buildVideoSection(context),
_buildSearchSection(context), _buildSearchSection(context),
_buildPrivacySection(context), _buildPrivacySection(context),
], ],
@ -213,6 +216,22 @@ class _SettingsPageState extends State<SettingsPage> {
); );
} }
Widget _buildVideoSection(BuildContext context) {
final hiddenFilters = settings.hiddenFilters;
final showVideos = !hiddenFilters.contains(MimeFilter.video);
return AvesExpansionTile(
title: context.l10n.settingsSectionVideo,
expandedNotifier: _expandedNotifier,
children: [
SwitchListTile(
value: showVideos,
onChanged: (v) => context.read<CollectionSource>().changeFilterVisibility(MimeFilter.video, v),
title: Text(context.l10n.settingsVideoShowVideos),
),
],
);
}
Widget _buildSearchSection(BuildContext context) { Widget _buildSearchSection(BuildContext context) {
return AvesExpansionTile( return AvesExpansionTile(
title: context.l10n.settingsSectionSearch, title: context.l10n.settingsSectionSearch,

View file

@ -7,7 +7,6 @@ import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/filters/type.dart'; import 'package:aves/model/filters/type.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/services.dart'; import 'package:aves/services/services.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/file_utils.dart'; import 'package:aves/utils/file_utils.dart';
@ -78,11 +77,11 @@ class BasicSection extends StatelessWidget {
final album = entry.directory; final album = entry.directory;
final filters = { final filters = {
MimeFilter(entry.mimeType), MimeFilter(entry.mimeType),
if (entry.isAnimated) TypeFilter(TypeFilter.animated), if (entry.isAnimated) TypeFilter.animated,
if (entry.isGeotiff) TypeFilter(TypeFilter.geotiff), if (entry.isGeotiff) TypeFilter.geotiff,
if (entry.isImage && entry.is360) TypeFilter(TypeFilter.panorama), if (entry.isImage && entry.is360) TypeFilter.panorama,
if (entry.isVideo && entry.is360) TypeFilter(TypeFilter.sphericalVideo), if (entry.isVideo && entry.is360) TypeFilter.sphericalVideo,
if (entry.isVideo && !entry.is360) MimeFilter(MimeTypes.anyVideo), if (entry.isVideo && !entry.is360) MimeFilter.video,
if (album != null) AlbumFilter(album, collection?.source?.getUniqueAlbumName(context, album)), if (album != null) AlbumFilter(album, collection?.source?.getUniqueAlbumName(context, album)),
...tags.map((tag) => TagFilter(tag)), ...tags.map((tag) => TagFilter(tag)),
}; };

View file

@ -6,7 +6,6 @@ import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/filters/type.dart'; import 'package:aves/model/filters/type.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
@ -22,10 +21,10 @@ void main() {
final location = LocationFilter(LocationLevel.country, 'France${LocationFilter.locationSeparator}FR'); final location = LocationFilter(LocationLevel.country, 'France${LocationFilter.locationSeparator}FR');
expect(location, jsonRoundTrip(location)); expect(location, jsonRoundTrip(location));
final type = TypeFilter(TypeFilter.sphericalVideo); final type = TypeFilter.sphericalVideo;
expect(type, jsonRoundTrip(type)); expect(type, jsonRoundTrip(type));
final mime = MimeFilter(MimeTypes.anyVideo); final mime = MimeFilter.video;
expect(mime, jsonRoundTrip(mime)); expect(mime, jsonRoundTrip(mime));
final query = QueryFilter('some query'); final query = QueryFilter('some query');