@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.
- package/android/build.gradle +110 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/android/gradle.properties +20 -0
- package/android/gradlew +234 -0
- package/android/gradlew.bat +89 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/AndroidManifestNew.xml +30 -0
- package/android/src/main/java/org/birkir/carplay/CarPlayModule.kt +321 -0
- package/android/src/main/java/org/birkir/carplay/CarPlayPackage.kt +18 -0
- package/android/src/main/java/org/birkir/carplay/CarPlayService.kt +35 -0
- package/android/src/main/java/org/birkir/carplay/CarPlaySession.kt +126 -0
- package/android/src/main/java/org/birkir/carplay/parser/Ext.kt +11 -0
- package/android/src/main/java/org/birkir/carplay/parser/Parser.kt +18 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTGridTemplate.kt +28 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTListTemplate.kt +64 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTMapTemplate.kt +128 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTMessageTemplate.kt +28 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTPaneTemplate.kt +24 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTSearchTemplate.kt +41 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTTabTemplate.kt +157 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTTemplate.kt +419 -0
- package/android/src/main/java/org/birkir/carplay/parser/TemplateParser.kt +35 -0
- package/android/src/main/java/org/birkir/carplay/screens/CarScreen.kt +76 -0
- package/android/src/main/java/org/birkir/carplay/screens/CarScreenContext.kt +10 -0
- package/android/src/main/java/org/birkir/carplay/utils/EventEmitter.kt +167 -0
- package/android/src/main/java/org/birkir/carplay/utils/VirtualRenderer.kt +75 -0
- package/ios/RCTConvert+RNCarPlay.h +7 -8
- package/ios/RCTConvert+RNCarPlay.m +4 -3
- package/ios/RNCarPlay.h +11 -14
- package/ios/RNCarPlay.m +749 -945
- package/ios/RNCarPlayViewController.h +10 -0
- package/ios/RNCarPlayViewController.m +50 -0
- package/lib/CarPlay.d.ts +183 -0
- package/lib/CarPlay.d.ts.map +1 -0
- package/lib/index.d.ts +44 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/interfaces/Action.d.ts +13 -0
- package/lib/interfaces/Action.d.ts.map +1 -0
- package/lib/interfaces/AlertAction.d.ts +6 -0
- package/lib/interfaces/AlertAction.d.ts.map +1 -0
- package/lib/interfaces/BarButton.d.ts +39 -0
- package/lib/interfaces/BarButton.d.ts.map +1 -0
- package/lib/interfaces/CarColor.d.ts +2 -0
- package/lib/interfaces/CarColor.d.ts.map +1 -0
- package/lib/interfaces/GridButton.d.ts +24 -0
- package/lib/interfaces/GridButton.d.ts.map +1 -0
- package/lib/interfaces/Header.d.ts +15 -0
- package/lib/interfaces/Header.d.ts.map +1 -0
- package/lib/interfaces/ListItem.d.ts +90 -0
- package/lib/interfaces/ListItem.d.ts.map +1 -0
- package/lib/interfaces/ListItemUpdate.d.ts +15 -0
- package/lib/interfaces/ListItemUpdate.d.ts.map +1 -0
- package/lib/interfaces/ListSection.d.ts +21 -0
- package/lib/interfaces/ListSection.d.ts.map +1 -0
- package/lib/interfaces/Maneuver.d.ts +31 -0
- package/lib/interfaces/Maneuver.d.ts.map +1 -0
- package/lib/interfaces/MapButton.d.ts +27 -0
- package/lib/interfaces/MapButton.d.ts.map +1 -0
- package/lib/interfaces/NavigationAlert.d.ts +44 -0
- package/lib/interfaces/NavigationAlert.d.ts.map +1 -0
- package/lib/interfaces/NavigationInfo.d.ts +17 -0
- package/lib/interfaces/NavigationInfo.d.ts.map +1 -0
- package/lib/interfaces/NavigationStep.d.ts +17 -0
- package/lib/interfaces/NavigationStep.d.ts.map +1 -0
- package/lib/interfaces/Pane.d.ts +29 -0
- package/lib/interfaces/Pane.d.ts.map +1 -0
- package/lib/interfaces/PauseReason.d.ts +8 -0
- package/lib/interfaces/PauseReason.d.ts.map +1 -0
- package/lib/interfaces/Place.d.ts +11 -0
- package/lib/interfaces/Place.d.ts.map +1 -0
- package/lib/interfaces/TextConfiguration.d.ts +6 -0
- package/lib/interfaces/TextConfiguration.d.ts.map +1 -0
- package/lib/interfaces/TimeRemainingColor.d.ts +2 -0
- package/lib/interfaces/TimeRemainingColor.d.ts.map +1 -0
- package/lib/interfaces/TravelEstimates.d.ts +37 -0
- package/lib/interfaces/TravelEstimates.d.ts.map +1 -0
- package/lib/interfaces/VoiceControlState.d.ts +8 -0
- package/lib/interfaces/VoiceControlState.d.ts.map +1 -0
- package/lib/navigation/NavigationSession.d.ts +18 -0
- package/lib/navigation/NavigationSession.d.ts.map +1 -0
- package/lib/navigation/Trip.d.ts +22 -0
- package/lib/navigation/Trip.d.ts.map +1 -0
- package/lib/templates/ActionSheetTemplate.d.ts +18 -0
- package/lib/templates/ActionSheetTemplate.d.ts.map +1 -0
- package/lib/templates/AlertTemplate.d.ts +17 -0
- package/lib/templates/AlertTemplate.d.ts.map +1 -0
- package/lib/templates/ContactTemplate.d.ts +36 -0
- package/lib/templates/ContactTemplate.d.ts.map +1 -0
- package/lib/templates/GridTemplate.d.ts +38 -0
- package/lib/templates/GridTemplate.d.ts.map +1 -0
- package/lib/templates/InformationTemplate.d.ts +28 -0
- package/lib/templates/InformationTemplate.d.ts.map +1 -0
- package/lib/templates/ListTemplate.d.ts +127 -0
- package/lib/templates/ListTemplate.d.ts.map +1 -0
- package/lib/templates/ListTemplate.js +24 -16
- package/lib/templates/MapTemplate.d.ts +171 -0
- package/lib/templates/MapTemplate.d.ts.map +1 -0
- package/lib/templates/NowPlayingTemplate.d.ts +31 -0
- package/lib/templates/NowPlayingTemplate.d.ts.map +1 -0
- package/lib/templates/PointOfInterestTemplate.d.ts +33 -0
- package/lib/templates/PointOfInterestTemplate.d.ts.map +1 -0
- package/lib/templates/SearchTemplate.d.ts +32 -0
- package/lib/templates/SearchTemplate.d.ts.map +1 -0
- package/lib/templates/SearchTemplate.js +2 -2
- package/lib/templates/TabBarTemplate.d.ts +27 -0
- package/lib/templates/TabBarTemplate.d.ts.map +1 -0
- package/lib/templates/Template.d.ts +82 -0
- package/lib/templates/Template.d.ts.map +1 -0
- package/lib/templates/Template.js +1 -1
- package/lib/templates/VoiceControlTemplate.d.ts +18 -0
- package/lib/templates/VoiceControlTemplate.d.ts.map +1 -0
- package/lib/templates/android/AndroidNavigationBaseTemplate.d.ts +19 -0
- package/lib/templates/android/AndroidNavigationBaseTemplate.d.ts.map +1 -0
- package/lib/templates/android/MessageTemplate.d.ts +16 -0
- package/lib/templates/android/MessageTemplate.d.ts.map +1 -0
- package/lib/templates/android/NavigationTemplate.d.ts +44 -0
- package/lib/templates/android/NavigationTemplate.d.ts.map +1 -0
- package/lib/templates/android/PaneTemplate.d.ts +13 -0
- package/lib/templates/android/PaneTemplate.d.ts.map +1 -0
- package/lib/templates/android/PlaceListMapTemplate.d.ts +58 -0
- package/lib/templates/android/PlaceListMapTemplate.d.ts.map +1 -0
- package/lib/templates/android/PlaceListNavigationTemplate.d.ts +51 -0
- package/lib/templates/android/PlaceListNavigationTemplate.d.ts.map +1 -0
- package/lib/templates/android/RoutePreviewNavigationTemplate.d.ts +60 -0
- package/lib/templates/android/RoutePreviewNavigationTemplate.d.ts.map +1 -0
- package/package.json +3 -3
- package/react-native-carplay.podspec +3 -3
- package/src/CarPlay.ts +28 -16
- package/src/interfaces/ListItem.ts +14 -8
- package/src/interfaces/ListSection.ts +1 -1
- package/src/templates/ListTemplate.ts +64 -44
- package/src/templates/NowPlayingTemplate.ts +10 -3
- package/src/templates/SearchTemplate.ts +2 -2
- package/src/templates/Template.ts +1 -1
- 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,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
|
+
}
|