From 083263c5614035d4597cfc9302bc5ae811bacd78 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Thu, 26 May 2022 11:58:05 +0800 Subject: [PATCH] Support sorting by filename in Photos tab --- app/lib/l10n/app_en.arb | 6 ++ app/lib/l10n/untranslated-messages.txt | 33 +++++++++ app/lib/pref.dart | 12 ++++ app/lib/widget/home_photos.dart | 78 +++++++++++++++----- app/lib/widget/settings.dart | 99 +++++++++++++++++++++++++- 5 files changed, 208 insertions(+), 20 deletions(-) diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 2b804626..91a9baf9 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -394,6 +394,12 @@ "@settingsUseBlackInDarkThemeFalseDescription": { "description": "When black in dark theme is set to false" }, + "settingsMiscellaneousTitle": "Miscellaneous", + "settingsMiscellaneousPageTitle": "Miscellaneous settings", + "settingsPhotosTabSortByNameTitle": "Sort by filename in Photos", + "@settingsPhotosTabSortByNameTitle": { + "description": "Sort photos listed in the Photos tab by filename (descending)" + }, "settingsExperimentalTitle": "Experimental", "settingsExperimentalDescription": "Features that are not ready for everyday use", "settingsExperimentalPageTitle": "Experimental settings", diff --git a/app/lib/l10n/untranslated-messages.txt b/app/lib/l10n/untranslated-messages.txt index c65b6556..edca28d4 100644 --- a/app/lib/l10n/untranslated-messages.txt +++ b/app/lib/l10n/untranslated-messages.txt @@ -20,6 +20,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "settingsExperimentalTitle", "settingsExperimentalDescription", "settingsExperimentalPageTitle", @@ -124,6 +127,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "settingsExperimentalTitle", "settingsExperimentalDescription", "settingsExperimentalPageTitle", @@ -257,6 +263,9 @@ "settingsUseBlackInDarkThemeTitle", "settingsUseBlackInDarkThemeTrueDescription", "settingsUseBlackInDarkThemeFalseDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "settingsExperimentalTitle", "settingsExperimentalDescription", "settingsExperimentalPageTitle", @@ -399,6 +408,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "rootPickerSkipConfirmationDialogContent2", "helpButtonLabel", "backgroundServiceStopping", @@ -418,6 +430,9 @@ ], "fi": [ + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "enhanceSuperResolution4xTitle", "enhanceStyleTransferTitle" ], @@ -428,6 +443,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "helpTooltip", "helpButtonLabel", "removeFromAlbumTooltip", @@ -450,6 +468,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "createCollectionTooltip", "createCollectionDialogAlbumLabel", "createCollectionDialogAlbumDescription", @@ -490,6 +511,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "enhanceTooltip", "enhanceButtonLabel", "enhanceIntroDialogTitle", @@ -509,6 +533,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "enhanceTooltip", "enhanceButtonLabel", "enhanceIntroDialogTitle", @@ -528,6 +555,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "enhanceTooltip", "enhanceButtonLabel", "enhanceIntroDialogTitle", @@ -547,6 +577,9 @@ "settingsPhotoEnhancementPageTitle", "settingsEnhanceMaxResolutionTitle", "settingsEnhanceMaxResolutionDescription", + "settingsMiscellaneousTitle", + "settingsMiscellaneousPageTitle", + "settingsPhotosTabSortByNameTitle", "enhanceTooltip", "enhanceButtonLabel", "enhanceIntroDialogTitle", diff --git a/app/lib/pref.dart b/app/lib/pref.dart index 84b62711..3aefc5b4 100644 --- a/app/lib/pref.dart +++ b/app/lib/pref.dart @@ -199,6 +199,15 @@ class Pref { Future setFirstRunTime(int value) => _set( PrefKey.firstRunTime, value, (key, value) => provider.setInt(key, value)); + bool? isPhotosTabSortByName() => + provider.getBool(PrefKey.isPhotosTabSortByName); + bool isPhotosTabSortByNameOr([bool def = false]) => + isPhotosTabSortByName() ?? def; + Future setPhotosTabSortByName(bool value) => _set( + PrefKey.isPhotosTabSortByName, + value, + (key, value) => provider.setBool(key, value)); + Future _set(PrefKey key, T value, Future Function(PrefKey key, T value) setFn) async { if (await setFn(key, value)) { @@ -492,6 +501,7 @@ enum PrefKey { enhanceMaxHeight, hasShownEnhanceInfo, firstRunTime, + isPhotosTabSortByName, // account pref isEnableFaceRecognitionApp, @@ -554,6 +564,8 @@ extension on PrefKey { return "hasShownEnhanceInfo"; case PrefKey.firstRunTime: return "firstRunTime"; + case PrefKey.isPhotosTabSortByName: + return "isPhotosTabSortByName"; // account pref case PrefKey.isEnableFaceRecognitionApp: diff --git a/app/lib/widget/home_photos.dart b/app/lib/widget/home_photos.dart index 002e3968..5541fa05 100644 --- a/app/lib/widget/home_photos.dart +++ b/app/lib/widget/home_photos.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'dart:ui'; +import 'package:collection/collection.dart' show compareNatural; import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -436,6 +437,13 @@ class _HomePhotosState extends State } else { _stopMetadataTask(); } + } else if (ev.key == PrefKey.isPhotosTabSortByName) { + if (_bloc.state is! ScanAccountDirBlocInit) { + _log.info("[_onPrefUpdated] Update view after changing sort option"); + setState(() { + _transformItems(_bloc.state.files); + }); + } } } @@ -484,6 +492,30 @@ class _HomePhotosState extends State /// Transform a File list to grid items void _transformItems(List files) { + if (!Pref().isPhotosTabSortByNameOr()) { + _transformItemsByDate(files); + } else { + _transformItemsByName(files); + } + } + + void _transformItemsByName(List files) { + _backingFiles = files + .where((f) => f.isArchived != true) + .sorted((a, b) => compareNatural(b.filename, a.filename)); + + itemStreamListItems = () sync* { + for (int i = 0; i < _backingFiles.length; ++i) { + final item = _transformItemToListItem(i, _backingFiles[i]); + if (item != null) { + yield item; + } + } + }() + .toList(); + } + + void _transformItemsByDate(List files) { _backingFiles = files .where((f) => f.isArchived != true) .sorted(compareFileDateTimeDescending); @@ -503,25 +535,9 @@ class _HomePhotosState extends State } memoryAlbumHelper.addFile(f); - final previewUrl = api_util.getFilePreviewUrl(widget.account, f, - width: k.photoThumbSize, height: k.photoThumbSize); - if (file_util.isSupportedImageFormat(f)) { - yield _ImageListItem( - file: f, - account: widget.account, - previewUrl: previewUrl, - onTap: () => _onItemTap(i), - ); - } else if (file_util.isSupportedVideoFormat(f)) { - yield _VideoListItem( - file: f, - account: widget.account, - previewUrl: previewUrl, - onTap: () => _onItemTap(i), - ); - } else { - _log.shout( - "[_transformItems] Unsupported file format: ${f.contentType}"); + final item = _transformItemToListItem(i, f); + if (item != null) { + yield item; } } }() @@ -530,6 +546,30 @@ class _HomePhotosState extends State .build((year) => L10n.global().memoryAlbumName(today.year - year)); } + _ListItem? _transformItemToListItem(int i, File f) { + final previewUrl = api_util.getFilePreviewUrl(widget.account, f, + width: k.photoThumbSize, height: k.photoThumbSize); + if (file_util.isSupportedImageFormat(f)) { + return _ImageListItem( + file: f, + account: widget.account, + previewUrl: previewUrl, + onTap: () => _onItemTap(i), + ); + } else if (file_util.isSupportedVideoFormat(f)) { + return _VideoListItem( + file: f, + account: widget.account, + previewUrl: previewUrl, + onTap: () => _onItemTap(i), + ); + } else { + _log.shout( + "[_transformItemToListItem] Unsupported file format: ${f.contentType}"); + return null; + } + } + void _reqQuery() { _bloc.add(const ScanAccountDirBlocQuery()); } diff --git a/app/lib/widget/settings.dart b/app/lib/widget/settings.dart index acefca29..c8289b16 100644 --- a/app/lib/widget/settings.dart +++ b/app/lib/widget/settings.dart @@ -66,6 +66,14 @@ class _SettingsState extends State { final settings = AccountPref.of(widget.account); _isEnableMemoryAlbum = settings.isEnableMemoryAlbumOr(true); + + _prefUpdatedListener.begin(); + } + + @override + dispose() { + _prefUpdatedListener.end(); + super.dispose(); } @override @@ -107,7 +115,9 @@ class _SettingsState extends State { title: Text(L10n.global().settingsMemoriesTitle), subtitle: Text(L10n.global().settingsMemoriesSubtitle), value: _isEnableMemoryAlbum, - onChanged: _onEnableMemoryAlbumChanged, + onChanged: Pref().isPhotosTabSortByNameOr() + ? null + : _onEnableMemoryAlbumChanged, ), _buildSubSettings( context, @@ -157,6 +167,15 @@ class _SettingsState extends State { description: L10n.global().settingsThemeDescription, builder: () => _ThemeSettings(), ), + _buildSubSettings( + context, + leading: Icon( + Icons.emoji_symbols_outlined, + color: AppTheme.getUnfocusedIconColor(context), + ), + label: L10n.global().settingsMiscellaneousTitle, + builder: () => const _MiscSettings(), + ), if (_enabledExperiments.isNotEmpty) _buildSubSettings( context, @@ -365,6 +384,12 @@ class _SettingsState extends State { } } + void _onPrefUpdated(PrefUpdatedEvent ev) { + if (ev.key == PrefKey.isPhotosTabSortByName) { + setState(() {}); + } + } + Future _setExifSupport(bool value) async { final oldValue = _isEnableExif; setState(() { @@ -385,6 +410,9 @@ class _SettingsState extends State { late bool _isEnableExif; late bool _isEnableMemoryAlbum; + late final _prefUpdatedListener = + AppEventListener(_onPrefUpdated); + static final _log = Logger("widget.settings._SettingsState"); static const String _sourceRepo = "https://bit.ly/3LQerBv"; @@ -1287,6 +1315,75 @@ class _ThemeSettingsState extends State<_ThemeSettings> { static final _log = Logger("widget.settings._ThemeSettingsState"); } +class _MiscSettings extends StatefulWidget { + const _MiscSettings({Key? key}) : super(key: key); + + @override + createState() => _MiscSettingsState(); +} + +class _MiscSettingsState extends State<_MiscSettings> { + @override + initState() { + super.initState(); + _isPhotosTabSortByName = Pref().isPhotosTabSortByNameOr(); + } + + @override + build(BuildContext context) { + return AppTheme( + child: Scaffold( + body: Builder( + builder: (context) => _buildContent(context), + ), + ), + ); + } + + Widget _buildContent(BuildContext context) { + return CustomScrollView( + slivers: [ + SliverAppBar( + pinned: true, + title: Text(L10n.global().settingsMiscellaneousPageTitle), + ), + SliverList( + delegate: SliverChildListDelegate( + [ + SwitchListTile( + title: Text(L10n.global().settingsPhotosTabSortByNameTitle), + value: _isPhotosTabSortByName, + onChanged: (value) => _onPhotosTabSortByNameChanged(value), + ), + ], + ), + ), + ], + ); + } + + void _onPhotosTabSortByNameChanged(bool value) async { + final oldValue = _isPhotosTabSortByName; + setState(() { + _isPhotosTabSortByName = value; + }); + if (!await Pref().setPhotosTabSortByName(value)) { + _log.severe("[_onPhotosTabSortByNameChanged] Failed writing pref"); + SnackBarManager().showSnackBar(SnackBar( + content: Text(L10n.global().writePreferenceFailureNotification), + duration: k.snackBarDurationNormal, + )); + setState(() { + _isPhotosTabSortByName = oldValue; + }); + } + } + + late bool _isPhotosTabSortByName; + + static final _log = Logger("widget.settings._MiscSettingsState"); +} + class _ExperimentalSettings extends StatefulWidget { @override createState() => _ExperimentalSettingsState();