diff --git a/app/src/main/java/com/kouros/navigation/MainActivity.kt b/app/src/main/java/com/kouros/navigation/MainActivity.kt index 113c383..ad4762e 100644 --- a/app/src/main/java/com/kouros/navigation/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/MainActivity.kt @@ -52,22 +52,29 @@ import com.example.places.ui.theme.PlacesTheme import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState 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.Constants +import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.StepData import com.kouros.navigation.model.RouteModel 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.calculateZoom +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.rememberCameraState 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.LineLayer import org.maplibre.compose.location.DesiredAccuracy @@ -88,6 +95,7 @@ import kotlin.time.Duration.Companion.seconds class MainActivity : ComponentActivity() { + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO) val routeData = MutableLiveData("") val vieModel = ViewModel(NavigationRepository()) @@ -115,10 +123,16 @@ class MainActivity : ComponentActivity() { var locationIndex = 0 - var test = false + var simulate = false init { vieModel.route.observe(this, observer) + if (simulate) { + vieModel.loadRoute( + Constants.homeLocation, + Constants.home2Location + ) + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -262,10 +276,10 @@ class MainActivity : ComponentActivity() { ) val userLocationState = rememberUserLocationState(locationProvider) val locationState = locationProvider.location.collectAsState() - if (!test) { + if (!simulate) { updateLocation(locationState.value) } else { - test() + simulate() } if (locationState.value != null && lastLocation.latitude == 0.0) { lastLocation.latitude = locationState.value?.position!!.latitude @@ -290,8 +304,16 @@ class MainActivity : ComponentActivity() { baseStyle = BaseStyle.Uri(Constants.STYLE), ) { getBaseSource(id = "openmaptiles")?.let { tiles -> - FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building") - RouteLayer(route) + if (!getBooleanKeyValue(context = applicationContext, SHOW_THREED_BUILDING) && Constants.STYLE.contains("liberty")) { + 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( idPrefix = "user-location1", @@ -321,39 +343,6 @@ class MainActivity : ComponentActivity() { 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?) { @@ -380,7 +369,7 @@ class MainActivity : ComponentActivity() { snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) bearing = routeModel.currentStep().bearing routeModel.updateLocation(snapedLocation) - instruction.value = routeModel.currentStep() + instruction.postValue(routeModel.currentStep()) } else { bearing = cameraPosition.value!!.bearing } @@ -394,14 +383,19 @@ class MainActivity : ComponentActivity() { ) } - fun test() { + fun simulate() { if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) { - val loc = routeModel.route.waypoints[locationIndex] - lastLocation.longitude = loc[0] - lastLocation.latitude = loc[1] - updateTestLocation(lastLocation) - Thread.sleep(1_000) - locationIndex++ + coroutineScope.launch { + delay( + 100 + ) + val loc = routeModel.route.waypoints[locationIndex] + lastLocation.longitude = loc[0] + lastLocation.latitude = loc[1] + updateTestLocation(lastLocation) + Thread.sleep(1_000) + locationIndex++ + } } } diff --git a/common/car/build.gradle.kts b/common/car/build.gradle.kts index 87fb36b..6fda2fb 100644 --- a/common/car/build.gradle.kts +++ b/common/car/build.gradle.kts @@ -49,6 +49,9 @@ dependencies { implementation(project(":common:data")) implementation(libs.androidx.runtime.livedata) implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.material3) + implementation(libs.androidx.compose.ui.text) androidTestImplementation(libs.androidx.junit) testImplementation(libs.junit) } \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/MapView.kt b/common/car/src/main/java/com/kouros/navigation/car/MapView.kt new file mode 100644 index 0000000..deabca2 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/MapView.kt @@ -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 + } +} \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt index 4d62483..20b03ab 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt @@ -24,6 +24,10 @@ import com.kouros.navigation.car.screen.RequestPermissionScreen import com.kouros.navigation.car.screen.SearchScreen import com.kouros.navigation.data.Constants.TAG 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() { val uriScheme = "samples"; @@ -31,16 +35,21 @@ class NavigationSession : Session() { val uriHost = "navigation"; 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? -> updateLocation(location) } + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO) + private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { Log.i(TAG, "In onCreate()") @@ -150,7 +159,7 @@ class NavigationSession : Session() { updateLocation(location) locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, - /* minTimeMs= */ 1000, + /* minTimeMs= */ 10, /* minDistanceM= */ 0f, mLocationListener ) @@ -158,26 +167,31 @@ class NavigationSession : Session() { fun updateLocation(location: Location?) { if (location != null) { - if (test) { - test(location) + if (simulate) { + simulate(location) } else { update(location) } } } - fun test(location: Location?) { + fun simulate(location: Location?) { if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) { - val loc = routeModel.route.waypoints[locationIndex] - val curLocation = Location(LocationManager.GPS_PROVIDER) - curLocation.longitude = loc[0] + 0.0003 - curLocation.latitude = loc[1] + 0.0002 - update(curLocation) - locationIndex += 1 - if (locationIndex > routeModel.route.waypoints.size) { - val locationManager = - carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager - locationManager.removeUpdates(mLocationListener) + coroutineScope.launch { + delay( + 100 + ) + val loc = routeModel.route.waypoints[locationIndex] + val curLocation = Location(LocationManager.GPS_PROVIDER) + curLocation.longitude = loc[0] + 0.0003 + curLocation.latitude = loc[1] + 0.0002 + update(curLocation) + locationIndex += 1 + if (locationIndex > routeModel.route.waypoints.size) { + val locationManager = + carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager + locationManager.removeUpdates(mLocationListener) + } } } else { update(location = location!!) diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index 2eedd57..9354696 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -1,6 +1,7 @@ package com.kouros.navigation.car import android.app.Presentation +import android.content.res.Resources.getSystem import android.graphics.Rect import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay @@ -11,14 +12,11 @@ import androidx.car.app.AppManager import androidx.car.app.CarContext import androidx.car.app.SurfaceCallback import androidx.car.app.SurfaceContainer -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.unit.dp import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle 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.calculateZoom import org.maplibre.compose.camera.CameraPosition -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.camera.CameraState 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.rememberGeoJsonSource 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( - carContext: CarContext, lifecycle: Lifecycle, + private var carContext: CarContext, lifecycle: Lifecycle, private var routeModel: RouteCarModel ) : DefaultLifecycleObserver { - private val mCarContext: CarContext = carContext var lastLocation = Location(LocationManager.GPS_PROVIDER) val cameraPosition = MutableLiveData( CameraPosition( @@ -63,6 +53,19 @@ class SurfaceRenderer( 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 previewRouteData = MutableLiveData("") @@ -74,9 +77,6 @@ class SurfaceRenderer( var panView = false 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 { @@ -107,6 +107,18 @@ class SurfaceRenderer( 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 { this.setViewTreeLifecycleOwner(lifecycleOwner) this.setViewTreeSavedStateRegistryOwner(lifecycleOwner) @@ -120,13 +132,15 @@ class SurfaceRenderer( } } - override fun onVisibleAreaChanged(visibleArea: Rect) { + override fun onVisibleAreaChanged(newVisibleArea: Rect) { synchronized(this@SurfaceRenderer) { + visibleArea.value = newVisibleArea } } - override fun onStableAreaChanged(stableArea: Rect) { + override fun onStableAreaChanged(newStableArea: Rect) { synchronized(this@SurfaceRenderer) { + stableArea = newStableArea } } @@ -143,7 +157,6 @@ class SurfaceRenderer( } override fun onScroll(distanceX: Float, distanceY: Float) { - Log.i(TAG, "onScroll") synchronized(this@SurfaceRenderer) { } } @@ -162,49 +175,41 @@ class SurfaceRenderer( val position: CameraPosition? by cameraPosition.observeAsState() val route: String? by routeData.observeAsState() val previewRoute: String? by previewRouteData.observeAsState() - val cameraState = - rememberCameraState( - firstPosition = - CameraPosition( - target = Position( - latitude = position!!.target.latitude, - longitude = position!!.target.longitude - ), - zoom = 15.0, - tilt = tilt, - padding = getPaddingValues() - ) - ) + val cameraState = cameraState(position, tilt, preview) + MaplibreMap( cameraState = cameraState, baseStyle = BaseStyle.Uri(Constants.STYLE), ) { getBaseSource(id = "openmaptiles")?.let { tiles -> - BuildingLayer(tiles) + if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING) + && Constants.STYLE.contains("liberty")) { + BuildingLayer(tiles) + } RouteLayer(route, previewRoute) } - - LocationPuck( - idPrefix = "user-location", - locationState = lastLocation, - cameraState = cameraState, - accuracyThreshold = 10f, - sizes = LocationPuckSizes(dotRadius = 10.dp), - colors = LocationPuckColors(accuracyStrokeColor = Color.Green) - ) + //Puck(cameraState, lastLocation) } + ShowPosition(cameraState, position) + } + + + @Composable + fun ShowPosition(cameraState: CameraState, position: CameraPosition?) { if (!preview) { + DrawImage(lastLocation) + val cameraDuration = duration(position) LaunchedEffect(position) { cameraState.animateTo( finalPosition = CameraPosition( bearing = position!!.bearing, - zoom = position!!.zoom, - target = position!!.target, + zoom = position.zoom, + target = position.target, tilt = tilt, - padding = getPaddingValues() + padding = getPaddingValues(preview) ), - duration = 3.seconds + duration = cameraDuration ) } } else { @@ -212,10 +217,10 @@ class SurfaceRenderer( cameraState.animateTo( finalPosition = CameraPosition( bearing = 0.0, - zoom = 9.0, + zoom = 11.0, target = Position(centerLocation.longitude, centerLocation.latitude), tilt = 0.0, - padding = getPaddingValues() + padding = getPaddingValues(preview) ), 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) { Log.i(TAG, "SurfaceRenderer created") - mCarContext.getCarService(AppManager::class.java) + carContext.getCarService(AppManager::class.java) .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. */ fun handleScale(zoomSign: Int) { synchronized(this) { @@ -304,32 +270,34 @@ class SurfaceRenderer( var bearing: Double if (routeModel.isNavigating()) { snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) - bearing = routeModel.currentStep().bearing - } else { - bearing = cameraPosition.value!!.bearing - if (lastLocation.latitude != snapedLocation.latitude) { - if (lastLocation.distanceTo(snapedLocation) > 5) { - bearing = lastLocation.bearingTo(snapedLocation).toDouble() - } + // stimmt nicht + //bearing = routeModel.currentStep().bearing + } + bearing = cameraPosition.value!!.bearing + if (lastLocation.latitude != snapedLocation.latitude) { + if (lastLocation.distanceTo(snapedLocation) > 5) { + bearing = lastLocation.bearingTo(snapedLocation).toDouble() } } + val zoom = if (!panView) { calculateZoom(snapedLocation.speed.toDouble()) } else { cameraPosition.value!!.zoom } + lastBearing = cameraPosition.value!!.bearing cameraPosition.postValue( cameraPosition.value!!.copy( bearing = bearing, zoom = zoom, - padding = getPaddingValues(), + padding = getPaddingValues(preview), target = Position(snapedLocation.longitude, snapedLocation.latitude), ) ) lastLocation = snapedLocation } else { val bearing = 0.0 - val zoom = 11.0 + val zoom = 14.0 cameraPosition.postValue( cameraPosition.value!!.copy( bearing = bearing, @@ -342,7 +310,6 @@ class SurfaceRenderer( } } - fun setRouteData() { routeData.value = routeModel.route.routeGeoJson preview = false @@ -355,17 +322,10 @@ class SurfaceRenderer( preview = true } - fun getPaddingValues(): PaddingValues { - return if (preview) { - prePadding - } else { - padding - } - } - companion object { private const val TAG = "MapRenderer" } } + diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index e5a5bdf..8ba4c33 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -63,7 +63,7 @@ class NavigationScreen( ) actionStripBuilder.addAction( Action.Builder() - .setIcon(routeModel.createCarIcon(carContext, R.drawable.ic_favorite_white_24dp)) + .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_24px)) .setOnClickListener { screenManager.push(SettingsScreen(carContext)) } @@ -91,8 +91,8 @@ class NavigationScreen( .build() ) .setOnClickListener { - surfaceRenderer.routeData.postValue("") routeModel.stopNavigation() + surfaceRenderer.routeData.postValue("") invalidate() } .build() diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index 1286f6f..3b382b9 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -21,7 +21,6 @@ import android.location.LocationManager import android.os.CountDownTimer import android.text.SpannableString import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.car.app.CarContext import androidx.car.app.CarToast import androidx.car.app.Screen @@ -164,9 +163,8 @@ class RoutePreviewScreen( ) .build() - val timer = object: CountDownTimer(10000, 10000) { + val timer = object: CountDownTimer(10000, 15000) { override fun onTick(millisUntilFinished: Long) {} - override fun onFinish() { onNavigate() } @@ -223,18 +221,17 @@ class RoutePreviewScreen( fun getMapActionStrip(): ActionStrip { return ActionStrip.Builder() .addAction( - createToastAction(R.drawable.ic_zoom_in_24, R.string.zoomed_in_toast_msg) + createToastAction(R.drawable.ic_zoom_in_24) ) .addAction( - createToastAction(R.drawable.ic_zoom_out_24, R.string.zoomed_out_toast_msg) + createToastAction(R.drawable.ic_zoom_out_24) ) .addAction(Action.PAN) .build() } private fun createToastAction( - @DrawableRes iconRes: Int, - @StringRes toastStringRes: Int + @DrawableRes iconRes: Int ): Action { return Action.Builder() .setOnClickListener { surfaceRenderer.handleScale(-1) } diff --git a/common/car/src/main/res/drawable-navexposed/assistant_navigation_48px.xml b/common/car/src/main/res/drawable-navexposed/assistant_navigation_48px.xml new file mode 100644 index 0000000..1077be0 --- /dev/null +++ b/common/car/src/main/res/drawable-navexposed/assistant_navigation_48px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/car/src/main/res/drawable/assistant_navigation_48px.xml b/common/car/src/main/res/drawable/assistant_navigation_48px.xml new file mode 100644 index 0000000..1077be0 --- /dev/null +++ b/common/car/src/main/res/drawable/assistant_navigation_48px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/car/src/main/res/drawable/settings_24px.xml b/common/car/src/main/res/drawable/settings_24px.xml new file mode 100644 index 0000000..4bcd4aa --- /dev/null +++ b/common/car/src/main/res/drawable/settings_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/car/src/main/res/layout/permission_request.xml b/common/car/src/main/res/layout/permission_request.xml deleted file mode 100644 index e7ef3cf..0000000 --- a/common/car/src/main/res/layout/permission_request.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/Data.kt b/common/data/src/main/java/com/kouros/navigation/data/Data.kt index a10fcb8..8c2d370 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Data.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Data.kt @@ -119,7 +119,9 @@ data class ValhallaLocation ( object Constants { 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 CONTACTS: String = "Contacts" diff --git a/common/data/src/main/java/com/kouros/navigation/data/valhalla/ExitBranchElements.kt b/common/data/src/main/java/com/kouros/navigation/data/valhalla/ExitBranchElements.kt new file mode 100644 index 0000000..526f103 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/valhalla/ExitBranchElements.kt @@ -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 + +) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/valhalla/ExitTowardElements.kt b/common/data/src/main/java/com/kouros/navigation/data/valhalla/ExitTowardElements.kt new file mode 100644 index 0000000..dbc106f --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/valhalla/ExitTowardElements.kt @@ -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 + +) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/valhalla/Maneuvers.kt b/common/data/src/main/java/com/kouros/navigation/data/valhalla/Maneuvers.kt index 10262c1..a446fa6 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/valhalla/Maneuvers.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/valhalla/Maneuvers.kt @@ -10,8 +10,7 @@ import kotlinx.serialization.json.JsonIgnoreUnknownKeys @JsonIgnoreUnknownKeys data class Maneuvers( - @SerializedName("begin_shape_index") var beginShapeIndex: Int, - @SerializedName("end_shape_index") var endShapeIndex: Int, + @SerializedName("type") var type: Int = 0, @SerializedName("instruction") var instruction: String = "", @SerializedName("verbal_succinct_transition_instruction") var verbalSuccinctTransitionInstruction: String = "", @@ -23,6 +22,10 @@ data class Maneuvers( @SerializedName("length") var length: Double = 0.0, @SerializedName("cost") var cost: Double = 0.0, @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_type") var travelType: String = "", diff --git a/common/data/src/main/java/com/kouros/navigation/data/valhalla/Sign.kt b/common/data/src/main/java/com/kouros/navigation/data/valhalla/Sign.kt new file mode 100644 index 0000000..8f13b50 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/valhalla/Sign.kt @@ -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 = arrayListOf(), + @SerializedName("exit_toward_elements" ) var exitTowardElements : ArrayList = arrayListOf() + +) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt b/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt index c204109..e2799aa 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt @@ -36,7 +36,7 @@ class Contacts(private var context: Context) { if (name.contains("Jola") || name.contains("Dominic") || name.contains("Martha") - || name.contains("Μεντη") + || name.contains("Rena") || name.contains("David")) { val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE)) if (mimeType == ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) { diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index d78a5e5..a8d818e 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -6,8 +6,11 @@ import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Place import com.kouros.navigation.data.Route import com.kouros.navigation.data.StepData +import com.kouros.navigation.utils.NavigationUtils import com.kouros.navigation.utils.location +import org.maplibre.geojson.FeatureCollection import org.maplibre.geojson.Point +import org.maplibre.turf.TurfMeasurement import kotlin.math.absoluteValue import kotlin.math.roundToInt @@ -47,15 +50,12 @@ open class RouteModel() { private fun createCenterLocation(): Location { - if (route.summary.maxLat == 0.0) { - return location(homeLocation.latitude, homeLocation.longitude) - } - val latitude = - 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 future = TurfMeasurement.center(FeatureCollection.fromJson(route.routeGeoJson)) + val point = future.geometry() as Point + return location(point.latitude(), point.longitude()) } + val currentDistance: Double /** Returns the current [Step] with information such as the cue text and images. */ get() { diff --git a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt index 3d8fca6..c1ff837 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt @@ -10,10 +10,14 @@ import com.kouros.navigation.data.GeoJsonFeature import com.kouros.navigation.data.GeoJsonFeatureCollection import com.kouros.navigation.data.GeoJsonLineString 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.turf.TurfClassification import org.maplibre.turf.TurfConversion import org.maplibre.turf.TurfJoins +import org.maplibre.turf.TurfMeasurement import org.maplibre.turf.TurfMeta import org.maplibre.turf.TurfMisc import org.maplibre.turf.TurfTransformation @@ -99,7 +103,8 @@ object NavigationUtils { return coordinates } - fun createGeoJson(lineCoordinates: List>): String { + fun createGeoJson(lineCoordinates: List>): String { + val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates) val feature = GeoJsonFeature(type = "Feature", geometry = lineString) val featureCollection = @@ -163,7 +168,7 @@ fun calculateZoom(speed: Double?): Double { in 31..40 -> 16.0 in 41..50 -> 15.0 in 51..60 -> 14.0 - else -> 11 + else -> 14 } return zoom.toDouble() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a5bff2..6070e35 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,22 +12,25 @@ junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" kotlinxSerializationJson = "1.9.0" -lifecycleRuntimeKtx = "2.9.4" -composeBom = "2025.11.00" +lifecycleRuntimeKtx = "2.10.0" +composeBom = "2025.11.01" appcompat = "1.7.1" material = "1.13.0" carApp = "1.7.0" objectboxKotlin = "5.0.1" objectboxProcessor = "5.0.1" -ui = "1.9.4" +ui = "1.9.5" material3 = "1.4.0" -runtimeLivedata = "1.9.4" -foundation = "1.9.4" +runtimeLivedata = "1.9.5" +foundation = "1.9.5" maplibre-composeMaterial3 = "0.12.2" maplibre-compose = "0.12.1" playServicesLocation = "21.3.0" -runtime = "1.9.4" -accompanist = "0.32.0" +runtime = "1.9.5" +accompanist = "0.37.3" +uiVersion = "1.9.5" +uiText = "1.9.5" + [libraries] 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" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } 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-processor = { module = "io.objectbox:objectbox-processor", version.ref = "objectboxProcessor" } 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" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" } 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] android-application = { id = "com.android.application", version.ref = "agp" }