@baeckerherz/expo-mapbox-navigation 0.1.0

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,377 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+ import CoreLocation
4
+ import MapboxNavigationUIKit
5
+ import MapboxNavigationCore
6
+ import MapboxDirections
7
+ import MapboxMaps
8
+
9
+ // MARK: - Custom Style that applies React Native color overrides
10
+
11
+ class CustomDayStyle: StandardDayStyle {
12
+ var customMapStyleURL: URL?
13
+ var customAccentColor: UIColor?
14
+ var customRouteColor: UIColor?
15
+ var customBannerBackgroundColor: UIColor?
16
+ var customBannerTextColor: UIColor?
17
+
18
+ override func apply() {
19
+ super.apply()
20
+
21
+ if let url = customMapStyleURL {
22
+ mapStyleURL = url
23
+ previewMapStyleURL = url
24
+ }
25
+
26
+ let tc = UITraitCollection(userInterfaceIdiom: .phone)
27
+
28
+ if let accent = customAccentColor {
29
+ tintColor = accent
30
+ FloatingButton.appearance(for: tc).tintColor = accent
31
+ }
32
+
33
+ if let bannerBg = customBannerBackgroundColor {
34
+ TopBannerView.appearance(for: tc).backgroundColor = bannerBg
35
+ InstructionsBannerView.appearance(for: tc).backgroundColor = bannerBg
36
+ }
37
+
38
+ if let bannerText = customBannerTextColor {
39
+ PrimaryLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).normalTextColor = bannerText
40
+ SecondaryLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).normalTextColor = bannerText
41
+ DistanceLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).valueTextColor = bannerText
42
+ DistanceLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).unitTextColor = bannerText.withAlphaComponent(0.7)
43
+ }
44
+
45
+ if let route = customRouteColor ?? customAccentColor {
46
+ Task { @MainActor in
47
+ NavigationMapView.appearance(for: tc).routeCasingColor = route.withAlphaComponent(0.8)
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ class CustomNightStyle: StandardNightStyle {
54
+ var customMapStyleURL: URL?
55
+ var customAccentColor: UIColor?
56
+ var customRouteColor: UIColor?
57
+ var customBannerBackgroundColor: UIColor?
58
+ var customBannerTextColor: UIColor?
59
+
60
+ override func apply() {
61
+ super.apply()
62
+
63
+ if let url = customMapStyleURL {
64
+ mapStyleURL = url
65
+ previewMapStyleURL = url
66
+ }
67
+
68
+ let tc = UITraitCollection(userInterfaceIdiom: .phone)
69
+
70
+ if let accent = customAccentColor {
71
+ tintColor = accent
72
+ FloatingButton.appearance(for: tc).tintColor = accent
73
+ }
74
+
75
+ if let bannerBg = customBannerBackgroundColor {
76
+ TopBannerView.appearance(for: tc).backgroundColor = bannerBg
77
+ InstructionsBannerView.appearance(for: tc).backgroundColor = bannerBg
78
+ }
79
+
80
+ if let bannerText = customBannerTextColor {
81
+ PrimaryLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).normalTextColor = bannerText
82
+ SecondaryLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).normalTextColor = bannerText
83
+ DistanceLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).valueTextColor = bannerText
84
+ DistanceLabel.appearance(for: tc, whenContainedInInstancesOf: [InstructionsBannerView.self]).unitTextColor = bannerText.withAlphaComponent(0.7)
85
+ }
86
+
87
+ if let route = customRouteColor ?? customAccentColor {
88
+ Task { @MainActor in
89
+ NavigationMapView.appearance(for: tc).routeCasingColor = route.withAlphaComponent(0.8)
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ // MARK: - Hex color parsing
96
+
97
+ extension UIColor {
98
+ convenience init?(hex: String) {
99
+ var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
100
+ hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
101
+ guard hexSanitized.count == 6, let rgb = UInt64(hexSanitized, radix: 16) else {
102
+ return nil
103
+ }
104
+ self.init(
105
+ red: CGFloat((rgb >> 16) & 0xFF) / 255.0,
106
+ green: CGFloat((rgb >> 8) & 0xFF) / 255.0,
107
+ blue: CGFloat(rgb & 0xFF) / 255.0,
108
+ alpha: 1.0
109
+ )
110
+ }
111
+ }
112
+
113
+ // MARK: - ExpoMapboxNavigationView
114
+
115
+ class ExpoMapboxNavigationView: ExpoView {
116
+
117
+ // MARK: Event dispatchers
118
+
119
+ let onRouteProgressChanged = EventDispatcher()
120
+ let onCancelNavigation = EventDispatcher()
121
+ let onWaypointArrival = EventDispatcher()
122
+ let onFinalDestinationArrival = EventDispatcher()
123
+ let onRouteChanged = EventDispatcher()
124
+ let onUserOffRoute = EventDispatcher()
125
+ let onError = EventDispatcher()
126
+
127
+ // MARK: Route configuration
128
+
129
+ private var coordinates: [CLLocationCoordinate2D] = []
130
+ var waypointIndices: [Int]?
131
+ var navigationLocale: String?
132
+ var routeProfile: String?
133
+ var isMuted: Bool = false
134
+
135
+ // MARK: Appearance configuration
136
+
137
+ var mapStyleURL: String?
138
+ var themeMode: String?
139
+ var accentColorHex: String?
140
+ var routeColorHex: String?
141
+ var bannerBackgroundColorHex: String?
142
+ var bannerTextColorHex: String?
143
+
144
+ // MARK: Navigation state
145
+
146
+ private var navigationViewController: NavigationViewController?
147
+ private var hasStartedNavigation = false
148
+
149
+ // MARK: Init
150
+
151
+ required init(appContext: AppContext? = nil) {
152
+ super.init(appContext: appContext)
153
+ clipsToBounds = true
154
+ }
155
+
156
+ // MARK: Props
157
+
158
+ func setCoordinates(_ raw: [[String: Double]]) {
159
+ coordinates = raw.compactMap { dict in
160
+ guard let lat = dict["latitude"], let lng = dict["longitude"] else {
161
+ return nil
162
+ }
163
+ return CLLocationCoordinate2D(latitude: lat, longitude: lng)
164
+ }
165
+ startNavigationIfReady()
166
+ }
167
+
168
+ // MARK: Layout
169
+
170
+ override func layoutSubviews() {
171
+ super.layoutSubviews()
172
+ navigationViewController?.view.frame = bounds
173
+ }
174
+
175
+ // MARK: Style builder
176
+
177
+ private func buildStyles() -> [Style] {
178
+ let accentColor = accentColorHex.flatMap { UIColor(hex: $0) }
179
+ let routeColor = routeColorHex.flatMap { UIColor(hex: $0) }
180
+ let bannerBg = bannerBackgroundColorHex.flatMap { UIColor(hex: $0) }
181
+ let bannerText = bannerTextColorHex.flatMap { UIColor(hex: $0) }
182
+ let customURL = mapStyleURL.flatMap { URL(string: $0) }
183
+
184
+ let dayStyle = CustomDayStyle()
185
+ dayStyle.customMapStyleURL = customURL
186
+ dayStyle.customAccentColor = accentColor
187
+ dayStyle.customRouteColor = routeColor
188
+ dayStyle.customBannerBackgroundColor = bannerBg
189
+ dayStyle.customBannerTextColor = bannerText
190
+
191
+ let nightStyle = CustomNightStyle()
192
+ nightStyle.customMapStyleURL = customURL
193
+ nightStyle.customAccentColor = accentColor
194
+ nightStyle.customRouteColor = routeColor
195
+ nightStyle.customBannerBackgroundColor = bannerBg
196
+ nightStyle.customBannerTextColor = bannerText
197
+
198
+ switch themeMode {
199
+ case "night":
200
+ return [nightStyle]
201
+ case "day":
202
+ return [dayStyle]
203
+ default:
204
+ // "auto" or nil -- provide both for automatic day/night switching
205
+ return [dayStyle, nightStyle]
206
+ }
207
+ }
208
+
209
+ // MARK: Navigation lifecycle
210
+
211
+ private func startNavigationIfReady() {
212
+ guard coordinates.count >= 2, !hasStartedNavigation else { return }
213
+ guard bounds.width > 0 else {
214
+ DispatchQueue.main.async { [weak self] in
215
+ self?.setNeedsLayout()
216
+ self?.layoutIfNeeded()
217
+ self?.startNavigationIfReady()
218
+ }
219
+ return
220
+ }
221
+
222
+ hasStartedNavigation = true
223
+
224
+ // Build waypoints
225
+ let waypoints = coordinates.enumerated().map { index, coord -> Waypoint in
226
+ var wp = Waypoint(coordinate: coord)
227
+ if let indices = waypointIndices {
228
+ wp.separatesLegs = indices.contains(index)
229
+ }
230
+ return wp
231
+ }
232
+
233
+ // Locale and measurement system
234
+ let resolvedLocale: Locale = {
235
+ if let code = navigationLocale {
236
+ return Locale(identifier: code)
237
+ }
238
+ return Locale.current
239
+ }()
240
+ let useMetric = !resolvedLocale.identifier.hasPrefix("en_US")
241
+
242
+ // Route options
243
+ var routeOptions = NavigationRouteOptions(waypoints: waypoints)
244
+ routeOptions.locale = resolvedLocale
245
+ routeOptions.distanceMeasurementSystem = useMetric ? .metric : .imperial
246
+
247
+ if let profile = routeProfile {
248
+ routeOptions.profileIdentifier = MapboxDirections.ProfileIdentifier(rawValue: profile)
249
+ }
250
+
251
+ // Core config
252
+ var coreConfig = CoreConfig()
253
+ coreConfig.locale = resolvedLocale
254
+ coreConfig.unitOfMeasurement = useMetric ? .metric : .imperial
255
+
256
+ let provider = MapboxNavigationProvider(coreConfig: coreConfig)
257
+ let mapboxNavigation = provider.mapboxNavigation
258
+
259
+ Task { @MainActor in
260
+ do {
261
+ let result = try await mapboxNavigation.routingProvider().calculateRoutes(
262
+ options: routeOptions
263
+ ).value
264
+
265
+ let navOptions = NavigationOptions(
266
+ mapboxNavigation: mapboxNavigation,
267
+ voiceController: provider.routeVoiceController,
268
+ eventsManager: provider.eventsManager(),
269
+ styles: self.buildStyles()
270
+ )
271
+
272
+ let navVC = NavigationViewController(
273
+ navigationRoutes: result,
274
+ navigationOptions: navOptions
275
+ )
276
+ navVC.delegate = self
277
+
278
+ // Apply mute setting
279
+ if self.isMuted {
280
+ provider.routeVoiceController.speechSynthesizer.muted = true
281
+ }
282
+
283
+ // Embed navigation view
284
+ navVC.view.frame = self.bounds
285
+ navVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
286
+ self.addSubview(navVC.view)
287
+ self.navigationViewController = navVC
288
+
289
+ if let parentVC = self.findViewController() {
290
+ parentVC.addChild(navVC)
291
+ navVC.didMove(toParent: parentVC)
292
+ }
293
+ } catch {
294
+ self.onError(["message": "Failed to calculate route: \(error.localizedDescription)"])
295
+ }
296
+ }
297
+ }
298
+
299
+ private func findViewController() -> UIViewController? {
300
+ var responder: UIResponder? = self
301
+ while let next = responder?.next {
302
+ if let vc = next as? UIViewController { return vc }
303
+ responder = next
304
+ }
305
+ return nil
306
+ }
307
+
308
+ // MARK: Cleanup
309
+
310
+ deinit {
311
+ navigationViewController?.willMove(toParent: nil)
312
+ navigationViewController?.view.removeFromSuperview()
313
+ navigationViewController?.removeFromParent()
314
+ }
315
+ }
316
+
317
+ // MARK: - NavigationViewControllerDelegate
318
+
319
+ extension ExpoMapboxNavigationView: NavigationViewControllerDelegate {
320
+
321
+ func navigationViewController(
322
+ _ navigationViewController: NavigationViewController,
323
+ didUpdate progress: RouteProgress,
324
+ with location: CLLocation,
325
+ rawLocation: CLLocation
326
+ ) {
327
+ onRouteProgressChanged([
328
+ "distanceRemaining": progress.distanceRemaining,
329
+ "durationRemaining": progress.durationRemaining,
330
+ "distanceTraveled": progress.distanceTraveled,
331
+ "fractionTraveled": progress.fractionTraveled,
332
+ ])
333
+ }
334
+
335
+ func navigationViewControllerDidDismiss(
336
+ _ navigationViewController: NavigationViewController,
337
+ byCanceling canceled: Bool
338
+ ) {
339
+ if canceled {
340
+ onCancelNavigation()
341
+ } else {
342
+ onFinalDestinationArrival()
343
+ }
344
+ }
345
+
346
+ func navigationViewController(
347
+ _ navigationViewController: NavigationViewController,
348
+ didArriveAt waypoint: Waypoint
349
+ ) -> Bool {
350
+ let index = coordinates.firstIndex { coord in
351
+ coord.latitude == waypoint.coordinate.latitude
352
+ && coord.longitude == waypoint.coordinate.longitude
353
+ } ?? -1
354
+
355
+ if index == coordinates.count - 1 {
356
+ onFinalDestinationArrival()
357
+ } else {
358
+ onWaypointArrival(["waypointIndex": index])
359
+ }
360
+ return true
361
+ }
362
+
363
+ func navigationViewController(
364
+ _ navigationViewController: NavigationViewController,
365
+ didRerouteAlong route: Route
366
+ ) {
367
+ onRouteChanged()
368
+ }
369
+
370
+ func navigationViewController(
371
+ _ navigationViewController: NavigationViewController,
372
+ shouldRerouteFrom location: CLLocation
373
+ ) -> Bool {
374
+ onUserOffRoute()
375
+ return true
376
+ }
377
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@baeckerherz/expo-mapbox-navigation",
3
+ "version": "0.1.0",
4
+ "description": "Expo module wrapping Mapbox Navigation SDK v3 for iOS and Android",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "expo": {
8
+ "plugin": "./plugin/build/index.js"
9
+ },
10
+ "scripts": {
11
+ "build:plugin": "cd plugin && tsc",
12
+ "build": "tsc && yarn build:plugin",
13
+ "clean": "rm -rf build plugin/build",
14
+ "prepublishOnly": "yarn build:plugin",
15
+ "lint": "eslint src/"
16
+ },
17
+ "keywords": [
18
+ "expo",
19
+ "react-native",
20
+ "mapbox",
21
+ "navigation",
22
+ "turn-by-turn",
23
+ "driving",
24
+ "mapbox-navigation-sdk",
25
+ "mapbox-navigation-v3"
26
+ ],
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/baeckerherz/expo-mapbox-navigation"
31
+ },
32
+ "files": [
33
+ "src/",
34
+ "ios/",
35
+ "android/",
36
+ "plugin/build/",
37
+ "plugin/src/",
38
+ "expo-module.config.json",
39
+ "README.md"
40
+ ],
41
+ "peerDependencies": {
42
+ "expo": ">=51.0.0",
43
+ "react": ">=18.0.0",
44
+ "react-native": ">=0.74.0"
45
+ },
46
+ "devDependencies": {
47
+ "expo-modules-core": "^2.5.0",
48
+ "expo-module-scripts": "^3.5.2",
49
+ "typescript": "^5.0.0",
50
+ "@expo/config-plugins": "^8.0.0"
51
+ }
52
+ }
@@ -0,0 +1,11 @@
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
+ interface PluginConfig {
3
+ /** Mapbox public access token (pk.xxx). Required for the native SDK. */
4
+ mapboxAccessToken: string;
5
+ /** Mapbox secret/download token (sk.xxx). Required for SPM and Maven auth. */
6
+ mapboxSecretToken?: string;
7
+ /** Mapbox Navigation SDK version. Default: "3.5.0" */
8
+ navigationSdkVersion?: string;
9
+ }
10
+ declare const _default: ConfigPlugin<PluginConfig>;
11
+ export default _default;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("@expo/config-plugins");
4
+ const withMapboxNavSPM_1 = require("./withMapboxNavSPM");
5
+ const withMapboxNavPodfile_1 = require("./withMapboxNavPodfile");
6
+ const withMapboxNavGradle_1 = require("./withMapboxNavGradle");
7
+ const withMapboxNavigation = (config, { mapboxAccessToken, mapboxSecretToken, navigationSdkVersion = "3.5.0", }) => {
8
+ if (!mapboxAccessToken) {
9
+ throw new Error("[@baeckerherz/expo-mapbox-navigation] mapboxAccessToken is required.");
10
+ }
11
+ // Inject MBXAccessToken and required background modes into Info.plist
12
+ if (!config.ios)
13
+ config.ios = {};
14
+ if (!config.ios.infoPlist)
15
+ config.ios.infoPlist = {};
16
+ config.ios.infoPlist.MBXAccessToken = mapboxAccessToken;
17
+ config.ios.infoPlist.UIBackgroundModes = [
18
+ ...(config.ios.infoPlist.UIBackgroundModes || []),
19
+ "audio",
20
+ "location",
21
+ ];
22
+ config.ios.infoPlist.NSLocationWhenInUseUsageDescription =
23
+ config.ios.infoPlist.NSLocationWhenInUseUsageDescription ||
24
+ "This app needs your location for turn-by-turn navigation.";
25
+ config.ios.infoPlist.NSLocationAlwaysAndWhenInUseUsageDescription =
26
+ config.ios.infoPlist.NSLocationAlwaysAndWhenInUseUsageDescription ||
27
+ "This app needs your location for turn-by-turn navigation, including in the background.";
28
+ // iOS: Inject SPM package references
29
+ config = (0, withMapboxNavSPM_1.withMapboxNavSPM)(config, { navigationSdkVersion });
30
+ // iOS: Patch Podfile for SPM framework visibility
31
+ config = (0, withMapboxNavPodfile_1.withMapboxNavPodfile)(config);
32
+ // Android: Add Mapbox Maven repository
33
+ config = (0, withMapboxNavGradle_1.withMapboxNavGradle)(config);
34
+ return config;
35
+ };
36
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(withMapboxNavigation, "@baeckerherz/expo-mapbox-navigation", "0.1.0");
@@ -0,0 +1,6 @@
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
+ /**
3
+ * Adds the authenticated Mapbox Maven repository to the Android
4
+ * project's root build.gradle for Navigation SDK v3 downloads.
5
+ */
6
+ export declare const withMapboxNavGradle: ConfigPlugin;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withMapboxNavGradle = void 0;
4
+ const config_plugins_1 = require("@expo/config-plugins");
5
+ /**
6
+ * Adds the authenticated Mapbox Maven repository to the Android
7
+ * project's root build.gradle for Navigation SDK v3 downloads.
8
+ */
9
+ const withMapboxNavGradle = (config) => {
10
+ return (0, config_plugins_1.withProjectBuildGradle)(config, (config) => {
11
+ let contents = config.modResults.contents;
12
+ if (!contents.includes("api.mapbox.com/downloads/v2/releases/maven")) {
13
+ const mapboxMaven = `
14
+ // @baeckerherz/expo-mapbox-navigation: Mapbox Navigation SDK Maven repository
15
+ maven {
16
+ url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
17
+ authentication {
18
+ create<BasicAuthentication>("basic")
19
+ }
20
+ credentials {
21
+ username = "mapbox"
22
+ password = providers.gradleProperty("MAPBOX_DOWNLOADS_TOKEN").getOrElse("")
23
+ }
24
+ }`;
25
+ contents = contents.replace(/allprojects\s*\{\s*repositories\s*\{/, `allprojects {\n repositories {${mapboxMaven}`);
26
+ }
27
+ config.modResults.contents = contents;
28
+ return config;
29
+ });
30
+ };
31
+ exports.withMapboxNavGradle = withMapboxNavGradle;
@@ -0,0 +1,7 @@
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
+ /**
3
+ * Patches the iOS Podfile to make SPM framework products visible
4
+ * to the ExpoMapboxNavigation pod target, and sets BUILD_LIBRARY_FOR_DISTRIBUTION
5
+ * for Mapbox-related pods.
6
+ */
7
+ export declare const withMapboxNavPodfile: ConfigPlugin;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.withMapboxNavPodfile = void 0;
37
+ const config_plugins_1 = require("@expo/config-plugins");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ /**
41
+ * Patches the iOS Podfile to make SPM framework products visible
42
+ * to the ExpoMapboxNavigation pod target, and sets BUILD_LIBRARY_FOR_DISTRIBUTION
43
+ * for Mapbox-related pods.
44
+ */
45
+ const withMapboxNavPodfile = (config) => {
46
+ return (0, config_plugins_1.withDangerousMod)(config, [
47
+ "ios",
48
+ async (config) => {
49
+ const podfilePath = path.join(config.modRequest.platformProjectRoot, "Podfile");
50
+ let contents = fs.readFileSync(podfilePath, "utf8");
51
+ const hook = `
52
+ # @baeckerherz/expo-mapbox-navigation: Make SPM frameworks visible to the pod target
53
+ installer.pods_project.targets.each do |target|
54
+ if target.name == 'ExpoMapboxNavigation'
55
+ target.build_configurations.each do |config|
56
+ shared_products = '$(BUILT_PRODUCTS_DIR)/..'
57
+ config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= ['$(inherited)']
58
+ config.build_settings['FRAMEWORK_SEARCH_PATHS'] << shared_products
59
+ config.build_settings['SWIFT_INCLUDE_PATHS'] ||= ['$(inherited)']
60
+ config.build_settings['SWIFT_INCLUDE_PATHS'] << shared_products
61
+ end
62
+ end
63
+ if target.name.start_with?('Mapbox') || target.name == 'Turf'
64
+ target.build_configurations.each do |config|
65
+ config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
66
+ end
67
+ end
68
+ end`;
69
+ if (!contents.includes("@baeckerherz/expo-mapbox-navigation: Make SPM frameworks")) {
70
+ if (contents.includes("post_install do |installer|")) {
71
+ contents = contents.replace("post_install do |installer|", `post_install do |installer|${hook}`);
72
+ }
73
+ }
74
+ fs.writeFileSync(podfilePath, contents);
75
+ return config;
76
+ },
77
+ ]);
78
+ };
79
+ exports.withMapboxNavPodfile = withMapboxNavPodfile;
@@ -0,0 +1,13 @@
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
+ interface SPMConfig {
3
+ navigationSdkVersion: string;
4
+ }
5
+ /**
6
+ * Injects Mapbox Navigation SDK v3 as a Swift Package Manager dependency
7
+ * into the Xcode project's .pbxproj. This avoids vendoring .xcframework files.
8
+ *
9
+ * Adds XCRemoteSwiftPackageReference, XCSwiftPackageProductDependency,
10
+ * and links MapboxNavigationUIKit + MapboxNavigationCore to the main target.
11
+ */
12
+ export declare const withMapboxNavSPM: ConfigPlugin<SPMConfig>;
13
+ export {};