Skip to content

Commit

Permalink
Cleanup, ktfmtFormat, and update the bundled views to make usage easy
Browse files Browse the repository at this point in the history
  • Loading branch information
ianthetechie committed Sep 23, 2024
1 parent d1eb044 commit 318754d
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,33 @@ import uniffi.ferrostar.UserLocation
import uniffi.ferrostar.VisualInstruction

data class NavigationUiState(
/** The user's location as reported by the location provider. */
val location: UserLocation?,
/** The user's location snapped to the route shape. */
val snappedLocation: UserLocation?,
/**
* The last known heading of the user.
*
* NOTE: This is distinct from the course over ground (direction of travel), which is included
* in the `location` and `snappedLocation` properties.
*/
val heading: Float?,
/** The geometry of the full route. */
val routeGeometry: List<GeographicCoordinate>,
/** Visual instructions which should be displayed based on the user's current progress. */
val visualInstruction: VisualInstruction?,
/**
* Instructions which should be spoken via speech synthesis based on the user's current
* progress.
*/
val spokenInstruction: SpokenInstruction?,
/** The user's progress through the current trip. */
val progress: TripProgress?,
/** If true, the core is currently calculating a new route. */
val isCalculatingNewRoute: Boolean?,
/** Describes whether the user is believed to be off the correct route. */
val routeDeviation: RouteDeviation?,
/** If true, spoken instructions will not be synthesized. */
val isMuted: Boolean?
) {
companion object {
Expand Down Expand Up @@ -66,19 +84,19 @@ class DefaultNavigationViewModel(
private val locationProvider: LocationProvider
) : ViewModel(), NavigationViewModel {

private var snappedLocation: UserLocation? = locationProvider.lastLocation
private var userLocation: UserLocation? = locationProvider.lastLocation

override val uiState =
ferrostarCore.state
.map { coreState ->
val location = locationProvider.lastLocation
snappedLocation =
val location = locationProvider.lastLocation
userLocation =
when (coreState.tripState) {
is TripState.Navigating -> coreState.tripState.snappedUserLocation
is TripState.Complete,
TripState.Idle -> locationProvider.lastLocation
}
uiState(coreState, spokenInstructionObserver?.isMuted, location, snappedLocation)
uiState(coreState, spokenInstructionObserver?.isMuted, location, userLocation)
// This awkward dance is required because Kotlin doesn't have a way to map over
// StateFlows
// without converting to a generic Flow in the process.
Expand All @@ -88,7 +106,10 @@ class DefaultNavigationViewModel(
started = SharingStarted.WhileSubscribed(),
initialValue =
uiState(
ferrostarCore.state.value, spokenInstructionObserver?.isMuted, locationProvider.lastLocation, locationProvider.lastLocation))
ferrostarCore.state.value,
spokenInstructionObserver?.isMuted,
locationProvider.lastLocation,
locationProvider.lastLocation))

override fun stopNavigation() {
ferrostarCore.stopNavigation()
Expand All @@ -102,6 +123,10 @@ class DefaultNavigationViewModel(
spokenInstructionObserver.isMuted = !spokenInstructionObserver.isMuted
}

private fun uiState(coreState: NavigationState, isMuted: Boolean?, location: UserLocation?, snappedLocation: UserLocation?) =
NavigationUiState.fromFerrostar(coreState, isMuted, location, snappedLocation)
private fun uiState(
coreState: NavigationState,
isMuted: Boolean?,
location: UserLocation?,
snappedLocation: UserLocation?
) = NavigationUiState.fromFerrostar(coreState, isMuted, location, snappedLocation)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ fun NavigationState.Companion.pedestrianExample(): NavigationState {
}

fun NavigationUiState.Companion.pedestrianExample(): NavigationUiState =
fromFerrostar(NavigationState.pedestrianExample(), false, UserLocation.pedestrianExample(), UserLocation.pedestrianExample())
fromFerrostar(
NavigationState.pedestrianExample(),
false,
UserLocation.pedestrianExample(),
UserLocation.pedestrianExample())

class MockNavigationViewModel(override val uiState: StateFlow<NavigationUiState>) :
ViewModel(), NavigationViewModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ fun DemoNavigationScene(
// Most vendors offer free API keys for development use.
styleUrl = "https://demotiles.maplibre.org/style.json",
viewModel = viewModel!!,
// This is the default value, which works well for motor vehicle navigation.
// Other travel modes though, such as walking, may not want snapping.
snapUserLocationToRoute = true,
onTapExit = { viewModel!!.stopNavigation() }) { uiState ->
// Trivial, if silly example of how to add your own overlay layers.
// (Also incidentally highlights the lag inherent in MapLibre location tracking
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.mapbox.mapboxsdk.geometry.LatLng
Expand All @@ -30,6 +29,8 @@ import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
* @param viewModel The navigation view model provided by Ferrostar Core.
* @param locationRequestProperties The location request properties to use for the map's location
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param onMapReadyCallback A callback that is invoked when the map is ready to be interacted with.
* You must set your desired MapViewCamera tracking mode here!
* @param content Any additional composable map symbol content to render.
Expand All @@ -43,13 +44,19 @@ fun NavigationMapView(
viewModel: NavigationViewModel,
locationRequestProperties: LocationRequestProperties =
LocationRequestProperties.NavigationDefault(),
snapUserLocationToRoute: Boolean = true,
onMapReadyCallback: (Style) -> Unit = { camera.value = navigationCamera },
content: @Composable @MapLibreComposable() ((State<NavigationUiState>) -> Unit)? = null
) {
val uiState = viewModel.uiState.collectAsState()

val locationEngine = remember { StaticLocationEngine() }
locationEngine.lastLocation = uiState.value.snappedLocation?.toAndroidLocation()
locationEngine.lastLocation =
if (snapUserLocationToRoute) {
uiState.value.snappedLocation?.toAndroidLocation()
} else {
uiState.value.location?.toAndroidLocation()
}

MapView(
modifier = Modifier.fillMaxSize(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import com.stadiamaps.ferrostar.maplibreui.views.overlays.PortraitNavigationOver
* @param viewModel The navigation view model provided by Ferrostar Core.
* @param locationRequestProperties The location request properties to use for the map's location
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param config The configuration for the navigation view.
* @param landscapeOverlayModifier The modifier to apply to the overlay view.
* @param portraitOverlayModifier The modifier to apply to the overlay view.
Expand All @@ -51,6 +53,7 @@ fun DynamicallyOrientingNavigationView(
viewModel: NavigationViewModel,
locationRequestProperties: LocationRequestProperties =
LocationRequestProperties.NavigationDefault(),
snapUserLocationToRoute: Boolean = true,
config: VisualNavigationViewConfig = VisualNavigationViewConfig.Default(),
landscapeOverlayModifier: Modifier = Modifier.fillMaxSize().padding(16.dp),
portraitOverlayModifier: Modifier = Modifier.fillMaxSize().padding(16.dp),
Expand All @@ -68,6 +71,7 @@ fun DynamicallyOrientingNavigationView(
navigationCamera,
viewModel,
locationRequestProperties,
snapUserLocationToRoute,
onMapReadyCallback = { camera.value = navigationCamera },
content)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import kotlinx.coroutines.flow.asStateFlow
* @param viewModel The navigation view model provided by Ferrostar Core.
* @param locationRequestProperties The location request properties to use for the map's location
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param config The configuration for the navigation view.
* @param overlayModifier The modifier to apply to the overlay view.
* @param onTapExit The callback to invoke when the exit button is tapped.
Expand All @@ -52,6 +54,7 @@ fun LandscapeNavigationView(
viewModel: NavigationViewModel,
locationRequestProperties: LocationRequestProperties =
LocationRequestProperties.NavigationDefault(),
snapUserLocationToRoute: Boolean = true,
config: VisualNavigationViewConfig = VisualNavigationViewConfig.Default(),
overlayModifier: Modifier = Modifier.fillMaxSize().padding(16.dp),
onTapExit: (() -> Unit)? = null,
Expand All @@ -65,6 +68,7 @@ fun LandscapeNavigationView(
navigationCamera,
viewModel,
locationRequestProperties,
snapUserLocationToRoute,
onMapReadyCallback = { camera.value = navigationCamera },
content)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import kotlinx.coroutines.flow.asStateFlow
* @param viewModel The navigation view model provided by Ferrostar Core.
* @param locationRequestProperties The location request properties to use for the map's location
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param config The configuration for the navigation view.
* @param overlayModifier The modifier to apply to the overlay view.
* @param onTapExit The callback to invoke when the exit button is tapped.
Expand All @@ -53,6 +55,7 @@ fun PortraitNavigationView(
viewModel: NavigationViewModel,
locationRequestProperties: LocationRequestProperties =
LocationRequestProperties.NavigationDefault(),
snapUserLocationToRoute: Boolean = true,
config: VisualNavigationViewConfig = VisualNavigationViewConfig.Default(),
overlayModifier: Modifier = Modifier.fillMaxSize().padding(16.dp),
onTapExit: (() -> Unit)? = null,
Expand All @@ -66,6 +69,7 @@ fun PortraitNavigationView(
navigationCamera,
viewModel,
locationRequestProperties,
snapUserLocationToRoute,
onMapReadyCallback = { camera.value = navigationCamera },
content)

Expand Down

0 comments on commit 318754d

Please sign in to comment.