#423 map: remove cluster location

This commit is contained in:
Thibault Deckers 2022-12-06 00:36:12 +01:00
parent 0cf7eafca9
commit eb48027540
8 changed files with 197 additions and 49 deletions

View file

@ -123,6 +123,7 @@
"entryInfoActionEditTags": "Edit tags", "entryInfoActionEditTags": "Edit tags",
"entryInfoActionRemoveMetadata": "Remove metadata", "entryInfoActionRemoveMetadata": "Remove metadata",
"entryInfoActionExportMetadata": "Export metadata", "entryInfoActionExportMetadata": "Export metadata",
"entryInfoActionRemoveLocation": "Remove location",
"filterAspectRatioLandscapeLabel": "Landscape", "filterAspectRatioLandscapeLabel": "Landscape",
"filterAspectRatioPortraitLabel": "Portrait", "filterAspectRatioPortraitLabel": "Portrait",

View file

@ -0,0 +1,30 @@
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart';
enum MapClusterAction {
editLocation,
removeLocation,
}
extension ExtraMapClusterAction on MapClusterAction {
String getText(BuildContext context) {
switch (this) {
case MapClusterAction.editLocation:
return context.l10n.entryInfoActionEditLocation;
case MapClusterAction.removeLocation:
return context.l10n.entryInfoActionRemoveLocation;
}
}
Widget getIcon() => Icon(_getIconData());
IconData _getIconData() {
switch (this) {
case MapClusterAction.editLocation:
return AIcons.edit;
case MapClusterAction.removeLocation:
return AIcons.clear;
}
}
}

View file

@ -83,6 +83,8 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
return dataTypes; return dataTypes;
} }
static final removalLocation = LatLng(0, 0);
Future<Set<EntryDataType>> editLocation(LatLng? latLng) async { Future<Set<EntryDataType>> editLocation(LatLng? latLng) async {
final dataTypes = <EntryDataType>{}; final dataTypes = <EntryDataType>{};
final metadata = <MetadataType, dynamic>{}; final metadata = <MetadataType, dynamic>{};
@ -93,10 +95,9 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
// clear every GPS field // clear every GPS field
final exifFields = Map<MetadataField, dynamic>.fromEntries(MetadataFields.exifGpsFields.map((k) => MapEntry(k, null))); final exifFields = Map<MetadataField, dynamic>.fromEntries(MetadataFields.exifGpsFields.map((k) => MapEntry(k, null)));
// add latitude & longitude, if any // add latitude & longitude, if any
if (latLng != null) { if (latLng != null && latLng != removalLocation) {
final latitude = latLng.latitude; final latitude = latLng.latitude;
final longitude = latLng.longitude; final longitude = latLng.longitude;
if (latitude != 0 && longitude != 0) {
exifFields.addAll({ exifFields.addAll({
MetadataField.exifGpsLatitude: latitude.abs(), MetadataField.exifGpsLatitude: latitude.abs(),
MetadataField.exifGpsLatitudeRef: latitude >= 0 ? Exif.latitudeNorth : Exif.latitudeSouth, MetadataField.exifGpsLatitudeRef: latitude >= 0 ? Exif.latitudeNorth : Exif.latitudeSouth,
@ -104,7 +105,6 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
MetadataField.exifGpsLongitudeRef: longitude >= 0 ? Exif.longitudeEast : Exif.longitudeWest, MetadataField.exifGpsLongitudeRef: longitude >= 0 ? Exif.longitudeEast : Exif.longitudeWest,
}); });
} }
}
metadata[MetadataType.exif] = Map<String, dynamic>.fromEntries(exifFields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value))); metadata[MetadataType.exif] = Map<String, dynamic>.fromEntries(exifFields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value)));
if (canEditXmp && missingDate != null) { if (canEditXmp && missingDate != null) {
@ -119,16 +119,14 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
final mp4Fields = <MetadataField, String?>{}; final mp4Fields = <MetadataField, String?>{};
String? iso6709String; String? iso6709String;
if (latLng != null) { if (latLng != null && latLng != removalLocation) {
final latitude = latLng.latitude; final latitude = latLng.latitude;
final longitude = latLng.longitude; final longitude = latLng.longitude;
if (latitude != 0 && longitude != 0) {
const locale = 'en_US'; const locale = 'en_US';
final isoLat = '${latitude >= 0 ? '+' : '-'}${NumberFormat('00.0000', locale).format(latitude.abs())}'; final isoLat = '${latitude >= 0 ? '+' : '-'}${NumberFormat('00.0000', locale).format(latitude.abs())}';
final isoLon = '${longitude >= 0 ? '+' : '-'}${NumberFormat('000.0000', locale).format(longitude.abs())}'; final isoLon = '${longitude >= 0 ? '+' : '-'}${NumberFormat('000.0000', locale).format(longitude.abs())}';
iso6709String = '$isoLat$isoLon/'; iso6709String = '$isoLat$isoLon/';
} }
}
mp4Fields[MetadataField.mp4GpsCoordinates] = iso6709String; mp4Fields[MetadataField.mp4GpsCoordinates] = iso6709String;
if (missingDate != null) { if (missingDate != null) {

View file

@ -509,7 +509,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
await _edit(context, entries, (entry) => entry.editLocation(location)); await _edit(context, entries, (entry) => entry.editLocation(location));
} }
Future<LatLng?> quickLocationByMap(BuildContext context, Set<AvesEntry> entries, LatLng clusterLocation, CollectionLens mapCollection) async { Future<LatLng?> editLocationByMap(BuildContext context, Set<AvesEntry> entries, LatLng clusterLocation, CollectionLens mapCollection) async {
final editableEntries = await _getEditableItems(context, entries, canEdit: (entry) => entry.canEditLocation); final editableEntries = await _getEditableItems(context, entries, canEdit: (entry) => entry.canEditLocation);
if (editableEntries == null || editableEntries.isEmpty) return null; if (editableEntries == null || editableEntries.isEmpty) return null;
@ -530,6 +530,33 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
return location; return location;
} }
Future<void> removeLocation(BuildContext context, Set<AvesEntry> entries) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return AvesDialog(
content: Text(context.l10n.convertMotionPhotoToStillImageWarningDialogMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(context.l10n.applyButtonLabel),
),
],
);
},
);
if (confirmed == null || !confirmed) return;
final editableEntries = await _getEditableItems(context, entries, canEdit: (entry) => entry.canEditLocation);
if (editableEntries == null || editableEntries.isEmpty) return;
await _edit(context, editableEntries, (entry) => entry.editLocation(ExtraAvesEntryMetadataEdition.removalLocation));
}
Future<void> _editTitleDescription(BuildContext context) async { Future<void> _editTitleDescription(BuildContext context) async {
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditTitleDescription); final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditTitleDescription);
if (entries == null || entries.isEmpty) return; if (entries == null || entries.isEmpty) return;

