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
74 changes: 74 additions & 0 deletions __tests__/components/Viewport.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { render } from '@testing-library/react-native';

import {
Viewport,
normalizeTransitionsToIdleUponUserInteraction,
} from '../../src/components/Viewport';
import RNMBXViewport from '../../src/specs/RNMBXViewportNativeComponent';

function nativeTransitionsToIdle(element: React.ReactElement) {
const { UNSAFE_getByType } = render(element);
return UNSAFE_getByType(RNMBXViewport).props
.transitionsToIdleUponUserInteraction;
}

describe('Viewport transitionsToIdleUponUserInteraction', () => {
it('passes booleans through unchanged', () => {
expect(
nativeTransitionsToIdle(
<Viewport transitionsToIdleUponUserInteraction={true} />,
),
).toBe(true);
expect(
nativeTransitionsToIdle(
<Viewport transitionsToIdleUponUserInteraction={false} />,
),
).toBe(false);
});

it('passes nothing when unset', () => {
expect(nativeTransitionsToIdle(<Viewport />)).toBeUndefined();
});

it('defaults omitted gestures to true', () => {
expect(
nativeTransitionsToIdle(
<Viewport
transitionsToIdleUponUserInteraction={{ pinch: false, rotate: false }}
/>,
),
).toEqual({
pan: true,
pinch: false,
doubleTap: true,
rotate: false,
pitch: true,
});
});

it('treats an empty object like true for every gesture', () => {
expect(normalizeTransitionsToIdleUponUserInteraction({})).toEqual({
pan: true,
pinch: true,
doubleTap: true,
rotate: true,
pitch: true,
});
});

it('drops unknown keys', () => {
expect(
normalizeTransitionsToIdleUponUserInteraction({
pan: false,
// @ts-expect-error unknown gesture
swipe: false,
}),
).toEqual({
pan: false,
pinch: true,
doubleTap: true,
rotate: true,
pitch: true,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.rnmapbox.rnmbx.components.camera
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Dynamic
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableType
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.common.MapBuilder
Expand Down Expand Up @@ -34,7 +35,11 @@ class RNMBXViewportManager(private val mContext: ReactApplicationContext, val vi

@ReactProp(name = "transitionsToIdleUponUserInteraction")
override fun setTransitionsToIdleUponUserInteraction(view: RNMBXViewport?, value: Dynamic?) {
view?.transitionsToIdleUponUserInteraction = value?.asBoolean()
when (value?.type) {
ReadableType.Boolean -> view?.setTransitionsToIdleUponUserInteraction(value.asBoolean(), null)
ReadableType.Map -> view?.setTransitionsToIdleUponUserInteraction(null, RNMBXViewportGestureHandler.IdleGestures.from(value.asMap()))
else -> view?.setTransitionsToIdleUponUserInteraction(null, null)
}
}

@ReactProp(name = "hasStatusChanged")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,53 @@ AbstractMapFeature(
mContext
) {
// region properties
var transitionsToIdleUponUserInteraction: Boolean? = null
set(value: Boolean?) {
field = value
if (value != null) {
mMapView?.let { applyTransitionsToIdleUponUserIntraction(it.mapView) }
private var transitionsToIdleUponUserInteraction: Boolean? = null
private var idleGestures: RNMBXViewportGestureHandler.IdleGestures? = null

/** Sets the boolean form, or the per-gesture form when `idleGestures` is non-null. */
fun setTransitionsToIdleUponUserInteraction(value: Boolean?, idleGestures: RNMBXViewportGestureHandler.IdleGestures?) {
this.transitionsToIdleUponUserInteraction = if (idleGestures != null) null else value
this.idleGestures = idleGestures
mMapView?.let { applyTransitionsToIdleUponUserIntraction(it) }
}

private var gestureHandler: RNMBXViewportGestureHandler? = null
private var gestureIdlePending = false

fun applyTransitionsToIdleUponUserIntraction(map: RNMBXMapView) {
val mapView = map.mapView
val idleGestures = this.idleGestures
val transitionsToIdle = this.transitionsToIdleUponUserInteraction
if (idleGestures != null) {
val gestureHandler = this.gestureHandler
if (gestureHandler != null) {
gestureHandler.idleGestures = idleGestures
} else {
this.gestureHandler = RNMBXViewportGestureHandler(map, idleGestures) {
gestureIdlePending = hasStatusChanged
}
}
setSdkTransitionsToIdle(mapView, false)
} else {
val hadGestureHandler = gestureHandler != null
gestureHandler?.detach()
gestureHandler = null
when {
transitionsToIdle != null -> setSdkTransitionsToIdle(mapView, transitionsToIdle)
hadGestureHandler -> restoreTransitionsToIdleUponUserInteraction(mapView)
}
}
}

// The map's value before this Viewport changed it, restored on removal so the option doesn't outlive the component.
private var savedTransitionsToIdleUponUserInteraction: Boolean? = null

fun applyTransitionsToIdleUponUserIntraction(mapView: MapView) {
this.transitionsToIdleUponUserInteraction?.let {
val options = mapView.viewport.options
if (savedTransitionsToIdleUponUserInteraction == null) {
savedTransitionsToIdleUponUserInteraction = options.transitionsToIdleUponUserInteraction
}
mapView.viewport.options = options.toBuilder().transitionsToIdleUponUserInteraction(it).build()
private fun setSdkTransitionsToIdle(mapView: MapView, value: Boolean) {
val options = mapView.viewport.options
if (savedTransitionsToIdleUponUserInteraction == null) {
savedTransitionsToIdleUponUserInteraction = options.transitionsToIdleUponUserInteraction
}
mapView.viewport.options = options.toBuilder().transitionsToIdleUponUserInteraction(value).build()
}

private fun restoreTransitionsToIdleUponUserInteraction(mapView: MapView) {
Expand All @@ -105,7 +133,12 @@ mContext
if (hasStatusChanged) {
if (statusObserver == null) {
val statusObserver = ViewportStatusObserver { from, to, reason ->
val payload = writableMapOf("from" to statusToMap(from), "to" to statusToMap(to), "reason" to reasonToSrting(reason))
val userInteraction = gestureIdlePending && to is ViewportStatus.Idle && reason == ViewportStatusChangeReason.IDLE_REQUESTED
if (userInteraction) {
gestureIdlePending = false
}
val reasonString = if (userInteraction) reasonToSrting(ViewportStatusChangeReason.USER_INTERACTION) else reasonToSrting(reason)
val payload = writableMapOf("from" to statusToMap(from), "to" to statusToMap(to), "reason" to reasonString)
mManager.dispatchEvent(
BaseEvent(
UIManagerHelper.getSurfaceId(mContext),
Expand All @@ -131,11 +164,13 @@ mContext

override fun addToMap(mapView: RNMBXMapView) {
super.addToMap(mapView)
applyTransitionsToIdleUponUserIntraction(mapView.mapView)
applyTransitionsToIdleUponUserIntraction(mapView)
applyHasStatusChanged(mapView.mapView)
}

override fun removeFromMap(mapView: RNMBXMapView, reason: RemovalReason): Boolean {
gestureHandler?.detach()
gestureHandler = null
restoreTransitionsToIdleUponUserInteraction(mapView.mapView)
return super.removeFromMap(mapView, reason)
}
Expand Down
Loading
Loading