@iternio/react-native-auto-play 0.0.6 → 0.0.8
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 +2 -0
- package/android/gradle.properties +3 -1
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/AndroidAutoService.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridCluster.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VirtualRenderer.kt +93 -11
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/MapTemplate.kt +25 -1
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/Parser.kt +11 -0
- package/android/src/main/res/drawable/ic_notification.xml +7 -0
- package/android/src/main/res/layout/cluster_splashscreen.xml +22 -0
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -65,6 +65,8 @@ android {
|
|
|
65
65
|
|
|
66
66
|
buildConfigField "float", "SCALE_FACTOR", getExtOrDefault("androidAutoScaleFactor")
|
|
67
67
|
buildConfigField "long", "TELEMETRY_UPDATE_INTERVAL", getExtOrDefault("androidTelemetryUpdateInterval")
|
|
68
|
+
buildConfigField "long", "CLUSTER_SPLASH_DELAY_MS", getExtOrDefault("clusterSplashDelayMs")
|
|
69
|
+
buildConfigField "long", "CLUSTER_SPLASH_DURATION_MS", getExtOrDefault("clusterSplashDurationMs")
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
externalNativeBuild {
|
|
@@ -7,4 +7,6 @@ ReactNativeAutoPlay_ndkVersion=27.1.12297006
|
|
|
7
7
|
ReactNativeAutoPlay_isAutomotiveApp=false
|
|
8
8
|
# apply a scale factor on the root view that affects everything rendered by react-native, does not affect templates
|
|
9
9
|
ReactNativeAutoPlay_androidAutoScaleFactor=1.5f
|
|
10
|
-
ReactNativeAutoPlay_androidTelemetryUpdateInterval=4000
|
|
10
|
+
ReactNativeAutoPlay_androidTelemetryUpdateInterval=4000
|
|
11
|
+
ReactNativeAutoPlay_clusterSplashDelayMs=1000
|
|
12
|
+
ReactNativeAutoPlay_clusterSplashDurationMs=500
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
package com.margelo.nitro.swe.iternio.reactnativeautoplay
|
|
2
2
|
|
|
3
|
+
import android.Manifest
|
|
3
4
|
import android.annotation.SuppressLint
|
|
5
|
+
import android.app.Notification
|
|
6
|
+
import android.app.NotificationChannel
|
|
7
|
+
import android.app.NotificationManager
|
|
4
8
|
import android.content.ComponentName
|
|
5
9
|
import android.content.Intent
|
|
6
10
|
import android.content.ServiceConnection
|
|
7
11
|
import android.content.pm.ApplicationInfo
|
|
12
|
+
import android.content.pm.PackageManager
|
|
13
|
+
import android.graphics.Bitmap
|
|
8
14
|
import android.os.IBinder
|
|
15
|
+
import android.util.Log
|
|
9
16
|
import androidx.car.app.CarAppService
|
|
10
17
|
import androidx.car.app.Session
|
|
11
18
|
import androidx.car.app.SessionInfo
|
|
19
|
+
import androidx.car.app.notification.CarAppExtender
|
|
12
20
|
import androidx.car.app.validation.HostValidator
|
|
21
|
+
import androidx.core.app.NotificationCompat
|
|
22
|
+
import androidx.core.app.NotificationManagerCompat
|
|
13
23
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
14
24
|
import androidx.lifecycle.LifecycleOwner
|
|
15
25
|
import com.facebook.react.ReactApplication
|
|
16
26
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
17
27
|
import com.facebook.react.bridge.ReactContext
|
|
28
|
+
import com.margelo.nitro.swe.iternio.reactnativeautoplay.utils.AppInfo
|
|
18
29
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.utils.ReactContextResolver
|
|
19
30
|
import kotlinx.coroutines.CoroutineScope
|
|
20
31
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -22,6 +33,7 @@ import kotlinx.coroutines.launch
|
|
|
22
33
|
|
|
23
34
|
class AndroidAutoService : CarAppService() {
|
|
24
35
|
private lateinit var reactContext: ReactContext
|
|
36
|
+
private lateinit var notificationManager: NotificationManager
|
|
25
37
|
|
|
26
38
|
private var isServiceBound = false
|
|
27
39
|
private var isSessionStarted = false
|
|
@@ -39,11 +51,21 @@ class AndroidAutoService : CarAppService() {
|
|
|
39
51
|
|
|
40
52
|
override fun onCreate() {
|
|
41
53
|
super.onCreate()
|
|
54
|
+
instance = this
|
|
42
55
|
|
|
43
56
|
CoroutineScope(Dispatchers.Main).launch {
|
|
44
57
|
reactContext = ReactContextResolver.getReactContext(application as ReactApplication)
|
|
45
58
|
reactContext.addLifecycleEventListener(reactLifecycleObserver)
|
|
46
59
|
}
|
|
60
|
+
|
|
61
|
+
notificationManager = getSystemService(NotificationManager::class.java)
|
|
62
|
+
val appLabel = AppInfo.getApplicationLabel(this)
|
|
63
|
+
|
|
64
|
+
getSystemService(NotificationManager::class.java).createNotificationChannel(
|
|
65
|
+
NotificationChannel(
|
|
66
|
+
CHANNEL_ID, appLabel, NotificationManager.IMPORTANCE_DEFAULT
|
|
67
|
+
)
|
|
68
|
+
)
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
override fun onCreateSession(sessionInfo: SessionInfo): Session {
|
|
@@ -60,6 +82,7 @@ class AndroidAutoService : CarAppService() {
|
|
|
60
82
|
|
|
61
83
|
override fun onDestroy() {
|
|
62
84
|
super.onDestroy()
|
|
85
|
+
instance = null
|
|
63
86
|
|
|
64
87
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
65
88
|
|
|
@@ -120,7 +143,61 @@ class AndroidAutoService : CarAppService() {
|
|
|
120
143
|
}
|
|
121
144
|
}
|
|
122
145
|
|
|
146
|
+
private fun createNotification(
|
|
147
|
+
title: String?, text: String?, largeIcon: Bitmap?
|
|
148
|
+
): Notification {
|
|
149
|
+
return NotificationCompat.Builder(this, CHANNEL_ID)
|
|
150
|
+
.setSmallIcon(R.drawable.ic_notification).setOngoing(true)
|
|
151
|
+
.setCategory(NotificationCompat.CATEGORY_NAVIGATION).setOnlyAlertOnce(true)
|
|
152
|
+
.setWhen(System.currentTimeMillis()).setPriority(NotificationManager.IMPORTANCE_LOW)
|
|
153
|
+
.extend(
|
|
154
|
+
CarAppExtender.Builder().setImportance(NotificationManagerCompat.IMPORTANCE_LOW)
|
|
155
|
+
.build()
|
|
156
|
+
).apply {
|
|
157
|
+
title?.let {
|
|
158
|
+
setContentTitle(it)
|
|
159
|
+
}
|
|
160
|
+
text?.let {
|
|
161
|
+
setContentText(it)
|
|
162
|
+
setTicker(it)
|
|
163
|
+
}
|
|
164
|
+
largeIcon?.let {
|
|
165
|
+
setLargeIcon(it)
|
|
166
|
+
}
|
|
167
|
+
}.build()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fun startForeground() {
|
|
171
|
+
val isLocationPermissionGranted =
|
|
172
|
+
checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED || checkSelfPermission(
|
|
173
|
+
Manifest.permission.ACCESS_FINE_LOCATION
|
|
174
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
175
|
+
|
|
176
|
+
if (!isLocationPermissionGranted) {
|
|
177
|
+
Log.w(TAG, "location permission not granted, unable to start foreground service!")
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
startForeground(
|
|
183
|
+
NOTIFICATION_ID, createNotification(null, null, null)
|
|
184
|
+
)
|
|
185
|
+
} catch (e: SecurityException) {
|
|
186
|
+
Log.e(TAG, "failed to start foreground service", e)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fun notify(title: String?, text: String?, icon: Bitmap?) {
|
|
191
|
+
val notification = createNotification(title, text, icon)
|
|
192
|
+
notificationManager.notify(NOTIFICATION_ID, notification)
|
|
193
|
+
}
|
|
194
|
+
|
|
123
195
|
companion object {
|
|
124
196
|
const val TAG = "AndroidAutoService"
|
|
197
|
+
private const val NOTIFICATION_ID = 1
|
|
198
|
+
private const val CHANNEL_ID = "AutoPlayServiceChannel"
|
|
199
|
+
|
|
200
|
+
var instance: AndroidAutoService? = null
|
|
201
|
+
private set
|
|
125
202
|
}
|
|
126
203
|
}
|
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridCluster.kt
CHANGED
|
@@ -28,7 +28,7 @@ class HybridCluster : HybridClusterSpec() {
|
|
|
28
28
|
val carContext = AndroidAutoSession.getCarContext(clusterId)
|
|
29
29
|
?: throw IllegalArgumentException("initRootView failed, carContext for $clusterId cluster not found")
|
|
30
30
|
val result = ThreadUtil.postOnUiAndAwait {
|
|
31
|
-
VirtualRenderer(carContext, clusterId)
|
|
31
|
+
VirtualRenderer(carContext, clusterId, isCluster = true)
|
|
32
32
|
}
|
|
33
33
|
if (result.isFailure) {
|
|
34
34
|
throw result.exceptionOrNull()
|
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VirtualRenderer.kt
CHANGED
|
@@ -7,9 +7,12 @@ import android.graphics.Rect
|
|
|
7
7
|
import android.hardware.display.DisplayManager
|
|
8
8
|
import android.os.Bundle
|
|
9
9
|
import android.view.Display
|
|
10
|
+
import android.view.LayoutInflater
|
|
10
11
|
import android.view.View
|
|
11
12
|
import android.view.ViewGroup
|
|
13
|
+
import android.view.ViewTreeObserver
|
|
12
14
|
import android.widget.FrameLayout
|
|
15
|
+
import android.widget.TextView
|
|
13
16
|
import androidx.car.app.AppManager
|
|
14
17
|
import androidx.car.app.CarContext
|
|
15
18
|
import androidx.car.app.SurfaceCallback
|
|
@@ -25,6 +28,7 @@ import com.facebook.react.uimanager.DisplayMetricsHolder
|
|
|
25
28
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
26
29
|
import com.facebook.react.uimanager.common.UIManagerType
|
|
27
30
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.template.AndroidAutoTemplate
|
|
31
|
+
import com.margelo.nitro.swe.iternio.reactnativeautoplay.utils.AppInfo
|
|
28
32
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.utils.Debouncer
|
|
29
33
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.utils.ReactContextResolver
|
|
30
34
|
import kotlinx.coroutines.CoroutineScope
|
|
@@ -33,7 +37,9 @@ import kotlinx.coroutines.launch
|
|
|
33
37
|
import kotlin.math.floor
|
|
34
38
|
|
|
35
39
|
class VirtualRenderer(
|
|
36
|
-
private val context: CarContext,
|
|
40
|
+
private val context: CarContext,
|
|
41
|
+
private val moduleName: String,
|
|
42
|
+
private val isCluster: Boolean = false
|
|
37
43
|
) {
|
|
38
44
|
private lateinit var uiManager: FabricUIManager
|
|
39
45
|
private lateinit var display: Display
|
|
@@ -48,6 +54,8 @@ class VirtualRenderer(
|
|
|
48
54
|
private var height: Int = 0
|
|
49
55
|
private var width: Int = 0
|
|
50
56
|
|
|
57
|
+
private var splashWillDisappear = false
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* scale is the actual scale factor required to calculate proper insets and is passed in initialProperties to js side
|
|
53
61
|
*/
|
|
@@ -55,7 +63,7 @@ class VirtualRenderer(
|
|
|
55
63
|
val scale = BuildConfig.SCALE_FACTOR * virtualScreenDensity
|
|
56
64
|
|
|
57
65
|
init {
|
|
58
|
-
virtualRenderer
|
|
66
|
+
virtualRenderer[moduleName] = this
|
|
59
67
|
|
|
60
68
|
CoroutineScope(Dispatchers.Main).launch {
|
|
61
69
|
reactContext =
|
|
@@ -63,7 +71,7 @@ class VirtualRenderer(
|
|
|
63
71
|
|
|
64
72
|
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
65
73
|
uiManager = UIManagerHelper.getUIManager(
|
|
66
|
-
reactContext, UIManagerType.
|
|
74
|
+
reactContext, UIManagerType.FABRIC
|
|
67
75
|
) as FabricUIManager
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -80,6 +88,11 @@ class VirtualRenderer(
|
|
|
80
88
|
var visibleArea = Rect(0, 0, 0, 0)
|
|
81
89
|
|
|
82
90
|
override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
|
|
91
|
+
if (surfaceContainer.surface == null) {
|
|
92
|
+
Log.w(TAG, "surface is null")
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
val manager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
|
84
97
|
val virtualDisplay = manager.createVirtualDisplay(
|
|
85
98
|
moduleName,
|
|
@@ -188,7 +201,7 @@ class VirtualRenderer(
|
|
|
188
201
|
val left = floor((visibleArea.left + additionalMarginLeft) / scale).toDouble()
|
|
189
202
|
val right =
|
|
190
203
|
floor((width - visibleArea.right + additionalMarginRight) / scale).toDouble()
|
|
191
|
-
HybridAutoPlay.
|
|
204
|
+
HybridAutoPlay.emitSafeAreaInsets(
|
|
192
205
|
moduleName = moduleName,
|
|
193
206
|
top = top,
|
|
194
207
|
bottom = bottom,
|
|
@@ -213,7 +226,7 @@ class VirtualRenderer(
|
|
|
213
226
|
defaultMargin
|
|
214
227
|
) / scale
|
|
215
228
|
).toDouble()
|
|
216
|
-
HybridAutoPlay.
|
|
229
|
+
HybridAutoPlay.emitSafeAreaInsets(
|
|
217
230
|
moduleName = moduleName,
|
|
218
231
|
top = top,
|
|
219
232
|
bottom = bottom,
|
|
@@ -227,9 +240,9 @@ class VirtualRenderer(
|
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
private fun getMapTemplateConfig(): MapTemplateConfig? {
|
|
230
|
-
val screenManager = AndroidAutoScreen.
|
|
243
|
+
val screenManager = AndroidAutoScreen.getScreen(moduleName)?.screenManager ?: return null
|
|
231
244
|
val marker = screenManager.top.marker ?: return null
|
|
232
|
-
return AndroidAutoTemplate.
|
|
245
|
+
return AndroidAutoTemplate.getConfig(marker) as MapTemplateConfig?
|
|
233
246
|
}
|
|
234
247
|
|
|
235
248
|
private fun initRenderer() {
|
|
@@ -283,12 +296,18 @@ class VirtualRenderer(
|
|
|
283
296
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
284
297
|
super.onCreate(savedInstanceState)
|
|
285
298
|
|
|
299
|
+
var splashScreenView: View? = null
|
|
300
|
+
|
|
286
301
|
if (!this@VirtualRenderer::reactRootView.isInitialized) {
|
|
302
|
+
splashScreenView =
|
|
303
|
+
if (isCluster) getClusterSplashScreen(context, height, width) else null
|
|
304
|
+
|
|
287
305
|
val instanceManager =
|
|
288
306
|
(context.applicationContext as ReactApplication).reactNativeHost.reactInstanceManager
|
|
289
307
|
reactRootView = ReactRootView(context.applicationContext).apply {
|
|
290
308
|
layoutParams = FrameLayout.LayoutParams(
|
|
291
|
-
(this@MapPresentation.width / reactNativeScale).toInt(),
|
|
309
|
+
(this@MapPresentation.width / reactNativeScale).toInt(),
|
|
310
|
+
(this@MapPresentation.height / reactNativeScale).toInt()
|
|
292
311
|
)
|
|
293
312
|
scaleX = reactNativeScale
|
|
294
313
|
scaleY = reactNativeScale
|
|
@@ -296,6 +315,10 @@ class VirtualRenderer(
|
|
|
296
315
|
pivotY = 0f
|
|
297
316
|
setBackgroundColor(Color.DKGRAY)
|
|
298
317
|
|
|
318
|
+
splashScreenView?.let {
|
|
319
|
+
removeClusterSplashScreen({ viewTreeObserver }, it)
|
|
320
|
+
}
|
|
321
|
+
|
|
299
322
|
startReactApplication(instanceManager, moduleName, initialProperties)
|
|
300
323
|
runApplication()
|
|
301
324
|
}
|
|
@@ -308,9 +331,13 @@ class VirtualRenderer(
|
|
|
308
331
|
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
|
|
309
332
|
)
|
|
310
333
|
clipChildren = false
|
|
334
|
+
|
|
335
|
+
addView(reactRootView)
|
|
311
336
|
}
|
|
312
337
|
|
|
313
|
-
|
|
338
|
+
splashScreenView?.let {
|
|
339
|
+
rootContainer.addView(it)
|
|
340
|
+
}
|
|
314
341
|
|
|
315
342
|
setContentView(rootContainer)
|
|
316
343
|
}
|
|
@@ -331,7 +358,12 @@ class VirtualRenderer(
|
|
|
331
358
|
reactSurfaceImpl = ReactSurfaceImpl(context, moduleName, initialProperties)
|
|
332
359
|
}
|
|
333
360
|
|
|
361
|
+
var splashScreenView: View? = null
|
|
362
|
+
|
|
334
363
|
if (!this@VirtualRenderer::reactSurfaceView.isInitialized) {
|
|
364
|
+
splashScreenView =
|
|
365
|
+
if (isCluster) getClusterSplashScreen(context, height, width) else null
|
|
366
|
+
|
|
335
367
|
reactSurfaceView = ReactSurfaceView(context, reactSurfaceImpl).apply {
|
|
336
368
|
layoutParams = FrameLayout.LayoutParams(
|
|
337
369
|
(width / reactNativeScale).toInt(), (height / reactNativeScale).toInt()
|
|
@@ -341,6 +373,10 @@ class VirtualRenderer(
|
|
|
341
373
|
pivotX = 0f
|
|
342
374
|
pivotY = 0f
|
|
343
375
|
setBackgroundColor(Color.DKGRAY)
|
|
376
|
+
|
|
377
|
+
splashScreenView?.let {
|
|
378
|
+
removeClusterSplashScreen({ viewTreeObserver }, it)
|
|
379
|
+
}
|
|
344
380
|
}
|
|
345
381
|
|
|
346
382
|
reactSurfaceId = uiManager.startSurface(
|
|
@@ -363,17 +399,63 @@ class VirtualRenderer(
|
|
|
363
399
|
(reactSurfaceView.parent as ViewGroup).removeView(reactSurfaceView)
|
|
364
400
|
}
|
|
365
401
|
|
|
366
|
-
|
|
402
|
+
val rootContainer = FrameLayout(context).apply {
|
|
367
403
|
layoutParams = FrameLayout.LayoutParams(
|
|
368
404
|
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
|
|
369
405
|
)
|
|
370
406
|
clipChildren = false
|
|
371
407
|
|
|
372
408
|
addView(reactSurfaceView)
|
|
373
|
-
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
splashScreenView?.let {
|
|
412
|
+
rootContainer.addView(it)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
setContentView(rootContainer)
|
|
374
416
|
}
|
|
375
417
|
}
|
|
376
418
|
|
|
419
|
+
private fun getClusterSplashScreen(
|
|
420
|
+
context: Context, containerHeight: Int, containerWidth: Int
|
|
421
|
+
): View {
|
|
422
|
+
val layout =
|
|
423
|
+
LayoutInflater.from(context).inflate(R.layout.cluster_splashscreen, null, false)
|
|
424
|
+
val text = layout.findViewById<TextView>(R.id.splash_text)
|
|
425
|
+
|
|
426
|
+
AppInfo.getApplicationIcon(context)?.let {
|
|
427
|
+
val maxIconSize = minOf(64, (0.25 * maxOf(containerHeight, containerWidth)).toInt())
|
|
428
|
+
|
|
429
|
+
it.setBounds(0, 0, maxIconSize, maxIconSize)
|
|
430
|
+
text.setCompoundDrawables(null, it, null, null)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
text.text = AppInfo.getApplicationLabel(context)
|
|
434
|
+
|
|
435
|
+
return layout
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private fun removeClusterSplashScreen(
|
|
439
|
+
getViewTreeObserver: () -> ViewTreeObserver, splashScreenView: View
|
|
440
|
+
) {
|
|
441
|
+
getViewTreeObserver().addOnGlobalLayoutListener(object :
|
|
442
|
+
ViewTreeObserver.OnGlobalLayoutListener {
|
|
443
|
+
override fun onGlobalLayout() {
|
|
444
|
+
if (splashWillDisappear) {
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
splashWillDisappear = true
|
|
448
|
+
|
|
449
|
+
splashScreenView.animate().alpha(0f)
|
|
450
|
+
.setStartDelay(BuildConfig.CLUSTER_SPLASH_DELAY_MS)
|
|
451
|
+
.setDuration(BuildConfig.CLUSTER_SPLASH_DURATION_MS).withEndAction {
|
|
452
|
+
(splashScreenView.parent as? ViewGroup)?.removeView(splashScreenView)
|
|
453
|
+
getViewTreeObserver().removeOnGlobalLayoutListener(this)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
|
|
377
459
|
companion object {
|
|
378
460
|
const val TAG = "VirtualRenderer"
|
|
379
461
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.margelo.nitro.swe.iternio.reactnativeautoplay.template
|
|
2
2
|
|
|
3
|
+
import android.app.Service
|
|
3
4
|
import android.graphics.Color
|
|
4
5
|
import androidx.car.app.AppManager
|
|
5
6
|
import androidx.car.app.CarContext
|
|
@@ -20,6 +21,7 @@ import com.facebook.react.bridge.UiThreadUtil
|
|
|
20
21
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.AlertActionStyle
|
|
21
22
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.AlertDismissalReason
|
|
22
23
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.AndroidAutoScreen
|
|
24
|
+
import com.margelo.nitro.swe.iternio.reactnativeautoplay.AndroidAutoService
|
|
23
25
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.AndroidAutoSession
|
|
24
26
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.MapTemplateConfig
|
|
25
27
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.NitroAction
|
|
@@ -262,6 +264,8 @@ class MapTemplate(
|
|
|
262
264
|
|
|
263
265
|
updateTripDestinations()
|
|
264
266
|
}
|
|
267
|
+
|
|
268
|
+
AndroidAutoService.instance?.startForeground()
|
|
265
269
|
}
|
|
266
270
|
|
|
267
271
|
fun updateTravelEstimates(steps: Array<TripPoint>) {
|
|
@@ -287,6 +291,8 @@ class MapTemplate(
|
|
|
287
291
|
navigationInfo = null
|
|
288
292
|
|
|
289
293
|
AndroidAutoScreen.invalidateSurfaceScreens()
|
|
294
|
+
|
|
295
|
+
AndroidAutoService.instance?.stopForeground(Service.STOP_FOREGROUND_REMOVE)
|
|
290
296
|
}
|
|
291
297
|
|
|
292
298
|
fun updateManeuvers(maneuvers: NitroManeuver) {
|
|
@@ -340,11 +346,29 @@ class MapTemplate(
|
|
|
340
346
|
cardBackgroundColor = Parser.parseColor(backgroundColor)
|
|
341
347
|
|
|
342
348
|
val currentStep = Parser.parseStep(context, current)
|
|
349
|
+
val currentDistance = Parser.parseDistance(current.travelEstimates.distanceRemaining)
|
|
350
|
+
|
|
343
351
|
val nextStep = next?.let { Parser.parseStep(context, it) }
|
|
344
352
|
|
|
353
|
+
AndroidAutoService.instance?.let {
|
|
354
|
+
val notificationIcon = Parser.parseImageToBitmap(
|
|
355
|
+
context,
|
|
356
|
+
current.symbolImage.asFirstOrNull(),
|
|
357
|
+
current.symbolImage.asSecondOrNull()
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
val notificationText = currentStep.cue?.toString()
|
|
361
|
+
|
|
362
|
+
val notificationTitle = "${currentDistance.displayDistance.toInt()} ${Parser.parseDistanceUnit(currentDistance.displayUnit)}"
|
|
363
|
+
|
|
364
|
+
it.notify(
|
|
365
|
+
notificationTitle, notificationText, notificationIcon
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
navigationInfo = RoutingInfo.Builder().apply {
|
|
346
370
|
setCurrentStep(
|
|
347
|
-
currentStep,
|
|
371
|
+
currentStep, currentDistance
|
|
348
372
|
)
|
|
349
373
|
nextStep?.let { setNextStep(it) }
|
|
350
374
|
}.build()
|
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/Parser.kt
CHANGED
|
@@ -707,6 +707,17 @@ object Parser {
|
|
|
707
707
|
}
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
+
fun parseDistanceUnit(@Distance.Unit displayUnit: Int): String {
|
|
711
|
+
return when (displayUnit) {
|
|
712
|
+
Distance.UNIT_FEET -> "ft"
|
|
713
|
+
Distance.UNIT_KILOMETERS, Distance.UNIT_KILOMETERS_P1 -> "km"
|
|
714
|
+
Distance.UNIT_METERS -> "m"
|
|
715
|
+
Distance.UNIT_MILES, Distance.UNIT_MILES_P1 -> "mi"
|
|
716
|
+
Distance.UNIT_YARDS -> "yd"
|
|
717
|
+
else -> ""
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
710
721
|
fun parseMapWithContentConfig(
|
|
711
722
|
context: CarContext, mapConfig: NitroBaseMapTemplateConfig?, template: Template
|
|
712
723
|
): Template {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="128dp" android:viewportHeight="192" android:viewportWidth="192" android:width="128dp">
|
|
2
|
+
|
|
3
|
+
<path android:fillColor="@android:color/transparent" android:pathData="M56,146H26.07c-3.11,0 -5.03,-3.4 -3.43,-6.06l69.93,-116.24c1.55,-2.58 5.3,-2.58 6.86,0l69.93,116.24c1.6,2.67 -0.32,6.06 -3.43,6.06H136" android:strokeColor="@android:color/white" android:strokeLineJoin="round" android:strokeWidth="12"/>
|
|
4
|
+
|
|
5
|
+
<path android:fillColor="@android:color/transparent" android:pathData="m42,170 l54,-92 54,92 -54,-24 -54,24Z" android:strokeColor="@android:color/white" android:strokeLineJoin="round" android:strokeWidth="12"/>
|
|
6
|
+
|
|
7
|
+
</vector>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
android:layout_width="match_parent"
|
|
4
|
+
android:layout_height="match_parent"
|
|
5
|
+
android:background="#444444"
|
|
6
|
+
android:gravity="center"
|
|
7
|
+
android:orientation="vertical">
|
|
8
|
+
|
|
9
|
+
<TextView
|
|
10
|
+
android:id="@+id/splash_text"
|
|
11
|
+
android:layout_width="match_parent"
|
|
12
|
+
android:layout_height="wrap_content"
|
|
13
|
+
android:drawableTop="@android:drawable/star_big_off"
|
|
14
|
+
android:drawablePadding="16sp"
|
|
15
|
+
android:gravity="center"
|
|
16
|
+
android:text="react-native-auto-play"
|
|
17
|
+
android:textColor="@android:color/white"
|
|
18
|
+
android:textSize="20sp"
|
|
19
|
+
android:textStyle="bold"
|
|
20
|
+
android:typeface="normal" />
|
|
21
|
+
|
|
22
|
+
</LinearLayout>
|