Preview
This commit is contained in:
@@ -33,7 +33,6 @@ import com.kouros.navigation.data.Constants.TILT
|
||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.model.BaseStyleModel
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.bearing
|
||||
import com.kouros.navigation.utils.calculateTilt
|
||||
import com.kouros.navigation.utils.calculateZoom
|
||||
@@ -46,6 +45,7 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.expressions.dsl.zoom
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
|
||||
@@ -261,6 +261,7 @@ class SurfaceRenderer(
|
||||
val paddingValues = getPaddingValues(height, viewStyle)
|
||||
val cameraState = cameraState(paddingValues, position, tilt)
|
||||
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||
val dark = darkMode == 1 || darkMode == 2 && carContext.isDarkMode
|
||||
|
||||
MapLibre(
|
||||
cameraState,
|
||||
@@ -271,7 +272,7 @@ class SurfaceRenderer(
|
||||
speedCameras,
|
||||
showBuildings
|
||||
)
|
||||
ShowPosition(cameraState, position, paddingValues, darkMode)
|
||||
ShowPosition(cameraState, position, paddingValues, dark)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,18 +284,18 @@ class SurfaceRenderer(
|
||||
cameraState: CameraState,
|
||||
position: CameraPosition?,
|
||||
paddingValues: PaddingValues,
|
||||
darkMode: Int
|
||||
darkMode: Boolean
|
||||
) {
|
||||
val cameraDuration =
|
||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
||||
val currentSpeed: Float? by speed.observeAsState()
|
||||
val speed: Int? by maxSpeed.observeAsState()
|
||||
val maximumSpeed: Int? by maxSpeed.observeAsState()
|
||||
val streetName: String? by street.observeAsState()
|
||||
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||
DrawNavigationImages(
|
||||
paddingValues,
|
||||
currentSpeed,
|
||||
speed!!,
|
||||
maximumSpeed!!,
|
||||
width,
|
||||
height,
|
||||
streetName,
|
||||
@@ -340,7 +341,7 @@ class SurfaceRenderer(
|
||||
updateCameraPosition(
|
||||
cameraPosition.value!!.bearing,
|
||||
newZoom,
|
||||
cameraPosition.value!!.target,
|
||||
cameraPosition.value!!.target, tilt
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -379,7 +380,7 @@ class SurfaceRenderer(
|
||||
updateCameraPosition(
|
||||
bearing,
|
||||
zoom,
|
||||
Position(location.longitude, location.latitude)
|
||||
Position(location.longitude, location.latitude), tilt
|
||||
)
|
||||
lastBearing = cameraPosition.value!!.bearing
|
||||
lastLocation = location
|
||||
@@ -387,11 +388,19 @@ class SurfaceRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets route data for active navigation and switches to VIEW mode.
|
||||
*/
|
||||
fun setRouteData() {
|
||||
routeData.value = routeModel.curRoute.routeGeoJson
|
||||
viewStyle = ViewStyle.VIEW
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates camera position with new bearing, zoom, and target.
|
||||
* Posts update to LiveData for UI observation.
|
||||
*/
|
||||
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
||||
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position, tilt: Double) {
|
||||
synchronized(this) {
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
@@ -408,9 +417,15 @@ class SurfaceRenderer(
|
||||
/**
|
||||
* Sets route data for active navigation and switches to VIEW mode.
|
||||
*/
|
||||
fun setRouteData() {
|
||||
routeData.value = routeModel.curRoute.routeGeoJson
|
||||
fun clearRouteData() {
|
||||
routeData.value = ""
|
||||
viewStyle = ViewStyle.VIEW
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
zoom = 16.0
|
||||
)
|
||||
)
|
||||
tilt = TILT
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -424,18 +439,21 @@ class SurfaceRenderer(
|
||||
* Sets up route preview mode with overview camera position.
|
||||
* Calculates appropriate zoom based on route distance.
|
||||
*/
|
||||
fun setPreviewRouteData(routeModel: RouteModel) {
|
||||
fun setPreviewRouteData(routeModel: RouteCarModel) {
|
||||
viewStyle = ViewStyle.PREVIEW
|
||||
with(routeModel) {
|
||||
routeData.value = curRoute.routeGeoJson
|
||||
centerLocation = curRoute.centerLocation
|
||||
previewDistance = curRoute.summary.distance
|
||||
}
|
||||
tilt = 0.0
|
||||
updateCameraPosition(
|
||||
0.0,
|
||||
previewZoom(previewDistance),
|
||||
Position(centerLocation.longitude, centerLocation.latitude)
|
||||
previewZoom(centerLocation, previewDistance),
|
||||
Position(centerLocation.longitude, centerLocation.latitude), 0.0
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,7 +466,7 @@ class SurfaceRenderer(
|
||||
updateCameraPosition(
|
||||
0.0,
|
||||
14.0,
|
||||
target = Position(location.longitude, location.latitude)
|
||||
target = Position(location.longitude, location.latitude), tilt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -19,6 +20,7 @@ import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.drawText
|
||||
@@ -33,6 +35,8 @@ import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.NavigationColor
|
||||
import com.kouros.navigation.data.RouteColor
|
||||
import com.kouros.navigation.data.SpeedColor
|
||||
import com.kouros.navigation.utils.isMetricSystem
|
||||
import com.kouros.navigation.utils.location
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.camera.rememberCameraState
|
||||
@@ -59,6 +63,7 @@ import org.maplibre.compose.sources.Source
|
||||
import org.maplibre.compose.sources.getBaseSource
|
||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
import org.maplibre.spatialk.geojson.BoundingBox
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
|
||||
|
||||
@@ -296,7 +301,7 @@ fun DrawNavigationImages(
|
||||
width: Int,
|
||||
height: Int,
|
||||
streetName: String?,
|
||||
darkMode: Int,
|
||||
darkMode: Boolean,
|
||||
) {
|
||||
NavigationImage(padding, width, height, streetName, darkMode)
|
||||
if (speed != null) {
|
||||
@@ -314,7 +319,7 @@ fun NavigationImage(
|
||||
width: Int,
|
||||
height: Int,
|
||||
streetName: String?,
|
||||
darkMode: Int
|
||||
darkMode: Boolean
|
||||
) {
|
||||
|
||||
val imageSize = (height / 8)
|
||||
@@ -323,9 +328,9 @@ fun NavigationImage(
|
||||
val textMeasurerStreet = rememberTextMeasurer()
|
||||
val street = streetName.toString()
|
||||
val styleStreet = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (darkMode == 1) Color.White else navigationColor,
|
||||
color = if (darkMode) Color.White else navigationColor,
|
||||
)
|
||||
val textLayoutStreet = remember(street) {
|
||||
textMeasurerStreet.measure(street, styleStreet, overflow = TextOverflow.Ellipsis)
|
||||
@@ -348,28 +353,31 @@ fun NavigationImage(
|
||||
.size(imageSize.dp, imageSize.dp)
|
||||
.scale(scaleX = 1f, scaleY = 0.7f),
|
||||
)
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.size((width / 5).dp, (height/4).dp)
|
||||
.size(textLayoutStreet.size.width.dp, textLayoutStreet.size.height.dp * 6 )
|
||||
) {
|
||||
if (!streetName.isNullOrEmpty()) {
|
||||
if (street.isNotEmpty()) {
|
||||
val topLeftX = center.x - textLayoutStreet.size.width / 2
|
||||
val topLeftY = center.y + textLayoutStreet.size.height
|
||||
drawRoundRect(
|
||||
topLeft = Offset(
|
||||
x = center.x - textLayoutStreet.size.width / 2 ,
|
||||
y = center.y + textLayoutStreet.size.height,
|
||||
x = topLeftX ,
|
||||
y = topLeftY,
|
||||
),
|
||||
color = if (darkMode == 1) navigationColor else Color.White,
|
||||
color = if (darkMode) navigationColor else Color.White,
|
||||
cornerRadius = CornerRadius(x = 10f, y = 10f),
|
||||
)
|
||||
drawText(
|
||||
textMeasurer = textMeasurerStreet,
|
||||
text = streetName,
|
||||
text = street,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = styleStreet,
|
||||
topLeft = Offset(
|
||||
x = center.x - textLayoutStreet.size.width / 2,
|
||||
y = center.y + textLayoutStreet.size.height + 10,
|
||||
x = topLeftX,
|
||||
y = topLeftY + 10,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -384,6 +392,7 @@ private fun CurrentSpeed(
|
||||
curSpeed: Float,
|
||||
maxSpeed: Int
|
||||
) {
|
||||
|
||||
val radius = 34
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -395,9 +404,11 @@ private fun CurrentSpeed(
|
||||
) {
|
||||
val textMeasurerSpeed = rememberTextMeasurer()
|
||||
val textMeasurerKm = rememberTextMeasurer()
|
||||
val speed = (curSpeed * 3.6).toInt().toString()
|
||||
|
||||
val kmh = "km/h"
|
||||
val speed = if (isMetricSystem()) (curSpeed * 3.6).toInt().toString() else (curSpeed * 3.6 * 0.6214).toInt().toString()
|
||||
|
||||
val kmh = if (isMetricSystem()) "km/h" else "mph"
|
||||
|
||||
val styleSpeed = TextStyle(
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -433,7 +444,7 @@ private fun CurrentSpeed(
|
||||
)
|
||||
drawText(
|
||||
textMeasurer = textMeasurerKm,
|
||||
text = "km/h",
|
||||
text = kmh,
|
||||
style = styleKm,
|
||||
topLeft = Offset(
|
||||
x = center.x - textLayoutKm.size.width / 2,
|
||||
|
||||
@@ -17,30 +17,32 @@ class Simulation {
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
updateLocation: (Location) -> Unit
|
||||
) {
|
||||
val points = routeModel.curRoute.waypoints
|
||||
if (points.isEmpty()) return
|
||||
simulationJob?.cancel()
|
||||
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
||||
var curBearing = 0f
|
||||
simulationJob = lifecycleScope.launch {
|
||||
for (point in points) {
|
||||
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
||||
latitude = point[1]
|
||||
longitude = point[0]
|
||||
bearing = curBearing
|
||||
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||
speed = 13.0f // ~50 km/h
|
||||
time = System.currentTimeMillis()
|
||||
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||
if (routeModel.navState.route.isRouteValid()) {
|
||||
val points = routeModel.curRoute.waypoints
|
||||
if (points.isEmpty()) return
|
||||
simulationJob?.cancel()
|
||||
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
||||
var curBearing = 0f
|
||||
simulationJob = lifecycleScope.launch {
|
||||
for (point in points) {
|
||||
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
||||
latitude = point[1]
|
||||
longitude = point[0]
|
||||
bearing = curBearing
|
||||
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||
speed = 13.0f // ~50 km/h
|
||||
time = System.currentTimeMillis()
|
||||
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||
}
|
||||
curBearing = lastLocation.bearingTo(fakeLocation)
|
||||
// Update your app's state as if a real GPS update occurred
|
||||
updateLocation(fakeLocation)
|
||||
// Wait before moving to the next point (e.g., every 1 second)
|
||||
delay(1000)
|
||||
lastLocation = fakeLocation
|
||||
}
|
||||
curBearing = lastLocation.bearingTo(fakeLocation)
|
||||
// Update your app's state as if a real GPS update occurred
|
||||
updateLocation(fakeLocation)
|
||||
// Wait before moving to the next point (e.g., every 2 seconds)
|
||||
delay(1000)
|
||||
lastLocation = fakeLocation
|
||||
routeModel.stopNavigation()
|
||||
}
|
||||
routeModel.stopNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,8 +116,9 @@ class CategoryScreen(
|
||||
} else {
|
||||
row.addText("${(it.distance / 1000).round(1)} km")
|
||||
}
|
||||
if (category == Constants.CHARGING_STATION) {
|
||||
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
|
||||
if (category == CHARGING_STATION) {
|
||||
if (it.tags.socketType2 != null)
|
||||
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
|
||||
} else {
|
||||
row.addText(carText("${it.tags.openingHours}"))
|
||||
}
|
||||
@@ -134,7 +135,7 @@ class CategoryScreen(
|
||||
setResult(
|
||||
Place(
|
||||
name = name,
|
||||
category = Constants.CHARGING_STATION,
|
||||
category = CHARGING_STATION,
|
||||
latitude = it.lat,
|
||||
longitude = it.lon
|
||||
)
|
||||
|
||||
@@ -377,7 +377,6 @@ class NavigationScreen(
|
||||
*/
|
||||
private fun stopAction(): Action {
|
||||
return Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.stop_action_title))
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
@@ -531,6 +530,8 @@ class NavigationScreen(
|
||||
* Pushes the search screen and handles the search result.
|
||||
*/
|
||||
private fun startSearchScreen() {
|
||||
navigationViewModel.recentPlaces.value = emptyList()
|
||||
navigationViewModel.previewRoute.value = ""
|
||||
screenManager
|
||||
.pushForResult(
|
||||
SearchScreen(
|
||||
@@ -558,16 +559,22 @@ class NavigationScreen(
|
||||
* Loads a route to the specified place and sets it as the destination.
|
||||
*/
|
||||
fun navigateToPlace(place: Place) {
|
||||
val preview = navigationViewModel.previewRoute.value
|
||||
navigationType = NavigationType.VIEW
|
||||
val location = location(place.longitude, place.latitude)
|
||||
navigationViewModel.saveRecent(carContext, place)
|
||||
currentNavigationLocation = location
|
||||
navigationViewModel.loadRoute(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
location,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
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)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
@@ -27,58 +26,52 @@ import com.kouros.navigation.data.Constants.RECENT
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
|
||||
class PlaceListScreen(
|
||||
private val carContext: CarContext,
|
||||
private val surfaceRenderer: SurfaceRenderer,
|
||||
private val category: String,
|
||||
private val navigationViewModel: NavigationViewModel
|
||||
private val navigationViewModel: NavigationViewModel,
|
||||
private val places: List<Place>
|
||||
) : Screen(carContext) {
|
||||
|
||||
var places = listOf<Place>()
|
||||
|
||||
val observer = Observer<List<Place>> { newPlaces ->
|
||||
places = newPlaces
|
||||
invalidate()
|
||||
}
|
||||
val routeModel = RouteCarModel()
|
||||
|
||||
val observerAddress = Observer<List<Place>> { newContacts ->
|
||||
places = newContacts
|
||||
invalidate()
|
||||
var place = Place()
|
||||
|
||||
val previewObserver = Observer<String> { route ->
|
||||
if (route.isNotEmpty()) {
|
||||
val repository = getSettingsRepository(carContext)
|
||||
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
||||
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||
routeModel.startNavigation(route)
|
||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||
screenManager
|
||||
.pushForResult(
|
||||
RoutePreviewScreen(
|
||||
carContext,
|
||||
surfaceRenderer,
|
||||
place,
|
||||
navigationViewModel,
|
||||
routeModel = routeModel
|
||||
)
|
||||
) { obj: Any? ->
|
||||
if (obj != null) {
|
||||
setResult(obj)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (category == RECENT) {
|
||||
navigationViewModel.places.observe(this, observer)
|
||||
}
|
||||
if (category == CONTACTS) {
|
||||
navigationViewModel.contactAddress.observe(this, observerAddress)
|
||||
}
|
||||
if (category == FAVORITES) {
|
||||
navigationViewModel.favorites.observe(this, observer)
|
||||
}
|
||||
loadPlaces()
|
||||
}
|
||||
|
||||
fun loadPlaces() {
|
||||
if (category == RECENT) {
|
||||
navigationViewModel.loadRecentPlaces(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
}
|
||||
if (category == CONTACTS) {
|
||||
navigationViewModel.loadContacts(carContext)
|
||||
}
|
||||
if (category == FAVORITES) {
|
||||
navigationViewModel.loadFavorites(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
}
|
||||
navigationViewModel.previewRoute.observe(this, previewObserver)
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
@@ -91,10 +84,10 @@ class PlaceListScreen(
|
||||
""
|
||||
}
|
||||
val row = Row.Builder()
|
||||
// .setImage(contactIcon(it.avatar, it.category))
|
||||
// .setImage(contactIcon(it.avatar, it.category))
|
||||
.setTitle("$street ${it.city}")
|
||||
.setOnClickListener {
|
||||
val place = Place(
|
||||
place = Place(
|
||||
0,
|
||||
it.name,
|
||||
it.category,
|
||||
@@ -103,29 +96,22 @@ class PlaceListScreen(
|
||||
it.postalCode,
|
||||
it.city,
|
||||
it.street,
|
||||
// avatar = null
|
||||
// avatar = null
|
||||
)
|
||||
val location = location(place.longitude, place.latitude)
|
||||
navigationViewModel.loadPreviewRoute(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
location,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
screenManager
|
||||
.pushForResult(
|
||||
RoutePreviewScreen(
|
||||
carContext,
|
||||
surfaceRenderer,
|
||||
place,
|
||||
navigationViewModel
|
||||
)
|
||||
) { obj: Any? ->
|
||||
if (obj != null) {
|
||||
setResult(obj)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (category != CONTACTS) {
|
||||
row.addText(SpannableString(" ").apply {
|
||||
setSpan(
|
||||
DistanceSpan.create(
|
||||
Distance.create(
|
||||
(it.distance/1000).toDouble(),
|
||||
(it.distance / 1000).toDouble(),
|
||||
Distance.UNIT_KILOMETERS
|
||||
)
|
||||
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
|
||||
@@ -184,4 +170,10 @@ class PlaceListScreen(
|
||||
}
|
||||
return CarIcon.Builder(IconCompat.createWithContentUri(avatar)).build()
|
||||
}
|
||||
|
||||
fun loadPlaces() {
|
||||
if (category == CONTACTS) {
|
||||
navigationViewModel.loadContacts(carContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package com.kouros.navigation.car.screen
|
||||
|
||||
import android.os.CountDownTimer
|
||||
import android.text.SpannableString
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.Action.FLAG_DEFAULT
|
||||
import androidx.car.app.model.ActionStrip
|
||||
@@ -20,88 +22,79 @@ import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.car.app.navigation.model.MapController
|
||||
import androidx.car.app.navigation.model.MapWithContentTemplate
|
||||
import androidx.car.app.versioning.CarAppApiLevels
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.route.Routes
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.min
|
||||
|
||||
/** Creates a screen using the new [androidx.car.app.navigation.model.MapWithContentTemplate] */
|
||||
class RoutePreviewScreen(
|
||||
carContext: CarContext,
|
||||
private var surfaceRenderer: SurfaceRenderer,
|
||||
private var destination: Place,
|
||||
private val navigationViewModel: NavigationViewModel
|
||||
private val navigationViewModel: NavigationViewModel,
|
||||
private val routeModel: RouteCarModel
|
||||
) :
|
||||
Screen(carContext) {
|
||||
private var isFavorite = false
|
||||
|
||||
val routeModel = RouteCarModel()
|
||||
|
||||
val maxListItems: Int = 3
|
||||
val navigationUtils = NavigationUtils(carContext)
|
||||
val observer = Observer<String> { route ->
|
||||
if (route.isNotEmpty()) {
|
||||
val repository = getSettingsRepository(carContext)
|
||||
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
||||
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||
routeModel.startNavigation(route)
|
||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||
|
||||
private val backPressedCallback = object : OnBackPressedCallback(false) {
|
||||
override fun handleOnBackPressed() {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
navigationViewModel.previewRoute.observe(this, observer)
|
||||
val location = location(destination.longitude, destination.latitude)
|
||||
navigationViewModel.loadPreviewRoute(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
location,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
carContext.onBackPressedDispatcher.addCallback(this, backPressedCallback)
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
val navigateActionIcon: CarIcon = CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext, R.drawable.navigation_48px
|
||||
)
|
||||
).build()
|
||||
val navigateAction = Action.Builder()
|
||||
.setFlags(FLAG_DEFAULT)
|
||||
.setIcon(navigateActionIcon)
|
||||
.setOnClickListener { this.onNavigate() }
|
||||
.build()
|
||||
|
||||
|
||||
val itemListBuilder = ItemList.Builder()
|
||||
var i = 0
|
||||
routeModel.route.routes.forEach { _ ->
|
||||
itemListBuilder.addItem(createRow(i++, navigateAction))
|
||||
if (carContext.getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) {
|
||||
val listLimit = min(
|
||||
maxListItems,
|
||||
carContext.getCarService(ConstraintManager::class.java)
|
||||
.getContentLimit(
|
||||
ConstraintManager.CONTENT_LIMIT_TYPE_LIST
|
||||
)
|
||||
)
|
||||
var index = 0
|
||||
routeModel.route.routes.forEach { route ->
|
||||
itemListBuilder.addItem(createRow(route, index++))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val header = Header.Builder()
|
||||
.setStartHeaderAction(Action.BACK)
|
||||
.setTitle(carContext.getString(R.string.route_preview))
|
||||
.addEndHeaderAction(
|
||||
|
||||
if (routeModel.route.routes.size == 1) {
|
||||
header.addEndHeaderAction(
|
||||
favoriteAction()
|
||||
)
|
||||
.addEndHeaderAction(
|
||||
header.addEndHeaderAction(
|
||||
deleteFavoriteAction()
|
||||
)
|
||||
.build()
|
||||
|
||||
}
|
||||
val message =
|
||||
if (routeModel.isNavigating() && routeModel.curRoute.waypoints.isNotEmpty()) {
|
||||
createRouteText(0)
|
||||
createRouteText(routeModel.route.routes.first())
|
||||
} else {
|
||||
CarText.Builder("Wait")
|
||||
.build()
|
||||
@@ -110,7 +103,7 @@ class RoutePreviewScreen(
|
||||
val timer = object : CountDownTimer(5000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {}
|
||||
override fun onFinish() {
|
||||
onNavigate()
|
||||
onNavigate(0)
|
||||
}
|
||||
}
|
||||
timer.start()
|
||||
@@ -118,18 +111,27 @@ class RoutePreviewScreen(
|
||||
|
||||
val content = if (routeModel.route.routes.size > 1) {
|
||||
ListTemplate.Builder()
|
||||
.setHeader(header)
|
||||
.setHeader(header.build())
|
||||
.setSingleList(itemListBuilder.build())
|
||||
.build()
|
||||
} else {
|
||||
val navigateActionIcon: CarIcon = CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext, R.drawable.navigation_48px
|
||||
)
|
||||
).build()
|
||||
val navigateAction = Action.Builder()
|
||||
.setFlags(FLAG_DEFAULT)
|
||||
.setIcon(navigateActionIcon)
|
||||
.setOnClickListener { this.onNavigate(0) }
|
||||
.build()
|
||||
MessageTemplate.Builder(
|
||||
message
|
||||
)
|
||||
.setHeader(header)
|
||||
.setHeader(header.build())
|
||||
.addAction(navigateAction)
|
||||
.setLoading(message.toString() == "Wait")
|
||||
.build()
|
||||
|
||||
}
|
||||
return MapWithContentTemplate.Builder()
|
||||
.setContentTemplate(content)
|
||||
@@ -176,7 +178,7 @@ class RoutePreviewScreen(
|
||||
private fun deleteFavoriteAction(): Action = Action.Builder()
|
||||
.setOnClickListener {
|
||||
if (isFavorite) {
|
||||
navigationViewModel.deleteFavorite(carContext,destination)
|
||||
navigationViewModel.deleteFavorite(carContext, destination)
|
||||
}
|
||||
isFavorite = !isFavorite
|
||||
finish()
|
||||
@@ -192,10 +194,10 @@ class RoutePreviewScreen(
|
||||
)
|
||||
.build()
|
||||
|
||||
private fun createRouteText(index: Int): CarText {
|
||||
val time = routeModel.route.routes[index].summary.duration
|
||||
private fun createRouteText(route: Routes): CarText {
|
||||
val time = route.summary.duration
|
||||
val length =
|
||||
BigDecimal((routeModel.route.routes[index].summary.distance) / 1000).setScale(
|
||||
BigDecimal((route.summary.distance) / 1000).setScale(
|
||||
1,
|
||||
RoundingMode.HALF_EVEN
|
||||
)
|
||||
@@ -207,18 +209,43 @@ class RoutePreviewScreen(
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createRow(index: Int, action: Action): Row {
|
||||
val route = createRouteText(index)
|
||||
val titleText = "$index"
|
||||
return Row.Builder()
|
||||
.setTitle(route)
|
||||
.setOnClickListener { onRouteSelected(index) }
|
||||
.addText(titleText)
|
||||
.addAction(action)
|
||||
private fun createRow(route: Routes, index: Int): Row {
|
||||
val navigateActionIcon: CarIcon = CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext, R.drawable.navigation_48px
|
||||
)
|
||||
).build()
|
||||
val navigateAction = Action.Builder()
|
||||
.setFlags(FLAG_DEFAULT)
|
||||
.setIcon(navigateActionIcon)
|
||||
.setOnClickListener { this.onNavigate(index) }
|
||||
.build()
|
||||
|
||||
val routeText = createRouteText(route)
|
||||
var street = ""
|
||||
var maxDistance = 0.0
|
||||
routeModel.route.routes[index].legs.first().steps.forEach {
|
||||
if (it.distance > maxDistance) {
|
||||
maxDistance = it.distance
|
||||
street = it.street
|
||||
}
|
||||
}
|
||||
val delay = (route.summary.trafficDelay / 60).toInt().toString()
|
||||
|
||||
val row = Row.Builder()
|
||||
.setTitle(routeText)
|
||||
.setOnClickListener { onRouteSelected(index) }
|
||||
.addText(street)
|
||||
.addAction(navigateAction)
|
||||
|
||||
if (route.summary.trafficDelay > 60) {
|
||||
row.addText("$delay min")
|
||||
}
|
||||
return row.build()
|
||||
}
|
||||
|
||||
private fun onNavigate() {
|
||||
private fun onNavigate(index: Int) {
|
||||
destination.routeIndex = index
|
||||
setResult(destination)
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import com.kouros.data.R
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.data.Category
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.CATEGORIES
|
||||
import com.kouros.navigation.data.Constants.FAVORITES
|
||||
import com.kouros.navigation.data.Constants.RECENT
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.nominatim.SearchResult
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
@@ -30,11 +32,13 @@ class SearchScreen(
|
||||
|
||||
var isSearchComplete: Boolean = false
|
||||
|
||||
var category = ""
|
||||
|
||||
var categories: List<Category> = listOf(
|
||||
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
||||
// Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
||||
Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)),
|
||||
Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites))
|
||||
Category(id = RECENT, name = carContext.getString(R.string.recent_destinations)),
|
||||
// Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
||||
Category(id = CATEGORIES, name = carContext.getString(R.string.category_title)),
|
||||
Category(id = FAVORITES, name = carContext.getString(R.string.favorites))
|
||||
)
|
||||
|
||||
lateinit var searchResult: List<SearchResult>
|
||||
@@ -44,8 +48,50 @@ class SearchScreen(
|
||||
invalidate()
|
||||
}
|
||||
|
||||
val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
|
||||
if (newPlaces.isNotEmpty()) {
|
||||
screenManager
|
||||
.pushForResult(
|
||||
PlaceListScreen(
|
||||
carContext,
|
||||
surfaceRenderer,
|
||||
RECENT,
|
||||
navigationViewModel,
|
||||
newPlaces
|
||||
)
|
||||
) { obj: Any? ->
|
||||
surfaceRenderer.clearRouteData()
|
||||
if (obj != null) {
|
||||
setResult(obj)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val observerFavorites = Observer<List<Place>> { newPlaces ->
|
||||
screenManager
|
||||
.pushForResult(
|
||||
PlaceListScreen(
|
||||
carContext,
|
||||
surfaceRenderer,
|
||||
FAVORITES,
|
||||
navigationViewModel,
|
||||
newPlaces
|
||||
)
|
||||
) { obj: Any? ->
|
||||
if (obj != null) {
|
||||
setResult(obj)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
navigationViewModel.searchPlaces.observe(this, observer)
|
||||
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
|
||||
navigationViewModel.favorites.observe(this, observerFavorites)
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
@@ -62,7 +108,7 @@ class SearchScreen(
|
||||
.setTitle(it.name)
|
||||
.setImage(categoryIcon(it.id))
|
||||
.setOnClickListener {
|
||||
if (it.id == Constants.CATEGORIES) {
|
||||
if (it.id == CATEGORIES) {
|
||||
screenManager
|
||||
.pushForResult(
|
||||
CategoriesScreen(
|
||||
@@ -78,20 +124,20 @@ class SearchScreen(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
screenManager
|
||||
.pushForResult(
|
||||
PlaceListScreen(
|
||||
carContext,
|
||||
surfaceRenderer,
|
||||
it.id,
|
||||
navigationViewModel
|
||||
)
|
||||
) { obj: Any? ->
|
||||
if (obj != null) {
|
||||
setResult(obj)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
if (it.id == RECENT) {
|
||||
navigationViewModel.loadRecentPlaces(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
}
|
||||
if (it.id == FAVORITES) {
|
||||
navigationViewModel.loadFavorites(
|
||||
carContext,
|
||||
surfaceRenderer.lastLocation,
|
||||
surfaceRenderer.carOrientation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.setBrowsable(true)
|
||||
@@ -122,13 +168,15 @@ class SearchScreen(
|
||||
}
|
||||
|
||||
fun categoryIcon(category: String?): CarIcon {
|
||||
val resId : Int = when (category) {
|
||||
Constants.RECENT -> {
|
||||
val resId: Int = when (category) {
|
||||
RECENT -> {
|
||||
R.drawable.ic_place_white_24dp
|
||||
}
|
||||
Constants.FAVORITES -> {
|
||||
|
||||
FAVORITES -> {
|
||||
R.drawable.ic_favorite_white_24dp
|
||||
}
|
||||
|
||||
else -> {
|
||||
R.drawable.navigation_48px
|
||||
}
|
||||
@@ -148,7 +196,7 @@ class SearchScreen(
|
||||
searchResult.forEach {
|
||||
searchItemListBuilder.addItem(
|
||||
Row.Builder()
|
||||
.setTitle("${(it.distance/1000).toInt()} km ${it.displayName} ")
|
||||
.setTitle("${(it.distance / 1000).toInt()} km ${it.displayName} ")
|
||||
.setOnClickListener {
|
||||
navigateToPlace(it)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user