diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapViewManager.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapViewManager.kt index 62f857d4f..e4b96c59f 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapViewManager.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapViewManager.kt @@ -23,6 +23,8 @@ import com.rnmapbox.rnmbx.utils.ConvertUtils import com.rnmapbox.rnmbx.utils.ExpressionParser import com.rnmapbox.rnmbx.utils.Logger import com.rnmapbox.rnmbx.utils.ViewTagResolver +import com.rnmapbox.rnmbx.utils.extensions.getAndLogIfNotBoolean +import com.rnmapbox.rnmbx.utils.extensions.getAndLogIfNotDouble import com.rnmapbox.rnmbx.utils.extensions.toCoordinate import com.rnmapbox.rnmbx.utils.extensions.toRectF import com.rnmapbox.rnmbx.utils.extensions.toScreenCoordinate @@ -139,6 +141,55 @@ open class RNMBXMapViewManager(context: ReactApplicationContext, val viewTagReso mapView.setReactLocalizeLabels(locale, layerIds) } + @ReactProp(name = "gestureSettings") + override fun setGestureSettings(mapView: RNMBXMapView, settings: Dynamic) { + mapView.getMapboxMap().gesturesPlugin { + val map = settings.asMap() + this.updateSettings { + map.getAndLogIfNotBoolean("doubleTapToZoomInEnabled", LOG_TAG)?.let { + this.doubleTapToZoomInEnabled = it + } + map.getAndLogIfNotBoolean("doubleTouchToZoomOutEnabled", LOG_TAG)?.let { + this.doubleTouchToZoomOutEnabled = it + } + map.getAndLogIfNotBoolean("pinchPanEnabled", LOG_TAG)?.let { + this.pinchScrollEnabled = it + } + map.getAndLogIfNotBoolean("pinchZoomEnabled", LOG_TAG)?.let { + this.pinchToZoomEnabled = it + } + map.getAndLogIfNotBoolean("pinchZoomDecelerationEnabled", LOG_TAG)?.let { + this.pinchToZoomDecelerationEnabled = it + } + map.getAndLogIfNotBoolean("pitchEnabled", LOG_TAG)?.let { + this.pitchEnabled = it + } + map.getAndLogIfNotBoolean("quickZoomEnabled", LOG_TAG)?.let { + this.quickZoomEnabled = it + } + map.getAndLogIfNotBoolean("rotateEnabled", LOG_TAG)?.let { + this.rotateEnabled = it + } + map.getAndLogIfNotBoolean("rotateDecelerationEnabled", LOG_TAG)?.let { + this.rotateDecelerationEnabled = it + } + map.getAndLogIfNotBoolean("panEnabled", LOG_TAG)?.let { + this.scrollEnabled = it + } + map.getAndLogIfNotDouble("panDecelerationFactor", LOG_TAG)?.let { + this.scrollDecelerationEnabled = it > 0.0 + } + map.getAndLogIfNotBoolean("simultaneousRotateAndPinchToZoomEnabled", LOG_TAG)?.let { + this.simultaneousRotateAndPinchToZoomEnabled = it + } + map.getAndLogIfNotDouble("zoomAnimationAmount", LOG_TAG)?.let { + this.zoomAnimationAmount = it.toFloat() + } + } + } + + } + @ReactProp(name = "styleURL") override fun setStyleURL(mapView: RNMBXMapView, styleURL:Dynamic) { mapView.setReactStyleURL(styleURL.asString()) diff --git a/android/src/main/java/com/rnmapbox/rnmbx/utils/extensions/ReadableMap.kt b/android/src/main/java/com/rnmapbox/rnmbx/utils/extensions/ReadableMap.kt index 98d659c9d..2297b9648 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/utils/extensions/ReadableMap.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/utils/extensions/ReadableMap.kt @@ -2,6 +2,7 @@ package com.rnmapbox.rnmbx.utils.extensions import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType +import com.rnmapbox.rnmbx.utils.Logger fun ReadableMap.forEach(action: (String, Any) -> Unit) { val iterator = this.entryIterator @@ -17,3 +18,37 @@ fun ReadableMap.getIfDouble(key: String): Double? { null } } + +fun ReadableMap.getIfBoolean(key: String): Boolean? { + return if (hasKey(key) && (getType(key) == ReadableType.Boolean)) { + getBoolean(key) + } else { + null + } +} + +fun ReadableMap.getAndLogIfNotBoolean(key: String, tag:String = "RNMBXReadableMap"): Boolean? { + return if (hasKey(key)) { + if (getType(key) == ReadableType.Boolean) { + getBoolean(key) + } else { + Logger.e("RNMBXReadableMap", "$key is expected to be a boolean but was: ${getType(key)}") + null + } + } else { + null + } +} + +fun ReadableMap.getAndLogIfNotDouble(key: String, tag: String = "RNMBXReadableMap"): Double? { + return if (hasKey(key)) { + if (getType(key) == ReadableType.Number) { + getDouble(key) + } else { + Logger.e("RNMBXReadableMap", "$key is expected to be a double but was: ${getType(key)}") + null + } + } else { + null + } +} diff --git a/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerDelegate.java b/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerDelegate.java index 4ca2e6166..84028e170 100644 --- a/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerDelegate.java +++ b/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerDelegate.java @@ -79,6 +79,9 @@ public void setProperty(T view, String propName, @Nullable Object value) { case "styleURL": mViewManager.setStyleURL(view, new DynamicFromObject(value)); break; + case "gestureSettings": + mViewManager.setGestureSettings(view, new DynamicFromObject(value)); + break; case "scaleBarViewMargins": mViewManager.setScaleBarViewMargins(view, new DynamicFromObject(value)); break; diff --git a/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerInterface.java b/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerInterface.java index 37f6a43a3..a92d2e0d2 100644 --- a/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerInterface.java +++ b/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerInterface.java @@ -32,6 +32,7 @@ public interface RNMBXMapViewManagerInterface { void setProjection(T view, Dynamic value); void setLocalizeLabels(T view, Dynamic value); void setStyleURL(T view, Dynamic value); + void setGestureSettings(T view, Dynamic value); void setScaleBarViewMargins(T view, Dynamic value); void setAttributionViewMargins(T view, Dynamic value); void setAttributionViewPosition(T view, Dynamic value); diff --git a/docs/MapView.md b/docs/MapView.md index 4c89b327e..9885b4ee5 100644 --- a/docs/MapView.md +++ b/docs/MapView.md @@ -30,7 +30,6 @@ The distance from the edges of the map view’s frame to the edges of the map vi ``` The projection used when rendering the map - _defaults to:_ `'mercator'` ### styleURL @@ -275,6 +274,35 @@ Set map's label locale, e.g. { "locale": "es" } will localize labels to Spanish, +### gestureSettings + +```tsx +type GestureSettings = { + doubleTapToZoomInEnabled: boolean; /* Whether double tapping the map with one touch results in a zoom-in animation. */ + doubleTouchToZoomOutEnabled: boolean; /* Whether single tapping the map with two touches results in a zoom-out animation. */ + pinchPanEnabled: boolean; /* Whether pan/scroll is enabled for the pinch gesture. */ + pinchZoomEnabled: boolean; /* Whether zoom is enabled for the pinch gesture. */ + pinchZoomDecelerationEnabled: boolean; /* Whether a deceleration animation following a pinch-zoom gesture is enabled. True by default. +(Android only) */ + pitchEnabled: boolean; /* Whether the pitch gesture is enabled. */ + quickZoomEnabled: boolean; /* Whether the quick zoom gesture is enabled. */ + rotateEnabled: boolean; /* Whether the rotate gesture is enabled. */ + rotateDecelerationEnabled: boolean; /* Whether a deceleration animation following a rotate gesture is enabled. True by default. +(Android only) */ + panEnabled: boolean; /* Whether the single-touch pan/scroll gesture is enabled. */ + panDecelerationFactor: number; /* A constant factor that determines how quickly pan deceleration animations happen. Multiplied with the velocity vector once per millisecond during deceleration animations. + +On iOS Defaults to UIScrollView.DecelerationRate.normal.rawValue +On android set to 0 to disable deceleration, and non zero to enabled it. */ + simultaneousRotateAndPinchZoomEnabled: boolean; /* Whether rotation is enabled for the pinch zoom gesture. */ + zoomAnimationAmount: number; /* The amount by which the zoom level increases or decreases during a double-tap-to-zoom-in or double-touch-to-zoom-out gesture. 1.0 by default. Must be positive. +(Android only) */ +} +``` +Gesture configuration allows to control the user touch interaction. + + + ### onPress ```tsx diff --git a/docs/docs.json b/docs/docs.json index a5e3b1e6a..a45abe391 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -3543,7 +3543,7 @@ "name": "projection", "required": false, "type": "'mercator' \\| 'globe'", - "default": "'mercator'", + "default": "none", "description": "The projection used when rendering the map" }, { @@ -3725,6 +3725,108 @@ "default": "none", "description": "[`mapbox` (v10) implementation only]\nSet map's label locale, e.g. { \"locale\": \"es\" } will localize labels to Spanish, { \"locale\": \"current\" } will localize labels to system locale." }, + { + "name": "gestureSettings", + "required": false, + "type": { + "name": "shape", + "value": [ + { + "name": "doubleTapToZoomInEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether double tapping the map with one touch results in a zoom-in animation." + }, + { + "name": "doubleTouchToZoomOutEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether single tapping the map with two touches results in a zoom-out animation." + }, + { + "name": "pinchPanEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether pan/scroll is enabled for the pinch gesture." + }, + { + "name": "pinchZoomEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether zoom is enabled for the pinch gesture." + }, + { + "name": "pinchZoomDecelerationEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether a deceleration animation following a pinch-zoom gesture is enabled. True by default.\n(Android only)" + }, + { + "name": "pitchEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether the pitch gesture is enabled." + }, + { + "name": "quickZoomEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether the quick zoom gesture is enabled." + }, + { + "name": "rotateEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether the rotate gesture is enabled." + }, + { + "name": "rotateDecelerationEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether a deceleration animation following a rotate gesture is enabled. True by default.\n(Android only)" + }, + { + "name": "panEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether the single-touch pan/scroll gesture is enabled." + }, + { + "name": "panDecelerationFactor", + "required": false, + "type": "number", + "default": "none", + "description": "A constant factor that determines how quickly pan deceleration animations happen. Multiplied with the velocity vector once per millisecond during deceleration animations.\n\nOn iOS Defaults to UIScrollView.DecelerationRate.normal.rawValue\nOn android set to 0 to disable deceleration, and non zero to enabled it." + }, + { + "name": "simultaneousRotateAndPinchZoomEnabled", + "required": false, + "type": "boolean", + "default": "none", + "description": "Whether rotation is enabled for the pinch zoom gesture." + }, + { + "name": "zoomAnimationAmount", + "required": false, + "type": "number", + "default": "none", + "description": "The amount by which the zoom level increases or decreases during a double-tap-to-zoom-in or double-touch-to-zoom-out gesture. 1.0 by default. Must be positive.\n(Android only)" + } + ] + }, + "default": "none", + "description": "Gesture configuration allows to control the user touch interaction." + }, { "name": "onPress", "required": false, diff --git a/example/ios/Podfile b/example/ios/Podfile index be48257e4..e937147b8 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -9,7 +9,7 @@ prepare_react_native_project! $RNMapboxMapsImpl = 'mapbox' if ENV['RNMBX11'] $RNMapboxMapsUseV11 = true # use 11 version - $RNMapboxMapsVersion = '= 11.0.0-beta.4' + $RNMapboxMapsVersion = '= 11.0.0-beta.5' end @@ -21,7 +21,7 @@ if ENV['CI_MAP_IMPL'] when 'mapbox11' $RNMapboxMapsImpl = 'mapbox' $RNMapboxMapsUseV11 = true # use 11 version - $RNMapboxMapsVersion = '= 11.0.0-beta.3' + $RNMapboxMapsVersion = '= 11.0.0-beta.5' else fail "Invalid CI_MAP_IMPL value: #{ENV['CI_MAP_IMPL']}" end diff --git a/ios/RNMBX/CustomHttpHeaders.swift b/ios/RNMBX/CustomHttpHeaders.swift index 439d0f8b5..57e3f5912 100644 --- a/ios/RNMBX/CustomHttpHeaders.swift +++ b/ios/RNMBX/CustomHttpHeaders.swift @@ -1,6 +1,17 @@ import MapboxMaps class CustomHttpHeaders : HttpServiceInterceptorInterface { + #if RNMBX_11 + func onRequest(for request: HttpRequest, continuation: @escaping HttpServiceInterceptorRequestContinuation) { + let request = onRequest(for: request) + continuation(HttpRequestOrResponse.fromHttpRequest(request)) + } + + func onResponse(for response: HttpResponse, continuation: @escaping HttpServiceInterceptorResponseContinuation) { + continuation(response) + } + #endif + static var shared : CustomHttpHeaders = { let headers = CustomHttpHeaders() headers.install() diff --git a/ios/RNMBX/RNMBXMapView.swift b/ios/RNMBX/RNMBXMapView.swift index df709d259..22923ff93 100644 --- a/ios/RNMBX/RNMBXMapView.swift +++ b/ios/RNMBX/RNMBXMapView.swift @@ -255,7 +255,9 @@ open class RNMBXMapView : MapView { @objc public func setReactProjection(_ value: String?) { if let value = value { let projection = StyleProjection(name: value == "globe" ? .globe : .mercator) - try! self.mapboxMap.style.setProjection(projection) + logged("RNMBXMapView.setReactProjection") { + try self.mapboxMap.style.setProjection(projection) + } } } @@ -272,6 +274,59 @@ open class RNMBXMapView : MapView { } } + @objc public func setReactGestureSettings(_ value: NSDictionary?) { + if let value = value, let gestures = self.mapView.gestures { + var options = gestures.options + if let doubleTapToZoomInEnabled = value["doubleTapToZoomInEnabled"] as? NSNumber { + options.doubleTapToZoomInEnabled = doubleTapToZoomInEnabled.boolValue + } + if let doubleTouchToZoomOutEnabled = value["doubleTouchToZoomOutEnabled"] as? NSNumber { + options.doubleTouchToZoomOutEnabled = doubleTouchToZoomOutEnabled.boolValue + } + if let pinchScrollEnabled = value["pinchPanEnabled"] as? NSNumber { + options.pinchPanEnabled = pinchScrollEnabled.boolValue + } + if let pinchZoomEnabled = value["pinchZoomEnabled"] as? NSNumber { + options.pinchZoomEnabled = pinchZoomEnabled.boolValue + } + /* android only + if let pinchZoomDecelerationEnabled = value["pinchZoomDecelerationEnabled"] as? NSNumber { + options.pinchZoomDecelerationEnabled = pinchZoomDecelerationEnabled.boolValue + } + */ + if let pitchEnabled = value["pitchEnabled"] as? NSNumber { + options.pitchEnabled = pitchEnabled.boolValue + } + if let quickZoomEnabled = value["quickZoomEnabled"] as? NSNumber { + options.quickZoomEnabled = quickZoomEnabled.boolValue + } + if let rotateEnabled = value["rotateEnabled"] as? NSNumber { + options.rotateEnabled = rotateEnabled.boolValue + } + /* android only + if let rotateDecelerationEnabled = value["rotateDecelerationEnabled"] as? NSNumber { + options.rotateDecelerationEnabled = rotateDecelerationEnabled.boolValue + }*/ + if let panEnabled = value["panEnabled"] as? NSNumber { + options.panEnabled = panEnabled.boolValue + } + if let panDecelerationFactor = value["panDecelerationFactor"] as? NSNumber { + options.panDecelerationFactor = panDecelerationFactor.CGFloat + } + #if RNMBX_11 + if let simultaneousRotateAndPinchZoomEnabled = value["simultaneousRotateAndPinchZoomEnabled"] as? NSNumber { + options.simultaneousRotateAndPinchZoomEnabled = simultaneousRotateAndPinchZoomEnabled.boolValue + } + #endif + /* android only + if let zoomAnimationAmount = value["zoomAnimationAmount"] as? NSNumber { + options.zoomAnimationAmount = zoomAnimationAmount.CGFloat + }*/ + if options != gestures.options { + gestures.options = options + } + } + } @objc public func setReactAttributionEnabled(_ value: Bool) { mapView.ornaments.options.attributionButton.visibility = value ? .visible : .hidden diff --git a/ios/RNMBX/RNMBXMapViewManager.m b/ios/RNMBX/RNMBXMapViewManager.m index 5a2d018ed..46aa79278 100644 --- a/ios/RNMBX/RNMBXMapViewManager.m +++ b/ios/RNMBX/RNMBXMapViewManager.m @@ -29,6 +29,8 @@ @interface RCT_EXTERN_REMAP_MODULE(RNMBXMapView, RNMBXMapViewManager, RCTViewMan RCT_REMAP_VIEW_PROPERTY(projection, reactProjection, NSString) RCT_REMAP_VIEW_PROPERTY(localizeLabels, reactLocalizeLabels, NSDictionary) +RCT_REMAP_VIEW_PROPERTY(gestureSettings, reactGestureSettings, NSDictionary) + RCT_REMAP_VIEW_PROPERTY(styleURL, reactStyleURL, NSString) RCT_REMAP_VIEW_PROPERTY(onPress, reactOnPress, RCTBubblingEventBlock) RCT_REMAP_VIEW_PROPERTY(onLongPress, reactOnLongPress, RCTBubblingEventBlock) diff --git a/ios/install.md b/ios/install.md index 809f87608..9656a21b2 100644 --- a/ios/install.md +++ b/ios/install.md @@ -90,7 +90,7 @@ Add the following to your Podfile: ```ruby $RNMapboxMapsUseV11 = true # use 11 version -$RNMapboxMapsVersion = '= 11.0.0-beta.3' +$RNMapboxMapsVersion = '= 11.0.0-beta.5' ``` diff --git a/src/components/MapView.tsx b/src/components/MapView.tsx index 28f731f1b..22215f736 100644 --- a/src/components/MapView.tsx +++ b/src/components/MapView.tsx @@ -69,6 +69,67 @@ export type RegionPayload = { pitch: number; }; +export type GestureSettings = { + /** + * Whether double tapping the map with one touch results in a zoom-in animation. + */ + doubleTapToZoomInEnabled?: boolean; + /** + * Whether single tapping the map with two touches results in a zoom-out animation. + */ + doubleTouchToZoomOutEnabled?: boolean; + /** + * Whether pan/scroll is enabled for the pinch gesture. + */ + pinchPanEnabled?: boolean; + /** + * Whether zoom is enabled for the pinch gesture. + */ + pinchZoomEnabled?: boolean; + /** + * Whether a deceleration animation following a pinch-zoom gesture is enabled. True by default. + * (Android only) + */ + pinchZoomDecelerationEnabled?: boolean; + /** + * Whether the pitch gesture is enabled. + */ + pitchEnabled?: boolean; + /** + * Whether the quick zoom gesture is enabled. + */ + quickZoomEnabled?: boolean; + /** + * Whether the rotate gesture is enabled. + */ + rotateEnabled?: boolean; + /** + * Whether a deceleration animation following a rotate gesture is enabled. True by default. + * (Android only) + */ + rotateDecelerationEnabled?: boolean; + /** + * Whether the single-touch pan/scroll gesture is enabled. + */ + panEnabled?: boolean; + /** + * A constant factor that determines how quickly pan deceleration animations happen. Multiplied with the velocity vector once per millisecond during deceleration animations. + * + * On iOS Defaults to UIScrollView.DecelerationRate.normal.rawValue + * On android set to 0 to disable deceleration, and non zero to enabled it. + */ + panDecelerationFactor?: number; + /** + * Whether rotation is enabled for the pinch zoom gesture. + */ + simultaneousRotateAndPinchZoomEnabled?: boolean; + /** + * The amount by which the zoom level increases or decreases during a double-tap-to-zoom-in or double-touch-to-zoom-out gesture. 1.0 by default. Must be positive. + * (Android only) + */ + zoomAnimationAmount?: number; +}; + /** * v10 only */ @@ -244,6 +305,11 @@ type Props = ViewProps & { */ localizeLabels?: LocalizeLabels; + /** + * Gesture configuration allows to control the user touch interaction. + */ + gestureSettings?: GestureSettings; + /** * Map press listener, gets called when a user presses the map */ @@ -400,7 +466,6 @@ class MapView extends NativeBridgeComponent( NATIVE_MODULE_NAME, ) { static defaultProps: Props = { - projection: 'mercator', scrollEnabled: true, pitchEnabled: true, rotateEnabled: true, diff --git a/src/specs/RNMBXMapViewNativeComponent.ts b/src/specs/RNMBXMapViewNativeComponent.ts index 81872e538..0d4da8e53 100644 --- a/src/specs/RNMBXMapViewNativeComponent.ts +++ b/src/specs/RNMBXMapViewNativeComponent.ts @@ -10,6 +10,13 @@ import type { LocalizeLabels, Point, UnsafeMixed } from './codegenUtils'; // see https://github.com/rnmapbox/maps/wiki/FabricOptionalProp type OptionalProp = UnsafeMixed; +type GestureSettings = { + doubleTapToZoomInEnabled?: boolean; + doubleTouchToZoomOutEnabled?: boolean; + pinchScrollEnabled?: boolean; + pinchToZoomDecelerationEnabled?: boolean; +}; + type OnCameraChangedEventType = { type: string; payload: string }; type OnPressEventType = { type: string; payload: string }; type OnMapChangeEventType = { type: string; payload: string }; @@ -44,6 +51,8 @@ export interface NativeProps extends ViewProps { styleURL?: OptionalProp; + gestureSettings?: UnsafeMixed; + // Android only scaleBarViewMargins?: UnsafeMixed; attributionViewMargins?: UnsafeMixed;