Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8bd5684
[GPS] Update user location on GPS screen when there is no active GPS …
GCyganek May 22, 2026
7ac11d9
Merge branch 'main' into @GCyganek/gps/user-location-updates-gps-screen
GCyganek May 25, 2026
9448813
Do not follow current user location when there is a recorded trip
GCyganek May 25, 2026
555483a
Do not follow user location on GPS map when there is a recorded trip
GCyganek May 25, 2026
e1cc363
Check directionCoordinates length
GCyganek May 25, 2026
eee9fe8
Fix isSingleSegmentRoute when [[]]
GCyganek May 25, 2026
4a09a9f
Move cancelled check to .then
GCyganek May 27, 2026
ee529e0
Merge branch 'main' into @GCyganek/gps/user-location-updates-gps-screen
GCyganek Jun 1, 2026
0d76dd9
Re-run tests
GCyganek Jun 1, 2026
91c6fd0
Merge branch 'main' into @GCyganek/gps/user-location-updates-gps-screen
GCyganek Jun 10, 2026
3dc8e68
Drop shouldFollowCurrentLocationWhenRoutePresent
GCyganek Jun 10, 2026
b2d5cdf
Use Mapbox's own location engine, animate route
GCyganek Jun 12, 2026
18a4705
Fix knip
GCyganek Jun 12, 2026
bf2bf06
Fix long fly-in animation from space
GCyganek Jun 15, 2026
ed66b6e
Code clean-up
GCyganek Jun 15, 2026
f10c77c
fix ts error
GCyganek Jun 15, 2026
557cfd7
Fix animation
GCyganek Jun 15, 2026
75de587
Fix TS error
GCyganek Jun 15, 2026
91a860d
Revert changes in MapView.tsx
GCyganek Jun 15, 2026
e1d80b8
Merge branch 'main' into @GCyganek/gps/user-location-updates-gps-screen
GCyganek Jun 16, 2026
18cce67
Code clean-up
GCyganek Jun 16, 2026
bacc2c4
Seatbelt update
GCyganek Jun 16, 2026
5c9ae29
Refactor code
GCyganek Jun 16, 2026
ec5c202
Handle foreground location permissions
GCyganek Jun 16, 2026
d46a1db
PendingMapView
GCyganek Jun 16, 2026
dc31cfd
Fix AI linter suggestions
GCyganek Jun 16, 2026
f8bdf43
Do not request locatio permissions while offline
GCyganek Jun 16, 2026
8526c5c
Merge branch 'main' into @GCyganek/gps/user-location-updates-gps-screen
GCyganek Jun 17, 2026
84fd6a6
Apply review feedback
GCyganek Jun 18, 2026
5342a09
Fix GPSMapView layers
GCyganek Jun 18, 2026
5a75416
Add empty line above comment
GCyganek Jun 22, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
diff --git a/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCamera.kt b/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCamera.kt
index 2bd4079..91ad32e 100644
--- a/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCamera.kt
+++ b/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCamera.kt
@@ -62,6 +62,7 @@ class RNMBXCamera(private val mContext: Context, private val mManager: RNMBXCame
private val mHeading = 0.0

private var mFollowUserLocation = defaultFollowUserLocation
+ private var mFollowUserLocationUseImmediateTransition = false
private var mFollowUserMode: String? = null
private var mFollowZoomLevel : Double? = null
private var mFollowPitch : Double? = null
@@ -143,6 +144,11 @@ class RNMBXCamera(private val mContext: Context, private val mManager: RNMBXCame
_updateViewportState()
}

+ fun setFollowUserLocationUseImmediateTransition(value: Boolean?) {
+ mFollowUserLocationUseImmediateTransition = value ?: false
+ _updateViewportState()
+ }
+
fun setFollowZoomLevel(zoomLevel: Double?) {
mFollowZoomLevel = zoomLevel
_updateViewportState();
@@ -517,7 +523,11 @@ class RNMBXCamera(private val mContext: Context, private val mManager: RNMBXCame


val followState = viewport.makeFollowPuckViewportState(followOptions.build())
- viewport.transitionTo(followState)
+ if (mFollowUserLocationUseImmediateTransition) {
+ viewport.transitionTo(followState, viewport.makeImmediateViewportTransition())
+ } else {
+ viewport.transitionTo(followState)
+ }
}
mapboxMap?.let {
it.getStyle()?.let {
diff --git a/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCameraManager.kt b/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCameraManager.kt
index ac2fd10..c625050 100644
--- a/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCameraManager.kt
+++ b/node_modules/@rnmapbox/maps/android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCameraManager.kt
@@ -84,6 +84,11 @@ class RNMBXCameraManager(private val mContext: ReactApplicationContext, val view
camera.setFollowUserLocation(value.asBooleanOrNull())
}

+ @ReactProp(name = "followUserLocationUseImmediateTransition")
+ override fun setFollowUserLocationUseImmediateTransition(camera: RNMBXCamera, value: Dynamic) {
+ camera.setFollowUserLocationUseImmediateTransition(value.asBooleanOrNull())
+ }
+
@ReactProp(name = "followUserMode")
override fun setFollowUserMode(camera: RNMBXCamera, value: Dynamic) {
camera.setFollowUserMode(value.asStringOrNull())
diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift
index 69c7e27..ca13fbd 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift
@@ -142,6 +142,8 @@ open class RNMBXCamera : RNMBXMapComponentBase {
}
}

+ @objc public var followUserLocationUseImmediateTransition: Bool = false
Comment thread
GCyganek marked this conversation as resolved.
+
@objc public var followUserMode: String? {
didSet {
_updateCameraFromTrackingMode()
@@ -365,7 +367,11 @@ open class RNMBXCamera : RNMBXMapComponentBase {

let followState = map.viewport.makeFollowPuckViewportState(options: followOptions)

- map.viewport.transition(to: followState)
+ if self.followUserLocationUseImmediateTransition {
+ map.viewport.transition(to: followState, transition: map.viewport.makeImmediateViewportTransition())
+ } else {
+ map.viewport.transition(to: followState)
+ }
map.viewport.addStatusObserver(self)
map.mapboxMap.setCamera(to: _camera)
}
diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraComponentView.mm b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraComponentView.mm
index abc8b4a..93d9940 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraComponentView.mm
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraComponentView.mm
@@ -97,6 +97,10 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
if (followUserLocationDynamic != nil) {
_view.followUserLocation = [followUserLocationDynamic boolValue];
}
+ id followUserLocationUseImmediateTransitionDynamic = RNMBXConvertFollyDynamicToId(newViewProps.followUserLocationUseImmediateTransition);
+ if (followUserLocationUseImmediateTransitionDynamic != nil) {
+ _view.followUserLocationUseImmediateTransition = [followUserLocationUseImmediateTransitionDynamic boolValue];
+ }
id followUserMode = RNMBXConvertFollyDynamicToId(newViewProps.followUserMode);
if (followUserMode != nil) {
_view.followUserMode = followUserMode;
diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraViewManager.m b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraViewManager.m
index 7002793..473ebc3 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraViewManager.m
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCameraViewManager.m
@@ -12,6 +12,7 @@ @interface RCT_EXTERN_REMAP_MODULE(RNMBXCamera, RNMBXCameraViewManager, RCTViewM
RCT_EXPORT_VIEW_PROPERTY(defaultStop, NSDictionary)

RCT_EXPORT_VIEW_PROPERTY(followUserLocation, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(followUserLocationUseImmediateTransition, BOOL)
RCT_EXPORT_VIEW_PROPERTY(followUserMode, NSString)
RCT_EXPORT_VIEW_PROPERTY(followZoomLevel, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(followPitch, NSNumber)
diff --git a/node_modules/@rnmapbox/maps/lib/typescript/src/components/Camera.d.ts b/node_modules/@rnmapbox/maps/lib/typescript/src/components/Camera.d.ts
index 9355007..2493343 100644
--- a/node_modules/@rnmapbox/maps/lib/typescript/src/components/Camera.d.ts
+++ b/node_modules/@rnmapbox/maps/lib/typescript/src/components/Camera.d.ts
@@ -61,6 +61,8 @@ export type CameraStop = {
export type CameraFollowConfig = {
/** Whether the map orientation follows the user location. */
followUserLocation?: boolean;
+ /** Whether to snap immediately when entering follow mode instead of using the default viewport transition. */
+ followUserLocationUseImmediateTransition?: boolean;
/** The mode used to track the user location on the map. */
followUserMode?: UserTrackingMode;
/** The zoom level used when following the user location. */
diff --git a/node_modules/@rnmapbox/maps/src/components/Camera.tsx b/node_modules/@rnmapbox/maps/src/components/Camera.tsx
index ee6dbc2..f991849 100644
--- a/node_modules/@rnmapbox/maps/src/components/Camera.tsx
+++ b/node_modules/@rnmapbox/maps/src/components/Camera.tsx
@@ -158,6 +158,8 @@ export type CameraStop = {
export type CameraFollowConfig = {
/** Whether the map orientation follows the user location. */
followUserLocation?: boolean;
+ /** Whether to snap immediately when entering follow mode instead of using the default viewport transition. */
+ followUserLocationUseImmediateTransition?: boolean;
/** The mode used to track the user location on the map. */
followUserMode?: UserTrackingMode;
/** The zoom level used when following the user location. */
@@ -270,6 +272,7 @@ export const Camera = memo(
maxZoomLevel,
maxBounds,
followUserLocation,
+ followUserLocationUseImmediateTransition,
followUserMode,
followZoomLevel,
followPitch,
@@ -674,6 +677,7 @@ export const Camera = memo(
animationMode={animationMode}
defaultStop={nativeDefaultStop}
followUserLocation={followUserLocation}
+ followUserLocationUseImmediateTransition={followUserLocationUseImmediateTransition}
followUserMode={followUserMode}
followZoomLevel={followZoomLevel}
followPitch={followPitch}
diff --git a/node_modules/@rnmapbox/maps/src/specs/RNMBXCameraNativeComponent.ts b/node_modules/@rnmapbox/maps/src/specs/RNMBXCameraNativeComponent.ts
index 43a17e8..10849c1 100644
--- a/node_modules/@rnmapbox/maps/src/specs/RNMBXCameraNativeComponent.ts
+++ b/node_modules/@rnmapbox/maps/src/specs/RNMBXCameraNativeComponent.ts
@@ -27,6 +27,7 @@ export interface NativeProps extends ViewProps {
userTrackingMode?: OptionalProp<Int32>;

followUserLocation?: OptionalProp<boolean>;
+ followUserLocationUseImmediateTransition?: OptionalProp<boolean>;
followUserMode?: OptionalProp<string>;
followZoomLevel?: OptionalProp<Double>;
followPitch?: OptionalProp<Double>;
14 changes: 14 additions & 0 deletions patches/@rnmapbox/maps/details.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# `@rnmapbox/maps` patches

### [@rnmapbox+maps+10.1.44+001+immediate-follow-camera-transition.patch](@rnmapbox+maps+10.1.44+001+immediate-follow-camera-transition.patch)

- Reason: Removes the long "fly-in from space" animation that plays the first time a `Camera` with `followUserLocation={true}` engages follow mode (used by `GPSMapView` on the GPS distance screen). When entering follow mode, `@rnmapbox/maps` calls the Mapbox viewport transition with no explicit transition argument, so the SDK uses its `DefaultViewportTransition`, which animates from the current camera (world view / zoom ~0) all the way in to the user. Because the JS layer discards `defaultStop`/`stop` while `followUserLocation` is true (`src/components/Camera.tsx`), there is no JS-only way to seed the start position or pick an animation mode — the only lever is the native transition itself.
- Solution: Adds an opt-in `followUserLocationUseImmediateTransition` prop to `Camera`. When `true`, entering follow mode uses `makeImmediateViewportTransition()` so the camera snaps straight to the follow state. When `false` (default), upstream behavior is preserved and the default viewport transition animates. `GPSMapView` passes the prop only on initial map load when follow is active with no route; later re-engagements (center button, follow after clearing a route) pass `false` so the camera animates back to the user.
- Files changed:
- `src/specs/RNMBXCameraNativeComponent.ts`, `src/components/Camera.tsx`, and `lib/typescript/src/components/Camera.d.ts` — new prop
- iOS — `ios/RNMBX/RNMBXCamera.swift`, `ios/RNMBX/RNMBXCameraComponentView.mm`, `ios/RNMBX/RNMBXCameraViewManager.m`
- Android — `android/src/main/java/com/rnmapbox/rnmbx/components/camera/RNMBXCamera.kt` and `RNMBXCameraManager.kt`

- Upstream PR/issue: 🛑

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB: Why no upstream PR or issue?

- E/App issue: https://github.com/Expensify/App/pull/90169#issuecomment-4476930634
- PR introducing patch: https://github.com/Expensify/App/pull/91418
5 changes: 5 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6024,6 +6024,7 @@ const CONST = {
DEFAULT_COORDINATE: [-122.4021, 37.7911] as [number, number],
STYLE_URL: 'mapbox://styles/expensify/cllcoiqds00cs01r80kp34tmq',
ANIMATION_DURATION_ON_CENTER_ME: 1000,
GPS_ROUTE_ANIMATION_DURATION_MS: 1000,
CENTER_BUTTON_FADE_DURATION: 300,
},
ONYX_UPDATE_TYPES: {
Expand Down Expand Up @@ -8843,8 +8844,12 @@ const CONST = {
ROUTE_SOURCE: 'route-source',
ROUTE_FILL: 'route-fill',
ROUTE_BORDER: 'route-border',
WAYPOINTS_SOURCE: 'waypoints-source',
WAYPOINTS: 'waypoints',
},

MAP_CURRENT_LOCATION_FILL_COLOR: '#0185FF',

PARTNER_ID: {
EXPENSIFY: 1,
IPHONE: 14,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/chunks/expensify-icons.chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ import Zoom from '@assets/images/zoom.svg';
// These icons are React components because they have
// drop shadow that is NOT handled by babel-plugin-transform-react-native-svg
import MapCurrentLocation from '@components/MapView/Icons/MapCurrentLocation';
import MapCurrentLocationPuck from '@components/MapView/Icons/MapCurrentLocationPuck';
import MapStartWaypoint from '@components/MapView/Icons/MapStartWaypoint';
import MapStopWaypoint from '@components/MapView/Icons/MapStopWaypoint';
import MapWaypoint from '@components/MapView/Icons/MapWaypoint';
Expand Down Expand Up @@ -409,6 +410,7 @@ const Expensicons = {
MakeAdmin,
Map,
MapCurrentLocation,
MapCurrentLocationPuck,
MapStartWaypoint,
MapStopWaypoint,
MapWaypoint,
Expand Down
38 changes: 38 additions & 0 deletions src/components/MapView/GPSDirection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Direction from './Direction';
import type {Coordinate, GPSDirectionProps} from './MapViewTypes';
import useAnimatedTrailingDirectionCoordinate from './useAnimatedTrailingDirectionCoordinate';

function GPSDirection({directionCoordinates, isTrackingGPS, lastLocation, belowLayerID}: GPSDirectionProps) {
const animatedTrailingCoordinate = useAnimatedTrailingDirectionCoordinate({
isEnabled: isTrackingGPS && !!lastLocation && directionCoordinates.flat().length > 0,
targetCoordinate: lastLocation ? [lastLocation.longitude, lastLocation.latitude] : undefined,
directionCoordinates,
});

const getCoordinates = () => {
if (!isTrackingGPS || !lastLocation || !directionCoordinates || directionCoordinates.length === 0) {
return directionCoordinates;
}

const lastSegment = directionCoordinates.at(-1);
if (!lastSegment?.length) {
return directionCoordinates;
}

const lastLocationCoordinate: Coordinate = animatedTrailingCoordinate ?? [lastLocation.longitude, lastLocation.latitude];

const newLastSegment = [...lastSegment.slice(0, lastSegment.length === 1 ? undefined : -1), lastLocationCoordinate];

const newDirectionCoordinates = [...directionCoordinates.slice(0, directionCoordinates.length - 1), newLastSegment];
return newDirectionCoordinates;
};

return (
<Direction
coordinates={getCoordinates()}
belowLayerID={belowLayerID}
/>
);
}

export default GPSDirection;
Loading
Loading