diff --git a/CHANGELOG.md b/CHANGELOG.md index 3016c71ab..8141fef8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Places: page & navigation entry + +### Fixed + +- replacing when moving item to vault +- exporting item to vault + ## [v1.8.1] - 2023-02-21 ### Added diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7104192c1..d13036067 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -74,6 +74,7 @@ "chipActionDelete": "Delete", "chipActionGoToAlbumPage": "Show in Albums", "chipActionGoToCountryPage": "Show in Countries", + "chipActionGoToPlacePage": "Show in Places", "chipActionGoToTagPage": "Show in Tags", "chipActionFilterOut": "Filter out", "chipActionFilterIn": "Filter in", @@ -621,6 +622,7 @@ "drawerCollectionSphericalVideos": "360° Videos", "drawerAlbumPage": "Albums", "drawerCountryPage": "Countries", + "drawerPlacePage": "Places", "drawerTagPage": "Tags", "sortByDate": "By date", @@ -665,6 +667,9 @@ "countryPageTitle": "Countries", "countryEmpty": "No countries", + "placePageTitle": "Places", + "placeEmpty": "No places", + "tagPageTitle": "Tags", "tagEmpty": "No tags", diff --git a/lib/model/actions/chip_actions.dart b/lib/model/actions/chip_actions.dart index bb7dc079a..f71b8c930 100644 --- a/lib/model/actions/chip_actions.dart +++ b/lib/model/actions/chip_actions.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; enum ChipAction { goToAlbumPage, goToCountryPage, + goToPlacePage, goToTagPage, reverse, hide, @@ -18,6 +19,8 @@ extension ExtraChipAction on ChipAction { return context.l10n.chipActionGoToAlbumPage; case ChipAction.goToCountryPage: return context.l10n.chipActionGoToCountryPage; + case ChipAction.goToPlacePage: + return context.l10n.chipActionGoToPlacePage; case ChipAction.goToTagPage: return context.l10n.chipActionGoToTagPage; case ChipAction.reverse: @@ -37,7 +40,9 @@ extension ExtraChipAction on ChipAction { case ChipAction.goToAlbumPage: return AIcons.album; case ChipAction.goToCountryPage: - return AIcons.location; + return AIcons.country; + case ChipAction.goToPlacePage: + return AIcons.place; case ChipAction.goToTagPage: return AIcons.tag; case ChipAction.reverse: diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index 97289cfd8..aab90081e 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -51,6 +51,8 @@ class LocationFilter extends CoveredCollectionFilter { String? get countryCode => _countryCode; + String get place => _location; + @override EntryFilter get positiveTest => _test; @@ -65,17 +67,25 @@ class LocationFilter extends CoveredCollectionFilter { @override Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) { - if (_countryCode != null && device.canRenderFlagEmojis) { - final flag = countryCodeToFlag(_countryCode); - if (flag != null) { - return Text( - flag, - style: TextStyle(fontSize: size), - textScaleFactor: 1.0, - ); - } + if (_location.isEmpty) { + return Icon(AIcons.locationUnlocated, size: size); + } + switch (level) { + case LocationLevel.place: + return Icon(AIcons.place, size: size); + case LocationLevel.country: + if (_countryCode != null && device.canRenderFlagEmojis) { + final flag = countryCodeToFlag(_countryCode); + if (flag != null) { + return Text( + flag, + style: TextStyle(fontSize: size), + textScaleFactor: 1.0, + ); + } + } + return Icon(AIcons.country, size: size); } - return Icon(_location.isEmpty ? AIcons.locationUnlocated : AIcons.location, size: size); } @override diff --git a/lib/model/filters/placeholder.dart b/lib/model/filters/placeholder.dart index 933ac331e..e0be8e084 100644 --- a/lib/model/filters/placeholder.dart +++ b/lib/model/filters/placeholder.dart @@ -23,8 +23,10 @@ class PlaceholderFilter extends CollectionFilter { PlaceholderFilter._private(this.placeholder) : super(reversed: false) { switch (placeholder) { case _country: + _icon = AIcons.country; + break; case _place: - _icon = AIcons.location; + _icon = AIcons.place; break; } } diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 13d04e8fd..72d9a972d 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -6,6 +6,7 @@ import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:flutter/material.dart'; @@ -40,6 +41,7 @@ class SettingsDefaults { static const drawerPageBookmarks = [ AlbumListPage.routeName, CountryListPage.routeName, + PlaceListPage.routeName, TagListPage.routeName, ]; @@ -65,6 +67,7 @@ class SettingsDefaults { static const albumGroupFactor = AlbumChipGroupFactor.importance; static const albumSortFactor = ChipSortFactor.name; static const countrySortFactor = ChipSortFactor.name; + static const placeSortFactor = ChipSortFactor.name; static const tagSortFactor = ChipSortFactor.name; // viewer diff --git a/lib/model/settings/enums/thumbnail_overlay_location_icon.dart b/lib/model/settings/enums/thumbnail_overlay_location_icon.dart index a0f76c54e..e61a0fd37 100644 --- a/lib/model/settings/enums/thumbnail_overlay_location_icon.dart +++ b/lib/model/settings/enums/thumbnail_overlay_location_icon.dart @@ -18,10 +18,9 @@ extension ExtraThumbnailOverlayLocationIcon on ThumbnailOverlayLocationIcon { IconData getIcon(BuildContext context) { switch (this) { - case ThumbnailOverlayLocationIcon.located: - return AIcons.location; case ThumbnailOverlayLocationIcon.unlocated: return AIcons.locationUnlocated; + case ThumbnailOverlayLocationIcon.located: case ThumbnailOverlayLocationIcon.none: return AIcons.location; } diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index ec3584ff1..7e8b20fa7 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -20,6 +20,7 @@ import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves_map/aves_map.dart'; import 'package:collection/collection.dart'; @@ -106,9 +107,11 @@ class Settings extends ChangeNotifier { static const albumGroupFactorKey = 'album_group_factor'; static const albumSortFactorKey = 'album_sort_factor'; static const countrySortFactorKey = 'country_sort_factor'; + static const placeSortFactorKey = 'place_sort_factor'; static const tagSortFactorKey = 'tag_sort_factor'; static const albumSortReverseKey = 'album_sort_reverse'; static const countrySortReverseKey = 'country_sort_reverse'; + static const placeSortReverseKey = 'place_sort_reverse'; static const tagSortReverseKey = 'tag_sort_reverse'; static const pinnedFiltersKey = 'pinned_filters'; static const hiddenFiltersKey = 'hidden_filters'; @@ -265,6 +268,7 @@ class Settings extends ChangeNotifier { drawerPageBookmarks = [ AlbumListPage.routeName, CountryListPage.routeName, + PlaceListPage.routeName, TagListPage.routeName, SearchPage.routeName, ]; @@ -543,6 +547,10 @@ class Settings extends ChangeNotifier { set countrySortFactor(ChipSortFactor newValue) => _set(countrySortFactorKey, newValue.toString()); + ChipSortFactor get placeSortFactor => getEnumOrDefault(placeSortFactorKey, SettingsDefaults.placeSortFactor, ChipSortFactor.values); + + set placeSortFactor(ChipSortFactor newValue) => _set(placeSortFactorKey, newValue.toString()); + ChipSortFactor get tagSortFactor => getEnumOrDefault(tagSortFactorKey, SettingsDefaults.tagSortFactor, ChipSortFactor.values); set tagSortFactor(ChipSortFactor newValue) => _set(tagSortFactorKey, newValue.toString()); @@ -555,6 +563,10 @@ class Settings extends ChangeNotifier { set countrySortReverse(bool newValue) => _set(countrySortReverseKey, newValue); + bool get placeSortReverse => getBool(placeSortReverseKey) ?? false; + + set placeSortReverse(bool newValue) => _set(placeSortReverseKey, newValue); + bool get tagSortReverse => getBool(tagSortReverseKey) ?? false; set tagSortReverse(bool newValue) => _set(tagSortReverseKey, newValue); @@ -1038,6 +1050,7 @@ class Settings extends ChangeNotifier { case showThumbnailVideoDurationKey: case albumSortReverseKey: case countrySortReverseKey: + case placeSortReverseKey: case tagSortReverseKey: case showOverlayOnOpeningKey: case showOverlayMinimapKey: @@ -1084,6 +1097,7 @@ class Settings extends ChangeNotifier { case albumGroupFactorKey: case albumSortFactorKey: case countrySortFactorKey: + case placeSortFactorKey: case tagSortFactorKey: case imageBackgroundKey: case videoAutoPlayModeKey: diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index f4e48bdd8..5c9730444 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -14,7 +14,7 @@ import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/events.dart'; -import 'package:aves/model/source/location.dart'; +import 'package:aves/model/source/location/location.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/utils/change_notifier.dart'; diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index fcb163e1c..e31296c46 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -15,7 +15,9 @@ import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/events.dart'; -import 'package:aves/model/source/location.dart'; +import 'package:aves/model/source/location/country.dart'; +import 'package:aves/model/source/location/location.dart'; +import 'package:aves/model/source/location/place.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/model/source/trash.dart'; import 'package:aves/model/vaults/vaults.dart'; @@ -54,7 +56,7 @@ mixin SourceBase { void invalidateEntries(); } -abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin, TrashMixin { +abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, PlaceMixin, LocationMixin, TagMixin, TrashMixin { CollectionSource() { settings.updateStream.where((event) => event.key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames()); settings.updateStream.where((event) => event.key == Settings.hiddenFiltersKey).listen((event) { @@ -136,6 +138,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM invalidateEntries(); invalidateAlbumFilterSummary(entries: entries, notify: notify); invalidateCountryFilterSummary(entries: entries, notify: notify); + invalidatePlaceFilterSummary(entries: entries, notify: notify); invalidateTagFilterSummary(entries: entries, notify: notify); } @@ -501,21 +504,42 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM int count(CollectionFilter filter) { if (filter is AlbumFilter) return albumEntryCount(filter); - if (filter is LocationFilter) return countryEntryCount(filter); + if (filter is LocationFilter) { + switch (filter.level) { + case LocationLevel.country: + return countryEntryCount(filter); + case LocationLevel.place: + return placeEntryCount(filter); + } + } if (filter is TagFilter) return tagEntryCount(filter); return 0; } int size(CollectionFilter filter) { if (filter is AlbumFilter) return albumSize(filter); - if (filter is LocationFilter) return countrySize(filter); + if (filter is LocationFilter) { + switch (filter.level) { + case LocationLevel.country: + return countrySize(filter); + case LocationLevel.place: + return placeSize(filter); + } + } if (filter is TagFilter) return tagSize(filter); return 0; } AvesEntry? recentEntry(CollectionFilter filter) { if (filter is AlbumFilter) return albumRecentEntry(filter); - if (filter is LocationFilter) return countryRecentEntry(filter); + if (filter is LocationFilter) { + switch (filter.level) { + case LocationLevel.country: + return countryRecentEntry(filter); + case LocationLevel.place: + return placeRecentEntry(filter); + } + } if (filter is TagFilter) return tagRecentEntry(filter); return null; } diff --git a/lib/model/source/location/country.dart b/lib/model/source/location/country.dart new file mode 100644 index 000000000..a6de50f6a --- /dev/null +++ b/lib/model/source/location/country.dart @@ -0,0 +1,66 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/model/filters/location.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/utils/collection_utils.dart'; +import 'package:collection/collection.dart'; + +mixin CountryMixin on SourceBase { + // filter summary + + // by country code + final Map _filterEntryCountMap = {}, _filterSizeMap = {}; + final Map _filterRecentEntryMap = {}; + + void invalidateCountryFilterSummary({ + Set? entries, + Set? countryCodes, + bool notify = true, + }) { + if (_filterEntryCountMap.isEmpty && _filterSizeMap.isEmpty && _filterRecentEntryMap.isEmpty) return; + + if (entries == null && countryCodes == null) { + _filterEntryCountMap.clear(); + _filterSizeMap.clear(); + _filterRecentEntryMap.clear(); + } else { + countryCodes ??= {}; + if (entries != null) { + countryCodes.addAll(entries.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.countryCode).whereNotNull()); + } + countryCodes.forEach((countryCode) { + _filterEntryCountMap.remove(countryCode); + _filterSizeMap.remove(countryCode); + _filterRecentEntryMap.remove(countryCode); + }); + } + if (notify) { + eventBus.fire(CountrySummaryInvalidatedEvent(countryCodes)); + } + } + + int countryEntryCount(LocationFilter filter) { + final countryCode = filter.countryCode; + if (countryCode == null) return 0; + return _filterEntryCountMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).length); + } + + int countrySize(LocationFilter filter) { + final countryCode = filter.countryCode; + if (countryCode == null) return 0; + return _filterSizeMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum); + } + + AvesEntry? countryRecentEntry(LocationFilter filter) { + final countryCode = filter.countryCode; + if (countryCode == null) return null; + return _filterRecentEntryMap.putIfAbsent(countryCode, () => sortedEntriesByDate.firstWhereOrNull(filter.test)); + } +} + +class CountriesChangedEvent {} + +class CountrySummaryInvalidatedEvent { + final Set? countryCodes; + + const CountrySummaryInvalidatedEvent(this.countryCodes); +} diff --git a/lib/model/source/location.dart b/lib/model/source/location/location.dart similarity index 76% rename from lib/model/source/location.dart rename to lib/model/source/location/location.dart index f063aab80..80703a5b6 100644 --- a/lib/model/source/location.dart +++ b/lib/model/source/location/location.dart @@ -6,15 +6,15 @@ import 'package:aves/model/filters/location.dart'; import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/analysis_controller.dart'; -import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/model/source/location/country.dart'; +import 'package:aves/model/source/location/place.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'; import 'package:tuple/tuple.dart'; -mixin LocationMixin on SourceBase { +mixin LocationMixin on CountryMixin, PlaceMixin { static const commitCountThreshold = 200; static const _stopCheckCountThreshold = 50; @@ -150,7 +150,7 @@ mixin LocationMixin on SourceBase { } void updateLocations() { - final locations = visibleEntries.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails).whereNotNull().toList(); + final locations = visibleEntries.map((entry) => entry.addressDetails).whereNotNull().toList(); final updatedPlaces = locations.map((address) => address.place).whereNotNull().where((v) => v.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase); if (!listEquals(updatedPlaces, sortedPlaces)) { sortedPlaces = List.unmodifiable(updatedPlaces); @@ -177,67 +177,6 @@ mixin LocationMixin on SourceBase { eventBus.fire(CountriesChangedEvent()); } } - - // filter summary - - // by country code - final Map _filterEntryCountMap = {}, _filterSizeMap = {}; - final Map _filterRecentEntryMap = {}; - - void invalidateCountryFilterSummary({ - Set? entries, - Set? countryCodes, - bool notify = true, - }) { - if (_filterEntryCountMap.isEmpty && _filterSizeMap.isEmpty && _filterRecentEntryMap.isEmpty) return; - - if (entries == null && countryCodes == null) { - _filterEntryCountMap.clear(); - _filterSizeMap.clear(); - _filterRecentEntryMap.clear(); - } else { - countryCodes ??= {}; - if (entries != null) { - countryCodes.addAll(entries.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.countryCode).whereNotNull()); - } - countryCodes.forEach((countryCode) { - _filterEntryCountMap.remove(countryCode); - _filterSizeMap.remove(countryCode); - _filterRecentEntryMap.remove(countryCode); - }); - } - if (notify) { - eventBus.fire(CountrySummaryInvalidatedEvent(countryCodes)); - } - } - - int countryEntryCount(LocationFilter filter) { - final countryCode = filter.countryCode; - if (countryCode == null) return 0; - return _filterEntryCountMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).length); - } - - int countrySize(LocationFilter filter) { - final countryCode = filter.countryCode; - if (countryCode == null) return 0; - return _filterSizeMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum); - } - - AvesEntry? countryRecentEntry(LocationFilter filter) { - final countryCode = filter.countryCode; - if (countryCode == null) return null; - return _filterRecentEntryMap.putIfAbsent(countryCode, () => sortedEntriesByDate.firstWhereOrNull(filter.test)); - } } class AddressMetadataChangedEvent {} - -class PlacesChangedEvent {} - -class CountriesChangedEvent {} - -class CountrySummaryInvalidatedEvent { - final Set? countryCodes; - - const CountrySummaryInvalidatedEvent(this.countryCodes); -} diff --git a/lib/model/source/location/place.dart b/lib/model/source/location/place.dart new file mode 100644 index 000000000..342202990 --- /dev/null +++ b/lib/model/source/location/place.dart @@ -0,0 +1,60 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/model/filters/location.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/utils/collection_utils.dart'; +import 'package:collection/collection.dart'; + +mixin PlaceMixin on SourceBase { + // filter summary + + // by place + final Map _filterEntryCountMap = {}, _filterSizeMap = {}; + final Map _filterRecentEntryMap = {}; + + void invalidatePlaceFilterSummary({ + Set? entries, + Set? places, + bool notify = true, + }) { + if (_filterEntryCountMap.isEmpty && _filterSizeMap.isEmpty && _filterRecentEntryMap.isEmpty) return; + + if (entries == null && places == null) { + _filterEntryCountMap.clear(); + _filterSizeMap.clear(); + _filterRecentEntryMap.clear(); + } else { + places ??= {}; + if (entries != null) { + places.addAll(entries.map((entry) => entry.addressDetails?.place).whereNotNull()); + } + places.forEach((place) { + _filterEntryCountMap.remove(place); + _filterSizeMap.remove(place); + _filterRecentEntryMap.remove(place); + }); + } + if (notify) { + eventBus.fire(PlaceSummaryInvalidatedEvent(places)); + } + } + + int placeEntryCount(LocationFilter filter) { + return _filterEntryCountMap.putIfAbsent(filter.place, () => visibleEntries.where(filter.test).length); + } + + int placeSize(LocationFilter filter) { + return _filterSizeMap.putIfAbsent(filter.place, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum); + } + + AvesEntry? placeRecentEntry(LocationFilter filter) { + return _filterRecentEntryMap.putIfAbsent(filter.place, () => sortedEntriesByDate.firstWhereOrNull(filter.test)); + } +} + +class PlacesChangedEvent {} + +class PlaceSummaryInvalidatedEvent { + final Set? places; + + const PlaceSummaryInvalidatedEvent(this.places); +} diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index a85aea75a..b801bb73f 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -36,6 +36,8 @@ class AIcons { static const IconData language = Icons.translate_outlined; static const IconData location = Icons.place_outlined; static const IconData locationUnlocated = Icons.location_off_outlined; + static const IconData country = Icons.flag_outlined; + static const IconData place = Icons.place_outlined; static const IconData mainStorage = Icons.smartphone_outlined; static const IconData mimeType = Icons.code_outlined; static const IconData opacity = Icons.opacity; diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index e23294c20..f37f7ed5a 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -96,6 +96,7 @@ class AvesFilterChip extends StatefulWidget { final actions = [ if (filter is AlbumFilter) ChipAction.goToAlbumPage, if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage, + if ((filter is LocationFilter && filter.level == LocationLevel.place)) ChipAction.goToPlacePage, if (filter is TagFilter) ChipAction.goToTagPage, ChipAction.reverse, ChipAction.hide, diff --git a/lib/widgets/debug/settings.dart b/lib/widgets/debug/settings.dart index ee08be83f..b03b03356 100644 --- a/lib/widgets/debug/settings.dart +++ b/lib/widgets/debug/settings.dart @@ -3,6 +3,7 @@ import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/viewer/info/common.dart'; import 'package:flutter/material.dart'; @@ -51,6 +52,7 @@ class DebugSettingsSection extends StatelessWidget { 'tileExtent - Collection': '${settings.getTileExtent(CollectionPage.routeName)}', 'tileExtent - Albums': '${settings.getTileExtent(AlbumListPage.routeName)}', 'tileExtent - Countries': '${settings.getTileExtent(CountryListPage.routeName)}', + 'tileExtent - Places': '${settings.getTileExtent(PlaceListPage.routeName)}', 'tileExtent - Tags': '${settings.getTileExtent(TagListPage.routeName)}', 'infoMapZoom': '${settings.infoMapZoom}', 'collectionSelectionQuickActions': '${settings.collectionSelectionQuickActions}', diff --git a/lib/widgets/filter_grids/common/action_delegates/chip.dart b/lib/widgets/filter_grids/common/action_delegates/chip.dart index 3679a812c..223d0aa38 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip.dart @@ -11,6 +11,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -23,6 +24,7 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin { switch (action) { case ChipAction.goToAlbumPage: case ChipAction.goToCountryPage: + case ChipAction.goToPlacePage: case ChipAction.goToTagPage: case ChipAction.reverse: return true; @@ -42,6 +44,9 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin { case ChipAction.goToCountryPage: _goTo(context, filter, CountryListPage.routeName, (context) => const CountryListPage()); break; + case ChipAction.goToPlacePage: + _goTo(context, filter, PlaceListPage.routeName, (context) => const PlaceListPage()); + break; case ChipAction.goToTagPage: _goTo(context, filter, TagListPage.routeName, (context) => const TagListPage()); break; diff --git a/lib/widgets/filter_grids/common/action_delegates/place_set.dart b/lib/widgets/filter_grids/common/action_delegates/place_set.dart new file mode 100644 index 000000000..35e5052bb --- /dev/null +++ b/lib/widgets/filter_grids/common/action_delegates/place_set.dart @@ -0,0 +1,33 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/location.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; + +class PlaceChipSetActionDelegate extends ChipSetActionDelegate { + final Iterable> _items; + + PlaceChipSetActionDelegate(Iterable> items) : _items = items; + + @override + Iterable> get allItems => _items; + + @override + ChipSortFactor get sortFactor => settings.placeSortFactor; + + @override + set sortFactor(ChipSortFactor factor) => settings.placeSortFactor = factor; + + @override + bool get sortReverse => settings.placeSortReverse; + + @override + set sortReverse(bool value) => settings.placeSortReverse = value; + + @override + TileLayout get tileLayout => settings.getTileLayout(PlaceListPage.routeName); + + @override + set tileLayout(TileLayout tileLayout) => settings.setTileLayout(PlaceListPage.routeName, tileLayout); +} diff --git a/lib/widgets/filter_grids/common/covered_filter_chip.dart b/lib/widgets/filter_grids/common/covered_filter_chip.dart index 4bf24cbd9..5187af03e 100644 --- a/lib/widgets/filter_grids/common/covered_filter_chip.dart +++ b/lib/widgets/filter_grids/common/covered_filter_chip.dart @@ -7,7 +7,7 @@ import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/location.dart'; +import 'package:aves/model/source/location/country.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/theme/durations.dart'; diff --git a/lib/widgets/filter_grids/countries_page.dart b/lib/widgets/filter_grids/countries_page.dart index 6f79a6d78..c44d4b4f6 100644 --- a/lib/widgets/filter_grids/countries_page.dart +++ b/lib/widgets/filter_grids/countries_page.dart @@ -3,7 +3,7 @@ import 'package:aves/model/filters/location.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums/enums.dart'; -import 'package:aves/model/source/location.dart'; +import 'package:aves/model/source/location/country.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; @@ -43,7 +43,7 @@ class CountryListPage extends StatelessWidget { filterSections: _groupToSections(gridItems), applyQuery: applyQuery, emptyBuilder: () => EmptyContent( - icon: AIcons.location, + icon: AIcons.country, text: context.l10n.countryEmpty, ), ); diff --git a/lib/widgets/filter_grids/places_page.dart b/lib/widgets/filter_grids/places_page.dart new file mode 100644 index 000000000..5c75843cb --- /dev/null +++ b/lib/widgets/filter_grids/places_page.dart @@ -0,0 +1,80 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/location.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/model/source/location/place.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/filter_grids/common/action_delegates/place_set.dart'; +import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart'; +import 'package:aves/widgets/filter_grids/common/section_keys.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; + +class PlaceListPage extends StatelessWidget { + static const routeName = '/places'; + + const PlaceListPage({super.key}); + + @override + Widget build(BuildContext context) { + final source = context.read(); + return Selector>>( + selector: (context, s) => Tuple3(s.placeSortFactor, s.placeSortReverse, s.pinnedFilters), + shouldRebuild: (t1, t2) { + // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` + const eq = DeepCollectionEquality(); + return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); + }, + builder: (context, s, child) { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) { + final gridItems = _getGridItems(source); + return FilterNavigationPage( + source: source, + title: context.l10n.placePageTitle, + sortFactor: settings.placeSortFactor, + actionDelegate: PlaceChipSetActionDelegate(gridItems), + filterSections: _groupToSections(gridItems), + applyQuery: applyQuery, + emptyBuilder: () => EmptyContent( + icon: AIcons.place, + text: context.l10n.placeEmpty, + ), + ); + }, + ); + }, + ); + } + + 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(); + + return FilterNavigationPage.sort(settings.placeSortFactor, settings.placeSortReverse, source, filters); + } + + static Map>> _groupToSections(Iterable> sortedMapEntries) { + final pinned = settings.pinnedFilters.whereType(); + final byPin = groupBy, bool>(sortedMapEntries, (e) => pinned.contains(e.filter)); + final pinnedMapEntries = (byPin[true] ?? []); + final unpinnedMapEntries = (byPin[false] ?? []); + + return { + if (pinnedMapEntries.isNotEmpty || unpinnedMapEntries.isNotEmpty) + const ChipSectionKey(): [ + ...pinnedMapEntries, + ...unpinnedMapEntries, + ], + }; + } +} diff --git a/lib/widgets/navigation/drawer/app_drawer.dart b/lib/widgets/navigation/drawer/app_drawer.dart index d2ab4380f..3e22c1b43 100644 --- a/lib/widgets/navigation/drawer/app_drawer.dart +++ b/lib/widgets/navigation/drawer/app_drawer.dart @@ -6,7 +6,8 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/location.dart'; +import 'package:aves/model/source/location/country.dart'; +import 'package:aves/model/source/location/place.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; @@ -19,6 +20,7 @@ import 'package:aves/widgets/common/identity/aves_logo.dart'; import 'package:aves/widgets/debug/app_debug_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/navigation/drawer/collection_nav_tile.dart'; import 'package:aves/widgets/navigation/drawer/page_nav_tile.dart'; @@ -242,6 +244,12 @@ class _AppDrawerState extends State { builder: (context, _) => Text('${source.sortedCountries.length}'), ); break; + case PlaceListPage.routeName: + trailing = StreamBuilder( + stream: source.eventBus.on(), + builder: (context, _) => Text('${source.sortedPlaces.length}'), + ); + break; case TagListPage.routeName: trailing = StreamBuilder( stream: source.eventBus.on(), diff --git a/lib/widgets/navigation/drawer/page_nav_tile.dart b/lib/widgets/navigation/drawer/page_nav_tile.dart index 1e77dbdd7..8315f82a8 100644 --- a/lib/widgets/navigation/drawer/page_nav_tile.dart +++ b/lib/widgets/navigation/drawer/page_nav_tile.dart @@ -7,6 +7,7 @@ import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/debug/app_debug_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/navigation/drawer/tile.dart'; import 'package:aves/widgets/search/search_delegate.dart'; @@ -88,6 +89,8 @@ class PageNavTile extends StatelessWidget { return (_) => const AlbumListPage(); case CountryListPage.routeName: return (_) => const CountryListPage(); + case PlaceListPage.routeName: + return (_) => const PlaceListPage(); case TagListPage.routeName: return (_) => const TagListPage(); case SettingsPage.routeName: diff --git a/lib/widgets/navigation/nav_display.dart b/lib/widgets/navigation/nav_display.dart index b21f7114f..7a0d3fb14 100644 --- a/lib/widgets/navigation/nav_display.dart +++ b/lib/widgets/navigation/nav_display.dart @@ -9,6 +9,7 @@ import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/debug/app_debug_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/settings/settings_page.dart'; import 'package:flutter/material.dart'; @@ -35,6 +36,8 @@ class NavigationDisplay { return l10n.drawerAlbumPage; case CountryListPage.routeName: return l10n.drawerCountryPage; + case PlaceListPage.routeName: + return l10n.drawerPlacePage; case TagListPage.routeName: return l10n.drawerTagPage; case SettingsPage.routeName: @@ -55,7 +58,9 @@ class NavigationDisplay { case AlbumListPage.routeName: return AIcons.album; case CountryListPage.routeName: - return AIcons.location; + return AIcons.country; + case PlaceListPage.routeName: + return AIcons.place; case TagListPage.routeName: return AIcons.tag; case SettingsPage.routeName: diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index bd6705ca9..7dba129a5 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -15,7 +15,8 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/location.dart'; +import 'package:aves/model/source/location/country.dart'; +import 'package:aves/model/source/location/place.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/widgets/collection/collection_page.dart'; diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart index 6cb73edfd..20e5a948e 100644 --- a/lib/widgets/settings/navigation/drawer.dart +++ b/lib/widgets/settings/navigation/drawer.dart @@ -6,6 +6,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/navigation/drawer/app_drawer.dart'; import 'package:aves/widgets/navigation/drawer/tile.dart'; @@ -40,6 +41,7 @@ class _NavigationDrawerEditorPageState extends State static const Set _pageOptions = { AlbumListPage.routeName, CountryListPage.routeName, + PlaceListPage.routeName, TagListPage.routeName, SearchPage.routeName, }; diff --git a/untranslated.json b/untranslated.json index 5cea5a17c..93b33381b 100644 --- a/untranslated.json +++ b/untranslated.json @@ -14,6 +14,7 @@ "chipActionDelete", "chipActionGoToAlbumPage", "chipActionGoToCountryPage", + "chipActionGoToPlacePage", "chipActionGoToTagPage", "chipActionFilterOut", "chipActionFilterIn", @@ -334,6 +335,7 @@ "drawerCollectionSphericalVideos", "drawerAlbumPage", "drawerCountryPage", + "drawerPlacePage", "drawerTagPage", "sortByDate", "sortByName", @@ -369,6 +371,8 @@ "newFilterBanner", "countryPageTitle", "countryEmpty", + "placePageTitle", + "placeEmpty", "tagPageTitle", "tagEmpty", "binPageTitle", @@ -591,6 +595,7 @@ ], "cs": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -609,11 +614,15 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsConfirmationVaultDataLoss", "settingsDisablingBinWarningDialogMessage" ], "de": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -632,11 +641,29 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsConfirmationVaultDataLoss", "settingsDisablingBinWarningDialogMessage" ], + "el": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + + "es": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + "eu": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -655,12 +682,16 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsConfirmationVaultDataLoss", "settingsDisablingBinWarningDialogMessage" ], "fa": [ "clearTooltip", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -858,6 +889,7 @@ "drawerCollectionSphericalVideos", "drawerAlbumPage", "drawerCountryPage", + "drawerPlacePage", "drawerTagPage", "sortByDate", "sortByName", @@ -892,6 +924,8 @@ "newFilterBanner", "countryPageTitle", "countryEmpty", + "placePageTitle", + "placeEmpty", "tagPageTitle", "tagEmpty", "binPageTitle", @@ -1120,8 +1154,16 @@ "filePickerUseThisFolder" ], + "fr": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + "gl": [ "columnCount", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -1344,6 +1386,7 @@ "drawerCollectionSphericalVideos", "drawerAlbumPage", "drawerCountryPage", + "drawerPlacePage", "drawerTagPage", "sortByDate", "sortByName", @@ -1379,6 +1422,8 @@ "newFilterBanner", "countryPageTitle", "countryEmpty", + "placePageTitle", + "placeEmpty", "tagPageTitle", "tagEmpty", "binPageTitle", @@ -1646,6 +1691,7 @@ "chipActionDelete", "chipActionGoToAlbumPage", "chipActionGoToCountryPage", + "chipActionGoToPlacePage", "chipActionGoToTagPage", "chipActionFilterOut", "chipActionFilterIn", @@ -1966,6 +2012,7 @@ "drawerCollectionSphericalVideos", "drawerAlbumPage", "drawerCountryPage", + "drawerPlacePage", "drawerTagPage", "sortByDate", "sortByName", @@ -2001,6 +2048,8 @@ "newFilterBanner", "countryPageTitle", "countryEmpty", + "placePageTitle", + "placeEmpty", "tagPageTitle", "tagEmpty", "binPageTitle", @@ -2235,7 +2284,15 @@ "filePickerUseThisFolder" ], + "id": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + "it": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2254,12 +2311,16 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsConfirmationVaultDataLoss", "settingsDisablingBinWarningDialogMessage" ], "ja": [ "columnCount", + "chipActionGoToPlacePage", "chipActionFilterIn", "chipActionLock", "chipActionCreateVault", @@ -2288,6 +2349,9 @@ "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "tooManyItemsErrorDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsModificationWarningDialogMessage", "settingsConfirmationVaultDataLoss", "settingsViewerShowDescription", @@ -2298,8 +2362,16 @@ "settingsWidgetDisplayedItem" ], + "ko": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + "lt": [ "columnCount", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2322,6 +2394,9 @@ "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "tooManyItemsErrorDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsModificationWarningDialogMessage", "settingsConfirmationVaultDataLoss", "settingsViewerShowDescription", @@ -2332,6 +2407,7 @@ ], "nb": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2350,12 +2426,16 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsConfirmationVaultDataLoss", "settingsDisablingBinWarningDialogMessage" ], "nl": [ "columnCount", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2389,6 +2469,9 @@ "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "tooManyItemsErrorDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsModificationWarningDialogMessage", "settingsConfirmationVaultDataLoss", "settingsViewerShowRatingTags", @@ -2405,6 +2488,7 @@ "nn": [ "columnCount", "sourceStateCataloguing", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2504,6 +2588,7 @@ "drawerCollectionSphericalVideos", "drawerAlbumPage", "drawerCountryPage", + "drawerPlacePage", "drawerTagPage", "sortByDate", "sortByName", @@ -2539,6 +2624,8 @@ "newFilterBanner", "countryPageTitle", "countryEmpty", + "placePageTitle", + "placeEmpty", "tagPageTitle", "tagEmpty", "binPageTitle", @@ -2709,8 +2796,16 @@ "wallpaperUseScrollEffect" ], + "pl": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + "pt": [ "columnCount", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2730,12 +2825,23 @@ "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "tooManyItemsErrorDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsConfirmationVaultDataLoss", "settingsVideoGestureVerticalDragBrightnessVolume", "settingsDisablingBinWarningDialogMessage" ], + "ro": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + "ru": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2757,6 +2863,9 @@ "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "tooManyItemsErrorDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsModificationWarningDialogMessage", "settingsConfirmationVaultDataLoss", "settingsVideoGestureVerticalDragBrightnessVolume", @@ -2768,6 +2877,7 @@ "itemCount", "columnCount", "timeSeconds", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -2905,6 +3015,7 @@ "drawerCollectionSphericalVideos", "drawerAlbumPage", "drawerCountryPage", + "drawerPlacePage", "drawerTagPage", "sortByDate", "sortByName", @@ -2940,6 +3051,8 @@ "newFilterBanner", "countryPageTitle", "countryEmpty", + "placePageTitle", + "placeEmpty", "tagPageTitle", "tagEmpty", "binPageTitle", @@ -3182,6 +3295,7 @@ "timeDays", "focalLength", "applyButtonLabel", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -3250,6 +3364,7 @@ "drawerCollectionSphericalVideos", "drawerAlbumPage", "drawerCountryPage", + "drawerPlacePage", "drawerTagPage", "sortByDate", "sortByName", @@ -3285,6 +3400,8 @@ "newFilterBanner", "countryPageTitle", "countryEmpty", + "placePageTitle", + "placeEmpty", "tagPageTitle", "tagEmpty", "binPageTitle", @@ -3520,6 +3637,7 @@ ], "tr": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -3538,11 +3656,22 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsConfirmationVaultDataLoss", "settingsDisablingBinWarningDialogMessage" ], + "uk": [ + "chipActionGoToPlacePage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty" + ], + "zh": [ + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -3564,6 +3693,9 @@ "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "tooManyItemsErrorDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsModificationWarningDialogMessage", "settingsConfirmationVaultDataLoss", "settingsViewerShowDescription", @@ -3575,6 +3707,7 @@ "zh_Hant": [ "columnCount", + "chipActionGoToPlacePage", "chipActionLock", "chipActionCreateVault", "chipActionConfigureVault", @@ -3596,6 +3729,9 @@ "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "tooManyItemsErrorDialogMessage", + "drawerPlacePage", + "placePageTitle", + "placeEmpty", "settingsModificationWarningDialogMessage", "settingsConfirmationVaultDataLoss", "settingsViewerShowDescription",