@iternio/react-native-auto-play 0.5.6 → 0.5.7

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 (56) hide show
  1. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAndroidWindowInformation.kt +49 -0
  2. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAutoPlay.kt +1 -0
  3. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VirtualRenderer.kt +116 -29
  4. package/lib/HybridAutoPlay.d.ts +2 -0
  5. package/lib/HybridAutoPlay.js +2 -0
  6. package/lib/components/OnAppearedChildRenderer.d.ts +10 -0
  7. package/lib/components/OnAppearedChildRenderer.js +26 -0
  8. package/lib/components/WindowInformationWrapper.d.ts +15 -0
  9. package/lib/components/WindowInformationWrapper.js +17 -0
  10. package/lib/hooks/useIsAutoPlayFocused.d.ts +7 -0
  11. package/lib/hooks/useIsAutoPlayFocused.js +20 -0
  12. package/lib/hybrid/HybridAndroidWindowInformation.android.d.ts +2 -0
  13. package/lib/hybrid/HybridAndroidWindowInformation.android.js +2 -0
  14. package/lib/hybrid/HybridAndroidWindowInformation.d.ts +2 -0
  15. package/lib/hybrid/HybridAndroidWindowInformation.js +1 -0
  16. package/lib/hybrid.d.ts +2 -0
  17. package/lib/hybrid.js +2 -0
  18. package/lib/index.d.ts +2 -1
  19. package/lib/index.js +2 -1
  20. package/lib/scenes/AutoPlayCluster.js +6 -1
  21. package/lib/specs/AndroidWindowInformation.nitro.d.ts +22 -0
  22. package/lib/specs/AndroidWindowInformation.nitro.js +1 -0
  23. package/lib/specs/AutomotivePermissionRequestTemplate.d.ts +11 -0
  24. package/lib/specs/AutomotivePermissionRequestTemplate.js +1 -0
  25. package/lib/specs/AutomotivePermissionRequestTemplate.nitro.d.ts +11 -0
  26. package/lib/specs/AutomotivePermissionRequestTemplate.nitro.js +1 -0
  27. package/lib/templates/AutomotivePermissionRequestTemplate.d.ts +23 -0
  28. package/lib/templates/AutomotivePermissionRequestTemplate.js +18 -0
  29. package/lib/templates/MapTemplate.js +6 -1
  30. package/lib/types/Glyphmap.d.ts +4105 -0
  31. package/lib/types/Glyphmap.js +4105 -0
  32. package/lib/types/RootComponent.d.ts +4 -0
  33. package/lib/utils/NitroOptionsPanel.d.ts +67 -0
  34. package/lib/utils/NitroOptionsPanel.js +48 -0
  35. package/nitro.json +6 -0
  36. package/nitrogen/generated/android/ReactNativeAutoPlay+autolinking.cmake +2 -0
  37. package/nitrogen/generated/android/ReactNativeAutoPlayOnLoad.cpp +18 -0
  38. package/nitrogen/generated/android/c++/JFunc_void_WindowInformation.hpp +77 -0
  39. package/nitrogen/generated/android/c++/JHybridAndroidWindowInformationSpec.cpp +68 -0
  40. package/nitrogen/generated/android/c++/JHybridAndroidWindowInformationSpec.hpp +63 -0
  41. package/nitrogen/generated/android/c++/JWindowInformation.hpp +65 -0
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Func_void_WindowInformation.kt +80 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAndroidWindowInformationSpec.kt +59 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/WindowInformation.kt +61 -0
  45. package/nitrogen/generated/shared/c++/HybridAndroidWindowInformationSpec.cpp +21 -0
  46. package/nitrogen/generated/shared/c++/HybridAndroidWindowInformationSpec.hpp +65 -0
  47. package/nitrogen/generated/shared/c++/WindowInformation.hpp +91 -0
  48. package/package.json +1 -1
  49. package/src/components/WindowInformationWrapper.tsx +30 -0
  50. package/src/hybrid/HybridAndroidWindowInformation.android.ts +5 -0
  51. package/src/hybrid/HybridAndroidWindowInformation.ts +3 -0
  52. package/src/index.ts +2 -1
  53. package/src/scenes/AutoPlayCluster.ts +6 -1
  54. package/src/specs/AndroidWindowInformation.nitro.ts +24 -0
  55. package/src/templates/MapTemplate.ts +6 -1
  56. package/src/types/RootComponent.ts +4 -0
