@iternio/react-native-auto-play 0.6.0 → 0.6.1

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 (82) hide show
  1. package/README.md +56 -7
  2. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/Parser.kt +49 -14
  3. package/jest.js +2 -0
  4. package/lib/index.web.d.ts +73 -0
  5. package/lib/index.web.js +72 -1
  6. package/lib/types/Image.d.ts +6 -1
  7. package/lib/utils/NitroColor.d.ts +1 -0
  8. package/lib/utils/NitroColor.js +8 -0
  9. package/lib/utils/NitroImage.js +1 -1
  10. package/lib/utils/NitroManeuver.js +1 -3
  11. package/lib-cjs/AutoPlayHeadlessJsTask.js +16 -0
  12. package/lib-cjs/Constants.js +9 -0
  13. package/lib-cjs/components/MapTemplateContext.js +10 -0
  14. package/lib-cjs/components/SafeAreaInsetsContext.js +21 -0
  15. package/lib-cjs/components/SafeAreaView.js +22 -0
  16. package/lib-cjs/components/WindowInformationWrapper.js +20 -0
  17. package/lib-cjs/hooks/useAndroidAutoTelemetry.js +115 -0
  18. package/lib-cjs/hooks/useFocusedEffect.js +35 -0
  19. package/lib-cjs/hooks/useMapTemplate.js +13 -0
  20. package/lib-cjs/hooks/useSafeAreaInsets.js +9 -0
  21. package/lib-cjs/hooks/useVoiceInput.js +42 -0
  22. package/lib-cjs/hybrid/HybridAndroidAutoTelemetry.js +4 -0
  23. package/lib-cjs/hybrid/HybridAndroidWindowInformation.js +4 -0
  24. package/lib-cjs/hybrid/HybridAutoPlay.js +5 -0
  25. package/lib-cjs/hybrid/HybridVoice.js +61 -0
  26. package/lib-cjs/index.js +76 -0
  27. package/lib-cjs/index.web.js +98 -0
  28. package/lib-cjs/scenes/AutoPlayCluster.js +140 -0
  29. package/lib-cjs/scenes/CarPlayDashboardScene.js +100 -0
  30. package/lib-cjs/specs/AndroidAutoTelemetry.nitro.js +2 -0
  31. package/lib-cjs/specs/AndroidAutomotive.nitro.js +16 -0
  32. package/lib-cjs/specs/AndroidWindowInformation.nitro.js +2 -0
  33. package/lib-cjs/specs/AutoPlay.nitro.js +2 -0
  34. package/lib-cjs/specs/CarPlayDashboard.nitro.js +2 -0
  35. package/lib-cjs/specs/Cluster.nitro.js +2 -0
  36. package/lib-cjs/specs/GridTemplate.nitro.js +2 -0
  37. package/lib-cjs/specs/InformationTemplate.nitro.js +2 -0
  38. package/lib-cjs/specs/ListTemplate.nitro.js +2 -0
  39. package/lib-cjs/specs/MapTemplate.nitro.js +2 -0
  40. package/lib-cjs/specs/MessageTemplate.nitro.js +2 -0
  41. package/lib-cjs/specs/SearchTemplate.nitro.js +2 -0
  42. package/lib-cjs/specs/SignInTemplate.nitro.js +2 -0
  43. package/lib-cjs/specs/Voice.nitro.js +2 -0
  44. package/lib-cjs/templates/GridTemplate.js +41 -0
  45. package/lib-cjs/templates/InformationTemplate.js +49 -0
  46. package/lib-cjs/templates/ListTemplate.js +33 -0
  47. package/lib-cjs/templates/MapTemplate.js +168 -0
  48. package/lib-cjs/templates/MessageTemplate.js +50 -0
  49. package/lib-cjs/templates/SearchTemplate.js +32 -0
  50. package/lib-cjs/templates/SignInTemplate.js +55 -0
  51. package/lib-cjs/templates/Template.js +40 -0
  52. package/lib-cjs/types/Button.js +12 -0
  53. package/lib-cjs/types/Event.js +2 -0
  54. package/lib-cjs/types/Image.js +2 -0
  55. package/lib-cjs/types/Maneuver.js +77 -0
  56. package/lib-cjs/types/RootComponent.js +2 -0
  57. package/lib-cjs/types/SignInMethod.js +22 -0
  58. package/lib-cjs/types/Telemetry.js +24 -0
  59. package/lib-cjs/types/Text.js +8 -0
  60. package/lib-cjs/types/Trip.js +2 -0
  61. package/lib-cjs/types/Voice.js +2 -0
  62. package/lib-cjs/utils/ErrorUtil.js +34 -0
  63. package/lib-cjs/utils/NitroAction.js +129 -0
  64. package/lib-cjs/utils/NitroAlert.js +19 -0
  65. package/lib-cjs/utils/NitroAttributedString.js +14 -0
  66. package/lib-cjs/utils/NitroColor.js +32 -0
  67. package/lib-cjs/utils/NitroGrid.js +12 -0
  68. package/lib-cjs/utils/NitroImage.js +97 -0
  69. package/lib-cjs/utils/NitroManeuver.js +79 -0
  70. package/lib-cjs/utils/NitroMapButton.js +33 -0
  71. package/lib-cjs/utils/NitroOptionsPanel.js +66 -0
  72. package/lib-cjs/utils/NitroSection.js +112 -0
  73. package/nitrogen/generated/android/c++/JNitroColor.hpp +8 -4
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroColor.kt +9 -4
  75. package/nitrogen/generated/ios/swift/NitroColor.swift +20 -2
  76. package/nitrogen/generated/shared/c++/NitroColor.hpp +7 -3
  77. package/package.json +7 -3
  78. package/src/index.web.ts +166 -0
  79. package/src/types/Image.ts +6 -1
  80. package/src/utils/NitroColor.ts +10 -1
  81. package/src/utils/NitroImage.ts +1 -5
  82. package/src/utils/NitroManeuver.ts +1 -4
