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

Upcoming instructions android #326

Merged
merged 10 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
@@ -1,15 +1,35 @@
package com.stadiamaps.ferrostar.composeui.views

import android.icu.util.ULocale
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.stadiamaps.ferrostar.composeui.formatting.DistanceFormatter
Expand All @@ -20,6 +40,7 @@ import com.stadiamaps.ferrostar.composeui.views.maneuver.ManeuverImage
import com.stadiamaps.ferrostar.composeui.views.maneuver.ManeuverInstructionView
import uniffi.ferrostar.ManeuverModifier
import uniffi.ferrostar.ManeuverType
import uniffi.ferrostar.RouteStep
import uniffi.ferrostar.VisualInstruction
import uniffi.ferrostar.VisualInstructionContent

Expand All @@ -36,23 +57,70 @@ fun InstructionsView(
distanceToNextManeuver: Double?,
distanceFormatter: DistanceFormatter = LocalizedDistanceFormatter(),
theme: InstructionRowTheme = DefaultInstructionRowTheme,
content: @Composable () -> Unit = {
ManeuverImage(instructions.primaryContent, tint = MaterialTheme.colorScheme.primary)
remainingSteps: List<RouteStep>? = null,
initExpanded: Boolean = false,
contentBuilder: @Composable (VisualInstruction) -> Unit = {
ManeuverImage(it.primaryContent, tint = MaterialTheme.colorScheme.primary)
}
) {
Column(
var isExpanded by remember { mutableStateOf(initExpanded) }
val screenHeight = LocalConfiguration.current.screenHeightDp.dp

Box(
modifier =
Modifier.fillMaxWidth()
.shadow(elevation = 5.dp, RoundedCornerShape(10.dp))
.heightIn(max = screenHeight)
.animateContentSize(animationSpec = spring(stiffness = Spring.StiffnessHigh))
.background(theme.backgroundColor, RoundedCornerShape(10.dp))
.padding(8.dp)) {
ManeuverInstructionView(
text = instructions.primaryContent.text,
distanceFormatter = distanceFormatter,
distanceToNextManeuver = distanceToNextManeuver,
theme = theme,
content = content)
// TODO: Secondary instructions
.padding(16.dp)) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
// Primary content
item {
ManeuverInstructionView(
text = instructions.primaryContent.text,
distanceFormatter = distanceFormatter,
distanceToNextManeuver = distanceToNextManeuver,
theme = theme) {
contentBuilder(instructions)
}
}

// TODO: Secondary content

// Expanded content
if (isExpanded && remainingSteps != null && remainingSteps.count() > 1) {
item { HorizontalDivider(thickness = 1.dp) }
items(remainingSteps.drop(1)) { step ->
step.visualInstructions.firstOrNull()?.let { upcomingInstruction ->
Spacer(modifier = Modifier.height(8.dp))
ManeuverInstructionView(
text = upcomingInstruction.primaryContent.text,
distanceFormatter = distanceFormatter,
distanceToNextManeuver = step.distance,
theme = theme) {
contentBuilder(upcomingInstruction)
}
Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider(thickness = 1.dp)
}
}
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
}

item {
Box(
modifier =
Modifier.height(36.dp).fillMaxWidth().clickable { isExpanded = !isExpanded }) {
Icon(
if (isExpanded) {
Icons.Default.KeyboardArrowUp
} else {
Icons.Default.KeyboardArrowDown
},
modifier = Modifier.align(Alignment.Center),
contentDescription = "Show upcoming maneuvers")
}
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.stadiamaps.ferrostar.views

import com.stadiamaps.ferrostar.composeui.views.InstructionsView
import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.mock.pedestrianExample
import com.stadiamaps.ferrostar.support.paparazziDefault
import com.stadiamaps.ferrostar.support.withSnapshotBackground
import org.junit.Rule
Expand Down Expand Up @@ -35,4 +37,19 @@ class InstructionViewTest {
}
}
}

@Test
fun testInstructionViewExpanded() {
val state = NavigationUiState.pedestrianExample()

paparazzi.snapshot {
withSnapshotBackground {
InstructionsView(
instructions = state.visualInstruction!!,
remainingSteps = state.remainingSteps,
distanceToNextManeuver = 42.0,
initExpanded = true)
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import androidx.lifecycle.viewModelScope
import com.stadiamaps.ferrostar.core.extensions.currentRoadName
import com.stadiamaps.ferrostar.core.extensions.deviation
import com.stadiamaps.ferrostar.core.extensions.progress
import com.stadiamaps.ferrostar.core.extensions.remainingSteps
import com.stadiamaps.ferrostar.core.extensions.visualInstruction
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import uniffi.ferrostar.GeographicCoordinate
import uniffi.ferrostar.RouteDeviation
import uniffi.ferrostar.RouteStep
import uniffi.ferrostar.SpokenInstruction
import uniffi.ferrostar.TripProgress
import uniffi.ferrostar.TripState
Expand Down Expand Up @@ -49,7 +51,9 @@ data class NavigationUiState(
/** If true, spoken instructions will not be synthesized. */
val isMuted: Boolean?,
/** The name of the road which the current route step is traversing. */
val currentStepRoadName: String?
val currentStepRoadName: String?,
/** The remaining steps in the trip (including the current step). */
val remainingSteps: List<RouteStep>?
) {
companion object {
fun fromFerrostar(
Expand All @@ -70,7 +74,8 @@ data class NavigationUiState(
isCalculatingNewRoute = coreState.isCalculatingNewRoute,
routeDeviation = coreState.tripState.deviation(),
isMuted = isMuted,
currentStepRoadName = coreState.tripState.currentRoadName())
currentStepRoadName = coreState.tripState.currentRoadName(),
remainingSteps = coreState.tripState.remainingSteps())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,15 @@ fun TripState.currentRoadName() =
is TripState.Complete,
TripState.Idle -> null
}

/**
* Get the remaining steps (including the current) in the current trip.
*
* @return The list of remaining steps (if any).
*/
fun TripState.remainingSteps() =
when (this) {
is TripState.Navigating -> this.remainingSteps
is TripState.Complete,
TripState.Idle -> null
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ class DemoNavigationViewModel : ViewModel(), NavigationViewModel {
.map { userLocation ->
// TODO: Heading
NavigationUiState(
userLocation, null, null, null, null, null, null, false, null, null, null)
userLocation, null, null, null, null, null, null, false, null, null, null, null)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
// TODO: Heading
initialValue =
NavigationUiState(
null, null, null, null, null, null, null, false, null, null, null))
null, null, null, null, null, null, null, false, null, null, null, null))

override fun toggleMute() {
// Do nothing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ fun LandscapeNavigationOverlayView(
Column(modifier = Modifier.fillMaxHeight().fillMaxWidth(0.5f)) {
uiState.visualInstruction?.let { instructions ->
InstructionsView(
instructions, distanceToNextManeuver = uiState.progress?.distanceToNextManeuver)
instructions,
remainingSteps = uiState.remainingSteps,
distanceToNextManeuver = uiState.progress?.distanceToNextManeuver)
}

Spacer(modifier = Modifier.weight(1f))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ fun PortraitNavigationOverlayView(
Column(modifier) {
uiState.visualInstruction?.let { instructions ->
InstructionsView(
instructions, distanceToNextManeuver = uiState.progress?.distanceToNextManeuver)
instructions,
remainingSteps = uiState.remainingSteps,
distanceToNextManeuver = uiState.progress?.distanceToNextManeuver)
}

val cameraIsTrackingLocation = camera.value.state is CameraState.TrackingUserLocationWithBearing
Expand Down
Loading