Compare commits

...

1 Commits

Author SHA1 Message Date
Adam Gastineau 37c68ae947 fix(mobile): live debounced map bounds tracking 2026-07-09 12:24:52 -07:00
2 changed files with 9 additions and 74 deletions
@@ -1,71 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:immich_mobile/models/map/map_marker.model.dart';
import 'package:immich_mobile/utils/map_utils.dart';
import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:maplibre_gl/maplibre_gl.dart';
extension MapMarkers on MapLibreMapController { extension MapMarkers on MapLibreMapController {
static var _completer = Completer()..complete();
Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async {
return addSource(
MapUtils.defaultSourceId,
GeojsonSourceProperties(data: MapUtils.generateGeoJsonForMarkers(markers.toList())),
);
}
Future<void> reloadAllLayersForMarkers(List<MapMarker> markers) async {
// Wait for previous reload to complete
if (!_completer.isCompleted) {
return _completer.future;
}
_completer = Completer();
// !! Make sure to remove layers before sources else the native
// maplibre library would crash when removing the source saying that
// the source is still in use
final existingLayers = await getLayerIds();
if (existingLayers.contains(MapUtils.defaultHeatMapLayerId)) {
await removeLayer(MapUtils.defaultHeatMapLayerId);
}
final existingSources = await getSourceIds();
if (existingSources.contains(MapUtils.defaultSourceId)) {
await removeSource(MapUtils.defaultSourceId);
}
await addGeoJSONSourceForMarkers(markers);
if (Platform.isAndroid) {
await addCircleLayer(
MapUtils.defaultSourceId,
MapUtils.defaultHeatMapLayerId,
const CircleLayerProperties(
circleRadius: 10,
circleColor: "rgba(150,86,34,0.7)",
circleBlur: 1.0,
circleOpacity: 0.7,
circleStrokeWidth: 0.1,
circleStrokeColor: "rgba(203,46,19,0.5)",
circleStrokeOpacity: 0.7,
),
);
}
if (Platform.isIOS) {
await addHeatmapLayer(
MapUtils.defaultSourceId,
MapUtils.defaultHeatMapLayerId,
MapUtils.defaultHeatMapLayerProperties,
);
}
_completer.complete();
}
Future<Symbol?> addMarkerAtLatLng(LatLng centre) async { Future<Symbol?> addMarkerAtLatLng(LatLng centre) async {
// no marker is displayed if asset-path is incorrect // no marker is displayed if asset-path is incorrect
try { try {
@@ -76,14 +14,4 @@ extension MapMarkers on MapLibreMapController {
// no-op // no-op
} }
} }
Future<LatLngBounds> getBoundsFromPoint(Point<double> point, double distance) async {
final southWestPx = Point(point.x - distance, point.y + distance);
final northEastPx = Point(point.x + distance, point.y - distance);
final southWest = await toLatLng(southWestPx);
final northEast = await toLatLng(northEastPx);
return LatLngBounds(southwest: southWest, northeast: northEast);
}
} }
@@ -52,7 +52,12 @@ class DriftMap extends ConsumerStatefulWidget {
class _DriftMapState extends ConsumerState<DriftMap> { class _DriftMapState extends ConsumerState<DriftMap> {
MapLibreMapController? mapController; MapLibreMapController? mapController;
final _reloadMutex = AsyncMutex(); final _reloadMutex = AsyncMutex();
final _debouncer = Debouncer(interval: const Duration(milliseconds: 500), maxWaitTime: const Duration(seconds: 2));
final _debouncer = Debouncer(
interval: const Duration(milliseconds: 150),
maxWaitTime: const Duration(milliseconds: 500),
);
final ValueNotifier<double> bottomSheetOffset = ValueNotifier(0.25); final ValueNotifier<double> bottomSheetOffset = ValueNotifier(0.25);
final GlobalKey _bottomSheetKey = GlobalKey(); final GlobalKey _bottomSheetKey = GlobalKey();
StreamSubscription? _eventSubscription; StreamSubscription? _eventSubscription;
@@ -117,7 +122,7 @@ class _DriftMapState extends ConsumerState<DriftMap> {
} }
void onMapMoved() { void onMapMoved() {
if (mapController!.isCameraMoving || !mounted) { if (!mounted) {
return; return;
} }
@@ -213,6 +218,8 @@ class _Map extends StatelessWidget {
: CameraPosition(target: initialLocation, zoom: MapUtils.mapZoomToAssetLevel), : CameraPosition(target: initialLocation, zoom: MapUtils.mapZoomToAssetLevel),
compassEnabled: false, compassEnabled: false,
rotateGesturesEnabled: false, rotateGesturesEnabled: false,
// Get continuous movement events for bounds tracking
trackCameraPosition: true,
styleString: style, styleString: style,
onMapCreated: onMapCreated, onMapCreated: onMapCreated,
onStyleLoadedCallback: onMapReady, onStyleLoadedCallback: onMapReady,