View file

@ -38,8 +38,17 @@ class GeoMap extends StatefulWidget {
final MapOverlay? overlayEntry; final MapOverlay? overlayEntry;
final UserZoomChangeCallback? onUserZoomChange; final UserZoomChangeCallback? onUserZoomChange;
final MapTapCallback? onMapTap; final MapTapCallback? onMapTap;
final void Function(LatLng clusterLocation, AvesEntry markerEntry)? onMarkerTap; final void Function(
final void Function(Offset tapLocalPosition, Set<AvesEntry> clusterEntries, LatLng clusterLocation, WidgetBuilder markerBuilder)? onMarkerLongPress; LatLng markerLocation,
AvesEntry markerEntry,
)? onMarkerTap;
final void Function(
LatLng markerLocation,
AvesEntry markerEntry,
Set<AvesEntry> clusterEntries,
Offset tapLocalPosition,
WidgetBuilder markerBuilder,
)? onMarkerLongPress;
final void Function(BuildContext context)? openMapPage; final void Function(BuildContext context)? openMapPage;
const GeoMap({ const GeoMap({
@ -360,15 +369,20 @@ class _GeoMapState extends State<GeoMap> {
} }
Fluster<GeoEntry<AvesEntry>> _buildFluster({int nodeSize = 64}) { Fluster<GeoEntry<AvesEntry>> _buildFluster({int nodeSize = 64}) {
final markers = entries.map((entry) { final markers = entries
final latLng = entry.latLng!; .map((entry) {
return GeoEntry<AvesEntry>( final latLng = entry.latLng;
return latLng != null
? GeoEntry<AvesEntry>(
entry: entry, entry: entry,
latitude: latLng.latitude, latitude: latLng.latitude,
longitude: latLng.longitude, longitude: latLng.longitude,
markerId: entry.uri, markerId: entry.uri,
); )
}).toList(); : null;
})
.whereNotNull()
.toList();
return Fluster<GeoEntry<AvesEntry>>( return Fluster<GeoEntry<AvesEntry>>(
// we keep clustering on the whole range of zooms (including the maximum) // we keep clustering on the whole range of zooms (including the maximum)
@ -433,8 +447,8 @@ class _GeoMapState extends State<GeoMap> {
} }
if (markerEntry == null) return; if (markerEntry == null) return;
final clusterLocation = LatLng(geoEntry.latitude!, geoEntry.longitude!); final markerLocation = LatLng(geoEntry.latitude!, geoEntry.longitude!);
onTap(clusterLocation, markerEntry); onTap(markerLocation, markerEntry);
} }
Future<void> _onMarkerLongPress(GeoEntry<AvesEntry> geoEntry, LatLng tapLocation) async { Future<void> _onMarkerLongPress(GeoEntry<AvesEntry> geoEntry, LatLng tapLocation) async {
@ -451,7 +465,7 @@ class _GeoMapState extends State<GeoMap> {
} else { } else {
markerEntry = geoEntry.entry!; markerEntry = geoEntry.entry!;
} }
final clusterLocation = LatLng(geoEntry.latitude!, geoEntry.longitude!); final markerLocation = LatLng(geoEntry.latitude!, geoEntry.longitude!);
Widget markerBuilder(BuildContext context) => ImageMarker( Widget markerBuilder(BuildContext context) => ImageMarker(
count: geoEntry.pointsSize, count: geoEntry.pointsSize,
drawArrow: false, drawArrow: false,
@ -460,7 +474,13 @@ class _GeoMapState extends State<GeoMap> {
extent: extent, extent: extent,
), ),
); );
onMarkerLongPress(tapLocalPosition, clusterEntries, clusterLocation, markerBuilder); onMarkerLongPress(
markerLocation,
markerEntry,
clusterEntries,
tapLocalPosition,
markerBuilder,
);
} }
Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child); Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child);

View file

@ -1,4 +1,5 @@
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_metadata_edition.dart';
import 'package:aves/model/metadata/enums/enums.dart'; import 'package:aves/model/metadata/enums/enums.dart';
import 'package:aves/model/metadata/enums/location_edit_action.dart'; import 'package:aves/model/metadata/enums/location_edit_action.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart'; import 'package:aves/model/settings/enums/coordinate_format.dart';
@ -341,7 +342,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
Navigator.pop(context, _parseLatLng()); Navigator.pop(context, _parseLatLng());
break; break;
case LocationEditAction.remove: case LocationEditAction.remove:
Navigator.pop(context, LatLng(0, 0)); Navigator.pop(context, ExtraAvesEntryMetadataEdition.removalLocation);
break; break;
} }
} }

