@lugg/maps 1.0.0-beta.16 → 1.0.0-beta.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/luggmaps/LuggMapView.kt +33 -0
- package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +9 -0
- package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +20 -2
- package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +29 -3
- package/ios/LuggMapView.mm +152 -2
- package/ios/LuggMapWrapperView.h +5 -0
- package/ios/LuggMapWrapperView.mm +18 -0
- package/ios/LuggMarkerView.mm +2 -0
- package/ios/core/AppleMapProvider.h +8 -0
- package/ios/core/AppleMapProvider.mm +41 -28
- package/ios/core/AppleStaticMapProvider.h +14 -0
- package/ios/core/AppleStaticMapProvider.mm +146 -0
- package/ios/core/GMSPolylineAnimator.m +40 -15
- package/ios/core/GoogleMapProvider.h +10 -0
- package/ios/core/GoogleMapProvider.mm +47 -41
- package/ios/core/GoogleStaticMapProvider.h +21 -0
- package/ios/core/GoogleStaticMapProvider.mm +204 -0
- package/ios/core/MKPolylineAnimator.m +67 -43
- package/ios/core/MapProviderDelegate.h +6 -0
- package/ios/core/StaticMapProviderBase.h +69 -0
- package/ios/core/StaticMapProviderBase.mm +745 -0
- package/ios/extensions/MKMapView+Zoom.h +14 -0
- package/ios/extensions/MKMapView+Zoom.m +17 -15
- package/ios/extensions/UIView+Snapshot.h +16 -0
- package/ios/extensions/UIView+Snapshot.m +27 -0
- package/lib/module/MapView.js +4 -0
- package/lib/module/MapView.js.map +1 -1
- package/lib/module/MapView.web.js +8 -5
- package/lib/module/MapView.web.js.map +1 -1
- package/lib/module/fabric/LuggMapViewNativeComponent.ts +2 -0
- package/lib/typescript/src/MapView.d.ts.map +1 -1
- package/lib/typescript/src/MapView.types.d.ts +18 -0
- package/lib/typescript/src/MapView.types.d.ts.map +1 -1
- package/lib/typescript/src/MapView.web.d.ts.map +1 -1
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +2 -0
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/MapView.tsx +4 -0
- package/src/MapView.types.ts +18 -0
- package/src/MapView.web.tsx +14 -10
- package/src/fabric/LuggMapViewNativeComponent.ts +2 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.luggmaps
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.res.Configuration
|
|
4
5
|
import android.view.View
|
|
5
6
|
import android.view.View.VISIBLE
|
|
6
7
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
@@ -65,6 +66,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
65
66
|
private var rotateEnabled: Boolean = true
|
|
66
67
|
private var pitchEnabled: Boolean = true
|
|
67
68
|
private var compassEnabled: Boolean = true
|
|
69
|
+
private var staticMode: Boolean = false
|
|
68
70
|
private var userLocationEnabled: Boolean = false
|
|
69
71
|
private var userLocationButtonEnabled: Boolean = false
|
|
70
72
|
private var poiEnabled: Boolean = true
|
|
@@ -132,8 +134,13 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
132
134
|
private fun initializeProvider() {
|
|
133
135
|
if (provider != null || mapWrapperView == null) return
|
|
134
136
|
|
|
137
|
+
if (staticMode) {
|
|
138
|
+
applyStaticPlaceholder()
|
|
139
|
+
}
|
|
140
|
+
|
|
135
141
|
val google = GoogleMapProvider(context)
|
|
136
142
|
google.mapId = mapId
|
|
143
|
+
google.staticMode = staticMode
|
|
137
144
|
google.delegate = this
|
|
138
145
|
provider = google
|
|
139
146
|
|
|
@@ -154,6 +161,23 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
// Shown while the map loads, until the map covers it
|
|
165
|
+
private fun applyStaticPlaceholder() {
|
|
166
|
+
// A user-provided background acts as the placeholder
|
|
167
|
+
if (background != null) return
|
|
168
|
+
|
|
169
|
+
val isDark = when (theme) {
|
|
170
|
+
"dark" -> true
|
|
171
|
+
|
|
172
|
+
"light" -> false
|
|
173
|
+
|
|
174
|
+
else ->
|
|
175
|
+
(context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
|
176
|
+
Configuration.UI_MODE_NIGHT_YES
|
|
177
|
+
}
|
|
178
|
+
mapWrapperView?.setBackgroundColor(if (isDark) COLOR_PLACEHOLDER_DARK else COLOR_PLACEHOLDER_LIGHT)
|
|
179
|
+
}
|
|
180
|
+
|
|
157
181
|
// endregion
|
|
158
182
|
|
|
159
183
|
// region MapProviderDelegate
|
|
@@ -251,6 +275,10 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
251
275
|
provider?.setCompassEnabled(enabled)
|
|
252
276
|
}
|
|
253
277
|
|
|
278
|
+
fun setStaticMode(enabled: Boolean) {
|
|
279
|
+
staticMode = enabled
|
|
280
|
+
}
|
|
281
|
+
|
|
254
282
|
fun setUserLocationEnabled(enabled: Boolean) {
|
|
255
283
|
if (userLocationEnabled == enabled) return
|
|
256
284
|
userLocationEnabled = enabled
|
|
@@ -344,4 +372,9 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
344
372
|
}
|
|
345
373
|
|
|
346
374
|
// endregion
|
|
375
|
+
|
|
376
|
+
companion object {
|
|
377
|
+
private const val COLOR_PLACEHOLDER_LIGHT = 0xFFF2F2F7.toInt()
|
|
378
|
+
private const val COLOR_PLACEHOLDER_DARK = 0xFF2C2C2E.toInt()
|
|
379
|
+
}
|
|
347
380
|
}
|
|
@@ -137,6 +137,15 @@ class LuggMapViewManager :
|
|
|
137
137
|
view.setCompassEnabled(value)
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
@ReactProp(name = "staticMode", defaultBoolean = false)
|
|
141
|
+
override fun setStaticMode(view: LuggMapView, value: Boolean) {
|
|
142
|
+
view.setStaticMode(value)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// iOS only - lite mode doesn't need snapshot caching
|
|
146
|
+
@ReactProp(name = "staticKey")
|
|
147
|
+
override fun setStaticKey(view: LuggMapView, value: String?) = Unit
|
|
148
|
+
|
|
140
149
|
@ReactProp(name = "userLocationEnabled", defaultBoolean = false)
|
|
141
150
|
override fun setUserLocationEnabled(view: LuggMapView, value: Boolean) {
|
|
142
151
|
view.setUserLocationEnabled(value)
|
|
@@ -10,11 +10,26 @@ class LuggMapWrapperView(context: ThemedReactContext) : ReactViewGroup(context)
|
|
|
10
10
|
|
|
11
11
|
var touchEventHandler: ((MotionEvent) -> Unit)? = null
|
|
12
12
|
|
|
13
|
+
// React Native never runs an Android layout pass on Yoga-managed views, so
|
|
14
|
+
// a child's requestLayout (e.g. a lite mode MapView displaying its bitmap
|
|
15
|
+
// once loaded) would otherwise never result in a measure/layout. Live maps
|
|
16
|
+
// don't need this - their GL surface renders continuously once sized.
|
|
17
|
+
var relayoutChildOnRequest: Boolean = false
|
|
18
|
+
|
|
19
|
+
private val measureAndLayoutChild = Runnable { layoutChild(width, height) }
|
|
20
|
+
|
|
13
21
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
14
22
|
touchEventHandler?.invoke(event)
|
|
15
23
|
return super.dispatchTouchEvent(event)
|
|
16
24
|
}
|
|
17
25
|
|
|
26
|
+
override fun requestLayout() {
|
|
27
|
+
super.requestLayout()
|
|
28
|
+
if (relayoutChildOnRequest) {
|
|
29
|
+
post(measureAndLayoutChild)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
override fun onLayout(
|
|
19
34
|
changed: Boolean,
|
|
20
35
|
left: Int,
|
|
@@ -23,8 +38,11 @@ class LuggMapWrapperView(context: ThemedReactContext) : ReactViewGroup(context)
|
|
|
23
38
|
bottom: Int
|
|
24
39
|
) {
|
|
25
40
|
super.onLayout(changed, left, top, right, bottom)
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
layoutChild(right - left, bottom - top)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private fun layoutChild(w: Int, h: Int) {
|
|
45
|
+
if (w <= 0 || h <= 0) return
|
|
28
46
|
getChildAt(0)?.let {
|
|
29
47
|
it.measure(
|
|
30
48
|
MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
|
|
@@ -78,6 +78,9 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
78
78
|
|
|
79
79
|
var mapId: String = DEMO_MAP_ID
|
|
80
80
|
|
|
81
|
+
// Renders the map in lite mode (static bitmap). Creation-time only.
|
|
82
|
+
var staticMode: Boolean = false
|
|
83
|
+
|
|
81
84
|
private var wrapperView: LuggMapWrapperView? = null
|
|
82
85
|
private var currentNightMode: Int = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
83
86
|
private var mapView: MapView? = null
|
|
@@ -96,6 +99,7 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
96
99
|
private val groundOverlayToViewMap = mutableMapOf<GroundOverlay, LuggGroundOverlayView>()
|
|
97
100
|
private val markerToViewMap = mutableMapOf<Marker, LuggMarkerView>()
|
|
98
101
|
private val liveMarkerViews = mutableSetOf<LuggMarkerView>()
|
|
102
|
+
private var liteProjectionReady = false
|
|
99
103
|
private var activeNonBubbledMarker: Marker? = null
|
|
100
104
|
private var tapLocation: LatLng? = null
|
|
101
105
|
|
|
@@ -137,11 +141,15 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
137
141
|
initialZoom = zoom
|
|
138
142
|
|
|
139
143
|
val wrapper = wrapperView as LuggMapWrapperView
|
|
144
|
+
wrapper.relayoutChildOnRequest = staticMode
|
|
140
145
|
this.wrapperView = wrapper
|
|
141
146
|
|
|
142
147
|
context.applicationContext.registerComponentCallbacks(this)
|
|
143
148
|
|
|
144
|
-
|
|
149
|
+
// Lite mode doesn't support map IDs (cloud-based styling); setting one
|
|
150
|
+
// makes the static map render blank
|
|
151
|
+
val options = GoogleMapOptions().liteMode(staticMode)
|
|
152
|
+
if (!staticMode) options.mapId(mapId)
|
|
145
153
|
mapView = MapView(context, options).also { view ->
|
|
146
154
|
view.onCreate(null)
|
|
147
155
|
view.onResume()
|
|
@@ -195,6 +203,17 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
195
203
|
googleMap = map
|
|
196
204
|
_isMapReady = true
|
|
197
205
|
|
|
206
|
+
if (staticMode) {
|
|
207
|
+
// Lite mode shows an "open in Google Maps" toolbar on tap by default
|
|
208
|
+
map.uiSettings.isMapToolbarEnabled = false
|
|
209
|
+
// The projection is only valid once the lite map has rendered, and no
|
|
210
|
+
// camera events fire afterwards to correct live marker positions
|
|
211
|
+
map.setOnMapLoadedCallback {
|
|
212
|
+
liteProjectionReady = true
|
|
213
|
+
positionLiveMarkers()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
198
217
|
val position = LatLng(initialLatitude, initialLongitude)
|
|
199
218
|
map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, initialZoom))
|
|
200
219
|
|
|
@@ -737,6 +756,10 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
737
756
|
|
|
738
757
|
val contentView = markerView.contentView
|
|
739
758
|
contentView.pointerEvents = com.facebook.react.uimanager.PointerEvents.NONE
|
|
759
|
+
// Until the lite map's projection is valid, hide the marker instead of
|
|
760
|
+
// flashing it at a wrong position; onMapLoaded positions and shows it
|
|
761
|
+
contentView.visibility =
|
|
762
|
+
if (staticMode && !liteProjectionReady) View.INVISIBLE else View.VISIBLE
|
|
740
763
|
(contentView.parent as? android.view.ViewGroup)?.removeView(contentView)
|
|
741
764
|
wrapper.addView(contentView)
|
|
742
765
|
liveMarkerViews.add(markerView)
|
|
@@ -768,10 +791,12 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
768
791
|
|
|
769
792
|
private fun positionLiveMarker(markerView: LuggMarkerView) {
|
|
770
793
|
val map = googleMap ?: return
|
|
794
|
+
if (staticMode && !liteProjectionReady) return
|
|
771
795
|
val contentView = markerView.contentView
|
|
772
796
|
val point = map.projection.toScreenLocation(LatLng(markerView.latitude, markerView.longitude))
|
|
773
797
|
contentView.translationX = point.x - contentView.width * markerView.anchorX
|
|
774
798
|
contentView.translationY = point.y - contentView.height * markerView.anchorY
|
|
799
|
+
contentView.visibility = View.VISIBLE
|
|
775
800
|
}
|
|
776
801
|
|
|
777
802
|
// endregion
|
|
@@ -813,7 +838,7 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
813
838
|
strokeColors = polylineView.strokeColors
|
|
814
839
|
strokeWidth = polylineView.strokeWidth.dpToPx()
|
|
815
840
|
animatedOptions = polylineView.animatedOptions
|
|
816
|
-
animated = polylineView.animated
|
|
841
|
+
animated = polylineView.animated && !staticMode
|
|
817
842
|
update()
|
|
818
843
|
}
|
|
819
844
|
}
|
|
@@ -842,7 +867,8 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
842
867
|
strokeColors = polylineView.strokeColors
|
|
843
868
|
strokeWidth = polylineView.strokeWidth.dpToPx()
|
|
844
869
|
animatedOptions = polylineView.animatedOptions
|
|
845
|
-
|
|
870
|
+
// Static maps render once; show the full polyline instead of animating
|
|
871
|
+
animated = polylineView.animated && !staticMode
|
|
846
872
|
update()
|
|
847
873
|
}
|
|
848
874
|
|
package/ios/LuggMapView.mm
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
#import "LuggPolylineView.h"
|
|
8
8
|
#import "LuggTileOverlayView.h"
|
|
9
9
|
#import "core/AppleMapProvider.h"
|
|
10
|
+
#import "core/AppleStaticMapProvider.h"
|
|
10
11
|
#import "core/GoogleMapProvider.h"
|
|
12
|
+
#import "core/GoogleStaticMapProvider.h"
|
|
11
13
|
#import "core/MapProviderDelegate.h"
|
|
12
14
|
#import "events/CameraIdleEvent.h"
|
|
13
15
|
#import "events/CameraMoveEvent.h"
|
|
@@ -25,6 +27,19 @@
|
|
|
25
27
|
using namespace facebook::react;
|
|
26
28
|
using namespace luggmaps::events;
|
|
27
29
|
|
|
30
|
+
// Static snapshots cached across view recycling, keyed by the user-provided
|
|
31
|
+
// staticKey plus the map settings that affect appearance.
|
|
32
|
+
static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
|
|
33
|
+
static NSCache<NSString *, UIImage *> *cache;
|
|
34
|
+
static dispatch_once_t onceToken;
|
|
35
|
+
dispatch_once(&onceToken, ^{
|
|
36
|
+
cache = [[NSCache alloc] init];
|
|
37
|
+
cache.countLimit = 100;
|
|
38
|
+
cache.totalCostLimit = 64 * 1024 * 1024; // bytes
|
|
39
|
+
});
|
|
40
|
+
return cache;
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
@interface LuggMapView () <RCTLuggMapViewViewProtocol, MapProviderDelegate>
|
|
29
44
|
@end
|
|
30
45
|
|
|
@@ -40,6 +55,9 @@ using namespace luggmaps::events;
|
|
|
40
55
|
BOOL _rotateEnabled;
|
|
41
56
|
BOOL _pitchEnabled;
|
|
42
57
|
BOOL _compassEnabled;
|
|
58
|
+
BOOL _staticMode;
|
|
59
|
+
NSString *_staticKey;
|
|
60
|
+
BOOL _staticSnapshotDone;
|
|
43
61
|
BOOL _userLocationEnabled;
|
|
44
62
|
LuggMapViewMapType _mapType;
|
|
45
63
|
LuggMapViewTheme _theme;
|
|
@@ -70,6 +88,8 @@ using namespace luggmaps::events;
|
|
|
70
88
|
_rotateEnabled = YES;
|
|
71
89
|
_pitchEnabled = YES;
|
|
72
90
|
_compassEnabled = YES;
|
|
91
|
+
_staticMode = NO;
|
|
92
|
+
_staticKey = @"";
|
|
73
93
|
_userLocationEnabled = NO;
|
|
74
94
|
_poiEnabled = NO;
|
|
75
95
|
_poiFilterMode = LuggMapViewPoiFilterMode::Including;
|
|
@@ -170,17 +190,109 @@ using namespace luggmaps::events;
|
|
|
170
190
|
[super didMoveToWindow];
|
|
171
191
|
if (self.window) {
|
|
172
192
|
if (!_provider && _mapWrapperView) {
|
|
173
|
-
|
|
193
|
+
if (_staticMode) {
|
|
194
|
+
[self startStaticContent];
|
|
195
|
+
} else {
|
|
196
|
+
[self initializeProvider];
|
|
197
|
+
}
|
|
174
198
|
}
|
|
175
199
|
[_provider resumeAnimations];
|
|
176
200
|
} else {
|
|
201
|
+
// A static map mid-warmup may hold an expensive live map view. Destroy
|
|
202
|
+
// it off-window and re-render on return instead of letting paused maps
|
|
203
|
+
// pile up across screen switches and all resume at once
|
|
204
|
+
if (_staticMode && _provider && !_staticSnapshotDone) {
|
|
205
|
+
[_provider destroy];
|
|
206
|
+
_provider = nil;
|
|
207
|
+
_initialized = NO;
|
|
208
|
+
}
|
|
177
209
|
[_provider pauseAnimations];
|
|
178
210
|
}
|
|
179
211
|
}
|
|
180
212
|
|
|
213
|
+
// Shown while the map warms up, until the live map or snapshot covers it
|
|
214
|
+
- (void)applyStaticPlaceholderBackground {
|
|
215
|
+
const auto &viewProps =
|
|
216
|
+
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
217
|
+
if (viewProps.backgroundColor) {
|
|
218
|
+
// A user-provided background acts as the placeholder
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
switch (_theme) {
|
|
223
|
+
case LuggMapViewTheme::Dark:
|
|
224
|
+
_mapWrapperView.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
225
|
+
break;
|
|
226
|
+
case LuggMapViewTheme::Light:
|
|
227
|
+
_mapWrapperView.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
_mapWrapperView.overrideUserInterfaceStyle =
|
|
231
|
+
UIUserInterfaceStyleUnspecified;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
_mapWrapperView.backgroundColor = UIColor.secondarySystemBackgroundColor;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
- (nullable NSString *)staticSnapshotCacheKey {
|
|
238
|
+
if (_staticKey.length == 0 || !_mapWrapperView)
|
|
239
|
+
return nil;
|
|
240
|
+
|
|
241
|
+
CGSize size = _mapWrapperView.bounds.size;
|
|
242
|
+
if (size.width <= 0 || size.height <= 0)
|
|
243
|
+
return nil;
|
|
244
|
+
|
|
245
|
+
// 'system' theme snapshots depend on the current appearance
|
|
246
|
+
NSInteger style = _theme == LuggMapViewTheme::System
|
|
247
|
+
? (NSInteger)self.traitCollection.userInterfaceStyle
|
|
248
|
+
: -(NSInteger)_theme;
|
|
249
|
+
|
|
250
|
+
// Camera is part of the key so a reused staticKey with a different
|
|
251
|
+
// location/zoom can't serve a stale snapshot
|
|
252
|
+
const auto &viewProps =
|
|
253
|
+
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
254
|
+
|
|
255
|
+
return [NSString stringWithFormat:@"%@|%d|%@|%d|%ld|%.0fx%.0f|%.6f,%.6f,%.2f",
|
|
256
|
+
_staticKey, (int)_providerType, _mapId,
|
|
257
|
+
(int)_mapType, (long)style, size.width,
|
|
258
|
+
size.height,
|
|
259
|
+
viewProps.initialCoordinate.latitude,
|
|
260
|
+
viewProps.initialCoordinate.longitude,
|
|
261
|
+
viewProps.initialZoom];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
- (void)startStaticContent {
|
|
265
|
+
[self applyStaticPlaceholderBackground];
|
|
266
|
+
|
|
267
|
+
// Both static providers render fully async: the placeholder shows until
|
|
268
|
+
// the snapshotter (Apple) or a brief live-map warmup (Google) delivers
|
|
269
|
+
// the base map, which reveals together with the marker/shape overlays
|
|
270
|
+
[self initializeProvider];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// staticKey identifies the map's content; when it changes (e.g. a recycled
|
|
274
|
+
// list row bound to a new item), any live warmup or displayed snapshot is
|
|
275
|
+
// stale and the static lifecycle restarts. Without this, an in-flight
|
|
276
|
+
// warmup caches the old item's map under the new key.
|
|
277
|
+
- (void)resetStaticContent {
|
|
278
|
+
_staticSnapshotDone = NO;
|
|
279
|
+
|
|
280
|
+
if (_provider) {
|
|
281
|
+
[_provider destroy];
|
|
282
|
+
_provider = nil;
|
|
283
|
+
_initialized = NO;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (self.window) {
|
|
287
|
+
[self startStaticContent];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
181
291
|
- (void)prepareForRecycle {
|
|
182
292
|
[super prepareForRecycle];
|
|
183
293
|
|
|
294
|
+
_staticSnapshotDone = NO;
|
|
295
|
+
|
|
184
296
|
[_provider destroy];
|
|
185
297
|
_provider = nil;
|
|
186
298
|
_mapWrapperView = nil;
|
|
@@ -197,14 +309,28 @@ using namespace luggmaps::events;
|
|
|
197
309
|
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
198
310
|
|
|
199
311
|
if (_providerType == LuggMapViewProvider::Apple) {
|
|
200
|
-
_provider = [[
|
|
312
|
+
_provider = _staticMode ? [[AppleStaticMapProvider alloc] init]
|
|
313
|
+
: [[AppleMapProvider alloc] init];
|
|
314
|
+
} else if (_staticMode) {
|
|
315
|
+
GoogleStaticMapProvider *google = [[GoogleStaticMapProvider alloc] init];
|
|
316
|
+
google.mapId = _mapId;
|
|
317
|
+
_provider = google;
|
|
201
318
|
} else {
|
|
202
319
|
GoogleMapProvider *google = [[GoogleMapProvider alloc] init];
|
|
203
320
|
google.mapId = _mapId;
|
|
204
321
|
_provider = google;
|
|
205
322
|
}
|
|
206
323
|
|
|
324
|
+
if (_staticMode) {
|
|
325
|
+
NSString *cacheKey = [self staticSnapshotCacheKey];
|
|
326
|
+
if (cacheKey) {
|
|
327
|
+
((StaticMapProviderBase *)_provider).cachedBaseImage =
|
|
328
|
+
[StaticSnapshotCache() objectForKey:cacheKey];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
207
332
|
_provider.delegate = self;
|
|
333
|
+
_provider.staticMode = _staticMode;
|
|
208
334
|
|
|
209
335
|
CLLocationCoordinate2D coordinate =
|
|
210
336
|
CLLocationCoordinate2DMake(viewProps.initialCoordinate.latitude,
|
|
@@ -244,6 +370,19 @@ using namespace luggmaps::events;
|
|
|
244
370
|
ReadyEvent::emit<LuggMapViewEventEmitter>(_eventEmitter);
|
|
245
371
|
}
|
|
246
372
|
|
|
373
|
+
- (void)mapProviderDidFinishStaticSnapshot {
|
|
374
|
+
_staticSnapshotDone = YES;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
- (void)mapProviderDidCaptureStaticImage:(UIImage *)image {
|
|
378
|
+
NSString *key = [self staticSnapshotCacheKey];
|
|
379
|
+
if (key) {
|
|
380
|
+
NSUInteger cost = (NSUInteger)(image.size.width * image.scale *
|
|
381
|
+
image.size.height * image.scale * 4);
|
|
382
|
+
[StaticSnapshotCache() setObject:image forKey:key cost:cost];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
247
386
|
- (void)mapProviderDidMoveCamera:(double)latitude
|
|
248
387
|
longitude:(double)longitude
|
|
249
388
|
zoom:(double)zoom
|
|
@@ -303,6 +442,12 @@ using namespace luggmaps::events;
|
|
|
303
442
|
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
304
443
|
|
|
305
444
|
_providerType = newViewProps.provider;
|
|
445
|
+
_staticMode = newViewProps.staticMode;
|
|
446
|
+
|
|
447
|
+
NSString *newStaticKey =
|
|
448
|
+
[NSString stringWithUTF8String:newViewProps.staticKey.c_str()];
|
|
449
|
+
BOOL staticKeyChanged = ![newStaticKey isEqualToString:_staticKey];
|
|
450
|
+
_staticKey = newStaticKey;
|
|
306
451
|
|
|
307
452
|
NSString *newMapId =
|
|
308
453
|
[NSString stringWithUTF8String:newViewProps.mapId.c_str()];
|
|
@@ -381,6 +526,11 @@ using namespace luggmaps::events;
|
|
|
381
526
|
}
|
|
382
527
|
|
|
383
528
|
[super updateProps:props oldProps:oldProps];
|
|
529
|
+
|
|
530
|
+
// After super so the snapshot cache key sees the new camera props
|
|
531
|
+
if (staticKeyChanged && _staticMode && _mapWrapperView) {
|
|
532
|
+
[self resetStaticContent];
|
|
533
|
+
}
|
|
384
534
|
}
|
|
385
535
|
|
|
386
536
|
#pragma mark - Commands
|
package/ios/LuggMapWrapperView.h
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
6
|
@interface LuggMapWrapperView : RCTViewComponentView
|
|
7
|
+
|
|
8
|
+
/// Called after layout; lets the static snapshotter start (or restart)
|
|
9
|
+
/// once the wrapper has a real size, since a snapshot can't autoresize
|
|
10
|
+
@property(nonatomic, copy, nullable) void (^layoutHandler)(void);
|
|
11
|
+
|
|
7
12
|
@end
|
|
8
13
|
|
|
9
14
|
NS_ASSUME_NONNULL_END
|
|
@@ -29,6 +29,24 @@ using namespace facebook::react;
|
|
|
29
29
|
return self;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
- (void)layoutSubviews {
|
|
33
|
+
[super layoutSubviews];
|
|
34
|
+
if (self.layoutHandler) {
|
|
35
|
+
self.layoutHandler();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
- (void)prepareForRecycle {
|
|
40
|
+
[super prepareForRecycle];
|
|
41
|
+
|
|
42
|
+
// Static placeholder styling is applied outside of props
|
|
43
|
+
// (LuggMapView applyStaticPlaceholderBackground) and would otherwise
|
|
44
|
+
// leak into the next map that reuses this recycled view
|
|
45
|
+
self.backgroundColor = nil;
|
|
46
|
+
self.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
|
|
47
|
+
self.layoutHandler = nil;
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
Class<RCTComponentViewProtocol> LuggMapWrapperViewCls(void) {
|
|
33
51
|
return LuggMapWrapperView.class;
|
|
34
52
|
}
|
package/ios/LuggMarkerView.mm
CHANGED
|
@@ -287,6 +287,8 @@ using namespace luggmaps::events;
|
|
|
287
287
|
_iconView.layer.anchorPoint = CGPointMake(0.5, 0.5);
|
|
288
288
|
_iconView.frame = CGRectMake(0, 0, _iconView.bounds.size.width,
|
|
289
289
|
_iconView.bounds.size.height);
|
|
290
|
+
// Static providers hide the icon view until positioned
|
|
291
|
+
_iconView.hidden = NO;
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
- (void)showCallout {
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
|
+
/// POI filter for the given props, shared by the live map view and the
|
|
7
|
+
/// static snapshotter. Returns nil for "show everything".
|
|
8
|
+
MKPointOfInterestFilter *_Nullable LuggPointOfInterestFilter(
|
|
9
|
+
BOOL poiEnabled, facebook::react::LuggMapViewPoiFilterMode filterMode,
|
|
10
|
+
NSArray<NSString *> *filterCategories);
|
|
11
|
+
|
|
12
|
+
MKMapType LuggMKMapTypeFromMapType(facebook::react::LuggMapViewMapType mapType);
|
|
13
|
+
|
|
6
14
|
@interface LuggAppleMapViewContent : MKMapView
|
|
7
15
|
@end
|
|
8
16
|
|
|
@@ -145,6 +145,7 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
@synthesize delegate = _delegate;
|
|
148
|
+
@synthesize staticMode = _staticMode;
|
|
148
149
|
|
|
149
150
|
- (instancetype)init {
|
|
150
151
|
if (self = [super init]) {
|
|
@@ -182,6 +183,10 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
182
183
|
_mapView.delegate = self;
|
|
183
184
|
_mapView.insetsLayoutMarginsFromSafeArea = NO;
|
|
184
185
|
|
|
186
|
+
if (_staticMode) {
|
|
187
|
+
_mapView.userInteractionEnabled = NO;
|
|
188
|
+
}
|
|
189
|
+
|
|
185
190
|
[self applyZoomRange];
|
|
186
191
|
|
|
187
192
|
_tapGesture =
|
|
@@ -212,6 +217,11 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
212
217
|
}
|
|
213
218
|
|
|
214
219
|
- (void)destroy {
|
|
220
|
+
[self destroyMapView];
|
|
221
|
+
_isMapReady = NO;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
- (void)destroyMapView {
|
|
215
225
|
[self dismissNonBubbledCallout];
|
|
216
226
|
[self stopEdgeInsetsAnimation];
|
|
217
227
|
if (_tapGesture) {
|
|
@@ -222,9 +232,9 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
222
232
|
[_mapView removeGestureRecognizer:_longPressGesture];
|
|
223
233
|
_longPressGesture = nil;
|
|
224
234
|
}
|
|
235
|
+
_mapView.delegate = nil;
|
|
225
236
|
[_mapView removeFromSuperview];
|
|
226
237
|
_mapView = nil;
|
|
227
|
-
_isMapReady = NO;
|
|
228
238
|
}
|
|
229
239
|
|
|
230
240
|
#pragma mark - Props
|
|
@@ -342,33 +352,36 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
342
352
|
return map[string];
|
|
343
353
|
}
|
|
344
354
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return;
|
|
355
|
+
MKPointOfInterestFilter *_Nullable LuggPointOfInterestFilter(
|
|
356
|
+
BOOL poiEnabled, LuggMapViewPoiFilterMode filterMode,
|
|
357
|
+
NSArray<NSString *> *filterCategories) {
|
|
358
|
+
if (!poiEnabled) {
|
|
359
|
+
return MKPointOfInterestFilter.filterExcludingAllCategories;
|
|
350
360
|
}
|
|
351
|
-
if (
|
|
361
|
+
if (filterCategories.count > 0) {
|
|
352
362
|
NSMutableArray<MKPointOfInterestCategory> *categories =
|
|
353
363
|
[NSMutableArray array];
|
|
354
|
-
for (NSString *name in
|
|
364
|
+
for (NSString *name in filterCategories) {
|
|
355
365
|
MKPointOfInterestCategory category = poiCategoryFromString(name);
|
|
356
366
|
if (category) {
|
|
357
367
|
[categories addObject:category];
|
|
358
368
|
}
|
|
359
369
|
}
|
|
360
370
|
if (categories.count > 0) {
|
|
361
|
-
if (
|
|
362
|
-
|
|
371
|
+
if (filterMode == LuggMapViewPoiFilterMode::Excluding) {
|
|
372
|
+
return [[MKPointOfInterestFilter alloc]
|
|
363
373
|
initExcludingCategories:categories];
|
|
364
|
-
} else {
|
|
365
|
-
_mapView.pointOfInterestFilter = [[MKPointOfInterestFilter alloc]
|
|
366
|
-
initIncludingCategories:categories];
|
|
367
374
|
}
|
|
368
|
-
return
|
|
375
|
+
return
|
|
376
|
+
[[MKPointOfInterestFilter alloc] initIncludingCategories:categories];
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
|
-
|
|
379
|
+
return nil;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
- (void)applyPoiFilter {
|
|
383
|
+
_mapView.pointOfInterestFilter = LuggPointOfInterestFilter(
|
|
384
|
+
_poiEnabled, _poiFilterMode, _poiFilterCategories);
|
|
372
385
|
}
|
|
373
386
|
|
|
374
387
|
- (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
|
|
@@ -399,26 +412,25 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
399
412
|
[self applyPoiFilter];
|
|
400
413
|
}
|
|
401
414
|
|
|
402
|
-
|
|
415
|
+
MKMapType LuggMKMapTypeFromMapType(LuggMapViewMapType mapType) {
|
|
403
416
|
switch (mapType) {
|
|
404
417
|
case LuggMapViewMapType::Satellite:
|
|
405
|
-
|
|
406
|
-
break;
|
|
418
|
+
return MKMapTypeSatellite;
|
|
407
419
|
case LuggMapViewMapType::Terrain:
|
|
408
|
-
|
|
409
|
-
break;
|
|
420
|
+
return MKMapTypeStandard;
|
|
410
421
|
case LuggMapViewMapType::Hybrid:
|
|
411
|
-
|
|
412
|
-
break;
|
|
422
|
+
return MKMapTypeHybrid;
|
|
413
423
|
case LuggMapViewMapType::MutedStandard:
|
|
414
|
-
|
|
415
|
-
break;
|
|
424
|
+
return MKMapTypeMutedStandard;
|
|
416
425
|
default:
|
|
417
|
-
|
|
418
|
-
break;
|
|
426
|
+
return MKMapTypeStandard;
|
|
419
427
|
}
|
|
420
428
|
}
|
|
421
429
|
|
|
430
|
+
- (void)setMapType:(LuggMapViewMapType)mapType {
|
|
431
|
+
_mapView.mapType = LuggMKMapTypeFromMapType(mapType);
|
|
432
|
+
}
|
|
433
|
+
|
|
422
434
|
- (void)setTheme:(LuggMapViewTheme)theme {
|
|
423
435
|
switch (theme) {
|
|
424
436
|
case LuggMapViewTheme::Dark:
|
|
@@ -836,7 +848,8 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
836
848
|
renderer.strokeColors = colors;
|
|
837
849
|
}
|
|
838
850
|
renderer.animatedOptions = polylineView.animatedOptions;
|
|
839
|
-
|
|
851
|
+
// Static maps snapshot an arbitrary frame; render the full polyline
|
|
852
|
+
renderer.animated = polylineView.animated && !_staticMode;
|
|
840
853
|
polylineView.renderer = renderer;
|
|
841
854
|
return renderer;
|
|
842
855
|
}
|
|
@@ -1365,7 +1378,7 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
1365
1378
|
renderer.strokeColors =
|
|
1366
1379
|
polylineView.strokeColors.count > 1 ? polylineView.strokeColors : nil;
|
|
1367
1380
|
renderer.animatedOptions = polylineView.animatedOptions;
|
|
1368
|
-
renderer.animated = polylineView.animated;
|
|
1381
|
+
renderer.animated = polylineView.animated && !_staticMode;
|
|
1369
1382
|
return;
|
|
1370
1383
|
}
|
|
1371
1384
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import "StaticMapProviderBase.h"
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Static Apple map rendered with MKMapSnapshotter. The base map renders
|
|
7
|
+
* fully asynchronously (no live MKMapView is ever created), so static maps
|
|
8
|
+
* keep loading while the user scrolls without blocking the main thread.
|
|
9
|
+
*/
|
|
10
|
+
@interface AppleStaticMapProvider : StaticMapProviderBase
|
|
11
|
+
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_END
|