Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions android/src/main/java/com/luggmaps/LuggMapView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) :

// region Provider Initialization

private fun initializeProvider() {
private fun initializeProvider(latitude: Double = initialLatitude, longitude: Double = initialLongitude, zoom: Float = initialZoom) {
if (provider != null || mapWrapperView == null) return

if (staticMode) {
Expand All @@ -146,7 +146,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) :

applyProps()

google.initializeMap(mapWrapperView!!, initialLatitude, initialLongitude, initialZoom)
google.initializeMap(mapWrapperView!!, latitude, longitude, zoom)

// Flush children mounted before provider was created
for (i in 0 until childCount) {
Expand Down Expand Up @@ -371,6 +371,19 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
provider?.fitCoordinates(coordinates, edgeInsetsTop, edgeInsetsLeft, edgeInsetsBottom, edgeInsetsRight, duration)
}

fun reload() {
val current = provider ?: return
val latitude = current.cameraLatitude
val longitude = current.cameraLongitude
val zoom = current.cameraZoom

current.destroy()
provider = null
if (isAttachedToWindow) {
initializeProvider(latitude, longitude, zoom)
}
}

// endregion

companion object {
Expand Down
4 changes: 4 additions & 0 deletions android/src/main/java/com/luggmaps/LuggMapViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ class LuggMapViewManager :
)
}

override fun reload(view: LuggMapView) {
view.reload()
}

companion object {
const val NAME = "LuggMapView"
}
Expand Down
9 changes: 9 additions & 0 deletions android/src/main/java/com/luggmaps/LuggMapWrapperView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.luggmaps

import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.View
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewGroup

Expand Down Expand Up @@ -34,6 +35,14 @@ class LuggMapWrapperView(context: ThemedReactContext) : ReactViewGroup(context)
}
}

override fun onViewAdded(child: View) {
super.onViewAdded(child)
// A replacement map needs sizing even when Yoga's layout is unchanged.
if (indexOfChild(child) == 0) {
layoutChild(width, height)
}
}

override fun onLayout(
changed: Boolean,
left: Int,
Expand Down
26 changes: 25 additions & 1 deletion android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class GoogleMapProvider(private val context: Context) :

// Theme
private var theme: String = "system"
private var mapType: String = "standard"

// Edge Insets
private var edgeInsets: EdgeInsets = EdgeInsets()
Expand Down Expand Up @@ -183,7 +184,7 @@ class GoogleMapProvider(private val context: Context) :
view.onCreate(null)
view.onResume()
view.getMapAsync(this)
wrapper.addView(view)
wrapper.addView(view, 0)
}
wrapper.onLayoutReady = null
}
Expand Down Expand Up @@ -211,6 +212,9 @@ class GoogleMapProvider(private val context: Context) :
groundOverlayToViewMap.clear()
markerToViewMap.clear()
wrapperView?.touchEventHandler = null
// The wrapper outlives the provider on reload; don't leave the dead map
// view in it
mapView?.let { wrapperView?.removeView(it) }
wrapperView = null
googleMap?.setOnCameraMoveStartedListener(null)
googleMap?.setOnCameraMoveListener(null)
Expand Down Expand Up @@ -276,6 +280,7 @@ class GoogleMapProvider(private val context: Context) :
applyZoomLimits()
applyInsetAdjustment()
applyTheme()
setMapType(mapType)
applyUserLocation()
processPendingMarkers()
processPendingPolylines()
Expand Down Expand Up @@ -542,6 +547,7 @@ class GoogleMapProvider(private val context: Context) :
}

