Loading cardinal-android/app/src/main/java/earth/maps/cardinal/data/ColorUtils.kt +64 −3 Original line number Diff line number Diff line Loading @@ -20,14 +20,34 @@ package earth.maps.cardinal.data import androidx.compose.ui.graphics.Color fun Color.isYellow(): Boolean { /** * Desaturates a color by reducing its saturation by the specified amount. * * @param amount The amount to desaturate, where 0.0f means no change and 1.0f means completely grayscale. * Values outside 0.0f..1.0f will be clamped to this range. * @return A new Color with reduced saturation. */ fun Color.desaturate(amount: Float): Color { val clampedAmount = amount.coerceIn(0f, 1f) val r = red val g = green val b = blue val max = maxOf(r, g, b) val min = minOf(r, g, b) if (max == min) return false // gray // If the color is already grayscale, no desaturation needed if (max == min) { return this } val delta = max - min val l = (max + min) / 2f // Calculate saturation val s = if (l <= 0.5f) delta / (max + min) else delta / (2f - max - min) // Calculate hue val h = when (max) { r -> 60 * (g - b) / delta g -> 60 * (2 + (b - r) / delta) Loading @@ -35,6 +55,47 @@ fun Color.isYellow(): Boolean { else -> 0f } val hue = if (h < 0) h + 360f else h return hue in 30f..75f // Apply desaturation val newSaturation = s * (1f - clampedAmount) // Convert back to RGB return hslToRgb(hue, newSaturation, l, alpha) } /** * Converts HSL color values to RGB. * * @param h Hue in degrees (0-360) * @param s Saturation (0-1) * @param l Lightness (0-1) * @param a Alpha (0-1) * @return Color in RGB space */ private fun hslToRgb(h: Float, s: Float, l: Float, a: Float = 1f): Color { if (s == 0f) { // Grayscale return Color(l, l, l, a) } val c = (1f - kotlin.math.abs(2f * l - 1f)) * s val hPrime = h / 60f val x = c * (1f - kotlin.math.abs(hPrime % 2f - 1f)) val m = l - c / 2f val (rPrime, gPrime, bPrime) = when { hPrime < 1f -> Triple(c, x, 0f) hPrime < 2f -> Triple(x, c, 0f) hPrime < 3f -> Triple(0f, c, x) hPrime < 4f -> Triple(0f, x, c) hPrime < 5f -> Triple(x, 0f, c) else -> Triple(c, 0f, x) } return Color( red = rPrime + m, green = gPrime + m, blue = bPrime + m, alpha = a ) } cardinal-android/app/src/main/java/earth/maps/cardinal/data/RouteStateRepository.kt +9 −4 Original line number Diff line number Diff line Loading @@ -25,9 +25,10 @@ import kotlinx.coroutines.flow.asStateFlow import uniffi.ferrostar.Route data class RouteState( val route: Route? = null, val routes: List<Route> = emptyList(), val isLoading: Boolean = false, val error: String? = null val error: String? = null, val selectedRouteIndex: Int? = null, ) class RouteStateRepository { Loading @@ -38,8 +39,12 @@ class RouteStateRepository { _routeState.value = _routeState.value.copy(isLoading = isLoading) } fun setRoute(route: Route?) { _routeState.value = _routeState.value.copy(route = route, isLoading = false, error = null) fun setRoutes(routes: List<Route>) { _routeState.value = _routeState.value.copy(routes = routes, isLoading = false, error = null) } fun selectRoute(index: Int) { _routeState.value = _routeState.value.copy(selectedRouteIndex = index) } fun setError(error: String?) { Loading cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt +5 −1 Original line number Diff line number Diff line Loading @@ -226,6 +226,7 @@ fun AppContent( appPreferences = appPreferenceRepository, selectedOfflineArea = state.selectedOfflineArea, currentRoute = state.currentRoute, allRoutes = state.allRoutes, currentTransitItinerary = state.currentTransitItinerary ) } else { Loading Loading @@ -809,7 +810,10 @@ private fun DirectionsRoute( cameraState = state.cameraState, appPreferences = appPreferenceRepository, padding = polylinePadding, onRouteUpdate = { route -> state.currentRoute = route }) onRouteUpdate = { route, allRoutes -> state.currentRoute = route state.allRoutes = allRoutes }) DisposableEffect(key1 = Unit) { onDispose { state.currentRoute = null Loading cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContentState.kt +2 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ class AppContentState( selectedOfflineArea: OfflineArea? = null, showToolbar: Boolean = true, currentRoute: Route? = null, allRoutes: List<Route> = emptyList(), currentTransitItinerary: Itinerary? = null, screenHeightDp: Dp = 0.dp, screenWidthDp: Dp = 0.dp, Loading @@ -58,6 +59,7 @@ class AppContentState( var selectedOfflineArea by mutableStateOf(selectedOfflineArea) var showToolbar by mutableStateOf(showToolbar) var currentRoute by mutableStateOf(currentRoute) var allRoutes by mutableStateOf(allRoutes) var currentTransitItinerary by mutableStateOf(currentTransitItinerary) var screenHeightDp by mutableStateOf(screenHeightDp) var screenWidthDp by mutableStateOf(screenWidthDp) Loading cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapView.kt +120 −22 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package earth.maps.cardinal.ui.core import android.content.Context import android.util.Log import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column Loading @@ -35,6 +36,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading @@ -45,6 +47,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em import androidx.compose.ui.util.fastSumBy import androidx.compose.ui.zIndex import androidx.core.graphics.toColorInt import earth.maps.cardinal.R Loading @@ -55,6 +58,8 @@ import earth.maps.cardinal.data.AppPreferenceRepository import earth.maps.cardinal.data.LatLng import earth.maps.cardinal.data.Place import earth.maps.cardinal.data.PolylineUtils import earth.maps.cardinal.data.desaturate import earth.maps.cardinal.data.formatDuration import earth.maps.cardinal.data.room.OfflineArea import earth.maps.cardinal.transit.Itinerary import earth.maps.cardinal.transit.Mode Loading @@ -62,6 +67,7 @@ import earth.maps.cardinal.ui.map.LocationPuck import io.github.dellisd.spatialk.geojson.Feature import io.github.dellisd.spatialk.geojson.FeatureCollection import io.github.dellisd.spatialk.geojson.LineString import io.github.dellisd.spatialk.geojson.Point import io.github.dellisd.spatialk.geojson.Polygon import io.github.dellisd.spatialk.geojson.Position import kotlinx.coroutines.launch Loading Loading @@ -105,6 +111,7 @@ fun MapView( appPreferences: AppPreferenceRepository, selectedOfflineArea: OfflineArea? = null, currentRoute: Route? = null, allRoutes: List<Route>, currentTransitItinerary: Itinerary? = null, ) { val context = LocalContext.current Loading Loading @@ -150,8 +157,7 @@ fun MapView( padding = fabInsets, isAttributionEnabled = true, attributionAlignment = Alignment.BottomStart ), renderOptions = RenderOptions() ), renderOptions = RenderOptions() ), onMapClick = { position, dpOffset -> mapViewModel.handleMapTap( Loading @@ -172,7 +178,7 @@ fun MapView( OfflineBoundsLayer(selectedOfflineArea) RouteLayer(currentRoute) RouteLayer(mapViewModel, currentRoute, allRoutes) TransitLayer(currentTransitItinerary) Loading Loading @@ -250,20 +256,69 @@ private fun OfflineBoundsLayer(selectedOfflineArea: OfflineArea?) { val color = MaterialTheme.colorScheme.onSurface LineLayer( id = "offline_download_bounds", source = offlineDownloadBoundsSource, color = rgbColor( id = "offline_download_bounds", source = offlineDownloadBoundsSource, color = rgbColor( const((color.red * 255).toInt()), const((color.green * 255).toInt()), const((color.blue * 255).toInt()) ), width = const(3.dp) ), width = const(3.dp) ) } } @Composable private fun RouteLayer(currentRoute: Route?) { private fun RouteLayer(viewModel: MapViewModel, currentRoute: Route?, allRoutes: List<Route>) { val annotations = remember(allRoutes) { viewModel.placeRouteAnnotations(allRoutes) } Log.d("annotations", "${annotations.values}") allRoutes.reversed().forEachIndexed { index, route -> if (route == currentRoute) { return@forEachIndexed } val routePositions = route.geometry.map { coord -> Position(coord.lng, coord.lat) // [longitude, latitude] } val routeLineString = LineString(routePositions) val routeFeature = Feature(geometry = routeLineString) val routeSource = rememberGeoJsonSource( GeoJsonData.Features(FeatureCollection(features = listOf(routeFeature))) ) val desaturateAmount = 0.8f val polylineColor = colorResource(R.color.polyline_color).desaturate(desaturateAmount) val polylineCasingColor = colorResource(R.color.polyline_casing_color).desaturate(desaturateAmount) LineLayer( id = "route_line_casing_$index", source = routeSource, color = rgbColor( const((polylineCasingColor.red * 255.0).toInt()), // Blue color const((polylineCasingColor.green * 255.0).toInt()), const((polylineCasingColor.blue * 255.0).toInt()) ), width = const(9.dp), opacity = const(1f), cap = const(LineCap.Round), join = const(LineJoin.Round), ) LineLayer( id = "route_line_$index", source = routeSource, color = rgbColor( const((polylineColor.red * 255.0).toInt()), // Blue color const((polylineColor.green * 255.0).toInt()), const((polylineColor.blue * 255.0).toInt()) ), width = const(6.dp), opacity = const(1f), cap = const(LineCap.Round), join = const(LineJoin.Round), ) annotations[route]?.let { annotationLatLng -> RouteAnnotation(annotationLatLng, index, route) } } currentRoute?.let { route -> val routePositions = route.geometry.map { coord -> Position(coord.lng, coord.lat) // [longitude, latitude] Loading @@ -275,23 +330,22 @@ private fun RouteLayer(currentRoute: Route?) { ) val polylineColor = colorResource(R.color.polyline_color) val polylineCasingColor = colorResource(R.color.polyline_casing_color) val polylineCasingColor = colorResource(R.color.polyline_casing_color) LineLayer( id = "route_line_casing", source = routeSource, id = "current_route_line_casing", source = routeSource, color = rgbColor( const((polylineCasingColor.red * 255.0).toInt()), // Blue color const((polylineCasingColor.green * 255.0).toInt()), const((polylineCasingColor.blue * 255.0).toInt()) ), width = const(8.dp), width = const(9.dp), opacity = const(1f), cap = const(LineCap.Round), join = const(LineJoin.Round), ) LineLayer( id = "route_line", source = routeSource, id = "current_route_line", source = routeSource, color = rgbColor( const((polylineColor.red * 255.0).toInt()), // Blue color const((polylineColor.green * 255.0).toInt()), Loading @@ -302,8 +356,53 @@ private fun RouteLayer(currentRoute: Route?) { cap = const(LineCap.Round), join = const(LineJoin.Round), ) annotations[route]?.let { annotationLatLng -> RouteAnnotation(annotationLatLng, index = null, route) } } } @Composable private fun RouteAnnotation( annotationLatLng: LatLng, index: Int?, route: Route ) { val index = index?.toString() ?: "selected" val annotationFeature = Feature( geometry = Point( coordinates = Position( longitude = annotationLatLng.longitude, latitude = annotationLatLng.latitude ) ) ) val annotationSource = rememberGeoJsonSource( GeoJsonData.Features(FeatureCollection(features = listOf(annotationFeature))) ) val textHaloColor = MaterialTheme.colorScheme.surface val textColor = MaterialTheme.colorScheme.onSurface SymbolLayer( id = "route_annotation_$index", source = annotationSource, textField = const(formatDuration(route.steps.sumOf { it.duration }.toInt())), textAnchor = const(SymbolAnchor.Bottom), textColor = rgbColor( const((textColor.red * 255.0).toInt()), // Blue color const((textColor.green * 255.0).toInt()), const((textColor.blue * 255.0).toInt()) ), textFont = const(listOf("Roboto Medium")), textHaloColor = rgbColor( const((textHaloColor.red * 255.0).toInt()), // Blue color const((textHaloColor.green * 255.0).toInt()), const((textHaloColor.blue * 255.0).toInt()) ), textHaloBlur = const(1.dp), textHaloWidth = const(2.dp) ) } @Composable private fun TransitLayer(currentTransitItinerary: Itinerary?) { Loading @@ -311,8 +410,7 @@ private fun TransitLayer(currentTransitItinerary: Itinerary?) { itinerary.legs.forEachIndexed { legIndex, leg -> leg.legGeometry?.let { geometry -> val positions = PolylineUtils.decodePolyline( encoded = geometry.points, precision = geometry.precision encoded = geometry.points, precision = geometry.precision ) if (positions.isNotEmpty()) { val lineString = LineString(positions) Loading Loading @@ -447,8 +545,7 @@ private fun MapControls( cameraState.animateTo( cameraState.position.copy( zoom = min( 22.0, cameraState.position.zoom + 1 22.0, cameraState.position.zoom + 1 ) ), duration = appPreferences.animationSpeedDurationValue, Loading @@ -467,13 +564,13 @@ private fun MapControls( FloatingActionButton( modifier = Modifier .align(Alignment.CenterHorizontally) .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { coroutineScope.launch { cameraState.animateTo( cameraState.position.copy( zoom = max( 0.0, cameraState.position.zoom - 1 0.0, cameraState.position.zoom - 1 ) ), duration = appPreferences.animationSpeedDurationValue / 2, Loading @@ -492,7 +589,8 @@ private fun MapControls( FloatingActionButton( modifier = Modifier .align(Alignment.CenterHorizontally) .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { // Request location permissions if we don't have them if (!hasLocationPermission) { mapViewModel.markLocationRequestPending() Loading Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/data/ColorUtils.kt +64 −3 Original line number Diff line number Diff line Loading @@ -20,14 +20,34 @@ package earth.maps.cardinal.data import androidx.compose.ui.graphics.Color fun Color.isYellow(): Boolean { /** * Desaturates a color by reducing its saturation by the specified amount. * * @param amount The amount to desaturate, where 0.0f means no change and 1.0f means completely grayscale. * Values outside 0.0f..1.0f will be clamped to this range. * @return A new Color with reduced saturation. */ fun Color.desaturate(amount: Float): Color { val clampedAmount = amount.coerceIn(0f, 1f) val r = red val g = green val b = blue val max = maxOf(r, g, b) val min = minOf(r, g, b) if (max == min) return false // gray // If the color is already grayscale, no desaturation needed if (max == min) { return this } val delta = max - min val l = (max + min) / 2f // Calculate saturation val s = if (l <= 0.5f) delta / (max + min) else delta / (2f - max - min) // Calculate hue val h = when (max) { r -> 60 * (g - b) / delta g -> 60 * (2 + (b - r) / delta) Loading @@ -35,6 +55,47 @@ fun Color.isYellow(): Boolean { else -> 0f } val hue = if (h < 0) h + 360f else h return hue in 30f..75f // Apply desaturation val newSaturation = s * (1f - clampedAmount) // Convert back to RGB return hslToRgb(hue, newSaturation, l, alpha) } /** * Converts HSL color values to RGB. * * @param h Hue in degrees (0-360) * @param s Saturation (0-1) * @param l Lightness (0-1) * @param a Alpha (0-1) * @return Color in RGB space */ private fun hslToRgb(h: Float, s: Float, l: Float, a: Float = 1f): Color { if (s == 0f) { // Grayscale return Color(l, l, l, a) } val c = (1f - kotlin.math.abs(2f * l - 1f)) * s val hPrime = h / 60f val x = c * (1f - kotlin.math.abs(hPrime % 2f - 1f)) val m = l - c / 2f val (rPrime, gPrime, bPrime) = when { hPrime < 1f -> Triple(c, x, 0f) hPrime < 2f -> Triple(x, c, 0f) hPrime < 3f -> Triple(0f, c, x) hPrime < 4f -> Triple(0f, x, c) hPrime < 5f -> Triple(x, 0f, c) else -> Triple(c, 0f, x) } return Color( red = rPrime + m, green = gPrime + m, blue = bPrime + m, alpha = a ) }
cardinal-android/app/src/main/java/earth/maps/cardinal/data/RouteStateRepository.kt +9 −4 Original line number Diff line number Diff line Loading @@ -25,9 +25,10 @@ import kotlinx.coroutines.flow.asStateFlow import uniffi.ferrostar.Route data class RouteState( val route: Route? = null, val routes: List<Route> = emptyList(), val isLoading: Boolean = false, val error: String? = null val error: String? = null, val selectedRouteIndex: Int? = null, ) class RouteStateRepository { Loading @@ -38,8 +39,12 @@ class RouteStateRepository { _routeState.value = _routeState.value.copy(isLoading = isLoading) } fun setRoute(route: Route?) { _routeState.value = _routeState.value.copy(route = route, isLoading = false, error = null) fun setRoutes(routes: List<Route>) { _routeState.value = _routeState.value.copy(routes = routes, isLoading = false, error = null) } fun selectRoute(index: Int) { _routeState.value = _routeState.value.copy(selectedRouteIndex = index) } fun setError(error: String?) { Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt +5 −1 Original line number Diff line number Diff line Loading @@ -226,6 +226,7 @@ fun AppContent( appPreferences = appPreferenceRepository, selectedOfflineArea = state.selectedOfflineArea, currentRoute = state.currentRoute, allRoutes = state.allRoutes, currentTransitItinerary = state.currentTransitItinerary ) } else { Loading Loading @@ -809,7 +810,10 @@ private fun DirectionsRoute( cameraState = state.cameraState, appPreferences = appPreferenceRepository, padding = polylinePadding, onRouteUpdate = { route -> state.currentRoute = route }) onRouteUpdate = { route, allRoutes -> state.currentRoute = route state.allRoutes = allRoutes }) DisposableEffect(key1 = Unit) { onDispose { state.currentRoute = null Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContentState.kt +2 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ class AppContentState( selectedOfflineArea: OfflineArea? = null, showToolbar: Boolean = true, currentRoute: Route? = null, allRoutes: List<Route> = emptyList(), currentTransitItinerary: Itinerary? = null, screenHeightDp: Dp = 0.dp, screenWidthDp: Dp = 0.dp, Loading @@ -58,6 +59,7 @@ class AppContentState( var selectedOfflineArea by mutableStateOf(selectedOfflineArea) var showToolbar by mutableStateOf(showToolbar) var currentRoute by mutableStateOf(currentRoute) var allRoutes by mutableStateOf(allRoutes) var currentTransitItinerary by mutableStateOf(currentTransitItinerary) var screenHeightDp by mutableStateOf(screenHeightDp) var screenWidthDp by mutableStateOf(screenWidthDp) Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapView.kt +120 −22 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package earth.maps.cardinal.ui.core import android.content.Context import android.util.Log import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column Loading @@ -35,6 +36,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading @@ -45,6 +47,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em import androidx.compose.ui.util.fastSumBy import androidx.compose.ui.zIndex import androidx.core.graphics.toColorInt import earth.maps.cardinal.R Loading @@ -55,6 +58,8 @@ import earth.maps.cardinal.data.AppPreferenceRepository import earth.maps.cardinal.data.LatLng import earth.maps.cardinal.data.Place import earth.maps.cardinal.data.PolylineUtils import earth.maps.cardinal.data.desaturate import earth.maps.cardinal.data.formatDuration import earth.maps.cardinal.data.room.OfflineArea import earth.maps.cardinal.transit.Itinerary import earth.maps.cardinal.transit.Mode Loading @@ -62,6 +67,7 @@ import earth.maps.cardinal.ui.map.LocationPuck import io.github.dellisd.spatialk.geojson.Feature import io.github.dellisd.spatialk.geojson.FeatureCollection import io.github.dellisd.spatialk.geojson.LineString import io.github.dellisd.spatialk.geojson.Point import io.github.dellisd.spatialk.geojson.Polygon import io.github.dellisd.spatialk.geojson.Position import kotlinx.coroutines.launch Loading Loading @@ -105,6 +111,7 @@ fun MapView( appPreferences: AppPreferenceRepository, selectedOfflineArea: OfflineArea? = null, currentRoute: Route? = null, allRoutes: List<Route>, currentTransitItinerary: Itinerary? = null, ) { val context = LocalContext.current Loading Loading @@ -150,8 +157,7 @@ fun MapView( padding = fabInsets, isAttributionEnabled = true, attributionAlignment = Alignment.BottomStart ), renderOptions = RenderOptions() ), renderOptions = RenderOptions() ), onMapClick = { position, dpOffset -> mapViewModel.handleMapTap( Loading @@ -172,7 +178,7 @@ fun MapView( OfflineBoundsLayer(selectedOfflineArea) RouteLayer(currentRoute) RouteLayer(mapViewModel, currentRoute, allRoutes) TransitLayer(currentTransitItinerary) Loading Loading @@ -250,20 +256,69 @@ private fun OfflineBoundsLayer(selectedOfflineArea: OfflineArea?) { val color = MaterialTheme.colorScheme.onSurface LineLayer( id = "offline_download_bounds", source = offlineDownloadBoundsSource, color = rgbColor( id = "offline_download_bounds", source = offlineDownloadBoundsSource, color = rgbColor( const((color.red * 255).toInt()), const((color.green * 255).toInt()), const((color.blue * 255).toInt()) ), width = const(3.dp) ), width = const(3.dp) ) } } @Composable private fun RouteLayer(currentRoute: Route?) { private fun RouteLayer(viewModel: MapViewModel, currentRoute: Route?, allRoutes: List<Route>) { val annotations = remember(allRoutes) { viewModel.placeRouteAnnotations(allRoutes) } Log.d("annotations", "${annotations.values}") allRoutes.reversed().forEachIndexed { index, route -> if (route == currentRoute) { return@forEachIndexed } val routePositions = route.geometry.map { coord -> Position(coord.lng, coord.lat) // [longitude, latitude] } val routeLineString = LineString(routePositions) val routeFeature = Feature(geometry = routeLineString) val routeSource = rememberGeoJsonSource( GeoJsonData.Features(FeatureCollection(features = listOf(routeFeature))) ) val desaturateAmount = 0.8f val polylineColor = colorResource(R.color.polyline_color).desaturate(desaturateAmount) val polylineCasingColor = colorResource(R.color.polyline_casing_color).desaturate(desaturateAmount) LineLayer( id = "route_line_casing_$index", source = routeSource, color = rgbColor( const((polylineCasingColor.red * 255.0).toInt()), // Blue color const((polylineCasingColor.green * 255.0).toInt()), const((polylineCasingColor.blue * 255.0).toInt()) ), width = const(9.dp), opacity = const(1f), cap = const(LineCap.Round), join = const(LineJoin.Round), ) LineLayer( id = "route_line_$index", source = routeSource, color = rgbColor( const((polylineColor.red * 255.0).toInt()), // Blue color const((polylineColor.green * 255.0).toInt()), const((polylineColor.blue * 255.0).toInt()) ), width = const(6.dp), opacity = const(1f), cap = const(LineCap.Round), join = const(LineJoin.Round), ) annotations[route]?.let { annotationLatLng -> RouteAnnotation(annotationLatLng, index, route) } } currentRoute?.let { route -> val routePositions = route.geometry.map { coord -> Position(coord.lng, coord.lat) // [longitude, latitude] Loading @@ -275,23 +330,22 @@ private fun RouteLayer(currentRoute: Route?) { ) val polylineColor = colorResource(R.color.polyline_color) val polylineCasingColor = colorResource(R.color.polyline_casing_color) val polylineCasingColor = colorResource(R.color.polyline_casing_color) LineLayer( id = "route_line_casing", source = routeSource, id = "current_route_line_casing", source = routeSource, color = rgbColor( const((polylineCasingColor.red * 255.0).toInt()), // Blue color const((polylineCasingColor.green * 255.0).toInt()), const((polylineCasingColor.blue * 255.0).toInt()) ), width = const(8.dp), width = const(9.dp), opacity = const(1f), cap = const(LineCap.Round), join = const(LineJoin.Round), ) LineLayer( id = "route_line", source = routeSource, id = "current_route_line", source = routeSource, color = rgbColor( const((polylineColor.red * 255.0).toInt()), // Blue color const((polylineColor.green * 255.0).toInt()), Loading @@ -302,8 +356,53 @@ private fun RouteLayer(currentRoute: Route?) { cap = const(LineCap.Round), join = const(LineJoin.Round), ) annotations[route]?.let { annotationLatLng -> RouteAnnotation(annotationLatLng, index = null, route) } } } @Composable private fun RouteAnnotation( annotationLatLng: LatLng, index: Int?, route: Route ) { val index = index?.toString() ?: "selected" val annotationFeature = Feature( geometry = Point( coordinates = Position( longitude = annotationLatLng.longitude, latitude = annotationLatLng.latitude ) ) ) val annotationSource = rememberGeoJsonSource( GeoJsonData.Features(FeatureCollection(features = listOf(annotationFeature))) ) val textHaloColor = MaterialTheme.colorScheme.surface val textColor = MaterialTheme.colorScheme.onSurface SymbolLayer( id = "route_annotation_$index", source = annotationSource, textField = const(formatDuration(route.steps.sumOf { it.duration }.toInt())), textAnchor = const(SymbolAnchor.Bottom), textColor = rgbColor( const((textColor.red * 255.0).toInt()), // Blue color const((textColor.green * 255.0).toInt()), const((textColor.blue * 255.0).toInt()) ), textFont = const(listOf("Roboto Medium")), textHaloColor = rgbColor( const((textHaloColor.red * 255.0).toInt()), // Blue color const((textHaloColor.green * 255.0).toInt()), const((textHaloColor.blue * 255.0).toInt()) ), textHaloBlur = const(1.dp), textHaloWidth = const(2.dp) ) } @Composable private fun TransitLayer(currentTransitItinerary: Itinerary?) { Loading @@ -311,8 +410,7 @@ private fun TransitLayer(currentTransitItinerary: Itinerary?) { itinerary.legs.forEachIndexed { legIndex, leg -> leg.legGeometry?.let { geometry -> val positions = PolylineUtils.decodePolyline( encoded = geometry.points, precision = geometry.precision encoded = geometry.points, precision = geometry.precision ) if (positions.isNotEmpty()) { val lineString = LineString(positions) Loading Loading @@ -447,8 +545,7 @@ private fun MapControls( cameraState.animateTo( cameraState.position.copy( zoom = min( 22.0, cameraState.position.zoom + 1 22.0, cameraState.position.zoom + 1 ) ), duration = appPreferences.animationSpeedDurationValue, Loading @@ -467,13 +564,13 @@ private fun MapControls( FloatingActionButton( modifier = Modifier .align(Alignment.CenterHorizontally) .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { coroutineScope.launch { cameraState.animateTo( cameraState.position.copy( zoom = max( 0.0, cameraState.position.zoom - 1 0.0, cameraState.position.zoom - 1 ) ), duration = appPreferences.animationSpeedDurationValue / 2, Loading @@ -492,7 +589,8 @@ private fun MapControls( FloatingActionButton( modifier = Modifier .align(Alignment.CenterHorizontally) .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { .padding(bottom = dimensionResource(dimen.padding_minor)), onClick = { // Request location permissions if we don't have them if (!hasLocationPermission) { mapViewModel.markLocationRequestPending() Loading