@iternio/react-native-auto-play 0.2.2 → 0.3.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.
package/README.md CHANGED
@@ -880,7 +880,7 @@ CarPlayDashboard.setButtons([
880
880
 
881
881
  ### iOS
882
882
 
883
- - **Broken exceptions with `react-native-skia`**: When using `react-native-skia` up to version `2.4.14`, exceptions on iOS are not reported correctly. This is fixed in newer versions of `react-native-skia`. For more details, see this [pull request](https://github.com/Shopify/react-native-skia/pull/3595) and [issue](https://github.com/Shopify/react-native-skia/issues/3635).
883
+ - **Broken exceptions with `react-native-skia`**: When using `react-native-skia` exceptions on iOS are not reported correctly. This is fixed since version `2.4.19` of `react-native-skia`. For more details, see this [pull request](https://github.com/Shopify/react-native-skia/pull/3595) and [issue](https://github.com/Shopify/react-native-skia/issues/3635).
884
884
  - **AppState on iOS**: The `AppState` module from React Native does not work correctly on iOS because this library uses scenes, which are not supported by the stock `AppState` module. This library provides a custom state listener that works for both Android and iOS. Use `HybridAutoPlay.addListenerRenderState` instead of `AppState`.
885
885
  - **Timers stop on screen lock**: iOS stops all timers when the device's main screen is turned off. To ensure timers continue to run (which is often necessary for background tasks related to autoplay), a patch for `react-native` is required. A patch is included in the root `patches/` directory and can be applied using `patch-package`.
886
886
  - **expo-splash-screen stuck on iOS**: The `expo-splash-screen` module is broken on iOS because it does not support scenes, which are used by this library. This can cause the splash screen to be stuck on either the mobile device or on CarPlay. To fix this, a patch for `expo-splash-screen` is included in the root `patches/` directory and can be applied using `patch-package`. After applying the patch, you can hide the splash screen for a specific scene by passing the module name to the `hide` or `hideAsync` function. The module name can be one of the values from the `AutoPlayModules` enum or the UUID of a cluster screen.
@@ -37,6 +37,7 @@ Pod::Spec.new do |s|
37
37
  load 'nitrogen/generated/ios/ReactNativeAutoPlay+autolinking.rb'
38
38
  add_nitrogen_files(s)
39
39
 
40
+ s.dependency 'React-Core'
40
41
  s.dependency 'React-jsi'
41
42
  s.dependency 'React-callinvoker'
42
43
  install_modules_dependencies(s)
@@ -44,7 +44,6 @@ android {
44
44
  defaultConfig {
45
45
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
46
46
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
47
- buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
48
47
 
49
48
  externalNativeBuild {
50
49
  cmake {
@@ -20,10 +20,8 @@ import androidx.car.app.CarContext
20
20
  import androidx.car.app.SurfaceCallback
21
21
  import androidx.car.app.SurfaceContainer
22
22
  import com.facebook.react.ReactApplication
23
- import com.facebook.react.ReactRootView
24
23
  import com.facebook.react.bridge.Arguments
25
24
  import com.facebook.react.bridge.ReactContext
26
- import com.facebook.react.bridge.UIManager
27
25
  import com.facebook.react.fabric.FabricUIManager
28
26
  import com.facebook.react.runtime.ReactSurfaceImpl
29
27
  import com.facebook.react.runtime.ReactSurfaceView
@@ -45,28 +43,18 @@ class VirtualRenderer(
45
43
  private val isCluster: Boolean = false
46
44
  ) {
47
45
  private lateinit var fabricUiManager: FabricUIManager
48
- private lateinit var uiManager: UIManager
49
46
 
50
47
  private fun isUiManagerInitialized(): Boolean {
51
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
52
- return ::fabricUiManager.isInitialized
53
- }
54
-
55
- return ::uiManager.isInitialized
48
+ return ::fabricUiManager.isInitialized
56
49
  }
57
50
 
58
51
  private lateinit var display: Display
59
52
  private lateinit var reactContext: ReactContext
60
53
 
61
54
  private lateinit var reactSurfaceImpl: ReactSurfaceImpl
62
- private lateinit var reactSurfaceView: ReactSurfaceView
55
+ private var reactSurfaceView: ReactSurfaceView? = null
63
56
  private var reactSurfaceId: Int? = null
64
57
 
65
- private lateinit var reactRootView: ReactRootView
66
- private fun isReactRootViewInitialized(): Boolean {
67
- return ::reactRootView.isInitialized
68
- }
69
-
70
58
  private var height: Int = 0
71
59
  private var width: Int = 0
72
60
 
@@ -85,20 +73,9 @@ class VirtualRenderer(
85
73
  reactContext =
86
74
  ReactContextResolver.getReactContext(context.applicationContext as ReactApplication)
87
75
 
88
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
89
- fabricUiManager = UIManagerHelper.getUIManager(
90
- reactContext, UIManagerType.FABRIC
91
- ) as FabricUIManager
92
- } else {
93
- // UIManagerType.DEFAULT was deprecated and will eventually be removed, but LEGACY is not available in lower RN versions.
94
- // So make sure both work to be backwards compatible
95
- val legacyType = try {
96
- UIManagerType::class.java.getField("LEGACY").getInt(null)
97
- } catch (e: NoSuchFieldException) {
98
- UIManagerType.DEFAULT
99
- }
100
- uiManager = UIManagerHelper.getUIManager(reactContext, legacyType) as UIManager
101
- }
76
+ fabricUiManager = UIManagerHelper.getUIManager(
77
+ reactContext, UIManagerType.FABRIC
78
+ ) as FabricUIManager
102
79
 
103
80
  initRenderer()
104
81
  }
@@ -216,7 +193,7 @@ class VirtualRenderer(
216
193
  val additionalMarginRight =
217
194
  if (stableArea.right == visibleArea.right && visibleArea.right != width) 0 else defaultMargin
218
195
  val additionalMarginTop =
219
- if (visibleArea.top != stableArea.top || (visibleArea.top > 0 && stableArea.top > 0 && visibleArea.right < width)) 0 else defaultMargin
196
+ if (visibleArea.top != stableArea.top || (visibleArea.top > 0 && visibleArea.right < width)) 0 else defaultMargin
220
197
  val additionalMarginBottom =
221
198
  if (stableArea.bottom == visibleArea.bottom) defaultMargin else 0
222
199
 
@@ -293,89 +270,15 @@ class VirtualRenderer(
293
270
  val mainScreenDensity = DisplayMetricsHolder.getScreenDisplayMetrics().density
294
271
  val reactNativeScale = virtualScreenDensity / mainScreenDensity * BuildConfig.SCALE_FACTOR
295
272
 
296
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
297
- if (!isUiManagerInitialized()) {
298
- // this makes sure we have all required instances
299
- // no matter if the app is launched on the phone or AA first
300
- return
301
- }
302
-
303
- FabricMapPresentation(
304
- context, display, height, width, initialProperties, reactNativeScale
305
- ).show()
306
- } else {
307
- if (!isUiManagerInitialized()) {
308
- // this makes sure we have all required instances
309
- // no matter if the app is launched on the phone or AA first
310
- return
311
- }
312
- MapPresentation(
313
- context, display, height, width, initialProperties, reactNativeScale
314
- ).show()
273
+ if (!isUiManagerInitialized()) {
274
+ // this makes sure we have all required instances
275
+ // no matter if the app is launched on the phone or AA first
276
+ return
315
277
  }
316
- }
317
-
318
- inner class MapPresentation(
319
- private val context: CarContext,
320
- display: Display,
321
- private val height: Int,
322
- private val width: Int,
323
- private val initialProperties: Bundle,
324
- private val reactNativeScale: Float,
325
- ) : Presentation(context, display) {
326
- override fun onCreate(savedInstanceState: Bundle?) {
327
- super.onCreate(savedInstanceState)
328
-
329
- var splashScreenView: View? = null
330
-
331
- // Wrap applicationContext with app theme to support AppCompat widgets like ReactTextView
332
- val appTheme = context.applicationContext.applicationInfo.theme
333
- val themedContext = ContextThemeWrapper(context.applicationContext, appTheme)
334
-
335
- if (!isReactRootViewInitialized()) {
336
- splashScreenView =
337
- if (isCluster) getClusterSplashScreen(themedContext, height, width) else null
338
-
339
- val instanceManager =
340
- (themedContext.applicationContext as ReactApplication).reactNativeHost.reactInstanceManager
341
-
342
- reactRootView = ReactRootView(themedContext).apply {
343
- layoutParams = FrameLayout.LayoutParams(
344
- (this@MapPresentation.width / reactNativeScale).toInt(),
345
- (this@MapPresentation.height / reactNativeScale).toInt()
346
- )
347
- scaleX = reactNativeScale
348
- scaleY = reactNativeScale
349
- pivotX = 0f
350
- pivotY = 0f
351
- setBackgroundColor(Color.DKGRAY)
352
-
353
- splashScreenView?.let {
354
- removeClusterSplashScreen({ viewTreeObserver }, it)
355
- }
356
-
357
- startReactApplication(instanceManager, moduleName, initialProperties)
358
- runApplication()
359
- }
360
- } else {
361
- (reactRootView.parent as? ViewGroup)?.removeView(reactRootView)
362
- }
363
-
364
- val rootContainer = FrameLayout(themedContext).apply {
365
- layoutParams = FrameLayout.LayoutParams(
366
- FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
367
- )
368
- clipChildren = false
369
-
370
- addView(reactRootView)
371
- }
372
-
373
- splashScreenView?.let {
374
- rootContainer.addView(it)
375
- }
376
278
 
377
- setContentView(rootContainer)
378
- }
279
+ FabricMapPresentation(
280
+ context, display, height, width, initialProperties, reactNativeScale
281
+ ).show()
379
282
  }
380
283
 
381
284
  inner class FabricMapPresentation(
@@ -398,11 +301,13 @@ class VirtualRenderer(
398
301
 
399
302
  var splashScreenView: View? = null
400
303
 
401
- if (!this@VirtualRenderer::reactSurfaceView.isInitialized) {
304
+ reactSurfaceView?.let {
305
+ (it.parent as ViewGroup).removeView(it)
306
+ } ?: run {
402
307
  splashScreenView =
403
308
  if (isCluster) getClusterSplashScreen(themedContext, height, width) else null
404
309
 
405
- reactSurfaceView = ReactSurfaceView(themedContext, reactSurfaceImpl).apply {
310
+ val surfaceView = ReactSurfaceView(themedContext, reactSurfaceImpl).apply {
406
311
  layoutParams = FrameLayout.LayoutParams(
407
312
  (width / reactNativeScale).toInt(), (height / reactNativeScale).toInt()
408
313
  )
@@ -418,7 +323,7 @@ class VirtualRenderer(
418
323
  }
419
324
 
420
325
  reactSurfaceId = fabricUiManager.startSurface(
421
- reactSurfaceView,
326
+ surfaceView,
422
327
  moduleName,
423
328
  Arguments.fromBundle(initialProperties),
424
329
  View.MeasureSpec.makeMeasureSpec(
@@ -433,10 +338,11 @@ class VirtualRenderer(
433
338
  reactContext.removeLifecycleEventListener(fabricUiManager)
434
339
  // trigger ui-managers onHostResume to make sure the surface is rendered properly even when AA only is starting without the phone app
435
340
  fabricUiManager.onHostResume()
436
- } else {
437
- (reactSurfaceView.parent as ViewGroup).removeView(reactSurfaceView)
341
+
342
+ reactSurfaceView = surfaceView
438
343
  }
439
344
 
345
+
440
346
  val rootContainer = FrameLayout(themedContext).apply {
441
347
  layoutParams = FrameLayout.LayoutParams(
442
348
  FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
@@ -457,8 +363,15 @@ class VirtualRenderer(
457
363
  private fun getClusterSplashScreen(
458
364
  context: Context, containerHeight: Int, containerWidth: Int
459
365
  ): View {
366
+ val root = FrameLayout(context).apply {
367
+ layoutParams = ViewGroup.LayoutParams(
368
+ ViewGroup.LayoutParams.MATCH_PARENT,
369
+ ViewGroup.LayoutParams.MATCH_PARENT
370
+ )
371
+ }
372
+
460
373
  val layout =
461
- LayoutInflater.from(context).inflate(R.layout.cluster_splashscreen, null, false)
374
+ LayoutInflater.from(context).inflate(R.layout.cluster_splashscreen, root, false)
462
375
  val text = layout.findViewById<TextView>(R.id.splash_text)
463
376
 
464
377
  AppInfo.getApplicationIcon(context)?.let {
@@ -506,15 +419,9 @@ class VirtualRenderer(
506
419
  fun removeRenderer(moduleId: String) {
507
420
  val renderer = virtualRenderer[moduleId]
508
421
 
509
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
510
- if (renderer?.isUiManagerInitialized() == true) {
511
- renderer.reactSurfaceId?.let {
512
- renderer.fabricUiManager.stopSurface(it)
513
- }
514
- }
515
- } else {
516
- if (renderer?.isReactRootViewInitialized() == true) {
517
- renderer.reactRootView.unmountReactApplication()
422
+ if (renderer?.isUiManagerInitialized() == true) {
423
+ renderer.reactSurfaceId?.let {
424
+ renderer.fabricUiManager.stopSurface(it)
518
425
  }
519
426
  }
520
427
 
@@ -6,6 +6,7 @@
6
6
  //
7
7
 
8
8
  import CarPlay
9
+ import React
9
10
 
10
11
  class AutoPlayScene: UIResponder {
11
12
  var initialProperties: [String: Any] = [:]
@@ -53,7 +54,10 @@ class AutoPlayScene: UIResponder {
53
54
  }
54
55
 
55
56
  func disconnect() {
56
- NitroSurface.stop(self.window?.rootViewController?.view)
57
+ if let view = self.window?.rootViewController?.view, let rootView = view as? RCTSurfaceHostingProxyRootView {
58
+ rootView.surface.stop()
59
+ }
60
+
57
61
  self.window = nil
58
62
  isConnected = false
59
63
 
@@ -54,18 +54,27 @@ class HeadUnitSceneDelegate: AutoPlayScene, CPTemplateApplicationSceneDelegate {
54
54
 
55
55
  func templateApplicationScene(
56
56
  _ templateApplicationScene: CPTemplateApplicationScene,
57
- didDisconnectInterfaceController interfaceController:
58
- CPInterfaceController
57
+ didDisconnect interfaceController: CPInterfaceController,
58
+ from window: CPWindow
59
59
  ) {
60
- HybridAutoPlay.emit(event: .diddisconnect)
61
-
62
60
  if let mapTemplate = try? templateStore.getTemplate(
63
61
  templateId: SceneStore.rootModuleName
64
62
  ) as? MapTemplate {
65
63
  mapTemplate.stopNavigation()
66
64
  }
67
-
65
+
68
66
  disconnect()
67
+
68
+ HybridAutoPlay.emit(event: .diddisconnect)
69
+ }
70
+
71
+ func templateApplicationScene(
72
+ _ templateApplicationScene: CPTemplateApplicationScene,
73
+ didDisconnectInterfaceController interfaceController:
74
+ CPInterfaceController
75
+ ) {
76
+ disconnect()
77
+ HybridAutoPlay.emit(event: .diddisconnect)
69
78
  }
70
79
 
71
80
  func sceneWillResignActive(_ scene: UIScene) {
@@ -84,7 +84,7 @@ class SceneStore {
84
84
  return store[SceneStore.rootModuleName]
85
85
  }
86
86
 
87
- static func getRootTraitCollection() -> UITraitCollection {
88
- return store[SceneStore.rootModuleName]!.traitCollection
87
+ static func getRootTraitCollection() -> UITraitCollection? {
88
+ return store[SceneStore.rootModuleName]?.traitCollection
89
89
  }
90
90
  }
@@ -42,10 +42,14 @@ class AutoPlayHeaderProviding: AutoPlayTemplate {
42
42
  func setBarButtons(template: CPTemplate, barButtons: [NitroAction]?) {
43
43
  guard let template = template as? CPBarButtonProviding else { return }
44
44
 
45
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
46
+ return
47
+ }
48
+
45
49
  if let headerActions = barButtons {
46
50
  let parsedActions = Parser.parseHeaderActions(
47
51
  headerActions: headerActions,
48
- traitCollection: SceneStore.getRootTraitCollection()
52
+ traitCollection: traitCollection
49
53
  )
50
54
 
51
55
  template.backButton = parsedActions.backButton
@@ -24,17 +24,17 @@ class GridTemplate: AutoPlayHeaderProviding {
24
24
  init(config: GridTemplateConfig) {
25
25
  self.config = config
26
26
  buttons = config.buttons
27
-
27
+
28
28
  template = CPGridTemplate(
29
29
  title: Parser.parseText(text: config.title),
30
30
  gridButtons: GridTemplate.parseButtons(buttons: buttons),
31
31
  id: config.id
32
32
  )
33
-
33
+
34
34
  super.init()
35
-
35
+
36
36
  barButtons = config.headerActions
37
-
37
+
38
38
  }
39
39
 
40
40
  static func parseButtons(buttons: [NitroGridButton]) -> [CPGridButton] {
@@ -47,7 +47,9 @@ class GridTemplate: AutoPlayHeaderProviding {
47
47
  gridButtonHeight = 44
48
48
  }
49
49
 
50
- let traitCollection = SceneStore.getRootTraitCollection()
50
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
51
+ return []
52
+ }
51
53
 
52
54
  return buttons.compactMap { button in
53
55
  var image: UIImage?
@@ -23,7 +23,7 @@ class InformationTemplate: AutoPlayHeaderProviding {
23
23
 
24
24
  init(config: InformationTemplateConfig) {
25
25
  self.config = config
26
-
26
+
27
27
  section = config.section
28
28
 
29
29
  template = CPInformationTemplate(
@@ -33,9 +33,9 @@ class InformationTemplate: AutoPlayHeaderProviding {
33
33
  actions: Parser.parseInformationActions(actions: config.actions),
34
34
  id: config.id
35
35
  )
36
-
37
- super.init();
38
-
36
+
37
+ super.init()
38
+
39
39
  barButtons = config.headerActions
40
40
  }
41
41
 
@@ -23,7 +23,7 @@ class ListTemplate: AutoPlayHeaderProviding {
23
23
 
24
24
  init(config: ListTemplateConfig) {
25
25
  self.config = config
26
-
26
+
27
27
  sections = config.sections
28
28
 
29
29
  template = CPListTemplate(
@@ -32,9 +32,9 @@ class ListTemplate: AutoPlayHeaderProviding {
32
32
  assistantCellConfiguration: nil,
33
33
  id: config.id
34
34
  )
35
-
35
+
36
36
  super.init()
37
-
37
+
38
38
  barButtons = config.headerActions
39
39
  }
40
40
 
@@ -42,11 +42,15 @@ class ListTemplate: AutoPlayHeaderProviding {
42
42
  override func _invalidate() {
43
43
  setBarButtons(template: template, barButtons: barButtons)
44
44
 
45
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
46
+ return
47
+ }
48
+
45
49
  template.updateSections(
46
50
  Parser.parseSections(
47
51
  sections: sections,
48
52
  updateSection: self.updateSection(section:sectionIndex:),
49
- traitCollection: SceneStore.getRootTraitCollection()
53
+ traitCollection: traitCollection
50
54
  )
51
55
  )
52
56
  }
@@ -45,10 +45,10 @@ class MapTemplate: AutoPlayHeaderProviding,
45
45
 
46
46
  init(config: MapTemplateConfig) {
47
47
  self.config = config
48
-
48
+
49
49
  mapButtons = config.mapButtons
50
50
  visibleTravelEstimate = config.visibleTravelEstimate
51
-
51
+
52
52
  template = CPMapTemplate(id: config.id)
53
53
 
54
54
  if let initialProperties = SceneStore.getRootScene()?.initialProperties,
@@ -81,12 +81,16 @@ class MapTemplate: AutoPlayHeaderProviding,
81
81
  }
82
82
 
83
83
  func parseMapButtons(mapButtons: [NitroMapButton]) -> [CPMapButton] {
84
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
85
+ return []
86
+ }
87
+
84
88
  return mapButtons.map { button in
85
89
  if let glyphImage = button.image.glyphImage,
86
90
  let icon = SymbolFont.imageFromNitroImage(
87
91
  image: glyphImage,
88
92
  size: CPButtonMaximumImageSize.height,
89
- traitCollection: SceneStore.getRootTraitCollection()
93
+ traitCollection: traitCollection
90
94
  )
91
95
  {
92
96
  return CPMapButton(image: icon) { _ in
@@ -100,7 +104,7 @@ class MapTemplate: AutoPlayHeaderProviding,
100
104
  if let assetImage = button.image.assetImage,
101
105
  let icon = Parser.parseAssetImage(
102
106
  assetImage: assetImage,
103
- traitCollection: SceneStore.getRootTraitCollection()
107
+ traitCollection: traitCollection
104
108
  )
105
109
  {
106
110
  return CPMapButton(image: icon) { _ in
@@ -180,7 +184,10 @@ class MapTemplate: AutoPlayHeaderProviding,
180
184
 
181
185
  @MainActor
182
186
  override func traitCollectionDidChange() {
183
- let traitCollection = SceneStore.getRootTraitCollection()
187
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
188
+ return
189
+ }
190
+
184
191
  let isDark = traitCollection.userInterfaceStyle == .dark
185
192
 
186
193
  config.onAppearanceDidChange?(
@@ -354,6 +361,10 @@ class MapTemplate: AutoPlayHeaderProviding,
354
361
  return
355
362
  }
356
363
 
364
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
365
+ return
366
+ }
367
+
357
368
  guard let title = Parser.parseText(text: alertConfig.title) else { return }
358
369
  let subtitle = alertConfig.subtitle.flatMap { subtitle in
359
370
  [Parser.parseText(text: subtitle)].compactMap { $0 }
@@ -361,7 +372,7 @@ class MapTemplate: AutoPlayHeaderProviding,
361
372
 
362
373
  let image = Parser.parseNitroImage(
363
374
  image: alertConfig.image,
364
- traitCollection: SceneStore.getRootTraitCollection()
375
+ traitCollection: traitCollection
365
376
  )
366
377
 
367
378
  let style = Parser.parseActionAlertStyle(
@@ -586,7 +597,7 @@ class MapTemplate: AutoPlayHeaderProviding,
586
597
 
587
598
  if var userInfo = route.userInfo as? [String: Any?] {
588
599
  userInfo["travelEstimates"] = steps.map { step in
589
- Parser.parseTravelEstiamtes(
600
+ Parser.parseTravelEstimates(
590
601
  travelEstimates: step.travelEstimates
591
602
  )
592
603
  }
@@ -599,6 +610,10 @@ class MapTemplate: AutoPlayHeaderProviding,
599
610
  func updateManeuvers(messageManeuver: NitroMessageManeuver) {
600
611
  guard let navigationSession = navigationSession else { return }
601
612
 
613
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
614
+ return
615
+ }
616
+
602
617
  let color = messageManeuver.cardBackgroundColor
603
618
  let cardBackgroundColor = Parser.parseColor(color: color)
604
619
 
@@ -615,7 +630,7 @@ class MapTemplate: AutoPlayHeaderProviding,
615
630
 
616
631
  if let symbolImage = Parser.parseNitroImage(
617
632
  image: messageManeuver.image,
618
- traitCollection: SceneStore.getRootTraitCollection()
633
+ traitCollection: traitCollection
619
634
  ) {
620
635
  maneuver.symbolImage = symbolImage
621
636
  }
@@ -635,6 +650,10 @@ class MapTemplate: AutoPlayHeaderProviding,
635
650
  return
636
651
  }
637
652
 
653
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
654
+ return
655
+ }
656
+
638
657
  if #unavailable(iOS 15.4),
639
658
  let color = maneuvers.first?.cardBackgroundColor
640
659
  {
@@ -656,7 +675,7 @@ class MapTemplate: AutoPlayHeaderProviding,
656
675
  .firstIndex(where: { $0.id == nitroManeuver.id })
657
676
  {
658
677
  navigationSession.updateEstimates(
659
- Parser.parseTravelEstiamtes(
678
+ Parser.parseTravelEstimates(
660
679
  travelEstimates: nitroManeuver.travelEstimates
661
680
  ),
662
681
  for: navigationSession.upcomingManeuvers[maneuverIndex]
@@ -674,7 +693,7 @@ class MapTemplate: AutoPlayHeaderProviding,
674
693
 
675
694
  let maneuver = Parser.parseManeuver(
676
695
  nitroManeuver: nitroManeuver,
677
- traitCollection: SceneStore.getRootTraitCollection()
696
+ traitCollection: traitCollection
678
697
  )
679
698
  upcomingManeuvers.append(maneuver)
680
699
  }
@@ -686,7 +705,7 @@ class MapTemplate: AutoPlayHeaderProviding,
686
705
  // CarPlay has a limitation of 120x18 for the symbolImage on secondaryManeuver that shows lanes only
687
706
  let secondarySymbolImage = Parser.imageFromLanes(
688
707
  laneImages: laneImages.prefix(Int(120 / 18)),
689
- traitCollection: SceneStore.getRootTraitCollection()
708
+ traitCollection: traitCollection
690
709
  )
691
710
 
692
711
  let secondaryManeuver = CPManeuver(
@@ -7,6 +7,7 @@
7
7
 
8
8
  import CarPlay
9
9
  import UIKit
10
+ import React
10
11
 
11
12
  struct HeaderActions {
12
13
  let leadingNavigationBarButtons: [CPBarButton]
@@ -452,7 +453,7 @@ class Parser {
452
453
  id: routeChoice.id,
453
454
  // we don't want to keep the origin travel estimate
454
455
  travelEstimates: routeChoice.steps[1...].map { step in
455
- parseTravelEstiamtes(travelEstimates: step.travelEstimates)
456
+ parseTravelEstimates(travelEstimates: step.travelEstimates)
456
457
  }
457
458
  )
458
459
 
@@ -492,7 +493,7 @@ class Parser {
492
493
  }
493
494
  }
494
495
 
495
- static func parseTravelEstiamtes(travelEstimates: TravelEstimates)
496
+ static func parseTravelEstimates(travelEstimates: TravelEstimates)
496
497
  -> CPTravelEstimates
497
498
  {
498
499
  return CPTravelEstimates(
@@ -515,7 +516,7 @@ class Parser {
515
516
  traitCollection: traitCollection
516
517
  )
517
518
 
518
- maneuver.initialTravelEstimates = Parser.parseTravelEstiamtes(
519
+ maneuver.initialTravelEstimates = Parser.parseTravelEstimates(
519
520
  travelEstimates: nitroManeuver.travelEstimates
520
521
  )
521
522
  maneuver.symbolImage = Parser.parseNitroImage(
@@ -739,7 +740,7 @@ class Parser {
739
740
  }
740
741
 
741
742
  static func doubleToColor(value: Double) -> UIColor {
742
- return NitroConvert.uiColor(value)
743
+ return RCTConvert.uiColor(value)
743
744
  }
744
745
 
745
746
  static func parseNitroImage(
@@ -767,7 +768,7 @@ class Parser {
767
768
  assetImage: AssetImage,
768
769
  traitCollection: UITraitCollection
769
770
  ) -> UIImage? {
770
- let uiImage = NitroConvert.uiImage([
771
+ let uiImage = RCTConvert.uiImage([
771
772
  "height": assetImage.height, "width": assetImage.width,
772
773
  "uri": assetImage.uri, "scale": assetImage.scale,
773
774
  "__packager_asset": assetImage.packager_asset,
@@ -776,6 +777,10 @@ class Parser {
776
777
  guard let color = assetImage.color else {
777
778
  return uiImage
778
779
  }
780
+
781
+ guard let uiImage = uiImage else {
782
+ return nil
783
+ }
779
784
 
780
785
  return getTintedImageAsset(
781
786
  color: color,
@@ -28,7 +28,7 @@ class SearchTemplate: AutoPlayTemplate, CPSearchTemplateDelegate {
28
28
  init(config: SearchTemplateConfig) {
29
29
  self.config = config
30
30
  results = config.results
31
-
31
+
32
32
  template = CPSearchTemplate(id: config.id)
33
33
  }
34
34
 
@@ -49,9 +49,13 @@ class SearchTemplate: AutoPlayTemplate, CPSearchTemplateDelegate {
49
49
  return
50
50
  }
51
51
 
52
+ guard let traitCollection = SceneStore.getRootTraitCollection() else {
53
+ return
54
+ }
55
+
52
56
  let listItems = Parser.parseSearchResults(
53
57
  section: results,
54
- traitCollection: SceneStore.getRootTraitCollection()
58
+ traitCollection: traitCollection
55
59
  )
56
60
  completionHandler(listItems)
57
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iternio/react-native-auto-play",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -1,19 +0,0 @@
1
- //
2
- // NitroConvert.h
3
- // Pods
4
- //
5
- // Created by Manuel Auer on 04.11.25.
6
- //
7
-
8
- // this is required for old architecture support since the React pod can not be imported
9
-
10
- #import <UIKit/UIKit.h>
11
-
12
- NS_ASSUME_NONNULL_BEGIN
13
-
14
- @interface NitroConvert : NSObject
15
- + (UIColor *)uiColor:(id)json;
16
- + (UIImage *)uiImage:(id)json;
17
- @end
18
-
19
- NS_ASSUME_NONNULL_END
@@ -1,21 +0,0 @@
1
- //
2
- // NitroConvert.m
3
- // Pods
4
- //
5
- // Created by Manuel Auer on 04.11.25.
6
- //
7
-
8
- // this is required for old architecture support since the React pod can not be imported
9
-
10
- #import "NitroConvert.h"
11
- #import <React/RCTConvert.h>
12
-
13
- @implementation NitroConvert
14
- + (UIColor *)uiColor:(id)json {
15
- return [RCTConvert UIColor:json];
16
- }
17
-
18
- + (nonnull UIImage *)uiImage:(nonnull id)json {
19
- return [RCTConvert UIImage:json];
20
- }
21
- @end
@@ -1,10 +0,0 @@
1
- //
2
- // NitroSurface.h
3
- // Pods
4
- //
5
- // Created by Manuel Auer on 28.11.25.
6
- //
7
-
8
- @interface NitroSurface : NSObject
9
- + (void)stop:(nullable UIView *)view;
10
- @end
@@ -1,51 +0,0 @@
1
- //
2
- // NitroSurface.m
3
- // Pods
4
- //
5
- // Created by Manuel Auer on 28.11.25.
6
- //
7
-
8
- #import "NitroSurface.h"
9
- #import <React/RCTInvalidating.h>
10
- #import <React/RCTRootView.h>
11
- #import <React/RCTSurface.h>
12
- #import <React/RCTSurfaceHostingProxyRootView.h>
13
-
14
- @implementation NitroSurface
15
-
16
- + (void)stop:(nullable UIView *)view {
17
- if (view == nil) {
18
- NSLog(@"[AutoPlay] View is nil, ignoring");
19
- return;
20
- }
21
-
22
- if ([view isKindOfClass:[RCTSurfaceHostingProxyRootView class]]) {
23
- RCTSurfaceHostingProxyRootView *rootView =
24
- (RCTSurfaceHostingProxyRootView *)view;
25
- RCTSurface *surface = rootView.surface;
26
-
27
- if (surface == nil) {
28
- NSLog(@"[AutoPlay] surface == nil, cannot stop");
29
- return;
30
- }
31
-
32
- [surface stop];
33
- return;
34
- }
35
-
36
- if ([view isKindOfClass:[RCTRootView class]]) {
37
- RCTRootView *rootView = (RCTRootView *)view;
38
- UIView<RCTInvalidating> *contentView = (UIView<RCTInvalidating> *)rootView.contentView;
39
-
40
- if ([contentView conformsToProtocol:@protocol(RCTInvalidating)]) {
41
- [contentView invalidate];
42
- } else {
43
- NSLog(@"[AutoPlay] contentView does not conform to RCTInvalidating");
44
- }
45
- return;
46
- }
47
-
48
- NSLog(@"[AutoPlay] View is not recognized type, ignoring");
49
- }
50
-
51
- @end