Navigation Image

This commit is contained in:
Dimitris
2025-11-25 15:14:34 +01:00
parent b1a9a2c7fe
commit ce382e304c
20 changed files with 437 additions and 244 deletions

View File

@@ -52,22 +52,29 @@ import com.example.places.ui.theme.PlacesTheme
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.car.BuildingLayer
import com.kouros.navigation.car.Puck
import com.kouros.navigation.car.RouteLayer
import com.kouros.navigation.data.Category import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.snapLocation import com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.CircleLayer
import org.maplibre.compose.layers.FillLayer import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.DesiredAccuracy
@@ -88,6 +95,7 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
val vieModel = ViewModel(NavigationRepository()) val vieModel = ViewModel(NavigationRepository())
@@ -115,10 +123,16 @@ class MainActivity : ComponentActivity() {
var locationIndex = 0 var locationIndex = 0
var test = false var simulate = false
init { init {
vieModel.route.observe(this, observer) vieModel.route.observe(this, observer)
if (simulate) {
vieModel.loadRoute(
Constants.homeLocation,
Constants.home2Location
)
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -262,10 +276,10 @@ class MainActivity : ComponentActivity() {
) )
val userLocationState = rememberUserLocationState(locationProvider) val userLocationState = rememberUserLocationState(locationProvider)
val locationState = locationProvider.location.collectAsState() val locationState = locationProvider.location.collectAsState()
if (!test) { if (!simulate) {
updateLocation(locationState.value) updateLocation(locationState.value)
} else { } else {
test() simulate()
} }
if (locationState.value != null && lastLocation.latitude == 0.0) { if (locationState.value != null && lastLocation.latitude == 0.0) {
lastLocation.latitude = locationState.value?.position!!.latitude lastLocation.latitude = locationState.value?.position!!.latitude
@@ -290,8 +304,16 @@ class MainActivity : ComponentActivity() {
baseStyle = BaseStyle.Uri(Constants.STYLE), baseStyle = BaseStyle.Uri(Constants.STYLE),
) { ) {
getBaseSource(id = "openmaptiles")?.let { tiles -> getBaseSource(id = "openmaptiles")?.let { tiles ->
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building") if (!getBooleanKeyValue(context = applicationContext, SHOW_THREED_BUILDING) && Constants.STYLE.contains("liberty")) {
RouteLayer(route) BuildingLayer(tiles)
}
RouteLayer(route, "")
}
if (userLocationState.location != null) {
val location = Location(LocationManager.GPS_PROVIDER)
location.longitude = userLocationState.location!!.position.longitude
location.latitude = userLocationState.location!!.position.latitude
Puck(cameraState, location,)
} }
LocationPuck( LocationPuck(
idPrefix = "user-location1", idPrefix = "user-location1",
@@ -321,39 +343,6 @@ class MainActivity : ComponentActivity() {
duration = 1.seconds duration = 1.seconds
) )
} }
// LaunchedEffect(position) {
// println("CameraPosition ${position!!.target.latitude}")
// cameraState.animateTo(
// finalPosition = CameraPosition(
// bearing = position!!.bearing,
// zoom = position!!.zoom,
// target = position!!.target,
// tilt = tilt
// ),
// duration = 3.seconds
// )
// }
}
@Composable
fun RouteLayer(routeData: String?) {
if (routeData!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(routeData!!))
LineLayer(
id = "routes-casing",
source = routes,
color = const(Color.White),
width = const(10.dp),
)
LineLayer(
id = "routes",
source = routes,
color = const(Color.Blue),
width = const(8.dp),
)
}
} }
fun updateLocation(location: org.maplibre.compose.location.Location?) { fun updateLocation(location: org.maplibre.compose.location.Location?) {
@@ -380,7 +369,7 @@ class MainActivity : ComponentActivity() {
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
bearing = routeModel.currentStep().bearing bearing = routeModel.currentStep().bearing
routeModel.updateLocation(snapedLocation) routeModel.updateLocation(snapedLocation)
instruction.value = routeModel.currentStep() instruction.postValue(routeModel.currentStep())
} else { } else {
bearing = cameraPosition.value!!.bearing bearing = cameraPosition.value!!.bearing
} }
@@ -394,8 +383,12 @@ class MainActivity : ComponentActivity() {
) )
} }
fun test() { fun simulate() {
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) { if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
coroutineScope.launch {
delay(
100
)
val loc = routeModel.route.waypoints[locationIndex] val loc = routeModel.route.waypoints[locationIndex]
lastLocation.longitude = loc[0] lastLocation.longitude = loc[0]
lastLocation.latitude = loc[1] lastLocation.latitude = loc[1]
@@ -404,6 +397,7 @@ class MainActivity : ComponentActivity() {
locationIndex++ locationIndex++
} }
} }
}
@Composable @Composable
fun PlaceList(viewModel: ViewModel = koinViewModel()) { fun PlaceList(viewModel: ViewModel = koinViewModel()) {

View File

@@ -49,6 +49,9 @@ dependencies {
implementation(project(":common:data")) implementation(project(":common:data"))
implementation(libs.androidx.runtime.livedata) implementation(libs.androidx.runtime.livedata)
implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.material3)
implementation(libs.androidx.compose.ui.text)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
testImplementation(libs.junit) testImplementation(libs.junit)
} }

