Skip to content

fix(android): preserve camera updates during long presses - #4358

Open
RobJacobson wants to merge 3 commits into
rnmapbox:mainfrom
RobJacobson:codex/issue-2738-long-press-camera
Open

RobJacobson wants to merge 3 commits into
rnmapbox:mainfrom
RobJacobson:codex/issue-2738-long-press-camera

Conversation

@RobJacobson

@RobJacobson RobJacobson commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Description

Addresses the Android camera behavior reported in #2738.

Finger movement after a long press can override a camera command started by onLongPress. Before a queued camera update, moveBy, or scaleBy, the wrapper cancels the active native map long press and consumes its remaining touch events until release. A fresh touch pans normally, and long presses without camera commands keep normal panning.

The relative camera methods use the same cancellation helper as the shared camera queue. Existing callers work without changes. The unchanged iOS implementation passes the held-finger drift scenario on the current SDK.

Validation

Validated on 2026-10-14 with Mapbox Maps SDK 11.23.1, React Native 0.84.1, and the New Architecture:

  • Android map code and app build pass; all 18 native unit tests pass.
  • Repository lint passes with no errors; all 25 JavaScript suites and 164 tests pass.
  • Generation completes, including the example index and the generated MapView example link.
  • The committed Android integration runner passes flyTo, eased and immediate setCamera moves, moveBy, scaleBy, panning on the next touch, and long presses without camera commands.
  • A control build with only super.dispatchTouchEvent(cancel) omitted fails the integration test: gestureActive remains true through the animated camera move. That build still consumes the remaining touches.
  • The permanent iOS Xcode UI suite passes all five cases: flyTo, eased and immediate setCamera, moveBy, and scaleBy. Each case checks held-finger drift, camera readings after release, and panning on the next touch. Relative cases also compare against a stationary run; scaleBy must reach zoom 17 from zoom 16.
  • Temporary no-effect iOS controls fail both new cases: zero displacement fails moveBy, and a scale factor of one fails scaleBy.

The Android regression holds a touch for four seconds while moving 24 physical pixels. It samples the camera before release and compares it again after release. Relative commands must retain their completed position; scaleBy must reach zoom 17 from zoom 16.

Relative camera case Original PR build Fixed build
moveBy Gesture remains active; continued movement changes longitude by 0.0000344 degrees after the held sample Gesture ends; camera center remains stable
scaleBy Gesture remains active; zoom stays at 16 instead of reaching 17 Gesture ends; zoom reaches 17 and the center remains stable

The iOS test holds for 1.5 seconds, drifts eight screen points at eight points per second, then holds for another three seconds. Both held and released positions must be within 0.000001 degrees per coordinate component of the expected destination. The relative cases compare against a stationary run of the same command; scaleBy must reach zoom 17. All five cases also verify panning on the next touch.

Simulator runs opened the committed example through a temporary entry and isolated dependency/Babel configuration. Android packaging reused unchanged Reanimated and Worklets native binaries from the baseline app while rebuilding the map code. The iOS 27 run reused the temporary scene bootstrap from the original validation. These temporary setup changes are absent from the PR.

Physical devices and repository-wide TypeScript checks were not run.

Checklist

  • I've read CONTRIBUTING.md.
  • I generated documentation and code using the root scripts/autogenerate.mjs entry point behind yarn generate.
  • I tested the example app with Mapbox v11 and the New Architecture on Android.
  • I tested all five camera modes on the unchanged iOS v11/New Architecture baseline and added the permanent LongPressCamera Xcode UI test scheme.
  • I added the Camera > Long Press Camera example and a native touch integration regression.

Evidence

The original queue-fix screenshots remain available: before and after.

Component to reproduce the issue

Build and run the example app, then open Camera > Long Press Camera. Select a camera mode, long press away from the center, and keep moving the held finger slightly. The camera command should finish and retain its position. Lift and pan again to verify that the next touch works. Select none to verify normal long-press panning without a camera command.

The self-contained component is committed at example/src/examples/Camera/LongPress.js. Run the Android regression separately against a running example app, with adb on PATH:

python3 example/e2e/longPressCamera.android.py emulator-5556

The serial argument is optional when only one Android device is connected. The runner uses Python's standard library and Android's native input and UI automation tools, with no added dependencies.

Run the permanent iOS suite against an installed example app using the instructions in CONTRIBUTING.md. The LongPressCamera scheme builds the native UI test runner and uses real simulator touch events. It runs separately from yarn test and Detox screenshots.

- Cancel native long-press gestures before queued camera updates
- Consume remaining touch events until release and restore panning on new touches
- Add regression tests for gesture ownership and camera-update ordering
- Cancel active map long presses before moveBy and scaleBy
- Add a native touch regression covering camera motion and gesture cleanup
- Add the long-press camera example and regenerate its documentation links
- Add a permanent native UI suite for all five long-press camera modes
- Verify relative camera destinations, zoom, release stability, and the next pan
- Add the LongPressCamera Xcode scheme and contributor run instructions
@RobJacobson
RobJacobson marked this pull request as ready for review October 15, 2026 03:32

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant