@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 +1 -1
- package/ReactNativeAutoPlay.podspec +1 -0
- package/android/build.gradle +0 -1
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VirtualRenderer.kt +32 -125
- package/ios/scenes/AutoPlayScene.swift +5 -1
- package/ios/scenes/HeadUnitSceneDelegate.swift +14 -5
- package/ios/scenes/SceneStore.swift +2 -2
- package/ios/templates/AutoPlayTemplate.swift +5 -1
- package/ios/templates/GridTemplate.swift +7 -5
- package/ios/templates/InformationTemplate.swift +4 -4
- package/ios/templates/ListTemplate.swift +8 -4
- package/ios/templates/MapTemplate.swift +30 -11
- package/ios/templates/Parser.swift +10 -5
- package/ios/templates/SearchTemplate.swift +6 -2
- package/package.json +1 -1
- package/ios/ReactHelpers/NitroConvert.h +0 -19
- package/ios/ReactHelpers/NitroConvert.m +0 -21
- package/ios/ReactHelpers/NitroSurface.h +0 -10
- package/ios/ReactHelpers/NitroSurface.m +0 -51
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`
|
|
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.
|
package/android/build.gradle
CHANGED
|
@@ -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 {
|
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VirtualRenderer.kt
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 &&
|
|
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 (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
437
|
-
|
|
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,
|
|
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 (
|
|
510
|
-
|
|
511
|
-
renderer.
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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]
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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 =
|
|
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:
|
|
58
|
+
traitCollection: traitCollection
|
|
55
59
|
)
|
|
56
60
|
completionHandler(listItems)
|
|
57
61
|
|
package/package.json
CHANGED
|
@@ -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,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
|