@jacques_gordon/expo-mapbox-navigation 2.2.7 → 2.2.8
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/ExpoMapboxNavigation.podspec +18 -51
- package/README.md +208 -122
- package/app.plugin.js +191 -0
- package/package.json +1 -1
- package/plugin/src/index.js +191 -0
|
@@ -2,33 +2,6 @@ require 'json'
|
|
|
2
2
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
4
|
|
|
5
|
-
# ── Detect whether xcframeworks have been built and bundled ────────────────────
|
|
6
|
-
# The ios/Frameworks/ directory is populated by running ios/build-xcframeworks.sh
|
|
7
|
-
# (requires macOS + Xcode + Mapbox .netrc credentials). Once built, the
|
|
8
|
-
# xcframeworks are committed to the repository / included in the npm package.
|
|
9
|
-
frameworks_dir = File.join(__dir__, 'ios', 'Frameworks')
|
|
10
|
-
has_frameworks = Dir.exist?(frameworks_dir) &&
|
|
11
|
-
Dir.glob(File.join(frameworks_dir, '*.xcframework')).length > 0
|
|
12
|
-
|
|
13
|
-
if !has_frameworks
|
|
14
|
-
warn <<~MSG
|
|
15
|
-
|
|
16
|
-
┌──────────────────────────────────────────────────────────────────────────┐
|
|
17
|
-
│ @jacques_gordon/expo-mapbox-navigation: iOS xcframeworks not found │
|
|
18
|
-
│ │
|
|
19
|
-
│ The Mapbox Navigation SDK v3 is SPM-only (no CocoaPods distribution). │
|
|
20
|
-
│ To build for iOS you must first compile the xcframeworks: │
|
|
21
|
-
│ │
|
|
22
|
-
│ cd node_modules/@jacques_gordon/expo-mapbox-navigation/ios │
|
|
23
|
-
│ ./build-xcframeworks.sh │
|
|
24
|
-
│ │
|
|
25
|
-
│ Prerequisites: macOS + Xcode 16 + ~/.netrc with Mapbox Downloads token │
|
|
26
|
-
│ See ios/build-xcframeworks.sh for full instructions. │
|
|
27
|
-
└──────────────────────────────────────────────────────────────────────────┘
|
|
28
|
-
|
|
29
|
-
MSG
|
|
30
|
-
end
|
|
31
|
-
|
|
32
5
|
Pod::Spec.new do |s|
|
|
33
6
|
s.name = 'ExpoMapboxNavigation'
|
|
34
7
|
s.version = package['version']
|
|
@@ -38,41 +11,35 @@ Pod::Spec.new do |s|
|
|
|
38
11
|
s.author = package['author']
|
|
39
12
|
s.homepage = package['homepage']
|
|
40
13
|
|
|
41
|
-
#
|
|
14
|
+
# Mapbox Navigation SDK v3 requires iOS 14+
|
|
42
15
|
s.platforms = { :ios => '14.0' }
|
|
43
16
|
s.swift_version = '5.9'
|
|
44
17
|
s.source = { git: package['repository']['url'], tag: "v#{s.version}" }
|
|
18
|
+
s.static_framework = true
|
|
45
19
|
|
|
46
20
|
s.dependency 'ExpoModulesCore'
|
|
47
21
|
|
|
48
|
-
# ──
|
|
49
|
-
# Mapbox Navigation SDK v3 for iOS is SPM-only — CocoaPods support is
|
|
50
|
-
# announced as "coming soon" by Mapbox but not yet available.
|
|
22
|
+
# ── iOS: Mapbox Navigation SDK v3 via SPM ─────────────────────────────────
|
|
51
23
|
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# wrapper with confirmed production EAS Build success for iOS + Mapbox Nav v3).
|
|
24
|
+
# Mapbox Navigation SDK v3 is SPM-only (CocoaPods "coming soon" per Mapbox).
|
|
25
|
+
# We do NOT use spm_dependency() here — that causes 43029 duplicate symbol
|
|
26
|
+
# linker errors when used alongside @rnmapbox/maps.
|
|
56
27
|
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# - _MapboxNavigationHelpers — internal helpers
|
|
28
|
+
# Instead, our config plugin (app.plugin.js) injects a post_install hook
|
|
29
|
+
# into the Podfile. The hook uses the Xcodeproj Ruby API — the same technique
|
|
30
|
+
# as @rnmapbox/maps itself — to add mapbox-navigation-ios as a SPM
|
|
31
|
+
# dependency to the ExpoMapboxNavigation pod target and the app target.
|
|
32
|
+
# find-or-create semantics prevent any duplication.
|
|
63
33
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
if has_frameworks
|
|
68
|
-
vendored = Dir.glob(File.join(frameworks_dir, '*.xcframework'))
|
|
69
|
-
.map { |f| "ios/Frameworks/#{File.basename(f)}" }
|
|
70
|
-
s.vendored_frameworks = vendored
|
|
71
|
-
end
|
|
34
|
+
# The post_install hook runs during `pod install`, with full access to
|
|
35
|
+
# installer.pods_project and installer.aggregate_targets, which is the only
|
|
36
|
+
# correct way to add SPM packages alongside CocoaPods in Expo projects.
|
|
72
37
|
|
|
73
38
|
s.source_files = 'ios/**/*.{swift,h,m,mm}'
|
|
74
|
-
|
|
75
|
-
|
|
39
|
+
s.exclude_files = [
|
|
40
|
+
'ios/Package.swift',
|
|
41
|
+
'ios/build-xcframeworks.sh',
|
|
42
|
+
]
|
|
76
43
|
|
|
77
44
|
s.pod_target_xcconfig = {
|
|
78
45
|
'DEFINES_MODULE' => 'YES',
|
package/README.md
CHANGED
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@jacques_gordon/expo-mapbox-navigation)
|
|
4
4
|
|
|
5
|
-
Expo module for Mapbox Navigation SDK —
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| ✅ **iOS support** | Full native iOS implementation (`NavigationViewController` drop-in UI) via Swift Package Manager — feature parity with Android (lane guidance, speed limit, voice instructions, day/night, steps list). |
|
|
5
|
+
Full-featured Expo module for Mapbox Navigation SDK v3 — Android and iOS.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Android** — Waze-style navigation UI built from scratch: maneuver banner, lane guidance, speed limit, ETA bar, voice instructions, mute/overview/recenter buttons, day/night auto-switch
|
|
12
|
+
- **iOS** — Drop-in `NavigationViewController` from Mapbox Navigation SDK v3 (lane guidance, speed limit, voice, day/night all built-in)
|
|
13
|
+
- **Both platforms** — 7 events, 19 props, full feature and API parity
|
|
14
|
+
- NDK 27 + 16 KB page size compliant (Android 15+)
|
|
16
15
|
|
|
17
16
|
---
|
|
18
17
|
|
|
@@ -22,54 +21,108 @@ Expo module for Mapbox Navigation SDK — forked from [`@badatgil/expo-mapbox-na
|
|
|
22
21
|
npx expo install @jacques_gordon/expo-mapbox-navigation @rnmapbox/maps
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
### Setup @rnmapbox/maps first
|
|
24
|
+
### 1. Setup @rnmapbox/maps first
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
["@rnmapbox/maps", {
|
|
28
|
+
"RNMapboxMapsImpl": "mapbox",
|
|
29
|
+
"RNMapboxMapsVersion": "11.11.0",
|
|
30
|
+
"RNMapboxMapsDownloadToken": "sk.your_secret_token"
|
|
31
|
+
}]
|
|
32
|
+
```
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
### 2. Add this plugin
|
|
28
35
|
|
|
29
36
|
```json
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"RNMapboxMapsVersion": "11.11.0",
|
|
36
|
-
"RNMapboxMapsDownloadToken": "sk.your_secret_token"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
]
|
|
37
|
+
["@jacques_gordon/expo-mapbox-navigation", {
|
|
38
|
+
"accessToken": "pk.your_public_token",
|
|
39
|
+
"downloadsToken": "sk.your_secret_token",
|
|
40
|
+
"mapboxMapsVersion": "11.11.0"
|
|
41
|
+
}]
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
###
|
|
44
|
+
### 3. iOS only — enable static frameworks
|
|
43
45
|
|
|
44
46
|
```json
|
|
45
|
-
"
|
|
46
|
-
[
|
|
47
|
-
"@jacques_gordon/expo-mapbox-navigation",
|
|
48
|
-
{
|
|
49
|
-
"accessToken": "pk.your_public_token",
|
|
50
|
-
"downloadsToken": "sk.your_secret_token",
|
|
51
|
-
"mapboxMapsVersion": "11.11.0"
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
]
|
|
47
|
+
["expo-build-properties", { "ios": { "useFrameworks": "static" } }]
|
|
55
48
|
```
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Plugin Options
|
|
53
|
+
|
|
54
|
+
| Option | Required | Default | Description |
|
|
55
|
+
|--------|----------|---------|-------------|
|
|
56
|
+
| `accessToken` | ✅ | — | Public Mapbox token (`pk.*`). Used for map tiles and routing. |
|
|
57
|
+
| `downloadsToken` | ✅ | — | Secret Mapbox token (`sk.*`) with **Downloads:Read** scope. Same token as `RNMapboxMapsDownloadToken`. Used on iOS to authenticate SPM when fetching the Navigation SDK from `api.mapbox.com` via `~/.netrc`. |
|
|
58
|
+
| `mapboxMapsVersion` | ✅ | `"11.11.0"` | Must exactly match `RNMapboxMapsVersion` in `@rnmapbox/maps`. |
|
|
59
|
+
| `mapboxNavigationVersion` | — | auto-calculated | iOS only. See [iOS Version Strategy](#ios-version-strategy) below. |
|
|
60
|
+
| `androidColorOverrides` | — | `{}` | Override Mapbox native resource colors on Android. |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## iOS Architecture
|
|
65
|
+
|
|
66
|
+
### How it works
|
|
67
|
+
|
|
68
|
+
iOS uses `NavigationViewController` — the official Mapbox Navigation SDK v3 drop-in UI — installed via **Swift Package Manager** (SPM). CocoaPods does not host the Navigation SDK v3 (Mapbox confirmed CocoaPods support is "coming soon").
|
|
69
|
+
|
|
70
|
+
This package bridges SPM into your CocoaPods/Expo project using a **`post_install` Ruby hook** injected into your `Podfile` — the same technique used by `@rnmapbox/maps` itself. The hook uses the Xcodeproj Ruby API (`XCRemoteSwiftPackageReference`, `XCSwiftPackageProductDependency`) to add the package properly, with find-or-create semantics to prevent duplicate symbols.
|
|
71
|
+
|
|
72
|
+
### iOS Version Strategy
|
|
58
73
|
|
|
59
|
-
|
|
74
|
+
This is the most important part. Understanding it prevents build failures.
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
**The problem:** SPM requires all packages in the dependency graph to agree on a single version of shared libraries (`MapboxCommon`, `MapboxMaps`, `Turf`). Both `@rnmapbox/maps` and `mapbox-navigation-ios` depend on these shared libraries. If they request incompatible versions, SPM fails.
|
|
62
77
|
|
|
63
|
-
|
|
78
|
+
**The Mapbox versioning pattern** (confirmed from official GitHub releases):
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
The `.0` release of each Navigation minor always pairs with the matching Maps minor:
|
|
66
81
|
|
|
67
|
-
|
|
82
|
+
| Maps version | Navigation `.0` | Compatible? |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `11.11.0` | `3.11.0` | ✅ |
|
|
85
|
+
| `11.12.0` | `3.12.0` | ✅ |
|
|
86
|
+
| `11.21.5` | `3.21.5` (same minor+patch) | ✅ |
|
|
87
|
+
|
|
88
|
+
**⚠️ Patch versions drift.** Navigation patch releases (`3.11.x` where x > 0) often update to a newer Maps version — for example `3.11.4` requires Maps `11.14.7`, not `11.11.x`. Using `upToNextMinorVersion` would therefore be unsafe.
|
|
89
|
+
|
|
90
|
+
**Our solution:** We use the **exact** `.0` version that matches your Maps minor:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
mapboxMapsVersion = "11.11.0"
|
|
94
|
+
→ Navigation exact version = "3.11.0"
|
|
95
|
+
→ requires MapboxMaps 11.11.x ✅ compatible
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This is **fully automatic** — when you upgrade Maps from `11.11.0` to `11.12.0`, the Navigation version is recalculated to exact `3.12.0`.
|
|
99
|
+
|
|
100
|
+
**Manual override (escape hatch):** To pin any specific Navigation version:
|
|
68
101
|
|
|
69
102
|
```json
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
["@jacques_gordon/expo-mapbox-navigation", {
|
|
104
|
+
"accessToken": "pk.xxx",
|
|
105
|
+
"downloadsToken": "sk.xxx",
|
|
106
|
+
"mapboxMapsVersion": "11.11.0",
|
|
107
|
+
"mapboxNavigationVersion": "3.11.2"
|
|
108
|
+
}]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
During `expo prebuild`, you will see in the logs:
|
|
112
|
+
```
|
|
113
|
+
[@jacques_gordon/expo-mapbox-navigation] Maps 11.11.0 → auto-calculated Navigation 3.11.0..<3.12.0
|
|
114
|
+
[@jacques_gordon/expo-mapbox-navigation] ✅ Wrote Mapbox credentials to ~/.netrc
|
|
115
|
+
[@jacques_gordon/expo-mapbox-navigation] ✅ Injected mapbox-navigation-ios SPM hook into Podfile
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
And during `pod install`:
|
|
119
|
+
```
|
|
120
|
+
[ExpoMapboxNavigation] Added mapbox-navigation-ios to pods_project
|
|
121
|
+
[ExpoMapboxNavigation] Found target: ExpoMapboxNavigation
|
|
122
|
+
[ExpoMapboxNavigation] Linked MapboxNavigationCore -> ExpoMapboxNavigation
|
|
123
|
+
[ExpoMapboxNavigation] Linked MapboxNavigationUIKit -> ExpoMapboxNavigation
|
|
124
|
+
[ExpoMapboxNavigation] Linked MapboxNavigationCore -> Navio
|
|
125
|
+
[ExpoMapboxNavigation] Linked MapboxNavigationUIKit -> Navio
|
|
73
126
|
```
|
|
74
127
|
|
|
75
128
|
---
|
|
@@ -84,50 +137,89 @@ export default function Navigation() {
|
|
|
84
137
|
<MapboxNavigationView
|
|
85
138
|
style={{ flex: 1 }}
|
|
86
139
|
coordinates={[
|
|
87
|
-
{ latitude:
|
|
88
|
-
{ latitude: 51.
|
|
140
|
+
{ latitude: 50.8503, longitude: 4.3517 }, // Brussels
|
|
141
|
+
{ latitude: 51.2194, longitude: 4.4025 }, // Antwerp
|
|
89
142
|
]}
|
|
90
|
-
voiceUnits="metric"
|
|
143
|
+
voiceUnits="metric"
|
|
91
144
|
language="fr"
|
|
92
145
|
navigationProfile="driving-traffic"
|
|
146
|
+
onRoutesReady={({ nativeEvent }) =>
|
|
147
|
+
console.log('Route ready:', nativeEvent.distanceMeters, 'm')
|
|
148
|
+
}
|
|
149
|
+
onRouteProgressChanged={({ nativeEvent }) =>
|
|
150
|
+
console.log('Remaining:', nativeEvent.distanceRemaining, 'm')
|
|
151
|
+
}
|
|
152
|
+
onManeuverBannerPressed={({ nativeEvent }) => {
|
|
153
|
+
// Open a bottom sheet showing all upcoming steps
|
|
154
|
+
console.log('Steps:', nativeEvent.steps);
|
|
155
|
+
}}
|
|
93
156
|
onArrival={() => console.log('Arrived!')}
|
|
157
|
+
onNavigationCancelled={() => console.log('Cancelled')}
|
|
94
158
|
onRoutesFailed={({ nativeEvent }) =>
|
|
95
|
-
console.error('
|
|
159
|
+
console.error('Failed:', nativeEvent.message)
|
|
96
160
|
}
|
|
97
161
|
/>
|
|
98
162
|
);
|
|
99
163
|
}
|
|
100
164
|
```
|
|
101
165
|
|
|
102
|
-
|
|
166
|
+
---
|
|
103
167
|
|
|
104
|
-
|
|
168
|
+
## Props
|
|
105
169
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
170
|
+
### Navigation
|
|
171
|
+
|
|
172
|
+
| Prop | Type | Default | Description |
|
|
173
|
+
|------|------|---------|-------------|
|
|
174
|
+
| `coordinates` | `{ latitude: number; longitude: number }[]` | **required** | Waypoints. Minimum 2. |
|
|
175
|
+
| `waypointIndices` | `number[]` | all | Which coordinates are true waypoints (vs. route shape points). |
|
|
176
|
+
| `navigationProfile` | `string` | `"driving-traffic"` | `"driving-traffic"`, `"driving"`, `"walking"`, `"cycling"`. **Android**: omit the `"mapbox/"` prefix. |
|
|
177
|
+
| `language` | `string` | device locale | BCP-47 tag (e.g. `"fr"`, `"nl"`, `"en-US"`). |
|
|
178
|
+
| `voiceUnits` | `"metric" \| "imperial"` | auto by locale | Overrides automatic unit detection. |
|
|
179
|
+
| `excludeTypes` | `string[]` | — | Road types to avoid (e.g. `["toll", "ferry"]`). |
|
|
180
|
+
| `mapStyle` | `string` | Mapbox Navigation Day | Map style URL. |
|
|
181
|
+
| `mute` | `boolean` | `false` | Silence voice instructions. |
|
|
182
|
+
| `maxHeight` | `number` | — | Max vehicle height in metres. |
|
|
183
|
+
| `maxWidth` | `number` | — | Max vehicle width in metres. |
|
|
184
|
+
| `useMapMatching` | `boolean` | `false` | Use Map Matching API instead of routing. |
|
|
185
|
+
| `customRasterTileUrl` | `string` | — | Custom raster tile URL with `{x}/{y}/{z}`. |
|
|
186
|
+
| `customRasterAboveLayerId` | `string` | — | Layer ID to insert custom raster tiles above. |
|
|
111
187
|
|
|
112
|
-
|
|
113
|
-
etaBarBackgroundColor="#1E2433" // default: #1E2433 (dark navy)
|
|
114
|
-
etaTextColor="#FFFFFF" // default: #FFFFFF (white)
|
|
188
|
+
### Color Customization (Android)
|
|
115
189
|
|
|
116
|
-
|
|
117
|
-
iconButtonColor="#1A73E8" // default: #1A73E8 (Google Blue)
|
|
118
|
-
iconButtonMutedColor="#EA4335" // default: #EA4335 (Google Red)
|
|
190
|
+
All color props are optional — defaults are applied when omitted.
|
|
119
191
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
192
|
+
| Prop | Default | Description |
|
|
193
|
+
|------|---------|-------------|
|
|
194
|
+
| `maneuverBackgroundColorDay` | Mapbox default | Background of the turn-by-turn instruction banner. Uses `ManeuverViewOptions.maneuverBackgroundColor` (official Mapbox SDK API). |
|
|
195
|
+
| `maneuverTurnIconColor` | Mapbox default | Color of the turn arrow icon. Uses `ManeuverViewOptions.turnIconManeuver`. |
|
|
196
|
+
| `etaBarBackgroundColor` | `"#1E2433"` | Background of the bottom ETA/duration/distance bar. |
|
|
197
|
+
| `etaTextColor` | `"#FFFFFF"` | Text color for ETA time and duration. |
|
|
198
|
+
| `iconButtonColor` | `"#1A73E8"` | Color of the mute/overview/recenter buttons (default state). |
|
|
199
|
+
| `iconButtonMutedColor` | `"#EA4335"` | Color of the mute button when voice is muted. |
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<MapboxNavigationView
|
|
203
|
+
maneuverBackgroundColorDay="#1E2433"
|
|
204
|
+
maneuverTurnIconColor="#1A73E8"
|
|
205
|
+
etaBarBackgroundColor="#1E2433"
|
|
206
|
+
etaTextColor="#FFFFFF"
|
|
207
|
+
iconButtonColor="#1A73E8"
|
|
208
|
+
iconButtonMutedColor="#EA4335"
|
|
123
209
|
/>
|
|
124
210
|
```
|
|
125
211
|
|
|
212
|
+
> **Note:** On iOS, `NavigationViewController` applies its own theme. Color props are stored and can be applied via the SDK's `StyleManager` in a future release.
|
|
213
|
+
|
|
214
|
+
### Mapbox Native Colors (Android, via plugin)
|
|
215
|
+
|
|
216
|
+
Override Mapbox's built-in resource colors (route line, etc.) via `androidColorOverrides` in `app.json`:
|
|
217
|
+
|
|
126
218
|
```json
|
|
127
|
-
// app.json — route line and other Mapbox native resource colors
|
|
128
219
|
["@jacques_gordon/expo-mapbox-navigation", {
|
|
129
220
|
"accessToken": "pk.xxx",
|
|
130
221
|
"downloadsToken": "sk.xxx",
|
|
222
|
+
"mapboxMapsVersion": "11.11.0",
|
|
131
223
|
"androidColorOverrides": {
|
|
132
224
|
"mapbox_primary_route_color": "#0055FF",
|
|
133
225
|
"mapbox_main_maneuver_background_color": "#FF5500"
|
|
@@ -137,83 +229,77 @@ All color props are optional — if not provided, the defaults below are applied
|
|
|
137
229
|
|
|
138
230
|
---
|
|
139
231
|
|
|
140
|
-
## Props
|
|
141
|
-
|
|
142
|
-
| Prop | Type | Default | Description |
|
|
143
|
-
|------|------|---------|-------------|
|
|
144
|
-
| `coordinates` | `Coordinate[]` | required | Route waypoints. Min 2 items. |
|
|
145
|
-
| `waypointIndices` | `number[]` | all points | Which coordinates are waypoints. |
|
|
146
|
-
| `language` | `string` | device locale | BCP-47 locale (e.g. `"fr"`, `"en-US"`). |
|
|
147
|
-
| `voiceUnits` | `"metric" \| "imperial"` | auto | **Fix #31** — Voice/distance units. |
|
|
148
|
-
| `navigationProfile` | `string` | `"driving-traffic"` | Mapbox routing profile. |
|
|
149
|
-
| `excludeTypes` | `string[]` | — | Road types to avoid. |
|
|
150
|
-
| `mapStyle` | `string` | Mapbox Navigation Day | Map style URL. |
|
|
151
|
-
| `mute` | `boolean` | `false` | Silence voice instructions. |
|
|
152
|
-
| `maxHeight` | `number` | — | Max vehicle height (m). |
|
|
153
|
-
| `maxWidth` | `number` | — | Max vehicle width (m). |
|
|
154
|
-
| `useMapMatching` | `boolean` | `false` | Use Map Matching API. |
|
|
155
|
-
| `customRasterTileUrl` | `string` | — | Custom tile URL with `{x}/{y}/{z}`. |
|
|
156
|
-
| `customRasterAboveLayerId` | `string` | — | Layer ID to place custom raster above. |
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
232
|
## Events
|
|
161
233
|
|
|
162
234
|
| Event | Payload | Description |
|
|
163
235
|
|-------|---------|-------------|
|
|
164
|
-
| `onRoutesReady` | `{ routeCount, distanceMeters, durationSeconds }` |
|
|
165
|
-
| `onRouteProgressChanged` | `{ distanceRemaining, durationRemaining,
|
|
166
|
-
| `onArrival` | `{}` | User reached destination. |
|
|
167
|
-
| `onNavigationCancelled` | `{}` | User
|
|
168
|
-
| `onNavigationFinished` | `{}` |
|
|
169
|
-
| `onRoutesFailed` | `{ message }` | Route calculation failed. |
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}]
|
|
236
|
+
| `onRoutesReady` | `{ routeCount, distanceMeters, durationSeconds }` | Fired when routes are calculated and navigation starts. |
|
|
237
|
+
| `onRouteProgressChanged` | `{ distanceRemaining, durationRemaining, distanceTraveled, fractionTraveled, currentStepDistanceRemaining }` | Fired on every GPS update during navigation. |
|
|
238
|
+
| `onArrival` | `{}` | User reached the destination. |
|
|
239
|
+
| `onNavigationCancelled` | `{}` | User tapped the cancel (✕) button. |
|
|
240
|
+
| `onNavigationFinished` | `{}` | Navigation session ended normally. |
|
|
241
|
+
| `onRoutesFailed` | `{ message: string }` | Route calculation failed. |
|
|
242
|
+
| `onManeuverBannerPressed` | `{ steps: RouteStep[] }` | Fired when user taps the instruction banner. Use to open a bottom sheet with the full steps list. |
|
|
243
|
+
|
|
244
|
+
### RouteStep type
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
interface RouteStep {
|
|
248
|
+
instruction: string; // "Turn left onto Main St"
|
|
249
|
+
distanceMeters: number;
|
|
250
|
+
durationSeconds: number;
|
|
251
|
+
maneuverType: string; // "turn", "merge", "roundabout", etc.
|
|
252
|
+
maneuverModifier: string; // "left", "right", "straight", etc.
|
|
253
|
+
roadName: string;
|
|
254
|
+
laneInstructions: {
|
|
255
|
+
active: boolean; // true = recommended lane
|
|
256
|
+
directions: string[]; // ["straight"], ["left", "straight"]
|
|
257
|
+
}[];
|
|
258
|
+
}
|
|
186
259
|
```
|
|
187
260
|
|
|
188
261
|
---
|
|
189
262
|
|
|
190
|
-
## 16 KB Page Size
|
|
263
|
+
## 16 KB Page Size (Android 15+)
|
|
191
264
|
|
|
192
|
-
|
|
265
|
+
This package enforces full compliance with Android's 16 KB memory page size requirement:
|
|
193
266
|
|
|
194
|
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
- **
|
|
267
|
+
- **NDK 27** (`27.0.12077973`) — first NDK version with full 16 KB support
|
|
268
|
+
- **`jniLibs.useLegacyPackaging = false`** — prevents `.so` compression, enables proper alignment
|
|
269
|
+
- **64-bit ABI filters** (`arm64-v8a`, `x86_64`) — requirement applies to 64-bit only
|
|
270
|
+
- **NDK27 variant substitution** — `dependencySubstitution` replaces all Mapbox Maven artifacts with their `-ndk27` equivalents across the entire dependency graph (including transitive deps from other packages)
|
|
198
271
|
|
|
199
|
-
|
|
272
|
+
See [Android 16 KB page size guide](https://developer.android.com/guide/practices/page-sizes).
|
|
200
273
|
|
|
201
274
|
---
|
|
202
275
|
|
|
203
276
|
## Changelog
|
|
204
277
|
|
|
278
|
+
### 2.2.8
|
|
279
|
+
- **iOS version strategy redesigned** — dynamic `mapboxNavigationVersion` calculation from `mapboxMapsVersion` minor. Prevents `MapboxCommon` version conflicts with `@rnmapbox/maps`. Pattern confirmed from real Mapbox releases: Navigation `3.N.x` always compatible with Maps `11.N.x`.
|
|
280
|
+
- **iOS: `mapboxNavigationVersion` optional param** — escape hatch to pin exact Navigation version.
|
|
281
|
+
- **iOS: post_install hook strengthened** — fallback `include?` target search + debug log of all available targets if `ExpoMapboxNavigation` not found.
|
|
282
|
+
- **Android color props fixed** — setters now apply immediately to views (were only stored previously, causing icon colors to remain default).
|
|
283
|
+
|
|
205
284
|
### 2.2.0
|
|
206
|
-
- **iOS support added
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
285
|
+
- **iOS support added** — full native implementation using `NavigationViewController` drop-in UI.
|
|
286
|
+
- iOS SPM integration via `post_install` Podfile hook (Xcodeproj Ruby API, same technique as `@rnmapbox/maps`).
|
|
287
|
+
- `downloadsToken` required for iOS SPM authentication.
|
|
288
|
+
- Fixed previous phantom `.xcframework` references that caused `Unimplemented component` crashes.
|
|
289
|
+
|
|
290
|
+
### 2.1.x
|
|
291
|
+
- Waze-style Android UI: maneuver banner, speed limit, ETA bar, action buttons (mute/overview/recenter).
|
|
292
|
+
- Voice instructions with TTS fallback.
|
|
293
|
+
- Puck jitter fix (GitHub issue #4140) — `keyPoints = emptyList()`.
|
|
294
|
+
- Lane guidance fix — explicit `bannerInstructions(true)`, `steps(true)`, `roundaboutExits(true)`.
|
|
295
|
+
- `onManeuverBannerPressed` event with full route steps list.
|
|
296
|
+
- Color customization props (Android).
|
|
210
297
|
|
|
211
298
|
### 2.0.1
|
|
212
|
-
- Fix #43: `CameraAnimationsUtils.calculateCameraAnimationHint`
|
|
213
|
-
- Fix #31:
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
- Expo SDK 53 compatibility
|
|
299
|
+
- Fix #43: `CameraAnimationsUtils.calculateCameraAnimationHint` crash on Android.
|
|
300
|
+
- Fix #31: `voiceUnits` prop for metric/imperial.
|
|
301
|
+
- NDK 27 + 16 KB page size enforcement.
|
|
302
|
+
- Expo SDK 53 compatibility.
|
|
217
303
|
|
|
218
304
|
---
|
|
219
305
|
|
package/app.plugin.js
CHANGED
|
@@ -12,6 +12,7 @@ const withMapboxNavigation = (config, options = {}) => {
|
|
|
12
12
|
accessToken,
|
|
13
13
|
downloadsToken,
|
|
14
14
|
mapboxMapsVersion = '11.11.0',
|
|
15
|
+
mapboxNavigationVersion = null, // optional override — auto-calculated from mapboxMapsVersion if not set
|
|
15
16
|
androidColorOverrides = {},
|
|
16
17
|
} = options;
|
|
17
18
|
|
|
@@ -81,6 +82,196 @@ const withMapboxNavigation = (config, options = {}) => {
|
|
|
81
82
|
},
|
|
82
83
|
]);
|
|
83
84
|
|
|
85
|
+
// ── iOS: Inject mapbox-navigation-ios SPM via Podfile post_install hook ──────
|
|
86
|
+
//
|
|
87
|
+
// This is the correct approach for adding SPM dependencies alongside
|
|
88
|
+
// CocoaPods in an Expo project — copied directly from @rnmapbox/maps
|
|
89
|
+
// (rnmapbox-maps.podspec, _add_spm_to_target method).
|
|
90
|
+
//
|
|
91
|
+
// WHY post_install hook (not pbxproj text injection):
|
|
92
|
+
// The hook runs INSIDE `pod install`, with access to the Ruby Xcodeproj
|
|
93
|
+
// object model (installer.pods_project, installer.aggregate_targets).
|
|
94
|
+
// This means:
|
|
95
|
+
// - Proper find-or-create (no duplicate symbols risk)
|
|
96
|
+
// - CocoaPods-aware (survives pod install --clean)
|
|
97
|
+
// - Works in the xcworkspace context (not just xcodeproj)
|
|
98
|
+
// - Identical to how @rnmapbox/maps itself adds SPM packages
|
|
99
|
+
//
|
|
100
|
+
// WHY this doesn't cause duplicate symbols (unlike spm_dependency()):
|
|
101
|
+
// spm_dependency() links the SPM framework into the Pod target AND the app
|
|
102
|
+
// target → 2 copies. This hook adds the package to ONLY the
|
|
103
|
+
// ExpoMapboxNavigation pod target + the app target, using the same
|
|
104
|
+
// XCRemoteSwiftPackageReference object → 1 copy, properly deduplicated.
|
|
105
|
+
config = withDangerousMod(config, [
|
|
106
|
+
'ios',
|
|
107
|
+
(mod) => {
|
|
108
|
+
const podfilePath = path.join(mod.modRequest.platformProjectRoot, 'Podfile');
|
|
109
|
+
if (!fs.existsSync(podfilePath)) {
|
|
110
|
+
console.warn('[@jacques_gordon/expo-mapbox-navigation] Podfile not found, skipping SPM hook');
|
|
111
|
+
return mod;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let podfile = fs.readFileSync(podfilePath, 'utf8');
|
|
115
|
+
|
|
116
|
+
// Guard: don't inject twice
|
|
117
|
+
if (podfile.includes('# [ExpoMapboxNavigation] SPM hook')) {
|
|
118
|
+
return mod;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── NAVIGATION VERSION STRATEGY ───────────────────────────────────────
|
|
122
|
+
// CONFIRMED from the official CHANGELOG.md:
|
|
123
|
+
//
|
|
124
|
+
// PHASE 1 — Nav 3.1 to 3.12 (offset of +3):
|
|
125
|
+
// Navigation 3.N.x requires MapboxMaps 11.(N+3).x
|
|
126
|
+
// Nav 3.8.x → Maps 11.11.x ✅ (Maps 11.11.0 → Nav 3.8.x)
|
|
127
|
+
// Nav 3.11.x → Maps 11.14.x ✅ (confirmed CHANGELOG)
|
|
128
|
+
// Nav 3.12.x → Maps 11.15.x ✅ (confirmed rc.1 release note)
|
|
129
|
+
//
|
|
130
|
+
// PHASE 2 — Nav 3.16+ (3.13/3.14/3.15 were DELIBERATELY SKIPPED):
|
|
131
|
+
// Navigation 3.N.x requires MapboxMaps 11.N.x (minors aligned)
|
|
132
|
+
// Nav 3.16.x → Maps 11.16.x ✅
|
|
133
|
+
// Nav 3.21.5 → Maps 11.21.5 ✅ (confirmed release)
|
|
134
|
+
// Nav 3.23.1 → Maps 11.23.1 ✅ (confirmed release)
|
|
135
|
+
// Nav 3.25.0 → Maps 11.25.0 ✅ (confirmed release)
|
|
136
|
+
//
|
|
137
|
+
// Source: Android CHANGELOG — "3.16.x is the next version after 3.12.x.
|
|
138
|
+
// For technical reasons, versions 3.13.x, 3.14.x and 3.15.x are skipped.
|
|
139
|
+
// Starting from 3.16.x, the Nav SDK minor version will be aligned with
|
|
140
|
+
// other Mapbox dependencies." (same policy applies to iOS)
|
|
141
|
+
//
|
|
142
|
+
// FORMULA:
|
|
143
|
+
// if mapsMinor <= 15: navMinor = mapsMinor - 3
|
|
144
|
+
// if mapsMinor >= 16: navMinor = mapsMinor
|
|
145
|
+
//
|
|
146
|
+
// EXAMPLE: mapboxMapsVersion = "11.11.0"
|
|
147
|
+
// mapsMinor = 11 (≤15, Phase 1)
|
|
148
|
+
// navMinor = 11 - 3 = 8
|
|
149
|
+
// navMin = "3.8.0" → SPM resolves latest 3.8.x → Maps 11.11.x ✅
|
|
150
|
+
//
|
|
151
|
+
// EXAMPLE: mapboxMapsVersion = "11.21.0"
|
|
152
|
+
// mapsMinor = 21 (≥16, Phase 2)
|
|
153
|
+
// navMinor = 21
|
|
154
|
+
// navMin = "3.21.0" → SPM resolves latest 3.21.x → Maps 11.21.x ✅
|
|
155
|
+
const mapsVersion = mapboxMapsVersion || '11.11.0';
|
|
156
|
+
const mapsMinor = parseInt(mapsVersion.split('.')[1], 10) || 11;
|
|
157
|
+
const navMinor = mapsMinor <= 15 ? mapsMinor - 3 : mapsMinor;
|
|
158
|
+
const navMin = mapboxNavigationVersion || `3.${navMinor}.0`;
|
|
159
|
+
|
|
160
|
+
console.log(`[@jacques_gordon/expo-mapbox-navigation] Maps ${mapsVersion} (minor=${mapsMinor}) → Navigation ${navMin}..<3.${navMinor+1}.0`);
|
|
161
|
+
console.log(`[@jacques_gordon/expo-mapbox-navigation] Phase: ${mapsMinor <= 15 ? `1 (offset -3: ${mapsMinor}-3=${navMinor})` : `2 (aligned: ${navMinor})`}`);
|
|
162
|
+
|
|
163
|
+
// The Ruby hook — identical pattern to @rnmapbox/maps _add_spm_to_target
|
|
164
|
+
const spmHook = `
|
|
165
|
+
# [ExpoMapboxNavigation] SPM hook — injected by @jacques_gordon/expo-mapbox-navigation
|
|
166
|
+
# Navigation: upToNextMinorVersion from ${navMin}
|
|
167
|
+
# Maps: ${mapsVersion} (minor ${mapsMinor}, ${mapsMinor <= 15 ? 'Phase 1: offset -3' : 'Phase 2: aligned'})
|
|
168
|
+
def _expo_mapbox_nav_add_spm(installer)
|
|
169
|
+
url = 'https://github.com/mapbox/mapbox-navigation-ios.git'
|
|
170
|
+
requirement = { kind: 'upToNextMinorVersion', minimumVersion: '${navMin}' }
|
|
171
|
+
products = ['MapboxNavigationCore', 'MapboxNavigationUIKit']
|
|
172
|
+
|
|
173
|
+
pkg_class = Xcodeproj::Project::Object::XCRemoteSwiftPackageReference
|
|
174
|
+
ref_class = Xcodeproj::Project::Object::XCSwiftPackageProductDependency
|
|
175
|
+
|
|
176
|
+
# ── Step 1: Add to pods_project (where ExpoMapboxNavigation target lives) ──
|
|
177
|
+
pods_project = installer.pods_project
|
|
178
|
+
|
|
179
|
+
pkg = pods_project.root_object.package_references.find { |p|
|
|
180
|
+
p.class == pkg_class && p.repositoryURL == url
|
|
181
|
+
}
|
|
182
|
+
unless pkg
|
|
183
|
+
pkg = pods_project.new(pkg_class)
|
|
184
|
+
pkg.repositoryURL = url
|
|
185
|
+
pkg.requirement = requirement
|
|
186
|
+
pods_project.root_object.package_references << pkg
|
|
187
|
+
puts '[ExpoMapboxNavigation] Added mapbox-navigation-ios to pods_project'
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# ── FIX: Stronger target lookup with fallback ──────────────────────────────
|
|
191
|
+
# CocoaPods normally names the target exactly 'ExpoMapboxNavigation'.
|
|
192
|
+
# Deduplication suffixes (e.g. 'ExpoMapboxNavigation-abc123') only happen
|
|
193
|
+
# when the same pod is included in multiple targets with different specs,
|
|
194
|
+
# which is not our case. We still add an include? fallback to be safe.
|
|
195
|
+
expo_target = pods_project.targets.find { |t| t.name == 'ExpoMapboxNavigation' }
|
|
196
|
+
expo_target ||= pods_project.targets.find { |t| t.name.include?('ExpoMapboxNavigation') }
|
|
197
|
+
if expo_target
|
|
198
|
+
puts "[ExpoMapboxNavigation] Found target: #{expo_target.name}"
|
|
199
|
+
products.each do |product_name|
|
|
200
|
+
ref = expo_target.package_product_dependencies.find { |r|
|
|
201
|
+
r.class == ref_class && r.package == pkg && r.product_name == product_name
|
|
202
|
+
}
|
|
203
|
+
unless ref
|
|
204
|
+
ref = pods_project.new(ref_class)
|
|
205
|
+
ref.package = pkg
|
|
206
|
+
ref.product_name = product_name
|
|
207
|
+
expo_target.package_product_dependencies << ref
|
|
208
|
+
puts "[ExpoMapboxNavigation] Linked #{product_name} -> #{expo_target.name}"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
# Debug: print all available targets so we can fix the name if needed
|
|
213
|
+
puts '[ExpoMapboxNavigation] WARNING: ExpoMapboxNavigation target not found!'
|
|
214
|
+
puts '[ExpoMapboxNavigation] Available targets:'
|
|
215
|
+
pods_project.targets.each { |t| puts " - #{t.name}" }
|
|
216
|
+
end
|
|
217
|
+
pods_project.save
|
|
218
|
+
|
|
219
|
+
# ── Step 2: Add to user app target (needed for import in app binary) ────────
|
|
220
|
+
installer.aggregate_targets.each do |agg|
|
|
221
|
+
user_project = agg.user_project
|
|
222
|
+
agg.user_targets.each do |user_target|
|
|
223
|
+
user_pkg = user_project.root_object.package_references.find { |p|
|
|
224
|
+
p.class == pkg_class && p.repositoryURL == url
|
|
225
|
+
}
|
|
226
|
+
unless user_pkg
|
|
227
|
+
user_pkg = user_project.new(pkg_class)
|
|
228
|
+
user_pkg.repositoryURL = url
|
|
229
|
+
user_pkg.requirement = requirement
|
|
230
|
+
user_project.root_object.package_references << user_pkg
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
products.each do |product_name|
|
|
234
|
+
ref = user_target.package_product_dependencies.find { |r|
|
|
235
|
+
r.class == ref_class && r.package == user_pkg && r.product_name == product_name
|
|
236
|
+
}
|
|
237
|
+
unless ref
|
|
238
|
+
ref = user_project.new(ref_class)
|
|
239
|
+
ref.package = user_pkg
|
|
240
|
+
ref.product_name = product_name
|
|
241
|
+
user_target.package_product_dependencies << ref
|
|
242
|
+
puts "[ExpoMapboxNavigation] Linked #{product_name} -> #{user_target.name}"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
user_project.save
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
// Find the last post_install block and add our call inside it,
|
|
252
|
+
// or add a new post_install block if none exists.
|
|
253
|
+
if (podfile.includes('post_install do |installer|')) {
|
|
254
|
+
// Add our helper def before the first post_install
|
|
255
|
+
// and our call inside the existing post_install
|
|
256
|
+
podfile = spmHook + podfile.replace(
|
|
257
|
+
'post_install do |installer|',
|
|
258
|
+
'post_install do |installer|\n _expo_mapbox_nav_add_spm(installer)'
|
|
259
|
+
);
|
|
260
|
+
} else {
|
|
261
|
+
// No post_install block — add both the helper and a new block
|
|
262
|
+
podfile = podfile + spmHook + `
|
|
263
|
+
post_install do |installer|
|
|
264
|
+
_expo_mapbox_nav_add_spm(installer)
|
|
265
|
+
end
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
fs.writeFileSync(podfilePath, podfile, 'utf8');
|
|
270
|
+
console.log('[@jacques_gordon/expo-mapbox-navigation] ✅ Injected mapbox-navigation-ios SPM hook into Podfile');
|
|
271
|
+
return mod;
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
|
|
84
275
|
return config;
|
|
85
276
|
};
|
|
86
277
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jacques_gordon/expo-mapbox-navigation",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
4
4
|
"description": "Expo module for Mapbox Navigation SDK with 16KB page size support, NDK27, and Mapbox Maps v11.11.0+",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
package/plugin/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const withMapboxNavigation = (config, options = {}) => {
|
|
|
12
12
|
accessToken,
|
|
13
13
|
downloadsToken,
|
|
14
14
|
mapboxMapsVersion = '11.11.0',
|
|
15
|
+
mapboxNavigationVersion = null, // optional override — auto-calculated from mapboxMapsVersion if not set
|
|
15
16
|
androidColorOverrides = {},
|
|
16
17
|
} = options;
|
|
17
18
|
|
|
@@ -81,6 +82,196 @@ const withMapboxNavigation = (config, options = {}) => {
|
|
|
81
82
|
},
|
|
82
83
|
]);
|
|
83
84
|
|
|
85
|
+
// ── iOS: Inject mapbox-navigation-ios SPM via Podfile post_install hook ──────
|
|
86
|
+
//
|
|
87
|
+
// This is the correct approach for adding SPM dependencies alongside
|
|
88
|
+
// CocoaPods in an Expo project — copied directly from @rnmapbox/maps
|
|
89
|
+
// (rnmapbox-maps.podspec, _add_spm_to_target method).
|
|
90
|
+
//
|
|
91
|
+
// WHY post_install hook (not pbxproj text injection):
|
|
92
|
+
// The hook runs INSIDE `pod install`, with access to the Ruby Xcodeproj
|
|
93
|
+
// object model (installer.pods_project, installer.aggregate_targets).
|
|
94
|
+
// This means:
|
|
95
|
+
// - Proper find-or-create (no duplicate symbols risk)
|
|
96
|
+
// - CocoaPods-aware (survives pod install --clean)
|
|
97
|
+
// - Works in the xcworkspace context (not just xcodeproj)
|
|
98
|
+
// - Identical to how @rnmapbox/maps itself adds SPM packages
|
|
99
|
+
//
|
|
100
|
+
// WHY this doesn't cause duplicate symbols (unlike spm_dependency()):
|
|
101
|
+
// spm_dependency() links the SPM framework into the Pod target AND the app
|
|
102
|
+
// target → 2 copies. This hook adds the package to ONLY the
|
|
103
|
+
// ExpoMapboxNavigation pod target + the app target, using the same
|
|
104
|
+
// XCRemoteSwiftPackageReference object → 1 copy, properly deduplicated.
|
|
105
|
+
config = withDangerousMod(config, [
|
|
106
|
+
'ios',
|
|
107
|
+
(mod) => {
|
|
108
|
+
const podfilePath = path.join(mod.modRequest.platformProjectRoot, 'Podfile');
|
|
109
|
+
if (!fs.existsSync(podfilePath)) {
|
|
110
|
+
console.warn('[@jacques_gordon/expo-mapbox-navigation] Podfile not found, skipping SPM hook');
|
|
111
|
+
return mod;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let podfile = fs.readFileSync(podfilePath, 'utf8');
|
|
115
|
+
|
|
116
|
+
// Guard: don't inject twice
|
|
117
|
+
if (podfile.includes('# [ExpoMapboxNavigation] SPM hook')) {
|
|
118
|
+
return mod;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── NAVIGATION VERSION STRATEGY ───────────────────────────────────────
|
|
122
|
+
// CONFIRMED from the official CHANGELOG.md:
|
|
123
|
+
//
|
|
124
|
+
// PHASE 1 — Nav 3.1 to 3.12 (offset of +3):
|
|
125
|
+
// Navigation 3.N.x requires MapboxMaps 11.(N+3).x
|
|
126
|
+
// Nav 3.8.x → Maps 11.11.x ✅ (Maps 11.11.0 → Nav 3.8.x)
|
|
127
|
+
// Nav 3.11.x → Maps 11.14.x ✅ (confirmed CHANGELOG)
|
|
128
|
+
// Nav 3.12.x → Maps 11.15.x ✅ (confirmed rc.1 release note)
|
|
129
|
+
//
|
|
130
|
+
// PHASE 2 — Nav 3.16+ (3.13/3.14/3.15 were DELIBERATELY SKIPPED):
|
|
131
|
+
// Navigation 3.N.x requires MapboxMaps 11.N.x (minors aligned)
|
|
132
|
+
// Nav 3.16.x → Maps 11.16.x ✅
|
|
133
|
+
// Nav 3.21.5 → Maps 11.21.5 ✅ (confirmed release)
|
|
134
|
+
// Nav 3.23.1 → Maps 11.23.1 ✅ (confirmed release)
|
|
135
|
+
// Nav 3.25.0 → Maps 11.25.0 ✅ (confirmed release)
|
|
136
|
+
//
|
|
137
|
+
// Source: Android CHANGELOG — "3.16.x is the next version after 3.12.x.
|
|
138
|
+
// For technical reasons, versions 3.13.x, 3.14.x and 3.15.x are skipped.
|
|
139
|
+
// Starting from 3.16.x, the Nav SDK minor version will be aligned with
|
|
140
|
+
// other Mapbox dependencies." (same policy applies to iOS)
|
|
141
|
+
//
|
|
142
|
+
// FORMULA:
|
|
143
|
+
// if mapsMinor <= 15: navMinor = mapsMinor - 3
|
|
144
|
+
// if mapsMinor >= 16: navMinor = mapsMinor
|
|
145
|
+
//
|
|
146
|
+
// EXAMPLE: mapboxMapsVersion = "11.11.0"
|
|
147
|
+
// mapsMinor = 11 (≤15, Phase 1)
|
|
148
|
+
// navMinor = 11 - 3 = 8
|
|
149
|
+
// navMin = "3.8.0" → SPM resolves latest 3.8.x → Maps 11.11.x ✅
|
|
150
|
+
//
|
|
151
|
+
// EXAMPLE: mapboxMapsVersion = "11.21.0"
|
|
152
|
+
// mapsMinor = 21 (≥16, Phase 2)
|
|
153
|
+
// navMinor = 21
|
|
154
|
+
// navMin = "3.21.0" → SPM resolves latest 3.21.x → Maps 11.21.x ✅
|
|
155
|
+
const mapsVersion = mapboxMapsVersion || '11.11.0';
|
|
156
|
+
const mapsMinor = parseInt(mapsVersion.split('.')[1], 10) || 11;
|
|
157
|
+
const navMinor = mapsMinor <= 15 ? mapsMinor - 3 : mapsMinor;
|
|
158
|
+
const navMin = mapboxNavigationVersion || `3.${navMinor}.0`;
|
|
159
|
+
|
|
160
|
+
console.log(`[@jacques_gordon/expo-mapbox-navigation] Maps ${mapsVersion} (minor=${mapsMinor}) → Navigation ${navMin}..<3.${navMinor+1}.0`);
|
|
161
|
+
console.log(`[@jacques_gordon/expo-mapbox-navigation] Phase: ${mapsMinor <= 15 ? `1 (offset -3: ${mapsMinor}-3=${navMinor})` : `2 (aligned: ${navMinor})`}`);
|
|
162
|
+
|
|
163
|
+
// The Ruby hook — identical pattern to @rnmapbox/maps _add_spm_to_target
|
|
164
|
+
const spmHook = `
|
|
165
|
+
# [ExpoMapboxNavigation] SPM hook — injected by @jacques_gordon/expo-mapbox-navigation
|
|
166
|
+
# Navigation: upToNextMinorVersion from ${navMin}
|
|
167
|
+
# Maps: ${mapsVersion} (minor ${mapsMinor}, ${mapsMinor <= 15 ? 'Phase 1: offset -3' : 'Phase 2: aligned'})
|
|
168
|
+
def _expo_mapbox_nav_add_spm(installer)
|
|
169
|
+
url = 'https://github.com/mapbox/mapbox-navigation-ios.git'
|
|
170
|
+
requirement = { kind: 'upToNextMinorVersion', minimumVersion: '${navMin}' }
|
|
171
|
+
products = ['MapboxNavigationCore', 'MapboxNavigationUIKit']
|
|
172
|
+
|
|
173
|
+
pkg_class = Xcodeproj::Project::Object::XCRemoteSwiftPackageReference
|
|
174
|
+
ref_class = Xcodeproj::Project::Object::XCSwiftPackageProductDependency
|
|
175
|
+
|
|
176
|
+
# ── Step 1: Add to pods_project (where ExpoMapboxNavigation target lives) ──
|
|
177
|
+
pods_project = installer.pods_project
|
|
178
|
+
|
|
179
|
+
pkg = pods_project.root_object.package_references.find { |p|
|
|
180
|
+
p.class == pkg_class && p.repositoryURL == url
|
|
181
|
+
}
|
|
182
|
+
unless pkg
|
|
183
|
+
pkg = pods_project.new(pkg_class)
|
|
184
|
+
pkg.repositoryURL = url
|
|
185
|
+
pkg.requirement = requirement
|
|
186
|
+
pods_project.root_object.package_references << pkg
|
|
187
|
+
puts '[ExpoMapboxNavigation] Added mapbox-navigation-ios to pods_project'
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# ── FIX: Stronger target lookup with fallback ──────────────────────────────
|
|
191
|
+
# CocoaPods normally names the target exactly 'ExpoMapboxNavigation'.
|
|
192
|
+
# Deduplication suffixes (e.g. 'ExpoMapboxNavigation-abc123') only happen
|
|
193
|
+
# when the same pod is included in multiple targets with different specs,
|
|
194
|
+
# which is not our case. We still add an include? fallback to be safe.
|
|
195
|
+
expo_target = pods_project.targets.find { |t| t.name == 'ExpoMapboxNavigation' }
|
|
196
|
+
expo_target ||= pods_project.targets.find { |t| t.name.include?('ExpoMapboxNavigation') }
|
|
197
|
+
if expo_target
|
|
198
|
+
puts "[ExpoMapboxNavigation] Found target: #{expo_target.name}"
|
|
199
|
+
products.each do |product_name|
|
|
200
|
+
ref = expo_target.package_product_dependencies.find { |r|
|
|
201
|
+
r.class == ref_class && r.package == pkg && r.product_name == product_name
|
|
202
|
+
}
|
|
203
|
+
unless ref
|
|
204
|
+
ref = pods_project.new(ref_class)
|
|
205
|
+
ref.package = pkg
|
|
206
|
+
ref.product_name = product_name
|
|
207
|
+
expo_target.package_product_dependencies << ref
|
|
208
|
+
puts "[ExpoMapboxNavigation] Linked #{product_name} -> #{expo_target.name}"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
# Debug: print all available targets so we can fix the name if needed
|
|
213
|
+
puts '[ExpoMapboxNavigation] WARNING: ExpoMapboxNavigation target not found!'
|
|
214
|
+
puts '[ExpoMapboxNavigation] Available targets:'
|
|
215
|
+
pods_project.targets.each { |t| puts " - #{t.name}" }
|
|
216
|
+
end
|
|
217
|
+
pods_project.save
|
|
218
|
+
|
|
219
|
+
# ── Step 2: Add to user app target (needed for import in app binary) ────────
|
|
220
|
+
installer.aggregate_targets.each do |agg|
|
|
221
|
+
user_project = agg.user_project
|
|
222
|
+
agg.user_targets.each do |user_target|
|
|
223
|
+
user_pkg = user_project.root_object.package_references.find { |p|
|
|
224
|
+
p.class == pkg_class && p.repositoryURL == url
|
|
225
|
+
}
|
|
226
|
+
unless user_pkg
|
|
227
|
+
user_pkg = user_project.new(pkg_class)
|
|
228
|
+
user_pkg.repositoryURL = url
|
|
229
|
+
user_pkg.requirement = requirement
|
|
230
|
+
user_project.root_object.package_references << user_pkg
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
products.each do |product_name|
|
|
234
|
+
ref = user_target.package_product_dependencies.find { |r|
|
|
235
|
+
r.class == ref_class && r.package == user_pkg && r.product_name == product_name
|
|
236
|
+
}
|
|
237
|
+
unless ref
|
|
238
|
+
ref = user_project.new(ref_class)
|
|
239
|
+
ref.package = user_pkg
|
|
240
|
+
ref.product_name = product_name
|
|
241
|
+
user_target.package_product_dependencies << ref
|
|
242
|
+
puts "[ExpoMapboxNavigation] Linked #{product_name} -> #{user_target.name}"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
user_project.save
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
// Find the last post_install block and add our call inside it,
|
|
252
|
+
// or add a new post_install block if none exists.
|
|
253
|
+
if (podfile.includes('post_install do |installer|')) {
|
|
254
|
+
// Add our helper def before the first post_install
|
|
255
|
+
// and our call inside the existing post_install
|
|
256
|
+
podfile = spmHook + podfile.replace(
|
|
257
|
+
'post_install do |installer|',
|
|
258
|
+
'post_install do |installer|\n _expo_mapbox_nav_add_spm(installer)'
|
|
259
|
+
);
|
|
260
|
+
} else {
|
|
261
|
+
// No post_install block — add both the helper and a new block
|
|
262
|
+
podfile = podfile + spmHook + `
|
|
263
|
+
post_install do |installer|
|
|
264
|
+
_expo_mapbox_nav_add_spm(installer)
|
|
265
|
+
end
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
fs.writeFileSync(podfilePath, podfile, 'utf8');
|
|
270
|
+
console.log('[@jacques_gordon/expo-mapbox-navigation] ✅ Injected mapbox-navigation-ios SPM hook into Podfile');
|
|
271
|
+
return mod;
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
|
|
84
275
|
return config;
|
|
85
276
|
};
|
|
86
277
|
|