From c31b64535d3cedbbc272738d0a63aab70c685e1b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 5 May 2025 19:14:40 +0200 Subject: [PATCH] #268 album grouping (WIP) --- .../channel/calls/MetadataFetchHandler.kt | 6 +- .../channel/calls/fetchers/RegionFetcher.kt | 2 +- lib/l10n/app_en.arb | 25 +- lib/l10ngen/app_localizations.dart | 110 +++++--- lib/l10ngen/app_localizations_ar.dart | 53 ++-- lib/l10ngen/app_localizations_az.dart | 55 ++-- lib/l10ngen/app_localizations_be.dart | 53 ++-- lib/l10ngen/app_localizations_bg.dart | 53 ++-- lib/l10ngen/app_localizations_bn.dart | 55 ++-- lib/l10ngen/app_localizations_ca.dart | 53 ++-- lib/l10ngen/app_localizations_ckb.dart | 55 ++-- lib/l10ngen/app_localizations_cs.dart | 53 ++-- lib/l10ngen/app_localizations_da.dart | 53 ++-- lib/l10ngen/app_localizations_de.dart | 53 ++-- lib/l10ngen/app_localizations_el.dart | 53 ++-- lib/l10ngen/app_localizations_en.dart | 76 +++--- lib/l10ngen/app_localizations_es.dart | 53 ++-- lib/l10ngen/app_localizations_et.dart | 53 ++-- lib/l10ngen/app_localizations_eu.dart | 53 ++-- lib/l10ngen/app_localizations_fa.dart | 53 ++-- lib/l10ngen/app_localizations_fi.dart | 55 ++-- lib/l10ngen/app_localizations_fr.dart | 53 ++-- lib/l10ngen/app_localizations_gl.dart | 53 ++-- lib/l10ngen/app_localizations_he.dart | 55 ++-- lib/l10ngen/app_localizations_hi.dart | 53 ++-- lib/l10ngen/app_localizations_hu.dart | 53 ++-- lib/l10ngen/app_localizations_id.dart | 53 ++-- lib/l10ngen/app_localizations_is.dart | 53 ++-- lib/l10ngen/app_localizations_it.dart | 53 ++-- lib/l10ngen/app_localizations_ja.dart | 53 ++-- lib/l10ngen/app_localizations_kn.dart | 53 ++-- lib/l10ngen/app_localizations_ko.dart | 53 ++-- lib/l10ngen/app_localizations_lt.dart | 53 ++-- lib/l10ngen/app_localizations_ml.dart | 55 ++-- lib/l10ngen/app_localizations_my.dart | 53 ++-- lib/l10ngen/app_localizations_nb.dart | 53 ++-- lib/l10ngen/app_localizations_ne.dart | 55 ++-- lib/l10ngen/app_localizations_nl.dart | 53 ++-- lib/l10ngen/app_localizations_nn.dart | 53 ++-- lib/l10ngen/app_localizations_or.dart | 53 ++-- lib/l10ngen/app_localizations_pl.dart | 53 ++-- lib/l10ngen/app_localizations_pt.dart | 53 ++-- lib/l10ngen/app_localizations_ro.dart | 53 ++-- lib/l10ngen/app_localizations_ru.dart | 53 ++-- lib/l10ngen/app_localizations_sat.dart | 55 ++-- lib/l10ngen/app_localizations_sk.dart | 53 ++-- lib/l10ngen/app_localizations_sl.dart | 55 ++-- lib/l10ngen/app_localizations_sr.dart | 55 ++-- lib/l10ngen/app_localizations_sv.dart | 53 ++-- lib/l10ngen/app_localizations_ta.dart | 53 ++-- lib/l10ngen/app_localizations_th.dart | 53 ++-- lib/l10ngen/app_localizations_tr.dart | 53 ++-- lib/l10ngen/app_localizations_uk.dart | 53 ++-- lib/l10ngen/app_localizations_ur.dart | 55 ++-- lib/l10ngen/app_localizations_vi.dart | 53 ++-- lib/l10ngen/app_localizations_zh.dart | 74 +++--- lib/model/covers.dart | 8 +- lib/model/db/db_sqflite.dart | 17 +- lib/model/dynamic_albums.dart | 2 +- lib/model/filters/covered/album_base.dart | 14 - lib/model/filters/covered/album_group.dart | 49 ++++ lib/model/filters/covered/dynamic_album.dart | 17 +- lib/model/filters/covered/group_base.dart | 35 +++ lib/model/filters/covered/stored_album.dart | 13 +- lib/model/filters/filters.dart | 33 ++- lib/model/filters/mime.dart | 5 + lib/model/filters/set_and.dart | 6 +- lib/model/filters/set_or.dart | 12 +- lib/model/grouping/common.dart | 250 ++++++++++++++++++ lib/model/grouping/convert.dart | 67 +++++ lib/model/highlight.dart | 2 +- lib/model/settings/defaults.dart | 7 +- lib/model/settings/modules/collection.dart | 4 +- lib/model/settings/modules/filter_grids.dart | 6 +- lib/model/settings/modules/navigation.dart | 4 +- lib/model/settings/modules/screen_saver.dart | 29 ++ lib/model/settings/modules/slideshow.dart | 32 +++ lib/model/settings/modules/widget.dart | 29 ++ lib/model/settings/settings.dart | 95 +------ lib/model/source/album.dart | 22 +- lib/model/source/collection_lens.dart | 18 +- lib/model/source/collection_source.dart | 74 +++--- lib/services/storage_service.dart | 4 +- lib/theme/icons.dart | 3 +- lib/utils/xmp_utils.dart | 2 +- lib/view/src/actions/chip_set.dart | 4 + lib/view/src/source/group.dart | 36 +-- lib/widgets/collection/app_bar.dart | 18 +- lib/widgets/collection/collection_grid.dart | 8 +- .../collection/draggable_thumb_label.dart | 8 +- .../collection/entry_set_action_delegate.dart | 16 +- lib/widgets/collection/grid/headers/any.dart | 8 +- .../quick_choosers/tag_button.dart | 2 +- .../common/action_mixins/entry_storage.dart | 15 +- lib/widgets/common/app_bar/crumb_line.dart | 104 ++++++++ .../basic/gestures/gesture_detector.dart | 2 +- .../common/behaviour/pop/tv_navigation.dart | 2 +- .../common/grid/sections/list_layout.dart | 2 +- .../common/identity/aves_filter_chip.dart | 12 +- .../providers/filter_group_provider.dart | 17 ++ lib/widgets/debug/app_debug_page.dart | 2 +- .../entry_editors/edit_date_dialog.dart | 2 +- .../entry_editors/tag_editor_page.dart | 2 +- ....dart => create_dynamic_album_dialog.dart} | 19 +- .../filter_editors/create_group_dialog.dart | 99 +++++++ .../rename_dynamic_album_dialog.dart | 5 +- .../filter_editors/rename_group_dialog.dart | 101 +++++++ .../dialogs/pick_dialogs/album_pick_page.dart | 243 +++++++++++------ lib/widgets/dialogs/tile_view_dialog.dart | 30 +-- lib/widgets/explorer/app_bar.dart | 13 +- lib/widgets/explorer/crumb_line.dart | 134 ++-------- lib/widgets/explorer/explorer_page.dart | 15 +- lib/widgets/filter_grids/albums_page.dart | 194 +++++++++----- .../common/action_delegates/album_set.dart | 127 +++++++-- .../common/action_delegates/chip.dart | 6 +- .../common/action_delegates/chip_set.dart | 6 + .../common/action_delegates/country_set.dart | 2 +- .../common/action_delegates/place_set.dart | 2 +- .../common/action_delegates/state_set.dart | 2 +- .../common/action_delegates/tag_set.dart | 2 +- lib/widgets/filter_grids/common/app_bar.dart | 28 ++ .../common/covered_filter_chip.dart | 25 +- lib/widgets/filter_grids/common/enums.dart | 4 +- .../filter_grids/common/filter_grid_page.dart | 57 ++-- .../filter_grids/common/filter_nav_page.dart | 35 ++- .../filter_grids/common/filter_tile.dart | 63 +---- .../filter_grids/common/group_crumb_line.dart | 44 +++ .../filter_grids/common/list_details.dart | 2 + .../filter_grids/common/section_keys.dart | 2 + lib/widgets/filter_grids/countries_page.dart | 7 +- lib/widgets/filter_grids/places_page.dart | 7 +- lib/widgets/filter_grids/states_page.dart | 7 +- lib/widgets/filter_grids/tags_page.dart | 7 +- lib/widgets/home/home_page.dart | 2 +- lib/widgets/navigation/drawer/app_drawer.dart | 2 +- .../drawer/collection_nav_tile.dart | 36 ++- .../navigation/drawer/page_nav_tile.dart | 2 +- lib/widgets/navigation/nav_bar/nav_bar.dart | 2 +- lib/widgets/search/search_delegate.dart | 4 +- lib/widgets/settings/navigation/drawer.dart | 2 +- .../navigation/drawer_tab_albums.dart | 10 +- lib/widgets/viewer/info/basic_section.dart | 8 +- lib/widgets/viewer/info/info_page.dart | 8 +- lib/widgets/viewer/info/location_section.dart | 6 +- plugins/aves_map/lib/src/marker/image.dart | 46 ++-- .../aves_model/lib/src/actions/chip_set.dart | 3 + plugins/aves_model/lib/src/settings/keys.dart | 3 - plugins/aves_model/lib/src/source/enums.dart | 4 +- .../lib/src/storage/relative_dir.dart | 5 + pubspec.lock | 96 +++---- test/fake/db.dart | 8 + test/model/filters_test.dart | 46 +++- test/model/grouping/common_test.dart | 145 ++++++++++ test/model/grouping/convert_test.dart | 41 +++ test_driver/driver_screenshots.dart | 2 +- test_driver/driver_shaders.dart | 2 +- 156 files changed, 3941 insertions(+), 1914 deletions(-) delete mode 100644 lib/model/filters/covered/album_base.dart create mode 100644 lib/model/filters/covered/album_group.dart create mode 100644 lib/model/filters/covered/group_base.dart create mode 100644 lib/model/grouping/common.dart create mode 100644 lib/model/grouping/convert.dart create mode 100644 lib/model/settings/modules/screen_saver.dart create mode 100644 lib/model/settings/modules/slideshow.dart create mode 100644 lib/model/settings/modules/widget.dart create mode 100644 lib/widgets/common/app_bar/crumb_line.dart create mode 100644 lib/widgets/common/providers/filter_group_provider.dart rename lib/widgets/dialogs/filter_editors/{add_dynamic_album_dialog.dart => create_dynamic_album_dialog.dart} (78%) create mode 100644 lib/widgets/dialogs/filter_editors/create_group_dialog.dart create mode 100644 lib/widgets/dialogs/filter_editors/rename_group_dialog.dart create mode 100644 lib/widgets/filter_grids/common/group_crumb_line.dart create mode 100644 test/model/grouping/common_test.dart create mode 100644 test/model/grouping/convert_test.dart diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index 435e32399..5172ad6c5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -1007,7 +1007,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { result.success(metadataMap) } - // return description from these fields (by precedence): + // returns description from these fields (by precedence): // - XMP / dc:description // - IPTC / caption-abstract // - Exif / UserComment @@ -1192,8 +1192,8 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { result.success(null) } - // return XMP components - // return an empty list if there is no XMP + // returns XMP components + // returns an empty list if there is no XMP private fun getXmp(call: MethodCall, result: MethodChannel.Result) { val mimeType = call.argument("mimeType") val uri = call.argument("uri")?.toUri() diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt index 0762a4df9..2932c08ff 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt @@ -31,7 +31,7 @@ import kotlin.math.roundToInt class RegionFetcher internal constructor( private val context: Context, ) { - // return decoded bytes in ARGB_8888, with trailer bytes: + // returns decoded bytes in ARGB_8888, with trailer bytes: // - width (int32) // - height (int32) fun fetch( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9a4e43bf9..50dafb3b1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -60,6 +60,7 @@ }, "applyButtonLabel": "APPLY", + "createButtonLabel": "CREATE", "deleteButtonLabel": "DELETE", "nextButtonLabel": "NEXT", "showButtonLabel": "SHOW", @@ -104,9 +105,11 @@ "chipActionLock": "Lock", "chipActionPin": "Pin to top", "chipActionUnpin": "Unpin from top", + "chipActionGroup": "Group", "chipActionRename": "Rename", "chipActionSetCover": "Set cover", "chipActionShowCountryStates": "Show states", + "chipActionCreateGroup": "Create group", "chipActionCreateAlbum": "Create album", "chipActionCreateVault": "Create vault", "chipActionConfigureVault": "Configure vault", @@ -208,6 +211,7 @@ "albumTierNew": "New", "albumTierPinned": "Pinned", + "albumTierGroups": "Groups", "albumTierSpecial": "Common", "albumTierApps": "Apps", "albumTierVaults": "Vaults", @@ -443,6 +447,14 @@ "newDynamicAlbumDialogTitle": "New Dynamic Album", "dynamicAlbumAlreadyExists": "Dynamic album already exists", + "newGroupDialogTitle": "New Group", + "newGroupDialogNameLabel": "Group name", + "groupAlreadyExists": "Group already exists", + "groupEmpty": "No groups", + "ungrouped": "Ungrouped", + "groupPickerTitle": "Pick Group", + "groupPickerUseThisGroupButton": "Use this group", + "newVaultWarningDialogMessage": "Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.", "newVaultDialogTitle": "New Vault", "configureVaultDialogTitle": "Configure Vault", @@ -558,7 +570,7 @@ "menuActionStats": "Stats", "viewDialogSortSectionTitle": "Sort", - "viewDialogGroupSectionTitle": "Group", + "viewDialogGroupSectionTitle": "Sections", "viewDialogLayoutSectionTitle": "Layout", "viewDialogReverseSortOrder": "Reverse sort order", @@ -630,8 +642,8 @@ "collectionGroupAlbum": "By album", "collectionGroupMonth": "By month", "collectionGroupDay": "By day", - "collectionGroupNone": "Do not group", + "sectionNone": "No sections", "sectionUnknown": "Unknown", "dateToday": "Today", "dateYesterday": "Yesterday", @@ -774,7 +786,6 @@ "albumGroupTier": "By tier", "albumGroupType": "By type", "albumGroupVolume": "By storage volume", - "albumGroupNone": "Do not group", "albumMimeTypeMixed": "Mixed", @@ -1101,11 +1112,5 @@ "panoramaEnableSensorControl": "Enable sensor control", "panoramaDisableSensorControl": "Disable sensor control", - "sourceViewerPageTitle": "Source", - - "filePickerShowHiddenFiles": "Show hidden files", - "filePickerDoNotShowHiddenFiles": "Don’t show hidden files", - "filePickerOpenFrom": "Open from", - "filePickerNoItems": "No items", - "filePickerUseThisFolder": "Use this folder" + "sourceViewerPageTitle": "Source" } diff --git a/lib/l10ngen/app_localizations.dart b/lib/l10ngen/app_localizations.dart index 00fe8b6b1..1135f25b1 100644 --- a/lib/l10ngen/app_localizations.dart +++ b/lib/l10ngen/app_localizations.dart @@ -263,6 +263,12 @@ abstract class AppLocalizations { /// **'APPLY'** String get applyButtonLabel; + /// No description provided for @createButtonLabel. + /// + /// In en, this message translates to: + /// **'CREATE'** + String get createButtonLabel; + /// No description provided for @deleteButtonLabel. /// /// In en, this message translates to: @@ -503,6 +509,12 @@ abstract class AppLocalizations { /// **'Unpin from top'** String get chipActionUnpin; + /// No description provided for @chipActionGroup. + /// + /// In en, this message translates to: + /// **'Group'** + String get chipActionGroup; + /// No description provided for @chipActionRename. /// /// In en, this message translates to: @@ -521,6 +533,12 @@ abstract class AppLocalizations { /// **'Show states'** String get chipActionShowCountryStates; + /// No description provided for @chipActionCreateGroup. + /// + /// In en, this message translates to: + /// **'Create group'** + String get chipActionCreateGroup; + /// No description provided for @chipActionCreateAlbum. /// /// In en, this message translates to: @@ -1055,6 +1073,12 @@ abstract class AppLocalizations { /// **'Pinned'** String get albumTierPinned; + /// No description provided for @albumTierGroups. + /// + /// In en, this message translates to: + /// **'Groups'** + String get albumTierGroups; + /// No description provided for @albumTierSpecial. /// /// In en, this message translates to: @@ -1667,6 +1691,48 @@ abstract class AppLocalizations { /// **'Dynamic album already exists'** String get dynamicAlbumAlreadyExists; + /// No description provided for @newGroupDialogTitle. + /// + /// In en, this message translates to: + /// **'New Group'** + String get newGroupDialogTitle; + + /// No description provided for @newGroupDialogNameLabel. + /// + /// In en, this message translates to: + /// **'Group name'** + String get newGroupDialogNameLabel; + + /// No description provided for @groupAlreadyExists. + /// + /// In en, this message translates to: + /// **'Group already exists'** + String get groupAlreadyExists; + + /// No description provided for @groupEmpty. + /// + /// In en, this message translates to: + /// **'No groups'** + String get groupEmpty; + + /// No description provided for @ungrouped. + /// + /// In en, this message translates to: + /// **'Ungrouped'** + String get ungrouped; + + /// No description provided for @groupPickerTitle. + /// + /// In en, this message translates to: + /// **'Pick Group'** + String get groupPickerTitle; + + /// No description provided for @groupPickerUseThisGroupButton. + /// + /// In en, this message translates to: + /// **'Use this group'** + String get groupPickerUseThisGroupButton; + /// No description provided for @newVaultWarningDialogMessage. /// /// In en, this message translates to: @@ -2114,7 +2180,7 @@ abstract class AppLocalizations { /// No description provided for @viewDialogGroupSectionTitle. /// /// In en, this message translates to: - /// **'Group'** + /// **'Sections'** String get viewDialogGroupSectionTitle; /// No description provided for @viewDialogLayoutSectionTitle. @@ -2459,11 +2525,11 @@ abstract class AppLocalizations { /// **'By day'** String get collectionGroupDay; - /// No description provided for @collectionGroupNone. + /// No description provided for @sectionNone. /// /// In en, this message translates to: - /// **'Do not group'** - String get collectionGroupNone; + /// **'No sections'** + String get sectionNone; /// No description provided for @sectionUnknown. /// @@ -2801,12 +2867,6 @@ abstract class AppLocalizations { /// **'By storage volume'** String get albumGroupVolume; - /// No description provided for @albumGroupNone. - /// - /// In en, this message translates to: - /// **'Do not group'** - String get albumGroupNone; - /// No description provided for @albumMimeTypeMixed. /// /// In en, this message translates to: @@ -4414,36 +4474,6 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Source'** String get sourceViewerPageTitle; - - /// No description provided for @filePickerShowHiddenFiles. - /// - /// In en, this message translates to: - /// **'Show hidden files'** - String get filePickerShowHiddenFiles; - - /// No description provided for @filePickerDoNotShowHiddenFiles. - /// - /// In en, this message translates to: - /// **'Don’t show hidden files'** - String get filePickerDoNotShowHiddenFiles; - - /// No description provided for @filePickerOpenFrom. - /// - /// In en, this message translates to: - /// **'Open from'** - String get filePickerOpenFrom; - - /// No description provided for @filePickerNoItems. - /// - /// In en, this message translates to: - /// **'No items'** - String get filePickerNoItems; - - /// No description provided for @filePickerUseThisFolder. - /// - /// In en, this message translates to: - /// **'Use this folder'** - String get filePickerUseThisFolder; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10ngen/app_localizations_ar.dart b/lib/l10ngen/app_localizations_ar.dart index bd7345d11..376598a81 100644 --- a/lib/l10ngen/app_localizations_ar.dart +++ b/lib/l10ngen/app_localizations_ar.dart @@ -92,6 +92,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get applyButtonLabel => 'تأكيد'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'حذف'; @@ -212,6 +215,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get chipActionUnpin => 'إلغاء التثبيت في الأعلى'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'إعادة تسمية'; @@ -221,6 +227,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get chipActionShowCountryStates => 'عرض الولايات'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'إنشاء ألبوم'; @@ -488,6 +497,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get albumTierPinned => 'مثبت'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'شائع'; @@ -830,6 +842,27 @@ class AppLocalizationsAr extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'الألبوم الديناميكي موجود بالفعل'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'العناصر الموجودة في الخزائن متاحة فقط لهذا التطبيق وليس للتطبيقات الأخرى.\n\nإذا قمت بإلغاء تثبيت هذا التطبيق، أو قمت بحذف بيانات هذا التطبيق، فسوف تفقد كل هذه العناصر.'; @@ -1243,7 +1276,7 @@ class AppLocalizationsAr extends AppLocalizations { String get collectionGroupDay => 'حسب اليوم'; @override - String get collectionGroupNone => 'لا تجمع'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'مجهول'; @@ -1493,9 +1526,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get albumGroupVolume => 'حسب حجم التخزين'; - @override - String get albumGroupNone => 'لا تجمع'; - @override String get albumMimeTypeMixed => 'مختلط'; @@ -2307,19 +2337,4 @@ class AppLocalizationsAr extends AppLocalizations { @override String get sourceViewerPageTitle => 'المصدر'; - - @override - String get filePickerShowHiddenFiles => 'إظهار الملفات المخفية'; - - @override - String get filePickerDoNotShowHiddenFiles => 'عدم إظهار الملفات المخفية'; - - @override - String get filePickerOpenFrom => 'فتح من'; - - @override - String get filePickerNoItems => 'لا توجد عناصر'; - - @override - String get filePickerUseThisFolder => 'إستخدام هذا المجلد'; } diff --git a/lib/l10ngen/app_localizations_az.dart b/lib/l10ngen/app_localizations_az.dart index 902b1307d..761877fd6 100644 --- a/lib/l10ngen/app_localizations_az.dart +++ b/lib/l10ngen/app_localizations_az.dart @@ -98,6 +98,9 @@ class AppLocalizationsAz extends AppLocalizations { @override String get applyButtonLabel => 'TƏTBİQ ET'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SİL'; @@ -218,6 +221,9 @@ class AppLocalizationsAz extends AppLocalizations { @override String get chipActionUnpin => 'Sabitləməyin'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Bir də adlandır'; @@ -227,6 +233,9 @@ class AppLocalizationsAz extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsAz extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsAz extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsAz extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsAz extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsAz extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsAz extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_be.dart b/lib/l10ngen/app_localizations_be.dart index 5474d00b4..517a11ebe 100644 --- a/lib/l10ngen/app_localizations_be.dart +++ b/lib/l10ngen/app_localizations_be.dart @@ -96,6 +96,9 @@ class AppLocalizationsBe extends AppLocalizations { @override String get applyButtonLabel => 'УЖЫВАЦЬ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ВЫДАЛІЦЬ'; @@ -216,6 +219,9 @@ class AppLocalizationsBe extends AppLocalizations { @override String get chipActionUnpin => 'Адмацаваць зверху'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Перайменаваць'; @@ -225,6 +231,9 @@ class AppLocalizationsBe extends AppLocalizations { @override String get chipActionShowCountryStates => 'Паказаць дзяржавы'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Стварыць альбом'; @@ -492,6 +501,9 @@ class AppLocalizationsBe extends AppLocalizations { @override String get albumTierPinned => 'Замацаваны'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Стандартныя'; @@ -836,6 +848,27 @@ class AppLocalizationsBe extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Элементы ў сховішчах даступныя толькі гэтай праграме і нікому больш.\n\nКалі вы выдаліце гэту праграму або ачысціце даныя праграмы, вы страціце ўсе гэтыя элементы.'; @@ -1251,7 +1284,7 @@ class AppLocalizationsBe extends AppLocalizations { String get collectionGroupDay => 'Па днях'; @override - String get collectionGroupNone => 'Не групаваць'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Невядома'; @@ -1511,9 +1544,6 @@ class AppLocalizationsBe extends AppLocalizations { @override String get albumGroupVolume => 'Па аб\'ёме захоўвання'; - @override - String get albumGroupNone => 'Не групаваць'; - @override String get albumMimeTypeMixed => 'Змешаны'; @@ -2326,19 +2356,4 @@ class AppLocalizationsBe extends AppLocalizations { @override String get sourceViewerPageTitle => 'Крыніца'; - - @override - String get filePickerShowHiddenFiles => 'Паказаць схаваныя файлы'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Не паказваць схаваныя файлы'; - - @override - String get filePickerOpenFrom => 'Адкрыць з'; - - @override - String get filePickerNoItems => 'Няма элементаў'; - - @override - String get filePickerUseThisFolder => 'Выкарыстоўваць гэтую тэчку'; } diff --git a/lib/l10ngen/app_localizations_bg.dart b/lib/l10ngen/app_localizations_bg.dart index f0ab710df..82430d12a 100644 --- a/lib/l10ngen/app_localizations_bg.dart +++ b/lib/l10ngen/app_localizations_bg.dart @@ -102,6 +102,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get applyButtonLabel => 'ПРИЕМАМ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ИЗТРИЙ'; @@ -222,6 +225,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get chipActionUnpin => 'Откачи от закачени'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Преименувайте'; @@ -231,6 +237,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get chipActionShowCountryStates => 'Показване на държава'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Създайте албум'; @@ -498,6 +507,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get albumTierPinned => 'Закачени'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Стандартни'; @@ -846,6 +858,27 @@ class AppLocalizationsBg extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Динамични албум вече съществува'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Елементите в трезорите са достъпни само за това приложение и за никое друго.\n\nАко деинсталирате или изчистите данните на това приложение, ще загубите цялото съдържание на трезора.'; @@ -1265,7 +1298,7 @@ class AppLocalizationsBg extends AppLocalizations { String get collectionGroupDay => 'По дни'; @override - String get collectionGroupNone => 'Не групирай'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Неизвестно'; @@ -1545,9 +1578,6 @@ class AppLocalizationsBg extends AppLocalizations { @override String get albumGroupVolume => 'По обем на съхранение'; - @override - String get albumGroupNone => 'Без групиране'; - @override String get albumMimeTypeMixed => 'Разни'; @@ -2362,19 +2392,4 @@ class AppLocalizationsBg extends AppLocalizations { @override String get sourceViewerPageTitle => 'Источник'; - - @override - String get filePickerShowHiddenFiles => 'Показване на скритите файлове'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Не показвай скритите файлове'; - - @override - String get filePickerOpenFrom => 'Отворете от'; - - @override - String get filePickerNoItems => 'Не откривам нищо'; - - @override - String get filePickerUseThisFolder => 'Използвай тази папка'; } diff --git a/lib/l10ngen/app_localizations_bn.dart b/lib/l10ngen/app_localizations_bn.dart index 1714234fb..5f71a7322 100644 --- a/lib/l10ngen/app_localizations_bn.dart +++ b/lib/l10ngen/app_localizations_bn.dart @@ -98,6 +98,9 @@ class AppLocalizationsBn extends AppLocalizations { @override String get applyButtonLabel => 'প্রয়োগ করুন'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ডিলিট'; @@ -218,6 +221,9 @@ class AppLocalizationsBn extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsBn extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsBn extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsBn extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsBn extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsBn extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsBn extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsBn extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_ca.dart b/lib/l10ngen/app_localizations_ca.dart index 66aed3380..a286c7e28 100644 --- a/lib/l10ngen/app_localizations_ca.dart +++ b/lib/l10ngen/app_localizations_ca.dart @@ -92,6 +92,9 @@ class AppLocalizationsCa extends AppLocalizations { @override String get applyButtonLabel => 'APLICAR'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SUPRIMEIX'; @@ -212,6 +215,9 @@ class AppLocalizationsCa extends AppLocalizations { @override String get chipActionUnpin => 'Desancora de dalt'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Canviar nom'; @@ -221,6 +227,9 @@ class AppLocalizationsCa extends AppLocalizations { @override String get chipActionShowCountryStates => 'Mostrar estats'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Crear àlbum'; @@ -488,6 +497,9 @@ class AppLocalizationsCa extends AppLocalizations { @override String get albumTierPinned => 'Fixat'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Comú'; @@ -830,6 +842,27 @@ class AppLocalizationsCa extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'L’àlbum dinàmic ja existeix'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Els elements en caixes fortes només son disponibles des d’aquesta aplicació.\n\nSi desinstaŀles aquesta aplicació o en borres les dades, perdràs aquests elements.'; @@ -1243,7 +1276,7 @@ class AppLocalizationsCa extends AppLocalizations { String get collectionGroupDay => 'Per dia'; @override - String get collectionGroupNone => 'No per grup'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Desconegut'; @@ -1493,9 +1526,6 @@ class AppLocalizationsCa extends AppLocalizations { @override String get albumGroupVolume => 'Per volum d’emmagatzematge'; - @override - String get albumGroupNone => 'No agrupar'; - @override String get albumMimeTypeMixed => 'Barrejat'; @@ -2307,19 +2337,4 @@ class AppLocalizationsCa extends AppLocalizations { @override String get sourceViewerPageTitle => 'Font'; - - @override - String get filePickerShowHiddenFiles => 'Mostra arxius amagats'; - - @override - String get filePickerDoNotShowHiddenFiles => 'No mostris arxius amagats'; - - @override - String get filePickerOpenFrom => 'Obrir des de'; - - @override - String get filePickerNoItems => 'Sense element'; - - @override - String get filePickerUseThisFolder => 'Utilitza aquesta carpeta'; } diff --git a/lib/l10ngen/app_localizations_ckb.dart b/lib/l10ngen/app_localizations_ckb.dart index 4336a9dd5..884d9dbc3 100644 --- a/lib/l10ngen/app_localizations_ckb.dart +++ b/lib/l10ngen/app_localizations_ckb.dart @@ -87,6 +87,9 @@ class AppLocalizationsCkb extends AppLocalizations { @override String get applyButtonLabel => 'جێبەجێکردن'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'سڕینەوە'; @@ -207,6 +210,9 @@ class AppLocalizationsCkb extends AppLocalizations { @override String get chipActionUnpin => 'لابردنی جێگیری'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'ناوگۆڕین'; @@ -216,6 +222,9 @@ class AppLocalizationsCkb extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'دروستکردنی ئەلبوم'; @@ -483,6 +492,9 @@ class AppLocalizationsCkb extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'باو'; @@ -831,6 +843,27 @@ class AppLocalizationsCkb extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1076,7 +1109,7 @@ class AppLocalizationsCkb extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1250,7 +1283,7 @@ class AppLocalizationsCkb extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1530,9 +1563,6 @@ class AppLocalizationsCkb extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2347,19 +2377,4 @@ class AppLocalizationsCkb extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_cs.dart b/lib/l10ngen/app_localizations_cs.dart index 9e1801924..6cbe164d3 100644 --- a/lib/l10ngen/app_localizations_cs.dart +++ b/lib/l10ngen/app_localizations_cs.dart @@ -97,6 +97,9 @@ class AppLocalizationsCs extends AppLocalizations { @override String get applyButtonLabel => 'POUŽÍT'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SMAZAT'; @@ -217,6 +220,9 @@ class AppLocalizationsCs extends AppLocalizations { @override String get chipActionUnpin => 'Odepnout seshora'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Přejmenovat'; @@ -226,6 +232,9 @@ class AppLocalizationsCs extends AppLocalizations { @override String get chipActionShowCountryStates => 'Zobrazit země'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Vytvořit album'; @@ -493,6 +502,9 @@ class AppLocalizationsCs extends AppLocalizations { @override String get albumTierPinned => 'Připnuté'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Společné'; @@ -837,6 +849,27 @@ class AppLocalizationsCs extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamické album již existuje'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Položky v trezorech jsou přístupné pouze této aplikaci a žádné jiné.\n\nPokud tuto aplikaci odinstalujete, nebo smažete její data, o všechny tyto položky přijdete.'; @@ -1252,7 +1285,7 @@ class AppLocalizationsCs extends AppLocalizations { String get collectionGroupDay => 'Podle dne'; @override - String get collectionGroupNone => 'Neseskupovat'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Neznámý'; @@ -1506,9 +1539,6 @@ class AppLocalizationsCs extends AppLocalizations { @override String get albumGroupVolume => 'Podle úložiště'; - @override - String get albumGroupNone => 'Neseskupovat'; - @override String get albumMimeTypeMixed => 'Smíšené'; @@ -2321,19 +2351,4 @@ class AppLocalizationsCs extends AppLocalizations { @override String get sourceViewerPageTitle => 'Zdroj'; - - @override - String get filePickerShowHiddenFiles => 'Zobrazit skryté soubory'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Nezobrazovat skryté soubory'; - - @override - String get filePickerOpenFrom => 'Otevřít z'; - - @override - String get filePickerNoItems => 'Žádné položky'; - - @override - String get filePickerUseThisFolder => 'Použít tuto složku'; } diff --git a/lib/l10ngen/app_localizations_da.dart b/lib/l10ngen/app_localizations_da.dart index 5619e872b..72c68ac97 100644 --- a/lib/l10ngen/app_localizations_da.dart +++ b/lib/l10ngen/app_localizations_da.dart @@ -98,6 +98,9 @@ class AppLocalizationsDa extends AppLocalizations { @override String get applyButtonLabel => 'ANVEND'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SLET'; @@ -218,6 +221,9 @@ class AppLocalizationsDa extends AppLocalizations { @override String get chipActionUnpin => 'Frigør fra toppen'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Omdøb'; @@ -227,6 +233,9 @@ class AppLocalizationsDa extends AppLocalizations { @override String get chipActionShowCountryStates => 'Vis stater'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Opret album'; @@ -494,6 +503,9 @@ class AppLocalizationsDa extends AppLocalizations { @override String get albumTierPinned => 'Fastgjort'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Almindelig'; @@ -842,6 +854,27 @@ class AppLocalizationsDa extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamisk album findes allerede'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Elementer i bokse er kun tilgængelige for denne app og ingen andre.\n\nHvis du afinstallerer appen eller rydder dens data, mister du alle disse elementer.'; @@ -1261,7 +1294,7 @@ class AppLocalizationsDa extends AppLocalizations { String get collectionGroupDay => 'Efter dag'; @override - String get collectionGroupNone => 'Gruppér ikke'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Ukendt'; @@ -1541,9 +1574,6 @@ class AppLocalizationsDa extends AppLocalizations { @override String get albumGroupVolume => 'Efter lagervolume'; - @override - String get albumGroupNone => 'Gruppér ikke'; - @override String get albumMimeTypeMixed => 'Blandet'; @@ -2358,19 +2388,4 @@ class AppLocalizationsDa extends AppLocalizations { @override String get sourceViewerPageTitle => 'Kilde'; - - @override - String get filePickerShowHiddenFiles => 'Vis skjulte filer'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Vis ikke skjulte filer'; - - @override - String get filePickerOpenFrom => 'Åbn fra'; - - @override - String get filePickerNoItems => 'Ingen elementer'; - - @override - String get filePickerUseThisFolder => 'Brug denne mappe'; } diff --git a/lib/l10ngen/app_localizations_de.dart b/lib/l10ngen/app_localizations_de.dart index 66e789ab5..9369f5c9e 100644 --- a/lib/l10ngen/app_localizations_de.dart +++ b/lib/l10ngen/app_localizations_de.dart @@ -95,6 +95,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get applyButtonLabel => 'ANWENDEN'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'LÖSCHEN'; @@ -215,6 +218,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get chipActionUnpin => 'Nicht mehr Anpinen'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Umbenennen'; @@ -224,6 +230,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get chipActionShowCountryStates => 'Staaten anzeigen'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Album erstellen'; @@ -491,6 +500,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get albumTierPinned => 'Angeheftet'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Häufig verwendet'; @@ -839,6 +851,27 @@ class AppLocalizationsDe extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamisches Album existiert bereits'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Elemente in Tresoren sind nur für diese App verfügbar und nicht in anderen.\n\nWenn Sie diese App deinstallieren oder die Daten dieser App löschen, gehen alle diese Elemente verloren.'; @@ -1258,7 +1291,7 @@ class AppLocalizationsDe extends AppLocalizations { String get collectionGroupDay => 'Nach Tag'; @override - String get collectionGroupNone => 'Nicht gruppieren'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unbekannt'; @@ -1538,9 +1571,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get albumGroupVolume => 'Nach Speichervolumen'; - @override - String get albumGroupNone => 'Nicht gruppieren'; - @override String get albumMimeTypeMixed => 'Gemischt'; @@ -2355,19 +2385,4 @@ class AppLocalizationsDe extends AppLocalizations { @override String get sourceViewerPageTitle => 'Quelle'; - - @override - String get filePickerShowHiddenFiles => 'Versteckte Dateien anzeigen'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Versteckte Dateien nicht anzeigen'; - - @override - String get filePickerOpenFrom => 'Öffnen von'; - - @override - String get filePickerNoItems => 'Keine Elemente'; - - @override - String get filePickerUseThisFolder => 'Diesen Ordner verwenden'; } diff --git a/lib/l10ngen/app_localizations_el.dart b/lib/l10ngen/app_localizations_el.dart index 11b786974..fc6fbe05c 100644 --- a/lib/l10ngen/app_localizations_el.dart +++ b/lib/l10ngen/app_localizations_el.dart @@ -95,6 +95,9 @@ class AppLocalizationsEl extends AppLocalizations { @override String get applyButtonLabel => 'ΕΦΑΡΜΟΓΗ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ΔΙΑΓΡΑΦΗ'; @@ -215,6 +218,9 @@ class AppLocalizationsEl extends AppLocalizations { @override String get chipActionUnpin => 'Ξέκαρφίτσωμα από την κορυφή'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Μετονομασία'; @@ -224,6 +230,9 @@ class AppLocalizationsEl extends AppLocalizations { @override String get chipActionShowCountryStates => 'Εμφάνιση πολιτειών'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Δημιουργία άλμπουμ'; @@ -491,6 +500,9 @@ class AppLocalizationsEl extends AppLocalizations { @override String get albumTierPinned => 'Καρφιτσωμένα'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Συστήματος'; @@ -839,6 +851,27 @@ class AppLocalizationsEl extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Το δυναμικό άλμπουμ υπάρχει ήδη'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Τα αρχεία στα θησαυροφυλάκια είναι διαθέσιμα μόνο σε αυτή την εφαρμογή και σε καμία άλλη.\n\nΑν απεγκαταστήσετε την εφαρμογή ή έστω διαγράψετε τα δεδομένα της εφαρμογής, θα χάσετε όλα σας τα κρυφά αρχεία.'; @@ -1258,7 +1291,7 @@ class AppLocalizationsEl extends AppLocalizations { String get collectionGroupDay => 'Ανά ημέρα'; @override - String get collectionGroupNone => 'Να μην γίνει ομαδοποίηση'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Χωρίς λεπτομέρειες'; @@ -1538,9 +1571,6 @@ class AppLocalizationsEl extends AppLocalizations { @override String get albumGroupVolume => 'Ανά αποθηκευτική μονάδα'; - @override - String get albumGroupNone => 'Να μην γίνει ομαδοποίηση'; - @override String get albumMimeTypeMixed => 'Μικτα'; @@ -2355,19 +2385,4 @@ class AppLocalizationsEl extends AppLocalizations { @override String get sourceViewerPageTitle => 'Πηγη'; - - @override - String get filePickerShowHiddenFiles => 'Εμφάνιση κρυφών αρχείων'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Να μην εμφανίζονται τα κρυφά αρχεία'; - - @override - String get filePickerOpenFrom => 'Άνοιγμα από'; - - @override - String get filePickerNoItems => 'Κανένα στοιχείο'; - - @override - String get filePickerUseThisFolder => 'Χρησιμοποιήστε αυτόν τον φάκελο'; } diff --git a/lib/l10ngen/app_localizations_en.dart b/lib/l10ngen/app_localizations_en.dart index 055e31a71..b31473422 100644 --- a/lib/l10ngen/app_localizations_en.dart +++ b/lib/l10ngen/app_localizations_en.dart @@ -98,6 +98,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsEn extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsEn extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsEn extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,21 +2388,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } /// The translations for English, using the Shavian Shaw script (`en_Shaw`). @@ -3601,9 +3616,6 @@ class AppLocalizationsEnShaw extends AppLocalizationsEn { @override String get collectionGroupDay => '𐑚𐑲 𐑛𐑱'; - @override - String get collectionGroupNone => '𐑛𐑵 𐑯𐑪𐑑 𐑜𐑮𐑵𐑐'; - @override String get sectionUnknown => '𐑳𐑯𐑯𐑴𐑯'; @@ -3879,9 +3891,6 @@ class AppLocalizationsEnShaw extends AppLocalizationsEn { @override String get albumGroupVolume => '𐑚𐑲 𐑕𐑑𐑹𐑦𐑡 𐑝𐑪𐑤𐑿𐑥'; - @override - String get albumGroupNone => '𐑛𐑵 𐑯𐑪𐑑 𐑜𐑮𐑵𐑐'; - @override String get albumMimeTypeMixed => '𐑥𐑦𐑒𐑕𐑑'; @@ -4690,19 +4699,4 @@ class AppLocalizationsEnShaw extends AppLocalizationsEn { @override String get sourceViewerPageTitle => '𐑕𐑹𐑕'; - - @override - String get filePickerShowHiddenFiles => '𐑖𐑴 𐑣𐑦𐑛𐑩𐑯 𐑓𐑲𐑤𐑟'; - - @override - String get filePickerDoNotShowHiddenFiles => '𐑛𐑴𐑯\'𐑑 𐑖𐑴 𐑣𐑦𐑛𐑩𐑯 𐑓𐑲𐑤𐑟'; - - @override - String get filePickerOpenFrom => '𐑴𐑐𐑩𐑯 𐑓𐑮𐑪𐑥'; - - @override - String get filePickerNoItems => '𐑯𐑴 𐑲𐑑𐑩𐑥𐑟'; - - @override - String get filePickerUseThisFolder => '𐑿𐑟 𐑞𐑦𐑕 𐑓𐑴𐑤𐑛𐑼'; } diff --git a/lib/l10ngen/app_localizations_es.dart b/lib/l10ngen/app_localizations_es.dart index 241eb3c3c..fc7399c4f 100644 --- a/lib/l10ngen/app_localizations_es.dart +++ b/lib/l10ngen/app_localizations_es.dart @@ -95,6 +95,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get applyButtonLabel => 'APLICAR'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'BORRAR'; @@ -215,6 +218,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get chipActionUnpin => 'Dejar de fijar'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Renombrar'; @@ -224,6 +230,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get chipActionShowCountryStates => 'Mostrar los estados'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Crear álbum'; @@ -491,6 +500,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get albumTierPinned => 'Fijado'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Común'; @@ -839,6 +851,27 @@ class AppLocalizationsEs extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'El álbum dinámico ya existe'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Los elementos de la caja fuerte sólo están disponibles para esta aplicación y no para otras.\n\nSi desinstalas esta aplicación o borras sus datos, perderás todos estos elementos.'; @@ -1258,7 +1291,7 @@ class AppLocalizationsEs extends AppLocalizations { String get collectionGroupDay => 'Por día'; @override - String get collectionGroupNone => 'No agrupar'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Desconocido'; @@ -1538,9 +1571,6 @@ class AppLocalizationsEs extends AppLocalizations { @override String get albumGroupVolume => 'Por volumen de almacenamiento'; - @override - String get albumGroupNone => 'No agrupar'; - @override String get albumMimeTypeMixed => 'Mezclado'; @@ -2355,19 +2385,4 @@ class AppLocalizationsEs extends AppLocalizations { @override String get sourceViewerPageTitle => 'Fuente'; - - @override - String get filePickerShowHiddenFiles => 'Mostrar archivos ocultos'; - - @override - String get filePickerDoNotShowHiddenFiles => 'No mostrar archivos ocultos'; - - @override - String get filePickerOpenFrom => 'Abrir desde'; - - @override - String get filePickerNoItems => 'Sin elementos'; - - @override - String get filePickerUseThisFolder => 'Usar esta carpeta'; } diff --git a/lib/l10ngen/app_localizations_et.dart b/lib/l10ngen/app_localizations_et.dart index 38856b654..f254fce09 100644 --- a/lib/l10ngen/app_localizations_et.dart +++ b/lib/l10ngen/app_localizations_et.dart @@ -98,6 +98,9 @@ class AppLocalizationsEt extends AppLocalizations { @override String get applyButtonLabel => 'RAKENDA'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'KUSTUTA'; @@ -218,6 +221,9 @@ class AppLocalizationsEt extends AppLocalizations { @override String get chipActionUnpin => 'Eemalda ülalt äärest'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Muuda nime'; @@ -227,6 +233,9 @@ class AppLocalizationsEt extends AppLocalizations { @override String get chipActionShowCountryStates => 'Näita osariike'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Loo album'; @@ -494,6 +503,9 @@ class AppLocalizationsEt extends AppLocalizations { @override String get albumTierPinned => 'Esiletõstetud'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Üldised'; @@ -842,6 +854,27 @@ class AppLocalizationsEt extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Selline dünaamiline album on juba olemas'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Turvaruumis asuvad objektid on nähtavad vaid sellele rakendusele ja mitte ühelgi muul viisil.\n\nKui sa eemaldad nutiseadmest selle rakenduse või kustutad rakenduse andmed, siis kaob igasugune ligipääs nendele objektidele.'; @@ -1261,7 +1294,7 @@ class AppLocalizationsEt extends AppLocalizations { String get collectionGroupDay => 'Päevade kaupa'; @override - String get collectionGroupNone => 'Ära rühmita'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Teadmata'; @@ -1541,9 +1574,6 @@ class AppLocalizationsEt extends AppLocalizations { @override String get albumGroupVolume => 'Andmemahu alusel'; - @override - String get albumGroupNone => 'Ära rühmita'; - @override String get albumMimeTypeMixed => 'Erinev sisu'; @@ -2358,19 +2388,4 @@ class AppLocalizationsEt extends AppLocalizations { @override String get sourceViewerPageTitle => 'Allikas'; - - @override - String get filePickerShowHiddenFiles => 'Näita peidetud faile'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Ära näita peidetud faile'; - - @override - String get filePickerOpenFrom => 'Ava asukohast'; - - @override - String get filePickerNoItems => 'Meediafaile pole'; - - @override - String get filePickerUseThisFolder => 'Kasuta seda kausta'; } diff --git a/lib/l10ngen/app_localizations_eu.dart b/lib/l10ngen/app_localizations_eu.dart index 84f7155e3..9fb84bfdd 100644 --- a/lib/l10ngen/app_localizations_eu.dart +++ b/lib/l10ngen/app_localizations_eu.dart @@ -92,6 +92,9 @@ class AppLocalizationsEu extends AppLocalizations { @override String get applyButtonLabel => 'APLIKATU'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'EZABATU'; @@ -212,6 +215,9 @@ class AppLocalizationsEu extends AppLocalizations { @override String get chipActionUnpin => 'Desfinkatu goitik'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Aldatu izena'; @@ -221,6 +227,9 @@ class AppLocalizationsEu extends AppLocalizations { @override String get chipActionShowCountryStates => 'Erakutsi egoerak'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Sortu albuma'; @@ -488,6 +497,9 @@ class AppLocalizationsEu extends AppLocalizations { @override String get albumTierPinned => 'Finkatua'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Ohiko'; @@ -830,6 +842,27 @@ class AppLocalizationsEu extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Kutxa gotorreko elementuak aplikazio honetarako soilik daude eskuragarri eta ez beste edozeinetarako.\n\nAplikazio hau desinstalatzen baduzu, edo aplikazio honen datuak garbitu, elementu guzti hauek galduko dituzu.'; @@ -1243,7 +1276,7 @@ class AppLocalizationsEu extends AppLocalizations { String get collectionGroupDay => 'Egunaren arabera'; @override - String get collectionGroupNone => 'Ez taldekatu'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Ezezaguna'; @@ -1493,9 +1526,6 @@ class AppLocalizationsEu extends AppLocalizations { @override String get albumGroupVolume => 'Biltegiratze-tamainaren arabera'; - @override - String get albumGroupNone => 'Ez taldekatu'; - @override String get albumMimeTypeMixed => 'Nahastua'; @@ -2307,19 +2337,4 @@ class AppLocalizationsEu extends AppLocalizations { @override String get sourceViewerPageTitle => 'Iturria'; - - @override - String get filePickerShowHiddenFiles => 'Erakutsi ezkutuko fitxategiak'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Ez erakutsi ezkutuko fitxategiak'; - - @override - String get filePickerOpenFrom => 'Ireki hemendik'; - - @override - String get filePickerNoItems => 'Elementurik ez'; - - @override - String get filePickerUseThisFolder => 'Erabili karpeta hau'; } diff --git a/lib/l10ngen/app_localizations_fa.dart b/lib/l10ngen/app_localizations_fa.dart index c1a730c6b..7044f51ab 100644 --- a/lib/l10ngen/app_localizations_fa.dart +++ b/lib/l10ngen/app_localizations_fa.dart @@ -89,6 +89,9 @@ class AppLocalizationsFa extends AppLocalizations { @override String get applyButtonLabel => 'اعمال'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'پاک کردن'; @@ -209,6 +212,9 @@ class AppLocalizationsFa extends AppLocalizations { @override String get chipActionUnpin => 'برداشتن سنجاق از بالا'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'تغییر نام فایل'; @@ -218,6 +224,9 @@ class AppLocalizationsFa extends AppLocalizations { @override String get chipActionShowCountryStates => 'نمایش شهر‌ها'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'ساخت البوم'; @@ -485,6 +494,9 @@ class AppLocalizationsFa extends AppLocalizations { @override String get albumTierPinned => 'سنجاق شده'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'مشترک'; @@ -827,6 +839,27 @@ class AppLocalizationsFa extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'آلبوم پویا از پیش موجود است'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'موارد موجود در گاوصندوق تنها برای این برنامه در دسترس هستند و در هیچ برنامه دیگری وجود ندارد.\n\nاگر این برنامه را پاک کنید، یا داده‌های برنامه را پاک کنید، همه این موارد را از دست خواهید داد.'; @@ -1240,7 +1273,7 @@ class AppLocalizationsFa extends AppLocalizations { String get collectionGroupDay => 'با روز'; @override - String get collectionGroupNone => 'گروه نکن'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'ناشناس'; @@ -1490,9 +1523,6 @@ class AppLocalizationsFa extends AppLocalizations { @override String get albumGroupVolume => 'با حجم ذخیره سازی'; - @override - String get albumGroupNone => 'گروه نکن'; - @override String get albumMimeTypeMixed => 'ترکیبی'; @@ -2304,19 +2334,4 @@ class AppLocalizationsFa extends AppLocalizations { @override String get sourceViewerPageTitle => 'منبع'; - - @override - String get filePickerShowHiddenFiles => 'نمایش پرونده‌های پنهان'; - - @override - String get filePickerDoNotShowHiddenFiles => 'پرونده‌های پنهان را نمایش نده'; - - @override - String get filePickerOpenFrom => 'بازکردن از'; - - @override - String get filePickerNoItems => 'چیزی نیست'; - - @override - String get filePickerUseThisFolder => 'استفاده از این پوشه'; } diff --git a/lib/l10ngen/app_localizations_fi.dart b/lib/l10ngen/app_localizations_fi.dart index 759701db8..102ec7d67 100644 --- a/lib/l10ngen/app_localizations_fi.dart +++ b/lib/l10ngen/app_localizations_fi.dart @@ -92,6 +92,9 @@ class AppLocalizationsFi extends AppLocalizations { @override String get applyButtonLabel => 'KÄYTÄ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'POISTA'; @@ -212,6 +215,9 @@ class AppLocalizationsFi extends AppLocalizations { @override String get chipActionUnpin => 'Irrota kiinnitys ylhäältä'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Nimeä uudelleen'; @@ -221,6 +227,9 @@ class AppLocalizationsFi extends AppLocalizations { @override String get chipActionShowCountryStates => 'Näytä maat'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Luo albumi'; @@ -488,6 +497,9 @@ class AppLocalizationsFi extends AppLocalizations { @override String get albumTierPinned => 'Kiinnitetty'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Yleistä'; @@ -836,6 +848,27 @@ class AppLocalizationsFi extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1081,7 +1114,7 @@ class AppLocalizationsFi extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1255,7 +1288,7 @@ class AppLocalizationsFi extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1535,9 +1568,6 @@ class AppLocalizationsFi extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2352,19 +2382,4 @@ class AppLocalizationsFi extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_fr.dart b/lib/l10ngen/app_localizations_fr.dart index c1b693df4..cfb8571c1 100644 --- a/lib/l10ngen/app_localizations_fr.dart +++ b/lib/l10ngen/app_localizations_fr.dart @@ -98,6 +98,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get applyButtonLabel => 'APPLIQUER'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SUPPRIMER'; @@ -218,6 +221,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get chipActionUnpin => 'Retirer'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Renommer'; @@ -227,6 +233,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get chipActionShowCountryStates => 'Afficher les États'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Créer un album'; @@ -494,6 +503,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get albumTierPinned => 'Épinglés'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Standards'; @@ -842,6 +854,27 @@ class AppLocalizationsFr extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'L’album dynamique existe déjà'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Les éléments dans les coffres-forts ne sont visibles que dans cette app et nulle autre.\n\nSi vous désinstallez cette app, ou que vous supprimez ses données, vous perdrez tous ces éléments.'; @@ -1261,7 +1294,7 @@ class AppLocalizationsFr extends AppLocalizations { String get collectionGroupDay => 'par jour'; @override - String get collectionGroupNone => 'ne pas grouper'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Inconnu'; @@ -1541,9 +1574,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get albumGroupVolume => 'par volume de stockage'; - @override - String get albumGroupNone => 'ne pas grouper'; - @override String get albumMimeTypeMixed => 'Mixte'; @@ -2358,19 +2388,4 @@ class AppLocalizationsFr extends AppLocalizations { @override String get sourceViewerPageTitle => 'Code source'; - - @override - String get filePickerShowHiddenFiles => 'Afficher les fichiers masqués'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Ne pas afficher les fichiers masqués'; - - @override - String get filePickerOpenFrom => 'Ouvrir à partir de'; - - @override - String get filePickerNoItems => 'Aucun élément'; - - @override - String get filePickerUseThisFolder => 'Utiliser ce dossier'; } diff --git a/lib/l10ngen/app_localizations_gl.dart b/lib/l10ngen/app_localizations_gl.dart index 4733d2fb5..727d6fa69 100644 --- a/lib/l10ngen/app_localizations_gl.dart +++ b/lib/l10ngen/app_localizations_gl.dart @@ -95,6 +95,9 @@ class AppLocalizationsGl extends AppLocalizations { @override String get applyButtonLabel => 'APLICAR'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ELIMINAR'; @@ -215,6 +218,9 @@ class AppLocalizationsGl extends AppLocalizations { @override String get chipActionUnpin => 'Desbloquear dende arriba'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Cambiar o nome'; @@ -224,6 +230,9 @@ class AppLocalizationsGl extends AppLocalizations { @override String get chipActionShowCountryStates => 'Amosar estados'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Crear álbum'; @@ -491,6 +500,9 @@ class AppLocalizationsGl extends AppLocalizations { @override String get albumTierPinned => 'Fixado'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Común'; @@ -839,6 +851,27 @@ class AppLocalizationsGl extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Xa existe o álbum dinámico'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Os elementos en cofres só están dispoñibles para esta aplicación.\n\nPerderanse ao desinstalala ou ao borrar os seus datos.'; @@ -1258,7 +1291,7 @@ class AppLocalizationsGl extends AppLocalizations { String get collectionGroupDay => 'Por día'; @override - String get collectionGroupNone => 'Non agrupar'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Descoñecido'; @@ -1538,9 +1571,6 @@ class AppLocalizationsGl extends AppLocalizations { @override String get albumGroupVolume => 'Por volume de almacenaxe'; - @override - String get albumGroupNone => 'Non agrupar'; - @override String get albumMimeTypeMixed => 'Mesturado'; @@ -2355,19 +2385,4 @@ class AppLocalizationsGl extends AppLocalizations { @override String get sourceViewerPageTitle => 'Fonte'; - - @override - String get filePickerShowHiddenFiles => 'Amosar arquivos agochados'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Non amosar arquivos agochados'; - - @override - String get filePickerOpenFrom => 'Abrir dende'; - - @override - String get filePickerNoItems => 'Sen elementos'; - - @override - String get filePickerUseThisFolder => 'Usar este cartafol'; } diff --git a/lib/l10ngen/app_localizations_he.dart b/lib/l10ngen/app_localizations_he.dart index fc3535e2a..31bb7c3e9 100644 --- a/lib/l10ngen/app_localizations_he.dart +++ b/lib/l10ngen/app_localizations_he.dart @@ -98,6 +98,9 @@ class AppLocalizationsHe extends AppLocalizations { @override String get applyButtonLabel => 'החל'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'מחק'; @@ -218,6 +221,9 @@ class AppLocalizationsHe extends AppLocalizations { @override String get chipActionUnpin => 'בטל הצמד למעלה'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'שנה שם'; @@ -227,6 +233,9 @@ class AppLocalizationsHe extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsHe extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsHe extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsHe extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsHe extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsHe extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsHe extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_hi.dart b/lib/l10ngen/app_localizations_hi.dart index bfd9aa858..dc8b6a5e7 100644 --- a/lib/l10ngen/app_localizations_hi.dart +++ b/lib/l10ngen/app_localizations_hi.dart @@ -89,6 +89,9 @@ class AppLocalizationsHi extends AppLocalizations { @override String get applyButtonLabel => 'लगाऐ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'मिटाए'; @@ -209,6 +212,9 @@ class AppLocalizationsHi extends AppLocalizations { @override String get chipActionUnpin => 'शीर्ष से अनपिन करें'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'नाम बदले'; @@ -218,6 +224,9 @@ class AppLocalizationsHi extends AppLocalizations { @override String get chipActionShowCountryStates => 'राज्यों को दिखाएं'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'एल्बम बनाएं'; @@ -485,6 +494,9 @@ class AppLocalizationsHi extends AppLocalizations { @override String get albumTierPinned => 'पिन किया गया'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'कॉमन'; @@ -833,6 +845,27 @@ class AppLocalizationsHi extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'डायनेमिक एल्बम पहले से ही मौजूद है'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'वॉल्ट में आइटम केवल इस ऐप के लिए व अन्य के लिए नहीं उपलब्ध हैं।\n\nयदि आप इस ऐप को अनइंस्टॉल करते हैं, या इस ऐप डेटा को साफ़ करते हैं, तो आप इन सभी आइटम को खो देंगे।।'; @@ -1252,7 +1285,7 @@ class AppLocalizationsHi extends AppLocalizations { String get collectionGroupDay => 'दिन के अनुसार'; @override - String get collectionGroupNone => 'समूह न बनाएं'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'अज्ञात'; @@ -1532,9 +1565,6 @@ class AppLocalizationsHi extends AppLocalizations { @override String get albumGroupVolume => 'स्टोरेज मात्रा के अनुसार'; - @override - String get albumGroupNone => 'ग्रुप न बनाए'; - @override String get albumMimeTypeMixed => 'मिक्सड'; @@ -2349,19 +2379,4 @@ class AppLocalizationsHi extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_hu.dart b/lib/l10ngen/app_localizations_hu.dart index 395b40ad8..47313ddce 100644 --- a/lib/l10ngen/app_localizations_hu.dart +++ b/lib/l10ngen/app_localizations_hu.dart @@ -87,6 +87,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get applyButtonLabel => 'ALKALMAZ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'TÖRLÉS'; @@ -207,6 +210,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get chipActionUnpin => 'Kitűzés megszüntetése'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Átnevezés'; @@ -216,6 +222,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get chipActionShowCountryStates => 'Megyék megjelenítése'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Új album'; @@ -483,6 +492,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get albumTierPinned => 'Kitűzött'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Gyakori'; @@ -825,6 +837,27 @@ class AppLocalizationsHu extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'A dinamikus album már létezik'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'A széfben lévő elemek csak ebben az alkalmazásban érhetőek el.\n\nHa eltávolítod az alkalmazást vagy törlöd az adatokat, ezeket az elemeket is elveszíted.'; @@ -1238,7 +1271,7 @@ class AppLocalizationsHu extends AppLocalizations { String get collectionGroupDay => 'Napok szerint'; @override - String get collectionGroupNone => 'Nincs csoportositás'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Ismeretlen'; @@ -1488,9 +1521,6 @@ class AppLocalizationsHu extends AppLocalizations { @override String get albumGroupVolume => 'Tárhely alapján'; - @override - String get albumGroupNone => 'Nincs csoportositás'; - @override String get albumMimeTypeMixed => 'Vegyesen'; @@ -2301,19 +2331,4 @@ class AppLocalizationsHu extends AppLocalizations { @override String get sourceViewerPageTitle => 'Forrás'; - - @override - String get filePickerShowHiddenFiles => 'Rejtett fájlok mutatása'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Ne mutassa a rejtett fájlokat'; - - @override - String get filePickerOpenFrom => 'Megnyitás innen'; - - @override - String get filePickerNoItems => 'Nincsenek elemek'; - - @override - String get filePickerUseThisFolder => 'Mappa használata'; } diff --git a/lib/l10ngen/app_localizations_id.dart b/lib/l10ngen/app_localizations_id.dart index bece5bbc8..e6b166f42 100644 --- a/lib/l10ngen/app_localizations_id.dart +++ b/lib/l10ngen/app_localizations_id.dart @@ -90,6 +90,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get applyButtonLabel => 'TERAPKAN'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'BUANG'; @@ -210,6 +213,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get chipActionUnpin => 'Lepas sematan dari atas'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Ganti nama'; @@ -219,6 +225,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get chipActionShowCountryStates => 'Tampilkan wilayah'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Buat album'; @@ -486,6 +495,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get albumTierPinned => 'Disemat'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Biasa'; @@ -833,6 +845,27 @@ class AppLocalizationsId extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Album dinamis sudah ada'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Item dalam brankas hanya tersedia untuk aplikasi ini dan bukan yang lain.\n\nJika Anda menghapus aplikasi ini, atau menghapus data aplikasi ini, Anda akan kehilangan semua item tersebut.'; @@ -1252,7 +1285,7 @@ class AppLocalizationsId extends AppLocalizations { String get collectionGroupDay => 'Lewat hari'; @override - String get collectionGroupNone => 'Jangan kelompokkan'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Tidak dikenal'; @@ -1522,9 +1555,6 @@ class AppLocalizationsId extends AppLocalizations { @override String get albumGroupVolume => 'Lewat volume penyimpanan'; - @override - String get albumGroupNone => 'Jangan kelompokkan'; - @override String get albumMimeTypeMixed => 'Tercampur'; @@ -2338,19 +2368,4 @@ class AppLocalizationsId extends AppLocalizations { @override String get sourceViewerPageTitle => 'Sumber'; - - @override - String get filePickerShowHiddenFiles => 'Tampilkan file tersembunyi'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Jangan tampilkan file tersembunyi'; - - @override - String get filePickerOpenFrom => 'Buka dari'; - - @override - String get filePickerNoItems => 'Tidak ada benda'; - - @override - String get filePickerUseThisFolder => 'Gunakan folder ini'; } diff --git a/lib/l10ngen/app_localizations_is.dart b/lib/l10ngen/app_localizations_is.dart index b86b64fbe..75a58c25e 100644 --- a/lib/l10ngen/app_localizations_is.dart +++ b/lib/l10ngen/app_localizations_is.dart @@ -92,6 +92,9 @@ class AppLocalizationsIs extends AppLocalizations { @override String get applyButtonLabel => 'VIRKJA'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'EYÐA'; @@ -212,6 +215,9 @@ class AppLocalizationsIs extends AppLocalizations { @override String get chipActionUnpin => 'Losa af efri hluta'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Endurnefna'; @@ -221,6 +227,9 @@ class AppLocalizationsIs extends AppLocalizations { @override String get chipActionShowCountryStates => 'Birta héruð'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Búa til albúm'; @@ -488,6 +497,9 @@ class AppLocalizationsIs extends AppLocalizations { @override String get albumTierPinned => 'Fest'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Algengt'; @@ -830,6 +842,27 @@ class AppLocalizationsIs extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Breytilegt albúm er þegar til staðar'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Atriði í öryggisgeymslum eru einungis aðgengileg í þessu forriti og engum öðrum.\n\nEf þú fjarlægir þetta forrit, eða hreinsar gögn forritsins, muntu tapa öllum þessum atriðum.'; @@ -1243,7 +1276,7 @@ class AppLocalizationsIs extends AppLocalizations { String get collectionGroupDay => 'Eftir dögum'; @override - String get collectionGroupNone => 'Ekki hópa'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Óþekkt'; @@ -1493,9 +1526,6 @@ class AppLocalizationsIs extends AppLocalizations { @override String get albumGroupVolume => 'Eftir gagnageymslu'; - @override - String get albumGroupNone => 'Ekki hópa'; - @override String get albumMimeTypeMixed => 'Blandað'; @@ -2307,19 +2337,4 @@ class AppLocalizationsIs extends AppLocalizations { @override String get sourceViewerPageTitle => 'Uppruni'; - - @override - String get filePickerShowHiddenFiles => 'Birta faldar skrár'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Ekki birta faldar skrár'; - - @override - String get filePickerOpenFrom => 'Opið frá'; - - @override - String get filePickerNoItems => 'Engir hlutir'; - - @override - String get filePickerUseThisFolder => 'Nota þessa möppu'; } diff --git a/lib/l10ngen/app_localizations_it.dart b/lib/l10ngen/app_localizations_it.dart index 4d58a53ab..ca8b20da2 100644 --- a/lib/l10ngen/app_localizations_it.dart +++ b/lib/l10ngen/app_localizations_it.dart @@ -95,6 +95,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get applyButtonLabel => 'APPLICA'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ELIMINA'; @@ -215,6 +218,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get chipActionUnpin => 'Rimuovi dall’alto'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rinomina'; @@ -224,6 +230,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get chipActionShowCountryStates => 'Visualizza stati'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Crea album'; @@ -491,6 +500,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get albumTierPinned => 'Fissati'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Frequenti'; @@ -839,6 +851,27 @@ class AppLocalizationsIt extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Album dinamico già esistente'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Gli elementi nelle cassaforti sono disponibili solo per questa app e non per altre.\n\nSe disinstalli l’app o cancelli i relativi dati, perderai tutti questi elementi.'; @@ -1258,7 +1291,7 @@ class AppLocalizationsIt extends AppLocalizations { String get collectionGroupDay => 'Per giorno'; @override - String get collectionGroupNone => 'Non raggruppare'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Sconosciuto'; @@ -1538,9 +1571,6 @@ class AppLocalizationsIt extends AppLocalizations { @override String get albumGroupVolume => 'Per volume archiviazione'; - @override - String get albumGroupNone => 'Non raggruppare'; - @override String get albumMimeTypeMixed => 'Misto'; @@ -2355,19 +2385,4 @@ class AppLocalizationsIt extends AppLocalizations { @override String get sourceViewerPageTitle => 'Codice sorgente'; - - @override - String get filePickerShowHiddenFiles => 'Visualizza file nascosti'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Non visualizzare i file nascosti'; - - @override - String get filePickerOpenFrom => 'Apri da'; - - @override - String get filePickerNoItems => 'Nessun elemento'; - - @override - String get filePickerUseThisFolder => 'Usa questa cartella'; } diff --git a/lib/l10ngen/app_localizations_ja.dart b/lib/l10ngen/app_localizations_ja.dart index 928a6fddd..3d160f8b2 100644 --- a/lib/l10ngen/app_localizations_ja.dart +++ b/lib/l10ngen/app_localizations_ja.dart @@ -90,6 +90,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get applyButtonLabel => '適用'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => '削除'; @@ -210,6 +213,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get chipActionUnpin => '一番上への固定を解除'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => '名前を変更'; @@ -219,6 +225,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get chipActionShowCountryStates => '地域を表示'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'アルバムを作成'; @@ -486,6 +495,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get albumTierPinned => '固定'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => '全体'; @@ -833,6 +845,27 @@ class AppLocalizationsJa extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'ダイナミックアルバムはすでに存在します'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => '保管庫のアイテムはアプリ内のみで保存しているため、他のアプリでは利用できません。\n\nこのアプリをアンインストールしたり、データを消去したりすると、これらのアイテムはすべて失われます。'; @@ -1252,7 +1285,7 @@ class AppLocalizationsJa extends AppLocalizations { String get collectionGroupDay => '日別'; @override - String get collectionGroupNone => 'グループ化しない'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => '不明'; @@ -1522,9 +1555,6 @@ class AppLocalizationsJa extends AppLocalizations { @override String get albumGroupVolume => 'ストレージ ボリューム別'; - @override - String get albumGroupNone => 'グループ化しない'; - @override String get albumMimeTypeMixed => '混合'; @@ -2338,19 +2368,4 @@ class AppLocalizationsJa extends AppLocalizations { @override String get sourceViewerPageTitle => 'ソース'; - - @override - String get filePickerShowHiddenFiles => '非表示のファイルを表示する'; - - @override - String get filePickerDoNotShowHiddenFiles => '非表示のファイルを表示しない'; - - @override - String get filePickerOpenFrom => '次から開く'; - - @override - String get filePickerNoItems => 'アイテムはありません'; - - @override - String get filePickerUseThisFolder => 'このフォルダを使用'; } diff --git a/lib/l10ngen/app_localizations_kn.dart b/lib/l10ngen/app_localizations_kn.dart index a515cb37d..6ea59fac0 100644 --- a/lib/l10ngen/app_localizations_kn.dart +++ b/lib/l10ngen/app_localizations_kn.dart @@ -98,6 +98,9 @@ class AppLocalizationsKn extends AppLocalizations { @override String get applyButtonLabel => 'ಅನ್ವಯಿಸು'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ಅಳಿಸಿ'; @@ -218,6 +221,9 @@ class AppLocalizationsKn extends AppLocalizations { @override String get chipActionUnpin => 'ಪಿನ್ ತೆಗೆಯಿರಿ'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'ಮರುನಾಮಕರಣ'; @@ -227,6 +233,9 @@ class AppLocalizationsKn extends AppLocalizations { @override String get chipActionShowCountryStates => 'ಅಂಕಿಅಂಶಗಳನ್ನು ತೋರಿಸಿ'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'ಆಲ್ಬಮ್ ರಚಿಸಿ'; @@ -494,6 +503,9 @@ class AppLocalizationsKn extends AppLocalizations { @override String get albumTierPinned => 'ಚುಚ್ಚಿರುವುದು'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'ಸಾಮಾನ್ಯ'; @@ -842,6 +854,27 @@ class AppLocalizationsKn extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'ಡೈನಾಮಿಕ್ ಆಲ್ಬಮ್ ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿದೆ'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'ನೆಲಮಾಳಿಗೆಯಲ್ಲಿನ ವಸ್ತುಗಳು ಈ ಅಪ್ಲಿಕೇಶನ್‌ನಲ್ಲಿ ಮಾತ್ರ ಲಭ್ಯವಿದೆ ಮತ್ತು ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಲ್ಲಿ ಇರುವುದಿಲ್ಲ.\n\nನೀವು ಈ ಅಪ್ಲಿಕೇಶನ್ ನನ್ನು ಅಸ್ಥಾಪಿಸಿದರೆ ಅಥವಾ ಈ ಅಪ್ಲಿಕೇಶನ್ ದತ್ತಾಂಶಗಳನ್ನು ತೆರವುಗೊಳಿಸಿದರೆ, ನೀವು ಈ ಎಲ್ಲ ವಸ್ತುಗಳನ್ನು ಕಳೆದುಕೊಳ್ಳುತ್ತೀರಿ.'; @@ -1261,7 +1294,7 @@ class AppLocalizationsKn extends AppLocalizations { String get collectionGroupDay => 'ದಿನದಂತೆ'; @override - String get collectionGroupNone => 'ಗುಂಪು ಮಾಡಬೇಡಿ'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'ಅಜ್ಞಾತ'; @@ -1541,9 +1574,6 @@ class AppLocalizationsKn extends AppLocalizations { @override String get albumGroupVolume => 'ಸಂಗ್ರಹಣೆಯ ಗಾತ್ರದಂತೆ'; - @override - String get albumGroupNone => 'ಗುಂಪು ಮಾಡಬೇಡಿ'; - @override String get albumMimeTypeMixed => 'ಮಿಶ್ರಿತ'; @@ -2358,19 +2388,4 @@ class AppLocalizationsKn extends AppLocalizations { @override String get sourceViewerPageTitle => 'ಮೂಲ'; - - @override - String get filePickerShowHiddenFiles => 'ಮರೆಮಾಡಿದ ಕಡತಗಳನ್ನು ತೋರಿಸಿ'; - - @override - String get filePickerDoNotShowHiddenFiles => 'ಮರೆಮಾಡಿದ ಕಡತಗಳನ್ನು ತೋರಿಸಬೇಡಿ'; - - @override - String get filePickerOpenFrom => 'ಇಂದ ತೆರೆಯಿರಿ'; - - @override - String get filePickerNoItems => 'ವಸ್ತುಗಳಿಲ್ಲ'; - - @override - String get filePickerUseThisFolder => 'ಈ ಕೋಶವನ್ನು ಬಳಸಿ'; } diff --git a/lib/l10ngen/app_localizations_ko.dart b/lib/l10ngen/app_localizations_ko.dart index 3ffa77937..5d64bcf11 100644 --- a/lib/l10ngen/app_localizations_ko.dart +++ b/lib/l10ngen/app_localizations_ko.dart @@ -90,6 +90,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get applyButtonLabel => '적용'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => '삭제'; @@ -210,6 +213,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get chipActionUnpin => '고정 해제'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => '이름 변경'; @@ -219,6 +225,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get chipActionShowCountryStates => '주 보기'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => '앨범 만들기'; @@ -486,6 +495,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get albumTierPinned => '고정'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => '기본'; @@ -833,6 +845,27 @@ class AppLocalizationsKo extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => '동적 앨범이 이미 있습니다'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => '금고에 있는 항목들은 이 앱에서만 볼 수 있습니다.\n\n이 앱을 삭제 시, 또한 이 앱의 데이터를 삭제 시, 항목을 완전히 삭제될 것입니다.'; @@ -1250,7 +1283,7 @@ class AppLocalizationsKo extends AppLocalizations { String get collectionGroupDay => '날짜별로'; @override - String get collectionGroupNone => '묶음 없음'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => '없음'; @@ -1520,9 +1553,6 @@ class AppLocalizationsKo extends AppLocalizations { @override String get albumGroupVolume => '저장공간별로'; - @override - String get albumGroupNone => '묶음 없음'; - @override String get albumMimeTypeMixed => '혼합'; @@ -2336,19 +2366,4 @@ class AppLocalizationsKo extends AppLocalizations { @override String get sourceViewerPageTitle => '소스 코드'; - - @override - String get filePickerShowHiddenFiles => '숨겨진 파일 표시'; - - @override - String get filePickerDoNotShowHiddenFiles => '숨겨진 파일 표시 안함'; - - @override - String get filePickerOpenFrom => '다음에서 열기:'; - - @override - String get filePickerNoItems => '항목 없음'; - - @override - String get filePickerUseThisFolder => '이 폴더 사용'; } diff --git a/lib/l10ngen/app_localizations_lt.dart b/lib/l10ngen/app_localizations_lt.dart index ba4ca116f..402f3285f 100644 --- a/lib/l10ngen/app_localizations_lt.dart +++ b/lib/l10ngen/app_localizations_lt.dart @@ -99,6 +99,9 @@ class AppLocalizationsLt extends AppLocalizations { @override String get applyButtonLabel => 'TAIKYTI'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'IŠTRINTI'; @@ -219,6 +222,9 @@ class AppLocalizationsLt extends AppLocalizations { @override String get chipActionUnpin => 'Atsegti nuo viršaus'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Pervadinti'; @@ -228,6 +234,9 @@ class AppLocalizationsLt extends AppLocalizations { @override String get chipActionShowCountryStates => 'Rodyti valstybes'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Sukurti albumą'; @@ -495,6 +504,9 @@ class AppLocalizationsLt extends AppLocalizations { @override String get albumTierPinned => 'Prisegti'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Bendri'; @@ -837,6 +849,27 @@ class AppLocalizationsLt extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Daiktai saugyklose yra prieinami tik šiai programėlei ir jokiom kitom.\n\nJei pašalinsite šią programą arba išvalysite jos duomenis, jūs prarasite visus šiuos daiktus.'; @@ -1250,7 +1283,7 @@ class AppLocalizationsLt extends AppLocalizations { String get collectionGroupDay => 'Pagal dieną'; @override - String get collectionGroupNone => 'Negrupuoti'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Nežinoma'; @@ -1500,9 +1533,6 @@ class AppLocalizationsLt extends AppLocalizations { @override String get albumGroupVolume => 'Pagal apimtį saugykloje'; - @override - String get albumGroupNone => 'Negrupuoti'; - @override String get albumMimeTypeMixed => 'Mišrus'; @@ -2314,19 +2344,4 @@ class AppLocalizationsLt extends AppLocalizations { @override String get sourceViewerPageTitle => 'Šaltinis'; - - @override - String get filePickerShowHiddenFiles => 'Rodyti paslėptus failus'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Nerodyti paslėptų failų'; - - @override - String get filePickerOpenFrom => 'Atidaryti iš'; - - @override - String get filePickerNoItems => 'Nėra elementų'; - - @override - String get filePickerUseThisFolder => 'Naudoti šį aplanką'; } diff --git a/lib/l10ngen/app_localizations_ml.dart b/lib/l10ngen/app_localizations_ml.dart index 5a302a86d..d23426cd0 100644 --- a/lib/l10ngen/app_localizations_ml.dart +++ b/lib/l10ngen/app_localizations_ml.dart @@ -98,6 +98,9 @@ class AppLocalizationsMl extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsMl extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsMl extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsMl extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsMl extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsMl extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsMl extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsMl extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsMl extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_my.dart b/lib/l10ngen/app_localizations_my.dart index 90fda817f..46a861cc7 100644 --- a/lib/l10ngen/app_localizations_my.dart +++ b/lib/l10ngen/app_localizations_my.dart @@ -87,6 +87,9 @@ class AppLocalizationsMy extends AppLocalizations { @override String get applyButtonLabel => 'လုပ်ဆောင်မည်'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ဖျက်မည်'; @@ -207,6 +210,9 @@ class AppLocalizationsMy extends AppLocalizations { @override String get chipActionUnpin => 'ထိပ်ဆုံးမှပင်ဖြုတ်ရန်'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'အမည်ပြောင်းရန်'; @@ -216,6 +222,9 @@ class AppLocalizationsMy extends AppLocalizations { @override String get chipActionShowCountryStates => 'နိုင်ငံများပြရန်'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'အယ်လ်ဘမ်ဖန်တီးရန်'; @@ -483,6 +492,9 @@ class AppLocalizationsMy extends AppLocalizations { @override String get albumTierPinned => 'ပင်တွဲထား'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'အကြည့်များ'; @@ -827,6 +839,27 @@ class AppLocalizationsMy extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'လျှို့ဝှက်သိုလှောင်ခန်းရှိ ဓာတ်ပုံနှင့်ဗီဒီယိုများကို ဤအက်ပ်တွင်သာမြင်ရမည်ဖြစ်ပြီး အခြားအက်ပ်များတွင် မမြင်ရနိုင်ပါ။\n\nဤအက်ပ်ကိုဖျက်လိုက်သည်ဖြစ်စေ၊ အက်ပ်ဒေတာကိုရှင်းလင်းလိုက်သည်ဖြစ်စေ ၎င်းဓာတ်ပုံနှင့်ဗီဒီယိုအားလုံးကို ဆုံးရှုံးသွားမည်ဖြစ်ပါသည်။'; @@ -1242,7 +1275,7 @@ class AppLocalizationsMy extends AppLocalizations { String get collectionGroupDay => 'ရက်အလိုက်'; @override - String get collectionGroupNone => 'စုမပြပါနှင့်'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'မသိထားသည်များ'; @@ -1502,9 +1535,6 @@ class AppLocalizationsMy extends AppLocalizations { @override String get albumGroupVolume => 'သိုလှောင်မှုပမာဏအလိုက်'; - @override - String get albumGroupNone => 'စုမပြပါနှင့်'; - @override String get albumMimeTypeMixed => 'ရောထား'; @@ -2319,19 +2349,4 @@ class AppLocalizationsMy extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_nb.dart b/lib/l10ngen/app_localizations_nb.dart index 17dc2750f..20fd437e6 100644 --- a/lib/l10ngen/app_localizations_nb.dart +++ b/lib/l10ngen/app_localizations_nb.dart @@ -92,6 +92,9 @@ class AppLocalizationsNb extends AppLocalizations { @override String get applyButtonLabel => 'BRUK'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SLETT'; @@ -212,6 +215,9 @@ class AppLocalizationsNb extends AppLocalizations { @override String get chipActionUnpin => 'Løsne fra toppen'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Gi nytt navn'; @@ -221,6 +227,9 @@ class AppLocalizationsNb extends AppLocalizations { @override String get chipActionShowCountryStates => 'Vis tilstander'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Opprett album'; @@ -488,6 +497,9 @@ class AppLocalizationsNb extends AppLocalizations { @override String get albumTierPinned => 'Festet'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Ofte åpnet'; @@ -830,6 +842,27 @@ class AppLocalizationsNb extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Elementer i hvelv er kun tilgjengelig for dette programmet, og ikke andre.\n\nHvis du avinstallerer dette programmet, eller tømmer denne programdataen vil du miste alle disse elementene.'; @@ -1243,7 +1276,7 @@ class AppLocalizationsNb extends AppLocalizations { String get collectionGroupDay => 'Etter dag'; @override - String get collectionGroupNone => 'Ikke grupper'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Ukjent'; @@ -1493,9 +1526,6 @@ class AppLocalizationsNb extends AppLocalizations { @override String get albumGroupVolume => 'Etter lagringsdataområde'; - @override - String get albumGroupNone => 'Ikke grupper'; - @override String get albumMimeTypeMixed => 'Blandet'; @@ -2307,19 +2337,4 @@ class AppLocalizationsNb extends AppLocalizations { @override String get sourceViewerPageTitle => 'Kilde'; - - @override - String get filePickerShowHiddenFiles => 'Vis skjulte filer'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Ikke vis skjulte filer'; - - @override - String get filePickerOpenFrom => 'Åpne fra'; - - @override - String get filePickerNoItems => 'Ingen elementer'; - - @override - String get filePickerUseThisFolder => 'Bruk denne mappen'; } diff --git a/lib/l10ngen/app_localizations_ne.dart b/lib/l10ngen/app_localizations_ne.dart index 658caac50..f066ad367 100644 --- a/lib/l10ngen/app_localizations_ne.dart +++ b/lib/l10ngen/app_localizations_ne.dart @@ -98,6 +98,9 @@ class AppLocalizationsNe extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsNe extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsNe extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsNe extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsNe extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsNe extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsNe extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsNe extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsNe extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_nl.dart b/lib/l10ngen/app_localizations_nl.dart index ac971733c..7aa99497f 100644 --- a/lib/l10ngen/app_localizations_nl.dart +++ b/lib/l10ngen/app_localizations_nl.dart @@ -98,6 +98,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get applyButtonLabel => 'TOEPASSEN'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'VERWIJDEREN'; @@ -218,6 +221,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get chipActionUnpin => 'Unpinnen'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Hernoemen'; @@ -227,6 +233,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get chipActionShowCountryStates => 'Staten tonen'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Album aanmaken'; @@ -494,6 +503,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get albumTierPinned => 'Gepint'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Veelgebruikt'; @@ -841,6 +853,27 @@ class AppLocalizationsNl extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamisch album bestaat al'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in kluizen zijn alleen beschikbaar voor deze app en niet voor andere.\n\nAls je deze app verwijdert of deze app-gegevens wist, verlies je al deze items.'; @@ -1260,7 +1293,7 @@ class AppLocalizationsNl extends AppLocalizations { String get collectionGroupDay => 'Op dag'; @override - String get collectionGroupNone => 'Niet groeperen'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Onbekend'; @@ -1540,9 +1573,6 @@ class AppLocalizationsNl extends AppLocalizations { @override String get albumGroupVolume => 'Op opslagvolume'; - @override - String get albumGroupNone => 'Niet groeperen'; - @override String get albumMimeTypeMixed => 'Gemengd'; @@ -2357,19 +2387,4 @@ class AppLocalizationsNl extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Verborgen bestanden weergeven'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Verborgen bestanden niet tonen'; - - @override - String get filePickerOpenFrom => 'Openen van'; - - @override - String get filePickerNoItems => 'Geen items'; - - @override - String get filePickerUseThisFolder => 'Deze map gebruiken'; } diff --git a/lib/l10ngen/app_localizations_nn.dart b/lib/l10ngen/app_localizations_nn.dart index 2556e386b..1314ac603 100644 --- a/lib/l10ngen/app_localizations_nn.dart +++ b/lib/l10ngen/app_localizations_nn.dart @@ -90,6 +90,9 @@ class AppLocalizationsNn extends AppLocalizations { @override String get applyButtonLabel => 'NYTTA'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SLETT'; @@ -210,6 +213,9 @@ class AppLocalizationsNn extends AppLocalizations { @override String get chipActionUnpin => 'losna ifrå toppen'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Byt namn'; @@ -219,6 +225,9 @@ class AppLocalizationsNn extends AppLocalizations { @override String get chipActionShowCountryStates => 'Vis landsdelar'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Lag eit album'; @@ -486,6 +495,9 @@ class AppLocalizationsNn extends AppLocalizations { @override String get albumTierPinned => 'Festa'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Ofte opna'; @@ -827,6 +839,27 @@ class AppLocalizationsNn extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Ting i kvelva er berre tilgjengelege for denne appen.\n\nOm du slettar denne appen eller tømmer dataa til denne appen, vil du missa alt som er i kvelva.'; @@ -1240,7 +1273,7 @@ class AppLocalizationsNn extends AppLocalizations { String get collectionGroupDay => 'Etter dag'; @override - String get collectionGroupNone => 'Ikkje hop'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Ukjend'; @@ -1482,9 +1515,6 @@ class AppLocalizationsNn extends AppLocalizations { @override String get albumGroupVolume => 'Etter gøymestad'; - @override - String get albumGroupNone => 'Ikkje hop'; - @override String get albumMimeTypeMixed => 'Blanda'; @@ -2295,19 +2325,4 @@ class AppLocalizationsNn extends AppLocalizations { @override String get sourceViewerPageTitle => 'Kjelde'; - - @override - String get filePickerShowHiddenFiles => 'Vis skjulte filer'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Ikkje vis skjulte filer'; - - @override - String get filePickerOpenFrom => 'Opne ifrå'; - - @override - String get filePickerNoItems => 'Ingen ting'; - - @override - String get filePickerUseThisFolder => 'Bruk denne mappa'; } diff --git a/lib/l10ngen/app_localizations_or.dart b/lib/l10ngen/app_localizations_or.dart index 4e793a957..6dd93f9e1 100644 --- a/lib/l10ngen/app_localizations_or.dart +++ b/lib/l10ngen/app_localizations_or.dart @@ -98,6 +98,9 @@ class AppLocalizationsOr extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsOr extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsOr extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsOr extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsOr extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1261,7 +1294,7 @@ class AppLocalizationsOr extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'ଅଜ୍ଞାତ'; @@ -1541,9 +1574,6 @@ class AppLocalizationsOr extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsOr extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_pl.dart b/lib/l10ngen/app_localizations_pl.dart index bb07c74cd..5f03dac93 100644 --- a/lib/l10ngen/app_localizations_pl.dart +++ b/lib/l10ngen/app_localizations_pl.dart @@ -97,6 +97,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get applyButtonLabel => 'ZASTOSUJ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'USUŃ'; @@ -217,6 +220,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get chipActionUnpin => 'Odepnij z góry'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Zmień nazwę'; @@ -226,6 +232,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get chipActionShowCountryStates => 'Pokaż stany'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Utwórz album'; @@ -493,6 +502,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get albumTierPinned => 'Przypięty'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Wspólne'; @@ -835,6 +847,27 @@ class AppLocalizationsPl extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamiczny album już istnieje'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Elementy w skarbcach są dostępne tylko dla tej aplikacji i żadnej innej.\n\nJeśli odinstalujesz tę aplikację lub wyczyścisz dane tej aplikacji, stracisz wszystkie te elementy.'; @@ -1250,7 +1283,7 @@ class AppLocalizationsPl extends AppLocalizations { String get collectionGroupDay => 'Według dnia'; @override - String get collectionGroupNone => 'Nie grupuj'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Nieznany'; @@ -1503,9 +1536,6 @@ class AppLocalizationsPl extends AppLocalizations { @override String get albumGroupVolume => 'Według pojemności magazynu'; - @override - String get albumGroupNone => 'Nie grupuj'; - @override String get albumMimeTypeMixed => 'Mieszane'; @@ -2318,19 +2348,4 @@ class AppLocalizationsPl extends AppLocalizations { @override String get sourceViewerPageTitle => 'Źródło'; - - @override - String get filePickerShowHiddenFiles => 'Pokaż ukryte pliki'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Nie pokazuj ukrytych plików'; - - @override - String get filePickerOpenFrom => 'Otwórz z'; - - @override - String get filePickerNoItems => 'Brak elementów'; - - @override - String get filePickerUseThisFolder => 'Użyj tego katalogu'; } diff --git a/lib/l10ngen/app_localizations_pt.dart b/lib/l10ngen/app_localizations_pt.dart index c6ae3300b..cb6c0ecee 100644 --- a/lib/l10ngen/app_localizations_pt.dart +++ b/lib/l10ngen/app_localizations_pt.dart @@ -95,6 +95,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get applyButtonLabel => 'APLICAR'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'EXCLUIR'; @@ -215,6 +218,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get chipActionUnpin => 'Desafixar do topo'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Renomear'; @@ -224,6 +230,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get chipActionShowCountryStates => 'Mostrar estados'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Criar álbum'; @@ -491,6 +500,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get albumTierPinned => 'Fixada'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Comum'; @@ -839,6 +851,27 @@ class AppLocalizationsPt extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'O álbum dinâmico já existe'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Os itens nos cofres estão disponíveis apenas para este aplicativo e nenhum outro.\n\nSe você desinstalar este aplicativo ou limpar os dados do aplicativo, perderá todos esses itens.'; @@ -1258,7 +1291,7 @@ class AppLocalizationsPt extends AppLocalizations { String get collectionGroupDay => 'Por dia'; @override - String get collectionGroupNone => 'Não agrupe'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Desconhecido'; @@ -1538,9 +1571,6 @@ class AppLocalizationsPt extends AppLocalizations { @override String get albumGroupVolume => 'Por volume de armazenamento'; - @override - String get albumGroupNone => 'Não agrupe'; - @override String get albumMimeTypeMixed => 'Misturado'; @@ -2355,19 +2385,4 @@ class AppLocalizationsPt extends AppLocalizations { @override String get sourceViewerPageTitle => 'Fonte'; - - @override - String get filePickerShowHiddenFiles => 'Mostrar arquivos ocultos'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Não mostre arquivos ocultos'; - - @override - String get filePickerOpenFrom => 'Abrir de'; - - @override - String get filePickerNoItems => 'Nenhum itens'; - - @override - String get filePickerUseThisFolder => 'Usar esta pasta'; } diff --git a/lib/l10ngen/app_localizations_ro.dart b/lib/l10ngen/app_localizations_ro.dart index 4f0953f8c..69ab85e96 100644 --- a/lib/l10ngen/app_localizations_ro.dart +++ b/lib/l10ngen/app_localizations_ro.dart @@ -92,6 +92,9 @@ class AppLocalizationsRo extends AppLocalizations { @override String get applyButtonLabel => 'APLICA'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ȘTERGE'; @@ -212,6 +215,9 @@ class AppLocalizationsRo extends AppLocalizations { @override String get chipActionUnpin => 'Anulați fixarea de sus'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Redenumiți'; @@ -221,6 +227,9 @@ class AppLocalizationsRo extends AppLocalizations { @override String get chipActionShowCountryStates => 'Afișare state'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Creați album'; @@ -488,6 +497,9 @@ class AppLocalizationsRo extends AppLocalizations { @override String get albumTierPinned => 'Fixat'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Uzual'; @@ -830,6 +842,27 @@ class AppLocalizationsRo extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Albumul dinamic există deja'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Elementele din seifuri sunt disponibile doar pentru această aplicație și nu pentru altele.\n\nDacă dezinstalezi această aplicație sau ștergi datele acestei aplicații, vei pierde toate aceste elemente.'; @@ -1243,7 +1276,7 @@ class AppLocalizationsRo extends AppLocalizations { String get collectionGroupDay => 'După zi'; @override - String get collectionGroupNone => 'Nu grupați'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Necunoscut'; @@ -1493,9 +1526,6 @@ class AppLocalizationsRo extends AppLocalizations { @override String get albumGroupVolume => 'După volumul de stocare'; - @override - String get albumGroupNone => 'Nu se grupează'; - @override String get albumMimeTypeMixed => 'Amestecat'; @@ -2307,19 +2337,4 @@ class AppLocalizationsRo extends AppLocalizations { @override String get sourceViewerPageTitle => 'Sursă'; - - @override - String get filePickerShowHiddenFiles => 'Afișare fișiere ascunse'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Nu se afișează fișiere ascunse'; - - @override - String get filePickerOpenFrom => 'Deschidere din'; - - @override - String get filePickerNoItems => 'Fără articole'; - - @override - String get filePickerUseThisFolder => 'Utilizați acest dosar'; } diff --git a/lib/l10ngen/app_localizations_ru.dart b/lib/l10ngen/app_localizations_ru.dart index 436f7e1b2..b93c3d889 100644 --- a/lib/l10ngen/app_localizations_ru.dart +++ b/lib/l10ngen/app_localizations_ru.dart @@ -100,6 +100,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get applyButtonLabel => 'ПРИМЕНИТЬ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'УДАЛИТЬ'; @@ -220,6 +223,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get chipActionUnpin => 'Открепить'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Переименовать'; @@ -229,6 +235,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get chipActionShowCountryStates => 'Показать государства'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Создать альбом'; @@ -496,6 +505,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get albumTierPinned => 'Закрепленные'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Стандартные'; @@ -846,6 +858,27 @@ class AppLocalizationsRu extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Динамический альбом уже существует'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Элементы внутри хранилищ доступны только для этого приложения, и никакого другого.\n\nЕсли вы удалите приложение или очистите его данные, то вы потеряете все содержимое внутри хранилищ.'; @@ -1265,7 +1298,7 @@ class AppLocalizationsRu extends AppLocalizations { String get collectionGroupDay => 'По дню'; @override - String get collectionGroupNone => 'Не группировать'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Неизвестно'; @@ -1555,9 +1588,6 @@ class AppLocalizationsRu extends AppLocalizations { @override String get albumGroupVolume => 'По накопителю'; - @override - String get albumGroupNone => 'Не группировать'; - @override String get albumMimeTypeMixed => 'Разное'; @@ -2373,19 +2403,4 @@ class AppLocalizationsRu extends AppLocalizations { @override String get sourceViewerPageTitle => 'Источник'; - - @override - String get filePickerShowHiddenFiles => 'Показывать скрытые файлы'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Не показывать скрытые файлы'; - - @override - String get filePickerOpenFrom => 'Открыть'; - - @override - String get filePickerNoItems => 'Ничего нет'; - - @override - String get filePickerUseThisFolder => 'Использовать эту папку'; } diff --git a/lib/l10ngen/app_localizations_sat.dart b/lib/l10ngen/app_localizations_sat.dart index f691c216a..f9dfdab16 100644 --- a/lib/l10ngen/app_localizations_sat.dart +++ b/lib/l10ngen/app_localizations_sat.dart @@ -98,6 +98,9 @@ class AppLocalizationsSat extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsSat extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsSat extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsSat extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsSat extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsSat extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsSat extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsSat extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsSat extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_sk.dart b/lib/l10ngen/app_localizations_sk.dart index 2e406f9cd..94233b935 100644 --- a/lib/l10ngen/app_localizations_sk.dart +++ b/lib/l10ngen/app_localizations_sk.dart @@ -92,6 +92,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get applyButtonLabel => 'POUŽIŤ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ODSTRÁNIŤ'; @@ -212,6 +215,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get chipActionUnpin => 'Odstrániť z pripnutia'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Premenovať'; @@ -221,6 +227,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get chipActionShowCountryStates => 'Zobraziť štáty'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Vytvoriť album'; @@ -488,6 +497,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get albumTierPinned => 'Pripnuté'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Spoločné'; @@ -830,6 +842,27 @@ class AppLocalizationsSk extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamický album už existuje'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Položky v trezoroch sú dostupné iba v tejto aplikácií\n\nAk aplikáciu odinštaluješ alebo vymažeš dáta aplikácie, stratíš všetky položky z trezorov.'; @@ -1243,7 +1276,7 @@ class AppLocalizationsSk extends AppLocalizations { String get collectionGroupDay => 'Podľa dňa'; @override - String get collectionGroupNone => 'Nezoskupovať'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Neznáme'; @@ -1498,9 +1531,6 @@ class AppLocalizationsSk extends AppLocalizations { @override String get albumGroupVolume => 'Podľa objemu pamäte'; - @override - String get albumGroupNone => 'Nezoskupovať'; - @override String get albumMimeTypeMixed => 'Zmiešané'; @@ -2312,19 +2342,4 @@ class AppLocalizationsSk extends AppLocalizations { @override String get sourceViewerPageTitle => 'Zdroj'; - - @override - String get filePickerShowHiddenFiles => 'Ukázať skryté súbory'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Neukazovať skryté súbory'; - - @override - String get filePickerOpenFrom => 'Otvoriť z'; - - @override - String get filePickerNoItems => 'Žiadne súbory'; - - @override - String get filePickerUseThisFolder => 'Použiť tento priečinok'; } diff --git a/lib/l10ngen/app_localizations_sl.dart b/lib/l10ngen/app_localizations_sl.dart index 1085ca0a1..2367ad352 100644 --- a/lib/l10ngen/app_localizations_sl.dart +++ b/lib/l10ngen/app_localizations_sl.dart @@ -98,6 +98,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsSl extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsSl extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsSl extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsSl extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsSl extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_sr.dart b/lib/l10ngen/app_localizations_sr.dart index 6bf41fbf9..781b9bcf3 100644 --- a/lib/l10ngen/app_localizations_sr.dart +++ b/lib/l10ngen/app_localizations_sr.dart @@ -98,6 +98,9 @@ class AppLocalizationsSr extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsSr extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsSr extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsSr extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsSr extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsSr extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsSr extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsSr extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsSr extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_sv.dart b/lib/l10ngen/app_localizations_sv.dart index 6ef787926..84dcdf99e 100644 --- a/lib/l10ngen/app_localizations_sv.dart +++ b/lib/l10ngen/app_localizations_sv.dart @@ -91,6 +91,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get applyButtonLabel => 'TILLÄMPA'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'Ta bort'; @@ -211,6 +214,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get chipActionUnpin => 'Släpp från fästet'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Byt namn'; @@ -220,6 +226,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get chipActionShowCountryStates => 'Visa delstater'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Skapa album'; @@ -487,6 +496,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get albumTierPinned => 'Fastnålad'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Vanliga'; @@ -835,6 +847,27 @@ class AppLocalizationsSv extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamiskt album existerar redan'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Objekt i valv är endast tillgängliga i denna app och inga andra.\n\nOm du avinstallerar den här appen eller rensar appens data kommer du att förlora alla dessa objekt.'; @@ -1254,7 +1287,7 @@ class AppLocalizationsSv extends AppLocalizations { String get collectionGroupDay => 'Efter dag'; @override - String get collectionGroupNone => 'Gruppera inte'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Okänd'; @@ -1521,9 +1554,6 @@ class AppLocalizationsSv extends AppLocalizations { @override String get albumGroupVolume => 'Efter lagringsmedia'; - @override - String get albumGroupNone => 'Gruppera inte'; - @override String get albumMimeTypeMixed => 'Blandat'; @@ -2338,19 +2368,4 @@ class AppLocalizationsSv extends AppLocalizations { @override String get sourceViewerPageTitle => 'Källa'; - - @override - String get filePickerShowHiddenFiles => 'Visa dolda filer'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Visa inte dolda filer'; - - @override - String get filePickerOpenFrom => 'Öppna från'; - - @override - String get filePickerNoItems => 'Inga objekt'; - - @override - String get filePickerUseThisFolder => 'Använd denna katalog'; } diff --git a/lib/l10ngen/app_localizations_ta.dart b/lib/l10ngen/app_localizations_ta.dart index a493100d4..84e1be168 100644 --- a/lib/l10ngen/app_localizations_ta.dart +++ b/lib/l10ngen/app_localizations_ta.dart @@ -98,6 +98,9 @@ class AppLocalizationsTa extends AppLocalizations { @override String get applyButtonLabel => 'இடு'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'நீக்கு'; @@ -218,6 +221,9 @@ class AppLocalizationsTa extends AppLocalizations { @override String get chipActionUnpin => 'மேலே இருந்து அவிழ்த்து விடு'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'மறுபெயரிடு'; @@ -227,6 +233,9 @@ class AppLocalizationsTa extends AppLocalizations { @override String get chipActionShowCountryStates => 'நிலைகளைக் காட்டு'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'ஆல்பத்தை உருவாக்கு'; @@ -494,6 +503,9 @@ class AppLocalizationsTa extends AppLocalizations { @override String get albumTierPinned => 'பின்செய்த்து'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'பொது'; @@ -842,6 +854,27 @@ class AppLocalizationsTa extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'மாறும் தொகுப்பு ஏற்கனவே உள்ளது'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'பெட்டகங்களில் உள்ள உருப்படிகள் இந்தப் பயன்பாட்டிற்கு மட்டுமே கிடைக்கின்றன, மற்றவைகளுக்கு இல்லை.\n\n இந்தப் பயன்பாட்டை நிறுவல் நீக்கினால் அல்லது இந்தப் பயன்பாட்டு தரவை அழித்தால், இந்த உருப்படிகள் அனைத்தையும் இழப்பீர்கள்.'; @@ -1261,7 +1294,7 @@ class AppLocalizationsTa extends AppLocalizations { String get collectionGroupDay => 'நாளால்'; @override - String get collectionGroupNone => 'குழு வேண்டாம்'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'தெரியாத'; @@ -1541,9 +1574,6 @@ class AppLocalizationsTa extends AppLocalizations { @override String get albumGroupVolume => 'சேமிப்பக அளவுமூலம்'; - @override - String get albumGroupNone => 'குழு வேண்டாம்'; - @override String get albumMimeTypeMixed => 'கலப்பு'; @@ -2358,19 +2388,4 @@ class AppLocalizationsTa extends AppLocalizations { @override String get sourceViewerPageTitle => 'மூலம்'; - - @override - String get filePickerShowHiddenFiles => 'மறைக்கப்பட்ட கோப்புகளைக் காட்டு'; - - @override - String get filePickerDoNotShowHiddenFiles => 'மறைக்கப்பட்ட கோப்புகளைக் காட்ட வேண்டாம்'; - - @override - String get filePickerOpenFrom => 'இருந்து திறந்திருக்கும்'; - - @override - String get filePickerNoItems => 'உருப்படிகள் இல்லை'; - - @override - String get filePickerUseThisFolder => 'இந்தக் கோப்புறையைப் பயன்படுத்து'; } diff --git a/lib/l10ngen/app_localizations_th.dart b/lib/l10ngen/app_localizations_th.dart index 8a3eff00c..359ae18bd 100644 --- a/lib/l10ngen/app_localizations_th.dart +++ b/lib/l10ngen/app_localizations_th.dart @@ -98,6 +98,9 @@ class AppLocalizationsTh extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ลบ'; @@ -218,6 +221,9 @@ class AppLocalizationsTh extends AppLocalizations { @override String get chipActionUnpin => 'ถอนหมุดออก'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'เปลี่ยนชื่อใหม่'; @@ -227,6 +233,9 @@ class AppLocalizationsTh extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'สร้างอัลบั้ม'; @@ -494,6 +503,9 @@ class AppLocalizationsTh extends AppLocalizations { @override String get albumTierPinned => 'ปักหมุด'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'ทั่วไป'; @@ -836,6 +848,27 @@ class AppLocalizationsTh extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1249,7 +1282,7 @@ class AppLocalizationsTh extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1529,9 +1562,6 @@ class AppLocalizationsTh extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2346,19 +2376,4 @@ class AppLocalizationsTh extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_tr.dart b/lib/l10ngen/app_localizations_tr.dart index 5ae5badb5..2b44fbc8c 100644 --- a/lib/l10ngen/app_localizations_tr.dart +++ b/lib/l10ngen/app_localizations_tr.dart @@ -90,6 +90,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get applyButtonLabel => 'UYGULA'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'SİL'; @@ -210,6 +213,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get chipActionUnpin => 'Sabitlemeyi kaldır'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Yeniden adlandır'; @@ -219,6 +225,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get chipActionShowCountryStates => 'Eyaletleri göster'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Albüm oluştur'; @@ -486,6 +495,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get albumTierPinned => 'Sabitlenmiş'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Genel'; @@ -834,6 +846,27 @@ class AppLocalizationsTr extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dinamik albüm zaten var'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Kilitli albümlere yalnızca bu uygulama erişebilir, başka herhangi bir uygulama erişemez.\n\nBu uygulamayı kaldırır veya verilerini silerseniz kilitli albümlerdeki bütün ögeleri kaybedersiniz.'; @@ -1253,7 +1286,7 @@ class AppLocalizationsTr extends AppLocalizations { String get collectionGroupDay => 'Güne göre'; @override - String get collectionGroupNone => 'Gruplama'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Bilinmeyen'; @@ -1533,9 +1566,6 @@ class AppLocalizationsTr extends AppLocalizations { @override String get albumGroupVolume => 'Depolama hacmine göre'; - @override - String get albumGroupNone => 'Gruplama'; - @override String get albumMimeTypeMixed => 'Karışık'; @@ -2350,19 +2380,4 @@ class AppLocalizationsTr extends AppLocalizations { @override String get sourceViewerPageTitle => 'Kaynak'; - - @override - String get filePickerShowHiddenFiles => 'Gizli dosyaları göster'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Gizli dosyaları gösterme'; - - @override - String get filePickerOpenFrom => 'Şuradan aç'; - - @override - String get filePickerNoItems => 'Öge yok'; - - @override - String get filePickerUseThisFolder => 'Bu klasörü kullan'; } diff --git a/lib/l10ngen/app_localizations_uk.dart b/lib/l10ngen/app_localizations_uk.dart index fc0a91f89..b42be86d3 100644 --- a/lib/l10ngen/app_localizations_uk.dart +++ b/lib/l10ngen/app_localizations_uk.dart @@ -97,6 +97,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get applyButtonLabel => 'ЗАСТОСУВАТИ'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'ВИДАЛИТИ'; @@ -217,6 +220,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get chipActionUnpin => 'Відкріпити'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Перейменувати'; @@ -226,6 +232,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get chipActionShowCountryStates => 'Показати штати'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Створити альбом'; @@ -493,6 +502,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get albumTierPinned => 'Закріплені'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Стандартні'; @@ -837,6 +849,27 @@ class AppLocalizationsUk extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Динамічний альбом уже існує'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Елементи у сховищах доступні лише для цього додатка і ні для кого іншого.\n\nЯкщо ви видалите цей додаток або очистите дані додатку, ви втратите всі ці елементи.'; @@ -1252,7 +1285,7 @@ class AppLocalizationsUk extends AppLocalizations { String get collectionGroupDay => 'По дню'; @override - String get collectionGroupNone => 'Не групувати'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Невідомо'; @@ -1512,9 +1545,6 @@ class AppLocalizationsUk extends AppLocalizations { @override String get albumGroupVolume => 'За накопичувачем'; - @override - String get albumGroupNone => 'Не групувати'; - @override String get albumMimeTypeMixed => 'Змішані'; @@ -2327,19 +2357,4 @@ class AppLocalizationsUk extends AppLocalizations { @override String get sourceViewerPageTitle => 'Джерело'; - - @override - String get filePickerShowHiddenFiles => 'Показати приховані файли'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Не показувати приховані файли'; - - @override - String get filePickerOpenFrom => 'Відкрити з'; - - @override - String get filePickerNoItems => 'Немає елементів'; - - @override - String get filePickerUseThisFolder => 'Використовувати цю теку'; } diff --git a/lib/l10ngen/app_localizations_ur.dart b/lib/l10ngen/app_localizations_ur.dart index 0b53f9d34..de2c8379d 100644 --- a/lib/l10ngen/app_localizations_ur.dart +++ b/lib/l10ngen/app_localizations_ur.dart @@ -98,6 +98,9 @@ class AppLocalizationsUr extends AppLocalizations { @override String get applyButtonLabel => 'APPLY'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'DELETE'; @@ -218,6 +221,9 @@ class AppLocalizationsUr extends AppLocalizations { @override String get chipActionUnpin => 'Unpin from top'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Rename'; @@ -227,6 +233,9 @@ class AppLocalizationsUr extends AppLocalizations { @override String get chipActionShowCountryStates => 'Show states'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Create album'; @@ -494,6 +503,9 @@ class AppLocalizationsUr extends AppLocalizations { @override String get albumTierPinned => 'Pinned'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Common'; @@ -842,6 +854,27 @@ class AppLocalizationsUr extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Dynamic album already exists'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.'; @@ -1087,7 +1120,7 @@ class AppLocalizationsUr extends AppLocalizations { String get viewDialogSortSectionTitle => 'Sort'; @override - String get viewDialogGroupSectionTitle => 'Group'; + String get viewDialogGroupSectionTitle => 'Sections'; @override String get viewDialogLayoutSectionTitle => 'Layout'; @@ -1261,7 +1294,7 @@ class AppLocalizationsUr extends AppLocalizations { String get collectionGroupDay => 'By day'; @override - String get collectionGroupNone => 'Do not group'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Unknown'; @@ -1541,9 +1574,6 @@ class AppLocalizationsUr extends AppLocalizations { @override String get albumGroupVolume => 'By storage volume'; - @override - String get albumGroupNone => 'Do not group'; - @override String get albumMimeTypeMixed => 'Mixed'; @@ -2358,19 +2388,4 @@ class AppLocalizationsUr extends AppLocalizations { @override String get sourceViewerPageTitle => 'Source'; - - @override - String get filePickerShowHiddenFiles => 'Show hidden files'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Don’t show hidden files'; - - @override - String get filePickerOpenFrom => 'Open from'; - - @override - String get filePickerNoItems => 'No items'; - - @override - String get filePickerUseThisFolder => 'Use this folder'; } diff --git a/lib/l10ngen/app_localizations_vi.dart b/lib/l10ngen/app_localizations_vi.dart index c16aa1c65..ae5c3e116 100644 --- a/lib/l10ngen/app_localizations_vi.dart +++ b/lib/l10ngen/app_localizations_vi.dart @@ -87,6 +87,9 @@ class AppLocalizationsVi extends AppLocalizations { @override String get applyButtonLabel => 'ÁP DỤNG'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => 'XÓA BỎ'; @@ -207,6 +210,9 @@ class AppLocalizationsVi extends AppLocalizations { @override String get chipActionUnpin => 'Bỏ ghim khỏi đầu'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => 'Đổi tên'; @@ -216,6 +222,9 @@ class AppLocalizationsVi extends AppLocalizations { @override String get chipActionShowCountryStates => 'Hiển thị tỉnh'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => 'Tạo bộ sưu tập'; @@ -483,6 +492,9 @@ class AppLocalizationsVi extends AppLocalizations { @override String get albumTierPinned => 'Đã ghim'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => 'Phổ biến'; @@ -825,6 +837,27 @@ class AppLocalizationsVi extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => 'Album động đã tồn tại'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => 'Các mục trong kho bí mật chỉ có sẵn cho ứng dụng này và không có sẵn cho ứng dụng khác.\n\nNếu bạn gỡ cài đặt ứng dụng này hoặc xóa dữ liệu ứng dụng này, bạn sẽ mất tất cả các mục này.'; @@ -1238,7 +1271,7 @@ class AppLocalizationsVi extends AppLocalizations { String get collectionGroupDay => 'Theo ngày'; @override - String get collectionGroupNone => 'Không kết hợp'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => 'Không biết'; @@ -1488,9 +1521,6 @@ class AppLocalizationsVi extends AppLocalizations { @override String get albumGroupVolume => 'Theo dung lượng lưu trữ'; - @override - String get albumGroupNone => 'Không nhóm các mục'; - @override String get albumMimeTypeMixed => 'Trộn'; @@ -2302,19 +2332,4 @@ class AppLocalizationsVi extends AppLocalizations { @override String get sourceViewerPageTitle => 'Nguồn'; - - @override - String get filePickerShowHiddenFiles => 'Hiển thị tệp ẩn'; - - @override - String get filePickerDoNotShowHiddenFiles => 'Đừng hiển thị tệp ẩn'; - - @override - String get filePickerOpenFrom => 'Mở từ'; - - @override - String get filePickerNoItems => 'Không có mục nào'; - - @override - String get filePickerUseThisFolder => 'Dùng thư mục này'; } diff --git a/lib/l10ngen/app_localizations_zh.dart b/lib/l10ngen/app_localizations_zh.dart index ab0191725..2d8ba0195 100644 --- a/lib/l10ngen/app_localizations_zh.dart +++ b/lib/l10ngen/app_localizations_zh.dart @@ -90,6 +90,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get applyButtonLabel => '应用'; + @override + String get createButtonLabel => 'CREATE'; + @override String get deleteButtonLabel => '删除'; @@ -210,6 +213,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get chipActionUnpin => '取消置顶'; + @override + String get chipActionGroup => 'Group'; + @override String get chipActionRename => '重命名'; @@ -219,6 +225,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get chipActionShowCountryStates => '显示区域'; + @override + String get chipActionCreateGroup => 'Create group'; + @override String get chipActionCreateAlbum => '创建相册'; @@ -486,6 +495,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get albumTierPinned => '钉选'; + @override + String get albumTierGroups => 'Groups'; + @override String get albumTierSpecial => '普通'; @@ -833,6 +845,27 @@ class AppLocalizationsZh extends AppLocalizations { @override String get dynamicAlbumAlreadyExists => '动态专辑已存在'; + @override + String get newGroupDialogTitle => 'New Group'; + + @override + String get newGroupDialogNameLabel => 'Group name'; + + @override + String get groupAlreadyExists => 'Group already exists'; + + @override + String get groupEmpty => 'No groups'; + + @override + String get ungrouped => 'Ungrouped'; + + @override + String get groupPickerTitle => 'Pick Group'; + + @override + String get groupPickerUseThisGroupButton => 'Use this group'; + @override String get newVaultWarningDialogMessage => '保险库中的项目仅供此应用使用,其他应用不可用。\n\n如果您卸载此应用或清除此应用数据,您将丢失所有这些项目。'; @@ -1252,7 +1285,7 @@ class AppLocalizationsZh extends AppLocalizations { String get collectionGroupDay => '按天'; @override - String get collectionGroupNone => '不分组'; + String get sectionNone => 'No sections'; @override String get sectionUnknown => '未知'; @@ -1522,9 +1555,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get albumGroupVolume => '按存储卷'; - @override - String get albumGroupNone => '不分组'; - @override String get albumMimeTypeMixed => '混合'; @@ -2338,21 +2368,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get sourceViewerPageTitle => '源码'; - - @override - String get filePickerShowHiddenFiles => '显示隐藏文件'; - - @override - String get filePickerDoNotShowHiddenFiles => '不显示隐藏文件'; - - @override - String get filePickerOpenFrom => '打开自'; - - @override - String get filePickerNoItems => '无项目'; - - @override - String get filePickerUseThisFolder => '使用此文件夹'; } /// The translations for Chinese, using the Han script (`zh_Hant`). @@ -3587,9 +3602,6 @@ class AppLocalizationsZhHant extends AppLocalizationsZh { @override String get collectionGroupDay => '依照日期'; - @override - String get collectionGroupNone => '不分群組'; - @override String get sectionUnknown => '未知'; @@ -3825,9 +3837,6 @@ class AppLocalizationsZhHant extends AppLocalizationsZh { @override String get albumGroupVolume => '依儲存容量'; - @override - String get albumGroupNone => '不分群組'; - @override String get albumMimeTypeMixed => '混合的'; @@ -4636,19 +4645,4 @@ class AppLocalizationsZhHant extends AppLocalizationsZh { @override String get sourceViewerPageTitle => '原始碼'; - - @override - String get filePickerShowHiddenFiles => '顯示隱藏檔案'; - - @override - String get filePickerDoNotShowHiddenFiles => '不顯示隱藏檔案'; - - @override - String get filePickerOpenFrom => '開啟自'; - - @override - String get filePickerNoItems => '沒有項目'; - - @override - String get filePickerUseThisFolder => '使用此資料夾'; } diff --git a/lib/model/covers.dart b/lib/model/covers.dart index 835f359d9..e7acf2d2d 100644 --- a/lib/model/covers.dart +++ b/lib/model/covers.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; @@ -94,8 +95,11 @@ class Covers { bool notify = true, }) async { // erase contextual properties from filters before saving them - if (filter is StoredAlbumFilter) { - filter = StoredAlbumFilter(filter.album, null); + switch (filter) { + case StoredAlbumFilter _: + filter = StoredAlbumFilter(filter.album, null); + case AlbumGroupFilter _: + filter = AlbumGroupFilter.empty(filter.uri); } final oldRows = _rows.where((row) => row.filter == filter).toSet(); diff --git a/lib/model/db/db_sqflite.dart b/lib/model/db/db_sqflite.dart index 7907f588f..ee6ac6e38 100644 --- a/lib/model/db/db_sqflite.dart +++ b/lib/model/db/db_sqflite.dart @@ -214,7 +214,7 @@ class SqfliteLocalMediaDb implements LocalMediaDb { if (duplicates.isNotEmpty) { debugPrint('Found duplicates=$duplicates'); } - // return most recent duplicate for each duplicated content ID + // returns most recent duplicate for each duplicated content ID return duplicates; } @@ -502,9 +502,12 @@ class SqfliteLocalMediaDb implements LocalMediaDb { final result = {}; final cursor = await _db.queryCursor(coverTable, bufferSize: _queryCursorBufferSize); while (await cursor.moveNext()) { - final row = CoverRow.fromMap(cursor.current); + final rowMap = cursor.current; + final row = CoverRow.fromMap(rowMap); if (row != null) { result.add(row); + } else { + debugPrint('failed to deserialize cover from row=$rowMap'); } } return result; @@ -572,9 +575,12 @@ class SqfliteLocalMediaDb implements LocalMediaDb { final result = {}; final cursor = await _db.queryCursor(dynamicAlbumTable, bufferSize: _queryCursorBufferSize); while (await cursor.moveNext()) { - final row = DynamicAlbumRow.fromMap(cursor.current); + final rowMap = cursor.current; + final row = DynamicAlbumRow.fromMap(rowMap); if (row != null) { result.add(row); + } else { + debugPrint('failed to deserialize dynamic album from row=$rowMap'); } } return result; @@ -620,9 +626,12 @@ class SqfliteLocalMediaDb implements LocalMediaDb { final result = {}; final cursor = await _db.queryCursor(videoPlaybackTable, bufferSize: _queryCursorBufferSize); while (await cursor.moveNext()) { - final row = VideoPlaybackRow.fromMap(cursor.current); + final rowMap = cursor.current; + final row = VideoPlaybackRow.fromMap(rowMap); if (row != null) { result.add(row); + } else { + debugPrint('failed to deserialize video playback from row=$rowMap'); } } return result; diff --git a/lib/model/dynamic_albums.dart b/lib/model/dynamic_albums.dart index bd6e91498..333308758 100644 --- a/lib/model/dynamic_albums.dart +++ b/lib/model/dynamic_albums.dart @@ -52,7 +52,7 @@ class DynamicAlbums with ChangeNotifier { DynamicAlbumFilter? get(String name) => _rows.firstWhereOrNull((row) => row.name == name); - bool contains(String name) => get(name) != null; + bool contains(String? name) => name != null && get(name) != null; // import/export diff --git a/lib/model/filters/covered/album_base.dart b/lib/model/filters/covered/album_base.dart deleted file mode 100644 index fdbe484fe..000000000 --- a/lib/model/filters/covered/album_base.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:aves/model/filters/filters.dart'; -import 'package:aves_model/aves_model.dart'; - -abstract class AlbumBaseFilter extends CollectionFilter { - const AlbumBaseFilter({required super.reversed}); - - bool match(String query); - - StorageVolume? get storageVolume; - - bool get canRename; - - bool get isVault; -} diff --git a/lib/model/filters/covered/album_group.dart b/lib/model/filters/covered/album_group.dart new file mode 100644 index 000000000..2d70fdfed --- /dev/null +++ b/lib/model/filters/covered/album_group.dart @@ -0,0 +1,49 @@ +import 'package:aves/model/filters/covered/group_base.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/set_or.dart'; + +mixin AlbumBaseFilter on CollectionFilter {} + +// placeholder to pick a group, distinguishing this root filter from cancelling +class _RootAlbumGroupFilter extends DummyCollectionFilter with AlbumBaseFilter { + _RootAlbumGroupFilter._private({required super.reversed}); +} + +class AlbumGroupFilter extends GroupBaseFilter with AlbumBaseFilter { + static const type = 'album_group'; + + static AlbumBaseFilter root = _RootAlbumGroupFilter._private(reversed: false); + + AlbumGroupFilter(super.uri, super.filter, {super.reversed = false}); + + factory AlbumGroupFilter.empty(Uri uri) => AlbumGroupFilter(uri, SetOrFilter(const {})); + + static AlbumGroupFilter? fromMap(Map json) { + final filter = CollectionFilter.fromJson(json['filter']); + if (filter == null || filter is! SetOrFilter) return null; + + final uriString = json['uri']; + final uri = uriString is String ? Uri.tryParse(uriString) : null; + if (uri == null) return null; + + return AlbumGroupFilter( + uri, + filter, + reversed: json['reversed'] ?? false, + ); + } + + @override + Map toMap() => { + 'type': type, + 'uri': uri.toString(), + 'filter': filter.toJson(), + 'reversed': reversed, + }; + + @override + String get category => type; + + @override + String get key => '$type-$reversed-$uri'; +} diff --git a/lib/model/filters/covered/dynamic_album.dart b/lib/model/filters/covered/dynamic_album.dart index 004798c95..c0f10e65a 100644 --- a/lib/model/filters/covered/dynamic_album.dart +++ b/lib/model/filters/covered/dynamic_album.dart @@ -1,11 +1,10 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/covered.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves_model/aves_model.dart'; import 'package:flutter/widgets.dart'; -class DynamicAlbumFilter extends AlbumBaseFilter with CoveredFilter { +class DynamicAlbumFilter extends CollectionFilter with CoveredFilter, AlbumBaseFilter { static const type = 'dynamic_album'; final String name; @@ -54,16 +53,4 @@ class DynamicAlbumFilter extends AlbumBaseFilter with CoveredFilter { @override String get key => '$type-$reversed-$name'; - - @override - bool match(String query) => name.toUpperCase().contains(query); - - @override - StorageVolume? get storageVolume => null; - - @override - bool get canRename => true; - - @override - bool get isVault => false; } diff --git a/lib/model/filters/covered/group_base.dart b/lib/model/filters/covered/group_base.dart new file mode 100644 index 000000000..a8b32d418 --- /dev/null +++ b/lib/model/filters/covered/group_base.dart @@ -0,0 +1,35 @@ +import 'package:aves/model/filters/covered/covered.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/grouping/common.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:flutter/widgets.dart'; + +abstract class GroupBaseFilter extends CollectionFilter with CoveredFilter { + final Uri uri; + final SetOrFilter filter; + late final String _name; + + // do not include contextual `filter` to `props` + // stringify URI because its runtime type is undetermined and different types falsify equality checks + @override + List get props => [uri.toString(), reversed]; + + GroupBaseFilter(this.uri, this.filter, {super.reversed = false}) { + _name = FilterGrouping.getGroupName(uri) ?? ''; + } + + @override + EntryFilter get positiveTest => filter.test; + + @override + bool get exclusiveProp => false; + + @override + String get universalLabel => _name; + + @override + Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) { + return allowGenericIcon ? Icon(AIcons.group, size: size) : null; + } +} diff --git a/lib/model/filters/covered/stored_album.dart b/lib/model/filters/covered/stored_album.dart index a6940a453..97b4f4bf9 100644 --- a/lib/model/filters/covered/stored_album.dart +++ b/lib/model/filters/covered/stored_album.dart @@ -1,5 +1,5 @@ import 'package:aves/model/covers.dart'; -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/covered.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/vaults/vaults.dart'; @@ -13,13 +13,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -class StoredAlbumFilter extends AlbumBaseFilter with CoveredFilter { +class StoredAlbumFilter extends CollectionFilter with CoveredFilter, AlbumBaseFilter { static const type = 'album'; final String album; final String? displayName; late final EntryFilter _test; + // do not include contextual `displayName` to `props` @override List get props => [album, reversed]; @@ -53,7 +54,7 @@ class StoredAlbumFilter extends AlbumBaseFilter with CoveredFilter { String get universalLabel => displayName ?? pContext.split(album).last; @override - String getTooltip(BuildContext context) => album; + String getTooltip(BuildContext context) => isVault ? super.getTooltip(context) : album; @override Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) { @@ -103,13 +104,8 @@ class StoredAlbumFilter extends AlbumBaseFilter with CoveredFilter { @override String get key => '$type-$reversed-$album'; - @override - bool match(String query) => (displayName ?? album).toUpperCase().contains(query); - - @override StorageVolume? get storageVolume => androidFileUtils.getStorageVolume(album); - @override bool get canRename { if (isVault) return true; @@ -118,6 +114,5 @@ class StoredAlbumFilter extends AlbumBaseFilter with CoveredFilter { return dir != null && dir.relativeDir.isNotEmpty; } - @override bool get isVault => vaults.isVault(album); } diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 3638dbdfe..b7ffa40e4 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -3,9 +3,10 @@ import 'dart:convert'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/aspect_ratio.dart'; import 'package:aves/model/filters/coordinate.dart'; -import 'package:aves/model/filters/covered/stored_album.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/date.dart'; import 'package:aves/model/filters/favourite.dart'; @@ -57,6 +58,8 @@ abstract class CollectionFilter extends Equatable implements Comparable jsonMap) { final type = jsonMap['type']; switch (type) { + case AlbumGroupFilter.type: + return AlbumGroupFilter.fromMap(jsonMap); case AspectRatioFilter.type: return AspectRatioFilter.fromMap(jsonMap); case CoordinateFilter.type: @@ -96,6 +99,7 @@ abstract class CollectionFilter extends Equatable implements Comparable getLabel(context); + bool match(BuildContext context, String query) => getLabel(context).toUpperCase().contains(query); + Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => null; Future color(BuildContext context) { @@ -175,3 +181,28 @@ class FilterGridItem with EquatableMixin { } typedef EntryFilter = bool Function(AvesEntry); + +abstract class DummyCollectionFilter extends CollectionFilter { + const DummyCollectionFilter({required super.reversed}); + + @override + String get category => throw UnimplementedError(); + + @override + bool get exclusiveProp => throw UnimplementedError(); + + @override + String get key => throw UnimplementedError(); + + @override + EntryFilter get positiveTest => throw UnimplementedError(); + + @override + List get props => throw UnimplementedError(); + + @override + Map toMap() => throw UnimplementedError(); + + @override + String get universalLabel => throw UnimplementedError(); +} diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index 36176697d..120b6fab8 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -76,6 +76,11 @@ class MimeFilter extends CollectionFilter { }; } + @override + String getTooltip(BuildContext context) { + return '${getLabel(context)} ($mime)'; + } + @override Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size); diff --git a/lib/model/filters/set_and.dart b/lib/model/filters/set_and.dart index 225bb2689..6254f45f2 100644 --- a/lib/model/filters/set_and.dart +++ b/lib/model/filters/set_and.dart @@ -16,7 +16,7 @@ class SetAndFilter extends CollectionFilter { @override List get props => [_filters, reversed]; - CollectionFilter get _first => _filters.first; + CollectionFilter? get _first => _filters.firstOrNull; Set get innerFilters => _filters.toSet(); @@ -66,11 +66,11 @@ class SetAndFilter extends CollectionFilter { @override Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) { - return _genericIcon != null ? Icon(_genericIcon, size: size) : _first.iconBuilder(context, size, allowGenericIcon: allowGenericIcon); + return _genericIcon != null ? Icon(_genericIcon, size: size) : _first?.iconBuilder(context, size, allowGenericIcon: allowGenericIcon); } @override - String get category => _first.category; + String get category => _first?.category ?? type; @override String get key => '$type-$reversed-${_filters.map((v) => v.key)}'; diff --git a/lib/model/filters/set_or.dart b/lib/model/filters/set_or.dart index 98f392ff3..3278799c3 100644 --- a/lib/model/filters/set_or.dart +++ b/lib/model/filters/set_or.dart @@ -1,6 +1,6 @@ +import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/theme/icons.dart'; import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; @@ -16,13 +16,15 @@ class SetOrFilter extends CollectionFilter { @override List get props => [_filters, reversed]; - CollectionFilter get _first => _filters.first; + CollectionFilter? get _first => _filters.firstOrNull; + + Set get innerFilters => _filters.toSet(); SetOrFilter(Set filters, {super.reversed = false}) { _filters = filters.toList().sorted(); _test = (entry) => _filters.any((v) => v.test(entry)); switch (_first) { - case StoredAlbumFilter(): + case StoredAlbumFilter _: _genericIcon = AIcons.album; case LocationFilter(level: LocationLevel.country): _genericIcon = AIcons.country; @@ -64,11 +66,11 @@ class SetOrFilter extends CollectionFilter { @override Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) { - return _genericIcon != null ? Icon(_genericIcon, size: size) : _first.iconBuilder(context, size, allowGenericIcon: allowGenericIcon); + return _genericIcon != null ? Icon(_genericIcon, size: size) : _first?.iconBuilder(context, size, allowGenericIcon: allowGenericIcon); } @override - String get category => _first.category; + String get category => _first?.category ?? type; @override String get key => '$type-$reversed-${_filters.map((v) => v.key)}'; diff --git a/lib/model/grouping/common.dart b/lib/model/grouping/common.dart new file mode 100644 index 000000000..b43d28667 --- /dev/null +++ b/lib/model/grouping/common.dart @@ -0,0 +1,250 @@ +import 'dart:convert'; + +import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/covered/group_base.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/grouping/convert.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/utils/collection_utils.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; + +final FilterGrouping albumGrouping = FilterGrouping._private(FilterGrouping.hostAlbums, AlbumGroupFilter.new); + +// album group URI: "aves://albums/group?path=/group12/subgroup34" +// stored album URI: "aves://albums/stored?path=/volume/dir/path12" +// dynamic album URI: "aves://albums/dynamic?name=dynalbum12" +class FilterGrouping with ChangeNotifier { + static const scheme = 'aves'; + static const hostAlbums = 'albums'; + + static const _groupPath = '/group'; + static const _groupPathParamKey = 'path'; + + final String _host; + final T Function(Uri uri, SetOrFilter filter) _createGroupFilter; + final Map> _groups = {}; + + FilterGrouping._private(this._host, this._createGroupFilter) { + if (kFlutterMemoryAllocationsEnabled) ChangeNotifier.maybeDispatchObjectCreation(this); + } + + void clear() => _groups.clear(); + + // TODO TLAD [nested] invalidate summary for derived filters (parent groups, dynamic albums) + void addToGroup(Set childrenUris, Uri? destinationGroup) { + _removeFromGroups(childrenUris); + if (destinationGroup != null) { + _ensureGroupFromRoot(destinationGroup); + final children = _groups[destinationGroup] ?? {}; + children.addAll(childrenUris); + _groups[destinationGroup] = children; + } + _reparentGroupPaths(childrenUris, destinationGroup); + _cleanEmptyGroups(); + notifyListeners(); + } + + void rename(Uri oldUri, Uri newUri) { + final childrenUris = _groups[oldUri]; + if (childrenUris != null) { + // local copy to prevent concurrent modification + addToGroup(Set.of(childrenUris), newUri); + } + } + + bool get isNotEmpty => _groups.isNotEmpty; + + bool exists(Uri? groupUri) => _groups.containsKey(groupUri); + + Set getGroups() => _groups.keys.toSet(); + + // returns number of filters within provided group, following subgroups without counting them + // providing the null root will yield 0, rather than the total number of filters in the collection (which is out of scope) + int countLeaves(Uri? groupUri) { + int count = 0; + if (groupUri != null) { + final childrenUri = _groups[groupUri]; + if (childrenUri != null) { + childrenUri.map(uriToFilter).nonNulls.forEach((filter) { + if (filter is GroupBaseFilter) { + count += countLeaves(filter.uri); + } else { + count++; + } + }); + } + } + return count; + } + + // returns filters directly within the provided group, including subgroups as filters + // providing the null root will yield its direct group filters + Set getDirectChildren(Uri? currentGroupUri) { + if (currentGroupUri == null) { + return _groups.entries.where((kv) => getParentGroup(kv.key) == currentGroupUri).map((kv) { + final groupUri = kv.key; + final childrenUri = kv.value; + final childrenFilters = childrenUri.map(uriToFilter).nonNulls.toSet(); + return _createGroupFilter(groupUri, SetOrFilter(childrenFilters)); + }).toSet(); + } + + final childrenUri = _groups.entries.firstWhereOrNull((kv) => kv.key == currentGroupUri)?.value; + if (childrenUri != null) { + return childrenUri.map(uriToFilter).nonNulls.toSet(); + } + + return {}; + } + + Uri buildGroupUri(Uri? parentGroupUri, String name) { + return _buildGroupUri(_host, parentGroupUri, name); + } + + CollectionFilter? uriToFilter(Uri? uri) { + if (uri == null || uri.host != _host) return null; + + if (FilterGrouping.isGroupUri(uri)) { + return _createGroupFilter(uri, SetOrFilter(getDirectChildren(uri))); + } + + return GroupingConversion.uriToFilter(uri); + } + + void _removeFromGroups(Set uris) { + _groups.forEach((_, childrenUris) { + childrenUris.removeAll(uris); + }); + } + + void _cleanEmptyGroups() { + final emptyGroupUris = _groups.entries.where((kv) => kv.value.isEmpty).map((v) => v.key).toSet(); + if (emptyGroupUris.isNotEmpty) { + _removeFromGroups(emptyGroupUris); + _groups.removeWhere((groupUri, _) => emptyGroupUris.contains(groupUri)); + _cleanEmptyGroups(); + } + } + + void _reparentGroupPaths(Set childrenUris, Uri? parentGroupUri) { + final groupUris = childrenUris.where(FilterGrouping.isGroupUri).toSet(); + groupUris.forEach((oldGroupUri) { + final name = FilterGrouping.getGroupName(oldGroupUri); + if (name != null) { + final groupChildrenUris = _groups.remove(oldGroupUri); + if (groupChildrenUris != null) { + // create child group with updated URI + final newGroupUri = buildGroupUri(parentGroupUri, name); + _groups[newGroupUri] = groupChildrenUris; + + // update child group URI in parent group itself + if (parentGroupUri != null) { + final children = _groups[parentGroupUri]; + if (children != null && children.remove(oldGroupUri)) { + children.add(newGroupUri); + } + } + + _reparentGroupPaths(groupChildrenUris, newGroupUri); + } + } + }); + } + + void _ensureGroupFromRoot(Uri groupUri) { + final parentGroupUri = FilterGrouping.getParentGroup(groupUri); + if (parentGroupUri != null) { + final children = _groups[parentGroupUri] ?? {}; + children.addAll({groupUri}); + _groups[parentGroupUri] = children; + _ensureGroupFromRoot(parentGroupUri); + } + } + + // group uri / filter conversion + + static String? getGroupPath(Uri? uri) => uri?.queryParameters[_groupPathParamKey]; + + static String? getGroupName(Uri? uri) { + final path = getGroupPath(uri); + return path != null ? pContext.split(path).lastOrNull : null; + } + + static bool isGroupUri(Uri uri) => uri.path == FilterGrouping._groupPath; + + // parent group URI is `null` for root + static Uri _buildGroupUri(String host, Uri? parentGroupUri, String name) { + if (parentGroupUri != null) { + final path = getGroupPath(parentGroupUri); + if (path != null) { + return parentGroupUri.replace( + queryParameters: { + _groupPathParamKey: pContext.join(path, name), + }, + ); + } + } + return Uri( + scheme: scheme, + host: host, + path: _groupPath, + queryParameters: { + _groupPathParamKey: name, + }, + ); + } + + // returns `null` for root + Uri? getFilterParent(CollectionFilter filter) { + final uri = GroupingConversion.filterToUri(filter); + if (uri == null) return null; + + if (isGroupUri(uri)) { + return getParentGroup(uri); + } else { + return _groups.entries.firstWhereOrNull((kv) { + return kv.value.contains(uri); + })?.key; + } + } + + // returns `null` for root + static Uri? getParentGroup(Uri? groupUri) { + if (groupUri == null) return null; + + final path = getGroupPath(groupUri); + if (path != null) { + final segments = pContext.split(path); + final newLength = segments.length - 1; + if (newLength > 0) { + return groupUri.replace( + queryParameters: { + _groupPathParamKey: pContext.joinAll(segments.take(newLength)), + }, + ); + } + } + return null; + } + + // serialization + + static String toJson(Map> groups) => jsonEncode(groups.map((parentUri, childrenUris) { + return MapEntry(parentUri.toString(), childrenUris.map((v) => v.toString())); + })); + + static Map>? fromJson(String? jsonString) { + if (jsonString == null || jsonString.isEmpty) return null; + + final jsonMap = jsonDecode(jsonString); + if (jsonMap is! Map) return null; + + return jsonMap.map((parent, children) { + final Uri? parentUri = parent is String ? Uri.tryParse(parent) : null; + final Set childrenUris = children is Iterable ? children.whereType().map(Uri.tryParse).nonNulls.toSet() : {}; + return MapEntry(parentUri, childrenUris); + }).whereNotNullKey(); + } +} diff --git a/lib/model/grouping/convert.dart b/lib/model/grouping/convert.dart new file mode 100644 index 000000000..01e94d05b --- /dev/null +++ b/lib/model/grouping/convert.dart @@ -0,0 +1,67 @@ +import 'package:aves/model/dynamic_albums.dart'; +import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/covered/group_base.dart'; +import 'package:aves/model/filters/covered/stored_album.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; + +mixin GroupingConversion { + static const _storedAlbumPath = '/stored'; + static const _dynamicAlbumPath = '/dynamic'; + static const _nameParamKey = 'name'; + static const _storagePathParamKey = 'path'; + + static CollectionFilter? uriToFilter(Uri uri) { + switch (uri.path) { + case _storedAlbumPath: + final album = uri.queryParameters[_storagePathParamKey]; + if (album != null) { + return StoredAlbumFilter(album, null); + } + case _dynamicAlbumPath: + final name = uri.queryParameters[_nameParamKey]; + if (name != null) { + return dynamicAlbums.get(name); + } + default: + throw Exception('unhandled path=${uri.path} with uri=$uri'); + } + + throw Exception('failed to convert to filter with uri=$uri'); + } + + static Uri? filterToUri(CollectionFilter filter) { + switch (filter) { + case GroupBaseFilter _: + return filter.uri; + case StoredAlbumFilter _: + return _buildStoredAlbumUri(filter.album); + case DynamicAlbumFilter _: + return _buildDynamicAlbumUri(filter.name); + default: + throw Exception('unknown type with filter=$filter'); + } + } + + static Uri _buildStoredAlbumUri(String album) { + return Uri( + scheme: FilterGrouping.scheme, + host: FilterGrouping.hostAlbums, + path: _storedAlbumPath, + queryParameters: { + _storagePathParamKey: album, + }, + ); + } + + static Uri _buildDynamicAlbumUri(String name) { + return Uri( + scheme: FilterGrouping.scheme, + host: FilterGrouping.hostAlbums, + path: _dynamicAlbumPath, + queryParameters: { + _nameParamKey: name, + }, + ); + } +} diff --git a/lib/model/highlight.dart b/lib/model/highlight.dart index 2d372eb4a..43f0340db 100644 --- a/lib/model/highlight.dart +++ b/lib/model/highlight.dart @@ -67,5 +67,5 @@ class TrackEvent { } // `itemVisibility`: percent of the item tracked already visible in viewport -// return whether to proceed with tracking +// returns whether to proceed with tracking typedef TrackPredicate = bool Function(double itemVisibility); diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 1e341cdd4..45439dba7 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -44,7 +44,7 @@ class SettingsDefaults { ]; // collection - static const collectionSectionFactor = EntryGroupFactor.month; + static const collectionSectionFactor = EntrySectionFactor.month; static const collectionSortFactor = EntrySortFactor.date; static const collectionBrowsingQuickActions = [ EntrySetAction.searchCollection, @@ -63,7 +63,7 @@ class SettingsDefaults { static const showThumbnailVideoDuration = true; // filter grids - static const albumGroupFactor = AlbumChipGroupFactor.importance; + static const albumGroupFactor = AlbumChipSectionFactor.importance; static const chipListSortFactor = ChipSortFactor.name; // viewer @@ -114,9 +114,6 @@ class SettingsDefaults { static const accessibilityAnimations = AccessibilityAnimations.system; static const timeToTakeAction = AccessibilityTimeout.s3; - // file picker - static const filePickerShowHiddenFiles = false; - // slideshow static const slideshowRepeat = false; static const slideshowShuffle = false; diff --git a/lib/model/settings/modules/collection.dart b/lib/model/settings/modules/collection.dart index 3a90fc600..5ccd095ab 100644 --- a/lib/model/settings/modules/collection.dart +++ b/lib/model/settings/modules/collection.dart @@ -6,9 +6,9 @@ mixin CollectionSettings on SettingsAccess { set collectionBurstPatterns(List newValue) => set(SettingKeys.collectionBurstPatternsKey, newValue); - EntryGroupFactor get collectionSectionFactor => getEnumOrDefault(SettingKeys.collectionGroupFactorKey, SettingsDefaults.collectionSectionFactor, EntryGroupFactor.values); + EntrySectionFactor get collectionSectionFactor => getEnumOrDefault(SettingKeys.collectionGroupFactorKey, SettingsDefaults.collectionSectionFactor, EntrySectionFactor.values); - set collectionSectionFactor(EntryGroupFactor newValue) => set(SettingKeys.collectionGroupFactorKey, newValue.toString()); + set collectionSectionFactor(EntrySectionFactor newValue) => set(SettingKeys.collectionGroupFactorKey, newValue.toString()); EntrySortFactor get collectionSortFactor => getEnumOrDefault(SettingKeys.collectionSortFactorKey, SettingsDefaults.collectionSortFactor, EntrySortFactor.values); diff --git a/lib/model/settings/modules/filter_grids.dart b/lib/model/settings/modules/filter_grids.dart index fc5a52275..05a443b57 100644 --- a/lib/model/settings/modules/filter_grids.dart +++ b/lib/model/settings/modules/filter_grids.dart @@ -3,9 +3,9 @@ import 'package:aves/model/settings/defaults.dart'; import 'package:aves_model/aves_model.dart'; mixin FilterGridsSettings on SettingsAccess { - AlbumChipGroupFactor get albumGroupFactor => getEnumOrDefault(SettingKeys.albumGroupFactorKey, SettingsDefaults.albumGroupFactor, AlbumChipGroupFactor.values); + AlbumChipSectionFactor get albumSectionFactor => getEnumOrDefault(SettingKeys.albumGroupFactorKey, SettingsDefaults.albumGroupFactor, AlbumChipSectionFactor.values); - set albumGroupFactor(AlbumChipGroupFactor newValue) => set(SettingKeys.albumGroupFactorKey, newValue.toString()); + set albumSectionFactor(AlbumChipSectionFactor newValue) => set(SettingKeys.albumGroupFactorKey, newValue.toString()); ChipSortFactor get albumSortFactor => getEnumOrDefault(SettingKeys.albumSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values); @@ -54,4 +54,6 @@ mixin FilterGridsSettings on SettingsAccess { bool getShowTitleQuery(String routeName) => getBool(SettingKeys.showTitleQueryPrefixKey + routeName) ?? false; void setShowTitleQuery(String routeName, bool newValue) => set(SettingKeys.showTitleQueryPrefixKey + routeName, newValue); + + // TODO TLAD [nested] save/load } diff --git a/lib/model/settings/modules/navigation.dart b/lib/model/settings/modules/navigation.dart index a09684233..95400360b 100644 --- a/lib/model/settings/modules/navigation.dart +++ b/lib/model/settings/modules/navigation.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/defaults.dart'; import 'package:aves_model/aves_model.dart'; @@ -57,7 +57,7 @@ mixin NavigationSettings on SettingsAccess { set setMetadataDateBeforeFileOp(bool newValue) => set(SettingKeys.setMetadataDateBeforeFileOpKey, newValue); List get drawerTypeBookmarks => - (getStringList(SettingKeys.drawerTypeBookmarksKey))?.map((v) { + getStringList(SettingKeys.drawerTypeBookmarksKey)?.map((v) { if (v.isEmpty) return null; return CollectionFilter.fromJson(v); }).toList() ?? diff --git a/lib/model/settings/modules/screen_saver.dart b/lib/model/settings/modules/screen_saver.dart new file mode 100644 index 000000000..176bac4ea --- /dev/null +++ b/lib/model/settings/modules/screen_saver.dart @@ -0,0 +1,29 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/settings/defaults.dart'; +import 'package:aves_model/aves_model.dart'; + +mixin ScreenSaverSettings on SettingsAccess { + bool get screenSaverFillScreen => getBool(SettingKeys.screenSaverFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; + + set screenSaverFillScreen(bool newValue) => set(SettingKeys.screenSaverFillScreenKey, newValue); + + bool get screenSaverAnimatedZoomEffect => getBool(SettingKeys.screenSaverAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; + + set screenSaverAnimatedZoomEffect(bool newValue) => set(SettingKeys.screenSaverAnimatedZoomEffectKey, newValue); + + ViewerTransition get screenSaverTransition => getEnumOrDefault(SettingKeys.screenSaverTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); + + set screenSaverTransition(ViewerTransition newValue) => set(SettingKeys.screenSaverTransitionKey, newValue.toString()); + + SlideshowVideoPlayback get screenSaverVideoPlayback => getEnumOrDefault(SettingKeys.screenSaverVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); + + set screenSaverVideoPlayback(SlideshowVideoPlayback newValue) => set(SettingKeys.screenSaverVideoPlaybackKey, newValue.toString()); + + int get screenSaverInterval => getInt(SettingKeys.screenSaverIntervalKey) ?? SettingsDefaults.slideshowInterval; + + set screenSaverInterval(int newValue) => set(SettingKeys.screenSaverIntervalKey, newValue); + + Set get screenSaverCollectionFilters => (getStringList(SettingKeys.screenSaverCollectionFiltersKey) ?? []).map(CollectionFilter.fromJson).nonNulls.toSet(); + + set screenSaverCollectionFilters(Set newValue) => set(SettingKeys.screenSaverCollectionFiltersKey, newValue.map((filter) => filter.toJson()).toList()); +} diff --git a/lib/model/settings/modules/slideshow.dart b/lib/model/settings/modules/slideshow.dart new file mode 100644 index 000000000..50740e995 --- /dev/null +++ b/lib/model/settings/modules/slideshow.dart @@ -0,0 +1,32 @@ +import 'package:aves/model/settings/defaults.dart'; +import 'package:aves_model/aves_model.dart'; + +mixin SlideshowSettings on SettingsAccess { + bool get slideshowRepeat => getBool(SettingKeys.slideshowRepeatKey) ?? SettingsDefaults.slideshowRepeat; + + set slideshowRepeat(bool newValue) => set(SettingKeys.slideshowRepeatKey, newValue); + + bool get slideshowShuffle => getBool(SettingKeys.slideshowShuffleKey) ?? SettingsDefaults.slideshowShuffle; + + set slideshowShuffle(bool newValue) => set(SettingKeys.slideshowShuffleKey, newValue); + + bool get slideshowFillScreen => getBool(SettingKeys.slideshowFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; + + set slideshowFillScreen(bool newValue) => set(SettingKeys.slideshowFillScreenKey, newValue); + + bool get slideshowAnimatedZoomEffect => getBool(SettingKeys.slideshowAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; + + set slideshowAnimatedZoomEffect(bool newValue) => set(SettingKeys.slideshowAnimatedZoomEffectKey, newValue); + + ViewerTransition get slideshowTransition => getEnumOrDefault(SettingKeys.slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); + + set slideshowTransition(ViewerTransition newValue) => set(SettingKeys.slideshowTransitionKey, newValue.toString()); + + SlideshowVideoPlayback get slideshowVideoPlayback => getEnumOrDefault(SettingKeys.slideshowVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); + + set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => set(SettingKeys.slideshowVideoPlaybackKey, newValue.toString()); + + int get slideshowInterval => getInt(SettingKeys.slideshowIntervalKey) ?? SettingsDefaults.slideshowInterval; + + set slideshowInterval(int newValue) => set(SettingKeys.slideshowIntervalKey, newValue); +} diff --git a/lib/model/settings/modules/widget.dart b/lib/model/settings/modules/widget.dart new file mode 100644 index 000000000..f1b3c9104 --- /dev/null +++ b/lib/model/settings/modules/widget.dart @@ -0,0 +1,29 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/settings/defaults.dart'; +import 'package:aves_model/aves_model.dart'; + +mixin WidgetSettings on SettingsAccess { + WidgetOutline getWidgetOutline(int widgetId) => getEnumOrDefault('${SettingKeys.widgetOutlinePrefixKey}$widgetId', WidgetOutline.none, WidgetOutline.values); + + void setWidgetOutline(int widgetId, WidgetOutline newValue) => set('${SettingKeys.widgetOutlinePrefixKey}$widgetId', newValue.toString()); + + WidgetShape getWidgetShape(int widgetId) => getEnumOrDefault('${SettingKeys.widgetShapePrefixKey}$widgetId', SettingsDefaults.widgetShape, WidgetShape.values); + + void setWidgetShape(int widgetId, WidgetShape newValue) => set('${SettingKeys.widgetShapePrefixKey}$widgetId', newValue.toString()); + + Set getWidgetCollectionFilters(int widgetId) => (getStringList('${SettingKeys.widgetCollectionFiltersPrefixKey}$widgetId') ?? []).map(CollectionFilter.fromJson).nonNulls.toSet(); + + void setWidgetCollectionFilters(int widgetId, Set newValue) => set('${SettingKeys.widgetCollectionFiltersPrefixKey}$widgetId', newValue.map((filter) => filter.toJson()).toList()); + + WidgetOpenPage getWidgetOpenPage(int widgetId) => getEnumOrDefault('${SettingKeys.widgetOpenPagePrefixKey}$widgetId', SettingsDefaults.widgetOpenPage, WidgetOpenPage.values); + + void setWidgetOpenPage(int widgetId, WidgetOpenPage newValue) => set('${SettingKeys.widgetOpenPagePrefixKey}$widgetId', newValue.toString()); + + WidgetDisplayedItem getWidgetDisplayedItem(int widgetId) => getEnumOrDefault('${SettingKeys.widgetDisplayedItemPrefixKey}$widgetId', SettingsDefaults.widgetDisplayedItem, WidgetDisplayedItem.values); + + void setWidgetDisplayedItem(int widgetId, WidgetDisplayedItem newValue) => set('${SettingKeys.widgetDisplayedItemPrefixKey}$widgetId', newValue.toString()); + + String? getWidgetUri(int widgetId) => getString('${SettingKeys.widgetUriPrefixKey}$widgetId'); + + void setWidgetUri(int widgetId, String? newValue) => set('${SettingKeys.widgetUriPrefixKey}$widgetId', newValue); +} diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index f4d181fd1..434896efb 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -5,7 +5,6 @@ import 'dart:math'; import 'package:aves/app_flavor.dart'; import 'package:aves/model/device.dart'; import 'package:aves/model/filters/favourite.dart'; -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; @@ -18,8 +17,11 @@ import 'package:aves/model/settings/modules/filter_grids.dart'; import 'package:aves/model/settings/modules/info.dart'; import 'package:aves/model/settings/modules/navigation.dart'; import 'package:aves/model/settings/modules/privacy.dart'; +import 'package:aves/model/settings/modules/screen_saver.dart'; import 'package:aves/model/settings/modules/search.dart'; +import 'package:aves/model/settings/modules/slideshow.dart'; import 'package:aves/model/settings/modules/viewer.dart'; +import 'package:aves/model/settings/modules/widget.dart'; import 'package:aves/ref/bursts.dart'; import 'package:aves/services/accessibility_service.dart'; import 'package:aves/services/common/services.dart'; @@ -41,7 +43,7 @@ import 'package:latlong2/latlong.dart'; final Settings settings = Settings._private(); -class Settings with ChangeNotifier, SettingsAccess, DebugSettings, AppSettings, DisplaySettings, NavigationSettings, SearchSettings, CollectionSettings, FilterGridsSettings, PrivacySettings, ViewerSettings, VideoSettings, SubtitlesSettings, InfoSettings { +class Settings with ChangeNotifier, SettingsAccess, SearchSettings, AppSettings, CollectionSettings, DebugSettings, DisplaySettings, FilterGridsSettings, InfoSettings, NavigationSettings, PrivacySettings, ScreenSaverSettings, SlideshowSettings, SubtitlesSettings, VideoSettings, ViewerSettings, WidgetSettings { final List _subscriptions = []; final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change'); final StreamController _updateStreamController = StreamController.broadcast(); @@ -220,94 +222,6 @@ class Settings with ChangeNotifier, SettingsAccess, DebugSettings, AppSettings, set timeToTakeAction(AccessibilityTimeout newValue) => set(SettingKeys.timeToTakeActionKey, newValue.toString()); - // file picker - - bool get filePickerShowHiddenFiles => getBool(SettingKeys.filePickerShowHiddenFilesKey) ?? SettingsDefaults.filePickerShowHiddenFiles; - - set filePickerShowHiddenFiles(bool newValue) => set(SettingKeys.filePickerShowHiddenFilesKey, newValue); - - // screen saver - - bool get screenSaverFillScreen => getBool(SettingKeys.screenSaverFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; - - set screenSaverFillScreen(bool newValue) => set(SettingKeys.screenSaverFillScreenKey, newValue); - - bool get screenSaverAnimatedZoomEffect => getBool(SettingKeys.screenSaverAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; - - set screenSaverAnimatedZoomEffect(bool newValue) => set(SettingKeys.screenSaverAnimatedZoomEffectKey, newValue); - - ViewerTransition get screenSaverTransition => getEnumOrDefault(SettingKeys.screenSaverTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); - - set screenSaverTransition(ViewerTransition newValue) => set(SettingKeys.screenSaverTransitionKey, newValue.toString()); - - SlideshowVideoPlayback get screenSaverVideoPlayback => getEnumOrDefault(SettingKeys.screenSaverVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); - - set screenSaverVideoPlayback(SlideshowVideoPlayback newValue) => set(SettingKeys.screenSaverVideoPlaybackKey, newValue.toString()); - - int get screenSaverInterval => getInt(SettingKeys.screenSaverIntervalKey) ?? SettingsDefaults.slideshowInterval; - - set screenSaverInterval(int newValue) => set(SettingKeys.screenSaverIntervalKey, newValue); - - Set get screenSaverCollectionFilters => (getStringList(SettingKeys.screenSaverCollectionFiltersKey) ?? []).map(CollectionFilter.fromJson).nonNulls.toSet(); - - set screenSaverCollectionFilters(Set newValue) => set(SettingKeys.screenSaverCollectionFiltersKey, newValue.map((filter) => filter.toJson()).toList()); - - // slideshow - - bool get slideshowRepeat => getBool(SettingKeys.slideshowRepeatKey) ?? SettingsDefaults.slideshowRepeat; - - set slideshowRepeat(bool newValue) => set(SettingKeys.slideshowRepeatKey, newValue); - - bool get slideshowShuffle => getBool(SettingKeys.slideshowShuffleKey) ?? SettingsDefaults.slideshowShuffle; - - set slideshowShuffle(bool newValue) => set(SettingKeys.slideshowShuffleKey, newValue); - - bool get slideshowFillScreen => getBool(SettingKeys.slideshowFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; - - set slideshowFillScreen(bool newValue) => set(SettingKeys.slideshowFillScreenKey, newValue); - - bool get slideshowAnimatedZoomEffect => getBool(SettingKeys.slideshowAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; - - set slideshowAnimatedZoomEffect(bool newValue) => set(SettingKeys.slideshowAnimatedZoomEffectKey, newValue); - - ViewerTransition get slideshowTransition => getEnumOrDefault(SettingKeys.slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); - - set slideshowTransition(ViewerTransition newValue) => set(SettingKeys.slideshowTransitionKey, newValue.toString()); - - SlideshowVideoPlayback get slideshowVideoPlayback => getEnumOrDefault(SettingKeys.slideshowVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); - - set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => set(SettingKeys.slideshowVideoPlaybackKey, newValue.toString()); - - int get slideshowInterval => getInt(SettingKeys.slideshowIntervalKey) ?? SettingsDefaults.slideshowInterval; - - set slideshowInterval(int newValue) => set(SettingKeys.slideshowIntervalKey, newValue); - - // widget - - WidgetOutline getWidgetOutline(int widgetId) => getEnumOrDefault('${SettingKeys.widgetOutlinePrefixKey}$widgetId', WidgetOutline.none, WidgetOutline.values); - - void setWidgetOutline(int widgetId, WidgetOutline newValue) => set('${SettingKeys.widgetOutlinePrefixKey}$widgetId', newValue.toString()); - - WidgetShape getWidgetShape(int widgetId) => getEnumOrDefault('${SettingKeys.widgetShapePrefixKey}$widgetId', SettingsDefaults.widgetShape, WidgetShape.values); - - void setWidgetShape(int widgetId, WidgetShape newValue) => set('${SettingKeys.widgetShapePrefixKey}$widgetId', newValue.toString()); - - Set getWidgetCollectionFilters(int widgetId) => (getStringList('${SettingKeys.widgetCollectionFiltersPrefixKey}$widgetId') ?? []).map(CollectionFilter.fromJson).nonNulls.toSet(); - - void setWidgetCollectionFilters(int widgetId, Set newValue) => set('${SettingKeys.widgetCollectionFiltersPrefixKey}$widgetId', newValue.map((filter) => filter.toJson()).toList()); - - WidgetOpenPage getWidgetOpenPage(int widgetId) => getEnumOrDefault('${SettingKeys.widgetOpenPagePrefixKey}$widgetId', SettingsDefaults.widgetOpenPage, WidgetOpenPage.values); - - void setWidgetOpenPage(int widgetId, WidgetOpenPage newValue) => set('${SettingKeys.widgetOpenPagePrefixKey}$widgetId', newValue.toString()); - - WidgetDisplayedItem getWidgetDisplayedItem(int widgetId) => getEnumOrDefault('${SettingKeys.widgetDisplayedItemPrefixKey}$widgetId', SettingsDefaults.widgetDisplayedItem, WidgetDisplayedItem.values); - - void setWidgetDisplayedItem(int widgetId, WidgetDisplayedItem newValue) => set('${SettingKeys.widgetDisplayedItemPrefixKey}$widgetId', newValue.toString()); - - String? getWidgetUri(int widgetId) => getString('${SettingKeys.widgetUriPrefixKey}$widgetId'); - - void setWidgetUri(int widgetId, String? newValue) => set('${SettingKeys.widgetUriPrefixKey}$widgetId', newValue); - // platform settings void _onPlatformSettingsChanged(Map? fields) { @@ -437,7 +351,6 @@ class Settings with ChangeNotifier, SettingsAccess, DebugSettings, AppSettings, case SettingKeys.convertWriteMetadataKey: case SettingKeys.saveSearchHistoryKey: case SettingKeys.showPinchGestureAlternativesKey: - case SettingKeys.filePickerShowHiddenFilesKey: case SettingKeys.screenSaverFillScreenKey: case SettingKeys.screenSaverAnimatedZoomEffectKey: case SettingKeys.slideshowRepeatKey: diff --git a/lib/model/source/album.dart b/lib/model/source/album.dart index 8fcd01967..70670e87e 100644 --- a/lib/model/source/album.dart +++ b/lib/model/source/album.dart @@ -1,5 +1,5 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/settings/settings.dart'; @@ -124,12 +124,14 @@ mixin AlbumMixin on SourceBase { _filterRecentEntryMap.remove(key); }); - // clear entries for all dynamic albums + // clear entries for all dynamic albums and groups invalidateDynamicAlbumFilterSummary(notify: false); + invalidateAlbumGroupFilterSummary(notify: false); } if (notify) { eventBus.fire(StoredAlbumSummaryInvalidatedEvent(directories)); eventBus.fire(const DynamicAlbumSummaryInvalidatedEvent()); + eventBus.fire(const AlbumGroupSummaryInvalidatedEvent()); } } @@ -143,8 +145,20 @@ mixin AlbumMixin on SourceBase { } } + void invalidateAlbumGroupFilterSummary({bool notify = true}) { + _filterEntryCountMap.removeWhere(_isAlbumGroupKey); + _filterSizeMap.removeWhere(_isAlbumGroupKey); + _filterRecentEntryMap.removeWhere(_isAlbumGroupKey); + + if (notify) { + eventBus.fire(const AlbumGroupSummaryInvalidatedEvent()); + } + } + bool _isDynamicAlbumKey(String key, _) => key.startsWith('${DynamicAlbumFilter.type}-'); + bool _isAlbumGroupKey(String key, _) => key.startsWith('${AlbumGroupFilter.type}-'); + int albumEntryCount(AlbumBaseFilter filter) { return _filterEntryCountMap.putIfAbsent(filter.key, () => visibleEntries.where(filter.test).length); } @@ -247,6 +261,10 @@ class DynamicAlbumSummaryInvalidatedEvent { const DynamicAlbumSummaryInvalidatedEvent(); } +class AlbumGroupSummaryInvalidatedEvent { + const AlbumGroupSummaryInvalidatedEvent(); +} + class StoredAlbumSummaryInvalidatedEvent { final Set? directories; diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 8fd67875e..8de5ee3bb 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -30,7 +30,7 @@ class CollectionLens with ChangeNotifier { final CollectionSource source; final Set filters; List burstPatterns; - EntryGroupFactor sectionFactor; + EntrySectionFactor sectionFactor; EntrySortFactor sortFactor; bool sortReverse; final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier(); @@ -147,13 +147,13 @@ class CollectionLens with ChangeNotifier { switch (sortFactor) { case EntrySortFactor.date: switch (sectionFactor) { - case EntryGroupFactor.none: + case EntrySectionFactor.none: return false; - case EntryGroupFactor.album: + case EntrySectionFactor.album: return showAlbumHeaders(); - case EntryGroupFactor.month: + case EntrySectionFactor.month: return true; - case EntryGroupFactor.day: + case EntrySectionFactor.day: return true; } case EntrySortFactor.name: @@ -281,13 +281,13 @@ class CollectionLens with ChangeNotifier { switch (sortFactor) { case EntrySortFactor.date: switch (sectionFactor) { - case EntryGroupFactor.album: + case EntrySectionFactor.album: sections = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); - case EntryGroupFactor.month: + case EntrySectionFactor.month: sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken)); - case EntryGroupFactor.day: + case EntrySectionFactor.day: sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken)); - case EntryGroupFactor.none: + case EntrySectionFactor.none: sections = Map.fromEntries([ MapEntry(const SectionKey(), _filteredSortedEntries), ]); diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index a9859c14a..9e48c9e90 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -8,7 +8,7 @@ import 'package:aves/model/entry/extensions/keys.dart'; import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/favourites.dart'; -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; @@ -545,55 +545,49 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place // filter summary int count(CollectionFilter filter) { - if (filter is AlbumBaseFilter) { - return albumEntryCount(filter); - } else if (filter is LocationFilter) { - switch (filter.level) { - case LocationLevel.country: - return countryEntryCount(filter); - case LocationLevel.state: - return stateEntryCount(filter); - case LocationLevel.place: - return placeEntryCount(filter); - } - } else if (filter is TagFilter) { - return tagEntryCount(filter); + switch (filter) { + case AlbumBaseFilter _: + return albumEntryCount(filter); + case LocationFilter(level: LocationLevel.country): + return countryEntryCount(filter); + case LocationFilter(level: LocationLevel.state): + return stateEntryCount(filter); + case LocationFilter(level: LocationLevel.place): + return placeEntryCount(filter); + case TagFilter _: + return tagEntryCount(filter); } return 0; } int size(CollectionFilter filter) { - if (filter is AlbumBaseFilter) { - return albumSize(filter); - } else if (filter is LocationFilter) { - switch (filter.level) { - case LocationLevel.country: - return countrySize(filter); - case LocationLevel.state: - return stateSize(filter); - case LocationLevel.place: - return placeSize(filter); - } - } else if (filter is TagFilter) { - return tagSize(filter); + switch (filter) { + case AlbumBaseFilter _: + return albumSize(filter); + case LocationFilter(level: LocationLevel.country): + return countrySize(filter); + case LocationFilter(level: LocationLevel.state): + return stateSize(filter); + case LocationFilter(level: LocationLevel.place): + return placeSize(filter); + case TagFilter _: + return tagSize(filter); } return 0; } AvesEntry? recentEntry(CollectionFilter filter) { - if (filter is AlbumBaseFilter) { - return albumRecentEntry(filter); - } else if (filter is LocationFilter) { - switch (filter.level) { - case LocationLevel.country: - return countryRecentEntry(filter); - case LocationLevel.state: - return stateRecentEntry(filter); - case LocationLevel.place: - return placeRecentEntry(filter); - } - } else if (filter is TagFilter) { - return tagRecentEntry(filter); + switch (filter) { + case AlbumBaseFilter _: + return albumRecentEntry(filter); + case LocationFilter(level: LocationLevel.country): + return countryRecentEntry(filter); + case LocationFilter(level: LocationLevel.state): + return stateRecentEntry(filter); + case LocationFilter(level: LocationLevel.place): + return placeRecentEntry(filter); + case TagFilter _: + return tagRecentEntry(filter); } return null; } diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 3f9fc6a64..9ef6143ba 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -49,12 +49,12 @@ abstract class StorageService { // returns whether user granted access to URIs Future requestMediaFileAccess(List uris, List mimeTypes); - // return whether operation succeeded (`null` if user cancelled) + // returns whether operation succeeded (`null` if user cancelled) Future createFile(String name, String mimeType, Uint8List bytes); Future openFile([String? mimeType]); - // return whether operation succeeded (`null` if user cancelled) + // returns whether operation succeeded (`null` if user cancelled) Future copyFile(String name, String mimeType, String sourceUri); } diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index c63e5cb0a..62b1d519e 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -80,7 +80,7 @@ class AIcons { static const place = Symbols.place; // view - static const group = Symbols.group_work; + static const section = Symbols.group_work; static const layout = Symbols.grid_view; static const layoutMosaic = Symbols.view_comfy; static const layoutGrid = Symbols.view_compact; @@ -112,6 +112,7 @@ class AIcons { static const filter = Symbols.filter_alt; static const filterOff = Symbols.filter_alt_off; static const goUp = Symbols.arrow_upward; + static const group = Symbols.stack_group; static const hide = Symbols.visibility_off; static const info = Symbols.info; static const layers = Symbols.layers; diff --git a/lib/utils/xmp_utils.dart b/lib/utils/xmp_utils.dart index b17f3cf5e..38d8dc50d 100644 --- a/lib/utils/xmp_utils.dart +++ b/lib/utils/xmp_utils.dart @@ -51,7 +51,7 @@ class XMP { return hasMeaningfulAttributes; } - // return time zone designator, formatted as `Z` or `+hh:mm` or `-hh:mm` + // returns time zone designator, formatted as `Z` or `+hh:mm` or `-hh:mm` // as of intl v0.17.0, formatting time zone offset is not implemented static String _xmpTimeZoneDesignator(DateTime date) { final offsetMinutes = date.timeZoneOffset.inMinutes; diff --git a/lib/view/src/actions/chip_set.dart b/lib/view/src/actions/chip_set.dart index e41ceb2d3..1c104b482 100644 --- a/lib/view/src/actions/chip_set.dart +++ b/lib/view/src/actions/chip_set.dart @@ -17,6 +17,7 @@ extension ExtraChipSetActionView on ChipSetAction { ChipSetAction.toggleTitleSearch => // different data depending on toggle state l10n.collectionActionShowTitleSearch, + ChipSetAction.createGroup => l10n.chipActionCreateGroup, ChipSetAction.createAlbum => l10n.chipActionCreateAlbum, ChipSetAction.createVault => l10n.chipActionCreateVault, // browsing or selecting @@ -29,6 +30,7 @@ extension ExtraChipSetActionView on ChipSetAction { ChipSetAction.hide => l10n.chipActionHide, ChipSetAction.pin => l10n.chipActionPin, ChipSetAction.unpin => l10n.chipActionUnpin, + ChipSetAction.group => l10n.chipActionGroup, ChipSetAction.lockVault => l10n.chipActionLock, ChipSetAction.showCountryStates => l10n.chipActionShowCountryStates, ChipSetAction.showCollection => l10n.chipActionShowCollection, @@ -53,6 +55,7 @@ extension ExtraChipSetActionView on ChipSetAction { ChipSetAction.toggleTitleSearch => // different data depending on toggle state AIcons.filter, + ChipSetAction.createGroup => AIcons.add, ChipSetAction.createAlbum => AIcons.add, ChipSetAction.createVault => AIcons.vaultAdd, // browsing or selecting @@ -65,6 +68,7 @@ extension ExtraChipSetActionView on ChipSetAction { ChipSetAction.hide => AIcons.hide, ChipSetAction.pin => AIcons.pin, ChipSetAction.unpin => AIcons.unpin, + ChipSetAction.group => AIcons.group, ChipSetAction.lockVault => AIcons.vaultLock, ChipSetAction.showCountryStates => AIcons.state, ChipSetAction.showCollection => AIcons.allCollection, diff --git a/lib/view/src/source/group.dart b/lib/view/src/source/group.dart index d9abaf867..3e8eab79b 100644 --- a/lib/view/src/source/group.dart +++ b/lib/view/src/source/group.dart @@ -3,44 +3,44 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/widgets.dart'; -extension ExtraEntryGroupFactorView on EntryGroupFactor { +extension ExtraEntryGroupFactorView on EntrySectionFactor { String getName(BuildContext context) { final l10n = context.l10n; return switch (this) { - EntryGroupFactor.album => l10n.collectionGroupAlbum, - EntryGroupFactor.month => l10n.collectionGroupMonth, - EntryGroupFactor.day => l10n.collectionGroupDay, - EntryGroupFactor.none => l10n.collectionGroupNone, + EntrySectionFactor.album => l10n.collectionGroupAlbum, + EntrySectionFactor.month => l10n.collectionGroupMonth, + EntrySectionFactor.day => l10n.collectionGroupDay, + EntrySectionFactor.none => l10n.sectionNone, }; } IconData get icon { return switch (this) { - EntryGroupFactor.album => AIcons.album, - EntryGroupFactor.month => AIcons.dateByMonth, - EntryGroupFactor.day => AIcons.dateByDay, - EntryGroupFactor.none => AIcons.clear, + EntrySectionFactor.album => AIcons.album, + EntrySectionFactor.month => AIcons.dateByMonth, + EntrySectionFactor.day => AIcons.dateByDay, + EntrySectionFactor.none => AIcons.clear, }; } } -extension ExtraAlbumChipGroupFactorView on AlbumChipGroupFactor { +extension ExtraAlbumChipGroupFactorView on AlbumChipSectionFactor { String getName(BuildContext context) { final l10n = context.l10n; return switch (this) { - AlbumChipGroupFactor.importance => l10n.albumGroupTier, - AlbumChipGroupFactor.mimeType => l10n.albumGroupType, - AlbumChipGroupFactor.volume => l10n.albumGroupVolume, - AlbumChipGroupFactor.none => l10n.albumGroupNone, + AlbumChipSectionFactor.importance => l10n.albumGroupTier, + AlbumChipSectionFactor.mimeType => l10n.albumGroupType, + AlbumChipSectionFactor.volume => l10n.albumGroupVolume, + AlbumChipSectionFactor.none => l10n.sectionNone, }; } IconData get icon { return switch (this) { - AlbumChipGroupFactor.importance => AIcons.important, - AlbumChipGroupFactor.mimeType => AIcons.mimeType, - AlbumChipGroupFactor.volume => AIcons.storageCard, - AlbumChipGroupFactor.none => AIcons.clear, + AlbumChipSectionFactor.importance => AIcons.important, + AlbumChipSectionFactor.mimeType => AIcons.mimeType, + AlbumChipSectionFactor.volume => AIcons.storageCard, + AlbumChipSectionFactor.none => AIcons.clear, }; } } diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index f0a33017e..2bcd767bb 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -83,11 +83,11 @@ class _CollectionAppBarState extends State with SingleTickerPr EntrySortFactor.duration, ]; - static const _groupOptions = [ - EntryGroupFactor.album, - EntryGroupFactor.month, - EntryGroupFactor.day, - EntryGroupFactor.none, + static const _sectionOptions = [ + EntrySectionFactor.album, + EntrySectionFactor.month, + EntrySectionFactor.day, + EntrySectionFactor.none, ]; static const _layoutOptions = [ @@ -690,16 +690,16 @@ class _CollectionAppBarState extends State with SingleTickerPr settings.collectionSortReverse, ); final extentController = context.read(); - final value = await showDialog<(EntrySortFactor?, EntryGroupFactor?, TileLayout?, bool)>( + final value = await showDialog<(EntrySortFactor?, EntrySectionFactor?, TileLayout?, bool)>( context: context, builder: (context) { - return TileViewDialog( + return TileViewDialog( initialValue: initialValue, sortOptions: _sortOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), - groupOptions: _groupOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), + sectionOptions: _sectionOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), layoutOptions: _layoutOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), sortOrder: (factor, reverse) => factor.getOrderName(context, reverse), - canGroup: (s, g, l) => s == EntrySortFactor.date, + canSection: (s, g, l) => s == EntrySortFactor.date, tileExtentController: extentController, ); }, diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 016f96b85..da8bb3ddb 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -697,10 +697,10 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge switch (collection.sortFactor) { case EntrySortFactor.date: switch (collection.sectionFactor) { - case EntryGroupFactor.album: + case EntrySectionFactor.album: addAlbums(collection, sectionLayouts, crumbs); - case EntryGroupFactor.month: - case EntryGroupFactor.day: + case EntrySectionFactor.month: + case EntrySectionFactor.day: final firstKey = sectionLayouts.first.sectionKey; final lastKey = sectionLayouts.last.sectionKey; if (firstKey is EntryDateSectionKey && lastKey is EntryDateSectionKey) { @@ -722,7 +722,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge }); } } - case EntryGroupFactor.none: + case EntrySectionFactor.none: break; } case EntrySortFactor.name: diff --git a/lib/widgets/collection/draggable_thumb_label.dart b/lib/widgets/collection/draggable_thumb_label.dart index 8337f22a0..7f10a2730 100644 --- a/lib/widgets/collection/draggable_thumb_label.dart +++ b/lib/widgets/collection/draggable_thumb_label.dart @@ -28,17 +28,17 @@ class CollectionDraggableThumbLabel extends StatelessWidget { switch (collection.sortFactor) { case EntrySortFactor.date: switch (collection.sectionFactor) { - case EntryGroupFactor.album: + case EntrySectionFactor.album: return [ DraggableThumbLabel.formatMonthThumbLabel(context, entry.bestDate), if (_showAlbumName(context, entry)) _getAlbumName(context, entry), ]; - case EntryGroupFactor.month: - case EntryGroupFactor.none: + case EntrySectionFactor.month: + case EntrySectionFactor.none: return [ DraggableThumbLabel.formatMonthThumbLabel(context, entry.bestDate), ]; - case EntryGroupFactor.day: + case EntrySectionFactor.day: return [ DraggableThumbLabel.formatDayThumbLabel(context, entry.bestDate), ]; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 37f1be782..0afc9433f 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -12,6 +12,7 @@ import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/set_and.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/metadata/date_modifier.dart'; import 'package:aves/model/naming_pattern.dart'; @@ -43,7 +44,7 @@ import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/convert_entry_dialog.dart'; import 'package:aves/widgets/dialogs/entry_editors/rename_entry_set_page.dart'; -import 'package:aves/widgets/dialogs/filter_editors/add_dynamic_album_dialog.dart'; +import 'package:aves/widgets/dialogs/filter_editors/create_dynamic_album_dialog.dart'; import 'package:aves/widgets/dialogs/pick_dialogs/location_pick_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/map/map_page.dart'; @@ -771,8 +772,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final name = await showDialog( context: context, - builder: (context) => const AddDynamicAlbumDialog(), - routeSettings: const RouteSettings(name: AddDynamicAlbumDialog.routeName), + builder: (context) => const CreateDynamicAlbumDialog(), + routeSettings: const RouteSettings(name: CreateDynamicAlbumDialog.routeName), ); if (name == null) return; @@ -792,19 +793,20 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware } } - Future _showDynamicAlbum(NavigatorState? navigator, DynamicAlbumFilter album) async { + Future _showDynamicAlbum(NavigatorState? navigator, DynamicAlbumFilter albumFilter) async { // local context may be deactivated when action is triggered after navigation if (navigator != null) { final context = navigator.context; final highlightInfo = context.read(); if (context.currentRouteName == AlbumListPage.routeName) { - highlightInfo.trackItem(FilterGridItem(album, null), highlightItem: album); + highlightInfo.trackItem(FilterGridItem(albumFilter, null), highlightItem: albumFilter); } else { - highlightInfo.set(album); + highlightInfo.set(albumFilter); + final initialGroup = albumGrouping.getFilterParent(albumFilter); await navigator.pushAndRemoveUntil( MaterialPageRoute( settings: const RouteSettings(name: AlbumListPage.routeName), - builder: (_) => const AlbumListPage(), + builder: (_) => AlbumListPage(initialGroup: initialGroup), ), (route) => false, ); diff --git a/lib/widgets/collection/grid/headers/any.dart b/lib/widgets/collection/grid/headers/any.dart index 6b14ab3dc..5872b196d 100644 --- a/lib/widgets/collection/grid/headers/any.dart +++ b/lib/widgets/collection/grid/headers/any.dart @@ -40,21 +40,21 @@ class CollectionSectionHeader extends StatelessWidget { switch (collection.sortFactor) { case EntrySortFactor.date: switch (collection.sectionFactor) { - case EntryGroupFactor.album: + case EntrySectionFactor.album: return _buildAlbumHeader(context); - case EntryGroupFactor.month: + case EntrySectionFactor.month: return MonthSectionHeader( key: ValueKey(sectionKey), date: (sectionKey as EntryDateSectionKey).date, selectable: selectable, ); - case EntryGroupFactor.day: + case EntrySectionFactor.day: return DaySectionHeader( key: ValueKey(sectionKey), date: (sectionKey as EntryDateSectionKey).date, selectable: selectable, ); - case EntryGroupFactor.none: + case EntrySectionFactor.none: break; } case EntrySortFactor.name: diff --git a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart index beb62331e..fd28ce38c 100644 --- a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/tag.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/view/view.dart'; diff --git a/lib/widgets/common/action_mixins/entry_storage.dart b/lib/widgets/common/action_mixins/entry_storage.dart index 15c4b88ac..ca2bc1614 100644 --- a/lib/widgets/common/action_mixins/entry_storage.dart +++ b/lib/widgets/common/action_mixins/entry_storage.dart @@ -33,6 +33,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart'; import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart'; +import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; @@ -42,7 +43,12 @@ import 'package:provider/provider.dart'; mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { // returns whether it completed the action (with or without failures) Future doExport(BuildContext context, Set targetEntries, EntryConvertOptions options) async { - final destinationAlbumFilter = await pickAlbum(context: context, moveType: MoveType.export, storedAlbumsOnly: true); + final destinationAlbumFilter = await pickAlbum( + context: context, + moveType: MoveType.export, + albumTypes: {AlbumChipType.stored}, + initialGroup: null, + ); if (destinationAlbumFilter == null || destinationAlbumFilter is! StoredAlbumFilter) return false; final destinationAlbum = destinationAlbumFilter.album; @@ -370,7 +376,12 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { case MoveType.copy: case MoveType.move: case MoveType.export: - final destinationAlbumFilter = await pickAlbum(context: context, moveType: moveType, storedAlbumsOnly: true); + final destinationAlbumFilter = await pickAlbum( + context: context, + moveType: moveType, + albumTypes: {AlbumChipType.stored}, + initialGroup: null, + ); if (destinationAlbumFilter == null || destinationAlbumFilter is! StoredAlbumFilter) return false; final destinationAlbum = destinationAlbumFilter.album; diff --git a/lib/widgets/common/app_bar/crumb_line.dart b/lib/widgets/common/app_bar/crumb_line.dart new file mode 100644 index 000000000..4ad8e3414 --- /dev/null +++ b/lib/widgets/common/app_bar/crumb_line.dart @@ -0,0 +1,104 @@ +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class CrumbLine extends StatefulWidget { + final List Function(BuildContext context) split; + final T Function(BuildContext context, int index) combine; + final void Function(T combined) onTap; + + const CrumbLine({ + super.key, + required this.split, + required this.combine, + required this.onTap, + }); + + @override + State> createState() => _CrumbLineState(); + + static double getPreferredHeight(TextScaler textScaler) => textScaler.scale(kToolbarHeight); +} + +class _CrumbLineState extends State> { + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(covariant CrumbLine oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.split(context).length < widget.split(context).length) { + // scroll to show last crumb + WidgetsBinding.instance.addPostFrameCallback((_) { + final animate = context.read().animate; + final extent = _scrollController.position.maxScrollExtent; + if (animate) { + _scrollController.animateTo( + extent, + duration: const Duration(milliseconds: 500), + curve: Curves.easeOutQuad, + ); + } else { + _scrollController.jumpTo(extent); + } + }); + } + } + + @override + Widget build(BuildContext context) { + final parts = widget.split(context); + + final crumbColor = DefaultTextStyle.of(context).style.color; + return ListView.builder( + scrollDirection: Axis.horizontal, + controller: _scrollController, + padding: const EdgeInsets.symmetric(horizontal: 8), + itemBuilder: (context, index) { + Widget _buildText(String text) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text(text), + ); + + if (index >= parts.length) return const SizedBox(); + final text = parts[index]; + if (index == parts.length - 1) { + return Center( + child: DefaultTextStyle.merge( + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + child: _buildText(text), + ), + ); + } + return GestureDetector( + onTap: parts.isNotEmpty ? () => widget.onTap(widget.combine(context, index)) : null, + child: Container( + // use a `Container` with a dummy color to make it expand + // so that we can also detect taps around the title `Text` + color: Colors.transparent, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildText(text), + Icon( + AIcons.next, + color: crumbColor, + ), + ], + ), + ), + ); + }, + itemCount: parts.length, + ); + } +} diff --git a/lib/widgets/common/basic/gestures/gesture_detector.dart b/lib/widgets/common/basic/gestures/gesture_detector.dart index f6dd5548c..e7a6d08ab 100644 --- a/lib/widgets/common/basic/gestures/gesture_detector.dart +++ b/lib/widgets/common/basic/gestures/gesture_detector.dart @@ -1,6 +1,6 @@ -import 'package:flutter/widgets.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; // as of Flutter v3.27.1, `GestureDetector` does not allow setting long press delay // adapted from Flutter `GestureDetector` in `/widgets/gesture_detector.dart` diff --git a/lib/widgets/common/behaviour/pop/tv_navigation.dart b/lib/widgets/common/behaviour/pop/tv_navigation.dart index 720a397ae..ad408bf16 100644 --- a/lib/widgets/common/behaviour/pop/tv_navigation.dart +++ b/lib/widgets/common/behaviour/pop/tv_navigation.dart @@ -54,7 +54,7 @@ class TvNavigationPopHandler implements PopHandler { return switch (homePage) { HomePageSetting.collection => buildRoute((context) => CollectionPage(source: context.read(), filters: null)), - HomePageSetting.albums => buildRoute((context) => const AlbumListPage()), + HomePageSetting.albums => buildRoute((context) => const AlbumListPage(initialGroup: null)), HomePageSetting.tags => buildRoute((context) => const TagListPage()), HomePageSetting.explorer => buildRoute((context) => const ExplorerPage()), }; diff --git a/lib/widgets/common/grid/sections/list_layout.dart b/lib/widgets/common/grid/sections/list_layout.dart index aac861bb8..06e9f0588 100644 --- a/lib/widgets/common/grid/sections/list_layout.dart +++ b/lib/widgets/common/grid/sections/list_layout.dart @@ -18,7 +18,7 @@ abstract class SectionedListLayout { required this.sectionLayouts, }); - // return tile rectangle in layout space, i.e. x=0 is start + // returns tile rectangle in layout space, i.e. x=0 is start Rect? getTileRect(T item); SectionLayout? getSectionAt(double offsetY) => sectionLayouts.firstWhereOrNull((sl) => offsetY < sl.maxOffset); diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 50bc64cc6..163017c57 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'package:aves/app_mode.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; @@ -24,8 +23,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -typedef AFilterCallback = void Function(CollectionFilter filter); -typedef OffsetFilterCallback = void Function(BuildContext context, CollectionFilter filter, Offset tapPosition); +typedef AFilterCallback = void Function(T filter); +typedef OffsetFilterCallback = void Function(BuildContext context, T filter, Offset tapPosition); enum HeroType { always, onTap, never } @@ -108,17 +107,12 @@ class AvesFilterChip extends StatefulWidget { final actionDelegate = ChipActionDelegate(); final animations = context.read().accessibilityAnimations; - var title = filter.getLabel(context); - if (filter is MimeFilter) { - title += ' (${filter.mime})'; - } - final selectedAction = await showMenu( context: context, position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size), items: [ PopupMenuItem( - child: Text(title), + child: Text(filter.getTooltip(context)), ), const PopupMenuDivider(), ...ChipAction.values.where((action) => actionDelegate.isVisible(action, filter: filter)).map((action) { diff --git a/lib/widgets/common/providers/filter_group_provider.dart b/lib/widgets/common/providers/filter_group_provider.dart new file mode 100644 index 000000000..884eae5a5 --- /dev/null +++ b/lib/widgets/common/providers/filter_group_provider.dart @@ -0,0 +1,17 @@ +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +class FilterGroupProvider extends ListenableProvider { + FilterGroupProvider({ + super.key, + Uri? initialValue, + super.child, + }) : super( + create: (context) => FilterGroupNotifier(initialValue), + dispose: (context, value) => value.dispose(), + ); +} + +class FilterGroupNotifier extends ValueNotifier { + FilterGroupNotifier(super.value); +} diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart index 9c0b74376..3678b6bc1 100644 --- a/lib/widgets/debug/app_debug_page.dart +++ b/lib/widgets/debug/app_debug_page.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/covered/location.dart'; -import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/covered/tag.dart'; +import 'package:aves/model/filters/path.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; diff --git a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart index c8a3b57b2..f20b55e5e 100644 --- a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart @@ -7,11 +7,11 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/basic/text_dropdown_button.dart'; +import 'package:aves/widgets/common/basic/time_shift_selector.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/transitions.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; -import 'package:aves/widgets/common/basic/time_shift_selector.dart'; import 'package:aves/widgets/dialogs/item_picker.dart'; import 'package:aves/widgets/dialogs/pick_dialogs/item_pick_page.dart'; import 'package:aves_model/aves_model.dart'; diff --git a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart index f2f88fe24..0f22a467c 100644 --- a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart +++ b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart @@ -1,7 +1,7 @@ import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/placeholder.dart'; -import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/durations.dart'; diff --git a/lib/widgets/dialogs/filter_editors/add_dynamic_album_dialog.dart b/lib/widgets/dialogs/filter_editors/create_dynamic_album_dialog.dart similarity index 78% rename from lib/widgets/dialogs/filter_editors/add_dynamic_album_dialog.dart rename to lib/widgets/dialogs/filter_editors/create_dynamic_album_dialog.dart index b673b8d52..6dc3e6895 100644 --- a/lib/widgets/dialogs/filter_editors/add_dynamic_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/create_dynamic_album_dialog.dart @@ -3,16 +3,16 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:flutter/material.dart'; -class AddDynamicAlbumDialog extends StatefulWidget { - static const routeName = '/dialog/add_dynamic_album'; +class CreateDynamicAlbumDialog extends StatefulWidget { + static const routeName = '/dialog/create_dynamic_album'; - const AddDynamicAlbumDialog({super.key}); + const CreateDynamicAlbumDialog({super.key}); @override - State createState() => _AddDynamicAlbumDialogState(); + State createState() => _CreateDynamicAlbumDialogState(); } -class _AddDynamicAlbumDialogState extends State { +class _CreateDynamicAlbumDialogState extends State { final TextEditingController _nameController = TextEditingController(); final ValueNotifier _existsNotifier = ValueNotifier(false); final ValueNotifier _isValidNotifier = ValueNotifier(false); @@ -55,13 +55,13 @@ class _AddDynamicAlbumDialogState extends State { const CancelButton(), ValueListenableBuilder( valueListenable: _existsNotifier, - builder: (context, albumExists, child) { + builder: (context, exists, child) { return ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { return TextButton( onPressed: isValid ? () => _submit(context) : null, - child: Text(albumExists ? l10n.showButtonLabel : l10n.createAlbumButtonLabel), + child: Text(exists ? l10n.showButtonLabel : l10n.createAlbumButtonLabel), ); }, ); @@ -80,9 +80,8 @@ class _AddDynamicAlbumDialogState extends State { void _validate() { final name = _formatAlbumName(); - final isValid = name != null; - _isValidNotifier.value = isValid; - _existsNotifier.value = isValid && dynamicAlbums.contains(name); + _isValidNotifier.value = name != null; + _existsNotifier.value = dynamicAlbums.contains(name); } void _submit(BuildContext context) { diff --git a/lib/widgets/dialogs/filter_editors/create_group_dialog.dart b/lib/widgets/dialogs/filter_editors/create_group_dialog.dart new file mode 100644 index 000000000..055c5fbc6 --- /dev/null +++ b/lib/widgets/dialogs/filter_editors/create_group_dialog.dart @@ -0,0 +1,99 @@ +import 'package:aves/model/grouping/common.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:flutter/material.dart'; + +class CreateGroupDialog extends StatefulWidget { + static const routeName = '/dialog/create_group'; + + final FilterGrouping grouping; + final Uri? parentGroupUri; + + const CreateGroupDialog({ + super.key, + required this.grouping, + required this.parentGroupUri, + }); + + @override + State createState() => _CreateGroupDialogState(); +} + +class _CreateGroupDialogState extends State { + final TextEditingController _nameController = TextEditingController(); + final ValueNotifier _existsNotifier = ValueNotifier(false); + final ValueNotifier _isValidNotifier = ValueNotifier(false); + + FilterGrouping get grouping => widget.grouping; + + Uri? get parentGroupUri => widget.parentGroupUri; + + @override + void initState() { + super.initState(); + _validate(); + } + + @override + void dispose() { + _nameController.dispose(); + _existsNotifier.dispose(); + _isValidNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return AvesDialog( + title: l10n.newGroupDialogTitle, + content: ValueListenableBuilder( + valueListenable: _existsNotifier, + builder: (context, exists, child) { + return TextField( + controller: _nameController, + decoration: InputDecoration( + labelText: l10n.newGroupDialogNameLabel, + helperText: exists ? l10n.groupAlreadyExists : '', + ), + autofocus: true, + onChanged: (_) => _validate(), + onSubmitted: (_) => _submit(context), + ); + }, + ), + actions: [ + const CancelButton(), + ValueListenableBuilder( + valueListenable: _isValidNotifier, + builder: (context, isValid, child) { + return TextButton( + onPressed: isValid ? () => _submit(context) : null, + child: Text(l10n.createButtonLabel), + ); + }, + ), + ], + ); + } + + Uri? _getNewGroupUri() { + final name = _nameController.text.trim(); + if (name.isEmpty) return null; + return grouping.buildGroupUri(parentGroupUri, name); + } + + void _validate() { + final newGroupUri = _getNewGroupUri(); + final exists = grouping.exists(newGroupUri); + _isValidNotifier.value = newGroupUri != null && !exists; + _existsNotifier.value = exists; + } + + void _submit(BuildContext context) { + if (_isValidNotifier.value) { + Navigator.maybeOf(context)?.pop(_getNewGroupUri()); + } + } +} diff --git a/lib/widgets/dialogs/filter_editors/rename_dynamic_album_dialog.dart b/lib/widgets/dialogs/filter_editors/rename_dynamic_album_dialog.dart index 8222b6a1f..7af1bc655 100644 --- a/lib/widgets/dialogs/filter_editors/rename_dynamic_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/rename_dynamic_album_dialog.dart @@ -80,8 +80,9 @@ class _RenameDynamicAlbumDialogState extends State { Future _validate() async { final newName = _formatAlbumName(); - _isValidNotifier.value = newName != null && !dynamicAlbums.contains(newName); - _existsNotifier.value = newName != null && dynamicAlbums.contains(newName) && newName != initialValue; + final exists = dynamicAlbums.contains(newName); + _isValidNotifier.value = newName != null && !exists; + _existsNotifier.value = exists && newName != initialValue; } void _submit(BuildContext context) { diff --git a/lib/widgets/dialogs/filter_editors/rename_group_dialog.dart b/lib/widgets/dialogs/filter_editors/rename_group_dialog.dart new file mode 100644 index 000000000..7cf9d4c4b --- /dev/null +++ b/lib/widgets/dialogs/filter_editors/rename_group_dialog.dart @@ -0,0 +1,101 @@ +import 'package:aves/model/grouping/common.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:flutter/material.dart'; + +class RenameGroupDialog extends StatefulWidget { + static const routeName = '/dialog/rename_group'; + + final FilterGrouping grouping; + final Uri groupUri; + + const RenameGroupDialog({ + super.key, + required this.grouping, + required this.groupUri, + }); + + @override + State createState() => _RenameGroupDialogState(); +} + +class _RenameGroupDialogState extends State { + final TextEditingController _nameController = TextEditingController(); + final ValueNotifier _existsNotifier = ValueNotifier(false); + final ValueNotifier _isValidNotifier = ValueNotifier(false); + + FilterGrouping get grouping => widget.grouping; + + Uri get initialGroupUri => widget.groupUri; + + Uri? get parentGroupUri => FilterGrouping.getParentGroup(initialGroupUri); + + @override + void initState() { + super.initState(); + _nameController.text = FilterGrouping.getGroupName(initialGroupUri) ?? ''; + _validate(); + } + + @override + void dispose() { + _nameController.dispose(); + _existsNotifier.dispose(); + _isValidNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return AvesDialog( + content: ValueListenableBuilder( + valueListenable: _existsNotifier, + builder: (context, exists, child) { + return TextField( + controller: _nameController, + decoration: InputDecoration( + labelText: l10n.renameAlbumDialogLabel, + helperText: exists ? l10n.groupAlreadyExists : '', + ), + autofocus: true, + onChanged: (_) => _validate(), + onSubmitted: (_) => _submit(context), + ); + }, + ), + actions: [ + const CancelButton(), + ValueListenableBuilder( + valueListenable: _isValidNotifier, + builder: (context, isValid, child) { + return TextButton( + onPressed: isValid ? () => _submit(context) : null, + child: Text(l10n.applyButtonLabel), + ); + }, + ), + ], + ); + } + + Uri? _getNewGroupUri() { + final name = _nameController.text.trim(); + if (name.isEmpty) return null; + return grouping.buildGroupUri(parentGroupUri, name); + } + + void _validate() { + final newGroupUri = _getNewGroupUri(); + final exists = grouping.exists(newGroupUri); + _isValidNotifier.value = newGroupUri != null && !exists; + _existsNotifier.value = exists && newGroupUri != initialGroupUri; + } + + void _submit(BuildContext context) { + if (_isValidNotifier.value) { + Navigator.maybeOf(context)?.pop(_getNewGroupUri()); + } + } +} diff --git a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart index f212d59f8..ec86cdcce 100644 --- a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart @@ -1,7 +1,9 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; @@ -13,13 +15,18 @@ import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/view/view.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; +import 'package:aves/widgets/common/action_mixins/vault_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/aves_fab.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:aves/widgets/common/identity/empty.dart'; +import 'package:aves/widgets/common/providers/filter_group_provider.dart'; import 'package:aves/widgets/common/providers/query_provider.dart'; import 'package:aves/widgets/common/providers/selection_provider.dart'; import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; +import 'package:aves/widgets/dialogs/filter_editors/create_group_dialog.dart'; import 'package:aves/widgets/dialogs/filter_editors/create_stored_album_dialog.dart'; import 'package:aves/widgets/dialogs/filter_editors/edit_vault_dialog.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; @@ -34,7 +41,8 @@ import 'package:provider/provider.dart'; Future pickAlbum({ required BuildContext context, required MoveType? moveType, - required bool storedAlbumsOnly, + required Iterable albumTypes, + required Uri? initialGroup, }) async { final source = context.read(); if (source.targetScope != CollectionSource.fullScope) { @@ -46,7 +54,12 @@ Future pickAlbum({ return await Navigator.maybeOf(context)?.push( MaterialPageRoute( settings: const RouteSettings(name: _AlbumPickPage.routeName), - builder: (context) => _AlbumPickPage(source: source, moveType: moveType, storedAlbumsOnly: storedAlbumsOnly), + builder: (context) => _AlbumPickPage( + source: source, + moveType: moveType, + albumChipTypes: albumTypes, + initialGroup: initialGroup, + ), ), ); } @@ -56,47 +69,44 @@ class _AlbumPickPage extends StatefulWidget { final CollectionSource source; final MoveType? moveType; - final bool storedAlbumsOnly; + final Iterable albumChipTypes; + final Uri? initialGroup; const _AlbumPickPage({ required this.source, required this.moveType, - required this.storedAlbumsOnly, + required this.albumChipTypes, + required this.initialGroup, }); @override State<_AlbumPickPage> createState() => _AlbumPickPageState(); } -class _AlbumPickPageState extends State<_AlbumPickPage> { +class _AlbumPickPageState extends State<_AlbumPickPage> with FeedbackMixin, VaultAwareMixin { final ValueNotifier _appBarHeightNotifier = ValueNotifier(0); final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.pickFilterInternal); CollectionSource get source => widget.source; + Iterable get albumChipTypes => widget.albumChipTypes; + + bool get isPickingGroup => albumChipTypes.length == 1 && albumChipTypes.contains(AlbumChipType.group); + String get title { final l10n = context.l10n; - return switch (widget.moveType) { - MoveType.copy => l10n.albumPickPageTitleCopy, - MoveType.move => l10n.albumPickPageTitleMove, - MoveType.export => l10n.albumPickPageTitleExport, - MoveType.toBin || MoveType.fromBin || null => l10n.albumPickPageTitlePick, - }; + if (isPickingGroup) { + return l10n.groupPickerTitle; + } else { + return switch (widget.moveType) { + MoveType.copy => l10n.albumPickPageTitleCopy, + MoveType.move => l10n.albumPickPageTitleMove, + MoveType.export => l10n.albumPickPageTitleExport, + MoveType.toBin || MoveType.fromBin || null => l10n.albumPickPageTitlePick, + }; + } } - static const _quickActions = [ - ChipSetAction.createAlbum, - ]; - - // `null` items are converted to dividers - static const _menuActions = [ - ...ChipSetActions.general, - null, - ChipSetAction.toggleTitleSearch, - null, - ChipSetAction.createVault, - ]; - @override void dispose() { _appBarHeightNotifier.dispose(); @@ -108,48 +118,91 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { Widget build(BuildContext context) { return ListenableProvider>.value( value: _appModeNotifier, - child: Selector( - selector: (context, s) => (s.albumGroupFactor, s.albumSortFactor), - builder: (context, s, child) { - return StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - final gridItems = AlbumListPage.getAlbumGridItems(context, source, storedAlbumsOnly: widget.storedAlbumsOnly); - return SelectionProvider>( - child: QueryProvider( - startEnabled: settings.getShowTitleQuery(context.currentRouteName!), - child: FilterGridPage( - settingsRouteKey: AlbumListPage.routeName, - appBar: FilterGridAppBar( - source: source, - title: title, - actionDelegate: AlbumChipSetActionDelegate(gridItems), - actionsBuilder: _buildActions, - isEmpty: false, - appBarHeightNotifier: _appBarHeightNotifier, - ), - appBarHeightNotifier: _appBarHeightNotifier, - sections: AlbumListPage.groupToSections(context, source, gridItems), - newFilters: source.getNewAlbumFilters(context), - sortFactor: settings.albumSortFactor, - showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none, - selectable: false, - applyQuery: AlbumListPage.applyQuery, - emptyBuilder: () => EmptyContent( - icon: AIcons.album, - text: context.l10n.albumEmpty, - ), - heroType: HeroType.never, - ), - ), - ); - }, - ); - }, + child: MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: albumGrouping), + FilterGroupProvider(initialValue: widget.initialGroup), + ], + child: Builder( + // to access filter group provider from subtree context + builder: (context) { + return Selector( + selector: (context, s) => (s.albumSectionFactor, s.albumSortFactor), + builder: (context, s, child) { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) { + final groupUri = context.watch().value; + final gridItems = AlbumListPage.getAlbumGridItems(context, source, albumChipTypes, groupUri); + return SelectionProvider>( + child: QueryProvider( + startEnabled: settings.getShowTitleQuery(context.currentRouteName!), + child: FilterGridPage( + settingsRouteKey: AlbumListPage.routeName, + appBar: FilterGridAppBar( + source: source, + title: title, + actionDelegate: AlbumChipSetActionDelegate(gridItems), + actionsBuilder: _buildActions, + isEmpty: false, + appBarHeightNotifier: _appBarHeightNotifier, + ), + appBarHeightNotifier: _appBarHeightNotifier, + sections: AlbumListPage.groupToSections(context, source, gridItems), + newFilters: source.getNewAlbumFilters(context), + sortFactor: settings.albumSortFactor, + showHeaders: settings.albumSectionFactor != AlbumChipSectionFactor.none, + selectable: false, + emptyBuilder: () => isPickingGroup + ? EmptyContent( + icon: AIcons.group, + text: context.l10n.groupEmpty, + ) + : EmptyContent( + icon: AIcons.album, + text: context.l10n.albumEmpty, + ), + heroType: HeroType.never, + floatingActionButton: _buildFab(context), + onTileTap: (gridItem, _) async { + final filter = gridItem.filter; + if (!await unlockFilter(context, filter)) return; + switch (filter) { + case AlbumGroupFilter _: + context.read().value = filter.uri; + case StoredAlbumFilter _: + case DynamicAlbumFilter _: + _pickFilter(context, filter); + } + }, + ), + ), + ); + }, + ); + }, + ); + }, + ), ), ); } + Widget? _buildFab(BuildContext context) { + return isPickingGroup + ? AvesFab( + tooltip: context.l10n.groupPickerUseThisGroupButton, + onPressed: () { + final groupUri = context.read().value; + final filter = groupUri != null ? albumGrouping.uriToFilter(groupUri) : AlbumGroupFilter.root; + if (filter is AlbumBaseFilter) { + _pickFilter(context, filter); + } + }, + ) + : null; + } + List _buildActions( BuildContext context, AppMode appMode, @@ -171,6 +224,9 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { void onActionSelected(ChipSetAction action) { switch (action) { + case ChipSetAction.createGroup: + final parentGroupUri = context.read().value; + _createGroup(parentGroupUri); case ChipSetAction.createAlbum: _createAlbum(); case ChipSetAction.createVault: @@ -215,18 +271,35 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { required void Function(ChipSetAction action) onActionSelected, }) { final animations = context.select((v) => v.accessibilityAnimations); + + final canCreateStoredAlbums = widget.moveType != null; + final quickActions = [ + if (isPickingGroup) ChipSetAction.createGroup, + if (canCreateStoredAlbums) ChipSetAction.createAlbum, + ]; + + // `null` items are converted to dividers + final menuActions = [ + ...ChipSetActions.general, + null, + ChipSetAction.toggleTitleSearch, + if (canCreateStoredAlbums) ...[ + null, + ChipSetAction.createVault, + ] + ]; + return [ - if (widget.moveType != null) - ..._quickActions.where(isVisible).map( - (action) => IconButton( - icon: action.getIcon(), - onPressed: () => onActionSelected(action), - tooltip: action.getText(context), - ), + ...quickActions.where(isVisible).map( + (action) => IconButton( + icon: action.getIcon(), + onPressed: () => onActionSelected(action), + tooltip: action.getText(context), ), + ), PopupMenuButton( itemBuilder: (context) { - return _menuActions.where((v) => v == null || isVisible(v)).map((action) { + return menuActions.where((v) => v == null || isVisible(v)).map((action) { if (action == null) return const PopupMenuDivider(); return FilterGridAppBar.toMenuItem(context, action, enabled: true); }).toList(); @@ -245,6 +318,20 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { ]; } + Future _createGroup(Uri? parentGroupUri) async { + final uri = await showDialog( + context: context, + builder: (context) => CreateGroupDialog(grouping: albumGrouping, parentGroupUri: parentGroupUri), + routeSettings: const RouteSettings(name: CreateGroupDialog.routeName), + ); + if (uri == null) return; + + // wait for the dialog to hide + await Future.delayed(ADurations.dialogTransitionLoose * timeDilation); + + _pickFilter(context, AlbumGroupFilter.empty(uri)); + } + Future _createAlbum() async { final directory = await showDialog( context: context, @@ -256,7 +343,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { // wait for the dialog to hide await Future.delayed(ADurations.dialogTransitionLoose * timeDilation); - _pickAlbum(directory); + _pickStoredAlbum(context, directory); } Future _createVault() async { @@ -281,12 +368,16 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { await Future.delayed(ADurations.dialogTransitionLoose * timeDilation); await vaults.create(details); - _pickAlbum(details.path); + _pickStoredAlbum(context, details.path); } - void _pickAlbum(String directory) { + void _pickStoredAlbum(BuildContext context, String directory) { source.createStoredAlbum(directory); - final filter = StoredAlbumFilter(directory, source.getStoredAlbumDisplayName(context, directory)); - Navigator.maybeOf(context)?.pop(filter); + final displayName = source.getStoredAlbumDisplayName(context, directory); + _pickFilter(context, StoredAlbumFilter(directory, displayName)); + } + + void _pickFilter(BuildContext context, AlbumBaseFilter filter) async { + Navigator.maybeOf(context)?.pop(filter); } } diff --git a/lib/widgets/dialogs/tile_view_dialog.dart b/lib/widgets/dialogs/tile_view_dialog.dart index b0111b67c..1e1b51473 100644 --- a/lib/widgets/dialogs/tile_view_dialog.dart +++ b/lib/widgets/dialogs/tile_view_dialog.dart @@ -17,22 +17,22 @@ import 'aves_dialog.dart'; class TileViewDialog extends StatefulWidget { static const routeName = '/dialog/tile_view'; - final (S? sort, G? group, L? layout, bool reverse) initialValue; + final (S? sort, G? section, L? layout, bool reverse) initialValue; final List> sortOptions; - final List> groupOptions; + final List> sectionOptions; final List> layoutOptions; final String Function(S sort, bool reverse) sortOrder; final TileExtentController tileExtentController; - final bool Function(S? sort, G? group, L? layout)? canGroup; + final bool Function(S? sort, G? section, L? layout)? canSection; const TileViewDialog({ super.key, required this.initialValue, this.sortOptions = const [], - this.groupOptions = const [], + this.sectionOptions = const [], this.layoutOptions = const [], required this.sortOrder, - this.canGroup, + this.canSection, required this.tileExtentController, }); @@ -42,7 +42,7 @@ class TileViewDialog extends StatefulWidget { class _TileViewDialogState extends State> with SingleTickerProviderStateMixin { late S? _selectedSort; - late G? _selectedGroup; + late G? _selectedSection; late L? _selectedLayout; late bool _reverseSort; late int _columnMin, _columnMax; @@ -50,20 +50,20 @@ class _TileViewDialogState extends State> with List> get sortOptions => widget.sortOptions; - List> get groupOptions => widget.groupOptions; + List> get sectionOptions => widget.sectionOptions; List> get layoutOptions => widget.layoutOptions; TileExtentController get tileExtentController => widget.tileExtentController; - bool get canGroup => (widget.canGroup ?? (s, g, l) => true).call(_selectedSort, _selectedGroup, _selectedLayout); + bool get canSection => (widget.canSection ?? (s, g, l) => true).call(_selectedSort, _selectedSection, _selectedLayout); @override void initState() { super.initState(); final initialValue = widget.initialValue; _selectedSort = initialValue.$1; - _selectedGroup = initialValue.$2; + _selectedSection = initialValue.$2; _selectedLayout = initialValue.$3; _reverseSort = initialValue.$4; @@ -107,12 +107,12 @@ class _TileViewDialogState extends State> with switchOutCurve: Curves.easeInOutCubic, transitionBuilder: AvesTransitions.formTransitionBuilder, child: _buildSection( - show: canGroup, - icon: AIcons.group, + show: canSection, + icon: AIcons.section, title: l10n.viewDialogGroupSectionTitle, - options: groupOptions, - value: _selectedGroup, - onChanged: (v) => _selectedGroup = v, + options: sectionOptions, + value: _selectedSection, + onChanged: (v) => _selectedSection = v, ), ), _buildSection( @@ -152,7 +152,7 @@ class _TileViewDialogState extends State> with key: const Key('button-apply'), onPressed: () { tileExtentController.setUserPreferredColumnCount(_columnCountNotifier.value); - Navigator.maybeOf(context)?.pop<(S?, G?, L?, bool)>((_selectedSort, _selectedGroup, _selectedLayout, _reverseSort)); + Navigator.maybeOf(context)?.pop<(S?, G?, L?, bool)>((_selectedSort, _selectedSection, _selectedLayout, _reverseSort)); }, child: Text(l10n.applyButtonLabel), ) diff --git a/lib/widgets/explorer/app_bar.dart b/lib/widgets/explorer/app_bar.dart index b182d11c9..7ee7e14a5 100644 --- a/lib/widgets/explorer/app_bar.dart +++ b/lib/widgets/explorer/app_bar.dart @@ -10,6 +10,7 @@ import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar/app_bar_title.dart'; +import 'package:aves/widgets/common/app_bar/crumb_line.dart'; import 'package:aves/widgets/common/basic/font_size_icon_theme.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -27,12 +28,12 @@ import 'package:provider/provider.dart'; class ExplorerAppBar extends StatefulWidget { final ValueNotifier directoryNotifier; - final void Function(String path) goTo; + final void Function(VolumeRelativeDirectory? dir) goToDir; const ExplorerAppBar({ super.key, required this.directoryNotifier, - required this.goTo, + required this.goToDir, }); @override @@ -70,10 +71,10 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse child: ValueListenableBuilder( valueListenable: widget.directoryNotifier, builder: (context, directory, child) { - return CrumbLine( + return ExplorerCrumbLine( key: const Key('crumbs'), directory: directory, - onTap: widget.goTo, + onTap: widget.goToDir, ); }, ), @@ -151,7 +152,7 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse final icon = otherVolume.isRemovable ? AIcons.storageCard : AIcons.storageMain; return IconButton( icon: Icon(icon), - onPressed: () => widget.goTo(otherVolume.path), + onPressed: () => widget.goToDir(VolumeRelativeDirectory.volume(otherVolume)), tooltip: otherVolume.getDescription(context), ); }, @@ -180,7 +181,7 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse routeSettings: const RouteSettings(name: SelectStorageDialog.routeName), ); if (volume != null) { - widget.goTo(volume.path); + widget.goToDir(VolumeRelativeDirectory.volume(volume)); } }, tooltip: context.l10n.explorerActionSelectStorageVolume, diff --git a/lib/widgets/explorer/crumb_line.dart b/lib/widgets/explorer/crumb_line.dart index 6ba442830..596a626da 100644 --- a/lib/widgets/explorer/crumb_line.dart +++ b/lib/widgets/explorer/crumb_line.dart @@ -1,122 +1,46 @@ -import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/theme/icons.dart'; +import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/view/view.dart'; +import 'package:aves/widgets/common/app_bar/crumb_line.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -class CrumbLine extends StatefulWidget { +class ExplorerCrumbLine extends StatelessWidget { final VolumeRelativeDirectory? directory; - final void Function(String path) onTap; + final void Function(VolumeRelativeDirectory? combinedPath) onTap; - const CrumbLine({ + const ExplorerCrumbLine({ super.key, required this.directory, required this.onTap, }); - @override - State createState() => _CrumbLineState(); - - static double getPreferredHeight(TextScaler textScaler) => textScaler.scale(kToolbarHeight); -} - -class _CrumbLineState extends State { - final ScrollController _scrollController = ScrollController(); - - VolumeRelativeDirectory? get directory => widget.directory; - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - void didUpdateWidget(covariant CrumbLine oldWidget) { - super.didUpdateWidget(oldWidget); - if ((oldWidget.directory?.relativeDir.length ?? 0) < (widget.directory?.relativeDir.length ?? 0)) { - // scroll to show last crumb - WidgetsBinding.instance.addPostFrameCallback((_) { - final animate = context.read().animate; - final extent = _scrollController.position.maxScrollExtent; - if (animate) { - _scrollController.animateTo( - extent, - duration: const Duration(milliseconds: 500), - curve: Curves.easeOutQuad, - ); - } else { - _scrollController.jumpTo(extent); - } - }); - } - } - @override Widget build(BuildContext context) { - final _directory = directory; - final parts = []; - if (_directory != null) { - parts.addAll([ - _directory.getVolumeDescription(context), - ...pContext.split(_directory.relativeDir), - ]); - } - - final crumbColor = DefaultTextStyle.of(context).style.color; - return ListView.builder( - scrollDirection: Axis.horizontal, - controller: _scrollController, - padding: const EdgeInsets.symmetric(horizontal: 8), - itemBuilder: (context, index) { - Widget _buildText(String text) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text(text), - ); - - if (index >= parts.length) return const SizedBox(); - final text = parts[index]; - if (index == parts.length - 1) { - return Center( - child: DefaultTextStyle.merge( - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - child: _buildText(text), - ), - ); - } - return GestureDetector( - onTap: _directory != null - ? () { - final path = pContext.joinAll([ - _directory.volumePath, - ...parts.skip(1).take(index), - ]); - widget.onTap(path); - } - : null, - child: Container( - // use a `Container` with a dummy color to make it expand - // so that we can also detect taps around the title `Text` - color: Colors.transparent, - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _buildText(text), - Icon( - AIcons.next, - color: crumbColor, - ), - ], - ), - ), - ); - }, - itemCount: parts.length, + return CrumbLine( + split: _split, + combine: _combine, + onTap: onTap, ); } + + List _split(BuildContext context) { + final _directory = directory; + if (_directory == null) return []; + return [ + _directory.getVolumeDescription(context), + ...pContext.split(_directory.relativeDir), + ]; + } + + VolumeRelativeDirectory? _combine(BuildContext context, int index) { + final _directory = directory; + if (_directory == null) return null; + + final path = pContext.joinAll([ + _directory.volumePath, + ..._split(context).skip(1).take(index), + ]); + return androidFileUtils.relativeDirectoryFromPath(path); + } } diff --git a/lib/widgets/explorer/explorer_page.dart b/lib/widgets/explorer/explorer_page.dart index a0748d8ea..cadf2e5f5 100644 --- a/lib/widgets/explorer/explorer_page.dart +++ b/lib/widgets/explorer/explorer_page.dart @@ -54,11 +54,11 @@ class _ExplorerPageState extends State { super.initState(); final path = widget.path; if (path != null && androidFileUtils.getStorageVolume(path) != null) { - _goTo(path); + _goToPath(path); } else { final primaryVolume = _volumes.firstWhereOrNull((v) => v.isPrimary); if (primaryVolume != null) { - _goTo(primaryVolume.path); + _goToPath(primaryVolume.path); } } _contents.addListener(() => PrimaryScrollController.of(context).jumpTo(0)); @@ -91,7 +91,7 @@ class _ExplorerPageState extends State { canPop: (context) => atRoot, onPopBlocked: (context) { if (path != null) { - _goTo(pContext.dirname(path)); + _goToPath(pContext.dirname(path)); } }, ), @@ -116,7 +116,7 @@ class _ExplorerPageState extends State { ExplorerAppBar( key: const Key('appbar'), directoryNotifier: _directory, - goTo: _goTo, + goToDir: _goToDir, ), AnimationLimiter( // animation limiter should not be above the app bar @@ -250,18 +250,19 @@ class _ExplorerPageState extends State { child: Icon(AIcons.folder), ), title: Text('${Unicode.FSI}${pContext.split(content.path).last}${Unicode.PDI}'), - onTap: () => _goTo(content.path), + onTap: () => _goToPath(content.path), ); } - void _goTo(String path) { - final dir = androidFileUtils.relativeDirectoryFromPath(path); + void _goToDir(VolumeRelativeDirectory? dir) { if (dir != null) { _directory.value = dir; _updateContents(); } } + void _goToPath(String path) => _goToDir(androidFileUtils.relativeDirectoryFromPath(path)); + void _updateContents() { final directory = _directory.value; final dirPath = _pathOf(directory); diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index 50d8949a1..f6b3c66d2 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -2,16 +2,18 @@ import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/entry/extensions/props.dart'; -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; +import 'package:aves/widgets/common/providers/filter_group_provider.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/album_set.dart'; import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart'; import 'package:aves/widgets/filter_grids/common/section_keys.dart'; @@ -20,68 +22,122 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +enum AlbumChipType { stored, dynamic, group } + class AlbumListPage extends StatelessWidget { static const routeName = '/albums'; - const AlbumListPage({super.key}); + final Uri? initialGroup; + + const AlbumListPage({ + super.key, + required this.initialGroup, + }); @override Widget build(BuildContext context) { - final source = context.read(); - return Selector)>( - selector: (context, s) => (s.albumGroupFactor, s.albumSortFactor, s.albumSortReverse, s.pinnedFilters), - shouldRebuild: (t1, t2) { - // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within records - const eq = DeepCollectionEquality(); - return !(eq.equals(t1.$1, t2.$1) && eq.equals(t1.$2, t2.$2) && eq.equals(t1.$3, t2.$3) && eq.equals(t1.$4, t2.$4)); - }, - builder: (context, s, child) { - return ValueListenableBuilder( - valueListenable: appInventory.areAppNamesReadyNotifier, - builder: (context, areAppNamesReady, child) { - return AnimatedBuilder( - animation: dynamicAlbums, - builder: (context, child) => StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - final gridItems = getAlbumGridItems(context, source); - return StreamBuilder?>( - // to update sections by tier - stream: covers.packageChangeStream, - builder: (context, snapshot) => FilterNavigationPage( - source: source, - title: context.l10n.albumPageTitle, - sortFactor: settings.albumSortFactor, - showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none, - actionDelegate: AlbumChipSetActionDelegate(gridItems), - filterSections: groupToSections(context, source, gridItems), - newFilters: source.getNewAlbumFilters(context), - applyQuery: applyQuery, - emptyBuilder: () => EmptyContent( - icon: AIcons.album, - text: context.l10n.albumEmpty, - ), + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: albumGrouping), + FilterGroupProvider(initialValue: initialGroup), + ], + child: Builder( + // to access filter group provider from subtree context + builder: (context) { + return Selector)>( + selector: (context, s) => (s.albumSectionFactor, s.albumSortFactor, s.albumSortReverse, s.pinnedFilters), + shouldRebuild: (t1, t2) { + // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within records + const eq = DeepCollectionEquality(); + return !(eq.equals(t1.$1, t2.$1) && eq.equals(t1.$2, t2.$2) && eq.equals(t1.$3, t2.$3) && eq.equals(t1.$4, t2.$4)); + }, + builder: (context, s, child) { + return ValueListenableBuilder( + valueListenable: appInventory.areAppNamesReadyNotifier, + builder: (context, areAppNamesReady, child) { + return AnimatedBuilder( + animation: Listenable.merge({albumGrouping, dynamicAlbums}), + builder: (context, child) => StreamBuilder( + stream: context.read().eventBus.on(), + builder: (context, snapshot) { + final source = context.read(); + final groupUri = context.watch().value; + final gridItems = AlbumListPage.getAlbumGridItems(context, source, AlbumChipType.values, groupUri); + return StreamBuilder?>( + // to update sections by tier + stream: covers.packageChangeStream, + builder: (context, snapshot) => FilterNavigationPage( + source: source, + title: context.l10n.albumPageTitle, + sortFactor: settings.albumSortFactor, + showHeaders: settings.albumSectionFactor != AlbumChipSectionFactor.none, + actionDelegate: AlbumChipSetActionDelegate(gridItems), + filterSections: AlbumListPage.groupToSections(context, source, gridItems), + newFilters: source.getNewAlbumFilters(context), + emptyBuilder: () => EmptyContent( + icon: AIcons.album, + text: context.l10n.albumEmpty, + ), + ), + ); + }, ), ); }, - ), - ); - }, - ); - }, + ); + }, + ); + }, + ), ); } // common with album selection page to move/copy entries - static List> applyQuery(BuildContext context, List> filters, String query) { - return filters.where((item) => item.filter.match(query)).toList(); - } + static List> getAlbumGridItems( + BuildContext context, + CollectionSource source, + Iterable albumTypes, + Uri? groupUri, + ) { + final groupContent = albumGrouping.getDirectChildren(groupUri); + + Set whereTypeRecursively(Set filters) { + return { + ...filters.whereType(), + ...filters.whereType().expand((v) => whereTypeRecursively(v.filter.innerFilters)), + }; + } + + final listedStoredAlbums = {}; + if (albumTypes.contains(AlbumChipType.stored)) { + if (groupUri == null) { + final withinGroups = whereTypeRecursively(groupContent).map((v) => v.album).toSet(); + listedStoredAlbums.addAll(source.rawAlbums.whereNot(withinGroups.contains)); + } else { + listedStoredAlbums.addAll(groupContent.whereType().map((v) => v.album)); + } + } + + final listedDynamicAlbums = {}; + if (albumTypes.contains(AlbumChipType.dynamic)) { + if (groupUri == null) { + final withinGroups = whereTypeRecursively(groupContent).toSet(); + listedDynamicAlbums.addAll(dynamicAlbums.all.whereNot(withinGroups.contains)); + } else { + listedDynamicAlbums.addAll(groupContent.whereType()); + } + } + + final albumGroupFilters = {}; + if (albumTypes.contains(AlbumChipType.group)) { + albumGroupFilters.addAll(groupContent.whereType()); + } - static List> getAlbumGridItems(BuildContext context, CollectionSource source, {bool storedAlbumsOnly = false}) { final filters = { - ...source.rawAlbums.map((album) => StoredAlbumFilter(album, source.getStoredAlbumDisplayName(context, album))), - if (!storedAlbumsOnly) ...dynamicAlbums.all, + ...albumGroupFilters, + ...listedStoredAlbums.map((album) => StoredAlbumFilter(album, source.getStoredAlbumDisplayName(context, album))), + ...listedDynamicAlbums, }; return FilterNavigationPage.sort(settings.albumSortFactor, settings.albumSortReverse, source, filters); @@ -104,8 +160,9 @@ class AlbumListPage extends StatelessWidget { } var sections = >>{}; - switch (settings.albumGroupFactor) { - case AlbumChipGroupFactor.importance: + switch (settings.albumSectionFactor) { + case AlbumChipSectionFactor.importance: + final groupKey = AlbumImportanceSectionKey.group(context); final specialKey = AlbumImportanceSectionKey.special(context); final appsKey = AlbumImportanceSectionKey.apps(context); final vaultKey = AlbumImportanceSectionKey.vault(context); @@ -113,32 +170,36 @@ class AlbumListPage extends StatelessWidget { final dynamicKey = AlbumImportanceSectionKey.dynamic(context); sections = groupBy, ChipSectionKey>(unpinnedMapEntries, (kv) { final filter = kv.filter; - if (filter is StoredAlbumFilter) { - switch (covers.effectiveAlbumType(filter.album)) { - case AlbumType.regular: - return regularKey; - case AlbumType.app: - return appsKey; - case AlbumType.vault: - return vaultKey; - default: - return specialKey; - } - } else if (filter is DynamicAlbumFilter) { - return dynamicKey; + switch (filter) { + case StoredAlbumFilter _: + switch (covers.effectiveAlbumType(filter.album)) { + case AlbumType.regular: + return regularKey; + case AlbumType.app: + return appsKey; + case AlbumType.vault: + return vaultKey; + default: + return specialKey; + } + case DynamicAlbumFilter _: + return dynamicKey; + case AlbumGroupFilter _: + return groupKey; } return specialKey; }); sections = { // group ordering + if (sections.containsKey(groupKey)) groupKey: sections[groupKey]!, if (sections.containsKey(specialKey)) specialKey: sections[specialKey]!, if (sections.containsKey(appsKey)) appsKey: sections[appsKey]!, if (sections.containsKey(vaultKey)) vaultKey: sections[vaultKey]!, if (sections.containsKey(dynamicKey)) dynamicKey: sections[dynamicKey]!, if (sections.containsKey(regularKey)) regularKey: sections[regularKey]!, }; - case AlbumChipGroupFactor.mimeType: + case AlbumChipSectionFactor.mimeType: final visibleEntries = source.visibleEntries; sections = groupBy, ChipSectionKey>(unpinnedMapEntries, (kv) { final matches = visibleEntries.where(kv.filter.test); @@ -148,11 +209,12 @@ class AlbumListPage extends StatelessWidget { if (!hasImage && hasVideo) return MimeTypeSectionKey.videos(context); return MimeTypeSectionKey.mixed(context); }); - case AlbumChipGroupFactor.volume: + case AlbumChipSectionFactor.volume: sections = groupBy, ChipSectionKey>(unpinnedMapEntries, (kv) { - return StorageVolumeSectionKey(context, kv.filter.storageVolume); + final filter = kv.filter; + return StorageVolumeSectionKey(context, filter is StoredAlbumFilter ? filter.storageVolume : null); }); - case AlbumChipGroupFactor.none: + case AlbumChipSectionFactor.none: return { if (sortedMapEntries.isNotEmpty) const ChipSectionKey(): [ diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index 930d0ba96..71983fb3b 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -1,13 +1,16 @@ +import 'dart:collection'; import 'dart:io'; import 'package:aves/app_mode.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; +import 'package:aves/model/grouping/convert.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; @@ -23,18 +26,22 @@ import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/action_mixins/entry_storage.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/providers/filter_group_provider.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/filter_editors/create_stored_album_dialog.dart'; import 'package:aves/widgets/dialogs/filter_editors/edit_vault_dialog.dart'; import 'package:aves/widgets/dialogs/filter_editors/rename_dynamic_album_dialog.dart'; +import 'package:aves/widgets/dialogs/filter_editors/rename_group_dialog.dart'; import 'package:aves/widgets/dialogs/filter_editors/rename_stored_album_dialog.dart'; +import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart'; import 'package:aves/widgets/dialogs/tile_view_dialog.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; @@ -65,11 +72,11 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate @override set tileLayout(TileLayout tileLayout) => settings.setTileLayout(AlbumListPage.routeName, tileLayout); - static const _groupOptions = [ - AlbumChipGroupFactor.importance, - AlbumChipGroupFactor.mimeType, - AlbumChipGroupFactor.volume, - AlbumChipGroupFactor.none, + static const _sectionOptions = [ + AlbumChipSectionFactor.importance, + AlbumChipSectionFactor.mimeType, + AlbumChipSectionFactor.volume, + AlbumChipSectionFactor.none, ]; @override @@ -82,11 +89,16 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate }) { final selectedSingleItem = selectedFilters.length == 1; final isMain = appMode == AppMode.main; + bool isVault(CollectionFilter filter) => filter is StoredAlbumFilter && filter.isVault; switch (action) { + case ChipSetAction.createGroup: + return true; case ChipSetAction.createAlbum: case ChipSetAction.createVault: return !settings.isReadOnly && appMode.canCreateFilter && !isSelecting; + case ChipSetAction.group: + return isMain && isSelecting; case ChipSetAction.delete: return isMain && isSelecting && !settings.isReadOnly && !(selectedFilters.whereType().isEmpty && selectedFilters.whereType().isNotEmpty); case ChipSetAction.remove: @@ -94,11 +106,11 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate case ChipSetAction.rename: return isMain && isSelecting && !settings.isReadOnly; case ChipSetAction.hide: - return isMain && selectedFilters.none((v) => v.isVault); + return isMain && selectedFilters.none(isVault); case ChipSetAction.configureVault: - return isMain && selectedSingleItem && selectedFilters.first.isVault; + return isMain && selectedSingleItem && isVault(selectedFilters.first); case ChipSetAction.lockVault: - return isMain && selectedFilters.any((v) => v.isVault); + return isMain && selectedFilters.any(isVault); default: return super.isVisible( action, @@ -124,7 +136,10 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate case ChipSetAction.delete: return selectedFilters.whereType().isNotEmpty && selectedFilters.whereType().isEmpty; case ChipSetAction.rename: - return selectedFilters.length == 1 && selectedFilters.first.canRename; + if (selectedFilters.length != 1) return false; + final filter = selectedFilters.first; + if (filter is StoredAlbumFilter) return filter.canRename; + return true; case ChipSetAction.hide: return hasSelection; case ChipSetAction.lockVault: @@ -155,6 +170,8 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate _deleteStoredAlbums(context); case ChipSetAction.remove: _removeDynamicAlbum(context); + case ChipSetAction.group: + _group(context); case ChipSetAction.lockVault: lockFilters(_getSelectedStoredAlbumFilters(context)); browse(context); @@ -181,18 +198,18 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate Future configureView(BuildContext context) async { final initialValue = ( sortFactor, - settings.albumGroupFactor, + settings.albumSectionFactor, tileLayout, sortReverse, ); final extentController = context.read(); - final value = await showDialog<(ChipSortFactor?, AlbumChipGroupFactor?, TileLayout?, bool)>( + final value = await showDialog<(ChipSortFactor?, AlbumChipSectionFactor?, TileLayout?, bool)>( context: context, builder: (context) { - return TileViewDialog( + return TileViewDialog( initialValue: initialValue, sortOptions: ChipSetActionDelegate.albumSortOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), - groupOptions: _groupOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), + sectionOptions: _sectionOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), layoutOptions: ChipSetActionDelegate.layoutOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(), sortOrder: (factor, reverse) => factor.getOrderName(context, reverse), tileExtentController: extentController, @@ -204,7 +221,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate await Future.delayed(ADurations.dialogTransitionLoose * timeDilation); if (value != null && initialValue != value) { sortFactor = value.$1!; - settings.albumGroupFactor = value.$2!; + settings.albumSectionFactor = value.$2!; tileLayout = value.$3!; sortReverse = value.$4; } @@ -276,10 +293,11 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate highlightInfo.trackItem(FilterGridItem(filter, null), highlightItem: filter); } else { highlightInfo.set(filter); + final initialGroup = albumGrouping.getFilterParent(filter); await navigator.pushAndRemoveUntil( MaterialPageRoute( settings: const RouteSettings(name: AlbumListPage.routeName), - builder: (_) => const AlbumListPage(), + builder: (_) => AlbumListPage(initialGroup: initialGroup), ), (route) => false, ); @@ -434,6 +452,28 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate browse(context); } + Future _group(BuildContext context) async { + final filters = getSelectedFilters(context); + final childrenUris = filters.map(GroupingConversion.filterToUri).nonNulls.toSet(); + + final initialGroup = albumGrouping.getFilterParent(filters.first); + final filter = await pickAlbum( + context: context, + moveType: null, + albumTypes: {AlbumChipType.group}, + initialGroup: initialGroup, + ); + if (filter == null) return; + + final destinationGroupUri = filter is AlbumGroupFilter ? filter.uri : null; + albumGrouping.addToGroup(childrenUris, destinationGroupUri); + context.read().value = destinationGroupUri; + + final source = context.read(); + source.invalidateAlbumGroupFilterSummary(notify: true); + browse(context); + } + Future _rename(BuildContext context) async { final filters = getSelectedFilters(context); if (filters.isEmpty) return; @@ -469,25 +509,41 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate final newName = await showDialog( context: context, builder: (context) => RenameDynamicAlbumDialog(name: filter.name), - routeSettings: const RouteSettings(name: RenameStoredAlbumDialog.routeName), + routeSettings: const RouteSettings(name: RenameDynamicAlbumDialog.routeName), ); if (newName == null || newName.isEmpty) return; await _doRenameDynamicAlbum(context, filter, newName); + } else if (filter is AlbumGroupFilter) { + final newGroupUri = await showDialog( + context: context, + builder: (context) => RenameGroupDialog(grouping: albumGrouping, groupUri: filter.uri), + routeSettings: const RouteSettings(name: RenameGroupDialog.routeName), + ); + if (newGroupUri == null) return; + + await _doRenameAlbumGroup(context, filter, newGroupUri); } } - Future _doRenameDynamicAlbum(BuildContext context, DynamicAlbumFilter albumFilter, String newName) async { - final oldName = albumFilter.name; - + Future _doRenameNonStoredAlbum( + BuildContext context, + T oldFilter, + Future Function() createNewFilter, + bool Function(T filter) isRenamed, + ) async { // save cover and bookmark before renaming - final cover = await covers.remove(albumFilter, notify: false); + final cover = await covers.remove(oldFilter, notify: false); final bookmarks = settings.drawerAlbumBookmarks; final pinnedFilters = settings.pinnedFilters; - await dynamicAlbums.rename(albumFilter, newName); - final newFilter = DynamicAlbumFilter(newName, albumFilter.filter); - bool isRenamed(CollectionFilter v) => v is DynamicAlbumFilter && v.name == oldName; + // new filter to match new name + final newFilter = await createNewFilter(); + if (newFilter == null) { + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); + return; + } + bool _isRenamed(CollectionFilter? v) => v is T && isRenamed(v); // update cover if (cover != null) { @@ -499,14 +555,16 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate notify: true, ); } + // update drawer bookmark - final bookmark = bookmarks?.firstWhereOrNull(isRenamed); + final bookmark = bookmarks?.firstWhereOrNull(_isRenamed); if (bookmark != null) { bookmarks?.replace(bookmark, newFilter); settings.drawerAlbumBookmarks = bookmarks; } + // update pin - final pin = pinnedFilters.firstWhereOrNull(isRenamed); + final pin = pinnedFilters.firstWhereOrNull(_isRenamed); if (pin != null) { pinnedFilters.replace(pin, newFilter); settings.pinnedFilters = pinnedFilters; @@ -515,6 +573,23 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate browse(context); } + Future _doRenameAlbumGroup(BuildContext context, AlbumGroupFilter oldFilter, Uri newUri) async { + final oldUri = oldFilter.uri; + await _doRenameNonStoredAlbum(context, oldFilter, () { + albumGrouping.rename(oldUri, newUri); + final newFilter = albumGrouping.uriToFilter(newUri); + return SynchronousFuture(newFilter is AlbumGroupFilter ? newFilter : null); + }, (filter) => filter.uri == oldUri); + } + + Future _doRenameDynamicAlbum(BuildContext context, DynamicAlbumFilter oldFilter, String newName) async { + final oldName = oldFilter.name; + await _doRenameNonStoredAlbum(context, oldFilter, () async { + await dynamicAlbums.rename(oldFilter, newName); + return DynamicAlbumFilter(newName, oldFilter.filter); + }, (filter) => filter.name == oldName); + } + Future _doRenameStoredAlbum(BuildContext context, StoredAlbumFilter albumFilter, String newName) async { final l10n = context.l10n; final messenger = ScaffoldMessenger.of(context); diff --git a/lib/widgets/filter_grids/common/action_delegates/chip.dart b/lib/widgets/filter_grids/common/action_delegates/chip.dart index 4f142b9b2..c04d4f80d 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; @@ -6,6 +6,7 @@ import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/rating.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/vaults/vaults.dart'; @@ -59,7 +60,8 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin { reportService.log('$runtimeType handles $action'); switch (action) { case ChipAction.goToAlbumPage: - _goTo(context, filter, AlbumListPage.routeName, (context) => const AlbumListPage()); + final initialGroup = albumGrouping.getFilterParent(filter); + _goTo(context, filter, AlbumListPage.routeName, (context) => AlbumListPage(initialGroup: initialGroup)); case ChipAction.goToCountryPage: _goTo(context, filter, CountryListPage.routeName, (context) => const CountryListPage()); case ChipAction.goToPlacePage: 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 b114d3b5b..d71011946 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -94,6 +94,7 @@ abstract class ChipSetActionDelegate with FeedbackMi return !useTvLayout && appMode.canNavigate && !isSelecting; case ChipSetAction.toggleTitleSearch: return !useTvLayout && !isSelecting; + case ChipSetAction.createGroup: case ChipSetAction.createAlbum: case ChipSetAction.createVault: return false; @@ -113,6 +114,7 @@ abstract class ChipSetActionDelegate with FeedbackMi return appMode.canNavigate; case ChipSetAction.delete: case ChipSetAction.remove: + case ChipSetAction.group: case ChipSetAction.lockVault: case ChipSetAction.showCountryStates: return false; @@ -144,6 +146,7 @@ abstract class ChipSetActionDelegate with FeedbackMi // browsing case ChipSetAction.search: case ChipSetAction.toggleTitleSearch: + case ChipSetAction.createGroup: case ChipSetAction.createAlbum: case ChipSetAction.createVault: return true; @@ -158,6 +161,7 @@ abstract class ChipSetActionDelegate with FeedbackMi case ChipSetAction.hide: case ChipSetAction.pin: case ChipSetAction.unpin: + case ChipSetAction.group: case ChipSetAction.lockVault: case ChipSetAction.showCountryStates: case ChipSetAction.showCollection: @@ -189,6 +193,7 @@ abstract class ChipSetActionDelegate with FeedbackMi final routeName = context.currentRouteName!; settings.setShowTitleQuery(routeName, !settings.getShowTitleQuery(routeName)); context.read().toggle(); + case ChipSetAction.createGroup: case ChipSetAction.createAlbum: case ChipSetAction.createVault: break; @@ -212,6 +217,7 @@ abstract class ChipSetActionDelegate with FeedbackMi _goToCollection(context); case ChipSetAction.delete: case ChipSetAction.remove: + case ChipSetAction.group: case ChipSetAction.lockVault: case ChipSetAction.showCountryStates: break; diff --git a/lib/widgets/filter_grids/common/action_delegates/country_set.dart b/lib/widgets/filter_grids/common/action_delegates/country_set.dart index 85aaa3b87..8aae1fcf1 100644 --- a/lib/widgets/filter_grids/common/action_delegates/country_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/country_set.dart @@ -1,7 +1,7 @@ import 'package:aves/app_mode.dart'; import 'package:aves/geo/states.dart'; -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/place_set.dart b/lib/widgets/filter_grids/common/action_delegates/place_set.dart index 53ebf9a61..3d80882b4 100644 --- a/lib/widgets/filter_grids/common/action_delegates/place_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/place_set.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves/widgets/filter_grids/places_page.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/state_set.dart b/lib/widgets/filter_grids/common/action_delegates/state_set.dart index 4cd2e5cbc..469c655c6 100644 --- a/lib/widgets/filter_grids/common/action_delegates/state_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/state_set.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves/widgets/filter_grids/states_page.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/tag_set.dart b/lib/widgets/filter_grids/common/action_delegates/tag_set.dart index 0ee0e31b6..b221b668d 100644 --- a/lib/widgets/filter_grids/common/action_delegates/tag_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/tag_set.dart @@ -1,6 +1,6 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/tag.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index 07bb607f2..322e8fe1d 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; @@ -13,12 +14,15 @@ import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/action_controls/togglers/title_search.dart'; import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar/app_bar_title.dart'; +import 'package:aves/widgets/common/app_bar/crumb_line.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_app_bar.dart'; import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; +import 'package:aves/widgets/common/providers/filter_group_provider.dart'; import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; +import 'package:aves/widgets/filter_grids/common/group_crumb_line.dart'; import 'package:aves/widgets/filter_grids/common/query_bar.dart'; import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves_model/aves_model.dart'; @@ -163,6 +167,25 @@ class _FilterGridAppBarState( + selector: (context, notifier) => notifier.value, + builder: (context, groupUri, child) { + return FilterGroupCrumbLine( + key: const Key('crumbs'), + groupUri: groupUri, + onTap: (uri) => context.read()?.value = uri, + ); + }, + ), + ); + }, + ), if (queryEnabled) FilterQueryBar( queryNotifier: context.select>((query) => query.queryNotifier), @@ -177,12 +200,17 @@ class _FilterGridAppBarState context.read()?.isNotEmpty ?? false; + double get appBarContentHeight { final textScaler = MediaQuery.textScalerOf(context); double height = textScaler.scale(kToolbarHeight); if (settings.useTvLayout) { height += CaptionedButton.getTelevisionButtonHeight(context); } + if (_showGroupCrumbLine(context)) { + height += CrumbLine.getPreferredHeight(textScaler); + } if (context.read().enabled) { height += FilterQueryBar.getPreferredHeight(textScaler); } diff --git a/lib/widgets/filter_grids/common/covered_filter_chip.dart b/lib/widgets/filter_grids/common/covered_filter_chip.dart index 18b6822e7..fe9a6132f 100644 --- a/lib/widgets/filter_grids/common/covered_filter_chip.dart +++ b/lib/widgets/filter_grids/common/covered_filter_chip.dart @@ -2,11 +2,13 @@ import 'dart:math'; import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/covers.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location/country.dart'; @@ -85,6 +87,13 @@ class CoveredFilterChip extends StatelessWidget { builder: (context, snapshot) => _buildChip(context, source), ); } + case AlbumGroupFilter _: + { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) => _buildChip(context, source), + ); + } case LocationFilter filter: { final countryCode = filter.code; @@ -180,6 +189,10 @@ class CoveredFilterChip extends StatelessWidget { Color _detailColor(BuildContext context) => Theme.of(context).colorScheme.onSurfaceVariant; Widget _buildDetails(BuildContext context, CollectionSource source, T filter) { + final textStyle = TextStyle( + color: _detailColor(context), + fontSize: detailFontSize(extent), + ); return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -187,12 +200,16 @@ class CoveredFilterChip extends StatelessWidget { if (filter is StoredAlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album)) _buildDetailIcon(context, AIcons.storageCard), if (filter is StoredAlbumFilter && vaults.isVault(filter.album)) _buildDetailIcon(context, AIcons.locked), if (filter is DynamicAlbumFilter) _buildDetailIcon(context, AIcons.dynamicAlbum), + if (filter is AlbumGroupFilter) ...[ + _buildDetailIcon(context, AIcons.album), + Text( + '${NumberFormat.decimalPattern(context.locale).format(albumGrouping.countLeaves(filter.uri))}${AText.separator}', + style: textStyle, + ), + ], Text( locked ? AText.valueNotAvailable : NumberFormat.decimalPattern(context.locale).format(source.count(filter)), - style: TextStyle( - color: _detailColor(context), - fontSize: detailFontSize(extent), - ), + style: textStyle, ), ], ); diff --git a/lib/widgets/filter_grids/common/enums.dart b/lib/widgets/filter_grids/common/enums.dart index f84d77168..a022d5c9c 100644 --- a/lib/widgets/filter_grids/common/enums.dart +++ b/lib/widgets/filter_grids/common/enums.dart @@ -2,7 +2,7 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/material.dart'; -enum AlbumImportance { newAlbum, pinned, special, apps, vaults, dynamic, regular } +enum AlbumImportance { newAlbum, pinned, group, special, apps, vaults, dynamic, regular } extension ExtraAlbumImportance on AlbumImportance { String getText(BuildContext context) { @@ -10,6 +10,7 @@ extension ExtraAlbumImportance on AlbumImportance { return switch (this) { AlbumImportance.newAlbum => l10n.albumTierNew, AlbumImportance.pinned => l10n.albumTierPinned, + AlbumImportance.group => l10n.albumTierGroups, AlbumImportance.special => l10n.albumTierSpecial, AlbumImportance.apps => l10n.albumTierApps, AlbumImportance.vaults => l10n.albumTierVaults, @@ -22,6 +23,7 @@ extension ExtraAlbumImportance on AlbumImportance { return switch (this) { AlbumImportance.newAlbum => AIcons.newTier, AlbumImportance.pinned => AIcons.pin, + AlbumImportance.group => AIcons.group, AlbumImportance.special => AIcons.important, AlbumImportance.apps => AIcons.app, AlbumImportance.vaults => AIcons.locked, diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 7efb3e432..22b1dc747 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; @@ -28,6 +29,7 @@ import 'package:aves/widgets/common/grid/sliver.dart'; import 'package:aves/widgets/common/grid/theme.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/scroll_thumb.dart'; +import 'package:aves/widgets/common/providers/filter_group_provider.dart'; import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart'; @@ -56,9 +58,10 @@ class FilterGridPage extends StatelessWidget { final Set newFilters; final ChipSortFactor sortFactor; final bool showHeaders, selectable; - final QueryTest applyQuery; final Widget Function() emptyBuilder; final HeroType heroType; + final Widget? floatingActionButton; + final FilterTileTapCallback onTileTap; final StreamController _draggableScrollBarEventStreamController = StreamController.broadcast(); FilterGridPage({ @@ -71,9 +74,10 @@ class FilterGridPage extends StatelessWidget { required this.sortFactor, required this.showHeaders, required this.selectable, - required this.applyQuery, required this.emptyBuilder, required this.heroType, + this.floatingActionButton, + required this.onTileTap, }); @override @@ -98,9 +102,9 @@ class FilterGridPage extends StatelessWidget { sortFactor: sortFactor, showHeaders: showHeaders, selectable: selectable, - applyQuery: applyQuery, emptyBuilder: emptyBuilder, heroType: heroType, + onTileTap: onTileTap, ); }, ), @@ -142,6 +146,7 @@ class FilterGridPage extends StatelessWidget { }, child: AvesScaffold( body: body, + floatingActionButton: floatingActionButton, drawer: canNavigate ? const AppDrawer() : null, bottomNavigationBar: showBottomNavigationBar ? AppBottomNavBar( @@ -166,9 +171,9 @@ class _FilterGrid extends StatefulWidget { final Set newFilters; final ChipSortFactor sortFactor; final bool showHeaders, selectable; - final QueryTest applyQuery; final Widget Function() emptyBuilder; final HeroType heroType; + final FilterTileTapCallback onTileTap; const _FilterGrid({ super.key, @@ -180,9 +185,9 @@ class _FilterGrid extends StatefulWidget { required this.sortFactor, required this.showHeaders, required this.selectable, - required this.applyQuery, required this.emptyBuilder, required this.heroType, + required this.onTileTap, }); @override @@ -214,6 +219,13 @@ class _FilterGridState extends State<_FilterGrid> canPop: (context) => context.select>, bool>((v) => !v.isSelecting), onPopBlocked: (context) => context.read>>().browse(), ), + APopHandler( + canPop: (context) => context.read()?.value == null, + onPopBlocked: (context) { + final filterGroupNotifier = context.read(); + filterGroupNotifier.value = FilterGrouping.getParentGroup(filterGroupNotifier.value); + }, + ), tvNavigationPopHandler, doubleBackPopHandler, ], @@ -227,9 +239,9 @@ class _FilterGridState extends State<_FilterGrid> sortFactor: widget.sortFactor, showHeaders: widget.showHeaders, selectable: widget.selectable, - applyQuery: widget.applyQuery, emptyBuilder: widget.emptyBuilder, heroType: widget.heroType, + onTileTap: widget.onTileTap, ), ), ); @@ -244,8 +256,8 @@ class _FilterGridContent extends StatefulWidget { final ChipSortFactor sortFactor; final bool showHeaders, selectable; final Widget Function() emptyBuilder; - final QueryTest applyQuery; final HeroType heroType; + final FilterTileTapCallback onTileTap; const _FilterGridContent({ super.key, @@ -256,9 +268,9 @@ class _FilterGridContent extends StatefulWidget { required this.sortFactor, required this.showHeaders, required this.selectable, - required this.applyQuery, required this.emptyBuilder, required this.heroType, + required this.onTileTap, }); @override @@ -296,8 +308,9 @@ class _FilterGridContentState extends State<_FilterG Map>> visibleSections; if (queryEnabled && query.isNotEmpty) { visibleSections = {}; + final queryUp = query.toUpperCase(); widget.sections.forEach((sectionKey, sectionFilters) { - final visibleFilters = widget.applyQuery(context, sectionFilters, query.toUpperCase()); + final visibleFilters = sectionFilters.where((item) => item.filter.match(context, queryUp)).toList(); if (visibleFilters.isNotEmpty) { visibleSections[sectionKey] = visibleFilters; } @@ -350,6 +363,7 @@ class _FilterGridContentState extends State<_FilterG tileLayout: tileLayout, banner: _getFilterBanner(context, gridItem.filter), heroType: widget.heroType, + onTap: widget.onTileTap, ); if (!settings.useTvLayout) return tile; @@ -488,15 +502,13 @@ class _FilterSectionedContentState extends State<_Fi @override Widget build(BuildContext context) { - final scrollView = AnimationLimiter( - child: _FilterScrollView( - scrollableKey: scrollableKey, - appBar: appBar, - appBarHeightNotifier: appBarHeightNotifier, - sortFactor: widget.sortFactor, - emptyBuilder: emptyBuilder, - scrollController: scrollController, - ), + final scrollView = _FilterScrollView( + scrollableKey: scrollableKey, + appBar: appBar, + appBarHeightNotifier: appBarHeightNotifier, + sortFactor: widget.sortFactor, + emptyBuilder: emptyBuilder, + scrollController: scrollController, ); final scaler = _FilterScaler( @@ -676,16 +688,21 @@ class _FilterScrollView extends StatelessWidget { controller: scrollController, slivers: [ appBar, - Selector>, bool>( + AnimationLimiter( + key: ValueKey(context.select((v) => v?.value)), + child: Selector>, bool>( selector: (context, layout) => layout.sections.isEmpty, builder: (context, empty, child) { return empty + // TODO TLAD [nested] prevent scrolling when empty (cf CollectionPage) ? SliverFillRemaining( hasScrollBody: false, child: emptyBuilder(), ) : SectionedListSliver>(); - }), + }, + ), + ), const NavBarPaddingSliver(), const BottomPaddingSliver(), const TvTileGridBottomPaddingSliver(), diff --git a/lib/widgets/filter_grids/common/filter_nav_page.dart b/lib/widgets/filter_grids/common/filter_nav_page.dart index 0a0c8daaa..8784a1384 100644 --- a/lib/widgets/filter_grids/common/filter_nav_page.dart +++ b/lib/widgets/filter_grids/common/filter_nav_page.dart @@ -1,11 +1,16 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/time_utils.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; +import 'package:aves/widgets/common/action_mixins/vault_aware.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/providers/filter_group_provider.dart'; import 'package:aves/widgets/common/providers/query_provider.dart'; import 'package:aves/widgets/common/providers/selection_provider.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; @@ -14,6 +19,7 @@ import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart'; import 'package:aves/widgets/filter_grids/common/section_keys.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class FilterNavigationPage> extends StatefulWidget { final CollectionSource source; @@ -23,7 +29,6 @@ class FilterNavigationPage>> filterSections; final Set? newFilters; - final QueryTest applyQuery; final Widget Function() emptyBuilder; const FilterNavigationPage({ @@ -35,7 +40,6 @@ class FilterNavigationPage> extends State> { +class _FilterNavigationPageState> extends State> with FeedbackMixin, VaultAwareMixin { final ValueNotifier _appBarHeightNotifier = ValueNotifier(0); @override @@ -143,7 +147,6 @@ class _FilterNavigationPageState ValueListenableBuilder( valueListenable: widget.source.stateNotifier, builder: (context, sourceState, child) { @@ -153,6 +156,28 @@ class _FilterNavigationPageState>?>(); + if (selection != null && selection.isSelecting) { + selection.toggleSelection(gridItem); + } else { + final filter = gridItem.filter; + if (!await unlockFilter(context, filter)) return; + + if (filter is AlbumGroupFilter) { + context.read().value = filter.uri; + } else { + final route = MaterialPageRoute( + settings: const RouteSettings(name: CollectionPage.routeName), + builder: (context) => CollectionPage( + source: context.read(), + filters: {gridItem.filter}, + ), + ); + navigate(route); + } + } + }, ), ), ), diff --git a/lib/widgets/filter_grids/common/filter_tile.dart b/lib/widgets/filter_grids/common/filter_tile.dart index d78e155e2..6386dd1df 100644 --- a/lib/widgets/filter_grids/common/filter_tile.dart +++ b/lib/widgets/filter_grids/common/filter_tile.dart @@ -1,13 +1,7 @@ -import 'package:aves/app_mode.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/vaults/vaults.dart'; -import 'package:aves/widgets/collection/collection_page.dart'; -import 'package:aves/widgets/common/action_mixins/feedback.dart'; -import 'package:aves/widgets/common/action_mixins/vault_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/grid/scaling.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; @@ -16,7 +10,8 @@ import 'package:aves/widgets/filter_grids/common/filter_chip_grid_decorator.dart import 'package:aves/widgets/filter_grids/common/list_details.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; + +typedef FilterTileTapCallback = void Function(FilterGridItem gridItem, void Function(Route route) navigate); class InteractiveFilterTile extends StatefulWidget { final FilterGridItem gridItem; @@ -24,6 +19,7 @@ class InteractiveFilterTile extends StatefulWidget { final TileLayout tileLayout; final String? banner; final HeroType heroType; + final FilterTileTapCallback onTap; const InteractiveFilterTile({ super.key, @@ -33,13 +29,14 @@ class InteractiveFilterTile extends StatefulWidget { required this.tileLayout, this.banner, required this.heroType, + required this.onTap, }); @override State> createState() => _InteractiveFilterTileState(); } -class _InteractiveFilterTileState extends State> with FeedbackMixin, VaultAwareMixin { +class _InteractiveFilterTileState extends State> { HeroType? _heroTypeOverride; FilterGridItem get gridItem => widget.gridItem; @@ -48,30 +45,6 @@ class _InteractiveFilterTileState extends State onTap() async { - if (!await unlockFilter(context, filter)) return; - - final appMode = context.read?>()?.value; - switch (appMode) { - case AppMode.main: - case AppMode.pickCollectionFiltersExternal: - case AppMode.pickSingleMediaExternal: - case AppMode.pickMultipleMediaExternal: - final selection = context.read>?>(); - if (selection != null && selection.isSelecting) { - selection.toggleSelection(gridItem); - } else { - _goToCollection(context, filter); - } - case AppMode.pickFilterInternal: - Navigator.maybeOf(context)?.pop(filter); - default: - break; - } - } - return MetaData( metaData: ScalerMetadata(gridItem), child: FilterTile( @@ -82,28 +55,20 @@ class _InteractiveFilterTileState extends State widget.onTap(gridItem, _navigate), heroType: effectiveHeroType, ), ); } - void _goToCollection(BuildContext context, CollectionFilter filter) { + void _navigate(Route route) { if (effectiveHeroType == HeroType.onTap) { // make sure the chip hero triggers, even when tapping on the list view details setState(() => _heroTypeOverride = HeroType.always); } WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; - Navigator.maybeOf(context)?.push( - MaterialPageRoute( - settings: const RouteSettings(name: CollectionPage.routeName), - builder: (context) => CollectionPage( - source: context.read(), - filters: {filter}, - ), - ), - ); + Navigator.maybeOf(context)?.push(route); }); } } @@ -114,7 +79,7 @@ class FilterTile extends StatelessWidget { final TileLayout tileLayout; final String? banner; final bool selectable, highlightable; - final VoidCallback? onTap; + final AFilterCallback? onTap; final HeroType heroType; const FilterTile({ @@ -135,7 +100,7 @@ class FilterTile extends StatelessWidget { final filter = gridItem.filter; final pinned = settings.pinnedFilters.contains(filter); final locked = filter is StoredAlbumFilter && vaults.isLocked(filter.album); - final onChipTap = onTap != null ? (filter) => onTap?.call() : null; + final _onTap = onTap; switch (tileLayout) { case TileLayout.mosaic: @@ -153,7 +118,7 @@ class FilterTile extends StatelessWidget { pinned: pinned, locked: locked, banner: banner, - onTap: onChipTap, + onTap: _onTap, heroType: heroType, ), ); @@ -173,7 +138,7 @@ class FilterTile extends StatelessWidget { showText: false, locked: locked, banner: banner, - onTap: onChipTap, + onTap: _onTap, heroType: heroType, ), ), @@ -186,13 +151,13 @@ class FilterTile extends StatelessWidget { ), ], ); - if (onTap != null) { + if (_onTap != null) { // larger than the chip corner radius, so ink effects will be effectively clipped from the leading chip corners const radius = Radius.circular(123); child = InkWell( // as of Flutter v2.8.1, `InkWell` does not use `BorderRadiusGeometry` borderRadius: context.isRtl ? const BorderRadius.only(topRight: radius, bottomRight: radius) : const BorderRadius.only(topLeft: radius, bottomLeft: radius), - onTap: onTap, + onTap: () => _onTap(filter), child: child, ); } diff --git a/lib/widgets/filter_grids/common/group_crumb_line.dart b/lib/widgets/filter_grids/common/group_crumb_line.dart new file mode 100644 index 000000000..b0d854631 --- /dev/null +++ b/lib/widgets/filter_grids/common/group_crumb_line.dart @@ -0,0 +1,44 @@ +import 'package:aves/model/grouping/common.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/widgets/common/app_bar/crumb_line.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/material.dart'; + +class FilterGroupCrumbLine extends StatelessWidget { + final Uri? groupUri; + final void Function(Uri? groupUri) onTap; + + const FilterGroupCrumbLine({ + super.key, + required this.groupUri, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return CrumbLine( + split: _split, + combine: _combine, + onTap: onTap, + ); + } + + List _split(BuildContext context) { + final path = FilterGrouping.getGroupPath(groupUri); + final crumbs = [context.l10n.ungrouped]; + if (path != null) { + crumbs.addAll(pContext.split(path)); + } + return crumbs; + } + + Uri? _combine(BuildContext context, int index) { + if (groupUri == null || index == 0) return null; + + var targetGroupUri = groupUri; + for (var i = _split(context).length - 1; i > index; i--) { + targetGroupUri = FilterGrouping.getParentGroup(targetGroupUri); + } + return targetGroupUri; + } +} diff --git a/lib/widgets/filter_grids/common/list_details.dart b/lib/widgets/filter_grids/common/list_details.dart index d8788c80f..825fc332a 100644 --- a/lib/widgets/filter_grids/common/list_details.dart +++ b/lib/widgets/filter_grids/common/list_details.dart @@ -1,4 +1,5 @@ import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; @@ -123,6 +124,7 @@ class FilterListDetails extends StatelessWidget { if (pinned) const Icon(AIcons.pin), if (removableStorage) const Icon(AIcons.storageCard), if (_filter is DynamicAlbumFilter) const Icon(AIcons.dynamicAlbum), + if (_filter is AlbumGroupFilter) const Icon(AIcons.group), ]; Widget? leading; diff --git a/lib/widgets/filter_grids/common/section_keys.dart b/lib/widgets/filter_grids/common/section_keys.dart index 26efbe654..6d8a1ea30 100644 --- a/lib/widgets/filter_grids/common/section_keys.dart +++ b/lib/widgets/filter_grids/common/section_keys.dart @@ -29,6 +29,8 @@ class AlbumImportanceSectionKey extends ChipSectionKey { factory AlbumImportanceSectionKey.pinned(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.pinned); + factory AlbumImportanceSectionKey.group(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.group); + factory AlbumImportanceSectionKey.special(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.special); factory AlbumImportanceSectionKey.apps(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.apps); diff --git a/lib/widgets/filter_grids/countries_page.dart b/lib/widgets/filter_grids/countries_page.dart index c02f418a9..d0c9934e0 100644 --- a/lib/widgets/filter_grids/countries_page.dart +++ b/lib/widgets/filter_grids/countries_page.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location/country.dart'; @@ -40,7 +40,6 @@ class CountryListPage extends StatelessWidget { sortFactor: settings.countrySortFactor, actionDelegate: CountryChipSetActionDelegate(gridItems), filterSections: _groupToSections(gridItems), - applyQuery: applyQuery, emptyBuilder: () => EmptyContent( icon: AIcons.country, text: context.l10n.countryEmpty, @@ -52,10 +51,6 @@ class CountryListPage extends StatelessWidget { ); } - List> applyQuery(BuildContext context, List> filters, String query) { - return filters.where((item) => item.filter.getLabel(context).toUpperCase().contains(query)).toList(); - } - List> _getGridItems(CollectionSource source) { final filters = source.sortedCountries.map((location) => LocationFilter(LocationLevel.country, location)).toSet(); diff --git a/lib/widgets/filter_grids/places_page.dart b/lib/widgets/filter_grids/places_page.dart index a03702341..8a0af4ab7 100644 --- a/lib/widgets/filter_grids/places_page.dart +++ b/lib/widgets/filter_grids/places_page.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location/place.dart'; @@ -40,7 +40,6 @@ class PlaceListPage extends StatelessWidget { sortFactor: settings.placeSortFactor, actionDelegate: PlaceChipSetActionDelegate(gridItems), filterSections: _groupToSections(gridItems), - applyQuery: applyQuery, emptyBuilder: () => EmptyContent( icon: AIcons.place, text: context.l10n.placeEmpty, @@ -52,10 +51,6 @@ class PlaceListPage extends StatelessWidget { ); } - List> applyQuery(BuildContext context, List> filters, String query) { - return filters.where((item) => item.filter.getLabel(context).toUpperCase().contains(query)).toList(); - } - List> _getGridItems(CollectionSource source) { final filters = source.sortedPlaces.map((location) => LocationFilter(LocationLevel.place, location)).toSet(); diff --git a/lib/widgets/filter_grids/states_page.dart b/lib/widgets/filter_grids/states_page.dart index 3fe194840..1402926fd 100644 --- a/lib/widgets/filter_grids/states_page.dart +++ b/lib/widgets/filter_grids/states_page.dart @@ -1,6 +1,6 @@ import 'package:aves/geo/states.dart'; -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location/place.dart'; @@ -46,7 +46,6 @@ class StateListPage extends StatelessWidget { sortFactor: settings.stateSortFactor, actionDelegate: StateChipSetActionDelegate(gridItems), filterSections: _groupToSections(gridItems), - applyQuery: applyQuery, emptyBuilder: () => EmptyContent( icon: AIcons.state, text: context.l10n.stateEmpty, @@ -58,10 +57,6 @@ class StateListPage extends StatelessWidget { ); } - List> applyQuery(BuildContext context, List> filters, String query) { - return filters.where((item) => item.filter.getLabel(context).toUpperCase().contains(query)).toList(); - } - List> _getGridItems(CollectionSource source) { final selectedStateCodes = countryCodes.expand((v) => GeoStates.stateCodesByCountryCode[v] ?? {}).toSet(); final filters = source.sortedStates.where((v) => selectedStateCodes.any(v.endsWith)).map((location) => LocationFilter(LocationLevel.state, location)).toSet(); diff --git a/lib/widgets/filter_grids/tags_page.dart b/lib/widgets/filter_grids/tags_page.dart index 7012ca3ff..e8d827bbc 100644 --- a/lib/widgets/filter_grids/tags_page.dart +++ b/lib/widgets/filter_grids/tags_page.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/tag.dart'; +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/tag.dart'; @@ -40,7 +40,6 @@ class TagListPage extends StatelessWidget { sortFactor: settings.tagSortFactor, actionDelegate: TagChipSetActionDelegate(gridItems), filterSections: _groupToSections(gridItems), - applyQuery: applyQuery, emptyBuilder: () => EmptyContent( icon: AIcons.tag, text: context.l10n.tagEmpty, @@ -52,10 +51,6 @@ class TagListPage extends StatelessWidget { ); } - List> applyQuery(BuildContext context, List> filters, String query) { - return filters.where((item) => item.filter.tag.toUpperCase().contains(query)).toList(); - } - List> _getGridItems(CollectionSource source) { final filters = source.sortedTags.map(TagFilter.new).toSet(); diff --git a/lib/widgets/home/home_page.dart b/lib/widgets/home/home_page.dart index cc4cd1875..98c353fa2 100644 --- a/lib/widgets/home/home_page.dart +++ b/lib/widgets/home/home_page.dart @@ -386,7 +386,7 @@ class _HomePageState extends State { final source = context.read(); switch (routeName) { case AlbumListPage.routeName: - return buildRoute((context) => const AlbumListPage()); + return buildRoute((context) => const AlbumListPage(initialGroup: null)); case TagListPage.routeName: return buildRoute((context) => const TagListPage()); case MapPage.routeName: diff --git a/lib/widgets/navigation/drawer/app_drawer.dart b/lib/widgets/navigation/drawer/app_drawer.dart index aadb5fce2..9740aaf77 100644 --- a/lib/widgets/navigation/drawer/app_drawer.dart +++ b/lib/widgets/navigation/drawer/app_drawer.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/trash.dart'; diff --git a/lib/widgets/navigation/drawer/collection_nav_tile.dart b/lib/widgets/navigation/drawer/collection_nav_tile.dart index 4d1f60cd5..6613d4f6a 100644 --- a/lib/widgets/navigation/drawer/collection_nav_tile.dart +++ b/lib/widgets/navigation/drawer/collection_nav_tile.dart @@ -1,14 +1,19 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'dart:async'; + +import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/collection/collection_page.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; +import 'package:aves/widgets/common/action_mixins/vault_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/navigation/drawer/tile.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -class CollectionNavTile extends StatelessWidget { +class CollectionNavTile extends StatelessWidget with FeedbackMixin, VaultAwareMixin { final Widget? leading; final Widget title; final Widget? trailing; @@ -52,18 +57,27 @@ class CollectionNavTile extends StatelessWidget { ); } - void _goToCollection(BuildContext context) { + Future _goToCollection(BuildContext context) async { + final _filters = filters; + if (_filters != null) { + for (final filter in _filters) { + if (filter != null) { + if (!await unlockFilter(context, filter)) return; + } + } + } + Navigator.maybeOf(context)?.pop(); - Navigator.maybeOf(context)?.pushAndRemoveUntil( + unawaited(Navigator.maybeOf(context)?.pushAndRemoveUntil( MaterialPageRoute( settings: const RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage( source: context.read(), - filters: filters, + filters: _filters, ), ), (route) => false, - ); + )); } } @@ -79,16 +93,18 @@ class AlbumNavTile extends StatelessWidget { @override Widget build(BuildContext context) { + final _filter = filter; + final storageVolume = _filter is StoredAlbumFilter ? _filter.storageVolume : null; return CollectionNavTile( - leading: DrawerFilterIcon(filter: filter), - title: DrawerFilterTitle(filter: filter), - trailing: filter.storageVolume?.isRemovable ?? false + leading: DrawerFilterIcon(filter: _filter), + title: DrawerFilterTitle(filter: _filter), + trailing: storageVolume?.isRemovable ?? false ? const Icon( AIcons.storageCard, size: 16, ) : null, - filters: {filter}, + filters: {_filter}, isSelected: isSelected, ); } diff --git a/lib/widgets/navigation/drawer/page_nav_tile.dart b/lib/widgets/navigation/drawer/page_nav_tile.dart index 172cbf510..96fd558f9 100644 --- a/lib/widgets/navigation/drawer/page_nav_tile.dart +++ b/lib/widgets/navigation/drawer/page_nav_tile.dart @@ -99,7 +99,7 @@ class PageNavTile extends StatelessWidget { static WidgetBuilder _materialPageBuilder(String routeName) { switch (routeName) { case AlbumListPage.routeName: - return (_) => const AlbumListPage(); + return (_) => const AlbumListPage(initialGroup: null); case CountryListPage.routeName: return (_) => const CountryListPage(); case PlaceListPage.routeName: diff --git a/lib/widgets/navigation/nav_bar/nav_bar.dart b/lib/widgets/navigation/nav_bar/nav_bar.dart index 87e9ea683..1b5f21282 100644 --- a/lib/widgets/navigation/nav_bar/nav_bar.dart +++ b/lib/widgets/navigation/nav_bar/nav_bar.dart @@ -147,7 +147,7 @@ class _AppBottomNavBarState extends State { builder: (context) { switch (routeName) { case AlbumListPage.routeName: - return const AlbumListPage(); + return const AlbumListPage(initialGroup: null); case CollectionPage.routeName: default: return CollectionPage( diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index a80ad0a1d..0aab58689 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -1,6 +1,6 @@ import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/filters/aspect_ratio.dart'; -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/covered/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; @@ -15,6 +15,7 @@ import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/filters/set_and.dart'; import 'package:aves/model/filters/type.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -223,6 +224,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va stream: source.eventBus.on(), builder: (context, snapshot) { final filters = [ + ...albumGrouping.getGroups().map(albumGrouping.uriToFilter).whereType(), ...source.rawAlbums .map((album) => StoredAlbumFilter( album, diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart index 66f0bc787..5cc6fc8d2 100644 --- a/lib/widgets/settings/navigation/drawer.dart +++ b/lib/widgets/settings/navigation/drawer.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/settings/navigation/drawer_tab_albums.dart b/lib/widgets/settings/navigation/drawer_tab_albums.dart index 77590098e..c48e6c222 100644 --- a/lib/widgets/settings/navigation/drawer_tab_albums.dart +++ b/lib/widgets/settings/navigation/drawer_tab_albums.dart @@ -1,9 +1,10 @@ -import 'package:aves/model/filters/covered/album_base.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart'; +import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/navigation/drawer/tile.dart'; import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart'; import 'package:flutter/material.dart'; @@ -64,7 +65,12 @@ class _DrawerAlbumTabState extends State { icon: const Icon(AIcons.add), label: context.l10n.settingsNavigationDrawerAddAlbum, onPressed: () async { - final albumFilter = await pickAlbum(context: context, moveType: null, storedAlbumsOnly: false); + final albumFilter = await pickAlbum( + context: context, + moveType: null, + albumTypes: AlbumChipType.values, + initialGroup: null, + ); if (albumFilter == null || items.contains(albumFilter)) return; setState(() => items.add(albumFilter)); }, diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index 99e1662b2..f3d91ccf5 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -7,11 +7,11 @@ import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; +import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/date.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/rating.dart'; -import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -41,7 +41,7 @@ class BasicSection extends StatefulWidget { final EntryInfoActionDelegate actionDelegate; final ValueNotifier isScrollingNotifier; final ValueNotifier isEditingMetadataNotifier; - final AFilterCallback onFilter; + final AFilterCallback onFilterSelection; const BasicSection({ super.key, @@ -50,7 +50,7 @@ class BasicSection extends StatefulWidget { required this.actionDelegate, required this.isScrollingNotifier, required this.isEditingMetadataNotifier, - required this.onFilter, + required this.onFilterSelection, }); @override @@ -151,7 +151,7 @@ class _BasicSectionState extends State { children: effectiveFilters .map((filter) => AvesFilterChip( filter: filter, - onTap: widget.onFilter, + onTap: widget.onFilterSelection, )) .toList(), ), diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index 551c4a2ee..2b88828f2 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -204,7 +204,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { actionDelegate: _actionDelegate, isScrollingNotifier: widget.isScrollingNotifier, isEditingMetadataNotifier: _isEditingMetadataNotifier, - onFilter: _onFilter, + onFilterSelection: _onFilterSelection, ); final locationAtTop = widget.split && entry.hasGps; final locationSection = LocationSection( @@ -212,7 +212,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { entry: entry, showTitle: !locationAtTop, isScrollingNotifier: widget.isScrollingNotifier, - onFilter: _onFilter, + onFilterSelection: _onFilterSelection, ); final basicAndLocationSliver = locationAtTop ? SliverToBoxAdapter( @@ -236,7 +236,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { return NotificationListener( onNotification: (notification) { - _onFilter(notification.filter); + _onFilterSelection(notification.filter); return true; }, child: CustomScrollView( @@ -285,7 +285,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { }); } - void _onFilter(CollectionFilter filter) { + void _onFilterSelection(CollectionFilter filter) { if (!mounted) return; SelectFilterNotification(filter).dispatch(context); } diff --git a/lib/widgets/viewer/info/location_section.dart b/lib/widgets/viewer/info/location_section.dart index 2f13b4ff6..0119430d8 100644 --- a/lib/widgets/viewer/info/location_section.dart +++ b/lib/widgets/viewer/info/location_section.dart @@ -24,7 +24,7 @@ class LocationSection extends StatefulWidget { final AvesEntry entry; final bool showTitle; final ValueNotifier isScrollingNotifier; - final AFilterCallback onFilter; + final AFilterCallback onFilterSelection; const LocationSection({ super.key, @@ -32,7 +32,7 @@ class LocationSection extends StatefulWidget { required this.entry, required this.showTitle, required this.isScrollingNotifier, - required this.onFilter, + required this.onFilterSelection, }); @override @@ -133,7 +133,7 @@ class _LocationSectionState extends State { children: filters .map((filter) => AvesFilterChip( filter: filter, - onTap: widget.onFilter, + onTap: widget.onFilterSelection, )) .toList(), ), diff --git a/plugins/aves_map/lib/src/marker/image.dart b/plugins/aves_map/lib/src/marker/image.dart index 7b776e0d9..aac19d24f 100644 --- a/plugins/aves_map/lib/src/marker/image.dart +++ b/plugins/aves_map/lib/src/marker/image.dart @@ -83,31 +83,7 @@ class ImageMarker extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 2), decoration: ShapeDecoration( color: theme.colorScheme.primary, - shape: Directionality.of(context) == TextDirection.rtl - ? CustomRoundedRectangleBorder( - leftSide: borderSide, - rightSide: borderSide, - topSide: borderSide, - bottomSide: borderSide, - topRightCornerSide: borderSide, - bottomLeftCornerSide: borderSide, - borderRadius: const BorderRadius.only( - topRight: innerRadius, - bottomLeft: innerRadius, - ), - ) - : CustomRoundedRectangleBorder( - leftSide: borderSide, - rightSide: borderSide, - topSide: borderSide, - bottomSide: borderSide, - topLeftCornerSide: borderSide, - bottomRightCornerSide: borderSide, - borderRadius: const BorderRadius.only( - topLeft: innerRadius, - bottomRight: innerRadius, - ), - ), + shape: _buildCornerDetailShape(context, borderSide), ), child: Text( countFormatter.format(count), @@ -144,6 +120,26 @@ class ImageMarker extends StatelessWidget { return child; } + ShapeBorder _buildCornerDetailShape(BuildContext context, BorderSide side) { + final isRtl = Directionality.of(context) == TextDirection.rtl; + return CustomRoundedRectangleBorder( + leftSide: side, + rightSide: side, + topSide: side, + bottomSide: side, + topLeftCornerSide: isRtl ? null : side, + topRightCornerSide: isRtl ? side : null, + bottomLeftCornerSide: isRtl ? side : null, + bottomRightCornerSide: isRtl ? null : side, + borderRadius: BorderRadius.only( + topLeft: isRtl ? Radius.zero : innerRadius, + topRight: isRtl ? innerRadius : Radius.zero, + bottomLeft: isRtl ? innerRadius : Radius.zero, + bottomRight: isRtl ? Radius.zero : innerRadius, + ), + ); + } + static const _crs = Epsg3857(); static GeoEntry? markerMatch(LatLng position, double zoom, Set> markers) { diff --git a/plugins/aves_model/lib/src/actions/chip_set.dart b/plugins/aves_model/lib/src/actions/chip_set.dart index ae7a53043..13db2f8bb 100644 --- a/plugins/aves_model/lib/src/actions/chip_set.dart +++ b/plugins/aves_model/lib/src/actions/chip_set.dart @@ -7,6 +7,7 @@ enum ChipSetAction { // browsing search, toggleTitleSearch, + createGroup, createAlbum, createVault, // browsing or selecting @@ -19,6 +20,7 @@ enum ChipSetAction { hide, pin, unpin, + group, lockVault, showCountryStates, showCollection, @@ -59,6 +61,7 @@ class ChipSetActions { ChipSetAction.rename, ChipSetAction.showCountryStates, ChipSetAction.hide, + ChipSetAction.group, null, ChipSetAction.showCollection, ChipSetAction.map, diff --git a/plugins/aves_model/lib/src/settings/keys.dart b/plugins/aves_model/lib/src/settings/keys.dart index 19cec2c3c..f28cf8163 100644 --- a/plugins/aves_model/lib/src/settings/keys.dart +++ b/plugins/aves_model/lib/src/settings/keys.dart @@ -158,9 +158,6 @@ class SettingKeys { static const accessibilityAnimationsKey = 'accessibility_animations'; static const timeToTakeActionKey = 'time_to_take_action'; - // file picker - static const filePickerShowHiddenFilesKey = 'file_picker_show_hidden_files'; - // screen saver static const screenSaverFillScreenKey = 'screen_saver_fill_screen'; static const screenSaverAnimatedZoomEffectKey = 'screen_saver_animated_zoom_effect'; diff --git a/plugins/aves_model/lib/src/source/enums.dart b/plugins/aves_model/lib/src/source/enums.dart index cad1c1a2b..9b574264e 100644 --- a/plugins/aves_model/lib/src/source/enums.dart +++ b/plugins/aves_model/lib/src/source/enums.dart @@ -2,10 +2,10 @@ enum SourceState { loading, cataloguing, locatingCountries, locatingPlaces, read enum ChipSortFactor { date, name, count, size, path } -enum AlbumChipGroupFactor { none, importance, mimeType, volume } +enum AlbumChipSectionFactor { none, importance, mimeType, volume } enum EntrySortFactor { date, name, rating, size, duration } -enum EntryGroupFactor { none, album, month, day } +enum EntrySectionFactor { none, album, month, day } enum TileLayout { mosaic, grid, list } diff --git a/plugins/aves_model/lib/src/storage/relative_dir.dart b/plugins/aves_model/lib/src/storage/relative_dir.dart index b7c84a0ca..377318f40 100644 --- a/plugins/aves_model/lib/src/storage/relative_dir.dart +++ b/plugins/aves_model/lib/src/storage/relative_dir.dart @@ -1,3 +1,4 @@ +import 'package:aves_model/aves_model.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; @@ -15,6 +16,10 @@ class VolumeRelativeDirectory extends Equatable { required this.relativeDir, }); + factory VolumeRelativeDirectory.volume(StorageVolume volume) { + return VolumeRelativeDirectory(volumePath: volume.path, relativeDir: ''); + } + static VolumeRelativeDirectory fromMap(Map map) { return VolumeRelativeDirectory( volumePath: map['volumePath'] ?? '', diff --git a/pubspec.lock b/pubspec.lock index a48701e9b..ba6cf30f3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f url: "https://pub.dev" source: hosted - version: "80.0.0" + version: "82.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: analyzer - sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + sha256: "13c1e6c6fd460522ea840abec3f677cc226f5fec7872c04ad7b425517ccf54f7" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.4.4" archive: dependency: transitive description: name: archive - sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12" + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "4.0.7" args: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: chalkdart - sha256: "937c94f1866b51155548e6237e1374a26134d39c1bf11b443db466dc9c8e62da" + sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.5" characters: dependency: transitive description: @@ -135,10 +135,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27" + sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99" url: "https://pub.dev" source: hosted - version: "6.1.3" + version: "6.1.4" connectivity_plus_platform_interface: dependency: transitive description: @@ -167,10 +167,10 @@ packages: dependency: transitive description: name: coverage - sha256: "9086475ef2da7102a0c0a4e37e1e30707e7fb7b6d28c209f559a9c5f8ce42016" + sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.13.1" crypto: dependency: transitive description: @@ -223,10 +223,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513" + sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53" url: "https://pub.dev" source: hosted - version: "11.3.3" + version: "11.4.0" device_info_plus_platform_interface: dependency: transitive description: @@ -360,10 +360,10 @@ packages: dependency: "direct main" description: name: flex_color_picker - sha256: c083b79f1c57eaeed9f464368be376951230b3cb1876323b784626152a86e480 + sha256: "8f753a1a026a13ea5cc5eddbae3ceb886f2537569ab2e5208efb1e3bb5af72ff" url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "3.7.1" flex_seed_scheme: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.0.28" flutter_staggered_animations: dependency: "direct main" description: @@ -559,18 +559,18 @@ packages: dependency: transitive description: name: google_maps_flutter_android - sha256: "0ede4ae8326335c0c007c8c7a8c9737449263123385e2bdf49f3e71103b2dc2e" + sha256: ab83128296fbeaa52e8f2b3bf53bcd895e64778edddcdc07bc8f33f4ea78076c url: "https://pub.dev" source: hosted - version: "2.16.0" + version: "2.16.1" google_maps_flutter_ios: dependency: transitive description: name: google_maps_flutter_ios - sha256: "2911e0c5654c2cc0d664324e0f49f055bb748740162821bdc0285045150b0439" + sha256: c7433645c4c9b61c587938cb06072f3dad601239e596b090c0f8f206c1f2ade7 url: "https://pub.dev" source: hosted - version: "2.15.1" + version: "2.15.2" google_maps_flutter_platform_interface: dependency: transitive description: @@ -607,10 +607,10 @@ packages: dependency: transitive description: name: html - sha256: "9475be233c437f0e3637af55e7702cbbe5c23a68bd56e8a5fa2d426297b7c6c8" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.5+1" + version: "0.15.6" http: dependency: "direct main" description: @@ -727,10 +727,10 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "0abe4e72f55c785b28900de52a2522c86baba0988838b5dc22241b072ecccd74" + sha256: "63ad7ca6396290626dc0cb34725a939e4cfe965d80d36112f08d49cf13a8136e" url: "https://pub.dev" source: hosted - version: "1.0.48" + version: "1.0.49" local_auth_darwin: dependency: transitive description: @@ -880,10 +880,10 @@ packages: dependency: "direct main" description: name: network_info_plus - sha256: "08f4166bbb77da9e407edef6322a33f87b18c0ca46483fb25606cb3d2bfcdd2a" + sha256: f926b2ba86aa0086a0dfbb9e5072089bc213d854135c1712f1d29fc89ba3c877 url: "https://pub.dev" source: hosted - version: "6.1.3" + version: "6.1.4" network_info_plus_platform_interface: dependency: transitive description: @@ -985,10 +985,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -1153,10 +1153,10 @@ packages: dependency: transitive description: name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" printing: dependency: "direct main" description: @@ -1193,10 +1193,10 @@ packages: dependency: "direct main" description: name: provider - sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310" + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.1.5" pub_semver: dependency: transitive description: @@ -1297,10 +1297,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: c2c8c46297b5d6a80bed7741ec1f2759742c77d272f1a1698176ae828f8e1a18 + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -1591,10 +1591,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.3.15" + version: "6.3.16" url_launcher_ios: dependency: transitive description: @@ -1631,10 +1631,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -1703,18 +1703,18 @@ packages: dependency: transitive description: name: wakelock_plus - sha256: b90fbcc8d7bdf3b883ea9706d9d76b9978cb1dfa4351fcc8014d6ec31a493354 + sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 url: "https://pub.dev" source: hosted - version: "1.2.11" + version: "1.3.2" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" + sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" watcher: dependency: transitive description: @@ -1735,18 +1735,18 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" webdriver: dependency: transitive description: diff --git a/test/fake/db.dart b/test/fake/db.dart index 429481c87..a0a979e82 100644 --- a/test/fake/db.dart +++ b/test/fake/db.dart @@ -117,6 +117,14 @@ class FakeAvesDb extends Fake implements LocalMediaDb { @override Future> loadAllDynamicAlbums() => SynchronousFuture({}); + // dynamic albums + + @override + Future clearDynamicAlbums() => SynchronousFuture(null); + + @override + Future addDynamicAlbums(Set rows) => SynchronousFuture(null); + // video playback @override diff --git a/test/model/filters_test.dart b/test/model/filters_test.dart index 39e35ed96..85b7e9ece 100644 --- a/test/model/filters_test.dart +++ b/test/model/filters_test.dart @@ -1,10 +1,13 @@ -import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/aspect_ratio.dart'; import 'package:aves/model/filters/coordinate.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/covered/location.dart'; +import 'package:aves/model/filters/covered/stored_album.dart'; +import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/date.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/missing.dart'; import 'package:aves/model/filters/path.dart'; @@ -12,8 +15,10 @@ import 'package:aves/model/filters/placeholder.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/recent.dart'; -import 'package:aves/model/filters/covered/tag.dart'; +import 'package:aves/model/filters/set_and.dart'; +import 'package:aves/model/filters/set_or.dart'; import 'package:aves/model/filters/type.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/services/common/services.dart'; import 'package:latlong2/latlong.dart'; import 'package:path/path.dart' as p; @@ -29,15 +34,13 @@ void main() { }); tearDown(() async { + albumGrouping.clear(); await getIt.reset(); }); test('Filter serialization', () { CollectionFilter? jsonRoundTrip(filter) => CollectionFilter.fromJson(filter.toJson()); - final album = StoredAlbumFilter('path/to/album', 'album'); - expect(album, jsonRoundTrip(album)); - final aspectRatio = AspectRatioFilter.landscape; expect(aspectRatio, jsonRoundTrip(aspectRatio)); @@ -53,9 +56,6 @@ void main() { const fav = FavouriteFilter.instance; expect(fav, jsonRoundTrip(fav)); - final location = LocationFilter(LocationLevel.country, 'France${LocationFilter.locationSeparator}FR'); - expect(location, jsonRoundTrip(location)); - final mime = MimeFilter.video; expect(mime, jsonRoundTrip(mime)); @@ -77,11 +77,35 @@ void main() { final recent = RecentlyAddedFilter.instance; expect(recent, jsonRoundTrip(recent)); + final type = TypeFilter.sphericalVideo; + expect(type, jsonRoundTrip(type)); + + // covered + + final album = StoredAlbumFilter('path/to/album', 'album'); + expect(album, jsonRoundTrip(album)); + + final location = LocationFilter(LocationLevel.country, 'France${LocationFilter.locationSeparator}FR'); + expect(location, jsonRoundTrip(location)); + final tag = TagFilter('some tag'); expect(tag, jsonRoundTrip(tag)); - final type = TypeFilter.sphericalVideo; - expect(type, jsonRoundTrip(type)); + // combinations + + final setAnd = SetAndFilter({album, location, tag}); + expect(setAnd, jsonRoundTrip(setAnd)); + + final setOr = SetOrFilter({album, location, tag}); + expect(setOr, jsonRoundTrip(setOr)); + + final dynamicAlbum = DynamicAlbumFilter('dynamic album', setAnd); + expect(dynamicAlbum, jsonRoundTrip(dynamicAlbum)); + + // groups + + final albumGroup = AlbumGroupFilter(albumGrouping.buildGroupUri(null, 'some group'), setOr); + expect(albumGroup, jsonRoundTrip(albumGroup)); }); test('Path filter', () { diff --git a/test/model/grouping/common_test.dart b/test/model/grouping/common_test.dart new file mode 100644 index 000000000..3527804f6 --- /dev/null +++ b/test/model/grouping/common_test.dart @@ -0,0 +1,145 @@ +import 'package:aves/model/db/db.dart'; +import 'package:aves/model/dynamic_albums.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/covered/stored_album.dart'; +import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/grouping/common.dart'; +import 'package:aves/model/grouping/convert.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +import '../../fake/db.dart'; + +void main() { + const groupName = 'some group name'; + const storedAlbumPath = '/path/to/album'; + + setUp(() async { + // specify Posix style path context for consistent behaviour when running tests on Windows + getIt.registerLazySingleton(() => p.Context(style: p.Style.posix)); + getIt.registerLazySingleton(FakeAvesDb.new); + }); + + tearDown(() async { + albumGrouping.clear(); + await dynamicAlbums.clear(); + await getIt.reset(); + }); + + test('Filter URI round trip', () { + final storedAlbumFilter = StoredAlbumFilter(storedAlbumPath, 'display name'); + final dynamicAlbumFilter = DynamicAlbumFilter('dynamic name', storedAlbumFilter); + dynamicAlbums.add(dynamicAlbumFilter); + final groupUri = albumGrouping.buildGroupUri(null, groupName); + final albumGroupFilter = AlbumGroupFilter(groupUri, SetOrFilter({storedAlbumFilter, dynamicAlbumFilter})); + + expect(albumGrouping.uriToFilter(GroupingConversion.filterToUri(storedAlbumFilter)), storedAlbumFilter); + expect(albumGrouping.uriToFilter(GroupingConversion.filterToUri(dynamicAlbumFilter)), dynamicAlbumFilter); + expect(albumGrouping.uriToFilter(GroupingConversion.filterToUri(albumGroupFilter)), albumGroupFilter); + }); + + test('Empty group', () { + final groupUri = albumGrouping.buildGroupUri(null, groupName); + expect(FilterGrouping.getGroupName(groupUri), groupName); + + expect(albumGrouping.exists(groupUri), false); + expect(albumGrouping.getDirectChildren(null).length, 0); + expect(albumGrouping.getDirectChildren(groupUri).length, 0); + expect(albumGrouping.countLeaves(groupUri), 0); + }); + + test('Adding album to group', () { + final groupUri = albumGrouping.buildGroupUri(null, groupName); + final childUri = GroupingConversion.filterToUri(StoredAlbumFilter(storedAlbumPath, null)); + albumGrouping.addToGroup({childUri}.nonNulls.toSet(), groupUri); + + expect(albumGrouping.exists(groupUri), true); + expect(albumGrouping.getDirectChildren(null).length, 1); + expect(albumGrouping.getDirectChildren(groupUri).length, 1); + expect(albumGrouping.countLeaves(groupUri), 1); + }); + + test('Adding subgroup to group', () { + final rootGroupUri = albumGrouping.buildGroupUri(null, 'root'); + final subGroupUri = albumGrouping.buildGroupUri(rootGroupUri, 'sub'); + final childUri = GroupingConversion.filterToUri(StoredAlbumFilter(storedAlbumPath, null)); + albumGrouping.addToGroup({childUri}.nonNulls.toSet(), subGroupUri); + albumGrouping.addToGroup({subGroupUri}, rootGroupUri); + + expect(albumGrouping.exists(rootGroupUri), true); + expect(albumGrouping.exists(subGroupUri), true); + expect(albumGrouping.getDirectChildren(null).length, 1); + expect(albumGrouping.getDirectChildren(rootGroupUri).length, 1); + expect(albumGrouping.getDirectChildren(subGroupUri).length, 1); + expect(albumGrouping.countLeaves(rootGroupUri), 1); + expect(albumGrouping.countLeaves(subGroupUri), 1); + }); + + test('Removing from group', () { + final groupUri = albumGrouping.buildGroupUri(null, groupName); + final childUri = GroupingConversion.filterToUri(StoredAlbumFilter(storedAlbumPath, null)); + albumGrouping.addToGroup({childUri}.nonNulls.toSet(), groupUri); + albumGrouping.addToGroup({childUri}.nonNulls.toSet(), null); + + expect(albumGrouping.exists(groupUri), false); + expect(albumGrouping.getDirectChildren(null).length, 0); + expect(albumGrouping.getDirectChildren(groupUri).length, 0); + expect(albumGrouping.countLeaves(groupUri), 0); + }); + + test('Reparent group', () { + const subgroupName = 'sub'; + + final rootGroupUri = albumGrouping.buildGroupUri(null, 'old root'); + final subGroupUri = albumGrouping.buildGroupUri(rootGroupUri, subgroupName); + final childUri = GroupingConversion.filterToUri(StoredAlbumFilter(storedAlbumPath, null)); + albumGrouping.addToGroup({childUri}.nonNulls.toSet(), subGroupUri); + albumGrouping.addToGroup({subGroupUri}, rootGroupUri); + + final newRootGroupUri = albumGrouping.buildGroupUri(null, 'new root'); + final newSubGroupUri = albumGrouping.buildGroupUri(newRootGroupUri, subgroupName); + albumGrouping.addToGroup({subGroupUri}, newRootGroupUri); + expect(albumGrouping.exists(rootGroupUri), false); + expect(albumGrouping.exists(subGroupUri), false); + expect(albumGrouping.exists(newRootGroupUri), true); + expect(albumGrouping.exists(newSubGroupUri), true); + expect(albumGrouping.getDirectChildren(newRootGroupUri).length, 1); + expect(albumGrouping.getDirectChildren(newSubGroupUri).length, 1); + }); + + test('Reparent content', () { + final rootGroupUri = albumGrouping.buildGroupUri(null, 'root'); + final childUriToKeep = GroupingConversion.filterToUri(StoredAlbumFilter('$storedAlbumPath 1', null)); + final childUriToMove = GroupingConversion.filterToUri(StoredAlbumFilter('$storedAlbumPath 2', null)); + albumGrouping.addToGroup({childUriToKeep, childUriToMove}.nonNulls.toSet(), rootGroupUri); + + final subGroupUri = albumGrouping.buildGroupUri(rootGroupUri, 'sub'); + albumGrouping.addToGroup({childUriToMove}.nonNulls.toSet(), subGroupUri); + + expect(albumGrouping.exists(rootGroupUri), true); + expect(albumGrouping.exists(subGroupUri), true); + expect(albumGrouping.getDirectChildren(rootGroupUri).length, 2); + expect(albumGrouping.getDirectChildren(subGroupUri).length, 1); + }); + + test('Reparent group deeper', () { + final rootGroupUri = albumGrouping.buildGroupUri(null, 'root'); + const movingGroupName = 'moving'; + final movingGroupUri = albumGrouping.buildGroupUri(null, movingGroupName); + final childUri = GroupingConversion.filterToUri(StoredAlbumFilter(storedAlbumPath, null)); + // > moving group > stored album + albumGrouping.addToGroup({childUri}.nonNulls.toSet(), movingGroupUri); + // > root group > moving group > stored album + albumGrouping.addToGroup({movingGroupUri}, rootGroupUri); + + final movedGroupUri = albumGrouping.buildGroupUri(rootGroupUri, movingGroupName); + expect(albumGrouping.exists(rootGroupUri), true); + expect(albumGrouping.exists(movingGroupUri), false); + expect(albumGrouping.exists(movedGroupUri), true); + expect(albumGrouping.getDirectChildren(rootGroupUri).length, 1); + expect(albumGrouping.getDirectChildren(movedGroupUri).length, 1); + expect(GroupingConversion.filterToUri(albumGrouping.getDirectChildren(rootGroupUri).first), movedGroupUri); + }); +} diff --git a/test/model/grouping/convert_test.dart b/test/model/grouping/convert_test.dart new file mode 100644 index 000000000..e88fd8c96 --- /dev/null +++ b/test/model/grouping/convert_test.dart @@ -0,0 +1,41 @@ +import 'package:aves/model/db/db.dart'; +import 'package:aves/model/dynamic_albums.dart'; +import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/covered/stored_album.dart'; +import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/grouping/common.dart'; +import 'package:aves/model/grouping/convert.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +import '../../fake/db.dart'; + +void main() { + const groupName = 'some group name'; + const storedAlbumPath = '/path/to/album/'; + + setUp(() async { + // specify Posix style path context for consistent behaviour when running tests on Windows + getIt.registerLazySingleton(() => p.Context(style: p.Style.posix)); + getIt.registerLazySingleton(FakeAvesDb.new); + }); + + tearDown(() async { + await dynamicAlbums.clear(); + await getIt.reset(); + }); + + test('Filter URI round trip', () { + final storedAlbumFilter = StoredAlbumFilter(storedAlbumPath, 'display name'); + final dynamicAlbumFilter = DynamicAlbumFilter('dynamic name', storedAlbumFilter); + dynamicAlbums.add(dynamicAlbumFilter); + final groupUri = albumGrouping.buildGroupUri(null, groupName); + final albumGroupFilter = AlbumGroupFilter(groupUri, SetOrFilter({storedAlbumFilter, dynamicAlbumFilter})); + + expect(albumGrouping.uriToFilter(GroupingConversion.filterToUri(storedAlbumFilter)), storedAlbumFilter); + expect(albumGrouping.uriToFilter(GroupingConversion.filterToUri(dynamicAlbumFilter)), dynamicAlbumFilter); + expect(albumGrouping.uriToFilter(GroupingConversion.filterToUri(albumGroupFilter)), albumGroupFilter); + }); +} diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index 2bcc263d8..912061f8a 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -35,7 +35,7 @@ Future configureAndLaunch() async { ..drawerTypeBookmarks = [null, FavouriteFilter.instance] ..drawerAlbumBookmarks = null // collection - ..collectionSectionFactor = EntryGroupFactor.month + ..collectionSectionFactor = EntrySectionFactor.month ..collectionSortFactor = EntrySortFactor.date ..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions ..showThumbnailFavourite = false diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index ac303883f..7f16433ef 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -29,7 +29,7 @@ Future configureAndLaunch() async { ..setHome(HomePageSetting.collection) ..enableBottomNavigationBar = true // collection - ..collectionSectionFactor = EntryGroupFactor.album + ..collectionSectionFactor = EntrySectionFactor.album ..collectionSortFactor = EntrySortFactor.date ..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions // viewer