@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.
- package/README.md +56 -7
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/Parser.kt +49 -14
- package/jest.js +2 -0
- package/lib/index.web.d.ts +73 -0
- package/lib/index.web.js +72 -1
- package/lib/types/Image.d.ts +6 -1
- package/lib/utils/NitroColor.d.ts +1 -0
- package/lib/utils/NitroColor.js +8 -0
- package/lib/utils/NitroImage.js +1 -1
- package/lib/utils/NitroManeuver.js +1 -3
- package/lib-cjs/AutoPlayHeadlessJsTask.js +16 -0
- package/lib-cjs/Constants.js +9 -0
- package/lib-cjs/components/MapTemplateContext.js +10 -0
- package/lib-cjs/components/SafeAreaInsetsContext.js +21 -0
- package/lib-cjs/components/SafeAreaView.js +22 -0
- package/lib-cjs/components/WindowInformationWrapper.js +20 -0
- package/lib-cjs/hooks/useAndroidAutoTelemetry.js +115 -0
- package/lib-cjs/hooks/useFocusedEffect.js +35 -0
- package/lib-cjs/hooks/useMapTemplate.js +13 -0
- package/lib-cjs/hooks/useSafeAreaInsets.js +9 -0
- package/lib-cjs/hooks/useVoiceInput.js +42 -0
- package/lib-cjs/hybrid/HybridAndroidAutoTelemetry.js +4 -0
- package/lib-cjs/hybrid/HybridAndroidWindowInformation.js +4 -0
- package/lib-cjs/hybrid/HybridAutoPlay.js +5 -0
- package/lib-cjs/hybrid/HybridVoice.js +61 -0
- package/lib-cjs/index.js +76 -0
- package/lib-cjs/index.web.js +98 -0
- package/lib-cjs/scenes/AutoPlayCluster.js +140 -0
- package/lib-cjs/scenes/CarPlayDashboardScene.js +100 -0
- package/lib-cjs/specs/AndroidAutoTelemetry.nitro.js +2 -0
- package/lib-cjs/specs/AndroidAutomotive.nitro.js +16 -0
- package/lib-cjs/specs/AndroidWindowInformation.nitro.js +2 -0
- package/lib-cjs/specs/AutoPlay.nitro.js +2 -0
- package/lib-cjs/specs/CarPlayDashboard.nitro.js +2 -0
- package/lib-cjs/specs/Cluster.nitro.js +2 -0
- package/lib-cjs/specs/GridTemplate.nitro.js +2 -0
- package/lib-cjs/specs/InformationTemplate.nitro.js +2 -0
- package/lib-cjs/specs/ListTemplate.nitro.js +2 -0
- package/lib-cjs/specs/MapTemplate.nitro.js +2 -0
- package/lib-cjs/specs/MessageTemplate.nitro.js +2 -0
- package/lib-cjs/specs/SearchTemplate.nitro.js +2 -0
- package/lib-cjs/specs/SignInTemplate.nitro.js +2 -0
- package/lib-cjs/specs/Voice.nitro.js +2 -0
- package/lib-cjs/templates/GridTemplate.js +41 -0
- package/lib-cjs/templates/InformationTemplate.js +49 -0
- package/lib-cjs/templates/ListTemplate.js +33 -0
- package/lib-cjs/templates/MapTemplate.js +168 -0
- package/lib-cjs/templates/MessageTemplate.js +50 -0
- package/lib-cjs/templates/SearchTemplate.js +32 -0
- package/lib-cjs/templates/SignInTemplate.js +55 -0
- package/lib-cjs/templates/Template.js +40 -0
- package/lib-cjs/types/Button.js +12 -0
- package/lib-cjs/types/Event.js +2 -0
- package/lib-cjs/types/Image.js +2 -0
- package/lib-cjs/types/Maneuver.js +77 -0
- package/lib-cjs/types/RootComponent.js +2 -0
- package/lib-cjs/types/SignInMethod.js +22 -0
- package/lib-cjs/types/Telemetry.js +24 -0
- package/lib-cjs/types/Text.js +8 -0
- package/lib-cjs/types/Trip.js +2 -0
- package/lib-cjs/types/Voice.js +2 -0
- package/lib-cjs/utils/ErrorUtil.js +34 -0
- package/lib-cjs/utils/NitroAction.js +129 -0
- package/lib-cjs/utils/NitroAlert.js +19 -0
- package/lib-cjs/utils/NitroAttributedString.js +14 -0
- package/lib-cjs/utils/NitroColor.js +32 -0
- package/lib-cjs/utils/NitroGrid.js +12 -0
- package/lib-cjs/utils/NitroImage.js +97 -0
- package/lib-cjs/utils/NitroManeuver.js +79 -0
- package/lib-cjs/utils/NitroMapButton.js +33 -0
- package/lib-cjs/utils/NitroOptionsPanel.js +66 -0
- package/lib-cjs/utils/NitroSection.js +112 -0
- package/nitrogen/generated/android/c++/JNitroColor.hpp +8 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroColor.kt +9 -4
- package/nitrogen/generated/ios/swift/NitroColor.swift +20 -2
- package/nitrogen/generated/shared/c++/NitroColor.hpp +7 -3
- package/package.json +7 -3
- package/src/index.web.ts +166 -0
- package/src/types/Image.ts +6 -1
- package/src/utils/NitroColor.ts +10 -1
- package/src/utils/NitroImage.ts +1 -5
- 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
|
|
52
|
-
- Select
|
|
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
|
|
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
|
-
|
|
300
|
-
|
|
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
|
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/Parser.kt
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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)).
|
|
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) {
|
|
638
|
+
} catch (_: Exception) {
|
|
639
|
+
return null
|
|
640
|
+
}
|
|
614
641
|
val result = try {
|
|
615
642
|
DataSources.waitForFinalResult(dataSource)
|
|
616
|
-
} catch (_: Exception) {
|
|
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
|
|
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(
|
|
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()
|
|
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()
|
|
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(
|
|
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(
|
|
889
|
+
setActionStrip(parseMapHeaderActions(context, headerActions))
|
|
855
890
|
}
|
|
856
891
|
}.build()
|
|
857
892
|
}
|
package/jest.js
ADDED
package/lib/index.web.d.ts
CHANGED
|
@@ -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
|
-
|
|
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();
|
package/lib/types/Image.d.ts
CHANGED
|
@@ -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
|
|
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
|
/**
|
package/lib/utils/NitroColor.js
CHANGED
|
@@ -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
|
}
|
package/lib/utils/NitroImage.js
CHANGED
|
@@ -59,7 +59,7 @@ function convert(image) {
|
|
|
59
59
|
return undefined;
|
|
60
60
|
}
|
|
61
61
|
if (image.type === 'glyph') {
|
|
62
|
-
const { color =
|
|
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
|
+
}
|