package/README.md CHANGED
@@ -48,8 +48,8 @@
48
48
 
49
49
  #### Bundle identifier
50
50
  To get the CarPlay app showing up you need to set a proper Bundle Identifier:
51
- - Open `example.xcodeproj`
52
- - Select the example target, go to the **Signing & Capabilities** tab.
51
+ - Open your app's `.xcodeproj` in Xcode.
52
+ - Select your app target, go to the **Signing & Capabilities** tab.
53
53
  - Under **Signing > Bundle Identifier**, enter your unique bundle ID (e.g., `at.g4rb4g3.autoplay.example`).
54
54
 
55
55
  #### Entitlements
@@ -178,7 +178,7 @@ This is an example that works for bare react-native (>= 0.82) and Expo SDK 57, c
178
178
  It is recommended to attach a listener to MapTemplate.onAppearanceDidChange and send maneuver updates based on this to make sure the colors are applied properly.
179
179
  Reason for this is that CarPlay does not allow for color updates on maneuvers shown on the screen. You need to send maneuvers with a new id to get them updated properly on the screen.
180
180
  The color properties do not need to handle the mode change, best practice is to use ThemedColor whenever possible and set appropriate light and dark mode colors.
181
- This is mainly required on CarPlay for now since Android Auto lacks light mode.
181
+ This is mainly required on CarPlay. Android Auto redraws on its own when the day/night state changes, but note that Android Auto 17.8 introduced white templates in day mode while older versions always show dark templates. The car's day/night state (`isDarkMode`) is the same on both, so it does not tell you which template color you are drawn on. Use the `'default'` color for icons that have to stay readable in both cases, see **Icon colors and Android Auto light templates**.
182
182
 
183
183
  #### CPListTemplate day/night header
184
184
 
@@ -293,13 +293,21 @@ This library also supports Android Automotive. To enable Android Automotive supp
293
293
 
294
294
  - **`isAutomotiveApp` flag**: You need to inform the library if this is an Automotive app by setting the `isAutomotiveApp` property to `true`. For Android Auto, it should be `false`.
295
295
 
296
- You can set these properties directly in your `android/gradle.properties` file:
296
+ You can set these properties directly in your `android/gradle.properties` file. **Note the
297
+ `ReactNativeAutoPlay_` prefix** — the library reads `rootProject.ext.<name>` first and falls
298
+ back to the prefixed project property, so an unprefixed `isAutomotiveApp=true` in
299
+ `gradle.properties` is silently ignored and you get an Android Auto build instead:
300
+
297
301
  ```properties
298
302
  # For Android Automotive
299
- minSdkVersion=29
300
- isAutomotiveApp=true
303
+ ReactNativeAutoPlay_minSdkVersion=29
304
+ ReactNativeAutoPlay_isAutomotiveApp=true
301
305
  ```
302
306
 
307
+ If your app's `android/build.gradle` already defines `ext.minSdkVersion` (the React Native
308
+ template does), that `rootProject.ext` value wins over the property above — raise it there
309
+ instead.
310
+
303
311
  Alternatively, if you need to support different build variants (e.g., for both Android Auto and Android Automotive from the same codebase), using `react-native-config` is the recommended approach.
304
312
 
305
313
  1. Install `react-native-config`:
