@jadamsbit/react-native-mapbox-navigation 1.0.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.
Files changed (117) hide show
  1. package/.gitattributes +1 -0
  2. package/CONTRIBUTING.md +11 -0
  3. package/LICENSE +21 -0
  4. package/README.md +357 -0
  5. package/android/.gradle/7.3.3/checksums/checksums.lock +0 -0
  6. package/android/.gradle/7.3.3/checksums/md5-checksums.bin +0 -0
  7. package/android/.gradle/7.3.3/checksums/sha1-checksums.bin +0 -0
  8. package/android/.gradle/7.3.3/dependencies-accessors/dependencies-accessors.lock +0 -0
  9. package/android/.gradle/7.3.3/dependencies-accessors/gc.properties +0 -0
  10. package/android/.gradle/7.3.3/executionHistory/executionHistory.lock +0 -0
  11. package/android/.gradle/7.3.3/fileChanges/last-build.bin +0 -0
  12. package/android/.gradle/7.3.3/fileHashes/fileHashes.lock +0 -0
  13. package/android/.gradle/7.3.3/gc.properties +0 -0
  14. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  15. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/README.md +14 -0
  18. package/android/build.gradle +99 -0
  19. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  20. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  21. package/android/gradle.properties +4 -0
  22. package/android/gradlew +244 -0
  23. package/android/gradlew.bat +92 -0
  24. package/android/src/main/AndroidManifest.xml +4 -0
  25. package/android/src/main/java/com/jadamsbit/mapboxnavigation/MapboxNavigationManager.kt +122 -0
  26. package/android/src/main/java/com/jadamsbit/mapboxnavigation/MapboxNavigationPackage.kt +16 -0
  27. package/android/src/main/java/com/jadamsbit/mapboxnavigation/MapboxNavigationView.kt +794 -0
  28. package/android/src/main/res/layout/navigation_view.xml +84 -0
  29. package/android/src/main/res/values/styles.xml +15 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.js +12 -0
  32. package/dist/typings.d.ts +40 -0
  33. package/dist/typings.js +1 -0
  34. package/example/.buckconfig +6 -0
  35. package/example/.eslintrc.js +4 -0
  36. package/example/.flowconfig +65 -0
  37. package/example/.prettierrc.js +7 -0
  38. package/example/.watchmanconfig +1 -0
  39. package/example/App.js +51 -0
  40. package/example/Gemfile +6 -0
  41. package/example/Gemfile.lock +100 -0
  42. package/example/NavigationComponent.js +56 -0
  43. package/example/__tests__/App-test.js +14 -0
  44. package/example/_bundle/config +2 -0
  45. package/example/_ruby-version +1 -0
  46. package/example/android/.gradle/8.2/checksums/checksums.lock +0 -0
  47. package/example/android/.gradle/8.2/fileChanges/last-build.bin +0 -0
  48. package/example/android/.gradle/8.2/fileHashes/fileHashes.lock +0 -0
  49. package/example/android/.gradle/8.2/gc.properties +0 -0
  50. package/example/android/.gradle/vcs-1/gc.properties +0 -0
  51. package/example/android/app/_BUCK +55 -0
  52. package/example/android/app/build.gradle +232 -0
  53. package/example/android/app/build_defs.bzl +19 -0
  54. package/example/android/app/debug.keystore +0 -0
  55. package/example/android/app/proguard-rules.pro +10 -0
  56. package/example/android/app/src/debug/AndroidManifest.xml +13 -0
  57. package/example/android/app/src/debug/java/com/basicapp/ReactNativeFlipper.java +72 -0
  58. package/example/android/app/src/main/AndroidManifest.xml +30 -0
  59. package/example/android/app/src/main/java/com/basicapp/MainActivity.java +15 -0
  60. package/example/android/app/src/main/java/com/basicapp/MainApplication.java +81 -0
  61. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +36 -0
  62. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  63. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  64. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  65. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  66. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  67. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  68. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  69. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  70. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  71. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  72. package/example/android/app/src/main/res/values/strings.xml +3 -0
  73. package/example/android/app/src/main/res/values/styles.xml +9 -0
  74. package/example/android/build.gradle +55 -0
  75. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  76. package/example/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  77. package/example/android/gradle.properties +35 -0
  78. package/example/android/gradlew +185 -0
  79. package/example/android/gradlew.bat +89 -0
  80. package/example/android/settings.gradle +6 -0
  81. package/example/app.json +4 -0
  82. package/example/babel.config.js +24 -0
  83. package/example/index.js +9 -0
  84. package/example/ios/BasicApp/AppDelegate.h +8 -0
  85. package/example/ios/BasicApp/AppDelegate.m +62 -0
  86. package/example/ios/BasicApp/Images.xcassets/AppIcon.appiconset/Contents.json +38 -0
  87. package/example/ios/BasicApp/Images.xcassets/Contents.json +6 -0
  88. package/example/ios/BasicApp/Info.plist +62 -0
  89. package/example/ios/BasicApp/LaunchScreen.storyboard +47 -0
  90. package/example/ios/BasicApp/main.m +9 -0
  91. package/example/ios/BasicApp-Bridging-Header.h +4 -0
  92. package/example/ios/BasicApp.xcodeproj/project.pbxproj +690 -0
  93. package/example/ios/BasicApp.xcodeproj/xcshareddata/xcschemes/BasicApp.xcscheme +88 -0
  94. package/example/ios/BasicApp.xcworkspace/contents.xcworkspacedata +10 -0
  95. package/example/ios/BasicApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  96. package/example/ios/BasicAppTests/BasicAppTests.m +65 -0
  97. package/example/ios/BasicAppTests/Info.plist +24 -0
  98. package/example/ios/BridgeHeader.swift +8 -0
  99. package/example/ios/Podfile +44 -0
  100. package/example/ios/Podfile.lock +473 -0
  101. package/example/metro.config.js +45 -0
  102. package/example/package.json +31 -0
  103. package/img/bridging-header.png +0 -0
  104. package/img/build-setting-linking.png +0 -0
  105. package/img/build-setting-path.png +0 -0
  106. package/ios/MapboxNavigation-Bridging-Header.h +3 -0
  107. package/ios/MapboxNavigation.xcodeproj/project.pbxproj +290 -0
  108. package/ios/MapboxNavigation.xcworkspace/contents.xcworkspacedata +7 -0
  109. package/ios/MapboxNavigation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  110. package/ios/MapboxNavigationManager.m +20 -0
  111. package/ios/MapboxNavigationManager.swift +10 -0
  112. package/ios/MapboxNavigationView.swift +163 -0
  113. package/package.json +33 -0
  114. package/react-native-mapbox-navigation.podspec +51 -0
  115. package/src/index.tsx +21 -0
  116. package/src/typings.ts +43 -0
  117. package/tsconfig.json +14 -0
