Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android route overview #345

Merged
merged 27 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7e77c8c
Initial checkpoint commit on Android route overview
ianthetechie Nov 4, 2024
ce94b79
Apply automatic changes
ianthetechie Nov 4, 2024
794d2de
Minor clean-up; version updates
ianthetechie Nov 5, 2024
5063fc7
Merge branch 'main' into route-overview-android
ianthetechie Nov 5, 2024
060e839
Merge branch 'main' into route-overview-android
ianthetechie Nov 6, 2024
6ab8f7e
Cleanup: by delegation
ianthetechie Nov 6, 2024
584bb74
State -> raw value
ianthetechie Nov 7, 2024
8318660
Move isNavigating into state so we don't have to pass a full viewmode…
ianthetechie Nov 7, 2024
bc096e3
Make onMapReadyCallback optional rather than a capturing default closure
ianthetechie Nov 7, 2024
3e99c47
Auto-adjust most insets
ianthetechie Nov 7, 2024
dd73e99
Support horizontal padding
ianthetechie Nov 7, 2024
8fc0ce8
Snapshots; hide mute button and move recenter to bottom leading corner
ianthetechie Nov 7, 2024
cf15dd8
Refactor; the layering makes sense now!
ianthetechie Nov 7, 2024
742a5ca
Undo experimental change
ianthetechie Nov 7, 2024
cee5ffd
Minor tweaks; update macOS CI to Sonoma
ianthetechie Nov 7, 2024
aee4461
Switch to iOS 18 simulator for snapshots; update nanopixel-perfect sn…
ianthetechie Nov 7, 2024
6819739
Ditch the view model swapping!
ianthetechie Nov 8, 2024
c12f3c8
Apply automatic changes
ianthetechie Nov 8, 2024
6545310
Remove old comments
ianthetechie Nov 8, 2024
06843fe
Fix imports
ianthetechie Nov 8, 2024
1d8d8db
Allow stopping demo VM location updates
ianthetechie Nov 10, 2024
df63897
Move view model to AppModule to better reflect a typical DI use case
ianthetechie Nov 11, 2024
5c03f41
Apply automatic changes
ianthetechie Nov 11, 2024
cc83bbd
Hide zoom controls as well when showing the recenter button
ianthetechie Nov 11, 2024
28dd2cc
Update snapshots
ianthetechie Nov 11, 2024
9cfff25
Freshen docs
ianthetechie Nov 11, 2024
98effcf
Release prep: bump version
ianthetechie Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.stadiamaps.ferrostar.composeui.config

// TODO: Nest cases
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
sealed class CameraControlState {
data object Hidden : CameraControlState()

data class ShowRecenter(val updateCamera: () -> Unit) : CameraControlState()

data class ShowRouteOverview(val updateCamera: () -> Unit) : CameraControlState()
}

data class VisualNavigationViewConfig(
var showMute: Boolean = false,
var showZoom: Boolean = false,
var cameraControlState: CameraControlState
) {
companion object {
fun Default() =
VisualNavigationViewConfig(
showMute = true, showZoom = true, cameraControlState = CameraControlState.Hidden)
}
}

/** Enables the mute button in the navigation view. */
fun VisualNavigationViewConfig.useMuteButton(): VisualNavigationViewConfig {
showMute = true
return this
}

