@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,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withMapboxNavSPM = void 0;
4
+ const config_plugins_1 = require("@expo/config-plugins");
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
+ const withMapboxNavSPM = (config, { navigationSdkVersion }) => {
13
+ return (0, config_plugins_1.withXcodeProject)(config, (config) => {
14
+ const xcodeProject = config.modResults;
15
+ const repoName = "mapbox-navigation-ios";
16
+ const repoUrl = "https://github.com/mapbox/mapbox-navigation-ios.git";
17
+ const products = ["MapboxNavigationUIKit", "MapboxNavigationCore"];
18
+ if (!xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"]) {
19
+ xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"] = {};
20
+ }
21
+ if (!xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"]) {
22
+ xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"] =
23
+ {};
24
+ }
25
+ // Idempotent: skip if already added
26
+ const existingRefs = Object.values(xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"]);
27
+ const alreadyAdded = existingRefs.some((ref) => typeof ref === "object" && ref.repositoryURL === repoUrl);
28
+ if (alreadyAdded)
29
+ return config;
30
+ // XCRemoteSwiftPackageReference
31
+ const packageRefUuid = xcodeProject.generateUuid();
32
+ xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"][packageRefUuid] = {
33
+ isa: "XCRemoteSwiftPackageReference",
34
+ repositoryURL: repoUrl,
35
+ requirement: { kind: "exactVersion", version: navigationSdkVersion },
36
+ };
37
+ xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"][`${packageRefUuid}_comment`] = `XCRemoteSwiftPackageReference "${repoName}"`;
38
+ // Add to PBXProject packageReferences
39
+ const projectKey = Object.keys(xcodeProject.hash.project.objects["PBXProject"]).find((key) => !key.includes("_comment"));
40
+ if (projectKey) {
41
+ const project = xcodeProject.hash.project.objects["PBXProject"][projectKey];
42
+ if (!project.packageReferences)
43
+ project.packageReferences = [];
44
+ project.packageReferences.push(packageRefUuid);
45
+ }
46
+ // For each product: add dependency, build file, and link
47
+ for (const productName of products) {
48
+ const productDepUuid = xcodeProject.generateUuid();
49
+ xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"][productDepUuid] = {
50
+ isa: "XCSwiftPackageProductDependency",
51
+ package: packageRefUuid,
52
+ productName,
53
+ };
54
+ xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"][`${productDepUuid}_comment`] = productName;
55
+ const buildFileUuid = xcodeProject.generateUuid();
56
+ xcodeProject.hash.project.objects["PBXBuildFile"][buildFileUuid] = {
57
+ isa: "PBXBuildFile",
58
+ productRef: productDepUuid,
59
+ productRef_comment: productName,
60
+ };
61
+ xcodeProject.hash.project.objects["PBXBuildFile"][`${buildFileUuid}_comment`] = `${productName} in Frameworks`;
62
+ const frameworkPhaseKey = Object.keys(xcodeProject.hash.project.objects["PBXFrameworksBuildPhase"]).find((key) => !key.includes("_comment"));
63
+ if (frameworkPhaseKey) {
64
+ const phase = xcodeProject.hash.project.objects["PBXFrameworksBuildPhase"][frameworkPhaseKey];
65
+ if (!phase.files)
66
+ phase.files = [];
67
+ phase.files.push(buildFileUuid);
68
+ }
69
+ const nativeTargetKey = Object.keys(xcodeProject.hash.project.objects["PBXNativeTarget"]).find((key) => !key.includes("_comment"));
70
+ if (nativeTargetKey) {
71
+ const target = xcodeProject.hash.project.objects["PBXNativeTarget"][nativeTargetKey];
72
+ if (!target.packageProductDependencies)
73
+ target.packageProductDependencies = [];
74
+ target.packageProductDependencies.push(productDepUuid);
75
+ }
76
+ }
77
+ return config;
78
+ });
79
+ };
80
+ exports.withMapboxNavSPM = withMapboxNavSPM;
@@ -0,0 +1,63 @@
1
+ import { ConfigPlugin, createRunOncePlugin } from "@expo/config-plugins";
2
+ import { withMapboxNavSPM } from "./withMapboxNavSPM";
3
+ import { withMapboxNavPodfile } from "./withMapboxNavPodfile";
4
+ import { withMapboxNavGradle } from "./withMapboxNavGradle";
5
+
6
+ interface PluginConfig {
7
+ /** Mapbox public access token (pk.xxx). Required for the native SDK. */
8
+ mapboxAccessToken: string;
9
+
10
+ /** Mapbox secret/download token (sk.xxx). Required for SPM and Maven auth. */
11
+ mapboxSecretToken?: string;
12
+
13
+ /** Mapbox Navigation SDK version. Default: "3.5.0" */
14
+ navigationSdkVersion?: string;
15
+ }
16
+
17
+ const withMapboxNavigation: ConfigPlugin<PluginConfig> = (
18
+ config,
19
+ {
20
+ mapboxAccessToken,
21
+ mapboxSecretToken,
22
+ navigationSdkVersion = "3.5.0",
23
+ }
24
+ ) => {
25
+ if (!mapboxAccessToken) {
26
+ throw new Error(
27
+ "[@baeckerherz/expo-mapbox-navigation] mapboxAccessToken is required."
28
+ );
29
+ }
30
+
31
+ // Inject MBXAccessToken and required background modes into Info.plist
32
+ if (!config.ios) config.ios = {};
33
+ if (!config.ios.infoPlist) config.ios.infoPlist = {};
34
+ config.ios.infoPlist.MBXAccessToken = mapboxAccessToken;
35
+ config.ios.infoPlist.UIBackgroundModes = [
36
+ ...(config.ios.infoPlist.UIBackgroundModes || []),
37
+ "audio",
38
+ "location",
39
+ ];
40
+ config.ios.infoPlist.NSLocationWhenInUseUsageDescription =
41
+ config.ios.infoPlist.NSLocationWhenInUseUsageDescription ||
42
+ "This app needs your location for turn-by-turn navigation.";
43
+ config.ios.infoPlist.NSLocationAlwaysAndWhenInUseUsageDescription =
44
+ config.ios.infoPlist.NSLocationAlwaysAndWhenInUseUsageDescription ||
45
+ "This app needs your location for turn-by-turn navigation, including in the background.";
46
+
47
+ // iOS: Inject SPM package references
48
+ config = withMapboxNavSPM(config, { navigationSdkVersion });
49
+
50
+ // iOS: Patch Podfile for SPM framework visibility
51
+ config = withMapboxNavPodfile(config);
52
+
53
+ // Android: Add Mapbox Maven repository
54
+ config = withMapboxNavGradle(config);
55
+
56
+ return config;
57
+ };
58
+
59
+ export default createRunOncePlugin(
60
+ withMapboxNavigation,
61
+ "@baeckerherz/expo-mapbox-navigation",
62
+ "0.1.0"
63
+ );
@@ -0,0 +1,34 @@
1
+ import { withProjectBuildGradle, ConfigPlugin } from "@expo/config-plugins";
2
+
3
+ /**
4
+ * Adds the authenticated Mapbox Maven repository to the Android
5
+ * project's root build.gradle for Navigation SDK v3 downloads.
6
+ */
7
+ export const withMapboxNavGradle: ConfigPlugin = (config) => {
8
+ return withProjectBuildGradle(config, (config) => {
9
+ let contents = config.modResults.contents;
10
+
11
+ if (!contents.includes("api.mapbox.com/downloads/v2/releases/maven")) {
12
+ const mapboxMaven = `
13
+ // @baeckerherz/expo-mapbox-navigation: Mapbox Navigation SDK Maven repository
14
+ maven {
15
+ url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
16
+ authentication {
17
+ create<BasicAuthentication>("basic")
18
+ }
19
+ credentials {
20
+ username = "mapbox"
21
+ password = providers.gradleProperty("MAPBOX_DOWNLOADS_TOKEN").getOrElse("")
22
+ }
23
+ }`;
24
+
25
+ contents = contents.replace(
26
+ /allprojects\s*\{\s*repositories\s*\{/,
27
+ `allprojects {\n repositories {${mapboxMaven}`
28
+ );
29
+ }
30
+
31
+ config.modResults.contents = contents;
32
+ return config;
33
+ });
34
+ };
@@ -0,0 +1,56 @@
1
+ import { withDangerousMod, ConfigPlugin } from "@expo/config-plugins";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+
5
+ /**
6
+ * Patches the iOS Podfile to make SPM framework products visible
7
+ * to the ExpoMapboxNavigation pod target, and sets BUILD_LIBRARY_FOR_DISTRIBUTION
8
+ * for Mapbox-related pods.
9
+ */
10
+ export const withMapboxNavPodfile: ConfigPlugin = (config) => {
11
+ return withDangerousMod(config, [
12
+ "ios",
13
+ async (config) => {
14
+ const podfilePath = path.join(
15
+ config.modRequest.platformProjectRoot,
16
+ "Podfile"
17
+ );
18
+ let contents = fs.readFileSync(podfilePath, "utf8");
19
+
20
+ const hook = `
21
+ # @baeckerherz/expo-mapbox-navigation: Make SPM frameworks visible to the pod target
22
+ installer.pods_project.targets.each do |target|
23
+ if target.name == 'ExpoMapboxNavigation'
24
+ target.build_configurations.each do |config|
25
+ shared_products = '$(BUILT_PRODUCTS_DIR)/..'
26
+ config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= ['$(inherited)']
27
+ config.build_settings['FRAMEWORK_SEARCH_PATHS'] << shared_products
28
+ config.build_settings['SWIFT_INCLUDE_PATHS'] ||= ['$(inherited)']
29
+ config.build_settings['SWIFT_INCLUDE_PATHS'] << shared_products
30
+ end
31
+ end
32
+ if target.name.start_with?('Mapbox') || target.name == 'Turf'
33
+ target.build_configurations.each do |config|
34
+ config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
35
+ end
36
+ end
37
+ end`;
38
+
39
+ if (
40
+ !contents.includes(
41
+ "@baeckerherz/expo-mapbox-navigation: Make SPM frameworks"
42
+ )
43
+ ) {
44
+ if (contents.includes("post_install do |installer|")) {
45
+ contents = contents.replace(
46
+ "post_install do |installer|",
47
+ `post_install do |installer|${hook}`
48
+ );
49
+ }
50
+ }
51
+
52
+ fs.writeFileSync(podfilePath, contents);
53
+ return config;
54
+ },
55
+ ]);
56
+ };
@@ -0,0 +1,124 @@
1
+ import { withXcodeProject, ConfigPlugin } from "@expo/config-plugins";
2
+
3
+ interface SPMConfig {
4
+ navigationSdkVersion: string;
5
+ }
6
+
7
+ /**
8
+ * Injects Mapbox Navigation SDK v3 as a Swift Package Manager dependency
9
+ * into the Xcode project's .pbxproj. This avoids vendoring .xcframework files.
10
+ *
11
+ * Adds XCRemoteSwiftPackageReference, XCSwiftPackageProductDependency,
12
+ * and links MapboxNavigationUIKit + MapboxNavigationCore to the main target.
13
+ */
14
+ export const withMapboxNavSPM: ConfigPlugin<SPMConfig> = (
15
+ config,
16
+ { navigationSdkVersion }
17
+ ) => {
18
+ return withXcodeProject(config, (config) => {
19
+ const xcodeProject = config.modResults;
20
+
21
+ const repoName = "mapbox-navigation-ios";
22
+ const repoUrl = "https://github.com/mapbox/mapbox-navigation-ios.git";
23
+ const products = ["MapboxNavigationUIKit", "MapboxNavigationCore"];
24
+
25
+ if (!xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"]) {
26
+ xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"] = {};
27
+ }
28
+ if (
29
+ !xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"]
30
+ ) {
31
+ xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"] =
32
+ {};
33
+ }
34
+
35
+ // Idempotent: skip if already added
36
+ const existingRefs = Object.values(
37
+ xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"]
38
+ );
39
+ const alreadyAdded = existingRefs.some(
40
+ (ref: any) =>
41
+ typeof ref === "object" && ref.repositoryURL === repoUrl
42
+ );
43
+ if (alreadyAdded) return config;
44
+
45
+ // XCRemoteSwiftPackageReference
46
+ const packageRefUuid = xcodeProject.generateUuid();
47
+ xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"][
48
+ packageRefUuid
49
+ ] = {
50
+ isa: "XCRemoteSwiftPackageReference",
51
+ repositoryURL: repoUrl,
52
+ requirement: { kind: "exactVersion", version: navigationSdkVersion },
53
+ };
54
+ xcodeProject.hash.project.objects["XCRemoteSwiftPackageReference"][
55
+ `${packageRefUuid}_comment`
56
+ ] = `XCRemoteSwiftPackageReference "${repoName}"`;
57
+
58
+ // Add to PBXProject packageReferences
59
+ const projectKey = Object.keys(
60
+ xcodeProject.hash.project.objects["PBXProject"]
61
+ ).find((key) => !key.includes("_comment"));
62
+
63
+ if (projectKey) {
64
+ const project =
65
+ xcodeProject.hash.project.objects["PBXProject"][projectKey];
66
+ if (!project.packageReferences) project.packageReferences = [];
67
+ project.packageReferences.push(packageRefUuid);
68
+ }
69
+
70
+ // For each product: add dependency, build file, and link
71
+ for (const productName of products) {
72
+ const productDepUuid = xcodeProject.generateUuid();
73
+ xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"][
74
+ productDepUuid
75
+ ] = {
76
+ isa: "XCSwiftPackageProductDependency",
77
+ package: packageRefUuid,
78
+ productName,
79
+ };
80
+ xcodeProject.hash.project.objects["XCSwiftPackageProductDependency"][
81
+ `${productDepUuid}_comment`
82
+ ] = productName;
83
+
84
+ const buildFileUuid = xcodeProject.generateUuid();
85
+ xcodeProject.hash.project.objects["PBXBuildFile"][buildFileUuid] = {
86
+ isa: "PBXBuildFile",
87
+ productRef: productDepUuid,
88
+ productRef_comment: productName,
89
+ };
90
+ xcodeProject.hash.project.objects["PBXBuildFile"][
91
+ `${buildFileUuid}_comment`
92
+ ] = `${productName} in Frameworks`;
93
+
94
+ const frameworkPhaseKey = Object.keys(
95
+ xcodeProject.hash.project.objects["PBXFrameworksBuildPhase"]
96
+ ).find((key) => !key.includes("_comment"));
97
+
98
+ if (frameworkPhaseKey) {
99
+ const phase =
100
+ xcodeProject.hash.project.objects["PBXFrameworksBuildPhase"][
101
+ frameworkPhaseKey
102
+ ];
103
+ if (!phase.files) phase.files = [];
104
+ phase.files.push(buildFileUuid);
105
+ }
106
+
107
+ const nativeTargetKey = Object.keys(
108
+ xcodeProject.hash.project.objects["PBXNativeTarget"]
109
+ ).find((key) => !key.includes("_comment"));
110
+
111
+ if (nativeTargetKey) {
112
+ const target =
113
+ xcodeProject.hash.project.objects["PBXNativeTarget"][
114
+ nativeTargetKey
115
+ ];
116
+ if (!target.packageProductDependencies)
117
+ target.packageProductDependencies = [];
118
+ target.packageProductDependencies.push(productDepUuid);
119
+ }
120
+ }
121
+
122
+ return config;
123
+ });
124
+ };
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { requireNativeViewManager } from "expo-modules-core";
3
+ import type { MapboxNavigationProps } from "./types";
4
+
5
+ const NativeView: React.ComponentType<MapboxNavigationProps> =
6
+ requireNativeViewManager("ExpoMapboxNavigation");
7
+
8
+ /**
9
+ * Full-screen Mapbox turn-by-turn navigation view.
10
+ *
11
+ * Renders the native Mapbox Navigation SDK UI (NavigationViewController on iOS,
12
+ * NavigationView on Android) with voice guidance, maneuver banners, and rerouting.
13
+ */
14
+ export default function MapboxNavigation(props: MapboxNavigationProps) {
15
+ return <NativeView {...props} />;
16
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { default as MapboxNavigation } from "./ExpoMapboxNavigation";
2
+ export type {
3
+ MapboxNavigationProps,
4
+ Coordinate,
5
+ RouteProgressEvent,
6
+ WaypointArrivalEvent,
7
+ NavigationErrorEvent,
8
+ } from "./types";
package/src/types.ts ADDED
@@ -0,0 +1,122 @@
1
+ import type { ViewProps } from "react-native";
2
+
3
+ export interface Coordinate {
4
+ latitude: number;
5
+ longitude: number;
6
+ }
7
+
8
+ export interface RouteProgressEvent {
9
+ distanceRemaining: number;
10
+ durationRemaining: number;
11
+ distanceTraveled: number;
12
+ fractionTraveled: number;
13
+ }
14
+
15
+ export interface WaypointArrivalEvent {
16
+ waypointIndex: number;
17
+ }
18
+
19
+ export interface NavigationErrorEvent {
20
+ message: string;
21
+ }
22
+
23
+ export interface MapboxNavigationProps extends ViewProps {
24
+ // -- Route --
25
+
26
+ /**
27
+ * Array of coordinates defining the route.
28
+ * Minimum 2 points (origin + destination). Intermediate points become waypoints.
29
+ */
30
+ coordinates: Coordinate[];
31
+
32
+ /**
33
+ * Indices into `coordinates` that should be treated as full waypoints
34
+ * (with arrival notification). All others are silent via-points.
35
+ * Must include the first and last index.
36
+ */
37
+ waypointIndices?: number[];
38
+
39
+ /**
40
+ * Mapbox routing profile.
41
+ * iOS: "mapbox/driving-traffic" (default), "mapbox/driving", "mapbox/walking", "mapbox/cycling"
42
+ * Android: omit the "mapbox/" prefix.
43
+ */
44
+ routeProfile?: string;
45
+
46
+ // -- Localization --
47
+
48
+ /**
49
+ * Language for voice guidance, maneuver instructions, and UI labels.
50
+ * BCP 47 language tag (e.g. "de", "en-US"). Defaults to device locale.
51
+ */
52
+ locale?: string;
53
+
54
+ /** Mute voice guidance. Default: false. */
55
+ mute?: boolean;
56
+
57
+ // -- Appearance --
58
+
59
+ /**
60
+ * Custom Mapbox map style URL. Overrides the default navigation style.
61
+ * Example: "mapbox://styles/mapbox/navigation-night-v1"
62
+ */
63
+ mapStyle?: string;
64
+
65
+ /**
66
+ * Controls day/night map appearance.
67
+ * - "day": Light map (default)
68
+ * - "night": Dark 3D map (like Google/Apple Maps night mode)
69
+ * - "auto": Automatic switching based on time of day
70
+ */
71
+ themeMode?: "day" | "night" | "auto";
72
+
73
+ /**
74
+ * Primary accent color (hex). Applied to route line, floating buttons,
75
+ * and interactive elements. Example: "#007AFF"
76
+ */
77
+ accentColor?: string;
78
+
79
+ /**
80
+ * Route line color (hex). Overrides accentColor for the route line only.
81
+ * Example: "#4264fb"
82
+ */
83
+ routeColor?: string;
84
+
85
+ /**
86
+ * Instruction banner background color (hex).
87
+ * Example: "#FFFFFF" for white, "#1A1A2E" for dark.
88
+ */
89
+ bannerBackgroundColor?: string;
90
+
91
+ /**
92
+ * Instruction banner text color (hex). Example: "#000000"
93
+ */
94
+ bannerTextColor?: string;
95
+
96
+ // -- Events --
97
+
98
+ /** Fires continuously as the user progresses along the route. */
99
+ onRouteProgressChanged?: (event: {
100
+ nativeEvent: RouteProgressEvent;
101
+ }) => void;
102
+
103
+ /** User tapped the cancel / close button. The library does NOT auto-dismiss. */
104
+ onCancelNavigation?: () => void;
105
+
106
+ /** Arrived at an intermediate waypoint. */
107
+ onWaypointArrival?: (event: {
108
+ nativeEvent: WaypointArrivalEvent;
109
+ }) => void;
110
+
111
+ /** Arrived at the final destination. */
112
+ onFinalDestinationArrival?: () => void;
113
+
114
+ /** Route was recalculated (e.g. reroute after going off-route). */
115
+ onRouteChanged?: () => void;
116
+
117
+ /** User deviated from the planned route. */
118
+ onUserOffRoute?: () => void;
119
+
120
+ /** A navigation error occurred. */
121
+ onError?: (event: { nativeEvent: NavigationErrorEvent }) => void;
122
+ }