Refactoring

This commit is contained in:
Dimitris
2025-12-12 15:32:15 +01:00
parent a02673af36
commit 72872cddeb
21 changed files with 588 additions and 349 deletions

View File

@@ -162,7 +162,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
val distance = location.distanceTo(snapedLocation)
if (distance > MAXIMAL_ROUTE_DEVIATION) {
navigationScreen.calculateNewRoute(routeModel.destination)
navigationScreen.calculateNewRoute(routeModel.routeState.destination)
return
}
navigationScreen.updateTrip(location)

View File

@@ -5,14 +5,14 @@ import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.location.Location
import android.location.LocationManager
import android.os.CountDownTimer
import android.os.Handler
import android.util.Log
import androidx.car.app.AppManager
import androidx.car.app.CarContext
import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer
import androidx.car.app.connection.CarConnection
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -27,31 +27,24 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.map.BuildingLayer
import com.kouros.navigation.car.map.DarkMode
import com.kouros.navigation.car.map.DrawImage
import com.kouros.navigation.car.map.RouteLayer
import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom
import com.kouros.navigation.utils.duration
import com.kouros.navigation.utils.location
import com.kouros.navigation.utils.previewZoom
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.map.OrnamentOptions
import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position
import kotlin.math.absoluteValue
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
class SurfaceRenderer(
@@ -59,14 +52,14 @@ class SurfaceRenderer(
private var routeModel: RouteCarModel
) : DefaultLifecycleObserver {
var lastLocation = Location(LocationManager.GPS_PROVIDER)
val cameraPosition = MutableLiveData(
var lastLocation = location(0.0, 0.0)
private val cameraPosition = MutableLiveData(
CameraPosition(
zoom = 15.0,
target = Position(latitude = 48.1857475, longitude = 11.5793627)
)
)
var visibleArea = MutableLiveData(
private var visibleArea = MutableLiveData(
Rect(0, 0, 0, 0)
)
@@ -81,6 +74,7 @@ class SurfaceRenderer(
val previewRouteData = MutableLiveData("")
val speed = MutableLiveData(0F)
lateinit var centerLocation: Location
var preview = false
@@ -90,6 +84,8 @@ class SurfaceRenderer(
var tilt = 55.0
var previewDistance = 0.0
var countDownTimerActive = false
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
lateinit var lifecycleOwner: CustomLifecycleOwner
@@ -171,6 +167,7 @@ class SurfaceRenderer(
init {
lifecycle.addObserver(this)
speed.value = 0F
}
fun onConnectionStateUpdated(connectionState: Int) {
@@ -192,26 +189,8 @@ class SurfaceRenderer(
val baseStyle = remember {
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
}
baseStyle.value =
(if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
Constants.STYLE
))
MaplibreMap(
options = MapOptions(
ornamentOptions =
OrnamentOptions(isScaleBarEnabled = false)),
cameraState = cameraState,
baseStyle = baseStyle.value
) {
getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)) {
BuildingLayer(tiles)
}
RouteLayer(route, previewRoute, position!!.zoom)
}
//Puck(cameraState, lastLocation)
}
DarkMode(carContext, baseStyle)
MapLibre(carContext, cameraState, baseStyle, route, previewRoute, position)
ShowPosition(cameraState, position, paddingValues)
}
@@ -221,16 +200,17 @@ class SurfaceRenderer(
position: CameraPosition?,
paddingValues: PaddingValues
) {
val cameraDuration = duration(position)
var bearing = position!!.bearing
val cameraDuration = duration(preview, position!!.bearing, lastBearing)
var bearing = position.bearing
var zoom = position.zoom
var target = position.target
var localTilt = tilt
val currentSpeed: Float? by speed.observeAsState()
if (!preview) {
if (routeModel.isNavigating()) {
DrawImage(paddingValues, lastLocation, width, height, "")
DrawImage(paddingValues, currentSpeed, width, height)
} else {
DrawImage(paddingValues, lastLocation, width, height, "")
DrawImage(paddingValues, currentSpeed, width, height)
}
} else {
bearing = 0.0
@@ -259,18 +239,6 @@ class SurfaceRenderer(
.setSurfaceCallback(mSurfaceCallback)
}
private fun duration(position: CameraPosition?): Duration {
if (preview) {
return 3.seconds
}
val cameraDuration = if ((lastBearing - position!!.bearing).absoluteValue > 20.0) {
2.seconds
} else {
1.seconds
}
return cameraDuration
}
/** Handles the map zoom-in and zoom-out events. */
fun handleScale(zoomSign: Int) {
synchronized(this) {
@@ -313,6 +281,13 @@ class SurfaceRenderer(
)
lastBearing = cameraPosition.value!!.bearing
lastLocation = location
speed.value = location.speed
if (!countDownTimerActive) {
countDownTimerActive = true
val mainThreadHandler = Handler(carContext.mainLooper)
val lastLocationTimer = lastLocation
checkUpdate(mainThreadHandler, lastLocationTimer)
}
} else {
updateCameraPosition(
0.0,
@@ -323,6 +298,23 @@ class SurfaceRenderer(
}
}
private fun checkUpdate(
mainThreadHandler: Handler,
lastLocationTimer: Location
) {
mainThreadHandler.post {
object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
countDownTimerActive = false
if (lastLocation.time - lastLocationTimer.time < 1500) {
speed.postValue(0F)
}
}
}.start()
}
}
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
cameraPosition.postValue(
cameraPosition.value!!.copy(
@@ -344,7 +336,7 @@ class SurfaceRenderer(
fun setPreviewRouteData(routeModel: RouteModel) {
previewRouteData.value = routeModel.route.routeGeoJson
centerLocation = routeModel.centerLocation
centerLocation = routeModel.route.centerLocation
preview = true
previewDistance = routeModel.route.distance
}

View File

@@ -1,7 +1,9 @@
package com.kouros.navigation.car.map
import android.location.Location
import android.content.Context
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
@@ -12,6 +14,7 @@ import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -25,9 +28,13 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.kouros.data.R
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationColor
import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
@@ -39,9 +46,14 @@ import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.location.UserLocationState
import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.map.OrnamentOptions
import org.maplibre.compose.sources.GeoJsonData
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.Position
@@ -65,6 +77,32 @@ fun cameraState(
)
}
@Composable
fun MapLibre(
context: Context,
cameraState: CameraState,
baseStyle: MutableState<BaseStyle.Uri>,
route: String?,
previewRoute: String?,
position: CameraPosition?
) {
MaplibreMap(
options = MapOptions(
ornamentOptions =
OrnamentOptions(isScaleBarEnabled = false)
),
cameraState = cameraState,
baseStyle = baseStyle.value
) {
getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) {
BuildingLayer(tiles)
}
RouteLayer(route, previewRoute, position!!.zoom)
}
//Puck(cameraState, lastLocation)
}
}
@Composable
fun RouteLayer(routeData: String?, previewRoute: String?, zoom: Double) {
val width = zoom - 2
@@ -115,26 +153,20 @@ fun BuildingLayer(tiles: Source) {
}
@Composable
fun DrawImage(padding: PaddingValues, location: Location, width: Int, height: Int, street: String) {
NavigationImage(padding, width,height, street)
Speed(width, height, location)
fun DrawImage(padding: PaddingValues, speed: Float?, width: Int, height: Int) {
NavigationImage(padding, width,height)
Speed(width, height, speed)
}
@Composable
fun NavigationImage(padding: PaddingValues, width: Int, height: Int, street: String) {
fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
val imageSize = (height/6)
val color = remember { NavigationColor }
BadgedBox(
modifier = Modifier
.padding(padding),
badge = {
Badge()
}
) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(padding)) {
Canvas(modifier =Modifier
.size(imageSize.dp, imageSize.dp)) {
scale(scaleX = 1f, scaleY = 0.7f) {
drawCircle(Color.DarkGray.copy(alpha = 0.2f))
drawCircle(Color.DarkGray.copy(alpha = 0.4f))
}
}
Icon(
@@ -143,8 +175,6 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int, street: Str
tint = color.copy(alpha = 1f),
modifier = Modifier.size(imageSize.dp, imageSize.dp),
)
if (street.isNotEmpty())
Text(text = street)
}
}
@@ -152,7 +182,7 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int, street: Str
private fun Speed(
width: Int,
height: Int,
location: Location
speed: Float?
) {
val radius = 32
Box(
@@ -165,7 +195,7 @@ private fun Speed(
) {
val textMeasurerSpeed = rememberTextMeasurer()
val textMeasurerKm = rememberTextMeasurer()
val speed = (location.speed * 3.6).toInt().toString()
val speed = (speed!! * 3.6).toInt().toString()
val kmh = "km/h"
val styleSpeed = TextStyle(
fontSize = 22.sp,
@@ -212,6 +242,23 @@ private fun Speed(
}
}
@Composable
fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) {
val darkMode = getIntKeyValue(context, Constants.DARK_MODE_SETTINGS)
if (darkMode == 0) {
baseStyle.value = BaseStyle.Uri(Constants.STYLE)
}
if (darkMode == 1) {
baseStyle.value = BaseStyle.Uri(Constants.STYLE_DARK)
}
if (darkMode == 2) {
baseStyle.value =
(if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
Constants.STYLE
))
}
}
fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
return if (preView) {
PaddingValues(start = 150.dp, bottom = 0.dp)

View File

@@ -49,7 +49,7 @@ class RouteCarModel() : RouteModel() {
.setIcon(createCarIcon(carContext, stepData.icon))
.build()
)
.setRoad(destination.street!!)
.setRoad(routeState.destination.street!!)
.build()
return step
}

View File

@@ -0,0 +1,87 @@
package com.kouros.navigation.car.screen
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.SectionedItemList
import androidx.car.app.model.Template
import com.kouros.data.R
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) {
private var darkModeSettings = 0
init {
darkModeSettings = getIntKeyValue(carContext, DARK_MODE_SETTINGS)
}
override fun onGetTemplate(): Template {
val templateBuilder = ListTemplate.Builder()
val radioList =
ItemList.Builder()
.addItem(
buildRowForTemplate(
R.string.off_action_title,
)
)
.addItem(
buildRowForTemplate(
R.string.on_action_title,
)
)
.addItem(
buildRowForTemplate(
R.string.use_telephon_settings,
)
)
.setOnSelectedListener { index: Int ->
this.onSelected(index)
}
.setSelectedIndex(darkModeSettings)
.build()
return templateBuilder
.addSectionedList(SectionedItemList.create(
radioList,
carContext.getString(R.string.dark_mode)
))
.setHeader(
Header.Builder()
.setTitle(carContext.getString(R.string.dark_mode))
.setStartHeaderAction(Action.BACK)
.build()
)
.build()
}
private fun onSelected(index: Int) {
setIntKeyValue(carContext, index, DARK_MODE_SETTINGS)
CarToast.makeText(
carContext,
(carContext
.getString(R.string.display_settings)
+ ":"
+ " " + index), CarToast.LENGTH_LONG
)
.show()
}
private fun buildRowForTemplate(title: Int): Row {
return Row.Builder()
.setTitle(carContext.getString(title))
.build()
}
}

View File

@@ -1,12 +1,12 @@
package com.kouros.navigation.car.screen
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.OnClickListener
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.car.app.model.Toggle
@@ -19,6 +19,7 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
private var buildingToggleState = false
init {
buildingToggleState = getBooleanKeyValue(carContext, SHOW_THREED_BUILDING)
}
@@ -35,7 +36,12 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
buildingToggleState = !buildingToggleState
}.setChecked(buildingToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.threed_building, buildingToggle))
listBuilder.addItem(
buildRowForScreenTemplate(
DarkModeSettings(carContext),
R.string.dark_mode
)
)
return ListTemplate.Builder()
.setSingleList(listBuilder.build())
.setHeader(
@@ -54,4 +60,12 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
.setToggle(toggle)
.build()
}
private fun buildRowForScreenTemplate(screen: Screen, title: Int): Row {
return Row.Builder()
.setTitle(carContext.getString(title))
.setOnClickListener { screenManager.push(screen) }
.setBrowsable(true)
.build()
}
}

View File

@@ -111,11 +111,11 @@ class NavigationScreen(
}
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
if (routeModel.isArrived()) {
if (routeModel.routeState.arrived) {
val timer = object : CountDownTimer(10000, 10000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
routeModel.arrived = false
routeModel.routeState = routeModel.routeState.copy(arrived = false)
invalidate()
}
}
@@ -135,12 +135,16 @@ class NavigationScreen(
}
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
var street = ""
if (routeModel.routeState.destination.street != null) {
street = routeModel.routeState.destination.street!!
}
return NavigationTemplate.Builder()
.setNavigationInfo(
MessageInfo.Builder(
carContext.getString(R.string.arrived_exclamation_msg)
)
.setText(routeModel.destination.street!!)
.setText(street)
.setImage(
CarIcon.Builder(
IconCompat.createWithResource(
@@ -192,7 +196,7 @@ class NavigationScreen(
}
fun getRoutingInfo(): RoutingInfo {
var currentDistance = routeModel.currentDistance
var currentDistance = routeModel.leftStepDistance()
val displayUnit = if (currentDistance > 1000.0) {
currentDistance /= 1000.0
Distance.UNIT_KILOMETERS
@@ -274,7 +278,7 @@ class NavigationScreen(
.setOnClickListener {
val navigateTo = location(recentPlace.longitude, recentPlace.latitude)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo)
routeModel.destination = recentPlace
routeModel.routeState.destination = recentPlace
}
.build()
}
@@ -398,7 +402,7 @@ class NavigationScreen(
viewModel.saveRecent(place)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
currentNavigationLocation = location
routeModel.destination = place
routeModel.routeState.destination = place
invalidate()
}
}
@@ -416,7 +420,7 @@ class NavigationScreen(
invalidate()
val mainThreadHandler = Handler(carContext.mainLooper)
mainThreadHandler.post {
object : CountDownTimer(5000, 1000) {
object : CountDownTimer(3000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
calculateNewRoute = false
@@ -427,19 +431,20 @@ class NavigationScreen(
}
fun reRoute(destination: Place) {
val dest = location( destination.longitude, destination.latitude)
val dest = location(destination.longitude, destination.latitude)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, dest)
}
fun updateTrip(location: Location) {
val start = System.currentTimeMillis()
routeModel.updateLocation(location)
val end = System.currentTimeMillis()
println("Time ${end-start}")
if (routeModel.maneuverType == Maneuver.TYPE_DESTINATION
&& routeModel.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE) {
routeModel.arrived = true
routeModel.stopNavigation()
with(routeModel) {
updateLocation(location)
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
stopNavigation()
routeState = routeState.copy(arrived = true)
surfaceRenderer.routeData.value = ""
}
}
invalidate()
}