MapView, Navigation to RecentPlace
This commit is contained in:
@@ -10,7 +10,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 30
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "0.1.3"
|
versionName = "0.1.3"
|
||||||
@@ -46,11 +46,11 @@ android {
|
|||||||
// Specifies one flavor dimension.
|
// Specifies one flavor dimension.
|
||||||
flavorDimensions += "version"
|
flavorDimensions += "version"
|
||||||
productFlavors {
|
productFlavors {
|
||||||
create("demo") {
|
// create("demo") {
|
||||||
dimension = "version"
|
// dimension = "version"
|
||||||
applicationIdSuffix = ".demo"
|
// applicationIdSuffix = ".demo"
|
||||||
versionNameSuffix = "-demo"
|
// versionNameSuffix = "-demo"
|
||||||
}
|
// }
|
||||||
create("full") {
|
create("full") {
|
||||||
dimension = "version"
|
dimension = "version"
|
||||||
applicationIdSuffix = ".full"
|
applicationIdSuffix = ".full"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ android {
|
|||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
minSdk = 33
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.Badge
|
import androidx.compose.material3.Badge
|
||||||
@@ -11,13 +13,12 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.drawText
|
import androidx.compose.ui.text.drawText
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
@@ -26,6 +27,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import com.kouros.android.cars.carappservice.R
|
import com.kouros.android.cars.carappservice.R
|
||||||
import com.kouros.navigation.data.NavigationColor
|
import com.kouros.navigation.data.NavigationColor
|
||||||
import com.kouros.navigation.data.RouteColor
|
import com.kouros.navigation.data.RouteColor
|
||||||
|
import com.kouros.navigation.data.SpeedColor
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.CameraState
|
import org.maplibre.compose.camera.CameraState
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
@@ -134,43 +136,78 @@ fun NavigationImage(height: Int, street: String) {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
modifier = Modifier.size(72.dp, 72.dp),
|
||||||
imageVector = vector,
|
imageVector = vector,
|
||||||
contentDescription = "Navigation",
|
contentDescription = "Navigation",
|
||||||
tint = color
|
tint = color
|
||||||
)
|
)
|
||||||
if (street.isNotEmpty())
|
if (street.isNotEmpty())
|
||||||
Text(text=street)
|
Text(text = street)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Speed(
|
private fun Speed(
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
location: Location
|
location: Location
|
||||||
) {
|
) {
|
||||||
|
val radius = 32
|
||||||
val textMeasurer = rememberTextMeasurer()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(30.dp, 30.dp)
|
|
||||||
.padding(
|
.padding(
|
||||||
start = width.dp - percent(width, 20).dp,
|
start = width.dp- 300.dp,
|
||||||
top = height.dp - 60.dp
|
top = height.dp- 80.dp
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
val textMeasurerSpeed = rememberTextMeasurer()
|
||||||
|
val textMeasurerKm = rememberTextMeasurer()
|
||||||
|
val speed = (location.speed * 3.6).toInt().toString()
|
||||||
|
val kmh = "km/h"
|
||||||
|
val styleSpeed = TextStyle(
|
||||||
|
fontSize = 22.sp,
|
||||||
|
color = Color.White,
|
||||||
)
|
)
|
||||||
.drawWithCache {
|
val styleKm = TextStyle(
|
||||||
val measuredText =
|
fontSize = 12.sp,
|
||||||
textMeasurer.measure(
|
color = Color.White,
|
||||||
AnnotatedString("${(location.speed * 3.6).toInt()}"),
|
|
||||||
style = TextStyle(color = Color.White, fontSize = 22.sp)
|
|
||||||
)
|
)
|
||||||
onDrawBehind {
|
val textLayoutSpeed = remember(speed) {
|
||||||
|
textMeasurerSpeed.measure(speed, styleSpeed)
|
||||||
|
}
|
||||||
|
val textLayoutKm = remember(kmh) {
|
||||||
|
textMeasurerSpeed.measure(kmh, styleKm)
|
||||||
|
}
|
||||||
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
drawCircle(
|
drawCircle(
|
||||||
Color.Black, radius = 30.dp.toPx(), center = Offset(15f, 12f)
|
center = Offset(
|
||||||
|
x = center.x,
|
||||||
|
y = center.y
|
||||||
|
),
|
||||||
|
radius = radius.toFloat(),
|
||||||
|
color = SpeedColor,
|
||||||
|
)
|
||||||
|
drawText(
|
||||||
|
textMeasurer = textMeasurerSpeed,
|
||||||
|
text = speed,
|
||||||
|
style = styleSpeed,
|
||||||
|
topLeft = Offset(
|
||||||
|
x = center.x - textLayoutSpeed.size.width / 2,
|
||||||
|
y = center.y - textLayoutSpeed.size.height / 2 - 5,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
drawText(
|
||||||
|
textMeasurer = textMeasurerKm,
|
||||||
|
text = "km/h",
|
||||||
|
style = styleKm,
|
||||||
|
topLeft = Offset(
|
||||||
|
x = center.x - textLayoutKm.size.width / 2,
|
||||||
|
y = center.y - textLayoutKm.size.height / 2 + 15,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
drawText(measuredText)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPaddingValues(height: Int, preView: Boolean): PaddingValues {
|
fun getPaddingValues(height: Int, preView: Boolean): PaddingValues {
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
lifecycle.addObserver(mLifeCycleObserver)
|
lifecycle.addObserver(mLifeCycleObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
override fun onCreateScreen(intent: Intent): Screen {
|
override fun onCreateScreen(intent: Intent): Screen {
|
||||||
routeModel = RouteCarModel()
|
routeModel = RouteCarModel()
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class SurfaceRenderer(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
var visibleArea = MutableLiveData(
|
var visibleArea = MutableLiveData(
|
||||||
Rect()
|
Rect(0,0,0,0)
|
||||||
)
|
)
|
||||||
|
|
||||||
var stableArea = Rect()
|
var stableArea = Rect()
|
||||||
@@ -76,6 +76,7 @@ class SurfaceRenderer(
|
|||||||
var panView = false
|
var panView = false
|
||||||
val tilt = 55.0
|
val tilt = 55.0
|
||||||
|
|
||||||
|
var previewDistance = 0.0
|
||||||
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
|
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
|
||||||
|
|
||||||
lateinit var lifecycleOwner: CustomLifecycleOwner
|
lateinit var lifecycleOwner: CustomLifecycleOwner
|
||||||
@@ -84,7 +85,6 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
lateinit var presentation: Presentation
|
lateinit var presentation: Presentation
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
|
override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
|
||||||
synchronized(this@SurfaceRenderer) {
|
synchronized(this@SurfaceRenderer) {
|
||||||
Log.i(TAG, "Surface available $surfaceContainer")
|
Log.i(TAG, "Surface available $surfaceContainer")
|
||||||
@@ -134,7 +134,6 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.KITKAT)
|
|
||||||
override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
|
override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
|
||||||
synchronized(this@SurfaceRenderer) {
|
synchronized(this@SurfaceRenderer) {
|
||||||
Log.i(TAG, "SurfaceRenderer destroyed")
|
Log.i(TAG, "SurfaceRenderer destroyed")
|
||||||
@@ -168,7 +167,7 @@ class SurfaceRenderer(
|
|||||||
val previewRoute: String? by previewRouteData.observeAsState()
|
val previewRoute: String? by previewRouteData.observeAsState()
|
||||||
val cameraState = cameraState(width, height, position, tilt, preview)
|
val cameraState = cameraState(width, height, position, tilt, preview)
|
||||||
|
|
||||||
val baseStyle =BaseStyle.Uri(Constants.STYLE)
|
val baseStyle = BaseStyle.Uri(Constants.STYLE)
|
||||||
// if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
|
// if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
|
||||||
// Constants.STYLE
|
// Constants.STYLE
|
||||||
// )
|
// )
|
||||||
@@ -307,25 +306,29 @@ class SurfaceRenderer(
|
|||||||
previewRouteData.value = routeModel.route.routeGeoJson
|
previewRouteData.value = routeModel.route.routeGeoJson
|
||||||
centerLocation = routeModel.centerLocation
|
centerLocation = routeModel.centerLocation
|
||||||
preview = true
|
preview = true
|
||||||
|
previewDistance = routeModel.route.distance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun previewZoom(): Double {
|
private fun previewZoom(): Double {
|
||||||
if (routeModel.isNavigating()) {
|
when (previewDistance) {
|
||||||
when (routeModel.route.distance) {
|
|
||||||
in 0.0..10.0 -> {
|
in 0.0..10.0 -> {
|
||||||
return 14.0
|
return 13.0
|
||||||
}
|
}
|
||||||
in 10.0..20.0 -> {
|
in 10.0..20.0 -> {
|
||||||
return 12.0
|
|
||||||
}
|
|
||||||
in 20.0..30.0 -> {
|
|
||||||
return 11.0
|
return 11.0
|
||||||
}
|
}
|
||||||
}
|
in 20.0..30.0 -> {
|
||||||
}
|
|
||||||
return 10.0
|
return 10.0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return 9.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPreViewDistance(): Double {
|
||||||
|
return previewDistance
|
||||||
|
}
|
||||||
|
|
||||||
companion
|
companion
|
||||||
object {
|
object {
|
||||||
private const val TAG = "MapRenderer"
|
private const val TAG = "MapRenderer"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.location.Location
|
|||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
@@ -14,8 +13,12 @@ import androidx.car.app.model.ActionStrip
|
|||||||
import androidx.car.app.model.CarColor
|
import androidx.car.app.model.CarColor
|
||||||
import androidx.car.app.model.CarIcon
|
import androidx.car.app.model.CarIcon
|
||||||
import androidx.car.app.model.Distance
|
import androidx.car.app.model.Distance
|
||||||
|
import androidx.car.app.model.Header
|
||||||
|
import androidx.car.app.model.MessageTemplate
|
||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
import androidx.car.app.navigation.model.Maneuver
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
|
import androidx.car.app.navigation.model.MapController
|
||||||
|
import androidx.car.app.navigation.model.MapWithContentTemplate
|
||||||
import androidx.car.app.navigation.model.MessageInfo
|
import androidx.car.app.navigation.model.MessageInfo
|
||||||
import androidx.car.app.navigation.model.NavigationTemplate
|
import androidx.car.app.navigation.model.NavigationTemplate
|
||||||
import androidx.car.app.navigation.model.RoutingInfo
|
import androidx.car.app.navigation.model.RoutingInfo
|
||||||
@@ -31,6 +34,7 @@ import com.kouros.navigation.car.navigation.RouteCarModel
|
|||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
|
|
||||||
class NavigationScreen(
|
class NavigationScreen(
|
||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
@@ -49,40 +53,39 @@ class NavigationScreen(
|
|||||||
|
|
||||||
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
|
|
||||||
|
lateinit var recentPlace: Place
|
||||||
|
|
||||||
|
var recentPlaceFound = false
|
||||||
|
|
||||||
|
var recentPlaceActive = true
|
||||||
|
|
||||||
var calculateNewRoute = false
|
var calculateNewRoute = false
|
||||||
val vieModel = ViewModel(NavigationRepository())
|
val viewModel = ViewModel(NavigationRepository())
|
||||||
val observer = Observer<String> { route ->
|
val observer = Observer<String> { route ->
|
||||||
if (route.isNotEmpty()) {
|
if (route.isNotEmpty()) {
|
||||||
routeModel.startNavigation(route)
|
routeModel.startNavigation(route)
|
||||||
surfaceRenderer.setRouteData()
|
surfaceRenderer.setRouteData()
|
||||||
|
recentPlaceActive = false
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val recentObserver = Observer<Place> { lastPlace ->
|
||||||
|
if (!routeModel.isNavigating()) {
|
||||||
|
recentPlace = lastPlace
|
||||||
|
recentPlaceFound = true
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
vieModel.route.observe(this, observer)
|
viewModel.route.observe(this, observer)
|
||||||
|
viewModel.recentPlace.observe(this, recentObserver)
|
||||||
|
viewModel.loadRecentPlace(location = surfaceRenderer.lastLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
val actionStripBuilder = createActionStripBuilder()
|
||||||
actionStripBuilder.addAction(
|
|
||||||
Action.Builder()
|
|
||||||
.setIcon(routeModel.createCarIcon(carContext, R.drawable.ic_search_black36dp))
|
|
||||||
.setOnClickListener {
|
|
||||||
startSearchScreen()
|
|
||||||
}
|
|
||||||
//.setFlags(Action.FLAG_IS_PERSISTENT)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
actionStripBuilder.addAction(
|
|
||||||
Action.Builder()
|
|
||||||
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_24px))
|
|
||||||
.setOnClickListener {
|
|
||||||
screenManager.push(SettingsScreen(carContext))
|
|
||||||
}
|
|
||||||
//.setFlags(Action.FLAG_IS_PERSISTENT)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
return if (routeModel.isNavigating()) {
|
return if (routeModel.isNavigating()) {
|
||||||
if (calculateNewRoute) {
|
if (calculateNewRoute) {
|
||||||
getNavigationLoadingTemplate(actionStripBuilder)
|
getNavigationLoadingTemplate(actionStripBuilder)
|
||||||
@@ -96,21 +99,7 @@ class NavigationScreen(
|
|||||||
|
|
||||||
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||||
actionStripBuilder.addAction(
|
actionStripBuilder.addAction(
|
||||||
Action.Builder()
|
stopAction()
|
||||||
.setTitle(carContext.getString(R.string.stop_action_title))
|
|
||||||
.setIcon(
|
|
||||||
CarIcon.Builder(
|
|
||||||
IconCompat.createWithResource(
|
|
||||||
carContext,
|
|
||||||
R.drawable.ic_close_white_24dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.setOnClickListener {
|
|
||||||
stopNavigation()
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
)
|
)
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setNavigationInfo(
|
.setNavigationInfo(
|
||||||
@@ -123,7 +112,7 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||||
if (routeModel.isArrived()) {
|
if (routeModel.isArrived()) {
|
||||||
val timer = object : CountDownTimer(10000, 10000) {
|
val timer = object : CountDownTimer(10000, 10000) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
@@ -155,13 +144,42 @@ class NavigationScreen(
|
|||||||
.setMapActionStrip(mapActionStripBuilder().build())
|
.setMapActionStrip(mapActionStripBuilder().build())
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
return NavigationTemplate.Builder()
|
return if (recentPlaceFound && recentPlaceActive) {
|
||||||
|
return getRecentPlaceTemplate()
|
||||||
|
} else {
|
||||||
|
NavigationTemplate.Builder()
|
||||||
.setBackgroundColor(CarColor.SECONDARY)
|
.setBackgroundColor(CarColor.SECONDARY)
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(mapActionStripBuilder().build())
|
.setMapActionStrip(mapActionStripBuilder().build())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRecentPlaceTemplate(): Template {
|
||||||
|
val messageTemplate = MessageTemplate.Builder(
|
||||||
|
recentPlace.name + "\n"
|
||||||
|
+ recentPlace.city
|
||||||
|
)
|
||||||
|
.setHeader(
|
||||||
|
Header.Builder()
|
||||||
|
.setTitle(carContext.getString(R.string.drive_now))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.addAction(navigateAction())
|
||||||
|
.addAction(closeAction())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val builder = MapWithContentTemplate.Builder()
|
||||||
|
.setContentTemplate(messageTemplate)
|
||||||
|
.setActionStrip(
|
||||||
|
mapActionStripBuilder()
|
||||||
|
.addAction(settingsAction())
|
||||||
|
.addAction(searchAction())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
fun getNavigationLoadingTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
fun getNavigationLoadingTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
@@ -198,11 +216,105 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createActionStripBuilder(): ActionStrip.Builder {
|
||||||
|
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
||||||
|
actionStripBuilder.addAction(
|
||||||
|
searchAction()
|
||||||
|
)
|
||||||
|
actionStripBuilder.addAction(
|
||||||
|
settingsAction()
|
||||||
|
)
|
||||||
|
return actionStripBuilder
|
||||||
|
}
|
||||||
|
|
||||||
private fun mapActionStripBuilder(): ActionStrip.Builder {
|
private fun mapActionStripBuilder(): ActionStrip.Builder {
|
||||||
val actionStripBuilder = ActionStrip.Builder()
|
val actionStripBuilder = ActionStrip.Builder()
|
||||||
|
.addAction(zoomPlus())
|
||||||
|
.addAction(zoomMinus())
|
||||||
|
if (surfaceRenderer.panView) {
|
||||||
|
actionStripBuilder
|
||||||
.addAction(
|
.addAction(
|
||||||
Action.Builder()
|
panAction()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return actionStripBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopAction(): Action {
|
||||||
|
return Action.Builder()
|
||||||
|
.setTitle(carContext.getString(R.string.stop_action_title))
|
||||||
|
.setIcon(
|
||||||
|
CarIcon.Builder(
|
||||||
|
IconCompat.createWithResource(
|
||||||
|
carContext,
|
||||||
|
R.drawable.ic_close_white_24dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.setOnClickListener {
|
||||||
|
stopNavigation()
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateAction(): Action {
|
||||||
|
return Action.Builder()
|
||||||
|
.setIcon(
|
||||||
|
CarIcon.Builder(
|
||||||
|
IconCompat.createWithResource(
|
||||||
|
carContext,
|
||||||
|
R.drawable.assistant_navigation_48px
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.setOnClickListener {
|
||||||
|
val navigateTo = location(recentPlace.latitude, recentPlace.longitude)
|
||||||
|
viewModel.loadRoute(surfaceRenderer.lastLocation, navigateTo)
|
||||||
|
routeModel.destination = recentPlace
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun closeAction(): Action {
|
||||||
|
return Action.Builder()
|
||||||
|
.setIcon(
|
||||||
|
CarIcon.Builder(
|
||||||
|
IconCompat.createWithResource(
|
||||||
|
carContext,
|
||||||
|
R.drawable.ic_close_white_24dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.setOnClickListener {
|
||||||
|
recentPlaceActive = false
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchAction(): Action {
|
||||||
|
return Action.Builder()
|
||||||
|
.setIcon(routeModel.createCarIcon(carContext, R.drawable.ic_search_black36dp))
|
||||||
|
.setOnClickListener {
|
||||||
|
startSearchScreen()
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun settingsAction(): Action {
|
||||||
|
return Action.Builder()
|
||||||
|
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_24px))
|
||||||
|
.setOnClickListener {
|
||||||
|
screenManager.push(SettingsScreen(carContext))
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun zoomPlus(): Action {
|
||||||
|
return Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
CarIcon.Builder(
|
CarIcon.Builder(
|
||||||
IconCompat.createWithResource(
|
IconCompat.createWithResource(
|
||||||
@@ -215,9 +327,10 @@ class NavigationScreen(
|
|||||||
surfaceRenderer.handleScale(1)
|
surfaceRenderer.handleScale(1)
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
)
|
}
|
||||||
.addAction(
|
|
||||||
Action.Builder()
|
private fun zoomMinus(): Action {
|
||||||
|
return Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
CarIcon.Builder(
|
CarIcon.Builder(
|
||||||
IconCompat.createWithResource(
|
IconCompat.createWithResource(
|
||||||
@@ -229,10 +342,11 @@ class NavigationScreen(
|
|||||||
).setOnClickListener {
|
).setOnClickListener {
|
||||||
surfaceRenderer.handleScale(-1)
|
surfaceRenderer.handleScale(-1)
|
||||||
}
|
}
|
||||||
.build())
|
.build()
|
||||||
if (surfaceRenderer.panView) {
|
}
|
||||||
actionStripBuilder.addAction(
|
|
||||||
Action.Builder()
|
private fun panAction(): Action {
|
||||||
|
return Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
CarIcon.Builder(
|
CarIcon.Builder(
|
||||||
IconCompat.createWithResource(
|
IconCompat.createWithResource(
|
||||||
@@ -245,9 +359,6 @@ class NavigationScreen(
|
|||||||
surfaceRenderer.panView = false
|
surfaceRenderer.panView = false
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
)
|
|
||||||
}
|
|
||||||
return actionStripBuilder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSuggestion(title: Int, subtitle: Int, icon: CarIcon): Suggestion {
|
private fun getSuggestion(title: Int, subtitle: Int, icon: CarIcon): Suggestion {
|
||||||
@@ -282,8 +393,8 @@ class NavigationScreen(
|
|||||||
val location = Location(LocationManager.GPS_PROVIDER)
|
val location = Location(LocationManager.GPS_PROVIDER)
|
||||||
location.latitude = place.latitude
|
location.latitude = place.latitude
|
||||||
location.longitude = place.longitude
|
location.longitude = place.longitude
|
||||||
vieModel.saveRecent(place)
|
viewModel.saveRecent(place)
|
||||||
vieModel.loadRoute(surfaceRenderer.lastLocation, location)
|
viewModel.loadRoute(surfaceRenderer.lastLocation, location)
|
||||||
currentNavigationLocation = location
|
currentNavigationLocation = location
|
||||||
routeModel.destination = place
|
routeModel.destination = place
|
||||||
invalidate()
|
invalidate()
|
||||||
@@ -303,7 +414,7 @@ class NavigationScreen(
|
|||||||
val mainThreadhandler = Handler(carContext.mainLooper)
|
val mainThreadhandler = Handler(carContext.mainLooper)
|
||||||
mainThreadhandler.post {
|
mainThreadhandler.post {
|
||||||
object : CountDownTimer(5000, 1000) {
|
object : CountDownTimer(5000, 1000) {
|
||||||
override fun onTick(millisUntilFinished: Long) { }
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
calculateNewRoute = false
|
calculateNewRoute = false
|
||||||
stopNavigation()
|
stopNavigation()
|
||||||
@@ -314,7 +425,7 @@ class NavigationScreen(
|
|||||||
|
|
||||||
fun reRoute() {
|
fun reRoute() {
|
||||||
NavigationMessage(carContext).createAlert()
|
NavigationMessage(carContext).createAlert()
|
||||||
vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
|
viewModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTrip() {
|
fun updateTrip() {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package com.kouros.navigation.car.screen
|
|||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
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
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
import android.location.Geocoder
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
@@ -60,7 +59,6 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
private var mItemLimit = 0
|
private var mItemLimit = 0
|
||||||
|
|
||||||
private var street = ""
|
|
||||||
val vieModel = ViewModel(NavigationRepository())
|
val vieModel = ViewModel(NavigationRepository())
|
||||||
|
|
||||||
val routeModel = RouteCarModel()
|
val routeModel = RouteCarModel()
|
||||||
@@ -70,16 +68,9 @@ class RoutePreviewScreen(
|
|||||||
if (route.isNotEmpty()) {
|
if (route.isNotEmpty()) {
|
||||||
routeModel.startNavigation(route)
|
routeModel.startNavigation(route)
|
||||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||||
val geocoder = Geocoder(carContext)
|
|
||||||
// nominatim ->
|
|
||||||
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
|
|
||||||
for (address in it) {
|
|
||||||
street = address.getAddressLine(0)
|
|
||||||
}
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
vieModel.previewRoute.observe(this, observer)
|
vieModel.previewRoute.observe(this, observer)
|
||||||
@@ -192,7 +183,7 @@ class RoutePreviewScreen(
|
|||||||
return Row.Builder()
|
return Row.Builder()
|
||||||
.setTitle(route)
|
.setTitle(route)
|
||||||
.setOnClickListener { onRouteSelected(index) }
|
.setOnClickListener { onRouteSelected(index) }
|
||||||
.addText(street)
|
.addText( "${destination.street!!} ${destination.postalCode} ${destination.city}")
|
||||||
.addAction(action)
|
.addAction(action)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class SearchScreen(
|
|||||||
lateinit var searchResult: List<SearchResult>
|
lateinit var searchResult: List<SearchResult>
|
||||||
|
|
||||||
val observer = Observer<List<SearchResult>> { newSearch ->
|
val observer = Observer<List<SearchResult>> { newSearch ->
|
||||||
println(newSearch)
|
|
||||||
searchResult = newSearch
|
searchResult = newSearch
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,4 +364,5 @@
|
|||||||
<string name="route_preview">Routen</string>
|
<string name="route_preview">Routen</string>
|
||||||
<string name="recent_destinations">Letzte Ziele</string>
|
<string name="recent_destinations">Letzte Ziele</string>
|
||||||
<string name="contacts">Kontakte</string>
|
<string name="contacts">Kontakte</string>
|
||||||
|
<string name="drive_now">Jetzt losfahren</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -491,4 +491,5 @@
|
|||||||
<string name="route_preview">Routes</string>
|
<string name="route_preview">Routes</string>
|
||||||
<string name="recent_destinations">Recent</string>
|
<string name="recent_destinations">Recent</string>
|
||||||
<string name="contacts">Contacts</string>
|
<string name="contacts">Contacts</string>
|
||||||
|
<string name="drive_now">Drive now</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ android {
|
|||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
minSdk = 33
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ import androidx.compose.ui.graphics.Color
|
|||||||
val NavigationColor = Color(0xFF052086)
|
val NavigationColor = Color(0xFF052086)
|
||||||
|
|
||||||
val RouteColor = Color(0xFF5582D0)
|
val RouteColor = Color(0xFF5582D0)
|
||||||
|
|
||||||
|
val SpeedColor = Color(0xFF262525)
|
||||||
@@ -32,7 +32,8 @@ class NavigationRepository {
|
|||||||
|
|
||||||
private val routeUrl = "https://kouros-online.de/valhalla/route?json="
|
private val routeUrl = "https://kouros-online.de/valhalla/route?json="
|
||||||
|
|
||||||
private val nominatimUrl = "https://nominatim.openstreetmap.org/search?q="
|
private val nominatimUrl = "https://nominatim.openstreetmap.org/"
|
||||||
|
|
||||||
fun getRoute(currentLocation : Location, location: Location): String {
|
fun getRoute(currentLocation : Location, location: Location): String {
|
||||||
val vLocation = listOf(
|
val vLocation = listOf(
|
||||||
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
|
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
|
||||||
@@ -57,7 +58,11 @@ class NavigationRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun searchPlaces(search : String) : String {
|
fun searchPlaces(search : String) : String {
|
||||||
return fetchUrl("$nominatimUrl$search&format=jsonv2&addressdetails=true&countrycodes=de", false)
|
return fetchUrl("${nominatimUrl}search?q=$search&format=jsonv2&addressdetails=true&countrycodes=de", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reverseAddress(location: Location) : String {
|
||||||
|
return fetchUrl("${nominatimUrl}reverse?lat=${location.latitude}&lon=${location.longitude}&format=jsonv2&addressdetails=true&countrycodes=de", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlaces(): List<Place> {
|
fun getPlaces(): List<Place> {
|
||||||
@@ -103,6 +108,7 @@ class NavigationRepository {
|
|||||||
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de");
|
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de");
|
||||||
httpURLConnection.requestMethod = "GET"
|
httpURLConnection.requestMethod = "GET"
|
||||||
val responseCode = httpURLConnection.responseCode
|
val responseCode = httpURLConnection.responseCode
|
||||||
|
println(responseCode)
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
val response = httpURLConnection.inputStream.bufferedReader()
|
val response = httpURLConnection.inputStream.bufferedReader()
|
||||||
.use { it.readText() } // defaults to UTF-8
|
.use { it.readText() } // defaults to UTF-8
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ data class SearchResult(
|
|||||||
@SerializedName("osm_type") var osmType: String = "",
|
@SerializedName("osm_type") var osmType: String = "",
|
||||||
@SerializedName("osm_id") var osmId: Long = 0,
|
@SerializedName("osm_id") var osmId: Long = 0,
|
||||||
@SerializedName("lat") var lat: String = "",
|
@SerializedName("lat") var lat: String = "",
|
||||||
@SerializedName("lon") var lon: String = "",
|
|
||||||
@SerializedName("category") var category: String = "",
|
@SerializedName("category") var category: String = "",
|
||||||
|
@SerializedName("lon") var lon: String = "",
|
||||||
@SerializedName("type") var type: String = "",
|
@SerializedName("type") var type: String = "",
|
||||||
@SerializedName("place_rank") var placeRank: Int = 0,
|
@SerializedName("place_rank") var placeRank: Int = 0,
|
||||||
@SerializedName("importance") var importance: Double = 0.0,
|
@SerializedName("importance") var importance: Double = 0.0,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.kouros.navigation.model
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Geocoder
|
import android.location.Geocoder
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -32,6 +34,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
MutableLiveData<String>()
|
MutableLiveData<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val recentPlace: MutableLiveData<Place> by lazy {
|
||||||
|
MutableLiveData<Place>()
|
||||||
|
}
|
||||||
val places: MutableLiveData<List<Place>> by lazy {
|
val places: MutableLiveData<List<Place>> by lazy {
|
||||||
MutableLiveData<List<Place>>()
|
MutableLiveData<List<Place>>()
|
||||||
}
|
}
|
||||||
@@ -45,6 +50,31 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun loadRecentPlace(location: Location) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val placeBox = boxStore.boxFor(Place::class)
|
||||||
|
val query = placeBox
|
||||||
|
.query(Place_.name.notEqual(""))
|
||||||
|
.orderDesc(Place_.lastDate)
|
||||||
|
.build()
|
||||||
|
val results = query.find()
|
||||||
|
query.close()
|
||||||
|
for (place in results) {
|
||||||
|
val plLocation = location(place.latitude, place.longitude)
|
||||||
|
// val distance = repository.getRouteDistance(location, plLocation)
|
||||||
|
//place.distance = distance.toFloat()
|
||||||
|
if (place.distance == 0F) {
|
||||||
|
recentPlace.postValue(place)
|
||||||
|
println("RecentPlace $recentPlace")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fun loadPlaces(location: Location) {
|
fun loadPlaces(location: Location) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
@@ -59,7 +89,6 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
val plLocation = location(place.latitude, place.longitude)
|
val plLocation = location(place.latitude, place.longitude)
|
||||||
val distance = repository.getRouteDistance(location, plLocation)
|
val distance = repository.getRouteDistance(location, plLocation)
|
||||||
place.distance = distance.toFloat()
|
place.distance = distance.toFloat()
|
||||||
println(place.lastDate)
|
|
||||||
}
|
}
|
||||||
places.postValue(results)
|
places.postValue(results)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -148,6 +177,15 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reverseAddress(location: Location ): String {
|
||||||
|
val address = repository.reverseAddress(location)
|
||||||
|
println(address)
|
||||||
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
|
val place = gson.fromJson(address, SearchResult::class.java)
|
||||||
|
println(place.address.road)
|
||||||
|
return place.address.road
|
||||||
|
}
|
||||||
|
|
||||||
fun saveRecent(place: Place) {
|
fun saveRecent(place: Place) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
place.category = Constants.RECENT
|
place.category = Constants.RECENT
|
||||||
|
|||||||
Reference in New Issue
Block a user