View File

@@ -0,0 +1,179 @@
package com.kouros.navigation.car
import android.R.attr.strokeWidth
import android.graphics.Rect
import android.location.Location
import androidx.car.app.CarContext
import androidx.compose.foundation.layout.Box
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.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.spatialk.geojson.Position
@Composable
fun cameraState(position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
val padding = getPaddingValues(preview)
return rememberCameraState(
firstPosition =
CameraPosition(
target = Position(
latitude = position!!.target.latitude,
longitude = position.target.longitude
),
zoom = 15.0,
tilt = tilt,
padding = padding
)
)
}
@Composable
fun RouteLayer(routeData: String?, previewRoute: String?) {
if (routeData!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
LineLayer(
id = "routes-casing",
source = routes,
color = const(Color.White),
width = const(16.dp),
)
LineLayer(
id = "routes",
source = routes,
color = const(Color.Blue),
width = const(14.dp),
)
}
if (previewRoute!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(previewRoute))
LineLayer(
id = "routes-casing-pre",
source = routes,
color = const(Color.White),
width = const(6.dp),
)
LineLayer(
id = "routes-pre",
source = routes,
color = const(Color.Cyan),
width = const(4.dp),
)
}
}
@Composable
fun BuildingLayer(tiles: Source) {
Anchor.Replace("building-3d") {
FillLayer(
id = "remove-building",
visible = false,
source = tiles,
sourceLayer = "building"
)
}
}
@Composable
fun DrawImage(location: Location) {
val textMeasurer = rememberTextMeasurer()
val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px)
val painter = rememberVectorPainter(image = vector)
val tint = remember { ColorFilter.tint(Color.Blue) }
Box(
modifier = Modifier
.padding(start = 450.dp - 30.dp, top = 350.dp - 30.dp)
.drawBehind {
with(painter) {
draw(
size = Size(60F, 60F),
colorFilter = tint
)
}
})
Box(
modifier = Modifier
.size(30.dp, 30.dp)
.padding(start = 650.dp, top = 350.dp)
.drawWithCache {
val measuredText =
textMeasurer.measure(
AnnotatedString("${(location.speed * 3.6).toInt()}"),
style = TextStyle(fontSize = 22.sp)
)
onDrawBehind {
drawCircle(
Color.LightGray, radius = 30.dp.toPx(), center = Offset(5f, 10f)
)
drawText(measuredText)
}
}
.fillMaxSize()
)
}
@Composable
fun Puck(cameraState: CameraState, location: Location) {
LocationPuck(
idPrefix = "user-location",
locationState = location,
cameraState = cameraState,
accuracyThreshold = 10f,
showBearing = false,
sizes = LocationPuckSizes(dotRadius = 10.dp),
colors = LocationPuckColors(
dotFillColorCurrentLocation = Color.Cyan,
accuracyStrokeColor = Color.Green
)
)
}
fun getPaddingValues(preView: Boolean): PaddingValues {
val padding = PaddingValues(start = 100.dp, top = 300.dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
return if (preView) {
prePadding
} else {
padding
}
}

View File

@@ -24,6 +24,10 @@ 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.data.Constants.TAG import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class NavigationSession : Session() { class NavigationSession : Session() {
val uriScheme = "samples"; val uriScheme = "samples";
@@ -31,16 +35,21 @@ class NavigationSession : Session() {
val uriHost = "navigation"; val uriHost = "navigation";
lateinit var routeModel: RouteCarModel; lateinit var routeModel: RouteCarModel;
lateinit var navigationScreen: NavigationScreen
lateinit var surfaceRenderer: SurfaceRenderer
var locationIndex = 0
val test = true lateinit var navigationScreen: NavigationScreen
lateinit var surfaceRenderer: SurfaceRenderer
var locationIndex = 100
val simulate = true
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? -> var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
updateLocation(location) updateLocation(location)
} }
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver { private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) { override fun onCreate(owner: LifecycleOwner) {
Log.i(TAG, "In onCreate()") Log.i(TAG, "In onCreate()")
@@ -150,7 +159,7 @@ class NavigationSession : Session() {
updateLocation(location) updateLocation(location)
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, LocationManager.GPS_PROVIDER,
/* minTimeMs= */ 1000, /* minTimeMs= */ 10,
/* minDistanceM= */ 0f, /* minDistanceM= */ 0f,
mLocationListener mLocationListener
) )
@@ -158,16 +167,20 @@ class NavigationSession : Session() {
fun updateLocation(location: Location?) { fun updateLocation(location: Location?) {
if (location != null) { if (location != null) {
if (test) { if (simulate) {
test(location) simulate(location)
} else { } else {
update(location) update(location)
} }
} }
} }
fun test(location: Location?) { fun simulate(location: Location?) {
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) { if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
coroutineScope.launch {
delay(
100
)
val loc = routeModel.route.waypoints[locationIndex] val loc = routeModel.route.waypoints[locationIndex]
val curLocation = Location(LocationManager.GPS_PROVIDER) val curLocation = Location(LocationManager.GPS_PROVIDER)
curLocation.longitude = loc[0] + 0.0003 curLocation.longitude = loc[0] + 0.0003
@@ -179,6 +192,7 @@ class NavigationSession : Session() {
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.removeUpdates(mLocationListener) locationManager.removeUpdates(mLocationListener)
} }
}
} else { } else {
update(location = location!!) update(location = location!!)
} }

View File

@@ -1,6 +1,7 @@
package com.kouros.navigation.car package com.kouros.navigation.car
import android.app.Presentation import android.app.Presentation
import android.content.res.Resources.getSystem
import android.graphics.Rect import android.graphics.Rect
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
@@ -11,14 +12,11 @@ import androidx.car.app.AppManager
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.SurfaceCallback import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer import androidx.car.app.SurfaceContainer
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@@ -33,29 +31,21 @@ import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.snapLocation import com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.map.MaplibreMap import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.getBaseSource import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import kotlin.math.absoluteValue
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
class SurfaceRenderer( class SurfaceRenderer(
carContext: CarContext, lifecycle: Lifecycle, private var carContext: CarContext, lifecycle: Lifecycle,
private var routeModel: RouteCarModel private var routeModel: RouteCarModel
) : DefaultLifecycleObserver { ) : DefaultLifecycleObserver {
private val mCarContext: CarContext = carContext
var lastLocation = Location(LocationManager.GPS_PROVIDER) var lastLocation = Location(LocationManager.GPS_PROVIDER)
val cameraPosition = MutableLiveData( val cameraPosition = MutableLiveData(
CameraPosition( CameraPosition(
@@ -63,6 +53,19 @@ class SurfaceRenderer(
target = Position(latitude = 48.1857475, longitude = 11.5793627) target = Position(latitude = 48.1857475, longitude = 11.5793627)
) )
) )
var visibleArea = MutableLiveData(
Rect()
)
var stableArea = Rect()
var containerWidth = 0
var containerHeight = 0
var containerDpi = 1
var lastBearing = 0.0
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
val previewRouteData = MutableLiveData("") val previewRouteData = MutableLiveData("")
@@ -74,9 +77,6 @@ class SurfaceRenderer(
var panView = false var panView = false
val tilt = 55.0 val tilt = 55.0
val padding = PaddingValues(start = 150.dp, top = 250.dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback { val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
@@ -107,6 +107,18 @@ class SurfaceRenderer(
0 0
) )
containerWidth = surfaceContainer.width
containerHeight = surfaceContainer.height
if (surfaceContainer.dpi != 0) {
containerDpi = surfaceContainer.dpi
}
println(surfaceContainer.toString())
println(containerDpi)
println(carContext.resources.displayMetrics.density)
println(carContext.resources.displayMetrics.densityDpi)
println(getSystem().displayMetrics.density)
println(getSystem().displayMetrics.densityDpi)
mapView = ComposeView(carContext).apply { mapView = ComposeView(carContext).apply {
this.setViewTreeLifecycleOwner(lifecycleOwner) this.setViewTreeLifecycleOwner(lifecycleOwner)
this.setViewTreeSavedStateRegistryOwner(lifecycleOwner) this.setViewTreeSavedStateRegistryOwner(lifecycleOwner)
@@ -120,13 +132,15 @@ class SurfaceRenderer(
} }
} }
override fun onVisibleAreaChanged(visibleArea: Rect) { override fun onVisibleAreaChanged(newVisibleArea: Rect) {
synchronized(this@SurfaceRenderer) { synchronized(this@SurfaceRenderer) {
visibleArea.value = newVisibleArea
} }
} }
override fun onStableAreaChanged(stableArea: Rect) { override fun onStableAreaChanged(newStableArea: Rect) {
synchronized(this@SurfaceRenderer) { synchronized(this@SurfaceRenderer) {
stableArea = newStableArea
} }
} }
@@ -143,7 +157,6 @@ class SurfaceRenderer(
} }
override fun onScroll(distanceX: Float, distanceY: Float) { override fun onScroll(distanceX: Float, distanceY: Float) {
Log.i(TAG, "onScroll")
synchronized(this@SurfaceRenderer) { synchronized(this@SurfaceRenderer) {
} }
} }
@@ -162,49 +175,41 @@ class SurfaceRenderer(
val position: CameraPosition? by cameraPosition.observeAsState() val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState() val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState() val previewRoute: String? by previewRouteData.observeAsState()
val cameraState = val cameraState = cameraState(position, tilt, preview)
rememberCameraState(
firstPosition =
CameraPosition(
target = Position(
latitude = position!!.target.latitude,
longitude = position!!.target.longitude
),
zoom = 15.0,
tilt = tilt,
padding = getPaddingValues()
)
)
MaplibreMap( MaplibreMap(
cameraState = cameraState, cameraState = cameraState,
baseStyle = BaseStyle.Uri(Constants.STYLE), baseStyle = BaseStyle.Uri(Constants.STYLE),
) { ) {
getBaseSource(id = "openmaptiles")?.let { tiles -> getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)
&& Constants.STYLE.contains("liberty")) {
BuildingLayer(tiles) BuildingLayer(tiles)
}
RouteLayer(route, previewRoute) RouteLayer(route, previewRoute)
} }
//Puck(cameraState, lastLocation)
LocationPuck( }
idPrefix = "user-location", ShowPosition(cameraState, position)
locationState = lastLocation,
cameraState = cameraState,
accuracyThreshold = 10f,
sizes = LocationPuckSizes(dotRadius = 10.dp),
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
)
} }
@Composable
fun ShowPosition(cameraState: CameraState, position: CameraPosition?) {
if (!preview) { if (!preview) {
DrawImage(lastLocation)
val cameraDuration = duration(position)
LaunchedEffect(position) { LaunchedEffect(position) {
cameraState.animateTo( cameraState.animateTo(
finalPosition = CameraPosition( finalPosition = CameraPosition(
bearing = position!!.bearing, bearing = position!!.bearing,
zoom = position!!.zoom, zoom = position.zoom,
target = position!!.target, target = position.target,
tilt = tilt, tilt = tilt,
padding = getPaddingValues() padding = getPaddingValues(preview)
), ),
duration = 3.seconds duration = cameraDuration
) )
} }
} else { } else {
@@ -212,10 +217,10 @@ class SurfaceRenderer(
cameraState.animateTo( cameraState.animateTo(
finalPosition = CameraPosition( finalPosition = CameraPosition(
bearing = 0.0, bearing = 0.0,
zoom = 9.0, zoom = 11.0,
target = Position(centerLocation.longitude, centerLocation.latitude), target = Position(centerLocation.longitude, centerLocation.latitude),
tilt = 0.0, tilt = 0.0,
padding = getPaddingValues() padding = getPaddingValues(preview)
), ),
duration = 3.seconds duration = 3.seconds
) )
@@ -223,62 +228,23 @@ class SurfaceRenderer(
} }
} }
@Composable
fun BuildingLayer(tiles: Source) {
if (!getBooleanKeyValue(context = mCarContext, SHOW_THREED_BUILDING)) {
Anchor.Replace("building-3d") {
FillLayer(
id = "remove-building",
visible = false,
source = tiles,
sourceLayer = "building"
)
}
}
}
@Composable
fun RouteLayer(routeData: String?, previewRoute: String?) {
if (routeData!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
LineLayer(
id = "routes-casing",
source = routes,
color = const(Color.White),
width = const(16.dp),
)
LineLayer(
id = "routes",
source = routes,
color = const(Color.Blue),
width = const(14.dp),
)
}
if (previewRoute!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(previewRoute))
LineLayer(
id = "routes-casing-pre",
source = routes,
color = const(Color.White),
width = const(6.dp),
)
LineLayer(
id = "routes-pre",
source = routes,
color = const(Color.Cyan),
width = const(4.dp),
)
}
}
override fun onCreate(owner: LifecycleOwner) { override fun onCreate(owner: LifecycleOwner) {
Log.i(TAG, "SurfaceRenderer created") Log.i(TAG, "SurfaceRenderer created")
mCarContext.getCarService(AppManager::class.java) carContext.getCarService(AppManager::class.java)
.setSurfaceCallback(mSurfaceCallback) .setSurfaceCallback(mSurfaceCallback)
} }
private fun duration(position: CameraPosition?): Duration {
val cameraDuration = if ((lastBearing - position!!.bearing).absoluteValue > 20.0) {
0.4.seconds
} else {
1.seconds
}
return cameraDuration
}
/** Handles the map zoom-in and zoom-out events. */ /** Handles the map zoom-in and zoom-out events. */
fun handleScale(zoomSign: Int) { fun handleScale(zoomSign: Int) {
synchronized(this) { synchronized(this) {
@@ -304,32 +270,34 @@ class SurfaceRenderer(
var bearing: Double var bearing: Double
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
bearing = routeModel.currentStep().bearing // stimmt nicht
} else { //bearing = routeModel.currentStep().bearing
}
bearing = cameraPosition.value!!.bearing bearing = cameraPosition.value!!.bearing
if (lastLocation.latitude != snapedLocation.latitude) { if (lastLocation.latitude != snapedLocation.latitude) {
if (lastLocation.distanceTo(snapedLocation) > 5) { if (lastLocation.distanceTo(snapedLocation) > 5) {
bearing = lastLocation.bearingTo(snapedLocation).toDouble() bearing = lastLocation.bearingTo(snapedLocation).toDouble()
} }
} }
}
val zoom = if (!panView) { val zoom = if (!panView) {
calculateZoom(snapedLocation.speed.toDouble()) calculateZoom(snapedLocation.speed.toDouble())
} else { } else {
cameraPosition.value!!.zoom cameraPosition.value!!.zoom
} }
lastBearing = cameraPosition.value!!.bearing
cameraPosition.postValue( cameraPosition.postValue(
cameraPosition.value!!.copy( cameraPosition.value!!.copy(
bearing = bearing, bearing = bearing,
zoom = zoom, zoom = zoom,
padding = getPaddingValues(), padding = getPaddingValues(preview),
target = Position(snapedLocation.longitude, snapedLocation.latitude), target = Position(snapedLocation.longitude, snapedLocation.latitude),
) )
) )
lastLocation = snapedLocation lastLocation = snapedLocation
} else { } else {
val bearing = 0.0 val bearing = 0.0
val zoom = 11.0 val zoom = 14.0
cameraPosition.postValue( cameraPosition.postValue(
cameraPosition.value!!.copy( cameraPosition.value!!.copy(
bearing = bearing, bearing = bearing,
@@ -342,7 +310,6 @@ class SurfaceRenderer(
} }
} }
fun setRouteData() { fun setRouteData() {
routeData.value = routeModel.route.routeGeoJson routeData.value = routeModel.route.routeGeoJson
preview = false preview = false
@@ -355,17 +322,10 @@ class SurfaceRenderer(
preview = true preview = true
} }
fun getPaddingValues(): PaddingValues {
return if (preview) {
prePadding
} else {
padding
}
}
companion companion
object { object {
private const val TAG = "MapRenderer" private const val TAG = "MapRenderer"
} }
} }

View File

@@ -63,7 +63,7 @@ class NavigationScreen(
) )
actionStripBuilder.addAction( actionStripBuilder.addAction(
Action.Builder() Action.Builder()
.setIcon(routeModel.createCarIcon(carContext, R.drawable.ic_favorite_white_24dp)) .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_24px))
.setOnClickListener { .setOnClickListener {
screenManager.push(SettingsScreen(carContext)) screenManager.push(SettingsScreen(carContext))
} }
@@ -91,8 +91,8 @@ class NavigationScreen(
.build() .build()
) )
.setOnClickListener { .setOnClickListener {
surfaceRenderer.routeData.postValue("")
routeModel.stopNavigation() routeModel.stopNavigation()
surfaceRenderer.routeData.postValue("")
invalidate() invalidate()
} }
.build() .build()

View File

@@ -21,7 +21,6 @@ import android.location.LocationManager
import android.os.CountDownTimer import android.os.CountDownTimer
import android.text.SpannableString import android.text.SpannableString
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.CarToast import androidx.car.app.CarToast
import androidx.car.app.Screen import androidx.car.app.Screen
@@ -164,9 +163,8 @@ class RoutePreviewScreen(
) )
.build() .build()
val timer = object: CountDownTimer(10000, 10000) { val timer = object: CountDownTimer(10000, 15000) {
override fun onTick(millisUntilFinished: Long) {} override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() { override fun onFinish() {
onNavigate() onNavigate()
} }
@@ -223,18 +221,17 @@ class RoutePreviewScreen(
fun getMapActionStrip(): ActionStrip { fun getMapActionStrip(): ActionStrip {
return ActionStrip.Builder() return ActionStrip.Builder()
.addAction( .addAction(
createToastAction(R.drawable.ic_zoom_in_24, R.string.zoomed_in_toast_msg) createToastAction(R.drawable.ic_zoom_in_24)
) )
.addAction( .addAction(
createToastAction(R.drawable.ic_zoom_out_24, R.string.zoomed_out_toast_msg) createToastAction(R.drawable.ic_zoom_out_24)
) )
.addAction(Action.PAN) .addAction(Action.PAN)
.build() .build()
} }
private fun createToastAction( private fun createToastAction(
@DrawableRes iconRes: Int, @DrawableRes iconRes: Int
@StringRes toastStringRes: Int
): Action { ): Action {
return Action.Builder() return Action.Builder()
.setOnClickListener { surfaceRenderer.handleScale(-1) } .setOnClickListener { surfaceRenderer.handleScale(-1) }

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M321,668L480,596L639,668L644,663L480,266L316,663L321,668ZM480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 111.5,324Q143,251 197.5,197Q252,143 325,111.5Q398,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,562 848.5,635Q817,708 763,762.5Q709,817 636,848.5Q563,880 480,880ZM480,820Q622,820 721,720.5Q820,621 820,480Q820,338 721,239Q622,140 480,140Q339,140 239.5,239Q140,338 140,480Q140,621 239.5,720.5Q339,820 480,820ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M321,668L480,596L639,668L644,663L480,266L316,663L321,668ZM480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 111.5,324Q143,251 197.5,197Q252,143 325,111.5Q398,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,562 848.5,635Q817,708 763,762.5Q709,817 636,848.5Q563,880 480,880ZM480,820Q622,820 721,720.5Q820,621 820,480Q820,338 721,239Q622,140 480,140Q339,140 239.5,239Q140,338 140,480Q140,621 239.5,720.5Q339,820 480,820ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
</vector>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1E1D1D">
<ImageView
android:id="@+id/imageView"
android:layout_width="158dp"
android:layout_height="123dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="32dp"
android:src="@drawable/ic_launcher" />
</RelativeLayout>

View File

@@ -119,7 +119,9 @@ data class ValhallaLocation (
object Constants { object Constants {
const val STYLE: String = "https://kouros-online.de/liberty.json" const val STYLE: String = "https://kouros-online.de/liberty.json"
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"), //const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
//const val STYLE: String = "https://tiles.openfreemap.org/styles/dark"
const val TAG: String = "Navigation" const val TAG: String = "Navigation"
const val CONTACTS: String = "Contacts" const val CONTACTS: String = "Contacts"

View File

@@ -0,0 +1,11 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
data class ExitBranchElements (
@SerializedName("text" ) var text : String? = null,
@SerializedName("consecutive_count" ) var consecutiveCount : Int? = null
)

View File

@@ -0,0 +1,10 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
data class ExitTowardElements (
@SerializedName("text" ) var text : String? = null
)

View File

@@ -10,8 +10,7 @@ import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@JsonIgnoreUnknownKeys @JsonIgnoreUnknownKeys
data class Maneuvers( data class Maneuvers(
@SerializedName("begin_shape_index") var beginShapeIndex: Int,
@SerializedName("end_shape_index") var endShapeIndex: Int,
@SerializedName("type") var type: Int = 0, @SerializedName("type") var type: Int = 0,
@SerializedName("instruction") var instruction: String = "", @SerializedName("instruction") var instruction: String = "",
@SerializedName("verbal_succinct_transition_instruction") var verbalSuccinctTransitionInstruction: String = "", @SerializedName("verbal_succinct_transition_instruction") var verbalSuccinctTransitionInstruction: String = "",
@@ -23,6 +22,10 @@ data class Maneuvers(
@SerializedName("length") var length: Double = 0.0, @SerializedName("length") var length: Double = 0.0,
@SerializedName("cost") var cost: Double = 0.0, @SerializedName("cost") var cost: Double = 0.0,
@SerializedName("verbal_multi_cue") var verbalMultiCue: Boolean = false, @SerializedName("verbal_multi_cue") var verbalMultiCue: Boolean = false,
@SerializedName("begin_shape_index") var beginShapeIndex: Int,
@SerializedName("end_shape_index") var endShapeIndex: Int,
@SerializedName("highway") var highway: Boolean = false,
@SerializedName("sign") var sign: Sign = Sign(),
@SerializedName("travel_mode") var travelMode: String = "", @SerializedName("travel_mode") var travelMode: String = "",
@SerializedName("travel_type") var travelType: String = "", @SerializedName("travel_type") var travelType: String = "",

View File

@@ -0,0 +1,11 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
data class Sign (
@SerializedName("exit_branch_elements" ) var exitBranchElements : ArrayList<ExitBranchElements> = arrayListOf(),
@SerializedName("exit_toward_elements" ) var exitTowardElements : ArrayList<ExitTowardElements> = arrayListOf()
)

View File

@@ -36,7 +36,7 @@ class Contacts(private var context: Context) {
if (name.contains("Jola") if (name.contains("Jola")
|| name.contains("Dominic") || name.contains("Dominic")
|| name.contains("Martha") || name.contains("Martha")
|| name.contains("Μεντη") || name.contains("Rena")
|| name.contains("David")) { || name.contains("David")) {
val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE)) val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE))
if (mimeType == ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) { if (mimeType == ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) {

View File

@@ -6,8 +6,11 @@ import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point import org.maplibre.geojson.Point
import org.maplibre.turf.TurfMeasurement
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -47,15 +50,12 @@ open class RouteModel() {
private fun createCenterLocation(): Location { private fun createCenterLocation(): Location {
if (route.summary.maxLat == 0.0) {
return location(homeLocation.latitude, homeLocation.longitude) val future = TurfMeasurement.center(FeatureCollection.fromJson(route.routeGeoJson))
} val point = future.geometry() as Point
val latitude = return location(point.latitude(), point.longitude())
route.summary.maxLat - (route.summary.maxLat - route.summary.minLat)
val longitude =
route.summary.maxLon - (route.summary.maxLon - route.summary.minLon)
return location(latitude, longitude)
} }
val currentDistance: Double val currentDistance: Double
/** Returns the current [Step] with information such as the cue text and images. */ /** Returns the current [Step] with information such as the cue text and images. */
get() { get() {

View File

@@ -10,10 +10,14 @@ import com.kouros.navigation.data.GeoJsonFeature
import com.kouros.navigation.data.GeoJsonFeatureCollection import com.kouros.navigation.data.GeoJsonFeatureCollection
import com.kouros.navigation.data.GeoJsonLineString import com.kouros.navigation.data.GeoJsonLineString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.maplibre.geojson.Feature
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.LineString
import org.maplibre.geojson.Point import org.maplibre.geojson.Point
import org.maplibre.turf.TurfClassification import org.maplibre.turf.TurfClassification
import org.maplibre.turf.TurfConversion import org.maplibre.turf.TurfConversion
import org.maplibre.turf.TurfJoins import org.maplibre.turf.TurfJoins
import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMeta import org.maplibre.turf.TurfMeta
import org.maplibre.turf.TurfMisc import org.maplibre.turf.TurfMisc
import org.maplibre.turf.TurfTransformation import org.maplibre.turf.TurfTransformation
@@ -100,6 +104,7 @@ object NavigationUtils {
} }
fun createGeoJson(lineCoordinates: List<List<Double>>): String { fun createGeoJson(lineCoordinates: List<List<Double>>): String {
val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates) val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates)
val feature = GeoJsonFeature(type = "Feature", geometry = lineString) val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
val featureCollection = val featureCollection =
@@ -163,7 +168,7 @@ fun calculateZoom(speed: Double?): Double {
in 31..40 -> 16.0 in 31..40 -> 16.0
in 41..50 -> 15.0 in 41..50 -> 15.0
in 51..60 -> 14.0 in 51..60 -> 14.0
else -> 11 else -> 14
} }
return zoom.toDouble() return zoom.toDouble()
} }

View File

@@ -12,22 +12,25 @@ junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
kotlinxSerializationJson = "1.9.0" kotlinxSerializationJson = "1.9.0"
lifecycleRuntimeKtx = "2.9.4" lifecycleRuntimeKtx = "2.10.0"
composeBom = "2025.11.00" composeBom = "2025.11.01"
appcompat = "1.7.1" appcompat = "1.7.1"
material = "1.13.0" material = "1.13.0"
carApp = "1.7.0" carApp = "1.7.0"
objectboxKotlin = "5.0.1" objectboxKotlin = "5.0.1"
objectboxProcessor = "5.0.1" objectboxProcessor = "5.0.1"
ui = "1.9.4" ui = "1.9.5"
material3 = "1.4.0" material3 = "1.4.0"
runtimeLivedata = "1.9.4" runtimeLivedata = "1.9.5"
foundation = "1.9.4" foundation = "1.9.5"
maplibre-composeMaterial3 = "0.12.2" maplibre-composeMaterial3 = "0.12.2"
maplibre-compose = "0.12.1" maplibre-compose = "0.12.1"
playServicesLocation = "21.3.0" playServicesLocation = "21.3.0"
runtime = "1.9.4" runtime = "1.9.5"
accompanist = "0.32.0" accompanist = "0.37.3"
uiVersion = "1.9.5"
uiText = "1.9.5"
[libraries] [libraries]
android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" } android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" }
@@ -47,7 +50,6 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koinCore" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" } material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" } androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }
#objectbox-kotlin = { module = "io.objectbox:objectbox-kotlin", version.ref = "objectboxKotlin" }
objectbox-kotlin = { module = "io.objectbox:objectbox-kotlin", version.ref = "objectboxKotlin" } objectbox-kotlin = { module = "io.objectbox:objectbox-kotlin", version.ref = "objectboxKotlin" }
objectbox-processor = { module = "io.objectbox:objectbox-processor", version.ref = "objectboxProcessor" } objectbox-processor = { module = "io.objectbox:objectbox-processor", version.ref = "objectboxProcessor" }
ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" } ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
@@ -59,6 +61,8 @@ androidx-compose-foundation = { group = "androidx.compose.foundation", name = "f
play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" } play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" }
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" } accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "uiVersion" }
androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }