Skip to content

Commit

Permalink
Merge pull request #195 from stadiamaps/feat/android/mute
Browse files Browse the repository at this point in the history
Feat/android/mute
  • Loading branch information
ianthetechie authored Aug 27, 2024
2 parents 13a97d8 + b501aa2 commit 1633e69
Show file tree
Hide file tree
Showing 28 changed files with 628 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.stadiamaps.ferrostar.composeui.R
import com.stadiamaps.ferrostar.composeui.formatting.DateTimeFormatter
import com.stadiamaps.ferrostar.composeui.formatting.DistanceFormatter
import com.stadiamaps.ferrostar.composeui.formatting.DurationFormatter
Expand Down Expand Up @@ -85,7 +87,9 @@ fun ArrivalView(
progress.estimatedArrivalTime(fromDate, timeZone)),
style = theme.measurementTextStyle)
if (theme.style == ArrivalViewStyle.INFORMATIONAL) {
Text(text = "Arrival", style = theme.secondaryTextStyle)
Text(
text = stringResource(id = R.string.arrival),
style = theme.secondaryTextStyle)
}
}

Expand All @@ -95,7 +99,9 @@ fun ArrivalView(
text = durationFormatter.format(progress.durationRemaining),
style = theme.measurementTextStyle)
if (theme.style == ArrivalViewStyle.INFORMATIONAL) {
Text(text = "Duration", style = theme.secondaryTextStyle)
Text(
text = stringResource(id = R.string.duration),
style = theme.secondaryTextStyle)
}
}

Expand All @@ -105,7 +111,9 @@ fun ArrivalView(
text = distanceFormatter.format(progress.distanceRemaining),
style = theme.measurementTextStyle)
if (theme.style == ArrivalViewStyle.INFORMATIONAL) {
Text(text = "Distance", style = theme.secondaryTextStyle)
Text(
text = stringResource(id = R.string.distance),
style = theme.secondaryTextStyle)
}
}

Expand All @@ -120,7 +128,7 @@ fun ArrivalView(
contentPadding = PaddingValues(0.dp)) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Close",
contentDescription = stringResource(id = R.string.end_navigation),
tint = theme.exitIconColor)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.stadiamaps.ferrostar.composeui.R

