mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
chore(mobile): replace maplibre_gl with maplibre
maplibre is a ground-up rewrite of maplibre_gl with a more modern and ergonomic API. It should fix a few bugs we've seen with maps, and perform better.
This commit is contained in:
@@ -17,7 +17,7 @@ import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/common/user_avatar.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:maplibre/maplibre.dart';
|
||||
|
||||
@RoutePage()
|
||||
class LibraryPage extends ConsumerWidget {
|
||||
@@ -325,7 +325,7 @@ class PlacesCollectionCard extends StatelessWidget {
|
||||
child: IgnorePointer(
|
||||
child: MapThumbnail(
|
||||
zoom: 8,
|
||||
centre: const LatLng(21.44950, -157.91959),
|
||||
centre: const Geographic(lat: 21.44950, lon: -157.91959),
|
||||
showAttribution: false,
|
||||
themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
),
|
||||
|
||||
@@ -15,12 +15,12 @@ import 'package:immich_mobile/providers/search/search_page_state.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:maplibre/maplibre.dart';
|
||||
|
||||
@RoutePage()
|
||||
class PlacesCollectionPage extends HookConsumerWidget {
|
||||
const PlacesCollectionPage({super.key, this.currentLocation});
|
||||
final LatLng? currentLocation;
|
||||
final Geographic? currentLocation;
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final places = ref.watch(getAllPlacesProvider);
|
||||
@@ -61,7 +61,7 @@ class PlacesCollectionPage extends HookConsumerWidget {
|
||||
child: MapThumbnail(
|
||||
onTap: (_, __) => context.pushRoute(MapRoute(initialLocation: currentLocation)),
|
||||
zoom: 8,
|
||||
centre: currentLocation ?? const LatLng(21.44950, -157.91959),
|
||||
centre: currentLocation ?? const Geographic(lat: 21.44950, lon: -157.91959),
|
||||
showAttribution: false,
|
||||
themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -12,8 +11,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/latlngbounds_extension.dart';
|
||||
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
|
||||
import 'package:immich_mobile/models/map/map_event.model.dart';
|
||||
import 'package:immich_mobile/models/map/map_event.model.dart' as app;
|
||||
import 'package:immich_mobile/models/map/map_marker.model.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||
@@ -26,25 +26,25 @@ import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
||||
import 'package:immich_mobile/utils/map_utils.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
import 'package:immich_mobile/widgets/map/asset_marker_icon.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_asset_grid.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_bottom_sheet.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_theme_override.dart';
|
||||
import 'package:immich_mobile/widgets/map/positioned_asset_marker_icon.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:maplibre/maplibre.dart';
|
||||
|
||||
@RoutePage()
|
||||
class MapPage extends HookConsumerWidget {
|
||||
const MapPage({super.key, this.initialLocation});
|
||||
final LatLng? initialLocation;
|
||||
final Geographic? initialLocation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final mapController = useRef<MapLibreMapController?>(null);
|
||||
final mapController = useRef<MapController?>(null);
|
||||
final markers = useRef<List<MapMarker>>([]);
|
||||
final markersInBounds = useRef<List<MapMarker>>([]);
|
||||
final bottomSheetStreamController = useStreamController<MapEvent>();
|
||||
final selectedMarker = useValueNotifier<_AssetMarkerMeta?>(null);
|
||||
final bottomSheetStreamController = useStreamController<app.MapEvent>();
|
||||
final selectedMarker = useValueNotifier<MapMarker?>(null);
|
||||
final assetsDebouncer = useDebouncer();
|
||||
final layerDebouncer = useDebouncer(interval: const Duration(seconds: 1));
|
||||
final isLoading = useProcessingOverlay();
|
||||
@@ -55,19 +55,17 @@ class MapPage extends HookConsumerWidget {
|
||||
|
||||
// updates the markersInBounds value with the map markers that are visible in the current
|
||||
// map camera bounds
|
||||
Future<void> updateAssetsInBounds() async {
|
||||
// Guard map not created
|
||||
if (mapController.value == null) {
|
||||
return;
|
||||
}
|
||||
void updateAssetsInBounds() {
|
||||
if (mapController.value == null) return;
|
||||
|
||||
final bounds = await mapController.value!.getVisibleRegion();
|
||||
final bounds = mapController.value!.getVisibleRegion();
|
||||
final inBounds = markers.value
|
||||
.where((m) => bounds.contains(LatLng(m.latLng.latitude, m.latLng.longitude)))
|
||||
.where((m) => bounds.contains(Geographic(lat: m.latLng.lat, lon: m.latLng.lon)))
|
||||
.toList();
|
||||
|
||||
// Notify bottom sheet to update asset grid only when there are new assets
|
||||
if (markersInBounds.value.length != inBounds.length) {
|
||||
bottomSheetStreamController.add(MapAssetsInBoundsUpdated(inBounds.map((e) => e.assetRemoteId).toList()));
|
||||
bottomSheetStreamController.add(app.MapAssetsInBoundsUpdated(inBounds.map((e) => e.assetRemoteId).toList()));
|
||||
}
|
||||
markersInBounds.value = inBounds;
|
||||
}
|
||||
@@ -99,57 +97,67 @@ class MapPage extends HookConsumerWidget {
|
||||
|
||||
// Refetch markers when map state is changed
|
||||
ref.listen(mapStateNotifierProvider, (_, current) {
|
||||
if (current.shouldRefetchMarkers) {
|
||||
markerDebouncer.run(() {
|
||||
ref.invalidate(mapMarkersProvider);
|
||||
// Reset marker
|
||||
selectedMarker.value = null;
|
||||
loadMarkers();
|
||||
ref.read(mapStateNotifierProvider.notifier).setRefetchMarkers(false);
|
||||
});
|
||||
}
|
||||
if (!current.shouldRefetchMarkers) return;
|
||||
|
||||
markerDebouncer.run(() {
|
||||
ref.invalidate(mapMarkersProvider);
|
||||
// Reset marker
|
||||
selectedMarker.value = null;
|
||||
loadMarkers();
|
||||
ref.read(mapStateNotifierProvider.notifier).setRefetchMarkers(false);
|
||||
});
|
||||
});
|
||||
|
||||
// updates the selected markers position based on the current map camera
|
||||
Future<void> updateAssetMarkerPosition(MapMarker marker, {bool shouldAnimate = true}) async {
|
||||
final assetPoint = await mapController.value!.toScreenLocation(marker.latLng);
|
||||
selectedMarker.value = _AssetMarkerMeta(point: assetPoint, marker: marker, shouldAnimate: shouldAnimate);
|
||||
(assetPoint, marker, shouldAnimate);
|
||||
void selectMarker(MapMarker marker) {
|
||||
selectedMarker.value = marker;
|
||||
}
|
||||
|
||||
// finds the nearest asset marker from the tap point and store it as the selectedMarker
|
||||
Future<void> onMarkerClicked(Point<double> point, LatLng _) async {
|
||||
// Guard map not created
|
||||
if (mapController.value == null) {
|
||||
return;
|
||||
}
|
||||
final latlngBound = await mapController.value!.getBoundsFromPoint(point, 50);
|
||||
final marker = markersInBounds.value.firstWhereOrNull(
|
||||
(m) => latlngBound.contains(LatLng(m.latLng.latitude, m.latLng.longitude)),
|
||||
void onMarkerClicked(Offset point) {
|
||||
if (mapController.value == null) return;
|
||||
|
||||
final features = mapController.value!.featuresInRect(
|
||||
Rect.fromCircle(center: point, radius: 50),
|
||||
layerIds: [MapUtils.defaultHeatMapLayerId],
|
||||
);
|
||||
|
||||
final featureId = features.firstOrNull?.id?.toString();
|
||||
|
||||
final marker = featureId != null
|
||||
? markersInBounds.value.firstWhereOrNull((m) => m.assetRemoteId == featureId)
|
||||
: null;
|
||||
|
||||
if (marker != null) {
|
||||
await updateAssetMarkerPosition(marker);
|
||||
} else {
|
||||
// If no asset was previously selected and no new asset is available, close the bottom sheet
|
||||
if (selectedMarker.value == null) {
|
||||
bottomSheetStreamController.add(const MapCloseBottomSheet());
|
||||
}
|
||||
selectedMarker.value = null;
|
||||
selectMarker(marker);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMarker.value == null) {
|
||||
// If no asset was previously selected and no new asset is available,
|
||||
// close the bottom sheet.
|
||||
bottomSheetStreamController.add(const app.MapCloseBottomSheet());
|
||||
return;
|
||||
}
|
||||
|
||||
selectedMarker.value = null;
|
||||
}
|
||||
|
||||
void onMapCreated(MapLibreMapController controller) async {
|
||||
void onMapCreated(MapController controller) {
|
||||
mapController.value = controller;
|
||||
controller.addListener(() {
|
||||
if (controller.isCameraMoving && selectedMarker.value != null) {
|
||||
updateAssetMarkerPosition(selectedMarker.value!.marker, shouldAnimate: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onMapEvent(MapEvent event) {
|
||||
switch (event) {
|
||||
case MapEventClick():
|
||||
onMarkerClicked(event.screenPoint);
|
||||
case MapEventCameraIdle():
|
||||
assetsDebouncer.run(updateAssetsInBounds);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onMarkerTapped() async {
|
||||
final assetId = selectedMarker.value?.marker.assetRemoteId;
|
||||
final assetId = selectedMarker.value?.assetRemoteId;
|
||||
if (assetId == null) {
|
||||
return;
|
||||
}
|
||||
@@ -171,14 +179,10 @@ class MapPage extends HookConsumerWidget {
|
||||
|
||||
/// BOTTOM SHEET CALLBACKS
|
||||
|
||||
Future<void> onMapMoved() async {
|
||||
assetsDebouncer.run(updateAssetsInBounds);
|
||||
}
|
||||
|
||||
void onBottomSheetScrolled(String assetRemoteId) {
|
||||
final assetMarker = markersInBounds.value.firstWhereOrNull((m) => m.assetRemoteId == assetRemoteId);
|
||||
if (assetMarker != null) {
|
||||
updateAssetMarkerPosition(assetMarker);
|
||||
selectMarker(assetMarker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,10 +191,11 @@ class MapPage extends HookConsumerWidget {
|
||||
if (mapController.value != null && assetMarker != null) {
|
||||
// Offset the latitude a little to show the marker just above the viewports center
|
||||
final offset = context.isMobile ? 0.02 : 0;
|
||||
final latlng = LatLng(assetMarker.latLng.latitude - offset, assetMarker.latLng.longitude);
|
||||
final latlng = Geographic(lat: assetMarker.latLng.lat - offset, lon: assetMarker.latLng.lon);
|
||||
mapController.value!.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(latlng, mapZoomToAssetLevel),
|
||||
duration: const Duration(milliseconds: 800),
|
||||
center: latlng,
|
||||
zoom: mapZoomToAssetLevel,
|
||||
nativeDuration: Durations.extralong2,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -211,8 +216,9 @@ class MapPage extends HookConsumerWidget {
|
||||
|
||||
if (mapController.value != null && location != null) {
|
||||
await mapController.value!.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), mapZoomToAssetLevel),
|
||||
duration: const Duration(milliseconds: 800),
|
||||
center: Geographic(lat: location.latitude, lon: location.longitude),
|
||||
zoom: mapZoomToAssetLevel,
|
||||
nativeDuration: Durations.extralong2,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -234,9 +240,8 @@ class MapPage extends HookConsumerWidget {
|
||||
style: style,
|
||||
selectedMarker: selectedMarker,
|
||||
onMapCreated: onMapCreated,
|
||||
onMapMoved: onMapMoved,
|
||||
onMapClicked: onMarkerClicked,
|
||||
onStyleLoaded: reloadLayers,
|
||||
onMapEvent: onMapEvent,
|
||||
onStyleLoaded: (_) => reloadLayers(),
|
||||
onMarkerTapped: onMarkerTapped,
|
||||
),
|
||||
// Should be a part of the body and not scaffold::bottomsheet for the
|
||||
@@ -266,9 +271,8 @@ class MapPage extends HookConsumerWidget {
|
||||
style: style,
|
||||
selectedMarker: selectedMarker,
|
||||
onMapCreated: onMapCreated,
|
||||
onMapMoved: onMapMoved,
|
||||
onMapClicked: onMarkerClicked,
|
||||
onStyleLoaded: reloadLayers,
|
||||
onMapEvent: onMapEvent,
|
||||
onStyleLoaded: (_) => reloadLayers(),
|
||||
onMarkerTapped: onMarkerTapped,
|
||||
),
|
||||
Positioned(
|
||||
@@ -302,32 +306,19 @@ class MapPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetMarkerMeta {
|
||||
final Point<num> point;
|
||||
final MapMarker marker;
|
||||
final bool shouldAnimate;
|
||||
|
||||
const _AssetMarkerMeta({required this.point, required this.marker, required this.shouldAnimate});
|
||||
|
||||
@override
|
||||
String toString() => '_AssetMarkerMeta(point: $point, marker: $marker, shouldAnimate: $shouldAnimate)';
|
||||
}
|
||||
|
||||
class _MapWithMarker extends StatelessWidget {
|
||||
final AsyncValue<String> style;
|
||||
final MapCreatedCallback onMapCreated;
|
||||
final OnCameraIdleCallback onMapMoved;
|
||||
final OnMapClickCallback onMapClicked;
|
||||
final OnStyleLoadedCallback onStyleLoaded;
|
||||
final void Function(MapController) onMapCreated;
|
||||
final void Function(MapEvent) onMapEvent;
|
||||
final void Function(StyleController) onStyleLoaded;
|
||||
final Function()? onMarkerTapped;
|
||||
final ValueNotifier<_AssetMarkerMeta?> selectedMarker;
|
||||
final LatLng? initialLocation;
|
||||
final ValueNotifier<MapMarker?> selectedMarker;
|
||||
final Geographic? initialLocation;
|
||||
|
||||
const _MapWithMarker({
|
||||
required this.style,
|
||||
required this.onMapCreated,
|
||||
required this.onMapMoved,
|
||||
required this.onMapClicked,
|
||||
required this.onMapEvent,
|
||||
required this.onStyleLoaded,
|
||||
required this.selectedMarker,
|
||||
this.onMarkerTapped,
|
||||
@@ -336,48 +327,44 @@ class _MapWithMarker extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (ctx, constraints) => SizedBox(
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxWidth,
|
||||
child: Stack(
|
||||
children: [
|
||||
style.widgetWhen(
|
||||
onData: (style) => MapLibreMap(
|
||||
attributionButtonMargins: const Point(8, kToolbarHeight),
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: initialLocation ?? const LatLng(0, 0),
|
||||
zoom: initialLocation != null ? 12 : 0,
|
||||
),
|
||||
styleString: style,
|
||||
// This is needed to update the selectedMarker's position on map camera updates
|
||||
// The changes are notified through the mapController ValueListener which is added in [onMapCreated]
|
||||
trackCameraPosition: true,
|
||||
onMapCreated: onMapCreated,
|
||||
onCameraIdle: onMapMoved,
|
||||
onMapClick: onMapClicked,
|
||||
onStyleLoadedCallback: onStyleLoaded,
|
||||
tiltGesturesEnabled: false,
|
||||
dragEnabled: false,
|
||||
myLocationEnabled: false,
|
||||
attributionButtonPosition: AttributionButtonPosition.topRight,
|
||||
rotateGesturesEnabled: false,
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: selectedMarker,
|
||||
builder: (ctx, value, _) => value != null
|
||||
? PositionedAssetMarkerIcon(
|
||||
point: value.point,
|
||||
assetRemoteId: value.marker.assetRemoteId,
|
||||
assetThumbhash: '',
|
||||
durationInMilliseconds: value.shouldAnimate ? 100 : 0,
|
||||
onTap: onMarkerTapped,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
return style.widgetWhen(
|
||||
onData: (style) => MapLibreMap(
|
||||
options: MapOptions(
|
||||
initCenter: initialLocation ?? const Geographic(lat: 0, lon: 0),
|
||||
initZoom: initialLocation != null ? 12 : 0,
|
||||
initStyle: style,
|
||||
gestures: const MapGestures.all(pitch: false, rotate: false),
|
||||
),
|
||||
onMapCreated: onMapCreated,
|
||||
onStyleLoaded: onStyleLoaded,
|
||||
onEvent: onMapEvent,
|
||||
children: [
|
||||
ValueListenableBuilder<MapMarker?>(
|
||||
valueListenable: selectedMarker,
|
||||
builder: (ctx, marker, _) => marker != null
|
||||
? WidgetLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: marker.latLng,
|
||||
size: const Size(100, 100),
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: GestureDetector(
|
||||
onTap: () => onMarkerTapped?.call(),
|
||||
child: SizedBox.square(
|
||||
dimension: 100,
|
||||
child: AssetMarkerIcon(
|
||||
id: marker.assetRemoteId,
|
||||
thumbhash: '',
|
||||
key: Key(marker.assetRemoteId),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -7,36 +5,34 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
|
||||
import 'package:immich_mobile/utils/map_utils.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_theme_override.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:maplibre/maplibre.dart';
|
||||
|
||||
@RoutePage()
|
||||
class MapLocationPickerPage extends HookConsumerWidget {
|
||||
final LatLng initialLatLng;
|
||||
final Geographic initialLatLng;
|
||||
|
||||
const MapLocationPickerPage({super.key, this.initialLatLng = const LatLng(0, 0)});
|
||||
const MapLocationPickerPage({super.key, this.initialLatLng = const Geographic(lat: 0, lon: 0)});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedLatLng = useValueNotifier<LatLng>(initialLatLng);
|
||||
final controller = useRef<MapLibreMapController?>(null);
|
||||
final marker = useRef<Symbol?>(null);
|
||||
final selectedLatLng = useValueNotifier<Geographic>(initialLatLng);
|
||||
final currentLatLng = useValueListenable(selectedLatLng);
|
||||
final controller = useRef<MapController?>(null);
|
||||
|
||||
Future<void> onStyleLoaded() async {
|
||||
marker.value = await controller.value?.addMarkerAtLatLng(initialLatLng);
|
||||
Future<void> onStyleLoaded(StyleController style) async {
|
||||
await style.addImageFromAssets(id: 'mapMarker', asset: 'assets/location-pin.png');
|
||||
}
|
||||
|
||||
Future<void> onMapClick(Point<num> _, LatLng centre) async {
|
||||
selectedLatLng.value = centre;
|
||||
await controller.value?.animateCamera(CameraUpdate.newLatLng(centre));
|
||||
if (marker.value != null) {
|
||||
await controller.value?.updateSymbol(marker.value!, SymbolOptions(geometry: centre));
|
||||
}
|
||||
void onEvent(MapEvent event) {
|
||||
if (event is! MapEventClick) return;
|
||||
|
||||
selectedLatLng.value = event.point;
|
||||
controller.value?.animateCamera(center: event.point);
|
||||
}
|
||||
|
||||
void onClose([LatLng? selected]) {
|
||||
void onClose([Geographic? selected]) {
|
||||
context.maybePop(selected);
|
||||
}
|
||||
|
||||
@@ -47,9 +43,9 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentLatLng = LatLng(currentLocation.latitude, currentLocation.longitude);
|
||||
var currentLatLng = Geographic(lat: currentLocation.latitude, lon: currentLocation.longitude);
|
||||
selectedLatLng.value = currentLatLng;
|
||||
await controller.value?.animateCamera(CameraUpdate.newLatLngZoom(currentLatLng, 12));
|
||||
await controller.value?.animateCamera(center: currentLatLng, zoom: 12);
|
||||
}
|
||||
|
||||
return MapThemeOverride(
|
||||
@@ -66,18 +62,24 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
||||
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(40), bottomRight: Radius.circular(40)),
|
||||
),
|
||||
child: MapLibreMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: initialLatLng,
|
||||
zoom: (initialLatLng.latitude == 0 && initialLatLng.longitude == 0) ? 1 : 12,
|
||||
options: MapOptions(
|
||||
initCenter: initialLatLng,
|
||||
initZoom: (initialLatLng.lat == 0 && initialLatLng.lon == 0) ? 1 : 12,
|
||||
initStyle: style,
|
||||
gestures: const MapGestures.all(pitch: false),
|
||||
),
|
||||
styleString: style,
|
||||
onMapCreated: (mapController) => controller.value = mapController,
|
||||
onStyleLoadedCallback: onStyleLoaded,
|
||||
onMapClick: onMapClick,
|
||||
dragEnabled: false,
|
||||
tiltGesturesEnabled: false,
|
||||
myLocationEnabled: false,
|
||||
attributionButtonMargins: const Point(20, 15),
|
||||
onStyleLoaded: onStyleLoaded,
|
||||
onEvent: onEvent,
|
||||
layers: [
|
||||
MarkerLayer(
|
||||
points: [Feature(geometry: Point(currentLatLng))],
|
||||
iconImage: 'mapMarker',
|
||||
iconSize: 0.15,
|
||||
iconAnchor: IconAnchor.bottom,
|
||||
iconAllowOverlap: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -117,7 +119,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
}
|
||||
|
||||
class _BottomBar extends StatelessWidget {
|
||||
final ValueNotifier<LatLng> selectedLatLng;
|
||||
final ValueNotifier<Geographic> selectedLatLng;
|
||||
final Function() onUseLocation;
|
||||
final Function() onGetCurrentLocation;
|
||||
|
||||
@@ -140,8 +142,7 @@ class _BottomBar extends StatelessWidget {
|
||||
const SizedBox(width: 15),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: selectedLatLng,
|
||||
builder: (_, value, __) =>
|
||||
Text("${value.latitude.toStringAsFixed(4)}, ${value.longitude.toStringAsFixed(4)}"),
|
||||
builder: (_, value, __) => Text("${value.lat.toStringAsFixed(4)}, ${value.lon.toStringAsFixed(4)}"),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user