@@ -0,0 +1,794 @@
1
+ package com.jadamsbit.mapboxnavigation
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.res.Configuration
5
+ import android.content.res.Resources
6
+ import android.location.Location
7
+ import android.location.LocationManager
8
+ import android.view.LayoutInflater
9
+ import android.view.View
10
+ import android.widget.FrameLayout
11
+ import android.widget.Toast
12
+ import androidx.core.content.ContextCompat
13
+ import com.facebook.react.bridge.Arguments
14
+ import com.facebook.react.uimanager.ThemedReactContext
15
+ import com.mapbox.api.directions.v5.models.DirectionsRoute
16
+ import com.mapbox.api.directions.v5.models.RouteOptions
17
+ import com.mapbox.bindgen.Expected
18
+ import com.mapbox.geojson.Point
19
+ import com.mapbox.maps.EdgeInsets
20
+ import com.mapbox.maps.MapView
21
+ import com.mapbox.maps.MapboxMap
22
+ import com.mapbox.maps.Style
23
+ import com.mapbox.maps.plugin.LocationPuck2D
24
+ import com.mapbox.maps.plugin.animation.camera
25
+ import com.mapbox.maps.plugin.locationcomponent.location
26
+ import com.mapbox.navigation.base.TimeFormat
27
+ import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
28
+ import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
29
+ import com.mapbox.navigation.base.options.NavigationOptions
30
+ import com.mapbox.navigation.base.route.RouterCallback
31
+ import com.mapbox.navigation.base.route.RouterFailure
32
+ import com.mapbox.navigation.base.route.RouterOrigin
33
+ import com.mapbox.navigation.core.MapboxNavigation
34
+ import com.mapbox.navigation.core.MapboxNavigationProvider
35
+ import com.mapbox.navigation.core.directions.session.RoutesObserver
36
+ import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter
37
+ import com.mapbox.navigation.core.replay.MapboxReplayer
38
+ import com.mapbox.navigation.core.replay.ReplayLocationEngine
39
+ import com.mapbox.navigation.core.replay.route.ReplayProgressObserver
40
+ import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
41
+ import com.mapbox.navigation.core.trip.session.LocationMatcherResult
42
+ import com.mapbox.navigation.core.trip.session.LocationObserver
43
+ import com.mapbox.navigation.core.trip.session.RouteProgressObserver
44
+ import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
45
+ import com.jadamsbit.mapboxnavigation.databinding.NavigationViewBinding
46
+ import com.mapbox.api.directions.v5.DirectionsCriteria
47
+ import com.mapbox.navigation.base.trip.model.RouteLegProgress
48
+ import com.mapbox.navigation.base.trip.model.RouteProgress
49
+ import com.mapbox.navigation.core.arrival.ArrivalObserver
50
+ import com.mapbox.navigation.ui.base.util.MapboxNavigationConsumer
51
+ import com.mapbox.navigation.ui.maneuver.api.MapboxManeuverApi
52
+ import com.mapbox.navigation.ui.maneuver.view.MapboxManeuverView
53
+ import com.mapbox.navigation.ui.maps.camera.NavigationCamera
54
+ import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
55
+ import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler
56
+ import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState
57
+ import com.mapbox.navigation.ui.maps.camera.transition.NavigationCameraTransitionOptions
58
+ import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
59
+ import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApi
60
+ import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowView
61
+ import com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptions
62
+ import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApi
63
+ import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineView
64
+ import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptions
65
+ import com.mapbox.navigation.ui.maps.route.line.model.RouteLine
66
+ import com.mapbox.navigation.ui.tripprogress.api.MapboxTripProgressApi
67
+ import com.mapbox.navigation.ui.tripprogress.model.DistanceRemainingFormatter
68
+ import com.mapbox.navigation.ui.tripprogress.model.EstimatedTimeToArrivalFormatter
69
+ import com.mapbox.navigation.ui.tripprogress.model.PercentDistanceTraveledFormatter
70
+ import com.mapbox.navigation.ui.tripprogress.model.TimeRemainingFormatter
71
+ import com.mapbox.navigation.ui.tripprogress.model.TripProgressUpdateFormatter
72
+ import com.mapbox.navigation.ui.tripprogress.view.MapboxTripProgressView
73
+ import com.mapbox.navigation.ui.voice.api.MapboxSpeechApi
74
+ import com.mapbox.navigation.ui.voice.api.MapboxVoiceInstructionsPlayer
75
+ import com.mapbox.navigation.ui.voice.model.SpeechAnnouncement
76
+ import com.mapbox.navigation.ui.voice.model.SpeechError
77
+ import com.mapbox.navigation.ui.voice.model.SpeechValue
78
+ import com.mapbox.navigation.ui.voice.model.SpeechVolume
79
+ import java.util.Locale
80
+ import com.facebook.react.uimanager.events.RCTEventEmitter
81
+
82
+ class MapboxNavigationView(private val context: ThemedReactContext, private val accessToken: String?) :
83
+ FrameLayout(context.baseContext) {
84
+
85
+ private companion object {
86
+ private const val BUTTON_ANIMATION_DURATION = 1500L
87
+ }
88
+
89
+ private var origin: Point? = null
90
+ private var waypoints: List<Point>? = null
91
+ private var destination: Point? = null
92
+ private var shouldSimulateRoute = false
93
+ private var showsEndOfRouteFeedback = false
94
+ private var maxHeight: Double? = null
95
+ private var maxWidth: Double? = null
96
+ /**
97
+ * Debug tool used to play, pause and seek route progress events that can be used to produce mocked location updates along the route.
98
+ */
99
+ private val mapboxReplayer = MapboxReplayer()
100
+
101
+ /**
102
+ * Debug tool that mocks location updates with an input from the [mapboxReplayer].
103
+ */
104
+ private val replayLocationEngine = ReplayLocationEngine(mapboxReplayer)
105
+
106
+ /**
107
+ * Debug observer that makes sure the replayer has always an up-to-date information to generate mock updates.
108
+ */
109
+ private val replayProgressObserver = ReplayProgressObserver(mapboxReplayer)
110
+
111
+ /**
112
+ * Bindings to the example layout.
113
+ */
114
+ private var binding: NavigationViewBinding =
115
+ NavigationViewBinding.inflate(LayoutInflater.from(context), this, true)
116
+
117
+ /**
118
+ * Mapbox Maps entry point obtained from the [MapView].
119
+ * You need to get a new reference to this object whenever the [MapView] is recreated.
120
+ */
121
+ private lateinit var mapboxMap: MapboxMap
122
+
123
+ /**
124
+ * Mapbox Navigation entry point. There should only be one instance of this object for the app.
125
+ * You can use [MapboxNavigationProvider] to help create and obtain that instance.
126
+ */
127
+ private lateinit var mapboxNavigation: MapboxNavigation
128
+
129
+ /**
130
+ * Used to execute camera transitions based on the data generated by the [viewportDataSource].
131
+ * This includes transitions from route overview to route following and continuously updating the camera as the location changes.
132
+ */
133
+ private lateinit var navigationCamera: NavigationCamera
134
+
135
+ /**
136
+ * Produces the camera frames based on the location and routing data for the [navigationCamera] to execute.
137
+ */
138
+ private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
139
+
140
+ /*
141
+ * Below are generated camera padding values to ensure that the route fits well on screen while
142
+ * other elements are overlaid on top of the map (including instruction view, buttons, etc.)
143
+ */
144
+ private val pixelDensity = Resources.getSystem().displayMetrics.density
145
+ private val overviewPadding: EdgeInsets by lazy {
146
+ EdgeInsets(
147
+ 140.0 * pixelDensity,
148
+ 40.0 * pixelDensity,
149
+ 120.0 * pixelDensity,
150
+ 40.0 * pixelDensity
151
+ )
152
+ }
153
+ private val landscapeOverviewPadding: EdgeInsets by lazy {
154
+ EdgeInsets(
155
+ 30.0 * pixelDensity,
156
+ 380.0 * pixelDensity,
157
+ 110.0 * pixelDensity,
158
+ 20.0 * pixelDensity
159
+ )
160
+ }
161
+ private val followingPadding: EdgeInsets by lazy {
162
+ EdgeInsets(
163
+ 180.0 * pixelDensity,
164
+ 40.0 * pixelDensity,
165
+ 150.0 * pixelDensity,
166
+ 40.0 * pixelDensity
167
+ )
168
+ }
169
+ private val landscapeFollowingPadding: EdgeInsets by lazy {
170
+ EdgeInsets(
171
+ 30.0 * pixelDensity,
172
+ 380.0 * pixelDensity,
173
+ 110.0 * pixelDensity,
174
+ 40.0 * pixelDensity
175
+ )
176
+ }
177
+
178
+ /**
179
+ * Generates updates for the [MapboxManeuverView] to display the upcoming maneuver instructions
180
+ * and remaining distance to the maneuver point.
181
+ */
182
+ private lateinit var maneuverApi: MapboxManeuverApi
183
+
184
+ /**
185
+ * Generates updates for the [MapboxTripProgressView] that include remaining time and distance to the destination.
186
+ */
187
+ private lateinit var tripProgressApi: MapboxTripProgressApi
188
+
189
+ /**
190
+ * Generates updates for the [routeLineView] with the geometries and properties of the routes that should be drawn on the map.
191
+ */
192
+ private lateinit var routeLineApi: MapboxRouteLineApi
193
+
194
+ /**
195
+ * Draws route lines on the map based on the data from the [routeLineApi]
196
+ */
197
+ private lateinit var routeLineView: MapboxRouteLineView
198
+
199
+ /**
200
+ * Generates updates for the [routeArrowView] with the geometries and properties of maneuver arrows that should be drawn on the map.
201
+ */
202
+ private val routeArrowApi: MapboxRouteArrowApi = MapboxRouteArrowApi()
203
+
204
+ /**
205
+ * Draws maneuver arrows on the map based on the data [routeArrowApi].
206
+ */
207
+ private lateinit var routeArrowView: MapboxRouteArrowView
208
+
209
+ /**
210
+ * Stores and updates the state of whether the voice instructions should be played as they come or muted.
211
+ */
212
+ private var isVoiceInstructionsMuted = false
213
+ set(value) {
214
+ field = value
215
+ if (value) {
216
+ binding.soundButton.muteAndExtend(BUTTON_ANIMATION_DURATION)
217
+ voiceInstructionsPlayer.volume(SpeechVolume(0f))
218
+ } else {
219
+ binding.soundButton.unmuteAndExtend(BUTTON_ANIMATION_DURATION)
220
+ voiceInstructionsPlayer.volume(SpeechVolume(1f))
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Extracts message that should be communicated to the driver about the upcoming maneuver.
226
+ * When possible, downloads a synthesized audio file that can be played back to the driver.
227
+ */
228
+ private lateinit var speechApi: MapboxSpeechApi
229
+
230
+ /**
231
+ * Plays the synthesized audio files with upcoming maneuver instructions
232
+ * or uses an on-device Text-To-Speech engine to communicate the message to the driver.
233
+ */
234
+ private lateinit var voiceInstructionsPlayer: MapboxVoiceInstructionsPlayer
235
+
236
+ /**
237
+ * Observes when a new voice instruction should be played.
238
+ */
239
+ private val voiceInstructionsObserver = VoiceInstructionsObserver { voiceInstructions ->
240
+ speechApi.generate(voiceInstructions, speechCallback)
241
+ }
242
+
243
+ /**
244
+ * Based on whether the synthesized audio file is available, the callback plays the file
245
+ * or uses the fall back which is played back using the on-device Text-To-Speech engine.
246
+ */
247
+ private val speechCallback =
248
+ MapboxNavigationConsumer<Expected<SpeechError, SpeechValue>> { expected ->
249
+ expected.fold(
250
+ { error ->
251
+ // play the instruction via fallback text-to-speech engine
252
+ voiceInstructionsPlayer.play(
253
+ error.fallback,
254
+ voiceInstructionsPlayerCallback
255
+ )
256
+ },
257
+ { value ->
258
+ // play the sound file from the external generator
259
+ voiceInstructionsPlayer.play(
260
+ value.announcement,
261
+ voiceInstructionsPlayerCallback
262
+ )
263
+ }
264
+ )
265
+ }
266
+
267
+ /**
268
+ * When a synthesized audio file was downloaded, this callback cleans up the disk after it was played.
269
+ */
270
+ private val voiceInstructionsPlayerCallback =
271
+ MapboxNavigationConsumer<SpeechAnnouncement> { value ->
272
+ // remove already consumed file to free-up space
273
+ speechApi.clean(value)
274
+ }
275
+
276
+ /**
277
+ * [NavigationLocationProvider] is a utility class that helps to provide location updates generated by the Navigation SDK
278
+ * to the Maps SDK in order to update the user location indicator on the map.
279
+ */
280
+ private val navigationLocationProvider = NavigationLocationProvider()
281
+
282
+ /**
283
+ * Gets notified with location updates.
284
+ *
285
+ * Exposes raw updates coming directly from the location services
286
+ * and the updates enhanced by the Navigation SDK (cleaned up and matched to the road).
287
+ */
288
+ private val locationObserver = object : LocationObserver {
289
+ override fun onNewRawLocation(rawLocation: Location) {
290
+ // not handled
291
+ }
292
+
293
+ override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
294
+ val enhancedLocation = locationMatcherResult.enhancedLocation
295
+ // update location puck's position on the map
296
+ navigationLocationProvider.changePosition(
297
+ location = enhancedLocation,
298
+ keyPoints = locationMatcherResult.keyPoints,
299
+ )
300
+
301
+ // update camera position to account for new location
302
+ viewportDataSource.onLocationChanged(enhancedLocation)
303
+ viewportDataSource.evaluate()
304
+
305
+ val event = Arguments.createMap()
306
+ event.putDouble("longitude", enhancedLocation.longitude)
307
+ event.putDouble("latitude", enhancedLocation.latitude)
308
+ context
309
+ .getJSModule(RCTEventEmitter::class.java)
310
+ .receiveEvent(id, "onLocationChange", event)
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Gets notified with progress along the currently active route.
316
+ */
317
+ private val routeProgressObserver = RouteProgressObserver { routeProgress ->
318
+ // update the camera position to account for the progressed fragment of the route
319
+ viewportDataSource.onRouteProgressChanged(routeProgress)
320
+ viewportDataSource.evaluate()
321
+
322
+ // draw the upcoming maneuver arrow on the map
323
+ val style = mapboxMap.getStyle()
324
+ if (style != null) {
325
+ val maneuverArrowResult = routeArrowApi.addUpcomingManeuverArrow(routeProgress)
326
+ routeArrowView.renderManeuverUpdate(style, maneuverArrowResult)
327
+ }
328
+
329
+ // update top banner with maneuver instructions
330
+ val maneuvers = maneuverApi.getManeuvers(routeProgress)
331
+ maneuvers.fold(
332
+ { error ->
333
+ Toast.makeText(
334
+ context,
335
+ error.errorMessage,
336
+ Toast.LENGTH_SHORT
337
+ ).show()
338
+ },
339
+ {
340
+ binding.maneuverView.visibility = View.VISIBLE
341
+ binding.maneuverView.updatePrimaryManeuverTextAppearance(R.style.PrimaryManeuverTextAppearance)
342
+ binding.maneuverView.updateSecondaryManeuverTextAppearance(R.style.ManeuverTextAppearance)
343
+ binding.maneuverView.updateSubManeuverTextAppearance(R.style.ManeuverTextAppearance)
344
+ binding.maneuverView.updateStepDistanceTextAppearance(R.style.StepDistanceRemainingAppearance)
345
+ binding.maneuverView.renderManeuvers(maneuvers)
346
+ }
347
+ )
348
+
349
+ // update bottom trip progress summary
350
+ binding.tripProgressView.render(
351
+ tripProgressApi.getTripProgress(routeProgress)
352
+ )
353
+
354
+ val event = Arguments.createMap()
355
+ event.putDouble("distanceTraveled", routeProgress.distanceTraveled.toDouble())
356
+ event.putDouble("durationRemaining", routeProgress.durationRemaining.toDouble())
357
+ event.putDouble("fractionTraveled", routeProgress.fractionTraveled.toDouble())
358
+ event.putDouble("distanceRemaining", routeProgress.distanceRemaining.toDouble())
359
+ context
360
+ .getJSModule(RCTEventEmitter::class.java)
361
+ .receiveEvent(id, "onRouteProgressChange", event)
362
+ }
363
+
364
+ /**
365
+ * Gets notified whenever the tracked routes change.
366
+ *
367
+ * A change can mean:
368
+ * - routes get changed with [MapboxNavigation.setRoutes]
369
+ * - routes annotations get refreshed (for example, congestion annotation that indicate the live traffic along the route)
370
+ * - driver got off route and a reroute was executed
371
+ */
372
+ private val routesObserver = RoutesObserver { routeUpdateResult ->
373
+ if (routeUpdateResult.routes.isNotEmpty()) {
374
+ // generate route geometries asynchronously and render them
375
+ val routeLines = routeUpdateResult.routes.map { RouteLine(it, null) }
376
+
377
+ routeLineApi.setRoutes(
378
+ routeLines
379
+ ) { value ->
380
+ mapboxMap.getStyle()?.apply {
381
+ routeLineView.renderRouteDrawData(this, value)
382
+ }
383
+ }
384
+
385
+ // update the camera position to account for the new route
386
+ viewportDataSource.onRouteChanged(routeUpdateResult.routes.first())
387
+ viewportDataSource.evaluate()
388
+ } else {
389
+ // remove the route line and route arrow from the map
390
+ val style = mapboxMap.getStyle()
391
+ if (style != null) {
392
+ routeLineApi.clearRouteLine { value ->
393
+ routeLineView.renderClearRouteLineValue(
394
+ style,
395
+ value
396
+ )
397
+ }
398
+ routeArrowView.render(style, routeArrowApi.clearArrows())
399
+ }
400
+
401
+ // remove the route reference from camera position evaluations
402
+ viewportDataSource.clearRouteData()
403
+ viewportDataSource.evaluate()
404
+ }
405
+ }
406
+
407
+ private val arrivalObserver = object : ArrivalObserver {
408
+
409
+ override fun onWaypointArrival(routeProgress: RouteProgress) {
410
+ // do something when the user arrives at a waypoint
411
+ }
412
+
413
+ override fun onNextRouteLegStart(routeLegProgress: RouteLegProgress) {
414
+ // do something when the user starts a new leg
415
+ }
416
+
417
+ override fun onFinalDestinationArrival(routeProgress: RouteProgress) {
418
+ val event = Arguments.createMap()
419
+ event.putString("onArrive", "")
420
+ context
421
+ .getJSModule(RCTEventEmitter::class.java)
422
+ .receiveEvent(id, "onRouteProgressChange", event)
423
+ }
424
+ }
425
+
426
+
427
+ override fun onAttachedToWindow() {
428
+ super.onAttachedToWindow()
429
+ onCreate()
430
+ }
431
+
432
+ override fun requestLayout() {
433
+ super.requestLayout()
434
+ post(measureAndLayout)
435
+ }
436
+
437
+ private val measureAndLayout = Runnable {
438
+ measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
439
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
440
+ layout(left, top, right, bottom)
441
+ }
442
+
443
+ private fun setCameraPositionToOrigin() {
444
+ val startingLocation = Location(LocationManager.GPS_PROVIDER)
445
+ startingLocation.latitude = origin!!.latitude()
446
+ startingLocation.longitude = origin!!.longitude()
447
+ viewportDataSource.onLocationChanged(startingLocation)
448
+
449
+ navigationCamera.requestNavigationCameraToFollowing(
450
+ stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
451
+ .maxDuration(0) // instant transition
452
+ .build()
453
+ )
454
+ }
455
+
456
+ @SuppressLint("MissingPermission")
457
+ fun onCreate() {
458
+ if (accessToken == null) {
459
+ sendErrorToReact("Mapbox access token is not set")
460
+ return
461
+ }
462
+
463
+ if (origin == null || destination == null) {
464
+ sendErrorToReact("origin and destination are required")
465
+ return
466
+ }
467
+
468
+ mapboxMap = binding.mapView.getMapboxMap()
469
+
470
+ // initialize the location puck
471
+ binding.mapView.location.apply {
472
+ this.locationPuck = LocationPuck2D(
473
+ bearingImage = ContextCompat.getDrawable(
474
+ context,
475
+ R.drawable.mapbox_navigation_puck_icon
476
+ )
477
+ )
478
+ setLocationProvider(navigationLocationProvider)
479
+ enabled = true
480
+ }
481
+
482
+ // initialize Mapbox Navigation
483
+ mapboxNavigation = if (MapboxNavigationProvider.isCreated()) {
484
+ MapboxNavigationProvider.retrieve()
485
+ } else if (shouldSimulateRoute) {
486
+ MapboxNavigationProvider.create(
487
+ NavigationOptions.Builder(context)
488
+ .accessToken(accessToken)
489
+ .locationEngine(replayLocationEngine)
490
+ .build()
491
+ )
492
+ } else {
493
+ MapboxNavigationProvider.create(
494
+ NavigationOptions.Builder(context)
495
+ .accessToken(accessToken)
496
+ .build()
497
+ )
498
+ }
499
+
500
+ // initialize Navigation Camera
501
+ viewportDataSource = MapboxNavigationViewportDataSource(mapboxMap)
502
+
503
+ navigationCamera = NavigationCamera(
504
+ mapboxMap,
505
+ binding.mapView.camera,
506
+ viewportDataSource
507
+ )
508
+ // set the animations lifecycle listener to ensure the NavigationCamera stops
509
+ // automatically following the user location when the map is interacted with
510
+ binding.mapView.camera.addCameraAnimationsLifecycleListener(
511
+ NavigationBasicGesturesHandler(navigationCamera)
512
+ )
513
+ navigationCamera.registerNavigationCameraStateChangeObserver { navigationCameraState ->
514
+ // shows/hide the recenter button depending on the camera state
515
+ when (navigationCameraState) {
516
+ NavigationCameraState.TRANSITION_TO_FOLLOWING,
517
+ NavigationCameraState.FOLLOWING -> binding.recenter.visibility = View.INVISIBLE
518
+ NavigationCameraState.TRANSITION_TO_OVERVIEW,
519
+ NavigationCameraState.OVERVIEW,
520
+ NavigationCameraState.IDLE -> binding.recenter.visibility = View.VISIBLE
521
+ }
522
+ }
523
+ // set the padding values depending on screen orientation and visible view layout
524
+ if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
525
+ viewportDataSource.overviewPadding = landscapeOverviewPadding
526
+ } else {
527
+ viewportDataSource.overviewPadding = overviewPadding
528
+ }
529
+ if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
530
+ viewportDataSource.followingPadding = landscapeFollowingPadding
531
+ } else {
532
+ viewportDataSource.followingPadding = followingPadding
533
+ }
534
+
535
+ // make sure to use the same DistanceFormatterOptions across different features
536
+ val distanceFormatterOptions = mapboxNavigation.navigationOptions.distanceFormatterOptions
537
+
538
+ // initialize maneuver api that feeds the data to the top banner maneuver view
539
+ maneuverApi = MapboxManeuverApi(
540
+ MapboxDistanceFormatter(distanceFormatterOptions)
541
+ )
542
+
543
+ // initialize bottom progress view
544
+ tripProgressApi = MapboxTripProgressApi(
545
+ TripProgressUpdateFormatter.Builder(context)
546
+ .distanceRemainingFormatter(
547
+ DistanceRemainingFormatter(distanceFormatterOptions)
548
+ )
549
+ .timeRemainingFormatter(
550
+ TimeRemainingFormatter(context)
551
+ )
552
+ .percentRouteTraveledFormatter(
553
+ PercentDistanceTraveledFormatter()
554
+ )
555
+ .estimatedTimeToArrivalFormatter(
556
+ EstimatedTimeToArrivalFormatter(context, TimeFormat.NONE_SPECIFIED)
557
+ )
558
+ .build()
559
+ )
560
+
561
+ // initialize voice instructions api and the voice instruction player
562
+ speechApi = MapboxSpeechApi(
563
+ context,
564
+ accessToken,
565
+ Locale.US.language
566
+ )
567
+ voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
568
+ context,
569
+ accessToken,
570
+ Locale.US.language
571
+ )
572
+
573
+ // initialize route line, the withRouteLineBelowLayerId is specified to place
574
+ // the route line below road labels layer on the map
575
+ // the value of this option will depend on the style that you are using
576
+ // and under which layer the route line should be placed on the map layers stack
577
+ val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(context)
578
+ .withRouteLineBelowLayerId("road-label")
579
+ .build()
580
+ routeLineApi = MapboxRouteLineApi(mapboxRouteLineOptions)
581
+ routeLineView = MapboxRouteLineView(mapboxRouteLineOptions)
582
+
583
+ // initialize maneuver arrow view to draw arrows on the map
584
+ val routeArrowOptions = RouteArrowOptions.Builder(context).build()
585
+ routeArrowView = MapboxRouteArrowView(routeArrowOptions)
586
+
587
+ setCameraPositionToOrigin()
588
+ // load map style
589
+ mapboxMap.loadStyleUri(
590
+ Style.MAPBOX_STREETS
591
+ )
592
+
593
+ // initialize view interactions
594
+ binding.stop.setOnClickListener {
595
+ // clearRouteAndStopNavigation() // TODO: figure out how we want to address this since a user cannot reinitialize a route once it is canceled.
596
+ val event = Arguments.createMap()
597
+ event.putString("onCancelNavigation", "Navigation Closed")
598
+ context
599
+ .getJSModule(RCTEventEmitter::class.java)
600
+ .receiveEvent(id, "onCancelNavigation", event)
601
+ }
602
+ binding.recenter.setOnClickListener {
603
+ navigationCamera.requestNavigationCameraToFollowing()
604
+ binding.routeOverview.showTextAndExtend(BUTTON_ANIMATION_DURATION)
605
+ }
606
+ binding.routeOverview.setOnClickListener {
607
+ navigationCamera.requestNavigationCameraToOverview()
608
+ binding.recenter.showTextAndExtend(BUTTON_ANIMATION_DURATION)
609
+ }
610
+ binding.soundButton.setOnClickListener {
611
+ // mute/unmute voice instructions
612
+ isVoiceInstructionsMuted = !isVoiceInstructionsMuted
613
+ }
614
+
615
+ // set initial sounds button state
616
+ binding.soundButton.unmute()
617
+
618
+ // start the trip session to being receiving location updates in free drive
619
+ // and later when a route is set also receiving route progress updates
620
+ mapboxNavigation.startTripSession()
621
+ startRoute()
622
+ }
623
+
624
+ private fun startRoute() {
625
+ // register event listeners
626
+ mapboxNavigation.registerRoutesObserver(routesObserver)
627
+ mapboxNavigation.registerArrivalObserver(arrivalObserver)
628
+ mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
629
+ mapboxNavigation.registerLocationObserver(locationObserver)
630
+ mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
631
+ mapboxNavigation.registerRouteProgressObserver(replayProgressObserver)
632
+
633
+ // Create a list of coordinates that includes origin, destination, and waypoints
634
+ val coordinatesList = mutableListOf<Point>()
635
+ this.origin?.let { coordinatesList.add(it) }
636
+ this.waypoints?.let { coordinatesList.addAll(it) }
637
+ this.destination?.let { coordinatesList.add(it) }
638
+
639
+ findRoute(coordinatesList)
640
+ }
641
+
642
+ override fun onDetachedFromWindow() {
643
+ super.onDetachedFromWindow()
644
+ mapboxNavigation.unregisterRoutesObserver(routesObserver)
645
+ mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
646
+ mapboxNavigation.unregisterLocationObserver(locationObserver)
647
+ mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
648
+ mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver)
649
+ }
650
+
651
+ private fun onDestroy() {
652
+ MapboxNavigationProvider.destroy()
653
+ mapboxReplayer.finish()
654
+ maneuverApi.cancel()
655
+ routeLineApi.cancel()
656
+ routeLineView.cancel()
657
+ speechApi.cancel()
658
+ voiceInstructionsPlayer.shutdown()
659
+ }
660
+
661
+ private fun findRoute(coordinates: List<Point>) {
662
+ try {
663
+ val routeOptionsBuilder = RouteOptions.builder()
664
+ .applyDefaultNavigationOptions()
665
+ .applyLanguageAndVoiceUnitOptions(context)
666
+ .coordinatesList(coordinates)
667
+ .profile(DirectionsCriteria.PROFILE_DRIVING)
668
+ .steps(true)
669
+
670
+ maxHeight?.let { routeOptionsBuilder.maxHeight(it) }
671
+ maxWidth?.let { routeOptionsBuilder.maxWidth(it) }
672
+
673
+ val routeOptions = routeOptionsBuilder.build()
674
+
675
+ mapboxNavigation.requestRoutes(
676
+ routeOptions,
677
+ object : RouterCallback {
678
+ override fun onRoutesReady(
679
+ routes: List<DirectionsRoute>,
680
+ routerOrigin: RouterOrigin
681
+ ) {
682
+ setRouteAndStartNavigation(routes)
683
+ }
684
+
685
+ override fun onFailure(
686
+ reasons: List<RouterFailure>,
687
+ routeOptions: RouteOptions
688
+ ) {
689
+ sendErrorToReact("Error finding route $reasons")
690
+ }
691
+
692
+ override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
693
+ // no impl
694
+ }
695
+ }
696
+ )
697
+ } catch (ex: Exception) {
698
+ sendErrorToReact(ex.toString())
699
+ }
700
+
701
+ }
702
+
703
+ private fun sendErrorToReact(error: String?) {
704
+ val event = Arguments.createMap()
705
+ event.putString("error", error)
706
+ context
707
+ .getJSModule(RCTEventEmitter::class.java)
708
+ .receiveEvent(id, "onError", event)
709
+ }
710
+
711
+ private fun setRouteAndStartNavigation(routes: List<DirectionsRoute>) {
712
+ if (routes.isEmpty()) {
713
+ sendErrorToReact("No route found")
714
+ return;
715
+ }
716
+ // set routes, where the first route in the list is the primary route that
717
+ // will be used for active guidance
718
+ mapboxNavigation.setRoutes(routes)
719
+
720
+ // start location simulation along the primary route
721
+ if (shouldSimulateRoute) {
722
+ startSimulation(routes.first())
723
+ }
724
+
725
+ // show UI elements
726
+ binding.soundButton.visibility = View.VISIBLE
727
+ binding.routeOverview.visibility = View.VISIBLE
728
+ binding.tripProgressCard.visibility = View.VISIBLE
729
+
730
+ // move the camera to overview when new route is available
731
+ navigationCamera.requestNavigationCameraToFollowing()
732
+ }
733
+
734
+ private fun clearRouteAndStopNavigation() {
735
+ // clear
736
+ mapboxNavigation.setRoutes(listOf())
737
+
738
+ // stop simulation
739
+ mapboxReplayer.stop()
740
+
741
+ // hide UI elements
742
+ binding.soundButton.visibility = View.INVISIBLE
743
+ binding.maneuverView.visibility = View.INVISIBLE
744
+ binding.routeOverview.visibility = View.INVISIBLE
745
+ binding.tripProgressCard.visibility = View.INVISIBLE
746
+ }
747
+
748
+ private fun startSimulation(route: DirectionsRoute) {
749
+ mapboxReplayer.run {
750
+ stop()
751
+ clearEvents()
752
+ val replayEvents = ReplayRouteMapper().mapDirectionsRouteGeometry(route)
753
+ pushEvents(replayEvents)
754
+ seekTo(replayEvents.first())
755
+ play()
756
+ }
757
+ }
758
+
759
+ fun onDropViewInstance() {
760
+ this.onDestroy()
761
+ }
762
+
763
+ fun setOrigin(origin: Point?) {
764
+ this.origin = origin
765
+ }
766
+
767
+ fun setWaypoints(waypoints: List<Point>) {
768
+ this.waypoints = waypoints
769
+ }
770
+
771
+ fun setDestination(destination: Point?) {
772
+ this.destination = destination
773
+ }
774
+
775
+ fun setShouldSimulateRoute(shouldSimulateRoute: Boolean) {
776
+ this.shouldSimulateRoute = shouldSimulateRoute
777
+ }
778
+
779
+ fun setShowsEndOfRouteFeedback(showsEndOfRouteFeedback: Boolean) {
780
+ this.showsEndOfRouteFeedback = showsEndOfRouteFeedback
781
+ }
782
+
783
+ fun setMute(mute: Boolean) {
784
+ this.isVoiceInstructionsMuted = mute
785
+ }
786
+
787
+ fun setMaxHeight(maxHeight: Double?) {
788
+ this.maxHeight = maxHeight
789
+ }
790
+
791
+ fun setMaxWidth(maxWidth: Double?) {
792
+ this.maxWidth = maxWidth
793
+ }
794
+ }