diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0df094a..78b8d7d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" } diff --git a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt index 428e323..3882fc5 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt @@ -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 by lazy { MutableLiveData() } - val nextStepData: MutableLiveData by lazy { MutableLiveData() } - var lastLocation = location(0.0, 0.0) - val observer = Observer { 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) diff --git a/app/src/main/java/com/kouros/navigation/ui/MapView.kt b/app/src/main/java/com/kouros/navigation/ui/MapView.kt index 325e64c..3f283b0 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MapView.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MapView.kt @@ -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( } } + + diff --git a/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt b/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt index ef8f912..2cfa951 100644 --- a/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt +++ b/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt @@ -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) diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index ea77adc..b52c9ff 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -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 + +} \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt index c1a156c..9d1c431 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt @@ -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, 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) { } } -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) } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt index 0f4a5ef..0397564 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt @@ -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 = 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") diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt index 7f5225d..8d06906 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt @@ -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> { newElements -> elements = newElements - invalidate() + val coordinates = mutableListOf>() + 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() + } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index ced89eb..95aa63c 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -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() } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt index 7d29022..24447a0 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt @@ -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() ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index 9d6a6fe..576d7bb 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -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( diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt index e5b4ad9..d1f6544 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt @@ -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() diff --git a/common/data/build.gradle.kts b/common/data/build.gradle.kts index 420b673..d7c493e 100644 --- a/common/data/build.gradle.kts +++ b/common/data/build.gradle.kts @@ -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) diff --git a/common/data/src/main/java/com/kouros/navigation/data/Data.kt b/common/data/src/main/java/com/kouros/navigation/data/Data.kt index c7cddee..2baf84f 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Data.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Data.kt @@ -68,8 +68,9 @@ data class StepData ( // GeoJSON data classes + @Serializable -data class GeoJsonLineString( +data class GeoJsonType( val type: String, val coordinates: List> ) @@ -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" diff --git a/common/data/src/main/java/com/kouros/navigation/data/Route.kt b/common/data/src/main/java/com/kouros/navigation/data/Route.kt index fb1681b..1958932 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Route.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Route.kt @@ -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, diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt index 7b3dad2..3cb354b 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt @@ -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() ) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/Overpass.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt similarity index 51% rename from common/data/src/main/java/com/kouros/navigation/data/Overpass.kt rename to common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt index e99fd73..4f73aa8 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Overpass.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt @@ -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 { - 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") diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt index 93c3f4b..7ae9104 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt @@ -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 ) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index 04e1e27..79069f4 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -52,7 +52,6 @@ open class RouteModel() { this.routeState = routeState.copy( route = null, isNavigating = false, - // destination = Place(), arrived = false, maneuverType = 0, currentShapeIndex = 0, diff --git a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt index b00c212..61dadbd 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt @@ -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() } diff --git a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt index 83f0ad0..6f444f8 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt @@ -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>): String { - - val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates) + fun createGeoJson(type : String, lineCoordinates: List>): 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")