@@ -443,6 +451,25 @@ It is also possible to use custom bundled images (e.g. PNG, WEBP or Vector Drawa
443
451
  - iOS: Add to your `Images.xcassets`
444
452
  - Android: Add to `res/drawable`
445
453
 
454
+ ### Icon colors and Android Auto light templates
455
+
456
+ Starting with **Android Auto 17.8** the host can show white (light) templates in day mode. Older versions (e.g. 17.6) always use dark templates, even in day mode. Both report the same car app API level, so an app cannot tell them apart, and a fixed icon color that is readable on one (white on dark) can be invisible on the other (white on white).
457
+
458
+ Use the color `'default'` for every monochrome icon that has to stay readable in both cases:
459
+
460
+ ```ts
461
+ { type: 'glyph', name: 'search', color: 'default' }
462
+ { type: 'asset', image: require('./icon.png'), color: 'default' }
463
+ { type: 'remote', uri: 'https://example.com/icon.png', color: 'default' }
464
+ ```
465
+
466
+ - **Android Auto**: the host tints the icon with its own default icon color for the template it is currently showing, so it follows dark and light templates on every Android Auto version.
467
+ - **CarPlay**: `'default'` resolves to black in light mode and white in dark mode, so it is safe to use on iOS and does not change anything there.
468
+ - **Glyphs** use `'default'` automatically when no `color` is set. Exception on Android Auto: a glyph with a non-transparent `backgroundColor` is not tinted, since the tint would recolor the background as well. It keeps the plain white (dark mode) / black (light mode) glyph color, so set `color` explicitly if that does not contrast with your background.
469
+ - **Asset and remote images** are not tinted unless you set a `color`, so colorful images such as a logo keep their original colors. Only pass `'default'` for monochrome icons.
470
+ - Any other color (a string or a `ThemedColor`) is applied as specified. Only use those where the color works on both dark and light templates, e.g. a colored icon.
471
+ - Known limitation: the host may not apply the tint to header action icons on Android Auto 17.8. That is an issue in Android Auto itself, not something the library can work around.
472
+
446
473
  ## Usage
447
474
 
448
475
  ### 1. Register the AutoPlay Components
@@ -576,7 +603,7 @@ All root components rendered by templates/scenes receive `RootComponentInitialPr
576
603
 
577
604
  - `id`: Module identifier (e.g. `AutoPlayRoot`, `CarPlayDashboard`, or a cluster UUID).
578
605
  - `rootTag`: React Native root tag.
579
- - `colorScheme`: `'light' | 'dark'` initial color scheme (listen to `onAppearanceDidChange` on `MapTemplate` for updates).
606
+ - `colorScheme`: `'light' | 'dark'` initial color scheme (listen to `onAppearanceDidChange` on `MapTemplate` for updates). On Android Auto this is the car's day/night state and does not tell you whether the templates are dark or white (17.8+ can show white templates in day mode, older versions never do).
580
607
  - `window`: `{ width, height, scale }`.
581
608
 
582
609
  ### Template Configs (Props)
@@ -1259,6 +1286,28 @@ CarPlayDashboard.setButtons([
1259
1286
  - `setAttributedInactiveDescriptionVariants(variants)` — iOS only inactive text.
1260
1287
  - `addListenerColorScheme(cb)` / `addListenerZoom(cb)` / `addListenerCompass(cb)` / `addListenerSpeedLimit(cb)`.
1261
1288
 
1289
+ ## Testing with Jest
1290
+
1291
+ The real package needs native modules and ships ESM, so it can't run under Jest. Use the bundled CommonJS mock instead, one line in your Jest setup file:
1292
+
1293
+ ```js
1294
+ // jest.setup.js
1295
+ jest.mock('@iternio/react-native-auto-play', () =>
1296
+ require('@iternio/react-native-auto-play/jest')
1297
+ );
1298
+ ```
1299
+
1300
+ Templates, `HybridAutoPlay`, `HybridVoice`, `AutoPlayCluster`, `CarPlayDashboard` and the hooks that need a car surface are safe no-ops (any method call returns `undefined`), `Constants.isIos27OrGreater` is `false`, and all types are unchanged. Tests that need to record constructions or assert on calls should extend it per test file:
1301
+
1302
+ ```ts
1303
+ jest.mock('@iternio/react-native-auto-play', () => {
1304
+ const actual = jest.requireActual('@iternio/react-native-auto-play/jest');
1305
+ return { ...actual, ListTemplate: class { push = jest.fn(() => Promise.resolve()); } };
1306
+ });
1307
+ ```
1308
+
1309
+ The same no-op surface is what `react-native-web` builds get automatically via `index.web.ts`.
1310
+
1262
1311
  ## Known Issues
1263
1312
 
1264
1313
  ### iOS
@@ -36,6 +36,7 @@ import androidx.car.app.navigation.model.TravelEstimate
36
36
  import androidx.core.graphics.createBitmap
37
37
  import androidx.core.graphics.drawable.IconCompat
38
38
  import androidx.core.graphics.drawable.toBitmap
39
+ import androidx.core.net.toUri
39
40
  import com.facebook.datasource.DataSources
40
41
  import com.facebook.drawee.backends.pipeline.Fresco
41
42
  import com.facebook.imagepipeline.image.CloseableBitmap
@@ -51,8 +52,8 @@ import com.margelo.nitro.swe.iternio.reactnativeautoplay.DurationWithTimeZone
51
52
  import com.margelo.nitro.swe.iternio.reactnativeautoplay.ForkType
52
53
  import com.margelo.nitro.swe.iternio.reactnativeautoplay.GlyphImage
53
54
  import com.margelo.nitro.swe.iternio.reactnativeautoplay.KeepType
54
- import com.margelo.nitro.swe.iternio.reactnativeautoplay.ListTemplateConfig
55
55
  import com.margelo.nitro.swe.iternio.reactnativeautoplay.ListImageType
56
+ import com.margelo.nitro.swe.iternio.reactnativeautoplay.ListTemplateConfig
56
57
  import com.margelo.nitro.swe.iternio.reactnativeautoplay.ManeuverType
57
58
  import com.margelo.nitro.swe.iternio.reactnativeautoplay.NitroAction
58
59
  import com.margelo.nitro.swe.iternio.reactnativeautoplay.NitroActionType
@@ -82,7 +83,6 @@ import java.util.Calendar
82
83
  import java.util.Locale
83
84
  import java.util.TimeZone
84
85
  import kotlin.math.abs
85
- import androidx.core.net.toUri
86
86
 
87
87
  object Parser {
88
88
  // IMAGE_TYPE_MEDIUM was added to androidx.car.app in 1.8.0. Keep the protocol value here
@@ -182,7 +182,11 @@ object Parser {
182
182
  }.build()
183
183
  }
184
184
 
185
- fun parseAction(context: CarContext, action: NitroAction, useParkedOnlyClickListener: Boolean = false): Action {
185
+ fun parseAction(
186
+ context: CarContext,
187
+ action: NitroAction,
188
+ useParkedOnlyClickListener: Boolean = false
189
+ ): Action {
186
190
  if (action.type == NitroActionType.APPICON) {
187
191
  return Action.APP_ICON
188
192
  }
@@ -219,11 +223,21 @@ object Parser {
219
223
  }
220
224
 
221
225
  fun parseImage(context: CarContext, image: Variant_GlyphImage_AssetImage_RemoteImage): CarIcon {
222
- return parseImage(context, image.asFirstOrNull(), image.asSecondOrNull(), image.asThirdOrNull())
226
+ return parseImage(
227
+ context,
228
+ image.asFirstOrNull(),
229
+ image.asSecondOrNull(),
230
+ image.asThirdOrNull()
231
+ )
223
232
  }
224
233
 
225
234
  fun parseImage(context: CarContext, image: NitroImage): CarIcon {
226
- return parseImage(context, image.asFirstOrNull(), image.asSecondOrNull(), image.asThirdOrNull())
235
+ return parseImage(
236
+ context,
237
+ image.asFirstOrNull(),
238
+ image.asSecondOrNull(),
239
+ image.asThirdOrNull()
240
+ )
227
241
  }
228
242
 
229
243
  fun parseImage(
@@ -234,8 +248,19 @@ object Parser {
234
248
  ): CarIcon {
235
249
  val bitmap = parseImageToBitmap(context, glyphImage, assetImage, remoteImage)
236
250
 
251
+ // the tint recolors every opaque pixel, so it would also fill the background drawn in SymbolFont
252
+ val hasGlyphBackground =
253
+ glyphImage != null && (glyphImage.backgroundColor.get(context) ushr 24) != 0
254
+
255
+ val applyDefaultTint = !hasGlyphBackground && (glyphImage?.color?.isDefault
256
+ ?: assetImage?.color?.isDefault ?: remoteImage?.color?.isDefault ?: false)
257
+
237
258
  bitmap?.let {
238
- return CarIcon.Builder(IconCompat.createWithBitmap(it)).build()
259
+ return CarIcon.Builder(IconCompat.createWithBitmap(it)).apply {
260
+ if (applyDefaultTint) {
261
+ setTint(CarColor.DEFAULT)
262
+ }
263
+ }.build()
239
264
  }
240
265
 
241
266
  // remote images might fail to load so we provide some placeholder then
@@ -610,10 +635,14 @@ object Parser {
610
635
  private fun fetchSync(context: CarContext, imageRequest: ImageRequest): Bitmap? {
611
636
  val dataSource = try {
612
637
  Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context)
613
- } catch (_: Exception) { return null }
638
+ } catch (_: Exception) {
639
+ return null
640
+ }
614
641
  val result = try {
615
642
  DataSources.waitForFinalResult(dataSource)
616
- } catch (_: Exception) { dataSource.close(); return null }
643
+ } catch (_: Exception) {
644
+ dataSource.close(); return null
645
+ }
617
646
  val image = result?.get()
618
647
  try {
619
648
  if (image is CloseableBitmap) {
@@ -621,10 +650,14 @@ object Parser {
621
650
  // whose backing bitmap has already been recycled or failed to allocate;
622
651
  // copy() can also throw (e.g., OOM on very large remote images). Either
623
652
  // way we return null so the caller falls back to a placeholder icon.
624
- return image.underlyingBitmap?.copy(Bitmap.Config.ARGB_8888, false)
653
+ return image.underlyingBitmap.copy(Bitmap.Config.ARGB_8888, false)
625
654
  } else if (image is CloseableXml) {
626
655
  val drawable = image.buildDrawable()
627
- return drawable?.toBitmap(width = image.width, height = image.height, Bitmap.Config.ARGB_8888)
656
+ return drawable?.toBitmap(
657
+ width = image.width,
658
+ height = image.height,
659
+ Bitmap.Config.ARGB_8888
660
+ )
628
661
  }
629
662
  } catch (_: Exception) {
630
663
  // Any decode/copy failure (OOM, recycled bitmap, invalid config, …) should
@@ -759,10 +792,12 @@ object Parser {
759
792
  }
760
793
  nitroManeuver.angle?.let { roundaboutExitAngle ->
761
794
  if (nitroManeuver.trafficSide == TrafficSide.LEFT) {
762
- val angle = ((180 + roundaboutExitAngle) % 360).toInt().let { if (it == 0) 360 else it }
795
+ val angle = ((180 + roundaboutExitAngle) % 360).toInt()
796
+ .let { if (it == 0) 360 else it }
763
797
  setRoundaboutExitAngle(angle)
764
798
  } else {
765
- val angle = ((180 - roundaboutExitAngle) % 360).toInt().let { if (it == 0) 360 else it }
799
+ val angle = ((180 - roundaboutExitAngle) % 360).toInt()
800
+ .let { if (it == 0) 360 else it }
766
801
  setRoundaboutExitAngle(angle)
767
802
  }
768
803
  }
@@ -841,7 +876,7 @@ object Parser {
841
876
  mapConfig.mapButtons?.let { mapButtons ->
842
877
  setMapController(
843
878
  MapController.Builder().apply {
844
- setMapActionStrip(Parser.parseMapActions(context, mapButtons)).build()
879
+ setMapActionStrip(parseMapActions(context, mapButtons)).build()
845
880
  setPanModeListener { isInPanMode ->
846
881
  mapConfig.onDidChangePanningInterface?.let {
847
882
  it(isInPanMode)
@@ -851,7 +886,7 @@ object Parser {
851
886
  )
852
887
  }
853
888
  mapConfig.headerActions?.let { headerActions ->
854
- setActionStrip(Parser.parseMapHeaderActions(context, headerActions))
889
+ setActionStrip(parseMapHeaderActions(context, headerActions))
855
890
  }
856
891
  }.build()
857
892
  }
package/jest.js ADDED
@@ -0,0 +1,2 @@
1
+ // CommonJS entry for Jest, see "Testing with Jest" in the README.
2
+ module.exports = require('./lib-cjs/index.web');
@@ -0,0 +1,73 @@
1
+ import { View, type ViewProps } from 'react-native';
2
+ import type { AndroidAutomotive } from './specs/AndroidAutomotive.nitro';
3
+ import type { AutoPlay } from './specs/AutoPlay.nitro';
4
+ import type { Voice } from './specs/Voice.nitro';
5
+ import type { GridTemplate as RealGridTemplate } from './templates/GridTemplate';
6
+ import type { InformationTemplate as RealInformationTemplate } from './templates/InformationTemplate';
7
+ import type { ListTemplate as RealListTemplate } from './templates/ListTemplate';
8
+ import type { MapTemplate as RealMapTemplate } from './templates/MapTemplate';
9
+ import type { MessageTemplate as RealMessageTemplate } from './templates/MessageTemplate';
10
+ import type { SearchTemplate as RealSearchTemplate } from './templates/SearchTemplate';
11
+ import type { SignInTemplate as RealSignInTemplate } from './templates/SignInTemplate';
12
+ import type { Template as RealTemplate } from './templates/Template';
13
+ import type { SafeAreaInsets } from './types/Event';
14
+ import type { Telemetry } from './types/Telemetry';
15
+ export * from './Constants';
16
+ export * from './hooks/useMapTemplate';
17
+ export type { ActiveCarUxRestrictions, AppFocusState, } from './specs/AndroidAutomotive.nitro';
18
+ export { CarUxRestrictions } from './specs/AndroidAutomotive.nitro';
19
+ export type { GridImageSize, GridTemplateConfig } from './templates/GridTemplate';
20
+ export type { InformationItems, InformationTemplateConfig } from './templates/InformationTemplate';
21
+ export type { DefaultRow, ListImageType, ListTemplateConfig, MultiSection, RadioRow, Section, SingleSection, TextRow, ToggleRow, WaypointCoordinate, WaypointRow, } from './templates/ListTemplate';
22
+ export type { BaseMapTemplateConfig, ChargerLocation, ChargerOutlet, ChargingConnector, HeaderActionsAndroidMap, MapButtons, MapHeaderActions, MapTemplateConfig, OptionsPanelChargerSection, OptionsPanelConfig, OptionsPanelGridSection, OptionsPanelListSection, OptionsPanelSection, PanelActionsIos, PanelHeaderActions, Point, TripSelectorCallback, VisibleTravelEstimate, } from './templates/MapTemplate';
23
+ export type { MessageTemplateConfig } from './templates/MessageTemplate';
24
+ export type { SearchSection, SearchTemplateConfig } from './templates/SearchTemplate';
25
+ export type { SignInTemplateConfig } from './templates/SignInTemplate';
26
+ export type { ActionButton, HeaderActions, HeaderActionsAndroid, HeaderActionsIos, NitroBaseMapTemplateConfig, NitroTemplateConfig, TemplateConfig, } from './templates/Template';
27
+ export * from './types/Button';
28
+ export * from './types/Event';
29
+ export * from './types/Image';
30
+ export * from './types/Maneuver';
31
+ export * from './types/RootComponent';
32
+ export * from './types/SignInMethod';
33
+ export * from './types/Telemetry';
34
+ export * from './types/Text';
35
+ export * from './types/Trip';
36
+ export type { VoiceInputChunk, VoiceInputOptions, VoiceInputResult } from './types/Voice';
37
+ export * from './utils/ErrorUtil';
38
+ export type { AlertPriority, NavigationAlert as Alert, NavigationAlertAction as AlertAction, } from './utils/NitroAlert';
39
+ export type { ThemedColor } from './utils/NitroColor';
40
+ export type { GridButton } from './utils/NitroGrid';
41
+ export declare enum AutoPlayModules {
42
+ App = "main",
43
+ AutoPlayRoot = "AutoPlayRoot",
44
+ CarPlayDashboard = "CarPlayDashboard"
45
+ }
46
+ export declare const useSafeAreaInsets: () => SafeAreaInsets;
47
+ export declare const SafeAreaView: (props: ViewProps) => import("react").CElement<ViewProps, View>;
48
+ export declare function setIconFont(_name: string, _glyphMap?: Record<string, number>): void;
49
+ export declare const useFocusedEffect: () => void;
50
+ export declare const useVoiceInput: () => {
51
+ voiceInputResult: undefined;
52
+ resetVoiceInputResult: () => void;
53
+ };
54
+ export declare const useAndroidAutoTelemetry: () => {
55
+ permissionsGranted: boolean | null;
56
+ telemetry: Telemetry | undefined;
57
+ error: string | undefined;
58
+ };
59
+ export declare const AutoPlayCluster: Record<string, unknown>;
60
+ export declare const CarPlayDashboard: Record<string, unknown>;
61
+ export declare const HybridAndroidAutomotive: AndroidAutomotive | null;
62
+ export { HybridAndroidAutoTelemetry } from './hybrid/HybridAndroidAutoTelemetry';
63
+ export { HybridAndroidWindowInformation } from './hybrid/HybridAndroidWindowInformation';
64
+ export declare const HybridAutoPlay: AutoPlay;
65
+ export declare const HybridVoice: Voice;
66
+ export declare const Template: new (...args: never[]) => RealTemplate<unknown, unknown>;
67
+ export declare const ListTemplate: new (...args: never[]) => RealListTemplate;
68
+ export declare const GridTemplate: new (...args: never[]) => RealGridTemplate;
69
+ export declare const InformationTemplate: new (...args: never[]) => RealInformationTemplate;
70
+ export declare const MessageTemplate: new (...args: never[]) => RealMessageTemplate;
71
+ export declare const MapTemplate: new (...args: never[]) => RealMapTemplate;
72
+ export declare const SearchTemplate: new (...args: never[]) => RealSearchTemplate;
73
+ export declare const SignInTemplate: new (...args: never[]) => RealSignInTemplate;
package/lib/index.web.js CHANGED
@@ -1 +1,72 @@
1
- "use strict";
1
+ // Web/Jest stub for the native CarPlay/Android Auto surface. Nothing here talks to a real native
2
+ // module — every function call resolves to `undefined` (or an already-resolved promise, which is
3
+ // indistinguishable from `undefined` to an `await`er) rather than throwing. Types are preserved by
4
+ // casting against the real exports, so callers still get full type-checking/autocomplete.
5
+ //
6
+ // Anything below that's already safe as-is (pure types, plain enums, or modules that neither
7
+ // create native objects nor import anything that does) is re-exported directly instead of stubbed.
8
+ import { createElement } from 'react';
9
+ import { View } from 'react-native';
10
+ function noop() {
11
+ return undefined;
12
+ }
13
+ /** Any property access returns a no-op function, so any method call is safe regardless of name. */
14
+ function createNoopObject() {
15
+ return new Proxy({}, {
16
+ get: (_target, prop) => (prop === 'then' || prop === 'toJSON' ? undefined : noop),
17
+ });
18
+ }
19
+ function createNoopClass() {
20
+ function NoopInstance() {
21
+ return createNoopObject();
22
+ }
23
+ return NoopInstance;
24
+ }
25
+ export * from './Constants';
26
+ export * from './hooks/useMapTemplate';
27
+ export { CarUxRestrictions } from './specs/AndroidAutomotive.nitro';
28
+ export * from './types/Button';
29
+ export * from './types/Event';
30
+ export * from './types/Image';
31
+ export * from './types/Maneuver';
32
+ export * from './types/RootComponent';
33
+ export * from './types/SignInMethod';
34
+ export * from './types/Telemetry';
35
+ export * from './types/Text';
36
+ export * from './types/Trip';
37
+ export * from './utils/ErrorUtil';
38
+ export var AutoPlayModules;
39
+ (function (AutoPlayModules) {
40
+ AutoPlayModules["App"] = "main";
41
+ AutoPlayModules["AutoPlayRoot"] = "AutoPlayRoot";
42
+ AutoPlayModules["CarPlayDashboard"] = "CarPlayDashboard";
43
+ })(AutoPlayModules || (AutoPlayModules = {}));
44
+ const NO_INSETS = { top: 0, bottom: 0, left: 0, right: 0 };
45
+ export const useSafeAreaInsets = () => NO_INSETS;
46
+ export const SafeAreaView = (props) => createElement(View, props);
47
+ export function setIconFont(_name, _glyphMap) { }
48
+ export const useFocusedEffect = () => { };
49
+ export const useVoiceInput = () => ({
50
+ voiceInputResult: undefined,
51
+ resetVoiceInputResult: () => { },
52
+ });
53
+ export const useAndroidAutoTelemetry = () => ({
54
+ permissionsGranted: null,
55
+ telemetry: undefined,
56
+ error: undefined,
57
+ });
58
+ export const AutoPlayCluster = createNoopObject();
59
+ export const CarPlayDashboard = createNoopObject();
60
+ export const HybridAndroidAutomotive = null;
61
+ export { HybridAndroidAutoTelemetry } from './hybrid/HybridAndroidAutoTelemetry';
62
+ export { HybridAndroidWindowInformation } from './hybrid/HybridAndroidWindowInformation';
63
+ export const HybridAutoPlay = createNoopObject();
64
+ export const HybridVoice = createNoopObject();
65
+ export const Template = createNoopClass();
66
+ export const ListTemplate = createNoopClass();
67
+ export const GridTemplate = createNoopClass();
68
+ export const InformationTemplate = createNoopClass();
69
+ export const MessageTemplate = createNoopClass();
70
+ export const MapTemplate = createNoopClass();
71
+ export const SearchTemplate = createNoopClass();
72
+ export const SignInTemplate = createNoopClass();
@@ -19,7 +19,8 @@ export type GlyphMapKey = keyof AutoPlayGlyphMap extends never ? string : Extrac
19
19
  type GlyphStyleFields = {
20
20
  /**
21
21
  * Sets the icon dark and light mode color or a single color for both.
22
- * Defaults to white for dark mode and black for light mode if not specified.
22
+ * Defaults to `'default'` if not specified, which is white for dark mode and black for light mode.
23
+ * On Android Auto the host tint that follows the template is not applied when `backgroundColor` is not transparent.
23
24
  * Might not get applied everywhere like MapTemplate buttons on Android.
24
25
  */
25
26
  color?: ThemedColor | string;
@@ -49,6 +50,8 @@ export type AutoImage = AutoGlyph | {
49
50
  /**
50
51
  * if specified the image gets tinted, if not it will just use the original image
51
52
  * Might not get applied everywhere like MapTemplate buttons on Android.
53
+ * Use `'default'` for monochrome icons that must stay readable on both dark and light
54
+ * Android Auto templates (17.8+), see README.
52
55
  */
53
56
  color?: ThemedColor | string;
54
57
  type: 'asset';
@@ -57,6 +60,8 @@ export type AutoImage = AutoGlyph | {
57
60
  uri: string;
58
61
  /**
59
62
  * if specified the image gets tinted, if not it will just use the original image
63
+ * Use `'default'` for monochrome icons that must stay readable on both dark and light
64
+ * Android Auto templates (17.8+), see README.
60
65
  */
61
66
  color?: ThemedColor | string;
62
67
  /**
@@ -1,6 +1,7 @@
1
1
  export type NitroColor = {
2
2
  lightColor: number;
3
3
  darkColor: number;
4
+ isDefault?: boolean;
4
5
  };
5
6
  export type ThemedColor = {
6
7
  lightColor: string;
@@ -1,4 +1,9 @@
1
1
  import { processColor } from 'react-native';
2
+ const DefaultNitroColor = {
3
+ darkColor: convertColor('white'),
4
+ lightColor: convertColor('black'),
5
+ isDefault: true,
6
+ };
2
7
  function convertColor(color) {
3
8
  // since we accept only string it can return a number only
4
9
  return processColor(color);
@@ -8,6 +13,9 @@ function convert(color) {
8
13
  return undefined;
9
14
  }
10
15
  if (typeof color === 'string') {
16
+ if (color === 'default') {
17
+ return DefaultNitroColor;
18
+ }
11
19
  const convertedColor = convertColor(color);
12
20
  return { darkColor: convertedColor, lightColor: convertedColor };
13
21
  }
@@ -59,7 +59,7 @@ function convert(image) {
59
59
  return undefined;
60
60
  }
61
61
  if (image.type === 'glyph') {
62
- const { color = { darkColor: 'white', lightColor: 'black' }, fontScale, backgroundColor = 'transparent', } = image;
62
+ const { color = 'default', fontScale, backgroundColor = 'transparent' } = image;
63
63
  return {
64
64
  glyph: resolveGlyph(image),
65
65
  fontName: getIconFont(),
@@ -6,9 +6,7 @@ function convertManeuverImage(image) {
6
6
  return undefined;
7
7
  }
8
8
  if (image.type === 'glyph') {
9
- const color = typeof image.color === 'string'
10
- ? image.color
11
- : (image.color ?? { darkColor: 'white', lightColor: 'black' });
9
+ const color = typeof image.color === 'string' ? image.color : (image.color ?? 'default');
12
10
  return NitroImageUtil.convert({
13
11
  ...image,
14
12
  color: color,
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_native_1 = require("react-native");
4
+ const createTaskProvider = (hybridAutoPlay) => () => () => new Promise((resolve) => {
5
+ const remove = hybridAutoPlay.addListener('didDisconnect', () => {
6
+ resolve();
7
+ remove();
8
+ });
9
+ });
10
+ const registerHeadlessTask = (hybridAutoPlay) => {
11
+ if (react_native_1.Platform.OS !== 'android') {
12
+ return;
13
+ }
14
+ react_native_1.AppRegistry.registerHeadlessTask('AndroidAutoHeadlessJsTask', createTaskProvider(hybridAutoPlay));
15
+ };
16
+ exports.default = { registerHeadlessTask };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Constants = void 0;
4
+ const react_native_1 = require("react-native");
5
+ const isIos27OrGreater = react_native_1.Platform.OS === 'ios' && Math.floor(Number(react_native_1.Platform.Version)) >= 27;
6
+ const Constants = {
7
+ isIos27OrGreater,
8
+ };
9
+ exports.Constants = Constants;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MapTemplateContext = void 0;
4
+ exports.MapTemplateProvider = MapTemplateProvider;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ exports.MapTemplateContext = (0, react_1.createContext)(null);
8
+ function MapTemplateProvider({ children, mapTemplate, }) {
9
+ return (0, jsx_runtime_1.jsx)(exports.MapTemplateContext.Provider, { value: mapTemplate, children: children });
10
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SafeAreaInsetsContext = void 0;
4
+ exports.SafeAreaInsetsProvider = SafeAreaInsetsProvider;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const HybridAutoPlay_1 = require("../hybrid/HybridAutoPlay");
8
+ const DEFAULT = { top: 0, bottom: 0, left: 0, right: 0 };
9
+ exports.SafeAreaInsetsContext = (0, react_1.createContext)(DEFAULT);
10
+ function SafeAreaInsetsProvider({ children, moduleName, }) {
11
+ const [insets, setInsets] = (0, react_1.useState)(DEFAULT);
12
+ (0, react_1.useEffect)(() => {
13
+ const removeSafeAreaInsetsListener = HybridAutoPlay_1.HybridAutoPlay.addSafeAreaInsetsListener(moduleName, (safeAreaInsets) => {
14
+ setInsets(safeAreaInsets);
15
+ });
16
+ return () => {
17
+ removeSafeAreaInsetsListener();
18
+ };
19
+ }, [moduleName]);
20
+ return (0, jsx_runtime_1.jsx)(exports.SafeAreaInsetsContext.Provider, { value: insets, children: children });
21
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SafeAreaView = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_native_1 = require("react-native");
6
+ const useSafeAreaInsets_1 = require("../hooks/useSafeAreaInsets");
7
+ const SafeAreaView = (props) => {
8
+ const { style, ...rest } = props;
9
+ const safeAreaInsets = (0, useSafeAreaInsets_1.useSafeAreaInsets)();
10
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
11
+ style,
12
+ {
13
+ ...react_native_1.StyleSheet.absoluteFillObject,
14
+ paddingTop: safeAreaInsets.top,
15
+ paddingBottom: safeAreaInsets.bottom,
16
+ paddingRight: safeAreaInsets.right,
17
+ paddingLeft: safeAreaInsets.left,
18
+ pointerEvents: 'box-none',
19
+ },
20
+ ], ...rest }));
21
+ };
22
+ exports.SafeAreaView = SafeAreaView;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WindowInformationWrapper = WindowInformationWrapper;
4
+ const react_1 = require("react");
5
+ const HybridAndroidWindowInformation_1 = require("../hybrid/HybridAndroidWindowInformation");
6
+ /**
7
+ * Renders a user-provided root component and keeps its `window` prop up to date when the host
8
+ * recreates the render surface with new dimensions (some head units run Android Auto windowed
9
+ * and resize it at runtime), so consumers can simply read `props.window` without registering a
10
+ * listener themselves.
11
+ * On iOS this only passes the initial props through — CarPlay windows keep their size for the
12
+ * lifetime of a scene, so there is nothing to subscribe to.
13
+ */
14
+ function WindowInformationWrapper({ moduleName, component, componentProps, }) {
15
+ const [window, setWindow] = (0, react_1.useState)(componentProps.window);
16
+ (0, react_1.useEffect)(() => {
17
+ return HybridAndroidWindowInformation_1.HybridAndroidWindowInformation?.addWindowInformationListener(moduleName, setWindow);
18
+ }, [moduleName]);
19
+ return (0, react_1.createElement)(component, { ...componentProps, window });
20
+ }