/**
* A FloatingActionButton styled for use in the navigation UI.
Expand Down Expand Up @@ -48,6 +50,8 @@ fun NavigationUIButton(
@Composable
fun NavigationUIButtonPreview() {
Box(Modifier.background(Color.LightGray).padding(16.dp)) {
NavigationUIButton({}) { Icon(Icons.Filled.Close, contentDescription = "Close") }
NavigationUIButton({}) {
Icon(Icons.Filled.Close, contentDescription = stringResource(id = R.string.end_navigation))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.stadiamaps.ferrostar.composeui.R

@Composable
fun NavigationUIZoomButton(
Expand All @@ -41,7 +43,9 @@ fun NavigationUIZoomButton(
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation) {
Icon(imageVector = Icons.Filled.Add, contentDescription = "Zoom In")
Icon(
imageVector = Icons.Filled.Add,
contentDescription = stringResource(id = R.string.zoom_in))
}

Box(modifier = Modifier.height(1.dp).width(56.dp)) {
Expand All @@ -55,7 +59,9 @@ fun NavigationUIZoomButton(
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation) {
Icon(imageVector = Icons.Filled.Remove, contentDescription = "Zoom Out")
Icon(
imageVector = Icons.Filled.Remove,
contentDescription = stringResource(id = R.string.zoom_out))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,33 @@ 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.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.views.controls.NavigationUIButton
import com.stadiamaps.ferrostar.composeui.views.controls.NavigationUIZoomButton

@Composable
fun NavigatingInnerGridView(
modifier: Modifier,
showMute: Boolean = true,
isMuted: Boolean?,
onClickMute: () -> Unit = {},
showZoom: Boolean = true,
onClickZoomIn: () -> Unit = {},
onClickZoomOut: () -> Unit = {},
showCentering: Boolean = true,
onClickCenter: () -> Unit = {},
topCenter: @Composable () -> Unit = { Spacer(Modifier.width(12.dp)) },
topEnd: @Composable () -> Unit = { Spacer(Modifier.width(12.dp)) },
centerStart: @Composable () -> Unit = { Spacer(Modifier.width(12.dp)) },
bottomEnd: @Composable () -> Unit = { Spacer(Modifier.width(12.dp)) }
) {
Expand All @@ -33,7 +40,21 @@ fun NavigatingInnerGridView(
// TODO: SpeedLimitView goes here
},
topCenter = topCenter,
topEnd = topEnd,
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))
}
}
}
},
centerStart = centerStart,
centerEnd = {
if (showZoom) {
Expand All @@ -43,7 +64,9 @@ fun NavigatingInnerGridView(
bottomStart = {
if (showCentering) {
NavigationUIButton(onClick = onClickCenter) {
Icon(Icons.Filled.Navigation, contentDescription = "Recenter Map")
Icon(
Icons.Filled.Navigation,
contentDescription = stringResource(id = R.string.recenter))
}
}
},
Expand All @@ -53,13 +76,13 @@ fun NavigatingInnerGridView(
@Preview(device = Devices.PIXEL_5)
@Composable
fun NavigatingInnerGridViewPreview() {
NavigatingInnerGridView(modifier = Modifier.fillMaxSize())
NavigatingInnerGridView(modifier = Modifier.fillMaxSize(), isMuted = false)
}

@Preview(
device =
"spec:width=411dp,height=891dp,dpi=420,isRound=false,chinSize=0dp,orientation=landscape")
@Composable
fun NavigatingInnerGridViewLandscapePreview() {
NavigatingInnerGridView(modifier = Modifier.fillMaxSize())
NavigatingInnerGridView(modifier = Modifier.fillMaxSize(), isMuted = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.stadiamaps.ferrostar.composeui.R
import uniffi.ferrostar.ManeuverModifier
import uniffi.ferrostar.ManeuverType
import uniffi.ferrostar.VisualInstructionContent
Expand All @@ -35,7 +37,7 @@ fun ManeuverImage(content: VisualInstructionContent, tint: Color = LocalContentC
if (resourceId != 0) {
Icon(
painter = painterResource(id = resourceId),
contentDescription = "Description for accessibility",
contentDescription = stringResource(id = R.string.maneuver_image),
tint = tint,
modifier = Modifier.size(64.dp))
} else {
Expand Down
10 changes: 10 additions & 0 deletions android/composeui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="recenter">Recenter Map</string>
<string name="unmute_description">Unmute</string>
<string name="mute_description">Mute</string>
<string name="zoom_in">Zoom In</string>
<string name="zoom_out">Zoom Out</string>
<string name="arrival">Arrival</string>
<string name="distance">Distance</string>
<string name="duration">Duration</string>
<string name="end_navigation">End Navigation</string>
<string name="maneuver_image">Maneuver direction image</string>
<string name="stop_navigation">Stop Navigating</string>
<string name="maneuver_instruction_image">Maneuver instruction image</string>
<string name="dot">•</string>
Expand Down
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 @@ -258,7 +258,7 @@ class FerrostarCore(

locationProvider.addListener(this, _executor)

return DefaultNavigationViewModel(this, locationProvider)
return DefaultNavigationViewModel(this, spokenInstructionObserver, locationProvider)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stadiamaps.ferrostar.core

import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.stadiamaps.ferrostar.core.extensions.deviation
Expand All @@ -25,10 +26,15 @@ data class NavigationUiState(
val spokenInstruction: SpokenInstruction?,
val progress: TripProgress?,
val isCalculatingNewRoute: Boolean?,
val routeDeviation: RouteDeviation?
val routeDeviation: RouteDeviation?,
val isMuted: Boolean?
) {
companion object {
fun fromFerrostar(coreState: NavigationState, userLocation: UserLocation?): NavigationUiState =
fun fromFerrostar(
coreState: NavigationState,
isMuted: Boolean?,
userLocation: UserLocation?
): NavigationUiState =
NavigationUiState(
snappedLocation = userLocation,
// TODO: Heading/course over ground
Expand All @@ -38,18 +44,22 @@ data class NavigationUiState(
spokenInstruction = null,
progress = coreState.tripState.progress(),
isCalculatingNewRoute = coreState.isCalculatingNewRoute,
routeDeviation = coreState.tripState.deviation())
routeDeviation = coreState.tripState.deviation(),
isMuted = isMuted)
}
}

interface NavigationViewModel {
val uiState: StateFlow<NavigationUiState>

fun toggleMute()

fun stopNavigation()
}

class DefaultNavigationViewModel(
private val ferrostarCore: FerrostarCore,
private val spokenInstructionObserver: SpokenInstructionObserver? = null,
locationProvider: LocationProvider
) : ViewModel(), NavigationViewModel {

Expand All @@ -65,20 +75,30 @@ class DefaultNavigationViewModel(
TripState.Idle -> locationProvider.lastLocation
}

uiState(coreState, lastLocation)
uiState(coreState, spokenInstructionObserver?.isMuted, lastLocation)
// 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.
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = uiState(ferrostarCore.state.value, lastLocation))
initialValue =
uiState(
ferrostarCore.state.value, spokenInstructionObserver?.isMuted, lastLocation))

override fun stopNavigation() {
ferrostarCore.stopNavigation()
}

private fun uiState(coreState: NavigationState, location: UserLocation?) =
NavigationUiState.fromFerrostar(coreState, location)
override fun toggleMute() {
if (spokenInstructionObserver == null) {
Log.d("NavigationViewModel", "Spoken instruction observer is null, mute operation ignored.")
return
}
spokenInstructionObserver.isMuted = !spokenInstructionObserver.isMuted
}

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

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

class MockNavigationViewModel(override val uiState: StateFlow<NavigationUiState>) :
ViewModel(), NavigationViewModel {
override fun toggleMute() {}

override fun stopNavigation() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ fun DemoNavigationScene(
}
}

// FIXME: This is restarting navigation every time the screen is rotated.
LaunchedEffect(savedInstanceState) {
// Request all permissions
permissionsLauncher.launch(allPermissions)
Expand Down Expand Up @@ -109,7 +110,8 @@ fun DemoNavigationScene(
// tiles.
// Most vendors offer free API keys for development use.
styleUrl = "https://demotiles.maplibre.org/style.json",
viewModel = viewModel!!) { uiState ->
viewModel = viewModel!!,
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
// as-is.)
Expand Down
1 change: 1 addition & 0 deletions android/maplibreui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
alias libs.plugins.androidLibrary
alias libs.plugins.jetbrainsKotlinAndroid
alias libs.plugins.ktfmt
alias libs.plugins.paparazzi
alias libs.plugins.compose.compiler
alias libs.plugins.mavenPublish
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import com.maplibre.compose.settings.MapControls
import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.NavigationViewModel
import com.stadiamaps.ferrostar.core.toAndroidLocation
import com.stadiamaps.ferrostar.maplibreui.extensions.AutomotiveNavigationCentered
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera

/**
* The base MapLibre MapView configured for navigation with a polyline representing the route.
Expand All @@ -39,12 +39,11 @@ fun NavigationMapView(
styleUrl: String,
mapControls: MapControls,
camera: MutableState<MapViewCamera>,
navigationCamera: MapViewCamera = navigationMapViewCamera(),
viewModel: NavigationViewModel,
locationRequestProperties: LocationRequestProperties =
LocationRequestProperties.NavigationDefault(),
onMapReadyCallback: (Style) -> Unit = {
camera.value = MapViewCamera.AutomotiveNavigationCentered()
},
onMapReadyCallback: (Style) -> Unit = { camera.value = navigationCamera },
content: @Composable @MapLibreComposable() ((State<NavigationUiState>) -> Unit)? = null
) {
val uiState = viewModel.uiState.collectAsState()
Expand Down
Loading

0 comments on commit 1633e69

Please sign in to comment.