@javascriptcommon/react-native-carplay 2.3.11 → 2.4.2

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 (136) hide show
  1. package/android/build.gradle +110 -0
  2. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  3. package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  4. package/android/gradle.properties +20 -0
  5. package/android/gradlew +234 -0
  6. package/android/gradlew.bat +89 -0
  7. package/android/src/main/AndroidManifest.xml +30 -0
  8. package/android/src/main/AndroidManifestNew.xml +30 -0
  9. package/android/src/main/java/org/birkir/carplay/CarPlayModule.kt +321 -0
  10. package/android/src/main/java/org/birkir/carplay/CarPlayPackage.kt +18 -0
  11. package/android/src/main/java/org/birkir/carplay/CarPlayService.kt +35 -0
  12. package/android/src/main/java/org/birkir/carplay/CarPlaySession.kt +126 -0
  13. package/android/src/main/java/org/birkir/carplay/parser/Ext.kt +11 -0
  14. package/android/src/main/java/org/birkir/carplay/parser/Parser.kt +18 -0
  15. package/android/src/main/java/org/birkir/carplay/parser/RCTGridTemplate.kt +28 -0
  16. package/android/src/main/java/org/birkir/carplay/parser/RCTListTemplate.kt +64 -0
  17. package/android/src/main/java/org/birkir/carplay/parser/RCTMapTemplate.kt +128 -0
  18. package/android/src/main/java/org/birkir/carplay/parser/RCTMessageTemplate.kt +28 -0
  19. package/android/src/main/java/org/birkir/carplay/parser/RCTPaneTemplate.kt +24 -0
  20. package/android/src/main/java/org/birkir/carplay/parser/RCTSearchTemplate.kt +41 -0
  21. package/android/src/main/java/org/birkir/carplay/parser/RCTTabTemplate.kt +157 -0
  22. package/android/src/main/java/org/birkir/carplay/parser/RCTTemplate.kt +419 -0
  23. package/android/src/main/java/org/birkir/carplay/parser/TemplateParser.kt +35 -0
  24. package/android/src/main/java/org/birkir/carplay/screens/CarScreen.kt +76 -0
  25. package/android/src/main/java/org/birkir/carplay/screens/CarScreenContext.kt +10 -0
  26. package/android/src/main/java/org/birkir/carplay/utils/EventEmitter.kt +167 -0
  27. package/android/src/main/java/org/birkir/carplay/utils/VirtualRenderer.kt +75 -0
  28. package/ios/RCTConvert+RNCarPlay.h +7 -8
  29. package/ios/RCTConvert+RNCarPlay.m +4 -3
  30. package/ios/RNCarPlay.h +11 -14
  31. package/ios/RNCarPlay.m +749 -945
  32. package/ios/RNCarPlayViewController.h +10 -0
  33. package/ios/RNCarPlayViewController.m +50 -0
  34. package/lib/CarPlay.d.ts +183 -0
  35. package/lib/CarPlay.d.ts.map +1 -0
  36. package/lib/index.d.ts +44 -0
  37. package/lib/index.d.ts.map +1 -0
  38. package/lib/interfaces/Action.d.ts +13 -0
  39. package/lib/interfaces/Action.d.ts.map +1 -0
  40. package/lib/interfaces/AlertAction.d.ts +6 -0
  41. package/lib/interfaces/AlertAction.d.ts.map +1 -0
  42. package/lib/interfaces/BarButton.d.ts +39 -0
  43. package/lib/interfaces/BarButton.d.ts.map +1 -0
  44. package/lib/interfaces/CarColor.d.ts +2 -0
  45. package/lib/interfaces/CarColor.d.ts.map +1 -0
  46. package/lib/interfaces/GridButton.d.ts +24 -0
  47. package/lib/interfaces/GridButton.d.ts.map +1 -0
  48. package/lib/interfaces/Header.d.ts +15 -0
  49. package/lib/interfaces/Header.d.ts.map +1 -0
  50. package/lib/interfaces/ListItem.d.ts +90 -0
  51. package/lib/interfaces/ListItem.d.ts.map +1 -0
  52. package/lib/interfaces/ListItemUpdate.d.ts +15 -0
  53. package/lib/interfaces/ListItemUpdate.d.ts.map +1 -0
  54. package/lib/interfaces/ListSection.d.ts +21 -0
  55. package/lib/interfaces/ListSection.d.ts.map +1 -0
  56. package/lib/interfaces/Maneuver.d.ts +31 -0
  57. package/lib/interfaces/Maneuver.d.ts.map +1 -0
  58. package/lib/interfaces/MapButton.d.ts +27 -0
  59. package/lib/interfaces/MapButton.d.ts.map +1 -0
  60. package/lib/interfaces/NavigationAlert.d.ts +44 -0
  61. package/lib/interfaces/NavigationAlert.d.ts.map +1 -0
  62. package/lib/interfaces/NavigationInfo.d.ts +17 -0
  63. package/lib/interfaces/NavigationInfo.d.ts.map +1 -0
  64. package/lib/interfaces/NavigationStep.d.ts +17 -0
  65. package/lib/interfaces/NavigationStep.d.ts.map +1 -0
  66. package/lib/interfaces/Pane.d.ts +29 -0
  67. package/lib/interfaces/Pane.d.ts.map +1 -0
  68. package/lib/interfaces/PauseReason.d.ts +8 -0
  69. package/lib/interfaces/PauseReason.d.ts.map +1 -0
  70. package/lib/interfaces/Place.d.ts +11 -0
  71. package/lib/interfaces/Place.d.ts.map +1 -0
  72. package/lib/interfaces/TextConfiguration.d.ts +6 -0
  73. package/lib/interfaces/TextConfiguration.d.ts.map +1 -0
  74. package/lib/interfaces/TimeRemainingColor.d.ts +2 -0
  75. package/lib/interfaces/TimeRemainingColor.d.ts.map +1 -0
  76. package/lib/interfaces/TravelEstimates.d.ts +37 -0
  77. package/lib/interfaces/TravelEstimates.d.ts.map +1 -0
  78. package/lib/interfaces/VoiceControlState.d.ts +8 -0
  79. package/lib/interfaces/VoiceControlState.d.ts.map +1 -0
  80. package/lib/navigation/NavigationSession.d.ts +18 -0
  81. package/lib/navigation/NavigationSession.d.ts.map +1 -0
  82. package/lib/navigation/Trip.d.ts +22 -0
  83. package/lib/navigation/Trip.d.ts.map +1 -0
  84. package/lib/templates/ActionSheetTemplate.d.ts +18 -0
  85. package/lib/templates/ActionSheetTemplate.d.ts.map +1 -0
  86. package/lib/templates/AlertTemplate.d.ts +17 -0
  87. package/lib/templates/AlertTemplate.d.ts.map +1 -0
  88. package/lib/templates/ContactTemplate.d.ts +36 -0
  89. package/lib/templates/ContactTemplate.d.ts.map +1 -0
  90. package/lib/templates/GridTemplate.d.ts +38 -0
  91. package/lib/templates/GridTemplate.d.ts.map +1 -0
  92. package/lib/templates/InformationTemplate.d.ts +28 -0
  93. package/lib/templates/InformationTemplate.d.ts.map +1 -0
  94. package/lib/templates/ListTemplate.d.ts +127 -0
  95. package/lib/templates/ListTemplate.d.ts.map +1 -0
  96. package/lib/templates/ListTemplate.js +24 -16
  97. package/lib/templates/MapTemplate.d.ts +171 -0
  98. package/lib/templates/MapTemplate.d.ts.map +1 -0
  99. package/lib/templates/NowPlayingTemplate.d.ts +31 -0
  100. package/lib/templates/NowPlayingTemplate.d.ts.map +1 -0
  101. package/lib/templates/PointOfInterestTemplate.d.ts +33 -0
  102. package/lib/templates/PointOfInterestTemplate.d.ts.map +1 -0
  103. package/lib/templates/SearchTemplate.d.ts +32 -0
  104. package/lib/templates/SearchTemplate.d.ts.map +1 -0
  105. package/lib/templates/SearchTemplate.js +2 -2
  106. package/lib/templates/TabBarTemplate.d.ts +27 -0
  107. package/lib/templates/TabBarTemplate.d.ts.map +1 -0
  108. package/lib/templates/Template.d.ts +82 -0
  109. package/lib/templates/Template.d.ts.map +1 -0
  110. package/lib/templates/Template.js +1 -1
  111. package/lib/templates/VoiceControlTemplate.d.ts +18 -0
  112. package/lib/templates/VoiceControlTemplate.d.ts.map +1 -0
  113. package/lib/templates/android/AndroidNavigationBaseTemplate.d.ts +19 -0
  114. package/lib/templates/android/AndroidNavigationBaseTemplate.d.ts.map +1 -0
  115. package/lib/templates/android/MessageTemplate.d.ts +16 -0
  116. package/lib/templates/android/MessageTemplate.d.ts.map +1 -0
  117. package/lib/templates/android/NavigationTemplate.d.ts +44 -0
  118. package/lib/templates/android/NavigationTemplate.d.ts.map +1 -0
  119. package/lib/templates/android/PaneTemplate.d.ts +13 -0
  120. package/lib/templates/android/PaneTemplate.d.ts.map +1 -0
  121. package/lib/templates/android/PlaceListMapTemplate.d.ts +58 -0
  122. package/lib/templates/android/PlaceListMapTemplate.d.ts.map +1 -0
  123. package/lib/templates/android/PlaceListNavigationTemplate.d.ts +51 -0
  124. package/lib/templates/android/PlaceListNavigationTemplate.d.ts.map +1 -0
  125. package/lib/templates/android/RoutePreviewNavigationTemplate.d.ts +60 -0
  126. package/lib/templates/android/RoutePreviewNavigationTemplate.d.ts.map +1 -0
  127. package/package.json +3 -3
  128. package/react-native-carplay.podspec +3 -3
  129. package/src/CarPlay.ts +28 -16
  130. package/src/interfaces/ListItem.ts +14 -8
  131. package/src/interfaces/ListSection.ts +1 -1
  132. package/src/templates/ListTemplate.ts +64 -44
  133. package/src/templates/NowPlayingTemplate.ts +10 -3
  134. package/src/templates/SearchTemplate.ts +2 -2
  135. package/src/templates/Template.ts +1 -1
  136. package/README.md +0 -633
