From 7321453337e6f9809e7e0cc04279f5d81b10871c Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 6 Jul 2026 11:22:47 -0700 Subject: [PATCH] Map: pin current view before 'Search this area' to defeat camera stop-revert The native MapLibre camera re-applies its last setCamera() stop on the search re-render (confirmed via trace: revert to prior target with userInteraction=false). Track the live viewport and, before searching the current area, setCamera() to the current center (0ms) so the re-applied stop is a no-op instead of a jump-back. Co-Authored-By: Claude Fable 5 --- app/src/screens/MapScreen.tsx | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/app/src/screens/MapScreen.tsx b/app/src/screens/MapScreen.tsx index 2857600..f8ca745 100644 --- a/app/src/screens/MapScreen.tsx +++ b/app/src/screens/MapScreen.tsx @@ -59,6 +59,11 @@ export function MapScreen() { // We position the camera exactly once, then never auto-recenter — otherwise // re-renders (from searching) would snap the map back and fight the user's pan. const positioned = useRef(false); + // The live viewport, tracked from region changes. MapLibre's native camera + // re-applies its last setCamera() stop on any re-render, so before a search we + // overwrite that stop with the current view (zero-duration) — otherwise the + // results re-render snaps the map back to the previous programmatic target. + const viewRef = useRef<{ center: [number, number]; zoom: number } | null>(null); // The native UserLocation dot has its own GPS feed — capture it so "My // location" works even when expo-location can't get a fix (e.g. indoors). const nativeFix = useRef(null); @@ -109,15 +114,16 @@ export function MapScreen() { const searchThisArea = async () => { try { const center = await mapRef.current?.getCenter?.(); // [lng, lat] - console.log('[CAM] searchThisArea getCenter=', JSON.stringify(center)); if (center && center.length === 2) { + // Commit the current view as the camera's stop so the results re-render + // doesn't revert to the last programmatic (e.g. "My location") target. + cameraRef.current?.setCamera?.({ centerCoordinate: center, animationDuration: 0 }); await searchAt({ latitude: center[1], longitude: center[0] }, 'this area'); return; } - } catch (err) { - console.log('[CAM] getCenter threw', String(err)); + } catch { + /* fall through */ } - console.log('[CAM] searchThisArea FALLBACK coords=', JSON.stringify(coords)); if (coords) await searchAt(coords, 'this area'); }; @@ -212,14 +218,19 @@ export function MapScreen() { console.log('[CAM] onDidFinishLoadingMap (mapReady already', mapReady, ')'); setMapReady(true); }} - onRegionDidChange={(f: any) => + onRegionDidChange={(f: any) => { + const c = f?.geometry?.coordinates; + const z = f?.properties?.zoomLevel; + if (Array.isArray(c) && c.length === 2) { + viewRef.current = { center: [c[0], c[1]], zoom: typeof z === 'number' ? z : 14 }; + } console.log( '[CAM] regionDidChange center=', - JSON.stringify(f?.geometry?.coordinates), - 'userInteraction=', + JSON.stringify(c), + 'ui=', f?.properties?.isUserInteraction, - ) - } + ); + }} > {cameraEl} {userLocationEl}