Navigation Image
This commit is contained in:
@@ -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,14 +383,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test() {
|
fun simulate() {
|
||||||
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
|
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
|
||||||
val loc = routeModel.route.waypoints[locationIndex]
|
coroutineScope.launch {
|
||||||
lastLocation.longitude = loc[0]
|
delay(
|
||||||
lastLocation.latitude = loc[1]
|
100
|
||||||
updateTestLocation(lastLocation)
|
)
|
||||||
Thread.sleep(1_000)
|
val loc = routeModel.route.waypoints[locationIndex]
|
||||||
locationIndex++
|
lastLocation.longitude = loc[0]
|
||||||
|
lastLocation.latitude = loc[1]
|
||||||
|
updateTestLocation(lastLocation)
|
||||||
|
Thread.sleep(1_000)
|
||||||
|
locationIndex++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
179
common/car/src/main/java/com/kouros/navigation/car/MapView.kt
Normal file
179
common/car/src/main/java/com/kouros/navigation/car/MapView.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,26 +167,31 @@ 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) {
|
||||||
val loc = routeModel.route.waypoints[locationIndex]
|
coroutineScope.launch {
|
||||||
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
delay(
|
||||||
curLocation.longitude = loc[0] + 0.0003
|
100
|
||||||
curLocation.latitude = loc[1] + 0.0002
|
)
|
||||||
update(curLocation)
|
val loc = routeModel.route.waypoints[locationIndex]
|
||||||
locationIndex += 1
|
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
if (locationIndex > routeModel.route.waypoints.size) {
|
curLocation.longitude = loc[0] + 0.0003
|
||||||
val locationManager =
|
curLocation.latitude = loc[1] + 0.0002
|
||||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
update(curLocation)
|
||||||
locationManager.removeUpdates(mLocationListener)
|
locationIndex += 1
|
||||||
|
if (locationIndex > routeModel.route.waypoints.size) {
|
||||||
|
val locationManager =
|
||||||
|
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
locationManager.removeUpdates(mLocationListener)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
update(location = location!!)
|
update(location = location!!)
|
||||||
|
|||||||
@@ -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 ->
|
||||||
BuildingLayer(tiles)
|
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)
|
||||||
|
&& Constants.STYLE.contains("liberty")) {
|
||||||
|
BuildingLayer(tiles)
|
||||||
|
}
|
||||||
RouteLayer(route, previewRoute)
|
RouteLayer(route, previewRoute)
|
||||||
}
|
}
|
||||||
|
//Puck(cameraState, lastLocation)
|
||||||
LocationPuck(
|
|
||||||
idPrefix = "user-location",
|
|
||||||
locationState = lastLocation,
|
|
||||||
cameraState = cameraState,
|
|
||||||
accuracyThreshold = 10f,
|
|
||||||
sizes = LocationPuckSizes(dotRadius = 10.dp),
|
|
||||||
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
ShowPosition(cameraState, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
}
|
||||||
if (lastLocation.latitude != snapedLocation.latitude) {
|
bearing = cameraPosition.value!!.bearing
|
||||||
if (lastLocation.distanceTo(snapedLocation) > 5) {
|
if (lastLocation.latitude != snapedLocation.latitude) {
|
||||||
bearing = lastLocation.bearingTo(snapedLocation).toDouble()
|
if (lastLocation.distanceTo(snapedLocation) > 5) {
|
||||||
}
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
10
common/car/src/main/res/drawable/settings_24px.xml
Normal file
10
common/car/src/main/res/drawable/settings_24px.xml
Normal 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>
|
||||||
@@ -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>
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
|
||||||
|
)
|
||||||
@@ -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 = "",
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
)
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -99,7 +103,8 @@ object NavigationUtils {
|
|||||||
return coordinates
|
return coordinates
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|||||||
Reference in New Issue
Block a user