#423 map: remove cluster location
This commit is contained in:
parent
0cf7eafca9
commit
eb48027540
8 changed files with 197 additions and 49 deletions
|
@ -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",
|
||||||
|
|
30
lib/model/actions/map_cluster_actions.dart
Normal file
30
lib/model/actions/map_cluster_actions.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,17 +95,15 @@ 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,
|
MetadataField.exifGpsLongitude: longitude.abs(),
|
||||||
MetadataField.exifGpsLongitude: longitude.abs(),
|
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)));
|
||||||
|
|
||||||
|
@ -119,15 +119,13 @@ 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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
entry: entry,
|
return latLng != null
|
||||||
latitude: latLng.latitude,
|
? GeoEntry<AvesEntry>(
|
||||||
longitude: latLng.longitude,
|
entry: entry,
|
||||||
markerId: entry.uri,
|
latitude: latLng.latitude,
|
||||||
);
|
longitude: latLng.longitude,
|
||||||
}).toList();
|
markerId: entry.uri,
|
||||||
|
)
|
||||||
|
: 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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue