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