@@ -0,0 +1,49 @@
1
+ package com.margelo.nitro.swe.iternio.reactnativeautoplay
2
+
3
+ import java.util.concurrent.ConcurrentHashMap
4
+ import java.util.concurrent.CopyOnWriteArrayList
5
+
6
+ class HybridAndroidWindowInformation : HybridAndroidWindowInformationSpec() {
7
+ init {
8
+ windowInformationListeners.clear()
9
+ }
10
+
11
+ override fun addWindowInformationListener(
12
+ moduleName: String, callback: (WindowInformation) -> Unit
13
+ ): () -> Unit {
14
+ val callbacks = windowInformationListeners.getOrPut(moduleName) {
15
+ CopyOnWriteArrayList()
16
+ }
17
+ callbacks.add(callback)
18
+
19
+ VirtualRenderer.getWindowInformation(moduleName)?.let {
20
+ callback(it)
21
+ }
22
+
23
+ return {
24
+ windowInformationListeners[moduleName]?.let {
25
+ it.remove(callback)
26
+ if (it.isEmpty()) {
27
+ windowInformationListeners.remove(moduleName)
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ companion object {
34
+ const val TAG = "HybridAndroidWindowInformation"
35
+
36
+ private val windowInformationListeners =
37
+ ConcurrentHashMap<String, CopyOnWriteArrayList<(WindowInformation) -> Unit>>()
38
+
39
+ fun removeListeners(templateId: String) {
40
+ windowInformationListeners.remove(templateId)
41
+ }
42
+
43
+ fun emitWindowInformation(moduleName: String, window: WindowInformation) {
44
+ windowInformationListeners[moduleName]?.forEach {
45
+ it(window)
46
+ }
47
+ }
48
+ }
49
+ }
@@ -264,6 +264,7 @@ class HybridAutoPlay : HybridAutoPlaySpec() {
264
264
  fun removeListeners(templateId: String) {
265
265
  renderStateListeners.remove(templateId)
266
266
  safeAreaInsetsListeners.remove(templateId)
267
+ HybridAndroidWindowInformation.removeListeners(templateId)
267
268
  }
268
269
 
269
270
  fun emit(event: EventName) {
@@ -42,20 +42,24 @@ class VirtualRenderer(
42
42
  ) {
43
43
  private var virtualDisplay: VirtualDisplay? = null
44
44
 
45
- private lateinit var reactSurfaceImpl: ReactSurfaceImpl
45
+ private var reactSurfaceImpl: ReactSurfaceImpl? = null
46
46
  private var reactSurfaceView: ReactSurfaceView? = null
47
47
  private var reactSurfaceId: Int? = null
48
48
 
49
49
  private var height: Int = 0
50
50
  private var width: Int = 0
51
+ private var dpi: Int = 0
51
52
 
52
53
  private var splashWillDisappear = false
53
54
 
54
55
  /**
55
- * scale is the actual scale factor required to calculate proper insets and is passed in initialProperties to js side
56
+ * scale is the actual scale factor required to calculate proper insets and is passed in initialProperties to js side.
57
+ * The density is derived from the surface container's dpi so it always matches the VirtualDisplay,
58
+ * and is recomputed on every onSurfaceAvailable since the host may recreate the surface with new metrics.
56
59
  */
57
- private val virtualScreenDensity = context.resources.displayMetrics.density
58
- val scale = BuildConfig.SCALE_FACTOR * virtualScreenDensity
60
+ private var virtualScreenDensity = context.resources.displayMetrics.density
61
+ var scale = BuildConfig.SCALE_FACTOR * virtualScreenDensity
62
+ private set
59
63
 
60
64
  private fun isSurfaceReady(surfaceContainer: SurfaceContainer): Boolean {
61
65
  return surfaceContainer.surface != null && surfaceContainer.dpi != 0 && surfaceContainer.height != 0 && surfaceContainer.width != 0
@@ -68,7 +72,7 @@ class VirtualRenderer(
68
72
  val areaDebouncer = Debouncer(200)
69
73
 
70
74
  // 12dp seems to be the default margin on AA for the ETA widget and the maneuver so use it as fallback
71
- val defaultMargin = (12.0 * context.resources.displayMetrics.density).toInt()
75
+ val defaultMargin get() = (12.0 * virtualScreenDensity).toInt()
72
76
  var minMargin = Int.MAX_VALUE
73
77
  var stableArea = Rect(0, 0, 0, 0)
74
78
  var visibleArea = Rect(0, 0, 0, 0)
@@ -78,25 +82,16 @@ class VirtualRenderer(
78
82
  return
79
83
  }
80
84
 
81
- val manager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
82
- virtualDisplay?.release()
83
- virtualDisplay = manager.createVirtualDisplay(
84
- moduleName,
85
- surfaceContainer.width,
86
- surfaceContainer.height,
87
- surfaceContainer.dpi,
88
- surfaceContainer.surface,
89
- DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION,
90
- )
91
-
92
- val display = virtualDisplay?.display ?: return
93
- height = surfaceContainer.height
94
- width = surfaceContainer.width
95
-
96
- initRenderer(display)
85
+ applySurface(surfaceContainer)
97
86
  }
98
87
 
99
88
  override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
89
+ // the host re-emits visible/stable areas for the next surface, so drop state derived
90
+ // from the old surface instead of combining it with the new dimensions
91
+ minMargin = Int.MAX_VALUE
92
+ stableArea = Rect(0, 0, 0, 0)
93
+ visibleArea = Rect(0, 0, 0, 0)
94
+
100
95
  virtualDisplay?.release()
101
96
  virtualDisplay = null
102
97
  }
@@ -230,6 +225,46 @@ class VirtualRenderer(
230
225
  })
231
226
  }
232
227
 
228
+ @MainThread
229
+ private fun applySurface(surfaceContainer: SurfaceContainer) {
230
+ val surfaceWidth = surfaceContainer.width
231
+ val surfaceHeight = surfaceContainer.height
232
+
233
+ // The host signals surface size changes only via onSurfaceDestroyed -> onSurfaceAvailable
234
+ // with new dimensions, so all size-derived state (layout params, scale, Fabric measure
235
+ // specs) must be recomputed when the metrics differ from the current ones. The react
236
+ // surface itself is kept alive so JS state survives the resize; the JS side is informed
237
+ // via the windowInformation listener since the window initial prop stays stale.
238
+ val isResize = reactSurfaceView != null && (width != surfaceWidth
239
+ || height != surfaceHeight || dpi != surfaceContainer.dpi)
240
+
241
+ val manager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
242
+ virtualDisplay?.release()
243
+ virtualDisplay = manager.createVirtualDisplay(
244
+ moduleName,
245
+ surfaceWidth,
246
+ surfaceHeight,
247
+ surfaceContainer.dpi,
248
+ surfaceContainer.surface,
249
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION,
250
+ )
251
+
252
+ val display = virtualDisplay?.display ?: return
253
+ height = surfaceHeight
254
+ width = surfaceWidth
255
+ dpi = surfaceContainer.dpi
256
+ virtualScreenDensity = surfaceContainer.dpi / 160f
257
+ scale = BuildConfig.SCALE_FACTOR * virtualScreenDensity
258
+
259
+ initRenderer(display)
260
+
261
+ if (isResize) {
262
+ getWindowInformation(moduleName)?.let {
263
+ HybridAndroidWindowInformation.emitWindowInformation(moduleName, it)
264
+ }
265
+ }
266
+ }
267
+
233
268
  private fun getMapTemplateConfig(): MapTemplateConfig? {
234
269
  val screenManager = AndroidAutoScreen.getScreen(moduleName)?.screenManager ?: return null
235
270
  val marker = screenManager.top.marker ?: return null
@@ -290,19 +325,48 @@ class VirtualRenderer(
290
325
  val appTheme = context.applicationContext.applicationInfo.theme
291
326
  val themedContext = ContextThemeWrapper(context.applicationContext, appTheme)
292
327
 
293
- if (!this@VirtualRenderer::reactSurfaceImpl.isInitialized) {
294
- reactSurfaceImpl = ReactSurfaceImpl(themedContext, moduleName, initialProperties)
328
+ val surfaceImpl = reactSurfaceImpl ?: ReactSurfaceImpl(
329
+ themedContext, moduleName, initialProperties
330
+ ).also {
331
+ reactSurfaceImpl = it
295
332
  }
296
333
 
297
334
  var splashScreenView: View? = null
298
335
 
299
336
  reactSurfaceView?.let {
300
337
  (it.parent as ViewGroup).removeView(it)
338
+
339
+ // the surface may have been recreated with new dimensions, so reapply all
340
+ // size-derived values and push the new layout specs into the running Fabric surface
341
+ it.layoutParams = FrameLayout.LayoutParams(
342
+ (width / reactNativeScale).toInt(), (height / reactNativeScale).toInt()
343
+ )
344
+ it.scaleX = reactNativeScale
345
+ it.scaleY = reactNativeScale
346
+
347
+ reactSurfaceId?.let { surfaceId ->
348
+ fabricUiManager.updateRootLayoutSpecs(
349
+ surfaceId,
350
+ View.MeasureSpec.makeMeasureSpec(
351
+ (width / reactNativeScale).toInt(), View.MeasureSpec.EXACTLY
352
+ ),
353
+ View.MeasureSpec.makeMeasureSpec(
354
+ (height / reactNativeScale).toInt(), View.MeasureSpec.EXACTLY
355
+ ),
356
+ 0,
357
+ 0
358
+ )
359
+ }
301
360
  } ?: run {
361
+ // skip the splash when the surface is rebuilt after a resize, content is already loaded
302
362
  splashScreenView =
303
- if (isCluster) getClusterSplashScreen(themedContext, height, width) else null
363
+ if (isCluster && !splashWillDisappear) {
364
+ getClusterSplashScreen(themedContext, height, width)
365
+ } else {
366
+ null
367
+ }
304
368
 
305
- val surfaceView = ReactSurfaceView(themedContext, reactSurfaceImpl).apply {
369
+ val surfaceView = ReactSurfaceView(themedContext, surfaceImpl).apply {
306
370
  layoutParams = FrameLayout.LayoutParams(
307
371
  (width / reactNativeScale).toInt(), (height / reactNativeScale).toInt()
308
372
  )
@@ -402,10 +466,7 @@ class VirtualRenderer(
402
466
  }
403
467
 
404
468
  @MainThread
405
- private fun stop() {
406
- virtualDisplay?.release()
407
- virtualDisplay = null
408
-
469
+ private fun stopReactSurface() {
409
470
  val uiManager = NitroModules.applicationContext?.let {
410
471
  UIManagerHelper.getUIManager(
411
472
  it, UIManagerType.FABRIC
@@ -420,6 +481,19 @@ class VirtualRenderer(
420
481
  // Fabric already invalidated
421
482
  } finally {
422
483
  reactSurfaceId = null
484
+ reactSurfaceView = null
485
+ reactSurfaceImpl = null
486
+ }
487
+ }
488
+
489
+ @MainThread
490
+ private fun stop() {
491
+ virtualDisplay?.release()
492
+ virtualDisplay = null
493
+
494
+ try {
495
+ stopReactSurface()
496
+ } finally {
423
497
  virtualRenderer.remove(moduleName)
424
498
  }
425
499
  }
@@ -433,6 +507,19 @@ class VirtualRenderer(
433
507
  return virtualRenderer.contains(moduleId)
434
508
  }
435
509
 
510
+ fun getWindowInformation(moduleId: String): WindowInformation? {
511
+ val renderer = virtualRenderer[moduleId] ?: return null
512
+ if (renderer.width == 0 || renderer.height == 0) {
513
+ return null
514
+ }
515
+
516
+ return WindowInformation(
517
+ width = (renderer.width / renderer.scale).toInt().toDouble(),
518
+ height = (renderer.height / renderer.scale).toInt().toDouble(),
519
+ scale = renderer.scale.toDouble()
520
+ )
521
+ }
522
+
436
523
  fun removeRenderer(moduleId: String) {
437
524
  virtualRenderer[moduleId]?.stop()
438
525
  virtualRenderer.remove(moduleId)
@@ -0,0 +1,2 @@
1
+ import type { AutoPlay } from './specs/AutoPlay.nitro';
2
+ export declare const HybridAutoPlay: AutoPlay;
@@ -0,0 +1,2 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ export const HybridAutoPlay = NitroModules.createHybridObject('AutoPlay');
@@ -0,0 +1,10 @@
1
+ type Props = {
2
+ children: React.ReactNode;
3
+ moduleName: string;
4
+ };
5
+ /**
6
+ * renders the passed children when the specified scene/screen appeared
7
+ * this makes sure child hooks are executed only when the map template is ready
8
+ */
9
+ export default function OnAppearedChildRenderer({ children, moduleName }: Props): import("react").ReactNode;
10
+ export {};
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { HybridAutoPlay } from '../hybrid/HybridAutoPlay';
3
+ /**
4
+ * renders the passed children when the specified scene/screen appeared
5
+ * this makes sure child hooks are executed only when the map template is ready
6
+ */
7
+ export default function OnAppearedChildRenderer({ children, moduleName }) {
8
+ const [didAppear, setDidAppear] = useState(false);
9
+ useEffect(() => {
10
+ let remove = HybridAutoPlay.addListenerRenderState(moduleName, (renderState) => {
11
+ if (renderState === 'didAppear') {
12
+ remove?.();
13
+ remove = null;
14
+ setDidAppear(true);
15
+ }
16
+ });
17
+ return () => {
18
+ remove?.();
19
+ remove = null;
20
+ };
21
+ }, [moduleName]);
22
+ if (didAppear) {
23
+ return children;
24
+ }
25
+ return null;
26
+ }
@@ -0,0 +1,15 @@
1
+ import type React from 'react';
2
+ import type { RootComponentInitialProps } from '../types/RootComponent';
3
+ /**
4
+ * Renders a user-provided root component and keeps its `window` prop up to date when the host
5
+ * recreates the render surface with new dimensions (some head units run Android Auto windowed
6
+ * and resize it at runtime), so consumers can simply read `props.window` without registering a
7
+ * listener themselves.
8
+ * On iOS this only passes the initial props through — CarPlay windows keep their size for the
9
+ * lifetime of a scene, so there is nothing to subscribe to.
10
+ */
11
+ export declare function WindowInformationWrapper({ moduleName, component, componentProps, }: {
12
+ moduleName: string;
13
+ component: React.ComponentType<RootComponentInitialProps>;
14
+ componentProps: RootComponentInitialProps;
15
+ }): React.ReactElement<RootComponentInitialProps, string | React.JSXElementConstructor<any>>;
@@ -0,0 +1,17 @@
1
+ import { createElement, useEffect, useState } from 'react';
2
+ import { HybridAndroidWindowInformation } from '../hybrid/HybridAndroidWindowInformation';
3
+ /**
4
+ * Renders a user-provided root component and keeps its `window` prop up to date when the host
5
+ * recreates the render surface with new dimensions (some head units run Android Auto windowed
6
+ * and resize it at runtime), so consumers can simply read `props.window` without registering a
7
+ * listener themselves.
8
+ * On iOS this only passes the initial props through — CarPlay windows keep their size for the
9
+ * lifetime of a scene, so there is nothing to subscribe to.
10
+ */
11
+ export function WindowInformationWrapper({ moduleName, component, componentProps, }) {
12
+ const [window, setWindow] = useState(componentProps.window);
13
+ useEffect(() => {
14
+ return HybridAndroidWindowInformation?.addWindowInformationListener(moduleName, setWindow);
15
+ }, [moduleName]);
16
+ return createElement(component, { ...componentProps, window });
17
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A hook to determine if the CarPlay/Android Auto screen is currently focused (visible).
3
+ *
4
+ * @param moduleName The name of the module to listen to.
5
+ * @returns `true` if the screen is focused, `false` otherwise.
6
+ */
7
+ export declare function useIsAutoPlayFocused(moduleName: string): boolean;
@@ -0,0 +1,20 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { HybridAutoPlay } from '..';
3
+ /**
4
+ * A hook to determine if the CarPlay/Android Auto screen is currently focused (visible).
5
+ *
6
+ * @param moduleName The name of the module to listen to.
7
+ * @returns `true` if the screen is focused, `false` otherwise.
8
+ */
9
+ export function useIsAutoPlayFocused(moduleName) {
10
+ const [isFocused, setIsFocused] = useState(false);
11
+ useEffect(() => {
12
+ const remove = HybridAutoPlay.addListenerRenderState(moduleName, (state) => {
13
+ setIsFocused(state === 'didAppear');
14
+ });
15
+ return () => {
16
+ remove();
17
+ };
18
+ }, [moduleName]);
19
+ return isFocused;
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { AndroidWindowInformation } from '../specs/AndroidWindowInformation.nitro';
2
+ export declare const HybridAndroidWindowInformation: AndroidWindowInformation | null;
@@ -0,0 +1,2 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ export const HybridAndroidWindowInformation = NitroModules.createHybridObject('AndroidWindowInformation');
@@ -0,0 +1,2 @@
1
+ import type { AndroidWindowInformation } from '../specs/AndroidWindowInformation.nitro';
2
+ export declare const HybridAndroidWindowInformation: AndroidWindowInformation | null;
@@ -0,0 +1 @@
1
+ export const HybridAndroidWindowInformation = null;
@@ -0,0 +1,2 @@
1
+ import type { AutoPlay as NitroAutoPlay } from './specs/AutoPlay.nitro';
2
+ export declare const HybridAutoPlay: NitroAutoPlay;
package/lib/hybrid.js ADDED
@@ -0,0 +1,2 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ export const HybridAutoPlay = NitroModules.createHybridObject('AutoPlay');
package/lib/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { HybridAndroidAutoTelemetry } from './hybrid/HybridAndroidAutoTelemetry';
2
+ import { HybridAndroidWindowInformation } from './hybrid/HybridAndroidWindowInformation';
2
3
  import { HybridAutoPlay } from './hybrid/HybridAutoPlay';
3
4
  import { HybridVoice } from './hybrid/HybridVoice';
4
5
  import type { AndroidAutomotive } from './specs/AndroidAutomotive.nitro';
5
- export { HybridAndroidAutoTelemetry, HybridAutoPlay, HybridVoice };
6
+ export { HybridAndroidAutoTelemetry, HybridAndroidWindowInformation, HybridAutoPlay, HybridVoice };
6
7
  export declare const HybridAndroidAutomotive: AndroidAutomotive | null;
7
8
  /**
8
9
  * These are the static module names for the app running on the mobile device, head unit screen and the CarPlay dashboard.
package/lib/index.js CHANGED
@@ -2,10 +2,11 @@ import { Platform } from 'react-native';
2
2
  import { NitroModules } from 'react-native-nitro-modules';
3
3
  import AutoPlayHeadlessJsTask from './AutoPlayHeadlessJsTask';
4
4
  import { HybridAndroidAutoTelemetry } from './hybrid/HybridAndroidAutoTelemetry';
5
+ import { HybridAndroidWindowInformation } from './hybrid/HybridAndroidWindowInformation';
5
6
  import { HybridAutoPlay } from './hybrid/HybridAutoPlay';
6
7
  import { HybridVoice } from './hybrid/HybridVoice';
7
8
  AutoPlayHeadlessJsTask.registerHeadlessTask(HybridAutoPlay);
8
- export { HybridAndroidAutoTelemetry, HybridAutoPlay, HybridVoice };
9
+ export { HybridAndroidAutoTelemetry, HybridAndroidWindowInformation, HybridAutoPlay, HybridVoice };
9
10
  export const HybridAndroidAutomotive = Platform.OS === 'android'
10
11
  ? NitroModules.createHybridObject('AndroidAutomotive')
11
12
  : null;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { AppRegistry, Platform } from 'react-native';
3
3
  import { NitroModules } from 'react-native-nitro-modules';
4
4
  import { SafeAreaInsetsProvider } from '../components/SafeAreaInsetsContext';
5
+ import { WindowInformationWrapper } from '../components/WindowInformationWrapper';
5
6
  import { NitroImageUtil } from '../utils/NitroImage';
6
7
  const HybridCluster = NitroModules.createHybridObject('Cluster');
7
8
  class Cluster {
@@ -49,7 +50,11 @@ class Cluster {
49
50
  AppRegistry.registerComponent(clusterId, () => (props) => React.createElement(SafeAreaInsetsProvider, {
50
51
  moduleName: clusterId,
51
52
  // biome-ignore lint/correctness/noChildrenProp: there is no other way in a ts file
52
- children: React.createElement(component, props),
53
+ children: React.createElement(WindowInformationWrapper, {
54
+ moduleName: clusterId,
55
+ component,
56
+ componentProps: props,
57
+ }),
53
58
  }));
54
59
  this.clusters[clusterId] = true;
55
60
  await HybridCluster.initRootView(clusterId);
@@ -0,0 +1,22 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules';
2
+ import type { CleanupCallback } from '../types/Event';
3
+ import type { WindowInformation } from '../types/RootComponent';
4
+ /**
5
+ * Android-only access to render-surface window dimensions. On iOS this module does not exist —
6
+ * use the platform-split `HybridAndroidWindowInformation` wrapper which resolves to `null` there.
7
+ */
8
+ export interface AndroidWindowInformation extends HybridObject<{
9
+ android: 'kotlin';
10
+ }> {
11
+ /**
12
+ * callback for window dimension changes, fired when the host recreates the render surface
13
+ * with a different size (some head units run Android Auto windowed and resize it at runtime).
14
+ * Root components rendered via a template's `component` prop do NOT need this — their
15
+ * `window` prop is kept up to date automatically. Use this listener only for
16
+ * size-dependent logic living outside a root component.
17
+ * Fires with the last known window information immediately if the surface is already initialized.
18
+ * @param moduleName one of @AutoPlayModules or a cluster scene uuid
19
+ * @namespace Android
20
+ */
21
+ addWindowInformationListener(moduleName: string, callback: (window: WindowInformation) => void): CleanupCallback;
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules';
2
+ import type { NitroAutomotivePermissionRequestTemplateConfig } from '../templates/AutomotivePermissionRequestTemplate';
3
+ import type { NitroTemplateConfig } from './AutoPlay.nitro';
4
+ interface AutomotivePermissionRequestTemplateConfig extends NitroTemplateConfig, NitroAutomotivePermissionRequestTemplateConfig {
5
+ }
6
+ export interface AutomotivePermissionRequestTemplate extends HybridObject<{
7
+ android: 'kotlin';
8
+ }> {
9
+ createAutomotivePermissionRequestTemplate(config: AutomotivePermissionRequestTemplateConfig): void;
10
+ }
11
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules';
2
+ import type { NitroAutomotivePermissionRequestTemplateConfig } from '../templates/AutomotivePermissionRequestTemplate';
3
+ import type { NitroTemplateConfig } from './AutoPlay.nitro';
4
+ interface AutomotivePermissionRequestTemplateConfig extends NitroTemplateConfig, NitroAutomotivePermissionRequestTemplateConfig {
5
+ }
6
+ export interface AutomotivePermissionRequestTemplate extends HybridObject<{
7
+ android: 'kotlin';
8
+ }> {
9
+ createAutomotivePermissionRequestTemplate(config: AutomotivePermissionRequestTemplateConfig): void;
10
+ }
11
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { CustomActionButtonAndroid } from '../types/Button';
2
+ import type { AutoText } from '../types/Text';
3
+ import { type NitroAction } from '../utils/NitroAction';
4
+ import { type HeaderActions, Template, type TemplateConfig } from './Template';
5
+ export interface NitroAutomotivePermissionRequestTemplateConfig extends TemplateConfig {
6
+ headerActions?: Array<NitroAction>;
7
+ title: AutoText;
8
+ actions: Array<NitroAction>;
9
+ }
10
+ export type AutomotivePermissionRequestTemplateConfig = Omit<NitroAutomotivePermissionRequestTemplateConfig, 'headerActions' | 'buttons'> & {
11
+ /**
12
+ * action buttons, usually at the the top right on Android and a top bar on iOS
13
+ */
14
+ headerActions?: HeaderActions<AutomotivePermissionRequestTemplate>;
15
+ actions: [CustomActionButtonAndroid<AutomotivePermissionRequestTemplate>] | [
16
+ CustomActionButtonAndroid<AutomotivePermissionRequestTemplate>,
17
+ CustomActionButtonAndroid<AutomotivePermissionRequestTemplate>
18
+ ];
19
+ };
20
+ export declare class AutomotivePermissionRequestTemplate extends Template<AutomotivePermissionRequestTemplateConfig, HeaderActions<AutomotivePermissionRequestTemplate>> {
21
+ private template;
22
+ constructor(config: AutomotivePermissionRequestTemplateConfig);
23
+ }
@@ -0,0 +1,18 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ import { NitroActionUtil } from '../utils/NitroAction';
3
+ import { Template, } from './Template';
4
+ const HybridAutomotivePermissionRequestTemplate = NitroModules.createHybridObject('AutomotivePermissionRequestTemplate');
5
+ export class AutomotivePermissionRequestTemplate extends Template {
6
+ template = this;
7
+ constructor(config) {
8
+ super(config);
9
+ const { headerActions, actions, ...rest } = config;
10
+ const nitroConfig = {
11
+ ...rest,
12
+ id: this.id,
13
+ headerActions: NitroActionUtil.convert(this.template, headerActions),
14
+ actions: NitroActionUtil.convert(this.template, actions),
15
+ };
16
+ HybridAutomotivePermissionRequestTemplate.createAutomotivePermissionRequestTemplate(nitroConfig);
17
+ }
18
+ }
@@ -3,6 +3,7 @@ import { AppRegistry, Platform } from 'react-native';
3
3
  import { NitroModules } from 'react-native-nitro-modules';
4
4
  import { MapTemplateProvider } from '../components/MapTemplateContext';
5
5
  import { SafeAreaInsetsProvider } from '../components/SafeAreaInsetsContext';
6
+ import { WindowInformationWrapper } from '../components/WindowInformationWrapper';
6
7
  import { HybridAutoPlay } from '../hybrid/HybridAutoPlay';
7
8
  import { NitroActionUtil } from '../utils/NitroAction';
8
9
  import { NitroAlertUtil } from '../utils/NitroAlert';
@@ -23,7 +24,11 @@ export class MapTemplate extends Template {
23
24
  children: React.createElement(SafeAreaInsetsProvider, {
24
25
  moduleName: this.id,
25
26
  // biome-ignore lint/correctness/noChildrenProp: there is no other way in a ts file
26
- children: React.createElement(component, props),
27
+ children: React.createElement(WindowInformationWrapper, {
28
+ moduleName: this.id,
29
+ component,
30
+ componentProps: props,
31
+ }),
27
32
  }),
28
33
  }));
29
34
  const nitroConfig = {