@@ -0,0 +1,419 @@
1
+ package org.birkir.carplay.parser
2
+
3
+ // import androidx.car.app.model.Template
4
+
5
+ import android.graphics.Bitmap
6
+ import android.text.Spannable
7
+ import android.text.SpannableString
8
+ import android.util.Log
9
+ import androidx.car.app.CarContext
10
+ import androidx.car.app.model.Action
11
+ import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
12
+ import androidx.car.app.model.Action.FLAG_PRIMARY
13
+ import androidx.car.app.model.ActionStrip
14
+ import androidx.car.app.model.CarColor
15
+ import androidx.car.app.model.CarIcon
16
+ import androidx.car.app.model.CarLocation
17
+ import androidx.car.app.model.CarText
18
+ import androidx.car.app.model.DateTimeWithZone
19
+ import androidx.car.app.model.Distance
20
+ import androidx.car.app.model.DistanceSpan
21
+ import androidx.car.app.model.GridItem
22
+ import androidx.car.app.model.Header
23
+ import androidx.car.app.model.ItemList
24
+ import androidx.car.app.model.Metadata
25
+ import androidx.car.app.model.Pane
26
+ import androidx.car.app.model.Place
27
+ import androidx.car.app.model.PlaceMarker
28
+ import androidx.car.app.model.Row
29
+ import androidx.car.app.model.Template
30
+ import androidx.car.app.navigation.model.Lane
31
+ import androidx.car.app.navigation.model.LaneDirection
32
+ import androidx.car.app.navigation.model.Maneuver
33
+ import androidx.car.app.navigation.model.MessageInfo
34
+ import androidx.car.app.navigation.model.NavigationTemplate
35
+ import androidx.car.app.navigation.model.RoutingInfo
36
+ import androidx.car.app.navigation.model.Step
37
+ import androidx.car.app.navigation.model.TravelEstimate
38
+ import androidx.core.graphics.drawable.IconCompat
39
+ import com.facebook.common.references.CloseableReference
40
+ import com.facebook.datasource.DataSource
41
+ import com.facebook.datasource.DataSources
42
+ import com.facebook.drawee.backends.pipeline.Fresco
43
+ import com.facebook.imagepipeline.image.CloseableBitmap
44
+ import com.facebook.imagepipeline.image.CloseableImage
45
+ import com.facebook.imagepipeline.request.ImageRequestBuilder
46
+ import com.facebook.react.bridge.ReadableArray
47
+ import com.facebook.react.bridge.ReadableMap
48
+ import com.facebook.react.views.imagehelper.ImageSource
49
+ import org.birkir.carplay.screens.CarScreenContext
50
+ import org.birkir.carplay.utils.EventEmitter
51
+ import java.util.TimeZone
52
+
53
+
54
+ /**
55
+ * Base class for parsing the template based on the props passed from ReactNative
56
+ *
57
+ * @property context
58
+ * @property carScreenContext
59
+ */
60
+ abstract class RCTTemplate(
61
+ protected val context: CarContext,
62
+ protected val carScreenContext: CarScreenContext
63
+ ) {
64
+
65
+ abstract fun parse(props: ReadableMap): Template
66
+
67
+ protected val eventEmitter: EventEmitter
68
+ get() = carScreenContext.eventEmitter
69
+
70
+ fun parseCarIcon(map: ReadableMap): CarIcon {
71
+ val source = ImageSource(context, map.getString("uri"))
72
+ val imageRequest = ImageRequestBuilder.newBuilderWithSource(source.uri).build()
73
+ val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context)
74
+ val result = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>
75
+ val bitmap = result.get().underlyingBitmap
76
+
77
+ CloseableReference.closeSafely(result)
78
+ dataSource.close()
79
+
80
+ return CarIcon.Builder(IconCompat.createWithBitmap(bitmap)).build()
81
+ }
82
+
83
+ fun parseColor(colorName: String?): CarColor {
84
+ // @todo implement CarColor.createCustom(light: 0x00, dark: 0x00)
85
+ // maybe use react native tooling for this
86
+
87
+ return when (colorName) {
88
+ "blue" -> CarColor.BLUE
89
+ "green" -> CarColor.GREEN
90
+ "primary" -> CarColor.PRIMARY
91
+ "red" -> CarColor.RED
92
+ "secondary" -> CarColor.SECONDARY
93
+ "yellow" -> CarColor.YELLOW
94
+ "default" -> CarColor.DEFAULT
95
+ else -> CarColor.DEFAULT
96
+ }
97
+ }
98
+
99
+ fun parseAction(map: ReadableMap?): Action {
100
+ val type = map?.getString("type")
101
+ if (type == "appIcon") {
102
+ return Action.APP_ICON
103
+ } else if (type == "back") {
104
+ return Action.BACK
105
+ } else if (type == "pan") {
106
+ return Action.PAN
107
+ }
108
+ val id = map?.getString("id")
109
+ val builder = Action.Builder()
110
+ if (map != null) {
111
+ map.getString("title")?.let {
112
+ builder.setTitle(it)
113
+ }
114
+ map.getMap("icon")?.let {
115
+ builder.setIcon(parseCarIcon(it))
116
+ }
117
+ map.getString("visibility")?.let {
118
+ if (it == "primary") {
119
+ builder.setFlags(FLAG_PRIMARY)
120
+ }
121
+ if (it == "persistent") {
122
+ builder.setFlags(FLAG_IS_PERSISTENT)
123
+ }
124
+ }
125
+ try {
126
+ builder.setBackgroundColor(parseColor(map.getString("backgroundColor")))
127
+ } catch (e: Exception) {
128
+ e.printStackTrace()
129
+ }
130
+ builder.setOnClickListener {
131
+ if (id != null) {
132
+ eventEmitter.buttonPressed(id)
133
+ }
134
+ }
135
+ }
136
+ return builder.build()
137
+ }
138
+
139
+ protected fun parseActionStrip(actions: ReadableArray): ActionStrip {
140
+ val builder = ActionStrip.Builder()
141
+ for (i in 0 until actions.size()) {
142
+ val actionMap = actions.getMap(i)
143
+ val action = parseAction(actionMap)
144
+ builder.addAction(action)
145
+ }
146
+ return builder.build()
147
+ }
148
+
149
+ protected fun parseItemList(
150
+ items: ReadableArray?,
151
+ type: String = "row"
152
+ ): ItemList {
153
+ return ItemList.Builder().apply {
154
+ for (i in 0 until items!!.size()) {
155
+ if (type == "row") {
156
+ addItem(parseRowItem(items.getMap(i), i))
157
+ } else if (type == "grid") {
158
+ addItem(parseGridItem(items.getMap(i), i))
159
+ }
160
+ }
161
+ }.build()
162
+ }
163
+
164
+ protected fun parseRowItem(item: ReadableMap, index: Int): Row {
165
+ val id = item.getString("id") ?: index.toString()
166
+ return Row.Builder().apply {
167
+ item.getString("text")?.let { setTitle(it) }
168
+ item.getString("detailText")?.let { addText(it) }
169
+ item.getMap("image")?.let { setImage(parseCarIcon(it)) }
170
+ if (item.hasKey("browsable") && item.getBoolean("browsable")) {
171
+ setOnClickListener {
172
+ eventEmitter.didSelectListItem(
173
+ id,
174
+ index
175
+ )
176
+ }
177
+ }
178
+ }.build()
179
+ }
180
+
181
+ protected fun parseGridItem(item: ReadableMap, index: Int): GridItem {
182
+ val id = item.getString("id") ?: index.toString()
183
+ return GridItem.Builder().apply {
184
+ val titleVariants = item.getArray("titleVariants")
185
+ val metadata = item.getMap("metadata");
186
+
187
+ if (titleVariants != null) {
188
+ if (titleVariants.size() > 0) {
189
+ setTitle(parseCarText(
190
+ titleVariants.getString(0),
191
+ metadata
192
+ ))
193
+ }
194
+ if (titleVariants.size() > 1) {
195
+ setText(titleVariants.getString(1))
196
+ }
197
+ }
198
+ item.getMap("image")?.let { setImage(parseCarIcon(it)) }
199
+ setLoading(item.isLoading())
200
+ setOnClickListener {
201
+ eventEmitter.gridButtonPressed(id, index)
202
+ }
203
+ }.build()
204
+ }
205
+
206
+ fun parsePlace(props: ReadableMap): Place {
207
+ val builder = Place.Builder(
208
+ CarLocation.create(
209
+ props.getDouble("latitude"),
210
+ props.getDouble("longitude"),
211
+ )
212
+ )
213
+ PlaceMarker.Builder().apply {
214
+ setIcon(parseCarIcon(props.getMap("icon")!!), PlaceMarker.TYPE_IMAGE)
215
+ builder.setMarker(this.build())
216
+
217
+ }
218
+
219
+ return builder.build()
220
+ }
221
+
222
+ fun parseMetadata(props: ReadableMap?): Metadata? {
223
+ val type = props?.getString("type")
224
+ if (props == null || type == null || type != "place") {
225
+ Log.w(TAG, "parseMetaData: invalid type provided $type")
226
+ return null
227
+ }
228
+ return Metadata.Builder().setPlace(parsePlace(props)).build()
229
+ }
230
+
231
+ fun parseCarText(title: String, props: ReadableMap?): CarText {
232
+ val spanBuilder = SpannableString(title)
233
+ props?.let {
234
+ try {
235
+ val index = title.indexOf("%d")
236
+ if (index != -1) {
237
+ spanBuilder.setSpan(
238
+ DistanceSpan.create(parseDistance(props)),
239
+ index,
240
+ index + 2,
241
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
242
+ )
243
+ }
244
+ it
245
+ } catch (e: Exception) {
246
+ Log.w(TAG, "getCarText: failed to parse the CarText")
247
+ }
248
+ }
249
+ return CarText.Builder(spanBuilder).build()
250
+ }
251
+
252
+ protected fun buildRow(props: ReadableMap): Row {
253
+ val builder = Row.Builder()
254
+ builder.setTitle(
255
+ parseCarText(
256
+ props.getString("title")!!,
257
+ props.getMap("metadata")
258
+ )
259
+ )
260
+ props.getArray("texts")?.let {
261
+ for (i in 0 until it.size()) {
262
+ builder.addText(it.getString(i))
263
+ }
264
+ }
265
+ props.getMap("image")?.let {
266
+ builder.setImage(parseCarIcon(it))
267
+ }
268
+ try {
269
+ val onPress = props.getInt("onPress")
270
+ builder.setBrowsable(true)
271
+ // builder.setOnClickListener { invokeCallback(onPress) }
272
+ } catch (e: Exception) {
273
+ Log.w(TAG, "buildRow: failed to set clickListener on the row")
274
+ }
275
+ parseMetadata(props.getMap("metadata"))?.let {
276
+ builder.setMetadata(it)
277
+ }
278
+ return builder.build()
279
+ }
280
+
281
+ protected fun parseDistanceUnit(value: String?): Int {
282
+ return when (value) {
283
+ "meters" -> Distance.UNIT_METERS
284
+ "miles" -> Distance.UNIT_MILES
285
+ "kilometers" -> Distance.UNIT_KILOMETERS
286
+ "yards" -> Distance.UNIT_YARDS
287
+ "feet" -> Distance.UNIT_FEET
288
+ else -> Distance.UNIT_METERS
289
+ }
290
+ }
291
+
292
+ protected fun parseDistance(map: ReadableMap): Distance {
293
+ return Distance.create(map.getDouble("distance"), parseDistanceUnit(map.getString("distanceUnits")))
294
+ }
295
+
296
+ protected fun parsePane(item: ReadableMap): Pane {
297
+ return Pane.Builder().apply {
298
+ setLoading(item.isLoading())
299
+ item.getMap("image")?.let {
300
+ setImage(parseCarIcon(it))
301
+ }
302
+ item.getArray("actions")?.let {
303
+ for (i in 0 until it.size()) {
304
+ addAction(parseAction(it.getMap(i)))
305
+ }
306
+ }
307
+ item.getArray("items")?.let {
308
+ for (i in 0 until it.size()) {
309
+ addRow(parseRowItem(it.getMap(i), i))
310
+ }
311
+ }
312
+ }.build()
313
+ }
314
+
315
+ protected fun parseHeader(map: ReadableMap): Header {
316
+ return Header.Builder().apply {
317
+ map.getString("title")?.let { setTitle(parseCarText(it, map)) }
318
+ map.getMap("startAction")?.let { setStartHeaderAction(parseAction(it)) }
319
+ map.getArray("endActions")?.let {
320
+ for (i in 0 until it.size()) {
321
+ addEndHeaderAction(parseAction(it.getMap(i)))
322
+ }
323
+ }
324
+ }.build()
325
+ }
326
+
327
+ protected fun parseStep(map: ReadableMap): Step {
328
+ return Step.Builder().apply {
329
+ map.getMap("lane")?.let { addLane(parseLane(it)) }
330
+ map.getString("cue")?.let { setCue(it) }
331
+ map.getMap("lanesImage")?.let { setLanesImage(parseCarIcon(it)) }
332
+ map.getMap("maneuver")?.let { setManeuver(parseManeuver(it)) }
333
+ map.getString("road")?.let { setRoad(it) }
334
+ }.build()
335
+ }
336
+
337
+ protected fun parseLane(map: ReadableMap): Lane {
338
+ val laneBuilder = Lane.Builder()
339
+ val shape = map.getInt("shape")
340
+ val recommended = map.getBoolean("recommended")
341
+ return laneBuilder.addDirection(LaneDirection.create(shape, recommended)).build()
342
+ }
343
+
344
+ protected fun parseManeuver(map: ReadableMap): Maneuver {
345
+ val type = map.getInt("type")
346
+ val builder = Maneuver.Builder(type)
347
+ builder.setIcon(parseCarIcon(map.getMap("icon")!!))
348
+ if (type == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE
349
+ || type == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE
350
+ ) {
351
+ builder.setRoundaboutExitAngle(map.getInt("roundaboutExitAngle"))
352
+ }
353
+
354
+ if (type == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
355
+ || type == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
356
+ || type == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE
357
+ || type == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE
358
+ ) {
359
+ builder.setRoundaboutExitNumber(map.getInt("roundaboutExitNumber"))
360
+ }
361
+
362
+ return builder.build()
363
+ }
364
+
365
+ protected fun parseMessageInfo(map: ReadableMap): MessageInfo {
366
+ val builder = MessageInfo.Builder(map.getString("title")!!)
367
+ map.getMap("icon")?.let { builder.setImage(parseCarIcon(it)) }
368
+ return builder.build()
369
+ }
370
+
371
+ protected fun parseTravelEstimate(map: ReadableMap): TravelEstimate {
372
+ val dateTimeMap = map.getMap("destinationTime")!!
373
+ val destinationDateTime = DateTimeWithZone.create(
374
+ dateTimeMap.getDouble("timeSinceEpochMillis").toLong(),
375
+ TimeZone.getTimeZone(dateTimeMap.getString("id")),
376
+ )
377
+ val builder = TravelEstimate.Builder(
378
+ Distance.create(
379
+ map.getDouble("distanceRemaining"),
380
+ parseDistanceUnit(map.getString("distanceUnits"))
381
+ ),
382
+ destinationDateTime,
383
+ )
384
+ map.getString("distanceRemainingColor")?.let {
385
+ builder.setRemainingDistanceColor(parseColor(it))
386
+ }
387
+ map.getString("timeRemainingColor")?.let {
388
+ builder.setRemainingTimeColor(parseColor(it))
389
+ }
390
+ builder.setRemainingTimeSeconds(map.getDouble("timeRemaining").toLong())
391
+ return builder.build()
392
+ }
393
+
394
+ protected fun parseRoutingInfo(map: ReadableMap): RoutingInfo {
395
+ return RoutingInfo.Builder()
396
+ .apply {
397
+ setLoading(map.isLoading())
398
+ setCurrentStep(
399
+ parseStep(map.getMap("step")!!),
400
+ parseDistance(map)
401
+ )
402
+ map.getMap("junctionImage")?.let { setJunctionImage(parseCarIcon(it)) }
403
+ map.getMap("nextStep")?.let { setNextStep(parseStep(it)) }
404
+ }.build()
405
+ }
406
+
407
+ protected fun parseNavigationInfo(map: ReadableMap): NavigationTemplate.NavigationInfo {
408
+ val type = map.getString("type")
409
+ return if (type == "routingInfo") {
410
+ parseRoutingInfo(map.getMap("info")!!)
411
+ } else {
412
+ parseMessageInfo(map.getMap("info")!!)
413
+ }
414
+ }
415
+
416
+ companion object {
417
+ const val TAG = "RNCarPlayTemplate"
418
+ }
419
+ }
@@ -0,0 +1,35 @@
1
+ package org.birkir.carplay.parser
2
+
3
+ import androidx.car.app.CarContext
4
+ import androidx.car.app.model.Pane
5
+ import androidx.car.app.model.PaneTemplate
6
+ import androidx.car.app.model.Template
7
+ import com.facebook.react.bridge.ReadableMap
8
+ import org.birkir.carplay.screens.CarScreenContext
9
+
10
+ class TemplateParser internal constructor(
11
+ private val context: CarContext,
12
+ private val carScreenContext: CarScreenContext) {
13
+
14
+ fun parse(props: ReadableMap): Template {
15
+ val template = when (props.getString("type")) {
16
+ "list" -> RCTListTemplate(context, carScreenContext)
17
+ "grid" -> RCTGridTemplate(context, carScreenContext)
18
+ "map" -> RCTMapTemplate(context, carScreenContext)
19
+ "navigation" -> RCTMapTemplate(context, carScreenContext)
20
+ "place-list-map" -> RCTMapTemplate(context, carScreenContext)
21
+ "place-list-navigation" -> RCTMapTemplate(context, carScreenContext)
22
+ "route-preview" -> RCTMapTemplate(context, carScreenContext)
23
+ "pane" -> RCTPaneTemplate(context, carScreenContext)
24
+ "search" -> RCTSearchTemplate(context, carScreenContext)
25
+ "tabbar" -> RCTTabTemplate(context, carScreenContext)
26
+ "message" -> RCTMessageTemplate(context, carScreenContext)
27
+ else -> null
28
+ }
29
+
30
+ return template?.parse(props) ?: PaneTemplate
31
+ .Builder(
32
+ Pane.Builder().setLoading(true).build()
33
+ ).setTitle("Template missing").build()
34
+ }
35
+ }
@@ -0,0 +1,76 @@
1
+ package org.birkir.carplay.screens
2
+
3
+ import android.util.Log
4
+ import androidx.car.app.CarContext
5
+ import androidx.car.app.Screen
6
+ import androidx.car.app.model.Pane
7
+ import androidx.car.app.model.PaneTemplate
8
+ import androidx.car.app.model.PlaceListMapTemplate
9
+ import androidx.car.app.model.TabTemplate
10
+ import androidx.car.app.model.Template
11
+ import androidx.car.app.navigation.model.MapTemplate
12
+ import androidx.car.app.navigation.model.NavigationTemplate
13
+ import androidx.car.app.navigation.model.PlaceListNavigationTemplate
14
+ import androidx.car.app.navigation.model.RoutePreviewNavigationTemplate
15
+ import androidx.lifecycle.Lifecycle
16
+ import androidx.lifecycle.LifecycleEventObserver
17
+ import androidx.lifecycle.LifecycleOwner
18
+ import com.facebook.react.bridge.ReadableMap
19
+ import org.birkir.carplay.utils.VirtualRenderer
20
+
21
+ class CarScreen(carContext: CarContext) : Screen(carContext) {
22
+
23
+ var template: Template? = null
24
+ set(value) {
25
+ field = value
26
+ Log.d(TAG, "Template set: ${value?.javaClass?.simpleName}")
27
+ invalidate()
28
+ }
29
+ private var virtualRenderer: VirtualRenderer? = null
30
+
31
+ init {
32
+ lifecycle.addObserver(object : LifecycleEventObserver {
33
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
34
+ if (event == Lifecycle.Event.ON_DESTROY && virtualRenderer != null) {
35
+ Log.d(TAG, "onStateChanged: got $event, removing virtual renderer")
36
+ virtualRenderer = null
37
+ }
38
+ }
39
+ })
40
+ }
41
+
42
+ fun setTemplate(template: Template?, templateId: String, props: ReadableMap?) {
43
+ // allow MapTemplate, NavigationTemplate and PlaceListMapTemplate
44
+ val isSurfaceTemplate = template is MapTemplate
45
+ || template is NavigationTemplate
46
+ || template is PlaceListMapTemplate
47
+ || template is PlaceListNavigationTemplate
48
+ || template is RoutePreviewNavigationTemplate
49
+
50
+ if (isSurfaceTemplate && virtualRenderer == null) {
51
+ Log.d(TAG, "setTemplate: received navigation template with args: $templateId")
52
+ virtualRenderer = VirtualRenderer(carContext, templateId)
53
+ }
54
+ this.template = template
55
+ if (template is TabTemplate) {
56
+ invalidate()
57
+ }
58
+ }
59
+
60
+ override fun onGetTemplate(): Template {
61
+ Log.d(TAG, "onGetTemplate for $marker")
62
+ val currentTemplate = template
63
+ if (currentTemplate == null) {
64
+ Log.d(TAG, "Template is null, returning loading template")
65
+ return PaneTemplate.Builder(Pane.Builder().setLoading(true).build())
66
+ .setTitle("Loading...")
67
+ .build()
68
+ }
69
+ Log.d(TAG, "Returning template: ${currentTemplate::class.simpleName}")
70
+ return currentTemplate
71
+ }
72
+
73
+ companion object {
74
+ const val TAG = "CarScreen"
75
+ }
76
+ }
@@ -0,0 +1,10 @@
1
+ package org.birkir.carplay.screens
2
+
3
+ import org.birkir.carplay.utils.EventEmitter
4
+ import java.util.WeakHashMap
5
+
6
+ data class CarScreenContext(
7
+ val screenMarker: String,
8
+ var eventEmitter: EventEmitter,
9
+ var screens: WeakHashMap<String, CarScreen>
10
+ )
@@ -0,0 +1,167 @@
1
+ package org.birkir.carplay.utils
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.ReactContext
6
+ import com.facebook.react.bridge.WritableMap
7
+ import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
8
+
9
+
10
+ class EventEmitter(
11
+ private var reactContext: ReactContext? = null,
12
+ private var templateId: String? = null
13
+ ) {
14
+
15
+ companion object {
16
+ const val DidConnect = "didConnect"
17
+ const val DidDisconnect = "didDisconnect"
18
+
19
+ // interface
20
+ const val BarButtonPressed = "barButtonPressed"
21
+ const val BackButtonPressed = "backButtonPressed"
22
+ const val DidAppear = "didAppear"
23
+ const val DidDisappear = "didDisappear"
24
+ const val WillAppear = "willAppear"
25
+ const val WillDisappear = "willDisappear"
26
+ const val ButtonPressed = "buttonPressed"
27
+
28
+ // grid
29
+ const val GridButtonPressed = "gridButtonPressed"
30
+
31
+ // information
32
+ const val ActionButtonPressed = "actionButtonPressed"
33
+
34
+ // list
35
+ const val DidSelectListItem = "didSelectListItem"
36
+
37
+ // search
38
+ const val UpdatedSearchText = "updatedSearchText"
39
+ const val SearchButtonPressed = "searchButtonPressed"
40
+ const val SelectedResult = "selectedResult"
41
+
42
+ // tab bar
43
+ const val DidSelectTemplate = "didSelectTemplate"
44
+
45
+ // now playing
46
+ const val UpNextButtonPressed = "upNextButtonPressed"
47
+ const val AlbumArtistButtonPressed = "albumArtistButtonPressed"
48
+
49
+ // poi
50
+ const val DidSelectPointOfInterest = "didSelectPointOfInterest"
51
+
52
+ // map
53
+ const val MapButtonPressed = "mapButtonPressed"
54
+ const val DidUpdatePanGestureWithTranslation = "didUpdatePanGestureWithTranslation"
55
+ const val DidEndPanGestureWithVelocity = "didEndPanGestureWithVelocity"
56
+ const val PanBeganWithDirection = "panBeganWithDirection"
57
+ const val PanEndedWithDirection = "panEndedWithDirection"
58
+ const val PanWithDirection = "panWithDirection"
59
+ const val DidBeginPanGesture = "didBeginPanGesture"
60
+ const val DidDismissPanningInterface = "didDismissPanningInterface"
61
+ const val WillDismissPanningInterface = "willDismissPanningInterface"
62
+ const val DidShowPanningInterface = "didShowPanningInterface"
63
+ const val DidDismissNavigationAlert = "didDismissNavigationAlert"
64
+ const val WillDismissNavigationAlert = "willDismissNavigationAlert"
65
+ const val DidShowNavigationAlert = "didShowNavigationAlert"
66
+ const val WillShowNavigationAlert = "willShowNavigationAlert"
67
+ const val DidCancelNavigation = "didCancelNavigation"
68
+ const val AlertActionPressed = "alertActionPressed"
69
+ const val SelectedPreviewForTrip = "selectedPreviewForTrip"
70
+ const val StartedTrip = "startedTrip"
71
+ }
72
+
73
+ fun didConnect() {
74
+ Log.d("EventEmitter", "Did connect")
75
+ emit(DidConnect)
76
+ }
77
+
78
+ fun didDisconnect() {
79
+ emit(DidDisconnect)
80
+ }
81
+
82
+ fun buttonPressed(buttonId: String) {
83
+ emit(ButtonPressed, Arguments.createMap().apply {
84
+ putString("buttonId", buttonId)
85
+ })
86
+ }
87
+
88
+ fun barButtonPressed(templateId: String, buttonId: String) {
89
+ emit(BarButtonPressed, Arguments.createMap().apply {
90
+ putString("buttonId", buttonId)
91
+ })
92
+ }
93
+
94
+ fun backButtonPressed(templateId: String?) {
95
+ emit(BackButtonPressed, Arguments.createMap().apply {
96
+ templateId?.let { putString("templateId", templateId) }
97
+ })
98
+ }
99
+
100
+ fun didSelectListItem(id: String, index: Int) {
101
+ emit(DidSelectListItem, Arguments.createMap().apply {
102
+ putString("id", id)
103
+ putInt("index", index)
104
+ })
105
+ }
106
+
107
+ fun didSelectTemplate(selectedTemplateId: String) {
108
+ emit(DidSelectTemplate, Arguments.createMap().apply {
109
+ putString("selectedTemplateId", selectedTemplateId)
110
+ })
111
+ }
112
+
113
+ fun updatedSearchText(searchText: String) {
114
+ emit(UpdatedSearchText, Arguments.createMap().apply {
115
+ putString("searchText", searchText)
116
+ })
117
+ }
118
+
119
+ fun searchButtonPressed(searchText: String) {
120
+ emit(SearchButtonPressed, Arguments.createMap().apply {
121
+ putString("searchText", searchText)
122
+ })
123
+ }
124
+
125
+ fun alertActionPressed(type: String, reason: String? = null) {
126
+ emit(AlertActionPressed, Arguments.createMap().apply {
127
+ putString("type", type);
128
+ reason?.let { putString("reason", reason) }
129
+ });
130
+ }
131
+
132
+ fun selectedResult(index: Int, id: String?) {
133
+ emit(SelectedResult, Arguments.createMap().apply {
134
+ id?.let { putString("id", id) }
135
+ putInt("index", index)
136
+ })
137
+ }
138
+
139
+ fun gridButtonPressed(id: String, index: Int) {
140
+ val event = Arguments.createMap()
141
+ event.putString("id", id)
142
+ event.putInt("index", index)
143
+ emit(GridButtonPressed, event)
144
+ }
145
+
146
+ fun didShowPanningInterface() {
147
+ emit(DidShowPanningInterface)
148
+ }
149
+
150
+ fun didDismissPanningInterface() {
151
+ emit(DidDismissPanningInterface)
152
+ }
153
+
154
+ private fun emit(eventName: String, data: WritableMap = Arguments.createMap()) {
155
+ if (reactContext == null) {
156
+ Log.e("RNCarPlay", "Could not send event $eventName. React context is null!")
157
+ return
158
+ }
159
+ if (templateId != null && !data.hasKey("templateId")) {
160
+ data.putString("templateId", templateId)
161
+ }
162
+ reactContext!!
163
+ .getJSModule(RCTDeviceEventEmitter::class.java)
164
+ .emit(eventName, data)
165
+ }
166
+
167
+ }