From 35a9deec92e6beb72cfb96e36618337bff090e91 Mon Sep 17 00:00:00 2001 From: Ian Wagner Date: Fri, 1 Nov 2024 12:00:08 +0900 Subject: [PATCH] Upcoming instructions android (#326) * First pass at upcoming maneuvers view for Android * Make it scroll * Apply automatic changes * Build the content view correctly * Add snapshot tests * Add s * Improve the expand button UX * Update snapshots * Implement pill for expand/contract * Regen snapshots --------- Co-authored-by: ianthetechie --- .../composeui/theme/InstructionRowTheme.kt | 2 +- .../composeui/views/InstructionsView.kt | 92 +++++++++++++++--- .../views/controls/PillDragHandle.kt | 68 +++++++++++++ .../ferrostar/views/InstructionViewTest.kt | 17 ++++ ...nstructionViewTest_testInstructionView.png | Bin 10485 -> 9663 bytes ...onViewTest_testInstructionViewExpanded.png | Bin 0 -> 9935 bytes ...uctionViewTests_testRTLInstructionView.png | Bin 9830 -> 8985 bytes .../ferrostar/core/NavigationViewModel.kt | 9 +- .../core/extensions/TripStateExtensions.kt | 12 +++ .../ferrostar/DemoNavigationViewModel.kt | 4 +- .../LandscapeNavigationOverlayView.kt | 4 +- .../overlays/PortraitNavigationOverlayView.kt | 4 +- 12 files changed, 193 insertions(+), 19 deletions(-) create mode 100644 android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/controls/PillDragHandle.kt create mode 100644 android/composeui/src/test/snapshots/images/com.stadiamaps.ferrostar.views_InstructionViewTest_testInstructionViewExpanded.png diff --git a/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/theme/InstructionRowTheme.kt b/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/theme/InstructionRowTheme.kt index c9b04263..43d0ca1d 100644 --- a/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/theme/InstructionRowTheme.kt +++ b/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/theme/InstructionRowTheme.kt @@ -33,5 +33,5 @@ object DefaultInstructionRowTheme : InstructionRowTheme { @Composable get() = MaterialTheme.colorScheme.onSurface override val backgroundColor: Color - @Composable get() = MaterialTheme.colorScheme.surface + @Composable get() = MaterialTheme.colorScheme.surfaceContainerLow } diff --git a/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/InstructionsView.kt b/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/InstructionsView.kt index a609f903..c6c747cb 100644 --- a/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/InstructionsView.kt +++ b/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/InstructionsView.kt @@ -1,25 +1,44 @@ 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.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +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.offset 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.material3.HorizontalDivider 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 import com.stadiamaps.ferrostar.composeui.formatting.LocalizedDistanceFormatter import com.stadiamaps.ferrostar.composeui.theme.DefaultInstructionRowTheme import com.stadiamaps.ferrostar.composeui.theme.InstructionRowTheme +import com.stadiamaps.ferrostar.composeui.views.controls.PillDragHandle 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 @@ -36,23 +55,72 @@ fun InstructionsView( distanceToNextManeuver: Double?, distanceFormatter: DistanceFormatter = LocalizedDistanceFormatter(), theme: InstructionRowTheme = DefaultInstructionRowTheme, - content: @Composable () -> Unit = { - ManeuverImage(instructions.primaryContent, tint = MaterialTheme.colorScheme.primary) + remainingSteps: List? = null, + initExpanded: Boolean = false, + contentBuilder: @Composable (VisualInstruction) -> Unit = { + ManeuverImage(it.primaryContent, tint = MaterialTheme.colorScheme.primary) } ) { + var isExpanded by remember { mutableStateOf(initExpanded) } + val screenHeight = LocalConfiguration.current.screenHeightDp.dp + Column( 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) + .clickable { + // This makes the entire view a click target for expansion. + // If only the pill is a click target, you need to be a ninja to tap it. + isExpanded = true + }) { + 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) + } + } + } + } + + if (isExpanded) { + Spacer(modifier = Modifier.weight(1.0f)) + } + + PillDragHandle( + isExpanded, + // The modifier here lets us keep the container as slim as possible + modifier = Modifier.offset(y = 4.dp).align(Alignment.CenterHorizontally), + iconTintColor = theme.iconTintColor) { + isExpanded = !isExpanded + } } } diff --git a/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/controls/PillDragHandle.kt b/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/controls/PillDragHandle.kt new file mode 100644 index 00000000..1eb7af00 --- /dev/null +++ b/android/composeui/src/main/java/com/stadiamaps/ferrostar/composeui/views/controls/PillDragHandle.kt @@ -0,0 +1,68 @@ +package com.stadiamaps.ferrostar.composeui.views.controls + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.KeyboardArrowUp +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +fun PillDragHandle( + isExpanded: Boolean, + modifier: Modifier = Modifier.fillMaxWidth(), + iconTintColor: Color = MaterialTheme.colorScheme.onSurface, + toggle: () -> Unit = {} +) { + val handleHeight = if (isExpanded) 36.dp else 4.dp + Box(modifier = modifier.height(handleHeight).clickable(onClick = toggle)) { + if (isExpanded) { + Icon( + Icons.Rounded.KeyboardArrowUp, + modifier = Modifier.align(Alignment.Center), + contentDescription = "Show upcoming maneuvers", + tint = iconTintColor) + } else { + Box( + modifier = + Modifier.align(Alignment.Center) + .height(handleHeight) + .width(24.dp) + .background(iconTintColor, RoundedCornerShape(6.dp)) + .semantics { + role = Role.Button + onClick(label = "Hide upcoming maneuvers") { + toggle() + true + } + }) + } + } +} + +@Preview +@Composable +fun PreviewPillDragHandleCollapsed() { + PillDragHandle(isExpanded = false, iconTintColor = Color.White) +} + +@Preview +@Composable +fun PreviewPillDragHandleExpanded() { + PillDragHandle(isExpanded = true, iconTintColor = Color.White) +} diff --git a/android/composeui/src/test/java/com/stadiamaps/ferrostar/views/InstructionViewTest.kt b/android/composeui/src/test/java/com/stadiamaps/ferrostar/views/InstructionViewTest.kt index 3850680d..4f356d16 100644 --- a/android/composeui/src/test/java/com/stadiamaps/ferrostar/views/InstructionViewTest.kt +++ b/android/composeui/src/test/java/com/stadiamaps/ferrostar/views/InstructionViewTest.kt @@ -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 @@ -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) + } + } + } } diff --git a/android/composeui/src/test/snapshots/images/com.stadiamaps.ferrostar.views_InstructionViewTest_testInstructionView.png b/android/composeui/src/test/snapshots/images/com.stadiamaps.ferrostar.views_InstructionViewTest_testInstructionView.png index 5ba73ea302349b3def11ba8f976366a196eebcac..e563f03bc6f3f07c17c40e4a6866c44cf006e7da 100644 GIT binary patch literal 9663 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 i*8M+OHgxabiqJ%g@i`*!ZNck|03&@b(a5 diff --git a/android/composeui/src/test/snapshots/images/com.stadiamaps.ferrostar.views_InstructionViewTest_testInstructionViewExpanded.png b/android/composeui/src/test/snapshots/images/com.stadiamaps.ferrostar.views_InstructionViewTest_testInstructionViewExpanded.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b9d9a51759f5486d7c75b237dc87e95a097195 GIT binary patch literal 9935 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

vP&!JDYjIhaDO*>A_?l#lq${sypn~ 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 literal 9830 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 diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt index 9ffa43b4..0c2a3ed8 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt @@ -6,6 +6,7 @@ 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 @@ -13,6 +14,7 @@ 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 @@ -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? ) { companion object { fun fromFerrostar( @@ -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()) } } diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/extensions/TripStateExtensions.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/extensions/TripStateExtensions.kt index f045cb91..b5ca06a0 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/extensions/TripStateExtensions.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/extensions/TripStateExtensions.kt @@ -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 + } diff --git a/android/demo-app/src/main/java/com/stadiamaps/ferrostar/DemoNavigationViewModel.kt b/android/demo-app/src/main/java/com/stadiamaps/ferrostar/DemoNavigationViewModel.kt index 16c7c6cf..40d59d91 100644 --- a/android/demo-app/src/main/java/com/stadiamaps/ferrostar/DemoNavigationViewModel.kt +++ b/android/demo-app/src/main/java/com/stadiamaps/ferrostar/DemoNavigationViewModel.kt @@ -42,7 +42,7 @@ 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, @@ -50,7 +50,7 @@ class DemoNavigationViewModel : ViewModel(), NavigationViewModel { // 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 diff --git a/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/LandscapeNavigationOverlayView.kt b/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/LandscapeNavigationOverlayView.kt index f3855f4a..790f144e 100644 --- a/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/LandscapeNavigationOverlayView.kt +++ b/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/LandscapeNavigationOverlayView.kt @@ -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)) diff --git a/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/PortraitNavigationOverlayView.kt b/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/PortraitNavigationOverlayView.kt index f378a657..c2bf2fd8 100644 --- a/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/PortraitNavigationOverlayView.kt +++ b/android/maplibreui/src/main/java/com/stadiamaps/ferrostar/maplibreui/views/overlays/PortraitNavigationOverlayView.kt @@ -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