override fun setMapType(value: String) {
mapType = value
googleMap?.mapType = when (value) {
"satellite" -> GoogleMap.MAP_TYPE_SATELLITE
"terrain" -> GoogleMap.MAP_TYPE_TERRAIN
Expand Down Expand Up @@ -1201,6 +1207,24 @@ class GoogleMapProvider(private val context: Context) :

// endregion

// region Camera

// A static map's camera is inset-shifted (staticCameraTarget), so report
// the requested camera instead
private val liveCameraPosition: CameraPosition?
get() = if (staticMode) null else googleMap?.cameraPosition

override val cameraLatitude: Double
get() = liveCameraPosition?.target?.latitude ?: initialLatitude

override val cameraLongitude: Double
get() = liveCameraPosition?.target?.longitude ?: initialLongitude

override val cameraZoom: Float
get() = liveCameraPosition?.zoom ?: initialZoom

// endregion

// region Lifecycle

override fun pauseAnimations() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ interface MapProvider {
fun addTileOverlayView(tileOverlayView: LuggTileOverlayView)
fun removeTileOverlayView(tileOverlayView: LuggTileOverlayView)

// Current camera (the initial camera until the map is ready)
val cameraLatitude: Double
val cameraLongitude: Double
val cameraZoom: Float

// Lifecycle
fun pauseAnimations()
fun resumeAnimations()
Expand Down
28 changes: 28 additions & 0 deletions docs/content/docs/components/map-view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ Notes:
re-render from props). The camera and map settings are part of the
cache key automatically. Changing `staticKey` discards the current
image and re-renders. The cache is bounded by count and total memory.
- On iOS (Google), the base map is only captured and cached once the SDK
reports the map stable with all tiles loaded. If tiles fail (e.g.
offline), the live warmup map stays in place instead and renders again
the next time the row appears, so a bad render can't get stuck behind
`staticKey`. To force it, call `reload()` on the ref.
- Alternatively, keeping rows mounted (larger `windowSize` with
`removeClippedSubviews`) avoids re-rendering entirely at the cost of
holding every row's snapshot in memory.
Expand Down Expand Up @@ -273,6 +278,9 @@ mapRef.current?.setEdgeInsets(
{ top: 0, left: 0, bottom: 200, right: 0 },
{ duration: 300 }
);

// Load the map again (e.g. after a network outage)
mapRef.current?.reload();
```

### moveCamera
Expand Down Expand Up @@ -313,6 +321,26 @@ interface SetEdgeInsetsOptions {
}
```

### reload

Load the map again, e.g. to recover from missing tiles after a network
outage. None of the map SDKs can reload tiles in place, so:

- A static map re-renders its base map. On iOS the cached snapshot for its
`staticKey` is discarded and the new render replaces the current image
when ready; on Android the lite map is recreated.
- A live map is recreated at its current coordinate and zoom. Heading and
pitch reset, and `onReady` fires again. Markers and overlays remain on
the map after reload.

Reload preserves the map type and edge insets. On web, `onReady` fires once
for each new map instance. On iOS, an unfinished static reload restarts
when the map leaves the window and returns.

```ts
reload(): void
```

## Events

### onPress / onLongPress
Expand Down
1 change: 1 addition & 0 deletions example/shared/src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ export const Map = memo(
moveCamera: (...args) => mapRef.current?.moveCamera(...args),
fitCoordinates: (...args) => mapRef.current?.fitCoordinates(...args),
setEdgeInsets: (...args) => mapRef.current?.setEdgeInsets(...args),
reload: () => mapRef.current?.reload(),
showMarkerCallout: (markerId) =>
markerRefsMap.current.get(markerId)?.showCallout(),
hideMarkerCallout: (markerId) =>
Expand Down
5 changes: 4 additions & 1 deletion example/shared/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@ const HomeContent = ({
);

const handleMapReady = useCallback(() => {
lockStatus();
setStatus({ text: 'Map ready', error: false });
const position = controlSheetRef.current?.animatedPosition;
if (!position) return;
const bottom = screenHeight - position.value;
if (bottom > 0) {
mapRef.current?.setEdgeInsets(bottomEdgeInsets(bottom));
}
}, [screenHeight]);
}, [lockStatus, screenHeight]);

const handleSheetEvent = useCallback(
(event: DetentChangeEvent) => {
Expand Down Expand Up @@ -279,6 +281,7 @@ const HomeContent = ({
onClearMarkers={clear}
onMoveCamera={moveToRandomMarker}
onFitMarkers={fitAllMarkers}
onReload={() => mapRef.current?.reload()}
onToggleMap={() => setShowMap((prev) => !prev)}
onToggleProvider={() =>
setProvider((p) => (p === 'google' ? 'apple' : 'google'))
Expand Down
24 changes: 23 additions & 1 deletion example/shared/src/screens/StaticMapsScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useRef, useState } from 'react';
import {
FlatList,
Platform,
Expand Down Expand Up @@ -262,6 +262,7 @@ const PlaceCard = ({
}) => {
const { colors } = useTheme();
const { width } = useWindowDimensions();
const mapRef = useRef<MapView>(null);

const camera = fittedCamera(
placeCoordinates(place),
Expand All @@ -281,6 +282,7 @@ const PlaceCard = ({
>
<View style={styles.map} pointerEvents="none">
<MapView
ref={mapRef}
key={provider}
staticMode
staticKey={place.id}
Expand All @@ -293,6 +295,18 @@ const PlaceCard = ({
<PlaceConstellation place={place} />
</MapView>
</View>
{/* Re-renders the base map, dropping its cached snapshot - e.g. to
recover a map that rendered with missing tiles */}
<Pressable
style={({ pressed }) => [
styles.reload,
{ backgroundColor: colors.backgroundElevated },
pressed && styles.cardPressed,
]}
onPress={() => mapRef.current?.reload()}
>
<ThemedText variant="caption">Reload</ThemedText>
</Pressable>
<View style={styles.cardContent}>
<ThemedText variant="title" style={styles.cardTitle}>
{place.name}
Expand Down Expand Up @@ -361,6 +375,14 @@ const styles = StyleSheet.create({
map: {
height: MAP_HEIGHT,
},
reload: {
position: 'absolute',
top: sizes.sm,
right: sizes.sm,
paddingHorizontal: sizes.md,
paddingVertical: sizes.xs,
borderRadius: sizes.radiusFull,
},
cardContent: {
padding: sizes.lg,
gap: sizes.xs,
Expand Down
8 changes: 8 additions & 0 deletions example/shared/src/sheets/ControlSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface ControlSheetProps {
onClearMarkers: () => void;
onMoveCamera: () => void;
onFitMarkers: () => void;
onReload: () => void;
onToggleMap: () => void;
onToggleProvider: () => void;
onLoadGeojson: () => void;
Expand Down Expand Up @@ -56,6 +57,7 @@ export const ControlSheet = forwardRef<ControlSheetRef, ControlSheetProps>(
onClearMarkers,
onMoveCamera,
onFitMarkers,
onReload,
onToggleMap,
onToggleProvider,
onLoadGeojson,
Expand Down Expand Up @@ -122,6 +124,12 @@ export const ControlSheet = forwardRef<ControlSheetRef, ControlSheetProps>(
onPress={onFitMarkers}
disabled={markerCount === 0}
/>
<Button
style={styles.sheetButton}
title="Reload Map"
onPress={onReload}
disabled={!showMap}
/>
<Button
style={styles.sheetButton}
title={showMap ? 'Hide Map' : 'Show Map'}
Expand Down
Loading
Loading