@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.
@@ -0,0 +1,118 @@
1
+ #!/bin/bash
2
+ # ─────────────────────────────────────────────────────────────────────────────
3
+ # build-xcframeworks.sh
4
+ # Builds xcframeworks for the ExpoMapboxNavigation module.
5
+ #
6
+ # PREREQUISITES:
7
+ # 1. macOS + Xcode 16+
8
+ # 2. ~/.netrc configured with your Mapbox Downloads token:
9
+ # machine api.mapbox.com
10
+ # login mapbox
11
+ # password sk.your_downloads_token
12
+ # 3. Swift Package Manager available (comes with Xcode)
13
+ # 4. Scipio installed (https://github.com/giginet/Scipio):
14
+ # swift package --disable-sandbox experimental-publish-xcbundles
15
+ # OR install via Homebrew: brew install giginet/scipio/scipio
16
+ #
17
+ # This script builds MapboxNavigationCore and MapboxNavigationUIKit
18
+ # (and their dependencies) as xcframeworks and copies them into the
19
+ # ios/Frameworks/ directory, which is referenced by ExpoMapboxNavigation.podspec.
20
+ #
21
+ # Based on the approach by youssefhenna/expo-mapbox-navigation:
22
+ # https://github.com/uju777/expo-mapbox-navigation#getting-the-xcframework-files
23
+ # ─────────────────────────────────────────────────────────────────────────────
24
+
25
+ set -e
26
+
27
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ MODULE_ROOT="$(dirname "$SCRIPT_DIR")"
29
+ FRAMEWORKS_DIR="$SCRIPT_DIR/Frameworks"
30
+
31
+ # ── Config ────────────────────────────────────────────────────────────────────
32
+ MAPBOX_NAV_VERSION="3.25.0"
33
+ MAPBOX_MAPS_VERSION="11.25.0"
34
+ MAPBOX_COMMON_VERSION="24.25.0"
35
+ MAPBOX_NAV_NATIVE_VERSION="324.25.0"
36
+
37
+ echo "🔧 Building xcframeworks for Mapbox Navigation SDK v$MAPBOX_NAV_VERSION"
38
+ echo " Output: $FRAMEWORKS_DIR"
39
+ echo ""
40
+
41
+ # ── Step 1: Clone mapbox-navigation-ios ──────────────────────────────────────
42
+ TMPDIR=$(mktemp -d)
43
+ echo "📦 Cloning mapbox-navigation-ios v$MAPBOX_NAV_VERSION..."
44
+ git clone --branch "v$MAPBOX_NAV_VERSION" --depth 1 \
45
+ https://github.com/mapbox/mapbox-navigation-ios.git \
46
+ "$TMPDIR/mapbox-navigation-ios"
47
+
48
+ cd "$TMPDIR/mapbox-navigation-ios"
49
+
50
+ # ── Step 2: Replace Package.swift with the modified version ──────────────────
51
+ echo "📝 Patching Package.swift..."
52
+ cp "$SCRIPT_DIR/Package.swift" Package.swift
53
+
54
+ # ── Step 3: Get the correct navNative checksum ───────────────────────────────
55
+ echo "🔍 Resolving navNative checksum (this may take a moment)..."
56
+ # Run swift build once to get the correct checksum — it will fail and print it
57
+ CHECKSUM=$(swift build -c release 2>&1 | grep -oE '"[a-f0-9]{64}"' | head -1 | tr -d '"' || true)
58
+
59
+ if [ -z "$CHECKSUM" ]; then
60
+ echo "⚠️ Could not auto-detect checksum. Please run manually and update Package.swift."
61
+ else
62
+ echo " Checksum: $CHECKSUM"
63
+ sed -i '' "s/placeholder_run_swift_build_to_get_real_checksum/$CHECKSUM/g" Package.swift
64
+ fi
65
+
66
+ # ── Step 4: Build xcframeworks with Scipio ────────────────────────────────────
67
+ echo ""
68
+ echo "🏗️ Building xcframeworks with Scipio..."
69
+ echo " This will take 10-30 minutes on first run."
70
+ echo ""
71
+
72
+ # Clone Scipio inside the navigation-ios repo
73
+ git clone --depth 1 https://github.com/giginet/Scipio.git Scipio
74
+ cd Scipio
75
+ swift build -c release
76
+
77
+ # Build the xcframeworks
78
+ cd "$TMPDIR/mapbox-navigation-ios"
79
+ Scipio/.build/release/scipio create ./ -f \
80
+ --platforms iOS \
81
+ --only-use-versions-from-resolved-file \
82
+ --enable-library-evolution \
83
+ --support-simulators \
84
+ --embed-debug-symbols \
85
+ --verbose
86
+
87
+ # ── Step 5: Copy to module ────────────────────────────────────────────────────
88
+ echo ""
89
+ echo "📋 Copying xcframeworks to $FRAMEWORKS_DIR..."
90
+ mkdir -p "$FRAMEWORKS_DIR"
91
+
92
+ BUILT_FRAMEWORKS="$TMPDIR/mapbox-navigation-ios/XCFrameworks"
93
+
94
+ # Copy the frameworks we need (others like MapboxMaps come from @rnmapbox/maps)
95
+ NEEDED_FRAMEWORKS=(
96
+ "MapboxNavigationCore"
97
+ "MapboxNavigationUIKit"
98
+ "MapboxNavigationNative"
99
+ "MapboxDirections"
100
+ "_MapboxNavigationHelpers"
101
+ )
102
+
103
+ for fw in "${NEEDED_FRAMEWORKS[@]}"; do
104
+ if [ -d "$BUILT_FRAMEWORKS/$fw.xcframework" ]; then
105
+ echo " ✅ $fw.xcframework"
106
+ cp -R "$BUILT_FRAMEWORKS/$fw.xcframework" "$FRAMEWORKS_DIR/"
107
+ else
108
+ echo " ❌ $fw.xcframework not found in $BUILT_FRAMEWORKS"
109
+ fi
110
+ done
111
+
112
+ # ── Cleanup ────────────────────────────────────────────────────────────────────
113
+ cd /
114
+ rm -rf "$TMPDIR"
115
+
116
+ echo ""
117
+ echo "✅ Done! xcframeworks are in $FRAMEWORKS_DIR"
118
+ echo " Commit the Frameworks/ directory and publish the package."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacques_gordon/expo-mapbox-navigation",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
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",
@@ -1,201 +1,54 @@
1
- const { withAppBuildGradle, withProjectBuildGradle, withAndroidManifest, withInfoPlist, withDangerousMod, withXcodeProject } = require('@expo/config-plugins');
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 = MAPBOX_MAPS_MIN_VERSION,
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 in the plugin config.\n' +
19
- 'Add it to your app.json plugins array:\n' +
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 builds.\n' +
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
- const [major, minor] = mapboxMapsVersion.split('.').map(Number);
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
- let contents = mod.modResults.contents;
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
- const manifest = mod.modResults.manifest;
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
- // ── Android: color overrides ─────────────────────────────────────────────
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
- if (!mod.modResults.NSLocationWhenInUseUsageDescription) {
192
- mod.modResults.NSLocationWhenInUseUsageDescription =
193
- 'Your location is used for turn-by-turn navigation.';
194
- }
195
- if (!mod.modResults.NSLocationAlwaysAndWhenInUseUsageDescription) {
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
- // Swift Package Manager (used by ExpoMapboxNavigation.podspec's
210
- // spm_dependency() to fetch the Mapbox Navigation SDK) authenticates
211
- // against api.mapbox.com using a machine entry in the user's ~/.netrc
212
- // file this is Mapbox's documented mechanism for private SPM package
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
- // ── iOS: Add Mapbox Navigation SPM to Xcode project main target ───────────
236
- //
237
- // WHY NOT spm_dependency() IN THE PODSPEC:
238
- // Using spm_dependency() in ExpoMapboxNavigation.podspec causes
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
- // Check if already added to avoid duplicates on repeated prebuild
254
- const existingPackages = xcodeProject.pbxXCRemoteSwiftPackageReferenceSection
255
- ? Object.values(xcodeProject.pbxXCRemoteSwiftPackageReferenceSection())
256
- : [];
257
- const alreadyAdded = existingPackages.some(
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
- if (!alreadyAdded) {
262
- // Add the SPM package reference to the project
263
- const packageRef = xcodeProject.addSwiftPackage(
264
- MAPBOX_NAV_REPO,
265
- {
266
- repositoryURL: MAPBOX_NAV_REPO,
267
- requirement: {
268
- kind: 'upToNextMajorVersion',
269
- minimumVersion: MAPBOX_NAV_VERSION,
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
- return mod;
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
- return config;
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;