Compare commits
3 Commits
aaa57c14b8
...
90010d91b7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90010d91b7 | ||
|
|
2348d3b633 | ||
|
|
263b5b576d |
@@ -17,8 +17,8 @@ android {
|
||||
applicationId = "com.kouros.navigation"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = 83
|
||||
versionName = "0.2.0.83"
|
||||
versionCode = 84
|
||||
versionName = "0.2.0.84"
|
||||
base.archivesName = "navi-$versionName"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name="com.kouros.navigation.car.navigation.NavigationService"
|
||||
android:enabled="true"
|
||||
android:name=".car.NavigationNotificationService"
|
||||
android:foregroundServiceType="location"
|
||||
android:exported="true">
|
||||
</service>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.car.permission.CAR_SPEED"/>
|
||||
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.type.automotive"
|
||||
@@ -71,8 +72,7 @@
|
||||
android:value="true" />
|
||||
</activity>
|
||||
<service
|
||||
android:name="com.kouros.navigation.car.navigation.NavigationService"
|
||||
android:enabled="true"
|
||||
android:name=".car.NavigationNotificationService"
|
||||
android:foregroundServiceType="location"
|
||||
android:exported="true">
|
||||
</service>
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.kouros.data.R
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.car.screen.NavigationListener
|
||||
import com.kouros.navigation.car.screen.NavigationScreen
|
||||
import com.kouros.navigation.data.Place
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -67,7 +68,7 @@ internal class ClusterSession : Session(), NavigationListener {
|
||||
OnClickListener {})
|
||||
.build()
|
||||
|
||||
mNavigationCarSurface = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||
mNavigationCarSurface = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner)
|
||||
|
||||
// mNavigationScreen =
|
||||
// new NavigationScreen(getCarContext(), mSettingsAction, this, mNavigationCarSurface);
|
||||
@@ -99,6 +100,10 @@ internal class ClusterSession : Session(), NavigationListener {
|
||||
override fun updateTrip(trip: Trip) {
|
||||
}
|
||||
|
||||
override fun navigateToPlace(place: Place) {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = ClusterSession::class.java.getSimpleName()
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class DeviceLocationManager(
|
||||
* @param minDistanceM Minimum distance between updates in meters (default: 5m)
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
fun startLocationUpdates(minTimeMs: Long = 500, minDistanceM: Float = 5f) {
|
||||
fun startLocationUpdates(minTimeMs: Long = 1000, minDistanceM: Float = 5f) {
|
||||
if (isListening) return
|
||||
|
||||
// Get and deliver last known location first
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.kouros.navigation.data.Constants.TAG
|
||||
|
||||
class NavigationCarAppService : CarAppService() {
|
||||
|
||||
val INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP =
|
||||
val intentActionNavNotificationOpenApp =
|
||||
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
|
||||
|
||||
val channelId: String = "NavigationSessionChannel"
|
||||
@@ -31,7 +31,6 @@ class NavigationCarAppService : CarAppService() {
|
||||
}
|
||||
|
||||
override fun onCreateSession(sessionInfo: SessionInfo): Session {
|
||||
Log.d(TAG, "Display Type: ${sessionInfo.displayType}")
|
||||
if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) {
|
||||
return ClusterSession()
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.util.Log
|
||||
import androidx.car.app.notification.CarAppExtender
|
||||
import androidx.car.app.notification.CarNotificationManager
|
||||
import androidx.car.app.notification.CarPendingIntent
|
||||
@@ -16,6 +17,7 @@ import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -44,17 +46,12 @@ class NavigationNotificationService : Service() {
|
||||
Handler(Looper.getMainLooper(), HandlerCallback())
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
val message = intent.getStringExtra("EXTRA_MESSAGE") ?: "Navigating..."
|
||||
initNotifications(this)
|
||||
startForeground(
|
||||
NAV_NOTIFICATION_ID,
|
||||
getNavigationNotification(this, mNotificationCount).build()
|
||||
)
|
||||
|
||||
// Start updating the notification continuously.
|
||||
mHandler.sendMessageDelayed(
|
||||
mHandler.obtainMessage(MSG_SEND_NOTIFICATION), NAV_NOTIFICATION_DELAY_IN_MILLIS
|
||||
)
|
||||
|
||||
val notification = getNavigationNotification(this, message)
|
||||
// This updates the existing notification if the service is already running
|
||||
CarNotificationManager.from(this).notify(NAV_NOTIFICATION_ID, notification)
|
||||
startForeground(NAV_NOTIFICATION_ID, notification.build())
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
@@ -71,11 +68,12 @@ class NavigationNotificationService : Service() {
|
||||
*/
|
||||
internal inner class HandlerCallback : Handler.Callback {
|
||||
override fun handleMessage(msg: Message): Boolean {
|
||||
Log.d(TAG, "Notification handleMessage: $msg")
|
||||
if (msg.what == MSG_SEND_NOTIFICATION) {
|
||||
val context: Context = this@NavigationNotificationService
|
||||
CarNotificationManager.from(context).notify(
|
||||
NAV_NOTIFICATION_ID,
|
||||
getNavigationNotification(context, mNotificationCount)
|
||||
getNavigationNotification(context, "Nachricht")
|
||||
)
|
||||
mNotificationCount++
|
||||
mHandler.sendMessageDelayed(
|
||||
@@ -96,6 +94,12 @@ class NavigationNotificationService : Service() {
|
||||
val mOnlyAlertOnce: Boolean
|
||||
)
|
||||
|
||||
fun startForeground(message: String) {
|
||||
startForeground(
|
||||
NAV_NOTIFICATION_ID,
|
||||
getNavigationNotification(this, message).build()
|
||||
)
|
||||
}
|
||||
companion object {
|
||||
private const val MSG_SEND_NOTIFICATION = 1
|
||||
private const val NAV_NOTIFICATION_CHANNEL_ID = "nav_channel_00"
|
||||
@@ -124,21 +128,40 @@ class NavigationNotificationService : Service() {
|
||||
CarNotificationManager.from(context).createNotificationChannel(navChannel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [DirectionInfo] that corresponds to the given notification count.
|
||||
*
|
||||
*
|
||||
* There are 5 directions, repeating in order. For each direction, the alert will only show
|
||||
* once, but the distance will update on every count on the rail widget.
|
||||
*/
|
||||
private fun getDirectionInfo(context: Context, message: String): DirectionInfo {
|
||||
val formatter = DecimalFormat("#.##")
|
||||
formatter.setRoundingMode(RoundingMode.DOWN)
|
||||
val distance = formatter.format((10) * 0.1) + "km"
|
||||
return DirectionInfo(
|
||||
message,
|
||||
distance,
|
||||
R.drawable.navigation_48px,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns the navigation notification that corresponds to the given notification count. */
|
||||
fun getNavigationNotification(
|
||||
context: Context, notificationCount: Int
|
||||
context: Context
|
||||
): NotificationCompat.Builder {
|
||||
val builder =
|
||||
NotificationCompat.Builder(context, NAV_NOTIFICATION_CHANNEL_ID)
|
||||
val directionInfo = getDirectionInfo(context, notificationCount)
|
||||
val directionInfo = getDirectionInfo(context, "Test")
|
||||
|
||||
// Set an intent to open the car app. The app receives this intent when the user taps the
|
||||
// heads-up notification or the rail widget.
|
||||
val pendingIntent = CarPendingIntent.getCarApp(
|
||||
context,
|
||||
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP.hashCode(),
|
||||
NavigationCarAppService().intentActionNavNotificationOpenApp.hashCode(),
|
||||
Intent(
|
||||
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP
|
||||
NavigationCarAppService().intentActionNavNotificationOpenApp
|
||||
).setComponent(
|
||||
ComponentName(
|
||||
context,
|
||||
@@ -146,7 +169,7 @@ class NavigationNotificationService : Service() {
|
||||
)
|
||||
).setData(
|
||||
NavigationCarAppService().createDeepLinkUri(
|
||||
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP
|
||||
NavigationCarAppService().intentActionNavNotificationOpenApp
|
||||
)
|
||||
),
|
||||
0
|
||||
@@ -179,54 +202,59 @@ class NavigationNotificationService : Service() {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [DirectionInfo] that corresponds to the given notification count.
|
||||
*
|
||||
*
|
||||
* There are 5 directions, repeating in order. For each direction, the alert will only show
|
||||
* once, but the distance will update on every count on the rail widget.
|
||||
*/
|
||||
private fun getDirectionInfo(context: Context, notificationCount: Int): DirectionInfo {
|
||||
val formatter = DecimalFormat("#.##")
|
||||
formatter.setRoundingMode(RoundingMode.DOWN)
|
||||
val repeatingCount = notificationCount % 35
|
||||
if (repeatingCount in 0..<10) {
|
||||
// Distance decreases from 1km to 0.1km
|
||||
val distance = formatter.format((10 - repeatingCount) * 0.1) + "km"
|
||||
return DirectionInfo(
|
||||
context.getString(R.string.stop_action_title),
|
||||
distance,
|
||||
R.drawable.arrow_back_24px,
|
||||
repeatingCount > 0
|
||||
fun getNavigationNotification(
|
||||
context: Context, message: String
|
||||
): NotificationCompat.Builder {
|
||||
val builder =
|
||||
NotificationCompat.Builder(context, NAV_NOTIFICATION_CHANNEL_ID)
|
||||
val directionInfo = getDirectionInfo(context, message)
|
||||
|
||||
// Set an intent to open the car app. The app receives this intent when the user taps the
|
||||
// heads-up notification or the rail widget.
|
||||
val pendingIntent = CarPendingIntent.getCarApp(
|
||||
context,
|
||||
NavigationCarAppService().intentActionNavNotificationOpenApp.hashCode(),
|
||||
Intent(
|
||||
NavigationCarAppService().intentActionNavNotificationOpenApp
|
||||
).setComponent(
|
||||
ComponentName(
|
||||
context,
|
||||
NavigationCarAppService()::class.java
|
||||
)
|
||||
).setData(
|
||||
NavigationCarAppService().createDeepLinkUri(
|
||||
NavigationCarAppService().intentActionNavNotificationOpenApp
|
||||
)
|
||||
),
|
||||
0
|
||||
)
|
||||
|
||||
return builder
|
||||
// This title, text, and icon will be shown in both phone and car screen. These
|
||||
// values can
|
||||
// be overridden in the extender below, to customize notifications in the car
|
||||
// screen.
|
||||
.setContentTitle(directionInfo.mTitle)
|
||||
.setContentText(directionInfo.mDistance)
|
||||
.setSmallIcon(directionInfo.mIcon) // The notification must be set to 'ongoing' and its category must be set to
|
||||
// CATEGORY_NAVIGATION in order to show it in the rail widget when the app is
|
||||
// navigating on
|
||||
// the background.
|
||||
// These values cannot be overridden in the extender.
|
||||
|
||||
.setOngoing(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_NAVIGATION) // If set to true, the notification will only show the alert once in both phone and
|
||||
// car screen. This value cannot be overridden in the extender.
|
||||
|
||||
.setOnlyAlertOnce(directionInfo.mOnlyAlertOnce) // This extender must be set in order to display the notification in the car screen.
|
||||
// The extender also allows various customizations, such as showing different title
|
||||
// or icon on the car screen.
|
||||
|
||||
.extend(
|
||||
CarAppExtender.Builder()
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
)
|
||||
} else if (repeatingCount in 10..<20) {
|
||||
// Distance decreases from 5km to 0.5km
|
||||
val distance = formatter.format((20 - repeatingCount) * 0.5) + "km"
|
||||
return DirectionInfo(
|
||||
context.getString(R.string.route_preview),
|
||||
distance,
|
||||
R.drawable.ic_turn_normal_right, /* onlyAlertOnce= */
|
||||
repeatingCount > 10
|
||||
)
|
||||
} else if (repeatingCount in 20..<25) {
|
||||
// Distance decreases from 200m to 40m
|
||||
val distance = formatter.format(((25 - repeatingCount) * 40).toLong()) + "m"
|
||||
return DirectionInfo(
|
||||
context.getString(R.string.route_preview),
|
||||
distance,
|
||||
R.drawable.navigation_48px, /* onlyAlertOnce= */
|
||||
repeatingCount > 20
|
||||
)
|
||||
} else {
|
||||
// Distance decreases from 1km to 0.1km
|
||||
val distance = formatter.format((35 - repeatingCount) * 0.1) + "km"
|
||||
return DirectionInfo(
|
||||
context.getString(R.string.charging_station),
|
||||
distance,
|
||||
R.drawable.local_gas_station_24,
|
||||
repeatingCount > 25
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package com.kouros.navigation.car
|
||||
|
||||
import android.Manifest.permission
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
@@ -15,13 +11,11 @@ import androidx.car.app.Screen
|
||||
import androidx.car.app.ScreenManager
|
||||
import androidx.car.app.Session
|
||||
import androidx.car.app.connection.CarConnection
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.Distance
|
||||
import androidx.car.app.navigation.NavigationManager
|
||||
import androidx.car.app.navigation.NavigationManagerCallback
|
||||
import androidx.car.app.navigation.model.Destination
|
||||
import androidx.car.app.navigation.model.Step
|
||||
import androidx.car.app.navigation.model.TravelEstimate
|
||||
import androidx.car.app.navigation.model.Trip
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
@@ -40,25 +34,38 @@ import com.kouros.navigation.car.screen.NavigationType
|
||||
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
||||
import com.kouros.navigation.car.screen.SearchScreen
|
||||
import com.kouros.navigation.car.screen.checkPermission
|
||||
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
||||
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
||||
import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION
|
||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION
|
||||
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.overpass.Elements
|
||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.GeoUtils
|
||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
||||
import com.kouros.navigation.utils.formattedDistance
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import com.kouros.navigation.utils.getSettingsViewModel
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
/**
|
||||
@@ -67,7 +74,7 @@ import java.time.ZoneOffset
|
||||
* car hardware sensors, routing engine selection, and screen navigation.
|
||||
* Implements NavigationScreen.Listener for handling navigation events.
|
||||
*/
|
||||
class NavigationSession : Session(), NavigationListener {
|
||||
class NavigationSession : Session(), NavigationListener, NavigationObserverCallback {
|
||||
|
||||
// Flag to enable/disable contact access feature
|
||||
val useContacts = false
|
||||
@@ -75,6 +82,8 @@ class NavigationSession : Session(), NavigationListener {
|
||||
// Model for managing route state and navigation logic for Android Auto
|
||||
lateinit var routeModel: RouteCarModel
|
||||
|
||||
var route = ""
|
||||
|
||||
// Main navigation screen displayed to the user
|
||||
lateinit var navigationScreen: NavigationScreen
|
||||
|
||||
@@ -91,18 +100,44 @@ class NavigationSession : Session(), NavigationListener {
|
||||
|
||||
lateinit var textToSpeechManager: TextToSpeechManager
|
||||
|
||||
lateinit var notificationManager: NotificationManager
|
||||
|
||||
|
||||
var autoDriveEnabled = false
|
||||
|
||||
val simulation = Simulation()
|
||||
|
||||
private var routingEngine = 0
|
||||
|
||||
private var showTraffic = false;
|
||||
|
||||
private var distanceMode = 0
|
||||
var lastCameraSearch = 0
|
||||
|
||||
var speedCameras = listOf<Elements>()
|
||||
|
||||
var lastRouteDate: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
var navigationManagerStarted = false
|
||||
|
||||
var notificationActive = false
|
||||
|
||||
/**
|
||||
* Lifecycle observer for managing session lifecycle events.
|
||||
* Cleans up resources when the session is destroyed.
|
||||
*/
|
||||
private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
Log.d(TAG, "NavigationSession paused")
|
||||
super.onPause(owner)
|
||||
}
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
Log.d(TAG, "NavigationSession resumed")
|
||||
super.onResume(owner)
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
if (::navigationManager.isInitialized) {
|
||||
navigationManager.clearNavigationManagerCallback()
|
||||
@@ -116,6 +151,13 @@ class NavigationSession : Session(), NavigationListener {
|
||||
if (::textToSpeechManager.isInitialized) {
|
||||
textToSpeechManager.cleanup()
|
||||
}
|
||||
carContext
|
||||
.stopService(
|
||||
Intent(
|
||||
carContext,
|
||||
NavigationNotificationService::class.java
|
||||
)
|
||||
)
|
||||
Log.i(TAG, "NavigationSession destroyed")
|
||||
}
|
||||
}
|
||||
@@ -130,8 +172,26 @@ class NavigationSession : Session(), NavigationListener {
|
||||
|
||||
var guidanceAudio = 0
|
||||
|
||||
var lastTrafficDate: LocalDateTime = LocalDateTime.MIN
|
||||
lateinit var observerManager: NavigationObserverManager
|
||||
|
||||
val repository = getSettingsRepository(carContext)
|
||||
|
||||
val settingsViewModel = getSettingsViewModel(carContext)
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(lifecycleObserver)
|
||||
repository.routingEngineFlow.asLiveData().observe(this, Observer {
|
||||
routingEngine = it
|
||||
})
|
||||
|
||||
repository.trafficFlow.asLiveData().observe(this, Observer {
|
||||
showTraffic = it
|
||||
})
|
||||
repository.distanceModeFlow.asLiveData().observe(this, Observer {
|
||||
distanceMode = it
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,6 +204,8 @@ class NavigationSession : Session(), NavigationListener {
|
||||
RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository())
|
||||
else -> NavigationViewModel(TomTomRepository())
|
||||
}
|
||||
observerManager = NavigationObserverManager(navigationViewModel, this)
|
||||
observerManager.attachAllObservers(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,11 +228,13 @@ class NavigationSession : Session(), NavigationListener {
|
||||
when (connectionState) {
|
||||
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> Unit
|
||||
CarConnection.CONNECTION_TYPE_NATIVE -> {
|
||||
navigationViewModel.permissionGranted.value = checkPermission(carContext,AUTOMOTIVE_CAR_SPEED_PERMISSION)
|
||||
navigationViewModel.permissionGranted.value =
|
||||
checkPermission(carContext, AUTOMOTIVE_CAR_SPEED_PERMISSION)
|
||||
}
|
||||
|
||||
CarConnection.CONNECTION_TYPE_PROJECTION -> {
|
||||
navigationViewModel.permissionGranted.value = checkPermission(carContext, GMS_CAR_SPEED_PERMISSION)
|
||||
navigationViewModel.permissionGranted.value =
|
||||
checkPermission(carContext, GMS_CAR_SPEED_PERMISSION)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +294,7 @@ class NavigationSession : Session(), NavigationListener {
|
||||
autoDriveEnabled = true
|
||||
startNavigation()
|
||||
CarToast.makeText(carContext, "Auto drive enabled", CarToast.LENGTH_LONG)
|
||||
.show()
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onStopNavigation() {
|
||||
@@ -242,7 +306,7 @@ class NavigationSession : Session(), NavigationListener {
|
||||
}
|
||||
}
|
||||
})
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner)
|
||||
|
||||
carSensorManager = CarSensorManager(
|
||||
carContext = carContext,
|
||||
@@ -272,6 +336,7 @@ class NavigationSession : Session(), NavigationListener {
|
||||
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
|
||||
guidanceAudio = it
|
||||
})
|
||||
notificationManager = NotificationManager(carContext, this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,7 +346,6 @@ class NavigationSession : Session(), NavigationListener {
|
||||
navigationScreen = NavigationScreen(
|
||||
carContext,
|
||||
surfaceRenderer,
|
||||
routeModel,
|
||||
this,
|
||||
navigationViewModel
|
||||
)
|
||||
@@ -369,6 +433,11 @@ class NavigationSession : Session(), NavigationListener {
|
||||
* Handles route snapping, deviation detection for rerouting, and map updates.
|
||||
*/
|
||||
fun updateLocation(location: Location) {
|
||||
val streetName = if (routeModel.isNavigating()) {
|
||||
routeModel.currentStep().street
|
||||
} else {
|
||||
""
|
||||
}
|
||||
if (routeModel.navState.carConnection == CarConnection.CONNECTION_TYPE_PROJECTION) {
|
||||
surfaceRenderer.updateCarSpeed(location.speed)
|
||||
}
|
||||
@@ -376,8 +445,8 @@ class NavigationSession : Session(), NavigationListener {
|
||||
if (routeModel.isNavigating()) {
|
||||
handleNavigationLocation(location)
|
||||
} else {
|
||||
navigationScreen.checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location)
|
||||
surfaceRenderer.updateLocation(location)
|
||||
checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location)
|
||||
surfaceRenderer.updateLocation(location, streetName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,43 +464,111 @@ class NavigationSession : Session(), NavigationListener {
|
||||
* Snaps location to route and checks for deviation requiring reroute.
|
||||
*/
|
||||
private fun handleNavigationLocation(location: Location) {
|
||||
|
||||
routeModel.updateLocation(location, navigationViewModel)
|
||||
if (routeModel.navState.arrived) return
|
||||
if (guidanceAudio == 1) {
|
||||
handleGuidanceAudio()
|
||||
}
|
||||
navigationScreen.updateTrip(location)
|
||||
if (routeModel.navState.arrived) return
|
||||
val streetName = routeModel.currentStep().street
|
||||
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
||||
|
||||
checkTraffic(currentDate, location)
|
||||
updateSpeedCamera(location)
|
||||
checkRoute(currentDate, location)
|
||||
checkArrival()
|
||||
updateNavigationScreen()
|
||||
|
||||
snapLocation(location, streetName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the surface renderer with snapped location and street name.
|
||||
* Checks if maximal route deviation is exceeded and reroutes if needed.
|
||||
*/
|
||||
private fun snapLocation(location: Location, streetName: String) {
|
||||
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||
val distance = location.distanceTo(snappedLocation)
|
||||
when {
|
||||
distance > MAXIMAL_ROUTE_DEVIATION -> {
|
||||
stopNavigation()
|
||||
navigationScreen.calculateNewRoute(routeModel.navState.destination)
|
||||
}
|
||||
|
||||
distance < MAXIMAL_SNAP_CORRECTION -> {
|
||||
surfaceRenderer.updateLocation(snappedLocation)
|
||||
surfaceRenderer.updateLocation(snappedLocation, streetName)
|
||||
}
|
||||
|
||||
else -> {
|
||||
surfaceRenderer.updateLocation(location)
|
||||
surfaceRenderer.updateLocation(location, streetName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops active navigation and clears route state.
|
||||
* Called when user exits navigation or arrives at destination.
|
||||
* Updates the navigation screen with new trip information.
|
||||
*/
|
||||
override fun stopNavigation() {
|
||||
routeModel.stopNavigation()
|
||||
navigationManager.navigationEnded()
|
||||
if (autoDriveEnabled) {
|
||||
simulation.stopSimulation()
|
||||
autoDriveEnabled = false
|
||||
fun updateNavigationScreen() {
|
||||
if (routeModel.navState.destination.name.isEmpty()
|
||||
&& routeModel.navState.destination.street.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val travelEstimateTrip = routeModel.travelEstimateTrip(carContext, distanceMode)
|
||||
val travelEstimateStep = routeModel.travelEstimateStep(carContext, distanceMode)
|
||||
val steps = mutableListOf<Step>()
|
||||
val destination = Destination.Builder()
|
||||
.setName(routeModel.navState.destination.name)
|
||||
.setAddress(routeModel.navState.destination.street)
|
||||
.build()
|
||||
val distance =
|
||||
formattedDistance(0, routeModel.routeCalculator.leftStepDistance())
|
||||
steps.add(routeModel.currentStep(carContext))
|
||||
if (routeModel.navState.nextStep) {
|
||||
steps.add(routeModel.nextStep(carContext = carContext))
|
||||
|
||||
}
|
||||
navigationScreen.updateTrip(
|
||||
isNavigating = routeModel.isNavigating(),
|
||||
isRerouting = false,
|
||||
hasArrived = routeModel.isArrival(),
|
||||
destinationTravelEstimate = travelEstimateTrip,
|
||||
stepTravelEstimate = travelEstimateStep,
|
||||
destinations = mutableListOf(destination),
|
||||
steps = steps,
|
||||
stepRemainingDistance = Distance.create(distance.first, distance.second),
|
||||
shouldShowNextStep = false,
|
||||
shouldShowLanes = true,
|
||||
junctionImage = null,
|
||||
backGroundColor = routeModel.backGroundColor()
|
||||
)
|
||||
|
||||
/**
|
||||
* Updates the trip information and notifies the listener with a new Trip object.
|
||||
* This includes destination name, address, travel estimate, and loading status.
|
||||
*/
|
||||
|
||||
val tripBuilder = Trip.Builder()
|
||||
tripBuilder.addDestination(
|
||||
destination,
|
||||
travelEstimateTrip
|
||||
)
|
||||
tripBuilder.setLoading(false)
|
||||
tripBuilder.setCurrentRoad(destination.name.toString())
|
||||
tripBuilder.addStep(steps.first(), travelEstimateStep)
|
||||
updateTrip(tripBuilder.build())
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for arrival
|
||||
*/
|
||||
fun checkArrival() {
|
||||
if (routeModel.isArrival()
|
||||
&& routeModel.routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||
) {
|
||||
stopNavigation()
|
||||
settingsViewModel.onLastRouteChanged("")
|
||||
routeModel.navState = routeModel.navState.copy(arrived = true)
|
||||
surfaceRenderer.routeData.value = ""
|
||||
}
|
||||
surfaceRenderer.routeData.value = ""
|
||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||
navigationScreen.navigationType = NavigationType.VIEW
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,6 +586,27 @@ class NavigationSession : Session(), NavigationListener {
|
||||
updateLocation(location)
|
||||
}
|
||||
}
|
||||
if (notificationActive)
|
||||
notificationManager.startNotificationService()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops active navigation and clears route state.
|
||||
* Called when user exits navigation or arrives at destination.
|
||||
*/
|
||||
override fun stopNavigation() {
|
||||
routeModel.stopNavigation()
|
||||
navigationManager.navigationEnded()
|
||||
if (autoDriveEnabled) {
|
||||
simulation.stopSimulation()
|
||||
autoDriveEnabled = false
|
||||
}
|
||||
surfaceRenderer.routeData.value = ""
|
||||
lastCameraSearch = 0
|
||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||
navigationScreen.navigationType = NavigationType.VIEW
|
||||
if (notificationActive)
|
||||
notificationManager.stopNotificationService()
|
||||
}
|
||||
|
||||
override fun updateTrip(trip: Trip) {
|
||||
@@ -467,10 +625,199 @@ class NavigationSession : Session(), NavigationListener {
|
||||
if (currentStep.index > lastStepIndex && stepData.leftStepDistance < INSTRUCTION_DISTANCE) {
|
||||
textToSpeechManager.speak(stepData.message)
|
||||
lastStepIndex = currentStep.index
|
||||
if (notificationActive) {
|
||||
notificationManager.sendMessage(stepData.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Handles the received route string.
|
||||
* Starts navigation and invalidates the screen.
|
||||
*/
|
||||
override fun onRouteReceived(route: String) {
|
||||
if (route.isNotEmpty()) {
|
||||
this.route = route
|
||||
if (routeModel.isNavigating()) {
|
||||
updateRoute(route)
|
||||
} else {
|
||||
prepareRoute(route)
|
||||
}
|
||||
updateNavigationScreen()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare route and start navigation
|
||||
*/
|
||||
private fun prepareRoute(route: String) {
|
||||
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||
routeModel.startNavigation(route)
|
||||
if (routeModel.hasLegs()) {
|
||||
settingsViewModel.onLastRouteChanged(route)
|
||||
}
|
||||
surfaceRenderer.setRouteData(routeModel.curRoute.routeGeoJson)
|
||||
startNavigation()
|
||||
updateNavigationScreen()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update route and traffic data
|
||||
*/
|
||||
private fun updateRoute(route: String) {
|
||||
val newRouteModel = RouteModel()
|
||||
newRouteModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||
newRouteModel.startNavigation(route)
|
||||
routeModel.curRoute.summary.trafficDelay = newRouteModel.curRoute.summary.trafficDelay
|
||||
updateNavigationScreen()
|
||||
}
|
||||
|
||||
|
||||
override fun isNavigating(): Boolean = routeModel.isNavigating()
|
||||
|
||||
/**
|
||||
* Handles received traffic data and updates the surface renderer.
|
||||
*/
|
||||
override fun onTrafficReceived(traffic: Map<String, String>) {
|
||||
if (traffic.isNotEmpty()) {
|
||||
surfaceRenderer.setTrafficData(traffic)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the received place search result.
|
||||
* Navigates to the specified place.
|
||||
*/
|
||||
override fun onPlaceSearchResultReceived(place: Place) {
|
||||
navigateToPlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles received speed camera data.
|
||||
* Updates the surface renderer with the camera locations.
|
||||
*/
|
||||
override fun onSpeedCamerasReceived(cameras: List<Elements>) {
|
||||
speedCameras = cameras
|
||||
val coordinates = mutableListOf<List<Double>>()
|
||||
cameras.forEach {
|
||||
coordinates.add(listOf(it.lon, it.lat))
|
||||
}
|
||||
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
||||
surfaceRenderer.speedCamerasData.value = speedData
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles received maximum speed data and updates the surface renderer.
|
||||
*/
|
||||
override fun onMaxSpeedReceived(speed: Int) {
|
||||
surfaceRenderer.maxSpeed.value = speed
|
||||
}
|
||||
|
||||
override fun invalidateScreen() {
|
||||
navigationScreen.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a route to the specified place and sets it as the destination.
|
||||
*/
|
||||
override fun navigateToPlace(place: Place) {
|
||||
val preview = place.route
|
||||
navigationViewModel.previewRoute.value = ""
|
||||
val location = location(place.longitude, place.latitude)
|
||||
navigationViewModel.saveRecent(carContext, place)
|
||||
routeModel.navState = routeModel.navState.copy(destination = place)
|
||||
if (preview.isEmpty()) {
|
||||
navigationViewModel.loadRoute(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
location,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
} else {
|
||||
routeModel.navState = routeModel.navState.copy(currentRouteIndex = place.routeIndex)
|
||||
onRouteReceived(preview)
|
||||
}
|
||||
surfaceRenderer.activateNavigationView()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if traffic data needs to be updated based on the time since the last update.
|
||||
*/
|
||||
fun checkTraffic(current: LocalDateTime, location: Location) {
|
||||
val duration = Duration.between(current, lastTrafficDate)
|
||||
if (showTraffic && duration.abs().seconds > TRAFFIC_UPDATE) {
|
||||
lastTrafficDate = current
|
||||
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically requests speed camera information near the current location.
|
||||
*/
|
||||
private fun updateSpeedCamera(location: Location) {
|
||||
if (lastCameraSearch++ % 100 == 0) {
|
||||
navigationViewModel.getSpeedCameras(location, 5.0)
|
||||
}
|
||||
if (speedCameras.isNotEmpty()) {
|
||||
updateDistance(location)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates distances to nearby speed cameras and checks for proximity alerts.
|
||||
*/
|
||||
private fun updateDistance(
|
||||
location: Location,
|
||||
) {
|
||||
val updatedCameras = mutableListOf<Elements>()
|
||||
speedCameras.forEach {
|
||||
val plLocation =
|
||||
location(longitude = it.lon, latitude = it.lat)
|
||||
val distance = plLocation.distanceTo(location)
|
||||
it.distance = distance.toDouble()
|
||||
updatedCameras.add(it)
|
||||
}
|
||||
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
|
||||
val camera = sortedList.firstOrNull() ?: return
|
||||
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
||||
try {
|
||||
camera.tags.direction!!.toFloat()
|
||||
} catch (e: Exception) {
|
||||
0F
|
||||
}
|
||||
} else {
|
||||
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
|
||||
}
|
||||
if (camera.distance < 80) {
|
||||
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
|
||||
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a new route is needed based on the time since the last update.
|
||||
*/
|
||||
private fun checkRoute(currentDate: LocalDateTime, location: Location) {
|
||||
val duration = Duration.between(currentDate, lastRouteDate)
|
||||
val routeUpdate = routeModel.curRoute.summary.duration / 4
|
||||
if (duration.abs().seconds > routeUpdate) {
|
||||
lastRouteDate = currentDate
|
||||
val destination = location(
|
||||
routeModel.navState.destination.longitude,
|
||||
routeModel.navState.destination.latitude
|
||||
)
|
||||
navigationViewModel.loadRoute(
|
||||
carContext,
|
||||
location,
|
||||
destination,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// URI host for deep linking
|
||||
var uriHost: String = "navigation"
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.kouros.navigation.car
|
||||
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import android.os.Message
|
||||
import android.util.Log
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NotificationManager(
|
||||
private val carContext: CarContext,
|
||||
private val lifecycleOwner: LifecycleOwner,
|
||||
) {
|
||||
|
||||
private var notificationServiceStarted = false
|
||||
|
||||
private var serviceStarted = false
|
||||
|
||||
init {
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
|
||||
}
|
||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.DESTROYED) {
|
||||
if (notificationServiceStarted) {
|
||||
stopNotificationService()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startNotificationService() {
|
||||
val intent = Intent(carContext, NavigationNotificationService::class.java)
|
||||
carContext.startForegroundService(intent)
|
||||
notificationServiceStarted = true
|
||||
}
|
||||
|
||||
fun stopNotificationService() {
|
||||
carContext
|
||||
.stopService(
|
||||
Intent(
|
||||
carContext,
|
||||
NavigationNotificationService::class.java
|
||||
)
|
||||
)
|
||||
notificationServiceStarted = false
|
||||
}
|
||||
|
||||
fun sendMessage(message: String) {
|
||||
val intent = Intent(carContext, NavigationNotificationService::class.java).apply {
|
||||
putExtra("EXTRA_MESSAGE", message)
|
||||
}
|
||||
carContext.startForegroundService(intent)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ import java.time.LocalDateTime
|
||||
class SurfaceRenderer(
|
||||
private var carContext: CarContext,
|
||||
private var lifecycle: Lifecycle,
|
||||
private var routeModel: RouteCarModel,
|
||||
//private var routeModel: RouteCarModel,
|
||||
private var viewModelStoreOwner: ViewModelStoreOwner
|
||||
) : DefaultLifecycleObserver {
|
||||
|
||||
@@ -362,13 +362,9 @@ class SurfaceRenderer(
|
||||
* Calculates appropriate bearing, zoom, and maintains view style.
|
||||
* Uses car orientation sensor if available, otherwise falls back to location bearing.
|
||||
*/
|
||||
fun updateLocation(location: Location) {
|
||||
fun updateLocation(location: Location, streetName : String) {
|
||||
synchronized(this) {
|
||||
if (routeModel.isNavigating()) {
|
||||
street.value = routeModel.currentStep().street
|
||||
} else {
|
||||
street.value = ""
|
||||
}
|
||||
street.value = streetName
|
||||
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||
val bearing = if (carOrientation == 999F) {
|
||||
if (location.hasBearing()) {
|
||||
@@ -402,8 +398,8 @@ class SurfaceRenderer(
|
||||
/**
|
||||
* Sets route data for active navigation and switches to VIEW mode.
|
||||
*/
|
||||
fun setRouteData() {
|
||||
routeData.value = routeModel.curRoute.routeGeoJson
|
||||
fun setRouteData(routeGeoJson: String) {
|
||||
routeData.value = routeGeoJson
|
||||
viewStyle = ViewStyle.VIEW
|
||||
}
|
||||
|
||||
@@ -413,7 +409,7 @@ class SurfaceRenderer(
|
||||
fun activateNavigationView() {
|
||||
viewStyle = ViewStyle.VIEW
|
||||
tilt = TILT
|
||||
updateLocation(lastLocation)
|
||||
updateLocation(lastLocation, "")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,11 +477,11 @@ class SurfaceRenderer(
|
||||
* Updates car location from the connected car system.
|
||||
* Only updates location when using OSRM routing engine.
|
||||
*/
|
||||
fun updateCarLocation(location: Location) {
|
||||
fun updateCarLocation(location: Location, streetName: String) {
|
||||
val repository = getSettingsRepository(carContext)
|
||||
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
||||
if (routingEngine == RouteEngine.OSRM.ordinal) {
|
||||
updateLocation(location)
|
||||
updateLocation(location, streetName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,8 +115,9 @@ class Simulation {
|
||||
updateLocation(fakeLocation)
|
||||
// Wait before moving to the next point (e.g., every 1 second)
|
||||
if (duration > 100) {
|
||||
delay(duration / 4)
|
||||
// delay(duration / 4)
|
||||
}
|
||||
delay(1000)
|
||||
lastTime = p.time
|
||||
lastLocation = fakeLocation
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kouros.navigation.car.screen
|
||||
|
||||
import androidx.car.app.navigation.model.Trip
|
||||
|
||||
import com.kouros.navigation.data.Place
|
||||
|
||||
|
||||
/** A listener for navigation start and stop signals. */
|
||||
@@ -14,4 +14,6 @@ interface NavigationListener {
|
||||
|
||||
/** Updates trip information. */
|
||||
fun updateTrip(trip: Trip)
|
||||
|
||||
fun navigateToPlace(place: Place)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.kouros.navigation.car.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.os.CountDownTimer
|
||||
import android.os.Handler
|
||||
import androidx.car.app.CarContext
|
||||
@@ -9,6 +13,7 @@ import androidx.car.app.Screen
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
|
||||
import androidx.car.app.model.ActionStrip
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.Distance
|
||||
import androidx.car.app.model.Header
|
||||
@@ -21,7 +26,8 @@ import androidx.car.app.navigation.model.MapWithContentTemplate
|
||||
import androidx.car.app.navigation.model.MessageInfo
|
||||
import androidx.car.app.navigation.model.NavigationTemplate
|
||||
import androidx.car.app.navigation.model.RoutingInfo
|
||||
import androidx.car.app.navigation.model.Trip
|
||||
import androidx.car.app.navigation.model.Step
|
||||
import androidx.car.app.navigation.model.TravelEstimate
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
@@ -29,30 +35,18 @@ import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.NavigationNotificationService
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
||||
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
||||
import com.kouros.navigation.car.screen.settings.SettingsScreen
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.data.overpass.Elements
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.GeoUtils
|
||||
import com.kouros.navigation.utils.formattedDistance
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import com.kouros.navigation.utils.getSettingsViewModel
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
/**
|
||||
* Main screen for car navigation.
|
||||
@@ -61,41 +55,37 @@ import kotlin.math.absoluteValue
|
||||
open class NavigationScreen(
|
||||
carContext: CarContext,
|
||||
private var surfaceRenderer: SurfaceRenderer,
|
||||
private var routeModel: RouteCarModel,
|
||||
private var listener: NavigationListener,
|
||||
private val navigationViewModel: NavigationViewModel
|
||||
) : Screen(carContext), NavigationObserverCallback {
|
||||
|
||||
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
) : Screen(carContext) {
|
||||
|
||||
var recentPlaces = mutableListOf<Place>()
|
||||
|
||||
var recentPlace: Place = Place()
|
||||
var navigationType = NavigationType.VIEW
|
||||
|
||||
var lastTrafficDate: LocalDateTime = LocalDateTime.MIN
|
||||
|
||||
var lastRouteDate: LocalDateTime = LocalDateTime.now()
|
||||
var lastCameraSearch = 0
|
||||
var speedCameras = listOf<Elements>()
|
||||
val observerManager = NavigationObserverManager(navigationViewModel, this)
|
||||
|
||||
val repository = getSettingsRepository(carContext)
|
||||
|
||||
val settingsViewModel = getSettingsViewModel(carContext)
|
||||
|
||||
private var distanceMode = 0
|
||||
|
||||
private var tripSuggestion = false
|
||||
|
||||
private var tripSuggestionCalled = false
|
||||
|
||||
private var routingEngine = 0
|
||||
|
||||
private var showTraffic = false;
|
||||
private var arrivalTimer: CountDownTimer? = null
|
||||
private var reRouteTimer: CountDownTimer? = null
|
||||
|
||||
private var isNavigating = false
|
||||
private var isRerouting = false
|
||||
private var hasArrived = false
|
||||
private lateinit var destinations: MutableList<Destination>
|
||||
private lateinit var stepRemainingDistance: Distance
|
||||
private lateinit var destinationTravelEstimate: TravelEstimate
|
||||
private lateinit var stepTravelEstimate: TravelEstimate
|
||||
private var shouldShowNextStep = false
|
||||
private var shouldShowLanes = false
|
||||
private lateinit var steps: MutableList<Step>
|
||||
private var junctionImage: CarIcon? = null
|
||||
private var backGroundColor = CarColor.BLUE
|
||||
val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
|
||||
recentPlaces.addAll(newPlaces)
|
||||
if (newPlaces.isNotEmpty() && !tripSuggestionCalled) {
|
||||
@@ -106,25 +96,14 @@ open class NavigationScreen(
|
||||
}
|
||||
|
||||
init {
|
||||
observerManager.attachAllObservers(this)
|
||||
lifecycleScope.launch {
|
||||
settingsViewModel.tripSuggestion.first()
|
||||
settingsViewModel.routingEngine.first()
|
||||
}
|
||||
repository.distanceModeFlow.asLiveData().observe(this, Observer {
|
||||
distanceMode = it
|
||||
})
|
||||
|
||||
repository.trafficFlow.asLiveData().observe(this, Observer {
|
||||
showTraffic = it
|
||||
})
|
||||
repository.tripSuggestionFlow.asLiveData().observe(this, Observer {
|
||||
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
|
||||
tripSuggestion = it
|
||||
})
|
||||
repository.routingEngineFlow.asLiveData().observe(this, Observer {
|
||||
routingEngine = it
|
||||
})
|
||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
arrivalTimer?.cancel()
|
||||
@@ -164,12 +143,11 @@ open class NavigationScreen(
|
||||
0,
|
||||
{ stopNavigation() })
|
||||
)
|
||||
updateTrip()
|
||||
return NavigationTemplate.Builder()
|
||||
.setNavigationInfo(
|
||||
getRoutingInfo()
|
||||
)
|
||||
.setDestinationTravelEstimate(routeModel.travelEstimateTrip(carContext, distanceMode))
|
||||
.setDestinationTravelEstimate(destinationTravelEstimate)
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setMapActionStrip(
|
||||
mapActionStrip(
|
||||
@@ -185,7 +163,7 @@ open class NavigationScreen(
|
||||
)
|
||||
})
|
||||
)
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
.setBackgroundColor(backGroundColor)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -194,7 +172,7 @@ open class NavigationScreen(
|
||||
*/
|
||||
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||
return NavigationTemplate.Builder()
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
.setBackgroundColor(backGroundColor)
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setMapActionStrip(
|
||||
mapActionStrip(
|
||||
@@ -221,7 +199,7 @@ open class NavigationScreen(
|
||||
arrivalTimer = object : CountDownTimer(8000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {}
|
||||
override fun onFinish() {
|
||||
routeModel.navState = routeModel.navState.copy(arrived = false)
|
||||
// routeModel.navState = routeModel.navState.copy(arrived = false)
|
||||
navigationType = NavigationType.VIEW
|
||||
invalidate()
|
||||
}
|
||||
@@ -235,8 +213,8 @@ open class NavigationScreen(
|
||||
*/
|
||||
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||
var street = ""
|
||||
if (routeModel.navState.destination.street != null) {
|
||||
street = routeModel.navState.destination.street!!
|
||||
if (destinations.first().address != null) {
|
||||
street = destinations.first().address.toString()
|
||||
}
|
||||
return NavigationTemplate.Builder()
|
||||
.setNavigationInfo(
|
||||
@@ -255,7 +233,7 @@ open class NavigationScreen(
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
// .setBackgroundColor(routeModel.backGroundColor())
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setMapActionStrip(
|
||||
mapActionStrip(
|
||||
@@ -294,12 +272,12 @@ open class NavigationScreen(
|
||||
val listBuilder = ItemList.Builder()
|
||||
recentPlaces.filter { it.category == Constants.RECENT && it.distance > 300F }.forEach {
|
||||
val row = Row.Builder()
|
||||
.setTitle(it.name!!)
|
||||
.setTitle(it.name)
|
||||
.addAction(
|
||||
createNavigateAction(it)
|
||||
)
|
||||
.setOnClickListener {
|
||||
navigateToPlace(it)
|
||||
listener.navigateToPlace(it)
|
||||
}
|
||||
listBuilder.addItem(
|
||||
row.build()
|
||||
@@ -349,7 +327,7 @@ open class NavigationScreen(
|
||||
return NavigationTemplate.Builder()
|
||||
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
// .setBackgroundColor(routeModel.backGroundColor())
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -357,16 +335,13 @@ open class NavigationScreen(
|
||||
* Builds and returns RoutingInfo based on the current step and distance.
|
||||
*/
|
||||
fun getRoutingInfo(): RoutingInfo {
|
||||
val distance =
|
||||
formattedDistance(distanceMode, routeModel.routeCalculator.leftStepDistance())
|
||||
val routingInfo = RoutingInfo.Builder()
|
||||
.setCurrentStep(
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
Distance.create(distance.first, distance.second)
|
||||
steps.first(),
|
||||
stepRemainingDistance
|
||||
)
|
||||
if (routeModel.navState.nextStep) {
|
||||
val nextStep = routeModel.nextStep(carContext = carContext)
|
||||
routingInfo.setNextStep(nextStep)
|
||||
if (shouldShowNextStep && steps.size > 1) {
|
||||
routingInfo.setNextStep(steps[1])
|
||||
}
|
||||
return routingInfo.build()
|
||||
}
|
||||
@@ -391,7 +366,7 @@ open class NavigationScreen(
|
||||
)
|
||||
) { obj: Any? ->
|
||||
if (obj != null) {
|
||||
navigateToPlace(place)
|
||||
listener.navigateToPlace(place)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,48 +446,22 @@ open class NavigationScreen(
|
||||
if (place.longitude == 0.0) {
|
||||
navigationViewModel.findAddress(
|
||||
"${obj.city} ${obj.street}},",
|
||||
currentNavigationLocation
|
||||
surfaceRenderer.lastLocation
|
||||
)
|
||||
// result see observer
|
||||
} else {
|
||||
navigateToPlace(place)
|
||||
listener.navigateToPlace(place)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a route to the specified place and sets it as the destination.
|
||||
*/
|
||||
fun navigateToPlace(place: Place) {
|
||||
val preview = navigationViewModel.previewRoute.value
|
||||
navigationViewModel.previewRoute.value = ""
|
||||
val location = location(place.longitude, place.latitude)
|
||||
navigationViewModel.saveRecent(carContext, place)
|
||||
currentNavigationLocation = location
|
||||
if (preview.isNullOrEmpty()) {
|
||||
navigationViewModel.loadRoute(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
location,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
} else {
|
||||
routeModel.navState = routeModel.navState.copy(currentRouteIndex = place.routeIndex)
|
||||
navigationViewModel.route.value = preview
|
||||
}
|
||||
routeModel.navState = routeModel.navState.copy(destination = place)
|
||||
surfaceRenderer.activateNavigationView()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops navigation, resets state, and notifies listeners.
|
||||
*/
|
||||
fun stopNavigation() {
|
||||
navigationType = NavigationType.VIEW
|
||||
listener.stopNavigation()
|
||||
lastCameraSearch = 0
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@@ -520,7 +469,6 @@ open class NavigationScreen(
|
||||
* Initiates recalculation for a new route to the destination.
|
||||
*/
|
||||
fun calculateNewRoute(destination: Place) {
|
||||
stopNavigation()
|
||||
navigationType = NavigationType.REROUTE
|
||||
invalidate()
|
||||
val mainThreadHandler = Handler(carContext.mainLooper)
|
||||
@@ -553,224 +501,36 @@ open class NavigationScreen(
|
||||
/**
|
||||
* Updates navigation state with the current location, checks for arrival, and traffic updates.
|
||||
*/
|
||||
fun updateTrip(location: Location) {
|
||||
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
||||
checkRoute(currentDate, location)
|
||||
checkTraffic(currentDate, location)
|
||||
|
||||
updateSpeedCamera(location)
|
||||
|
||||
routeModel.updateLocation(location, navigationViewModel)
|
||||
checkArrival()
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a new route is needed based on the time since the last update.
|
||||
*/
|
||||
private fun checkRoute(currentDate: LocalDateTime, location: Location) {
|
||||
val duration = Duration.between(currentDate, lastRouteDate)
|
||||
val routeUpdate = routeModel.curRoute.summary.duration / 4
|
||||
if (duration.abs().seconds > routeUpdate) {
|
||||
lastRouteDate = currentDate
|
||||
val destination = location(
|
||||
routeModel.navState.destination.longitude,
|
||||
routeModel.navState.destination.latitude
|
||||
)
|
||||
navigationViewModel.loadRoute(
|
||||
carContext,
|
||||
location,
|
||||
destination,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if traffic data needs to be updated based on the time since the last update.
|
||||
*/
|
||||
fun checkTraffic(current: LocalDateTime, location: Location) {
|
||||
val duration = Duration.between(current, lastTrafficDate)
|
||||
if (showTraffic && duration.abs().seconds > TRAFFIC_UPDATE) {
|
||||
lastTrafficDate = current
|
||||
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for arrival
|
||||
*/
|
||||
fun checkArrival() {
|
||||
if (routeModel.isArrival()
|
||||
&& routeModel.routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||
) {
|
||||
listener.stopNavigation()
|
||||
settingsViewModel.onLastRouteChanged("")
|
||||
routeModel.navState = routeModel.navState.copy(arrived = true)
|
||||
surfaceRenderer.routeData.value = ""
|
||||
navigationType = NavigationType.ARRIVAL
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the trip information and notifies the listener with a new Trip object.
|
||||
* This includes destination name, address, travel estimate, and loading status.
|
||||
*/
|
||||
private fun updateTrip() {
|
||||
if (routeModel.isNavigating() && !routeModel.navState.destination.name.isNullOrEmpty()) {
|
||||
val tripBuilder = Trip.Builder()
|
||||
val destination = Destination.Builder()
|
||||
.setName(routeModel.navState.destination.name ?: "")
|
||||
.setAddress(routeModel.navState.destination.street ?: "")
|
||||
.build()
|
||||
tripBuilder.addDestination(
|
||||
destination,
|
||||
routeModel.travelEstimateTrip(carContext, distanceMode)
|
||||
)
|
||||
tripBuilder.setLoading(false)
|
||||
tripBuilder.setCurrentRoad(routeModel.currentStep.street)
|
||||
tripBuilder.addStep(routeModel.currentStep(carContext), routeModel.travelEstimateStep(carContext, distanceMode ))
|
||||
listener.updateTrip(tripBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically requests speed camera information near the current location.
|
||||
*/
|
||||
private fun updateSpeedCamera(location: Location) {
|
||||
if (lastCameraSearch++ % 100 == 0) {
|
||||
navigationViewModel.getSpeedCameras(location, 5.0)
|
||||
}
|
||||
if (speedCameras.isNotEmpty()) {
|
||||
updateDistance(location)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates distances to nearby speed cameras and checks for proximity alerts.
|
||||
*/
|
||||
private fun updateDistance(
|
||||
location: Location,
|
||||
fun updateTrip(
|
||||
isNavigating: Boolean,
|
||||
isRerouting: Boolean,
|
||||
hasArrived: Boolean,
|
||||
destinations: MutableList<Destination>,
|
||||
steps: MutableList<Step>,
|
||||
destinationTravelEstimate: TravelEstimate,
|
||||
stepTravelEstimate: TravelEstimate,
|
||||
stepRemainingDistance: Distance,
|
||||
shouldShowNextStep: Boolean,
|
||||
shouldShowLanes: Boolean,
|
||||
junctionImage: CarIcon?,
|
||||
backGroundColor: CarColor
|
||||
) {
|
||||
val updatedCameras = mutableListOf<Elements>()
|
||||
speedCameras.forEach {
|
||||
val plLocation =
|
||||
location(longitude = it.lon, latitude = it.lat)
|
||||
val distance = plLocation.distanceTo(location)
|
||||
it.distance = distance.toDouble()
|
||||
updatedCameras.add(it)
|
||||
}
|
||||
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
|
||||
val camera = sortedList.firstOrNull() ?: return
|
||||
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
||||
try {
|
||||
camera.tags.direction!!.toFloat()
|
||||
} catch (e: Exception) {
|
||||
0F
|
||||
}
|
||||
} else {
|
||||
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
|
||||
}
|
||||
if (camera.distance < 80) {
|
||||
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
|
||||
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.isNavigating = isNavigating
|
||||
this.isRerouting = isRerouting
|
||||
this.hasArrived = hasArrived
|
||||
this.destinations = destinations
|
||||
this.steps = steps
|
||||
this.stepRemainingDistance = stepRemainingDistance
|
||||
this.destinationTravelEstimate = destinationTravelEstimate
|
||||
this.stepTravelEstimate = stepTravelEstimate
|
||||
this.shouldShowNextStep = shouldShowNextStep
|
||||
this.shouldShowLanes = shouldShowLanes
|
||||
this.junctionImage = junctionImage
|
||||
this.backGroundColor = backGroundColor
|
||||
|
||||
/**
|
||||
* Handles the received route string.
|
||||
* Starts navigation and invalidates the screen.
|
||||
*/
|
||||
override fun onRouteReceived(route: String) {
|
||||
if (route.isNotEmpty()) {
|
||||
if (routeModel.isNavigating()) {
|
||||
updateRoute(route)
|
||||
} else {
|
||||
prepareRoute(route)
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare route and start navigation
|
||||
*/
|
||||
private fun prepareRoute(route: String) {
|
||||
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||
navigationType = NavigationType.NAVIGATION
|
||||
routeModel.startNavigation(route)
|
||||
if (routeModel.hasLegs()) {
|
||||
settingsViewModel.onLastRouteChanged(route)
|
||||
}
|
||||
surfaceRenderer.setRouteData()
|
||||
listener.startNavigation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update route and traffic data
|
||||
*/
|
||||
private fun updateRoute(route: String) {
|
||||
val newRouteModel = RouteModel()
|
||||
newRouteModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||
navigationType = NavigationType.NAVIGATION
|
||||
newRouteModel.startNavigation(route)
|
||||
routeModel.curRoute.summary.trafficDelay = newRouteModel.curRoute.summary.trafficDelay
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if navigation is currently active.
|
||||
*/
|
||||
override fun isNavigating(): Boolean = routeModel.isNavigating()
|
||||
|
||||
/**
|
||||
* Handles received traffic data and updates the surface renderer.
|
||||
*/
|
||||
override fun onTrafficReceived(traffic: Map<String, String>) {
|
||||
if (traffic.isNotEmpty()) {
|
||||
surfaceRenderer.setTrafficData(traffic)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the received place search result.
|
||||
* Navigates to the specified place.
|
||||
*/
|
||||
override fun onPlaceSearchResultReceived(place: Place) {
|
||||
navigateToPlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles received speed camera data.
|
||||
* Updates the surface renderer with the camera locations.
|
||||
*/
|
||||
override fun onSpeedCamerasReceived(cameras: List<Elements>) {
|
||||
speedCameras = cameras
|
||||
val coordinates = mutableListOf<List<Double>>()
|
||||
cameras.forEach {
|
||||
coordinates.add(listOf(it.lon, it.lat))
|
||||
}
|
||||
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
||||
surfaceRenderer.speedCamerasData.value = speedData
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles received maximum speed data and updates the surface renderer.
|
||||
*/
|
||||
override fun onMaxSpeedReceived(speed: Int) {
|
||||
surfaceRenderer.maxSpeed.value = speed
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the screen.
|
||||
*/
|
||||
override fun invalidateScreen() {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -347,6 +347,7 @@ class RoutePreviewScreen(
|
||||
|
||||
private fun onNavigate(index: Int) {
|
||||
destination.routeIndex = index
|
||||
destination.route = navigationViewModel.previewRoute.value.toString()
|
||||
setResult(destination)
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kouros.navigation.car.screen.observers
|
||||
|
||||
import com.kouros.navigation.car.NavigationSession
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
|
||||
/**
|
||||
@@ -17,16 +18,12 @@ class NavigationObserverManager(
|
||||
val speedCameraObserver = SpeedCameraObserver(callback)
|
||||
val maxSpeedObserver = MaxSpeedObserver(callback)
|
||||
|
||||
/**
|
||||
* Attaches all observers to the ViewModel.
|
||||
* Call this from NavigationScreen's init block or lifecycle method.
|
||||
*/
|
||||
fun attachAllObservers(screen: androidx.car.app.Screen) {
|
||||
viewModel.route.observe(screen, routeObserver)
|
||||
viewModel.traffic.observe(screen, trafficObserver)
|
||||
viewModel.placeLocation.observe(screen, placeSearchObserver)
|
||||
viewModel.speedCameras.observe(screen, speedCameraObserver)
|
||||
viewModel.maxSpeed.observe(screen, maxSpeedObserver)
|
||||
fun attachAllObservers(session: NavigationSession) {
|
||||
viewModel.route.observe(session, routeObserver)
|
||||
viewModel.traffic.observe(session, trafficObserver)
|
||||
viewModel.placeLocation.observe(session, placeSearchObserver)
|
||||
viewModel.speedCameras.observe(session, speedCameraObserver)
|
||||
viewModel.maxSpeed.observe(session, maxSpeedObserver)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,17 +34,18 @@ data class Places(
|
||||
@Serializable
|
||||
data class Place(
|
||||
var id: Long = 0,
|
||||
var name: String? = null,
|
||||
var category: String? = null,
|
||||
var name: String = "",
|
||||
var category: String = "",
|
||||
var latitude: Double = 0.0,
|
||||
var longitude: Double = 0.0,
|
||||
var postalCode: String? = null,
|
||||
var city: String? = null,
|
||||
var street: String? = null,
|
||||
var postalCode: String = "",
|
||||
var city: String = "",
|
||||
var street: String = "",
|
||||
var distance: Float = 0F,
|
||||
//var avatar: Uri? = null,
|
||||
var lastDate: Long = 0,
|
||||
var routeIndex: Int = 0
|
||||
var routeIndex: Int = 0,
|
||||
var route: String = "",
|
||||
)
|
||||
|
||||
data class ContactData(
|
||||
|
||||
@@ -3,7 +3,9 @@ package com.kouros.navigation.utils
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.util.Log
|
||||
import androidx.car.app.model.Distance
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.Constants.TILT
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
@@ -141,7 +143,7 @@ fun duration(
|
||||
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
||||
2.seconds
|
||||
} else {
|
||||
1.seconds
|
||||
1.2.seconds
|
||||
//val updateDuration = java.time.Duration.between(LocalDateTime.now(), lastLocationUpdate)
|
||||
//((updateDuration!!.toMillis().absoluteValue * 1.2).toDuration(DurationUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user