@jacques_gordon/expo-mapbox-navigation 2.2.5 → 2.2.7
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 +58 -25
- package/android/src/main/java/expo/modules/mapboxnavigation/ExpoMapboxNavigationView.kt +47 -5
- package/app.plugin.js +137 -221
- package/ios/ExpoMapboxNavigationModule.swift +49 -27
- package/ios/ExpoMapboxNavigationView.swift +122 -116
- package/ios/Package.swift +69 -0
- package/ios/build-xcframeworks.sh +118 -0
- package/package.json +1 -1
- package/plugin/src/index.js +137 -221
|
@@ -2,6 +2,33 @@ 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
|
+
|
|
5
32
|
Pod::Spec.new do |s|
|
|
6
33
|
s.name = 'ExpoMapboxNavigation'
|
|
7
34
|
s.version = package['version']
|
|
@@ -10,40 +37,46 @@ Pod::Spec.new do |s|
|
|
|
10
37
|
s.license = package['license']
|
|
11
38
|
s.author = package['author']
|
|
12
39
|
s.homepage = package['homepage']
|
|
40
|
+
|
|
41
|
+
# iOS 14+ as required by Mapbox Navigation SDK v3
|
|
13
42
|
s.platforms = { :ios => '14.0' }
|
|
14
43
|
s.swift_version = '5.9'
|
|
15
44
|
s.source = { git: package['repository']['url'], tag: "v#{s.version}" }
|
|
16
45
|
|
|
17
46
|
s.dependency 'ExpoModulesCore'
|
|
18
47
|
|
|
19
|
-
# ──
|
|
48
|
+
# ── Vendored xcframeworks ──────────────────────────────────────────────────
|
|
49
|
+
# Mapbox Navigation SDK v3 for iOS is SPM-only — CocoaPods support is
|
|
50
|
+
# announced as "coming soon" by Mapbox but not yet available.
|
|
20
51
|
#
|
|
21
|
-
#
|
|
52
|
+
# We vendor pre-compiled xcframeworks built from the official source using
|
|
53
|
+
# the Scipio build tool (see ios/build-xcframeworks.sh). This is the same
|
|
54
|
+
# approach used by @badatgil/expo-mapbox-navigation (the only community
|
|
55
|
+
# wrapper with confirmed production EAS Build success for iOS + Mapbox Nav v3).
|
|
22
56
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
57
|
+
# The vendored frameworks are:
|
|
58
|
+
# - MapboxNavigationCore — routing engine, location matching, progress
|
|
59
|
+
# - MapboxNavigationUIKit — NavigationViewController drop-in UI
|
|
60
|
+
# - MapboxNavigationNative — binary (pre-compiled, from Mapbox Maven)
|
|
61
|
+
# - MapboxDirections — Waypoint, NavigationRouteOptions, etc.
|
|
62
|
+
# - _MapboxNavigationHelpers — internal helpers
|
|
28
63
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# the actual linking happens at the app level from the SPM dependency added
|
|
38
|
-
# by the config plugin.
|
|
39
|
-
s.pod_target_xcconfig = {
|
|
40
|
-
'DEFINES_MODULE' => 'YES',
|
|
41
|
-
'SWIFT_COMPILATION_MODE' => 'wholemodule',
|
|
42
|
-
'OTHER_SWIFT_FLAGS' => '$(inherited) -Xfrontend -disable-reflection-metadata',
|
|
43
|
-
'FRAMEWORK_SEARCH_PATHS' => '$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks',
|
|
44
|
-
'OTHER_LDFLAGS' => '$(inherited)',
|
|
45
|
-
'IPHONEOS_DEPLOYMENT_TARGET' => '14.0',
|
|
46
|
-
}
|
|
64
|
+
# MapboxMaps, MapboxCommon, Turf are NOT vendored here — they come from
|
|
65
|
+
# @rnmapbox/maps which is a required peer dependency. Vendoring them again
|
|
66
|
+
# would cause "duplicate symbols" (the exact error we hit with spm_dependency).
|
|
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
|
|
47
72
|
|
|
48
73
|
s.source_files = 'ios/**/*.{swift,h,m,mm}'
|
|
74
|
+
# Exclude Package.swift and build script from compilation
|
|
75
|
+
s.exclude_files = 'ios/Package.swift', 'ios/build-xcframeworks.sh'
|
|
76
|
+
|
|
77
|
+
s.pod_target_xcconfig = {
|
|
78
|
+
'DEFINES_MODULE' => 'YES',
|
|
79
|
+
'SWIFT_COMPILATION_MODE' => 'wholemodule',
|
|
80
|
+
'IPHONEOS_DEPLOYMENT_TARGET' => '14.0',
|
|
81
|
+
}
|
|
49
82
|
end
|
|
@@ -933,12 +933,54 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
933
933
|
fun setUseMapMatching(u: Boolean) { useMapMatching = u }
|
|
934
934
|
fun setCustomRasterTileUrl(u: String?) { customRasterTileUrl = u }
|
|
935
935
|
fun setCustomRasterAboveLayerId(l: String?) { customRasterAboveLayerId = l }
|
|
936
|
-
|
|
936
|
+
|
|
937
|
+
fun setManeuverBackgroundColorDay(c: String?) {
|
|
938
|
+
maneuverBackgroundColorDay = c
|
|
939
|
+
// ManeuverView is constructed with options in buildUI() which runs before
|
|
940
|
+
// props are received — we cannot rebuild it, but the color can be applied
|
|
941
|
+
// by updating the ManeuverView background tint directly on the view itself.
|
|
942
|
+
c?.let { hex ->
|
|
943
|
+
try { maneuverView?.setBackgroundColor(Color.parseColor(hex)) } catch (e: Exception) {}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
937
947
|
fun setManeuverTurnIconColor(c: String?) { maneuverTurnIconColor = c }
|
|
938
|
-
|
|
939
|
-
fun
|
|
940
|
-
|
|
941
|
-
|
|
948
|
+
|
|
949
|
+
fun setEtaBarBackgroundColor(c: String?) {
|
|
950
|
+
etaBarBackgroundColor = c
|
|
951
|
+
c?.let { hex ->
|
|
952
|
+
try { etaBar?.setBackgroundColor(Color.parseColor(hex)) } catch (e: Exception) {}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
fun setEtaTextColor(c: String?) {
|
|
957
|
+
etaTextColor = c
|
|
958
|
+
c?.let { hex ->
|
|
959
|
+
try {
|
|
960
|
+
val color = Color.parseColor(hex)
|
|
961
|
+
tvEtaTime?.setTextColor(color)
|
|
962
|
+
tvDuration?.setTextColor(color)
|
|
963
|
+
} catch (e: Exception) {}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
fun setIconButtonColor(c: String?) {
|
|
968
|
+
iconButtonColor = c
|
|
969
|
+
// Redraw overview and recenter icons with new color
|
|
970
|
+
btnOverviewView?.setImageBitmap(drawRouteOverviewIcon())
|
|
971
|
+
// Recenter keeps its current alpha (0.4 following / 1.0 overview)
|
|
972
|
+
val currentAlpha = btnRecenterView?.alpha ?: 0.4f
|
|
973
|
+
btnRecenterView?.setImageBitmap(drawNavigationArrowIcon())
|
|
974
|
+
btnRecenterView?.alpha = currentAlpha
|
|
975
|
+
// Redraw mute icon only if not currently muted (muted uses iconButtonMutedColor)
|
|
976
|
+
if (!isMuted) btnMuteView?.setImageBitmap(drawSpeakerIcon(false))
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
fun setIconButtonMutedColor(c: String?) {
|
|
980
|
+
iconButtonMutedColor = c
|
|
981
|
+
// Redraw mute icon only if currently muted
|
|
982
|
+
if (isMuted) btnMuteView?.setImageBitmap(drawSpeakerIcon(true))
|
|
983
|
+
}
|
|
942
984
|
|
|
943
985
|
override fun onDetachedFromWindow() {
|
|
944
986
|
super.onDetachedFromWindow()
|
package/app.plugin.js
CHANGED
|
@@ -1,201 +1,54 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
withAppBuildGradle,
|
|
3
|
+
withAndroidManifest,
|
|
4
|
+
withInfoPlist,
|
|
5
|
+
withDangerousMod,
|
|
6
|
+
} = require('@expo/config-plugins');
|
|
2
7
|
const fs = require('fs');
|
|
3
8
|
const path = require('path');
|
|
4
9
|
|
|
5
|
-
const NDK_VERSION = '27.0.12077973';
|
|
6
|
-
const MAPBOX_MAPS_MIN_VERSION = '11.11.0';
|
|
7
|
-
|
|
8
10
|
const withMapboxNavigation = (config, options = {}) => {
|
|
9
11
|
const {
|
|
10
12
|
accessToken,
|
|
11
13
|
downloadsToken,
|
|
12
|
-
mapboxMapsVersion =
|
|
14
|
+
mapboxMapsVersion = '11.11.0',
|
|
13
15
|
androidColorOverrides = {},
|
|
14
16
|
} = options;
|
|
15
17
|
|
|
16
18
|
if (!accessToken) {
|
|
17
19
|
throw new Error(
|
|
18
|
-
'[@jacques_gordon/expo-mapbox-navigation] `accessToken` is required
|
|
19
|
-
'
|
|
20
|
-
' ["@jacques_gordon/expo-mapbox-navigation", { "accessToken": "pk.your_token" }]'
|
|
20
|
+
'[@jacques_gordon/expo-mapbox-navigation] `accessToken` is required.\n' +
|
|
21
|
+
' ["@jacques_gordon/expo-mapbox-navigation", { "accessToken": "pk.xxx" }]'
|
|
21
22
|
);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
// downloadsToken (a secret sk.* token with the Downloads:Read scope) is
|
|
25
|
-
// required on iOS to authenticate Swift Package Manager's fetch of the
|
|
26
|
-
// Mapbox Navigation SDK package — without it, `pod install`/`expo
|
|
27
|
-
// prebuild` will fail to resolve the SPM dependency declared in
|
|
28
|
-
// ExpoMapboxNavigation.podspec.
|
|
29
25
|
if (!downloadsToken) {
|
|
30
26
|
throw new Error(
|
|
31
|
-
'[@jacques_gordon/expo-mapbox-navigation] `downloadsToken` is required for iOS
|
|
32
|
-
'This must be a SECRET Mapbox token (starts with "sk.") with the "Downloads:Read" scope,\n' +
|
|
33
|
-
'used to authenticate Swift Package Manager when fetching the Mapbox Navigation SDK.\n' +
|
|
34
|
-
'Add it to your app.json plugins array:\n' +
|
|
27
|
+
'[@jacques_gordon/expo-mapbox-navigation] `downloadsToken` (secret sk.* token) is required for iOS.\n' +
|
|
35
28
|
' ["@jacques_gordon/expo-mapbox-navigation", { "accessToken": "pk.xxx", "downloadsToken": "sk.xxx" }]'
|
|
36
29
|
);
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
if (major < 11 || (major === 11 && minor < 11)) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`[@jacques_gordon/expo-mapbox-navigation] mapboxMapsVersion must be >= 11.11.0.\n` +
|
|
43
|
-
`Provided: ${mapboxMapsVersion}`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── Android: project-level build.gradle ─────────────────────────────────
|
|
48
|
-
config = withProjectBuildGradle(config, (mod) => {
|
|
49
|
-
let contents = mod.modResults.contents;
|
|
50
|
-
|
|
51
|
-
if (!contents.includes('api.mapbox.com/downloads/v2/releases/maven')) {
|
|
52
|
-
const mavenBlock = `
|
|
53
|
-
maven {
|
|
54
|
-
url 'https://api.mapbox.com/downloads/v2/releases/maven'
|
|
55
|
-
authentication { basic(BasicAuthentication) }
|
|
56
|
-
credentials {
|
|
57
|
-
username = 'mapbox'
|
|
58
|
-
password = project.hasProperty('MAPBOX_DOWNLOADS_TOKEN')
|
|
59
|
-
? project.property('MAPBOX_DOWNLOADS_TOKEN')
|
|
60
|
-
: System.getenv('MAPBOX_DOWNLOADS_TOKEN') ?: ""
|
|
61
|
-
}
|
|
62
|
-
}`;
|
|
63
|
-
if (contents.includes('allprojects') && contents.includes('repositories')) {
|
|
64
|
-
contents = contents.replace(
|
|
65
|
-
/allprojects\s*\{[\s\S]*?repositories\s*\{/,
|
|
66
|
-
(match) => match + mavenBlock
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
mod.modResults.contents = contents;
|
|
72
|
-
return mod;
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// ── Android: app-level build.gradle ─────────────────────────────────────
|
|
32
|
+
// ── Android ───────────────────────────────────────────────────────────────
|
|
76
33
|
config = withAppBuildGradle(config, (mod) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!contents.includes('ndkVersion')) {
|
|
80
|
-
contents = contents.replace(
|
|
81
|
-
/android\s*\{/,
|
|
82
|
-
`android {\n ndkVersion "${NDK_VERSION}"`
|
|
83
|
-
);
|
|
84
|
-
} else if (!contents.includes(NDK_VERSION)) {
|
|
85
|
-
contents = contents.replace(
|
|
86
|
-
/ndkVersion\s+["'][^"']*["']/,
|
|
87
|
-
`ndkVersion "${NDK_VERSION}"`
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!contents.includes('useLegacyPackaging')) {
|
|
92
|
-
contents = contents.replace(
|
|
93
|
-
/android\s*\{/,
|
|
94
|
-
`android {\n packagingOptions {\n jniLibs {\n useLegacyPackaging = false\n }\n }`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const resolutionBlock = `
|
|
99
|
-
configurations.all {
|
|
100
|
-
resolutionStrategy {
|
|
101
|
-
force "com.mapbox.maps:android:${mapboxMapsVersion}"
|
|
102
|
-
force "com.mapbox.maps:android-ndk27:${mapboxMapsVersion}"
|
|
103
|
-
}
|
|
104
|
-
}`;
|
|
105
|
-
if (!contents.includes('com.mapbox.maps:android:')) {
|
|
106
|
-
contents = contents + '\n' + resolutionBlock;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
mod.modResults.contents = contents;
|
|
34
|
+
addAndroidConfig(mod, mapboxMapsVersion, androidColorOverrides);
|
|
110
35
|
return mod;
|
|
111
36
|
});
|
|
112
37
|
|
|
113
|
-
// ── Android: AndroidManifest ─────────────────────────────────────────────
|
|
114
38
|
config = withAndroidManifest(config, (mod) => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// ── Permissions required by Mapbox Navigation SDK (targetSdk 35) ────────
|
|
118
|
-
//
|
|
119
|
-
// SecurityException: Starting FGS with type location requires:
|
|
120
|
-
// - android.permission.FOREGROUND_SERVICE_LOCATION (mandatory API 34+)
|
|
121
|
-
// - android.permission.FOREGROUND_SERVICE (mandatory)
|
|
122
|
-
// - android.permission.ACCESS_FINE_LOCATION (at least one of coarse/fine)
|
|
123
|
-
// - android.permission.ACCESS_COARSE_LOCATION
|
|
124
|
-
//
|
|
125
|
-
const requiredPermissions = [
|
|
126
|
-
'android.permission.ACCESS_FINE_LOCATION',
|
|
127
|
-
'android.permission.ACCESS_COARSE_LOCATION',
|
|
128
|
-
'android.permission.FOREGROUND_SERVICE',
|
|
129
|
-
'android.permission.FOREGROUND_SERVICE_LOCATION', // ← fixes the crash
|
|
130
|
-
'android.permission.POST_NOTIFICATIONS', // needed for nav notification (API 33+)
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
if (!manifest['uses-permission']) {
|
|
134
|
-
manifest['uses-permission'] = [];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (const permission of requiredPermissions) {
|
|
138
|
-
const already = manifest['uses-permission'].some(
|
|
139
|
-
(p) => p.$?.['android:name'] === permission
|
|
140
|
-
);
|
|
141
|
-
if (!already) {
|
|
142
|
-
manifest['uses-permission'].push({ $: { 'android:name': permission } });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ── Mapbox access token meta-data ────────────────────────────────────────
|
|
147
|
-
const mainApp = manifest.application?.[0];
|
|
148
|
-
if (mainApp) {
|
|
149
|
-
if (!mainApp['meta-data']) mainApp['meta-data'] = [];
|
|
150
|
-
const tokenMeta = mainApp['meta-data'].find(
|
|
151
|
-
(m) => m.$?.['android:name'] === 'com.mapbox.token'
|
|
152
|
-
);
|
|
153
|
-
if (!tokenMeta) {
|
|
154
|
-
mainApp['meta-data'].push({
|
|
155
|
-
$: { 'android:name': 'com.mapbox.token', 'android:value': accessToken },
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
39
|
+
addAndroidPermissions(mod, accessToken);
|
|
160
40
|
return mod;
|
|
161
41
|
});
|
|
162
42
|
|
|
163
|
-
// ──
|
|
164
|
-
if (Object.keys(androidColorOverrides).length > 0) {
|
|
165
|
-
config = withDangerousMod(config, [
|
|
166
|
-
'android',
|
|
167
|
-
async (mod) => {
|
|
168
|
-
const colorsDir = path.join(
|
|
169
|
-
mod.modRequest.platformProjectRoot,
|
|
170
|
-
'app/src/main/res/values'
|
|
171
|
-
);
|
|
172
|
-
if (!fs.existsSync(colorsDir)) fs.mkdirSync(colorsDir, { recursive: true });
|
|
173
|
-
|
|
174
|
-
const colorEntries = Object.entries(androidColorOverrides)
|
|
175
|
-
.map(([name, value]) => ` <color name="${name}">${value}</color>`)
|
|
176
|
-
.join('\n');
|
|
177
|
-
|
|
178
|
-
fs.writeFileSync(
|
|
179
|
-
path.join(colorsDir, 'mapbox_navigation_colors.xml'),
|
|
180
|
-
`<?xml version="1.0" encoding="utf-8"?>\n<resources>\n${colorEntries}\n</resources>\n`
|
|
181
|
-
);
|
|
182
|
-
return mod;
|
|
183
|
-
},
|
|
184
|
-
]);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ── iOS: Info.plist ──────────────────────────────────────────────────────
|
|
43
|
+
// ── iOS: Info.plist — MBXAccessToken + permissions ────────────────────────
|
|
188
44
|
config = withInfoPlist(config, (mod) => {
|
|
189
45
|
mod.modResults.MBXAccessToken = accessToken;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
mod.modResults.NSLocationAlwaysAndWhenInUseUsageDescription =
|
|
197
|
-
'Your location is used for turn-by-turn navigation even when the app is in the background.';
|
|
198
|
-
}
|
|
46
|
+
mod.modResults.NSLocationWhenInUseUsageDescription =
|
|
47
|
+
mod.modResults.NSLocationWhenInUseUsageDescription ||
|
|
48
|
+
'Your location is used for navigation.';
|
|
49
|
+
mod.modResults.NSLocationAlwaysAndWhenInUseUsageDescription =
|
|
50
|
+
mod.modResults.NSLocationAlwaysAndWhenInUseUsageDescription ||
|
|
51
|
+
'Your location is used for navigation.';
|
|
199
52
|
if (!mod.modResults.UIBackgroundModes) mod.modResults.UIBackgroundModes = [];
|
|
200
53
|
for (const mode of ['audio', 'location']) {
|
|
201
54
|
if (!mod.modResults.UIBackgroundModes.includes(mode)) {
|
|
@@ -205,82 +58,145 @@ configurations.all {
|
|
|
205
58
|
return mod;
|
|
206
59
|
});
|
|
207
60
|
|
|
208
|
-
// ── iOS: .netrc for SPM authentication
|
|
209
|
-
//
|
|
210
|
-
// spm_dependency() to
|
|
211
|
-
// against api.mapbox.com using
|
|
212
|
-
//
|
|
213
|
-
// access, identical in spirit to the MAPBOX_DOWNLOADS_TOKEN gradle
|
|
214
|
-
// property used on Android.
|
|
61
|
+
// ── iOS: .netrc for SPM authentication ────────────────────────────────────
|
|
62
|
+
// The Mapbox Navigation SDK v3 is distributed as source code via SPM only.
|
|
63
|
+
// Our podspec uses spm_dependency() to declare the dependency, and SPM
|
|
64
|
+
// authenticates against api.mapbox.com using ~/.netrc credentials.
|
|
65
|
+
// This is the official Mapbox-documented authentication mechanism.
|
|
215
66
|
config = withDangerousMod(config, [
|
|
216
67
|
'ios',
|
|
217
68
|
(mod) => {
|
|
218
69
|
const homeDir = require('os').homedir();
|
|
219
70
|
const netrcPath = path.join(homeDir, '.netrc');
|
|
220
71
|
const netrcEntry = `machine api.mapbox.com\nlogin mapbox\npassword ${downloadsToken}\n`;
|
|
221
|
-
|
|
222
72
|
let existingContent = '';
|
|
223
73
|
if (fs.existsSync(netrcPath)) {
|
|
224
74
|
existingContent = fs.readFileSync(netrcPath, 'utf8');
|
|
225
75
|
}
|
|
226
|
-
|
|
227
76
|
if (!existingContent.includes('machine api.mapbox.com')) {
|
|
228
77
|
fs.writeFileSync(netrcPath, existingContent + netrcEntry, { mode: 0o600 });
|
|
78
|
+
console.log('[@jacques_gordon/expo-mapbox-navigation] ✅ Wrote Mapbox credentials to ~/.netrc');
|
|
229
79
|
}
|
|
230
|
-
|
|
231
80
|
return mod;
|
|
232
81
|
},
|
|
233
82
|
]);
|
|
234
83
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// "43029 duplicate symbols" linker errors (React Native GitHub #47344,
|
|
240
|
-
// Expo GitHub #37813). The framework ends up linked in both the Pod target
|
|
241
|
-
// AND the main app target, so the linker sees every symbol twice.
|
|
242
|
-
//
|
|
243
|
-
// THE CORRECT APPROACH:
|
|
244
|
-
// Add MapboxNavigation as a Swift Package directly to the Xcode project's
|
|
245
|
-
// main target via withXcodeProject. This ensures only ONE copy of each
|
|
246
|
-
// framework is linked in the final binary — exactly the same technique
|
|
247
|
-
// used by rnmapbox/maps itself for MapboxMaps.
|
|
248
|
-
config = withXcodeProject(config, (mod) => {
|
|
249
|
-
const xcodeProject = mod.modResults;
|
|
250
|
-
const MAPBOX_NAV_REPO = 'https://github.com/mapbox/mapbox-navigation-ios.git';
|
|
251
|
-
const MAPBOX_NAV_VERSION = '3.25.0';
|
|
84
|
+
return config;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// ── Android helpers ───────────────────────────────────────────────────────────
|
|
252
88
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
(pkg) => pkg && pkg.repositoryURL && pkg.repositoryURL.includes('mapbox-navigation-ios')
|
|
89
|
+
function addAndroidConfig(mod, mapboxMapsVersion, androidColorOverrides) {
|
|
90
|
+
if (!mod.modResults.contents.includes('abiFilters')) {
|
|
91
|
+
mod.modResults.contents = mod.modResults.contents.replace(
|
|
92
|
+
/defaultConfig {([\s\S]*?)}/,
|
|
93
|
+
`defaultConfig {\n ndk {\n abiFilters "arm64-v8a", "x86_64"\n }\n $1\n }`
|
|
259
94
|
);
|
|
95
|
+
}
|
|
260
96
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
},
|
|
272
|
-
[
|
|
273
|
-
// Products from MapboxNavigation SPM package that our Swift files import
|
|
274
|
-
{ product: 'MapboxNavigationCore' },
|
|
275
|
-
{ product: 'MapboxNavigationUIKit' },
|
|
276
|
-
]
|
|
277
|
-
);
|
|
97
|
+
const workVersion = '2.8.0';
|
|
98
|
+
mod.modResults.contents = mod.modResults.contents.replace(
|
|
99
|
+
/implementation ['"]androidx.work:work-runtime-ktx:[\d.]+['"]/,
|
|
100
|
+
''
|
|
101
|
+
);
|
|
102
|
+
if (!mod.modResults.contents.includes('work-runtime')) {
|
|
103
|
+
mod.modResults.contents += `
|
|
104
|
+
dependencies {
|
|
105
|
+
implementation 'androidx.work:work-runtime:${workVersion}'
|
|
106
|
+
implementation 'androidx.work:work-runtime-ktx:${workVersion}'
|
|
278
107
|
}
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
279
110
|
|
|
280
|
-
|
|
281
|
-
|
|
111
|
+
if (!mod.modResults.contents.includes('dependencySubstitution')) {
|
|
112
|
+
const MAPS_VER = mapboxMapsVersion || '11.11.0';
|
|
113
|
+
const COMMON_VER = '24.11.3';
|
|
114
|
+
mod.modResults.contents += `
|
|
115
|
+
configurations.all {
|
|
116
|
+
resolutionStrategy {
|
|
117
|
+
dependencySubstitution {
|
|
118
|
+
substitute module('com.mapbox.maps:android') using module('com.mapbox.maps:android-ndk27:${MAPS_VER}')
|
|
119
|
+
substitute module('com.mapbox.maps:android-core') using module('com.mapbox.maps:android-core-ndk27:${MAPS_VER}')
|
|
120
|
+
substitute module('com.mapbox.maps:base') using module('com.mapbox.maps:base-ndk27:${MAPS_VER}')
|
|
121
|
+
substitute module('com.mapbox.common:common') using module('com.mapbox.common:common-ndk27:${COMMON_VER}')
|
|
122
|
+
substitute module('com.mapbox.module:maps-telemetry') using module('com.mapbox.module:maps-telemetry-ndk27:${MAPS_VER}')
|
|
123
|
+
substitute module('com.mapbox.plugin:maps-attribution') using module('com.mapbox.plugin:maps-attribution-ndk27:${MAPS_VER}')
|
|
124
|
+
substitute module('com.mapbox.plugin:maps-scalebar') using module('com.mapbox.plugin:maps-scalebar-ndk27:${MAPS_VER}')
|
|
125
|
+
substitute module('com.mapbox.plugin:maps-gestures') using module('com.mapbox.plugin:maps-gestures-ndk27:${MAPS_VER}')
|
|
126
|
+
substitute module('com.mapbox.plugin:maps-logo') using module('com.mapbox.plugin:maps-logo-ndk27:${MAPS_VER}')
|
|
127
|
+
substitute module('com.mapbox.plugin:maps-compass') using module('com.mapbox.plugin:maps-compass-ndk27:${MAPS_VER}')
|
|
128
|
+
substitute module('com.mapbox.plugin:maps-lifecycle') using module('com.mapbox.plugin:maps-lifecycle-ndk27:${MAPS_VER}')
|
|
129
|
+
substitute module('com.mapbox.plugin:maps-animation') using module('com.mapbox.plugin:maps-animation-ndk27:${MAPS_VER}')
|
|
130
|
+
substitute module('com.mapbox.plugin:maps-overlay') using module('com.mapbox.plugin:maps-overlay-ndk27:${MAPS_VER}')
|
|
131
|
+
substitute module('com.mapbox.plugin:maps-annotation') using module('com.mapbox.plugin:maps-annotation-ndk27:${MAPS_VER}')
|
|
132
|
+
substitute module('com.mapbox.plugin:maps-locationcomponent') using module('com.mapbox.plugin:maps-locationcomponent-ndk27:${MAPS_VER}')
|
|
133
|
+
substitute module('com.mapbox.plugin:maps-viewport') using module('com.mapbox.plugin:maps-viewport-ndk27:${MAPS_VER}')
|
|
134
|
+
substitute module('com.mapbox.extension:maps-localization') using module('com.mapbox.extension:maps-localization-ndk27:${MAPS_VER}')
|
|
135
|
+
substitute module('com.mapbox.extension:maps-style') using module('com.mapbox.extension:maps-style-ndk27:${MAPS_VER}')
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
282
141
|
|
|
283
|
-
|
|
284
|
-
|
|
142
|
+
// Android color overrides for Mapbox resource colors (route line, banner, etc.)
|
|
143
|
+
if (Object.keys(androidColorOverrides).length > 0) {
|
|
144
|
+
const resDir = path.join(
|
|
145
|
+
mod.modRequest?.platformProjectRoot || '',
|
|
146
|
+
'app', 'src', 'main', 'res', 'values'
|
|
147
|
+
);
|
|
148
|
+
try {
|
|
149
|
+
fs.mkdirSync(resDir, { recursive: true });
|
|
150
|
+
const colorEntries = Object.entries(androidColorOverrides)
|
|
151
|
+
.map(([name, value]) => ` <color name="${name}">${value}</color>`)
|
|
152
|
+
.join('\n');
|
|
153
|
+
const xmlContent = `<?xml version="1.0" encoding="utf-8"?>\n<resources>\n${colorEntries}\n</resources>\n`;
|
|
154
|
+
fs.writeFileSync(path.join(resDir, 'mapbox_color_overrides.xml'), xmlContent);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
// Ignore — resDir may not exist at plugin resolution time
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function addAndroidPermissions(mod, accessToken) {
|
|
162
|
+
const manifest = mod.modResults.manifest;
|
|
163
|
+
const application = manifest.application[0];
|
|
164
|
+
|
|
165
|
+
if (!application['meta-data']) application['meta-data'] = [];
|
|
166
|
+
const existingToken = application['meta-data'].find(
|
|
167
|
+
(item) => item['$']['android:name'] === 'com.mapbox.token'
|
|
168
|
+
);
|
|
169
|
+
if (!existingToken) {
|
|
170
|
+
application['meta-data'].push({
|
|
171
|
+
$: { 'android:name': 'com.mapbox.token', 'android:value': accessToken },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const requiredPermissions = [
|
|
176
|
+
'android.permission.ACCESS_FINE_LOCATION',
|
|
177
|
+
'android.permission.ACCESS_COARSE_LOCATION',
|
|
178
|
+
'android.permission.FOREGROUND_SERVICE',
|
|
179
|
+
'android.permission.FOREGROUND_SERVICE_LOCATION',
|
|
180
|
+
'android.permission.POST_NOTIFICATIONS',
|
|
181
|
+
];
|
|
182
|
+
if (!manifest['uses-permission']) manifest['uses-permission'] = [];
|
|
183
|
+
for (const perm of requiredPermissions) {
|
|
184
|
+
if (!manifest['uses-permission'].find((p) => p['$']['android:name'] === perm)) {
|
|
185
|
+
manifest['uses-permission'].push({ $: { 'android:name': perm } });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!manifest.service) manifest.service = [];
|
|
190
|
+
const svcName = 'com.mapbox.navigation.core.trip.service.NavigationNotificationService';
|
|
191
|
+
if (!manifest.service.find((s) => s['$']['android:name'] === svcName)) {
|
|
192
|
+
manifest.service.push({
|
|
193
|
+
$: {
|
|
194
|
+
'android:name': svcName,
|
|
195
|
+
'android:foregroundServiceType': 'location',
|
|
196
|
+
'android:exported': 'false',
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
285
201
|
|
|
286
202
|
module.exports = withMapboxNavigation;
|