View file

@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/entry_set_actions.dart'; import 'package:aves/model/actions/map_cluster_actions.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/coordinate.dart'; import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
@ -10,6 +10,7 @@ import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/tag.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/utils/debouncer.dart'; import 'package:aves/utils/debouncer.dart';
@ -102,6 +103,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true); final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
late AnimationController _overlayAnimationController; late AnimationController _overlayAnimationController;
late Animation<double> _overlayScale, _scrollerSize; late Animation<double> _overlayScale, _scrollerSize;
CoordinateFilter? _regionFilter;
CollectionLens? get regionCollection => _regionCollectionNotifier.value; CollectionLens? get regionCollection => _regionCollectionNotifier.value;
@ -119,7 +121,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
}); });
} }
_dotEntryNotifier.addListener(_updateInfoEntry); _dotEntryNotifier.addListener(_onSelectedEntryChanged);
_overlayAnimationController = AnimationController( _overlayAnimationController = AnimationController(
duration: context.read<DurationsData>().viewerOverlayAnimation, duration: context.read<DurationsData>().viewerOverlayAnimation,
@ -136,6 +138,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_overlayVisible.addListener(_onOverlayVisibleChange); _overlayVisible.addListener(_onOverlayVisibleChange);
_subscriptions.add(_mapController.idleUpdates.listen((event) => _onIdle(event.bounds))); _subscriptions.add(_mapController.idleUpdates.listen((event) => _onIdle(event.bounds)));
_subscriptions.add(openingCollection.source.eventBus.on<CatalogMetadataChangedEvent>().listen((e) => _updateRegionCollection()));
_selectedIndexNotifier.addListener(_onThumbnailIndexChange); _selectedIndexNotifier.addListener(_onThumbnailIndexChange);
Future.delayed(Durations.pageTransitionAnimation * timeDilation + const Duration(seconds: 1), () { Future.delayed(Durations.pageTransitionAnimation * timeDilation + const Duration(seconds: 1), () {
@ -158,12 +161,13 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
_dotEntryNotifier.removeListener(_updateInfoEntry); _dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
_dotEntryNotifier.removeListener(_onSelectedEntryChanged);
_overlayAnimationController.dispose(); _overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange); _overlayVisible.removeListener(_onOverlayVisibleChange);
_mapController.dispose(); _mapController.dispose();
_selectedIndexNotifier.removeListener(_onThumbnailIndexChange); _selectedIndexNotifier.removeListener(_onThumbnailIndexChange);
_regionCollectionNotifier.value?.dispose(); regionCollection?.dispose();
super.dispose(); super.dispose();
} }
@ -344,6 +348,14 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
} }
void _onIdle(ZoomedBounds bounds) { void _onIdle(ZoomedBounds bounds) {
_regionFilter = CoordinateFilter(bounds.sw, bounds.ne);
_updateRegionCollection();
}
void _updateRegionCollection() {
final regionFilter = _regionFilter;
if (regionFilter == null) return;
AvesEntry? selectedEntry; AvesEntry? selectedEntry;
if (regionCollection != null) { if (regionCollection != null) {
final regionEntries = regionCollection!.sortedEntries; final regionEntries = regionCollection!.sortedEntries;
@ -351,11 +363,11 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
selectedEntry = selectedIndex != null && 0 <= selectedIndex && selectedIndex < regionEntries.length ? regionEntries[selectedIndex] : null; selectedEntry = selectedIndex != null && 0 <= selectedIndex && selectedIndex < regionEntries.length ? regionEntries[selectedIndex] : null;
} }
final oldRegionCollection = _regionCollectionNotifier.value; final oldRegionCollection = regionCollection;
final newRegionCollection = openingCollection.copyWith( final newRegionCollection = openingCollection.copyWith(
filters: { filters: {
...openingCollection.filters.whereNot((v) => v is CoordinateFilter), ...openingCollection.filters.whereNot((v) => v is CoordinateFilter),
CoordinateFilter(bounds.sw, bounds.ne), regionFilter,
}, },
); );
_regionCollectionNotifier.value = newRegionCollection; _regionCollectionNotifier.value = newRegionCollection;
@ -389,11 +401,17 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
void _onThumbnailIndexChange() => _onEntrySelected(_getRegionEntry(_selectedIndexNotifier.value)); void _onThumbnailIndexChange() => _onEntrySelected(_getRegionEntry(_selectedIndexNotifier.value));
void _onEntrySelected(AvesEntry? selectedEntry) { void _onEntrySelected(AvesEntry? selectedEntry) {
_dotLocationNotifier.value = selectedEntry?.latLng; _dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
_dotEntryNotifier.value = selectedEntry; _dotEntryNotifier.value = selectedEntry;
selectedEntry?.metadataChangeNotifier.addListener(_onMarkerEntryMetadataChanged);
_onMarkerEntryMetadataChanged();
} }
void _updateInfoEntry() { void _onMarkerEntryMetadataChanged() {
_dotLocationNotifier.value = _dotEntryNotifier.value?.latLng;
}
void _onSelectedEntryChanged() {
final selectedEntry = _dotEntryNotifier.value; final selectedEntry = _dotEntryNotifier.value;
if (_infoEntryNotifier.value == null || selectedEntry == null) { if (_infoEntryNotifier.value == null || selectedEntry == null) {
_infoEntryNotifier.value = selectedEntry; _infoEntryNotifier.value = selectedEntry;
@ -461,14 +479,15 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
// cluster context menu // cluster context menu
Future<void> _onMarkerLongPress( Future<void> _onMarkerLongPress(
Offset tapLocalPosition, LatLng markerLocation,
AvesEntry markerEntry,
Set<AvesEntry> clusterEntries, Set<AvesEntry> clusterEntries,
LatLng clusterLocation, Offset tapLocalPosition,
WidgetBuilder markerBuilder, WidgetBuilder markerBuilder,
) async { ) async {
final overlay = Overlay.of(context)!.context.findRenderObject() as RenderBox; final overlay = Overlay.of(context)!.context.findRenderObject() as RenderBox;
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension); const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
final selectedAction = await showMenu<EntrySetAction>( final selectedAction = await showMenu<MapClusterAction>(
context: context, context: context,
position: RelativeRect.fromRect(tapLocalPosition & touchArea, Offset.zero & overlay.size), position: RelativeRect.fromRect(tapLocalPosition & touchArea, Offset.zero & overlay.size),
items: [ items: [
@ -483,26 +502,36 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
), ),
), ),
const PopupMenuDivider(), const PopupMenuDivider(),
_buildMenuItem(EntrySetAction.editLocation), ...[
MapClusterAction.editLocation,
MapClusterAction.removeLocation,
].map(_buildMenuItem),
], ],
); );
if (selectedAction != null) { if (selectedAction != null) {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(Durations.popupMenuAnimation * timeDilation); await Future.delayed(Durations.popupMenuAnimation * timeDilation);
final delegate = EntrySetActionDelegate();
switch (selectedAction) { switch (selectedAction) {
case EntrySetAction.editLocation: case MapClusterAction.editLocation:
final location = await EntrySetActionDelegate().quickLocationByMap(context, clusterEntries, clusterLocation, openingCollection); final regionEntries = regionCollection?.sortedEntries ?? [];
final markerIndex = regionEntries.indexOf(markerEntry);
final location = await delegate.editLocationByMap(context, clusterEntries, markerLocation, openingCollection);
if (location != null) { if (location != null) {
if (markerIndex != -1) {
_selectedIndexNotifier.value = markerIndex;
}
_mapController.moveTo(location); _mapController.moveTo(location);
} }
break; break;
default: case MapClusterAction.removeLocation:
await delegate.removeLocation(context, clusterEntries);
break; break;
} }
} }
} }
PopupMenuItem<EntrySetAction> _buildMenuItem(EntrySetAction action) { PopupMenuItem<MapClusterAction> _buildMenuItem(MapClusterAction action) {
return PopupMenuItem( return PopupMenuItem(
value: action, value: action,
child: MenuIconTheme( child: MenuIconTheme(

View file

@ -86,6 +86,7 @@
"entryInfoActionEditTags", "entryInfoActionEditTags",
"entryInfoActionRemoveMetadata", "entryInfoActionRemoveMetadata",
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterBinLabel", "filterBinLabel",
@ -593,13 +594,22 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"de": [
"entryInfoActionRemoveLocation"
],
"el": [ "el": [
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
"settingsViewerShowRatingTags" "settingsViewerShowRatingTags"
], ],
"es": [
"entryInfoActionRemoveLocation"
],
"fa": [ "fa": [
"appName", "appName",
"welcomeMessage", "welcomeMessage",
@ -687,6 +697,7 @@
"entryInfoActionEditTags", "entryInfoActionEditTags",
"entryInfoActionRemoveMetadata", "entryInfoActionRemoveMetadata",
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterBinLabel", "filterBinLabel",
@ -1194,8 +1205,13 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"fr": [
"entryInfoActionRemoveLocation"
],
"gl": [ "gl": [
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -1657,6 +1673,7 @@
"id": [ "id": [
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -1671,6 +1688,7 @@
], ],
"it": [ "it": [
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -1680,6 +1698,7 @@
"ja": [ "ja": [
"chipActionFilterIn", "chipActionFilterIn",
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -1693,8 +1712,21 @@
"settingsWidgetDisplayedItem" "settingsWidgetDisplayedItem"
], ],
"ko": [
"entryInfoActionRemoveLocation"
],
"lt": [
"entryInfoActionRemoveLocation"
],
"nb": [
"entryInfoActionRemoveLocation"
],
"nl": [ "nl": [
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -1715,6 +1747,7 @@
"timeDays", "timeDays",
"focalLength", "focalLength",
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -2211,6 +2244,7 @@
"pt": [ "pt": [
"entryInfoActionExportMetadata", "entryInfoActionExportMetadata",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -2225,6 +2259,7 @@
], ],
"ro": [ "ro": [
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -2232,6 +2267,7 @@
], ],
"ru": [ "ru": [
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -2247,6 +2283,7 @@
"applyButtonLabel", "applyButtonLabel",
"entryActionShowGeoTiffOnMap", "entryActionShowGeoTiffOnMap",
"videoActionCaptureFrame", "videoActionCaptureFrame",
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",
@ -2612,7 +2649,12 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"tr": [
"entryInfoActionRemoveLocation"
],
"zh": [ "zh": [
"entryInfoActionRemoveLocation",
"filterAspectRatioLandscapeLabel", "filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel", "filterAspectRatioPortraitLabel",
"filterNoAddressLabel", "filterNoAddressLabel",