/** Enables the zoom button in the navigation view. */
fun VisualNavigationViewConfig.useZoomButton(): VisualNavigationViewConfig {
showZoom = true
return this
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package com.stadiamaps.ferrostar.composeui.views.gridviews

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.VolumeOff
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.filled.Navigation
import androidx.compose.material.icons.filled.VolumeOff
import androidx.compose.material.icons.filled.Route
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.stadiamaps.ferrostar.composeui.R
import com.stadiamaps.ferrostar.composeui.config.CameraControlState
import com.stadiamaps.ferrostar.composeui.views.controls.NavigationUIButton
import com.stadiamaps.ferrostar.composeui.views.controls.NavigationUIZoomButton

Expand All @@ -25,6 +29,7 @@ fun NavigatingInnerGridView(
showMute: Boolean = true,
isMuted: Boolean?,
onClickMute: () -> Unit = {},
cameraControlState: CameraControlState = CameraControlState.Hidden,
showZoom: Boolean = true,
onClickZoomIn: () -> Unit = {},
onClickZoomOut: () -> Unit = {},
Expand All @@ -41,16 +46,40 @@ fun NavigatingInnerGridView(
},
topCenter = topCenter,
topEnd = {
if (showMute && isMuted != null) {
NavigationUIButton(onClick = onClickMute) {
if (isMuted) {
Icon(
Icons.AutoMirrored.Filled.VolumeOff,
contentDescription = stringResource(id = R.string.unmute_description))
} else {
Icon(
Icons.AutoMirrored.Filled.VolumeUp,
contentDescription = stringResource(id = R.string.mute_description))
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
when (cameraControlState) {
CameraControlState.Hidden -> {
// Nothing to draw here :)
}
is CameraControlState.ShowRecenter -> {
NavigationUIButton(onClick = cameraControlState.updateCamera) {
Icon(
Icons.Filled.Navigation,
contentDescription = stringResource(id = R.string.recenter))
}
}
is CameraControlState.ShowRouteOverview -> {
NavigationUIButton(onClick = cameraControlState.updateCamera) {
// TODO: Switch icon based on state
Icon(
Icons.Default.Route,
modifier = Modifier.rotate(90.0f),
contentDescription = stringResource(id = R.string.route_overview))
}
}
}

if (showMute && isMuted != null) {
NavigationUIButton(onClick = onClickMute) {
if (isMuted) {
Icon(
Icons.AutoMirrored.Filled.VolumeOff,
contentDescription = stringResource(id = R.string.unmute_description))
} else {
Icon(
Icons.AutoMirrored.Filled.VolumeUp,
contentDescription = stringResource(id = R.string.mute_description))
}
}
}
}
Expand All @@ -62,13 +91,7 @@ fun NavigatingInnerGridView(
}
},
bottomStart = {
if (showCentering) {
NavigationUIButton(onClick = onClickCenter) {
Icon(
Icons.Filled.Navigation,
contentDescription = stringResource(id = R.string.recenter))
}
}
// TODO: Fill this with something else optionally
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
},
bottomEnd = bottomEnd)
}
Expand Down
1 change: 1 addition & 0 deletions android/composeui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
<string name="preparing">Preparing...</string>
<string name="arrived_title">Arrived</string>
<string name="arrived_description">You have arrived at your destination.</string>
<string name="route_overview">Route Overview</string>
</resources>
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.stadiamaps.ferrostar.core

import kotlin.math.max
import kotlin.math.min
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import uniffi.ferrostar.GeographicCoordinate
import uniffi.ferrostar.Route
import uniffi.ferrostar.RouteRequest
import uniffi.ferrostar.getRoutePolyline
Expand All @@ -25,3 +28,26 @@ fun RouteRequest.toOkhttp3Request(): Request {
.apply { headers.map { (name, value) -> header(name, value) } }
.build()
}

/** A neutral bounding box type, which is not dependent on any particular map library. */
data class BoundingBox(
val north: Double,
val east: Double,
val south: Double,
val west: Double,
)

fun List<GeographicCoordinate>.boundingBox(): BoundingBox? =
this.firstOrNull()?.let { start ->
val initial =
BoundingBox(north = start.lat, east = start.lng, south = start.lat, west = start.lng)

fold(initial) { acc, current ->
BoundingBox(
north = max(acc.north, current.lat),
east = max(acc.east, current.lng),
south = min(acc.south, current.lat),
west = min(acc.west, current.lng),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.mapbox.mapboxsdk.geometry.LatLng
import com.maplibre.compose.camera.MapViewCamera
import com.maplibre.compose.rememberSaveableMapViewCamera
import com.maplibre.compose.symbols.Circle
import com.stadiamaps.autocomplete.AutocompleteSearch
import com.stadiamaps.autocomplete.center
import com.stadiamaps.ferrostar.composeui.runtime.KeepScreenOnDisposableEffect
Expand All @@ -35,7 +33,6 @@ import com.stadiamaps.ferrostar.core.toAndroidLocation
import com.stadiamaps.ferrostar.googleplayservices.FusedLocationProvider
import com.stadiamaps.ferrostar.maplibreui.views.DynamicallyOrientingNavigationView
import java.util.concurrent.Executors
import kotlin.math.min
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import uniffi.ferrostar.GeographicCoordinate
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import com.maplibre.compose.camera.MapViewCamera
import com.maplibre.compose.ramani.LocationRequestProperties
import com.maplibre.compose.ramani.MapLibreComposable
import com.maplibre.compose.rememberSaveableMapViewCamera
import com.stadiamaps.ferrostar.composeui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.composeui.runtime.paddingForGridView
import com.stadiamaps.ferrostar.composeui.views.CurrentRoadNameView
import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.NavigationViewModel
import com.stadiamaps.ferrostar.maplibreui.NavigationMapView
import com.stadiamaps.ferrostar.maplibreui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import com.stadiamaps.ferrostar.maplibreui.runtime.rememberMapControlsForProgressViewHeight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import com.maplibre.compose.camera.MapViewCamera
import com.maplibre.compose.ramani.LocationRequestProperties
import com.maplibre.compose.ramani.MapLibreComposable
import com.maplibre.compose.rememberSaveableMapViewCamera
import com.stadiamaps.ferrostar.composeui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.composeui.runtime.paddingForGridView
import com.stadiamaps.ferrostar.composeui.views.CurrentRoadNameView
import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.NavigationViewModel
import com.stadiamaps.ferrostar.core.mock.MockNavigationViewModel
import com.stadiamaps.ferrostar.core.mock.pedestrianExample
import com.stadiamaps.ferrostar.maplibreui.NavigationMapView
import com.stadiamaps.ferrostar.maplibreui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import com.stadiamaps.ferrostar.maplibreui.runtime.rememberMapControlsForProgressViewHeight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import com.maplibre.compose.camera.MapViewCamera
import com.maplibre.compose.ramani.LocationRequestProperties
import com.maplibre.compose.ramani.MapLibreComposable
import com.maplibre.compose.rememberSaveableMapViewCamera
import com.stadiamaps.ferrostar.composeui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.composeui.runtime.paddingForGridView
import com.stadiamaps.ferrostar.composeui.views.CurrentRoadNameView
import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.NavigationViewModel
import com.stadiamaps.ferrostar.core.mock.MockNavigationViewModel
import com.stadiamaps.ferrostar.core.mock.pedestrianExample
import com.stadiamaps.ferrostar.maplibreui.NavigationMapView
import com.stadiamaps.ferrostar.maplibreui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import com.stadiamaps.ferrostar.maplibreui.runtime.rememberMapControlsForProgressViewHeight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.maplibre.compose.camera.CameraState
import com.maplibre.compose.camera.MapViewCamera
import com.maplibre.compose.camera.extensions.incrementZoom
import com.maplibre.compose.rememberSaveableMapViewCamera
import com.stadiamaps.ferrostar.composeui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.composeui.views.CurrentRoadNameView
import com.stadiamaps.ferrostar.composeui.views.InstructionsView
import com.stadiamaps.ferrostar.composeui.views.TripProgressView
Expand All @@ -27,7 +28,6 @@ import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.NavigationViewModel
import com.stadiamaps.ferrostar.core.mock.MockNavigationViewModel
import com.stadiamaps.ferrostar.core.mock.pedestrianExample
import com.stadiamaps.ferrostar.maplibreui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -50,6 +50,8 @@ fun LandscapeNavigationOverlayView(
val uiState by viewModel.uiState.collectAsState()
val cameraIsTrackingLocation = camera.value.state is CameraState.TrackingUserLocationWithBearing

// TODO: cameraControlState (basically copy implementation from the portrait overlay)

Row(modifier) {
Column(modifier = Modifier.fillMaxHeight().fillMaxWidth(0.5f)) {
uiState.visualInstruction?.let { instructions ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.mapbox.mapboxsdk.geometry.LatLngBounds
import com.maplibre.compose.camera.CameraState
import com.maplibre.compose.camera.MapViewCamera
import com.maplibre.compose.camera.extensions.incrementZoom
import com.maplibre.compose.camera.models.CameraPadding
import com.maplibre.compose.rememberSaveableMapViewCamera
import com.stadiamaps.ferrostar.composeui.config.CameraControlState
import com.stadiamaps.ferrostar.composeui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.composeui.views.CurrentRoadNameView
import com.stadiamaps.ferrostar.composeui.views.InstructionsView
import com.stadiamaps.ferrostar.composeui.views.TripProgressView
import com.stadiamaps.ferrostar.composeui.views.gridviews.NavigatingInnerGridView
import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.NavigationViewModel
import com.stadiamaps.ferrostar.core.boundingBox
import com.stadiamaps.ferrostar.core.mock.MockNavigationViewModel
import com.stadiamaps.ferrostar.core.mock.pedestrianExample
import com.stadiamaps.ferrostar.maplibreui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand Down Expand Up @@ -64,14 +68,35 @@ fun PortraitNavigationOverlayView(

val cameraIsTrackingLocation = camera.value.state is CameraState.TrackingUserLocationWithBearing

val cameraControlState =
if (viewModel.isNavigating() && !cameraIsTrackingLocation) {
CameraControlState.ShowRecenter { camera.value = navigationCamera }
} else {
val bbox = uiState.routeGeometry?.boundingBox()
if (viewModel.isNavigating() && cameraIsTrackingLocation && bbox != null) {
CameraControlState.ShowRouteOverview {
val scale = density.density
camera.value =
MapViewCamera.BoundingBox(
LatLngBounds.from(bbox.north, bbox.east, bbox.south, bbox.west),
padding =
CameraPadding(20.0 * scale, 200.0 * scale, 100.0 * scale, 150.0 * scale))
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
CameraControlState.Hidden
}
}

NavigatingInnerGridView(
modifier = Modifier.fillMaxSize().weight(1f).padding(bottom = 16.dp, top = 16.dp),
showMute = config.showMute,
isMuted = uiState.isMuted,
onClickMute = { viewModel.toggleMute() },
cameraControlState = cameraControlState,
showZoom = config.showZoom,
onClickZoomIn = { camera.value = camera.value.incrementZoom(1.0) },
onClickZoomOut = { camera.value = camera.value.incrementZoom(-1.0) },
// TODO: Remove if we go with this model
showCentering = !cameraIsTrackingLocation,
onClickCenter = { camera.value = navigationCamera },
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.stadiamaps.ferrostar.maplibreui.config

import com.stadiamaps.ferrostar.composeui.config.VisualNavigationViewConfig
import com.stadiamaps.ferrostar.composeui.config.useMuteButton
import com.stadiamaps.ferrostar.composeui.config.useZoomButton
import org.junit.Assert.assertFalse
import org.junit.Test

Expand Down