Amenities

This commit is contained in:
Dimitris
2025-12-17 09:31:12 +01:00
parent d546ede0e5
commit ed24e71473
21 changed files with 284 additions and 246 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId = "com.kouros.navigation"
minSdk = 33
targetSdk = 36
versionCode = 9
versionName = "0.1.3.9"
versionCode = 10
versionName = "0.1.3.10"
base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -72,23 +72,17 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() {
val routeData = MutableLiveData("")
val viewModel = ViewModel(NavigationRepository())
val routeModel = RouteModel()
var tilt = 50.0
val useMock = true
val stepData: MutableLiveData<StepData> by lazy {
MutableLiveData<StepData>()
}
val nextStepData: MutableLiveData<StepData> by lazy {
MutableLiveData<StepData>()
}
var lastLocation = location(0.0, 0.0)
val observer = Observer<String> { newRoute ->
if (newRoute.isNotEmpty()) {
routeModel.startNavigation(newRoute)
@@ -188,7 +182,7 @@ class MainActivity : ComponentActivity() {
sheetPeekHeightState.value = 128.dp
}
}
NavigationTheme() {
NavigationTheme {
BottomSheetScaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)

View File

@@ -14,6 +14,7 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -28,16 +29,34 @@ import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import androidx.window.layout.WindowMetricsCalculator
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.map.BuildingLayer
import com.kouros.navigation.car.map.DarkMode
import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.NavigationImage
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.StepData
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.expressions.dsl.exponential
import org.maplibre.compose.expressions.dsl.interpolate
import org.maplibre.compose.expressions.dsl.zoom
import org.maplibre.compose.layers.CircleLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationTrackingEffect
import org.maplibre.compose.location.UserLocationState
import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.map.OrnamentOptions
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.GeoJsonSource
import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
@@ -77,8 +96,8 @@ fun MapView(
Column {
NavigationInfo(step)
Box(contentAlignment = Alignment.Center) {
MapLibre(applicationContext, cameraState, baseStyle, route, "", position)
LocationTrackingEffect(
MapLibre(applicationContext, cameraState, baseStyle, route, ViewStyle.VIEW)
LocationTrackingEffect(
locationState = userLocationState,
) {
cameraState.animateTo(
@@ -97,3 +116,5 @@ fun MapView(
}
}

View File

@@ -75,7 +75,6 @@ fun SearchSheet(
}
}
if (recentPlaces.value != null) {
println("Recent Places ${recentPlaces.value}")
val textFieldState = rememberTextFieldState()
val items = listOf(recentPlaces)
if (items.isNotEmpty()) {
@@ -108,7 +107,7 @@ fun Home(
closeSheet()
}) {
Icon(
painter = painterResource(id = com.google.android.gms.base.R.drawable.common_full_open_on_phone),
painter = painterResource(id = R.drawable.ic_place_white_24dp),
"Home",
modifier = Modifier.size(24.dp, 24.dp),
)
@@ -222,7 +221,6 @@ private fun SearchPlaces(
street = place.address.road
)
viewModel.saveRecent(pl)
println("Save $pl")
val toLocation =
location(place.lon.toDouble(), place.lat.toDouble())
viewModel.loadRoute(context, location, toLocation)

View File

@@ -21,7 +21,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -63,7 +62,6 @@ class SurfaceRenderer(
private var visibleArea = MutableLiveData(
Rect(0, 0, 0, 0)
)
var stableArea = Rect()
var width = 0
var height = 0
@@ -71,11 +69,9 @@ class SurfaceRenderer(
val routeData = MutableLiveData("")
val speed = MutableLiveData(0F)
lateinit var centerLocation: Location
var preview = false
var viewStyle = ViewStyle.VIEW
var previewDistance = 0.0
val previewRouteData = MutableLiveData("")
lateinit var mapView: ComposeView
var panView = false
var tilt = 55.0
var countDownTimerActive = false
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
@@ -171,18 +167,16 @@ class SurfaceRenderer(
@Composable
fun MapView() {
val stateWidth = visibleArea.observeAsState()
val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState()
val paddingValues = getPaddingValues(width - stateWidth.value!!.width(), height, preview)
val paddingValues = getPaddingValues(height, viewStyle)
val cameraState = cameraState(paddingValues, position, tilt)
val baseStyle = remember {
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
}
DarkMode(carContext, baseStyle)
MapLibre(carContext, cameraState, baseStyle, route, previewRoute, position)
MapLibre(carContext, cameraState, baseStyle, route, viewStyle)
ShowPosition(cameraState, position, paddingValues)
}
@@ -192,25 +186,32 @@ class SurfaceRenderer(
position: CameraPosition?,
paddingValues: PaddingValues
) {
val cameraDuration = duration(preview, position!!.bearing, lastBearing)
val cameraDuration =
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
var bearing = position.bearing
var zoom = position.zoom
var target = position.target
var localTilt = tilt
val currentSpeed: Float? by speed.observeAsState()
if (!preview) {
if (routeModel.isNavigating()) {
DrawImage(paddingValues, currentSpeed, width, height)
} else {
when (viewStyle) {
ViewStyle.VIEW -> {
DrawImage(paddingValues, currentSpeed, width, height)
}
} else {
bearing = 0.0
zoom = previewZoom(previewDistance)
target = Position(centerLocation.longitude, centerLocation.latitude)
localTilt = 0.0
ViewStyle.PREVIEW -> {
bearing = 0.0
zoom = previewZoom(previewDistance)
target = Position(centerLocation.longitude, centerLocation.latitude)
localTilt = 0.0
}
else -> {
bearing = 0.0
localTilt = 0.0
zoom = 12.0
}
}
LaunchedEffect(position) {
LaunchedEffect(position, viewStyle) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = bearing,
@@ -234,7 +235,7 @@ class SurfaceRenderer(
/** Handles the map zoom-in and zoom-out events. */
fun handleScale(zoomSign: Int) {
synchronized(this) {
panView = true
viewStyle = ViewStyle.PAN_VIEW
val newZoom = if (zoomSign < 0) {
cameraPosition.value!!.zoom - 1.0
} else {
@@ -259,9 +260,9 @@ class SurfaceRenderer(
fun updateLocation(location: Location) {
synchronized(this) {
if (!preview) {
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
val bearing = bearing(lastLocation, location, cameraPosition.value!!.bearing)
val zoom = if (!panView) {
val zoom = if (viewStyle == ViewStyle.VIEW) {
calculateZoom(location.speed.toDouble())
} else {
cameraPosition.value!!.zoom
@@ -280,12 +281,6 @@ class SurfaceRenderer(
val lastLocationTimer = lastLocation
checkUpdate(mainThreadHandler, lastLocationTimer)
}
} else {
updateCameraPosition(
0.0,
previewZoom(previewDistance),
Position(centerLocation.longitude, centerLocation.latitude)
)
}
}
}
@@ -295,7 +290,7 @@ class SurfaceRenderer(
lastLocationTimer: Location
) {
mainThreadHandler.post {
object : CountDownTimer(5000, 1000) {
object : CountDownTimer(3000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
countDownTimerActive = false
@@ -313,7 +308,7 @@ class SurfaceRenderer(
bearing = bearing,
zoom = zoom,
tilt = tilt,
padding = getPaddingValues(width - visibleArea.value!!.width(), height, preview),
padding = getPaddingValues(height, viewStyle),
target = target
)
)
@@ -321,28 +316,41 @@ class SurfaceRenderer(
fun setRouteData() {
routeData.value = routeModel.route.routeGeoJson
previewRouteData.value = ""
preview = false
panView = false
viewStyle = ViewStyle.VIEW
}
fun setPreviewRouteData(routeModel: RouteModel) {
previewRouteData.value = routeModel.route.routeGeoJson
centerLocation = routeModel.route.centerLocation
preview = true
previewDistance = routeModel.route.distance
viewStyle = ViewStyle.PREVIEW
with(routeModel) {
routeData.value = route.routeGeoJson
centerLocation = route.centerLocation
previewDistance = route.distance
}
updateCameraPosition(
0.0,
previewZoom(previewDistance),
Position(centerLocation.longitude, centerLocation.latitude)
)
}
fun setLocation(location: Location) {
fun setCategories(location: Location, route: String) {
viewStyle = ViewStyle.SEARCH_VIEW
routeData.value = route
updateCameraPosition(
0.0,
12.0,
target = Position(location.longitude, location.latitude)
)
}
fun setCategoryLocation(location: Location) {
viewStyle = ViewStyle.SEARCH_VIEW
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = 0.0,
zoom = 15.0,
tilt = 0.0,
padding = PaddingValues(start = 350.dp, bottom = 0.dp),
target = Position(location.longitude, location.latitude)
)
)
}
companion
@@ -352,3 +360,7 @@ class SurfaceRenderer(
}
enum class ViewStyle {
VIEW, PREVIEW, PAN_VIEW, SEARCH_VIEW
}

View File

@@ -9,10 +9,7 @@ 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.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember
@@ -28,6 +25,7 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.kouros.data.R
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationColor
@@ -39,9 +37,15 @@ 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.expressions.dsl.exponential
import org.maplibre.compose.expressions.dsl.image
import org.maplibre.compose.expressions.dsl.interpolate
import org.maplibre.compose.expressions.dsl.zoom
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.CircleLayer
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.layers.SymbolLayer
import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
@@ -54,12 +58,14 @@ import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.FeatureCollection
import org.maplibre.spatialk.geojson.Position
@Composable
fun cameraState(
padding : PaddingValues,
padding: PaddingValues,
position: CameraPosition?,
tilt: Double,
): CameraState {
@@ -83,8 +89,7 @@ fun MapLibre(
cameraState: CameraState,
baseStyle: MutableState<BaseStyle.Uri>,
route: String?,
previewRoute: String?,
position: CameraPosition?
viewStyle: ViewStyle
) {
MaplibreMap(
options = MapOptions(
@@ -98,45 +103,58 @@ fun MapLibre(
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) {
BuildingLayer(tiles)
}
RouteLayer(route, previewRoute, position!!.zoom)
RouteLayer(route, viewStyle)
}
//Puck(cameraState, lastLocation)
}
}
@Composable
fun RouteLayer(routeData: String?, previewRoute: String?, zoom: Double) {
val width = zoom - 2
if (routeData!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
LineLayer(
id = "routes-casing",
source = routes,
color = const(Color.White),
width = const((width+2).dp),
)
LineLayer(
id = "routes",
source = routes,
color = const(RouteColor),
width = const(width.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(RouteColor),
width = const(4.dp),
)
fun RouteLayer(routeData: String?, viewStyle: ViewStyle) {
if (routeData != null && routeData.isNotEmpty()) {
println(routeData)
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
if (viewStyle == ViewStyle.VIEW) {
LineLayer(
id = "routes-casing$viewStyle",
source = routes,
color = const(Color.White),
width =
interpolate(
type = exponential(1.2f),
input = zoom(),
5 to const(0.4.dp),
6 to const(0.8.dp),
7 to const(2.0.dp),
20 to const(24.dp),
),
)
LineLayer(
id = "routes$viewStyle",
source = routes,
color = const(RouteColor),
width =
interpolate(
type = exponential(1.2f),
input = zoom(),
5 to const(0.4.dp),
6 to const(0.7.dp),
7 to const(1.75.dp),
20 to const(22.dp),
),
)
} else {
SymbolLayer(
id = "my-symbol-layer",
source = routes,
// Convert a drawable resource to a MapLibre image
// drawAsSdf = true allows us to tint the image programmatically
iconImage = image(painterResource(R.drawable.ic_place_white_24dp), drawAsSdf = true),
// Now we can apply any color we want!
iconColor = const(Color.Blue),
iconSize = const(1.5f)
)
}
}
}
@@ -154,17 +172,19 @@ fun BuildingLayer(tiles: Source) {
@Composable
fun DrawImage(padding: PaddingValues, speed: Float?, width: Int, height: Int) {
NavigationImage(padding, width,height)
NavigationImage(padding, width, height)
Speed(width, height, speed)
}
@Composable
fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
val imageSize = (height/6)
val imageSize = (height / 6)
val color = remember { NavigationColor }
Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(padding)) {
Canvas(modifier =Modifier
.size(imageSize.dp, imageSize.dp)) {
Canvas(
modifier = Modifier
.size(imageSize.dp, imageSize.dp)
) {
scale(scaleX = 1f, scaleY = 0.7f) {
drawCircle(Color.DarkGray.copy(alpha = 0.2f))
}
@@ -188,8 +208,8 @@ private fun Speed(
Box(
modifier = Modifier
.padding(
start = width.dp- 250.dp,
top = height.dp- 80.dp
start = width.dp - 250.dp,
top = height.dp - 80.dp
),
contentAlignment = Alignment.Center
) {
@@ -259,11 +279,11 @@ fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) {
}
}
fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
return if (preView) {
PaddingValues(start = 150.dp, bottom = 0.dp)
} else {
PaddingValues(start = 50.dp, top = distanceFromTop(height).dp)
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
return when (viewStyle) {
ViewStyle.VIEW -> PaddingValues(start = 50.dp, top = distanceFromTop(height).dp)
ViewStyle.PREVIEW -> PaddingValues(start = 150.dp, bottom = 0.dp)
else -> PaddingValues(start = 450.dp, bottom = 0.dp)
}
}

View File

@@ -4,13 +4,16 @@ import android.location.Location
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants
@@ -21,7 +24,7 @@ class CategoriesScreen(
) : Screen(carContext) {
var categories: List<Category> = listOf(
Category(id = Constants.GAS_STATION, name = carContext.getString(R.string.gas_station)),
Category(id = Constants.FUEL_STATION, name = carContext.getString(R.string.fuel_station)),
Category(id = Constants.PHARMACY, name = carContext.getString(R.string.pharmacy)),
Category(id = Constants.CHARGING_STATION, name = carContext.getString(R.string.charging_station))
)
@@ -29,12 +32,17 @@ class CategoriesScreen(
override fun onGetTemplate(): Template {
val itemListBuilder = ItemList.Builder()
.setNoItemsMessage("No categories to show")
categories.forEach {
it.name
itemListBuilder.addItem(
Row.Builder()
.setTitle(it.name)
.setImage(CarIcon.Builder(
IconCompat.createWithResource(
carContext,
com.kouros.android.cars.carappservice.R.drawable.ev_station_24px
)
)
.build())
.setOnClickListener {
screenManager
.pushForResult(
@@ -56,6 +64,8 @@ class CategoriesScreen(
)
}
surfaceRenderer.viewStyle = ViewStyle.SEARCH_VIEW
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle("title")

View File

@@ -1,6 +1,7 @@
package com.kouros.navigation.car.screen
import android.location.Location
import androidx.annotation.DrawableRes
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
@@ -14,15 +15,19 @@ import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.versioning.CarAppApiLevels
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
import com.kouros.navigation.utils.location
import kotlin.math.min
@@ -38,82 +43,70 @@ class CategoryScreen(
val observer = Observer<List<Elements>> { newElements ->
elements = newElements
invalidate()
val coordinates = mutableListOf<List<Double>>()
val loc = location(0.0, 0.0)
elements.forEach {
if (loc.latitude == 0.0) {
loc.longitude = it.lon!!
loc.latitude = it.lat!!
}
if (coordinates.isEmpty())
coordinates.add(listOf(location.longitude, location.latitude))
}
if (elements.isNotEmpty()) {
val route = createGeoJson("Point", coordinates)
surfaceRenderer.setCategories(loc, route)
invalidate()
}
}
init {
viewModel.elements.observe(this, observer)
viewModel.getAmenities(category, location)
invalidate()
}
override fun onGetTemplate(): Template {
val listBuilder = ItemList.Builder()
if (carContext.getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) {
var index = 0
val listLimit = min(
10,
100,
carContext.getCarService(ConstraintManager::class.java)
.getContentLimit(
ConstraintManager.CONTENT_LIMIT_TYPE_LIST
)
)
elements.forEach {
//listBuilder.addItem(createRow(it.tags!!.operator.toString(), it.tags?.capacity.toString()))
listBuilder.addItem(
Row.Builder()
.setOnClickListener({
val location = location(it.lon!!, it.lat!!)
surfaceRenderer.setLocation(location)
})
.setTitle(secondText(it.tags?.capacity.toString()))
.setImage(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
com.kouros.android.cars.carappservice.R.drawable.ev_station_24px
if (index++ < listLimit) {
listBuilder.addItem(
Row.Builder()
.setOnClickListener {
val location = location(it.lon!!, it.lat!!)
surfaceRenderer.setCategoryLocation(location)
}
.setTitle(it.tags.operator.toString())
.setImage(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
com.kouros.android.cars.carappservice.R.drawable.ev_station_24px
)
)
.build()
)
.build()
)
.addText(it.tags!!.operator.toString())
.addText(secondText(it.tags?.capacity.toString()))
.build()
)
.addText(it.tags.network.toString())
.build()
)
}
}
}
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.charging_station))
.build()
val actionStrip = ActionStrip.Builder()
.addAction(
Action.Builder()
.setOnClickListener {
CarToast.makeText(
carContext,
carContext.getString(
R.string.notification_demo
),
CarToast.LENGTH_SHORT
)
.show()
}
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_place_white_24dp
)
).build()
)
.setFlags(Action.FLAG_IS_PERSISTENT)
.build()
)
.build()
val builder = MapWithContentTemplate.Builder()
.setContentTemplate(
ListTemplate.Builder()
@@ -121,7 +114,11 @@ class CategoryScreen(
.setSingleList(listBuilder.build())
.build()
)
.setActionStrip(actionStrip)
.setMapController(
MapController.Builder().setMapActionStrip(
getMapActionStrip()
).build()
)
return builder.build()
}
@@ -140,4 +137,27 @@ class CategoryScreen(
return secondText
}
fun getMapActionStrip(): ActionStrip {
return ActionStrip.Builder()
.addAction(
createAction(R.drawable.ic_zoom_in_24, 1)
)
.addAction(
createAction(R.drawable.ic_zoom_out_24, -1)
)
.addAction(Action.PAN)
.build()
}
private fun createAction(
@DrawableRes iconRes: Int,
scale: Int
): Action {
val navigationMessage = NavigationMessage(carContext)
return Action.Builder()
.setOnClickListener { surfaceRenderer.handleScale(scale) }
.setIcon(navigationMessage.createCarIcon(iconRes))
.build()
}
}

View File

@@ -28,6 +28,7 @@ import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.NavigationCarAppService
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.NavigationRepository
@@ -237,7 +238,7 @@ class NavigationScreen(
val actionStripBuilder = ActionStrip.Builder()
.addAction(zoomPlus())
.addAction(zoomMinus())
if (surfaceRenderer.panView) {
if (surfaceRenderer.viewStyle == ViewStyle.PAN_VIEW) {
actionStripBuilder
.addAction(
panAction()
@@ -362,7 +363,7 @@ class NavigationScreen(
)
.build()
).setOnClickListener {
surfaceRenderer.panView = false
surfaceRenderer.viewStyle = ViewStyle.VIEW
}
.build()
}
@@ -410,7 +411,7 @@ class NavigationScreen(
fun stopNavigation() {
listener.stopNavigation()
surfaceRenderer.routeData.postValue("")
surfaceRenderer.routeData.value = ""
invalidate()
}

View File

@@ -105,19 +105,21 @@ class PlaceListScreen(
it.street,
avatar = null
)
screenManager
.pushForResult(
RoutePreviewScreen(
carContext,
surfaceRenderer,
place
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
}
}
setResult(place)
finish()
// screenManager
// .pushForResult(
// RoutePreviewScreen(
// carContext,
// surfaceRenderer,
// place
// )
// ) { obj: Any? ->
// if (obj != null) {
// setResult(obj)
// finish()
// }
// }
}
.build()
)

View File

@@ -15,16 +15,12 @@
*/
package com.kouros.navigation.car.screen
import android.graphics.drawable.Icon
import android.location.Location
import android.location.LocationManager
import android.os.CountDownTimer
import android.text.SpannableString
import androidx.annotation.DrawableRes
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_PRIMARY
import androidx.car.app.model.ActionStrip
@@ -47,6 +43,7 @@ import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.location
import java.math.BigDecimal
import java.math.RoundingMode
@@ -57,9 +54,7 @@ class RoutePreviewScreen(
private var destination: Place
) :
Screen(carContext) {
private var mIsFavorite = false
private var mItemLimit = 0
private var isFavorite = false
val vieModel = ViewModel(NavigationRepository())
@@ -76,19 +71,11 @@ class RoutePreviewScreen(
init {
vieModel.previewRoute.observe(this, observer)
val location = Location(LocationManager.GPS_PROVIDER)
location.latitude = destination.latitude
location.longitude = destination.longitude
val location = location(destination.longitude, destination.latitude)
vieModel.loadPreviewRoute(carContext, surfaceRenderer.lastLocation, location)
}
override fun onGetTemplate(): Template {
mItemLimit =
carContext.getCarService(ConstraintManager::class.java)
.getContentLimit(
ConstraintManager.CONTENT_LIMIT_TYPE_ROUTE_LIST
)
val navigateActionIcon: CarIcon = CarIcon.Builder(
IconCompat.createWithResource(
carContext, R.drawable.baseline_assistant_navigation_24
@@ -145,7 +132,7 @@ class RoutePreviewScreen(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
if (mIsFavorite)
if (isFavorite)
R.drawable.ic_favorite_filled_white_24dp
else
R.drawable.ic_favorite_white_24dp
@@ -154,10 +141,10 @@ class RoutePreviewScreen(
.build()
)
.setOnClickListener {
mIsFavorite = !mIsFavorite
isFavorite = !isFavorite
CarToast.makeText(
carContext,
if (mIsFavorite)
if (isFavorite)
carContext
.getString(R.string.favorites)
else
@@ -174,10 +161,10 @@ class RoutePreviewScreen(
private fun deleteFavoriteAction(): Action = Action.Builder()
.setOnClickListener {
if (mIsFavorite) {
if (isFavorite) {
vieModel.deleteFavorite(destination)
}
mIsFavorite = !mIsFavorite
isFavorite = !isFavorite
finish()
}
.setIcon(

View File

@@ -16,6 +16,7 @@ import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository
@@ -76,6 +77,7 @@ class SearchScreen(
location,
)
) { obj: Any? ->
surfaceRenderer.viewStyle = ViewStyle.VIEW
if (obj != null) {
setResult(obj)
finish()

View File

@@ -58,7 +58,6 @@ dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.maplibre.compose)
implementation("hu.supercluster:overpasser:0.2.2")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)

View File

@@ -68,8 +68,9 @@ data class StepData (
// GeoJSON data classes
@Serializable
data class GeoJsonLineString(
data class GeoJsonType(
val type: String,
val coordinates: List<List<Double>>
)
@@ -77,7 +78,7 @@ data class GeoJsonLineString(
@Serializable
data class GeoJsonFeature(
val type: String,
val geometry: GeoJsonLineString
val geometry: GeoJsonType,
)
@Serializable
@@ -156,7 +157,7 @@ object Constants {
const val FAVORITES: String = "Favorites"
const val GAS_STATION: String ="GasStation"
const val FUEL_STATION: String ="fuel"
const val PHARMACY: String ="pharmacy"

View File

@@ -95,7 +95,7 @@ data class Route(
points.add(point)
}
pointLocations = points
routeGeoJson = createGeoJson(waypoints)
routeGeoJson = createGeoJson("LineString", waypoints)
centerLocation = createCenterLocation(routeGeoJson)
return Route(
maneuvers,

View File

@@ -9,6 +9,6 @@ data class Elements (
@SerializedName("id" ) var id : Long? = null,
@SerializedName("lat" ) var lat : Double? = null,
@SerializedName("lon" ) var lon : Double? = null,
@SerializedName("tags" ) var tags : Tags? = Tags()
@SerializedName("tags" ) var tags : Tags = Tags()
)

View File

@@ -1,45 +1,25 @@
package com.kouros.navigation.data
package com.kouros.navigation.data.overpass
import android.location.Location
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.overpass.Amenity
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox2
import com.kouros.navigation.utils.NavigationUtils.getOverpassBbox
import hu.supercluster.overpasser.library.output.OutputFormat
import hu.supercluster.overpasser.library.output.OutputModificator
import hu.supercluster.overpasser.library.output.OutputOrder
import hu.supercluster.overpasser.library.output.OutputVerbosity
import hu.supercluster.overpasser.library.query.OverpassQuery
import com.kouros.navigation.utils.NavigationUtils
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
class Overpass {
val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
fun getAmenities(category: String, location: Location) : List<Elements> {
val boundingBox = getOverpassBbox(location, 2.0)
val bb = getBoundingBox2(location, 2.0)
val url = "https://overpass.kumi.systems/api/interpreter"
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
val boundingBox = NavigationUtils.getOverpassBbox(location, 2.0)
val bb = NavigationUtils.getBoundingBox2(location, 2.0)
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
"Accept",
"application/json"
)
httpURLConnection.setDoOutput(true);
val query = OverpassQuery()
.format(OutputFormat.JSON)
.timeout(30)
.filterQuery()
.node()
.amenity("charging_station")
.tagNot("access", "private")
.boundingBox(bb.southernLat, bb.westernLon, bb.northerLat, bb.easternLon)
.end()
.output(OutputVerbosity.BODY, OutputModificator.CENTER, OutputOrder.QT, 100)
.build()
// define a query
val test = """
|[out:json];
@@ -56,12 +36,9 @@ class Overpass {
outputStreamWriter.flush()
// Check if the connection is successful
val responseCode = httpURLConnection.responseCode
println("Overpass: $responseCode")
if (responseCode == HttpURLConnection.HTTP_OK) {
val response = httpURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8
val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java)
println("Overpass: $response")

View File

@@ -17,6 +17,6 @@ data class Tags (
@SerializedName("operator:wikipedia" ) var operatorWikipedia : String? = null,
@SerializedName("ref" ) var ref : String? = null,
@SerializedName("socket:type2" ) var socketType2 : String? = null,
@SerializedName("socket:type2:output" ) var socketType2Ooutput : String? = null
@SerializedName("socket:type2:output" ) var socketType2Output : String? = null
)

View File

@@ -52,7 +52,6 @@ open class RouteModel() {
this.routeState = routeState.copy(
route = null,
isNavigating = false,
// destination = Place(),
arrived = false,
maneuverType = 0,
currentShapeIndex = 0,

View File

@@ -13,7 +13,7 @@ import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Locations
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.Overpass
import com.kouros.navigation.data.overpass.Overpass
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_
import com.kouros.navigation.data.SearchFilter
@@ -118,7 +118,6 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
.orderDesc(Place_.lastDate)
.build()
val results = query.find()
println("Favorites $results")
query.close()
for (place in results) {
val plLocation = location(place.longitude, place.latitude)
@@ -233,7 +232,6 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val address = repository.reverseAddress(location)
val gson = GsonBuilder().serializeNulls().create()
val place = gson.fromJson(address, SearchResult::class.java)
println(place.address.road)
return place.address.road
}
@@ -271,7 +269,6 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val current = LocalDateTime.now(ZoneOffset.UTC)
place.lastDate = current.atZone(ZoneOffset.UTC).toEpochSecond()
placeBox.put(place)
println("Save Place $place")
} catch (e: Exception) {
e.printStackTrace()
}

View File

@@ -8,7 +8,7 @@ import com.kouros.navigation.data.BoundingBox
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.GeoJsonFeature
import com.kouros.navigation.data.GeoJsonFeatureCollection
import com.kouros.navigation.data.GeoJsonLineString
import com.kouros.navigation.data.GeoJsonType
import kotlinx.serialization.json.Json
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
@@ -23,12 +23,9 @@ import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import kotlin.math.absoluteValue
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sin
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@@ -149,9 +146,8 @@ object NavigationUtils {
// 4. Create and return the Location object.
return location(centerPoint.longitude(), centerPoint.latitude())
}
fun createGeoJson(lineCoordinates: List<List<Double>>): String {
val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates)
fun createGeoJson(type : String, lineCoordinates: List<List<Double>>): String {
val lineString = GeoJsonType(type = type, coordinates = lineCoordinates)
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
val featureCollection =
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
@@ -159,6 +155,8 @@ object NavigationUtils {
return jsonString
}
fun getOverpassBbox(location: Location, radius: Double): String {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")