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 speed limit view #397

Merged
merged 4 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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,11 +1,16 @@
package com.stadiamaps.ferrostar.composeui.config

import com.stadiamaps.ferrostar.composeui.views.components.speedlimit.SignageStyle

data class VisualNavigationViewConfig(
// Mute
var showMute: Boolean = false,

// Zoom
var showZoom: Boolean = false,

// Speed Limit
var speedLimitStyle: SignageStyle? = null,
) {
companion object {
fun Default() = VisualNavigationViewConfig(showMute = true, showZoom = true)
Expand All @@ -21,3 +26,9 @@ fun VisualNavigationViewConfig.useMuteButton(): VisualNavigationViewConfig {
fun VisualNavigationViewConfig.useZoomButton(): VisualNavigationViewConfig {
return copy(showZoom = true)
}

fun VisualNavigationViewConfig.withSpeedLimitStyle(
style: SignageStyle
): VisualNavigationViewConfig {
return copy(speedLimitStyle = style)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package com.stadiamaps.ferrostar.composeui.formatting

import android.content.Context
import android.icu.util.ULocale
import com.stadiamaps.ferrostar.composeui.measurement.localizedString
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeed
import com.stadiamaps.ferrostar.core.measurement.SpeedUnit
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeedUnit
import java.util.Locale

class MeasurementSpeedFormatter(val measurementSpeed: MeasurementSpeed) {
class MeasurementSpeedFormatter(context: Context, val measurementSpeed: MeasurementSpeed) {

// This allows us to avoid capturing the context downstream
private val unitLocalizations =
mapOf(
MeasurementSpeedUnit.MetersPerSecond to
MeasurementSpeedUnit.MetersPerSecond.localizedString(context),
MeasurementSpeedUnit.MilesPerHour to
MeasurementSpeedUnit.MilesPerHour.localizedString(context),
MeasurementSpeedUnit.KilometersPerHour to
MeasurementSpeedUnit.KilometersPerHour.localizedString(context),
MeasurementSpeedUnit.Knots to MeasurementSpeedUnit.Knots.localizedString(context))

fun formattedValue(locale: ULocale = ULocale.getDefault()): String {
val locale = locale.let { Locale(it.language, it.country) }
Expand All @@ -15,17 +27,17 @@ class MeasurementSpeedFormatter(val measurementSpeed: MeasurementSpeed) {

fun formattedValue(
locale: ULocale = ULocale.getDefault(),
converted: SpeedUnit = measurementSpeed.unit
converted: MeasurementSpeedUnit = measurementSpeed.unit
): String {
val locale = locale.let { Locale(it.language, it.country) }
return String.format(locale = locale, "%.0f", measurementSpeed.value(converted))
}

fun formatted(): String {
return "${measurementSpeed.value} ${measurementSpeed.unit.localizedString()}"
return "${measurementSpeed.value} ${unitLocalizations[measurementSpeed.unit]}"
}

fun formatted(converted: SpeedUnit): String {
return "${measurementSpeed.value(converted)} ${converted.localizedString()}"
fun formatted(converted: MeasurementSpeedUnit): String {
return "${measurementSpeed.value(converted)} ${unitLocalizations[converted]}"
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.stadiamaps.ferrostar.composeui.measurement

import android.content.res.Resources
import android.content.Context
import com.stadiamaps.ferrostar.composeui.R
import com.stadiamaps.ferrostar.core.measurement.SpeedUnit
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeedUnit

fun SpeedUnit.localizedString(): String {
fun MeasurementSpeedUnit.localizedString(context: Context): String {
return when (this) {
SpeedUnit.MetersPerSecond -> Resources.getSystem().getString(R.string.unit_short_mps)
SpeedUnit.MilesPerHour -> Resources.getSystem().getString(R.string.unit_short_kph)
SpeedUnit.KilometersPerHour -> Resources.getSystem().getString(R.string.unit_short_mph)
MeasurementSpeedUnit.MetersPerSecond -> context.getString(R.string.unit_short_mps)
MeasurementSpeedUnit.MilesPerHour -> context.getString(R.string.unit_short_mph)
MeasurementSpeedUnit.KilometersPerHour -> context.getString(R.string.unit_short_kph)
MeasurementSpeedUnit.Knots -> context.getString(R.string.unit_short_knot)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.stadiamaps.ferrostar.composeui.support

import android.content.res.Configuration
import androidx.compose.ui.tooling.preview.Preview

@Preview(
name = "Dark Mode",
showBackground = true,
uiMode = Configuration.UI_MODE_NIGHT_YES,
backgroundColor = 0xFF93C97C)
@Preview(
name = "Light Mode",
showBackground = true,
uiMode = Configuration.UI_MODE_NIGHT_NO,
backgroundColor = 0xFF93C97C)
internal annotation class GreenScreenPreview
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ import com.stadiamaps.ferrostar.composeui.R
import com.stadiamaps.ferrostar.composeui.models.CameraControlState
import com.stadiamaps.ferrostar.composeui.views.components.controls.NavigationUIButton
import com.stadiamaps.ferrostar.composeui.views.components.controls.NavigationUIZoomButton
import com.stadiamaps.ferrostar.composeui.views.components.speedlimit.SignageStyle
import com.stadiamaps.ferrostar.composeui.views.components.speedlimit.SpeedLimitView
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeed
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeedUnit

@Composable
fun NavigatingInnerGridView(
modifier: Modifier,
speedLimit: MeasurementSpeed? = null,
speedLimitStyle: SignageStyle? = null,
showMute: Boolean = true,
isMuted: Boolean?,
onClickMute: () -> Unit = {},
Expand All @@ -43,7 +49,9 @@ fun NavigatingInnerGridView(
InnerGridView(
modifier,
topStart = {
// TODO: SpeedLimitView goes here
speedLimit?.let {
speedLimitStyle?.let { style -> SpeedLimitView(speedLimit = it, signageStyle = style) }
}
},
topCenter = topCenter,
topEnd = {
Expand Down Expand Up @@ -108,6 +116,8 @@ fun NavigatingInnerGridView(
fun NavigatingInnerGridViewNonTrackingPreview() {
NavigatingInnerGridView(
modifier = Modifier.fillMaxSize(),
speedLimit = MeasurementSpeed(24.6, MeasurementSpeedUnit.MetersPerSecond),
speedLimitStyle = SignageStyle.MUTCD,
isMuted = false,
buttonSize = DpSize(56.dp, 56.dp),
cameraControlState =
Expand All @@ -121,6 +131,8 @@ fun NavigatingInnerGridViewNonTrackingPreview() {
fun NavigatingInnerGridViewTrackingPreview() {
NavigatingInnerGridView(
modifier = Modifier.fillMaxSize(),
speedLimit = MeasurementSpeed(24.6, MeasurementSpeedUnit.MetersPerSecond),
speedLimitStyle = SignageStyle.MUTCD,
isMuted = false,
buttonSize = DpSize(56.dp, 56.dp),
cameraControlState =
Expand All @@ -136,6 +148,8 @@ fun NavigatingInnerGridViewTrackingPreview() {
fun NavigatingInnerGridViewLandscapeNonTrackingPreview() {
NavigatingInnerGridView(
modifier = Modifier.fillMaxSize(),
speedLimit = MeasurementSpeed(27.8, MeasurementSpeedUnit.MetersPerSecond),
speedLimitStyle = SignageStyle.ViennaConvention,
isMuted = true,
buttonSize = DpSize(56.dp, 56.dp),
cameraControlState =
Expand All @@ -151,6 +165,8 @@ fun NavigatingInnerGridViewLandscapeNonTrackingPreview() {
fun NavigatingInnerGridViewLandscapeTrackingPreview() {
NavigatingInnerGridView(
modifier = Modifier.fillMaxSize(),
speedLimit = MeasurementSpeed(27.8, MeasurementSpeedUnit.MetersPerSecond),
speedLimitStyle = SignageStyle.ViennaConvention,
isMuted = true,
buttonSize = DpSize(56.dp, 56.dp),
cameraControlState =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.stadiamaps.ferrostar.composeui.views.components.speedlimit

import android.content.Context
import android.icu.util.ULocale
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.stadiamaps.ferrostar.composeui.formatting.MeasurementSpeedFormatter
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeed
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeedUnit

enum class SignageStyle {
MUTCD,
ViennaConvention
}

@Composable
fun SpeedLimitView(
modifier: Modifier = Modifier,
speedLimit: MeasurementSpeed,
signageStyle: SignageStyle = SignageStyle.ViennaConvention, // TODO: This could be nicer
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
context: Context = LocalContext.current,
formatter: MeasurementSpeedFormatter = MeasurementSpeedFormatter(context, speedLimit),
locale: ULocale = ULocale.getDefault()
) {
when (signageStyle) {
SignageStyle.MUTCD ->
USStyleSpeedLimitView(
modifier, speedLimit, MeasurementSpeedUnit.MilesPerHour, context, formatter, locale)
SignageStyle.ViennaConvention ->
ViennaConventionStyleSpeedLimitView(
modifier,
speedLimit,
MeasurementSpeedUnit.KilometersPerHour,
context,
formatter,
locale)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.stadiamaps.ferrostar.composeui.views.components.speedlimit

import android.content.Context
import android.icu.util.ULocale
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.stadiamaps.ferrostar.composeui.R
import com.stadiamaps.ferrostar.composeui.formatting.MeasurementSpeedFormatter
import com.stadiamaps.ferrostar.composeui.measurement.localizedString
import com.stadiamaps.ferrostar.composeui.support.GreenScreenPreview
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeed
import com.stadiamaps.ferrostar.core.measurement.MeasurementSpeedUnit

@Composable
fun USStyleSpeedLimitView(
modifier: Modifier = Modifier,
speedLimit: MeasurementSpeed,
units: MeasurementSpeedUnit = MeasurementSpeedUnit.MilesPerHour,
context: Context = LocalContext.current,
formatter: MeasurementSpeedFormatter = MeasurementSpeedFormatter(context, speedLimit),
locale: ULocale = ULocale.getDefault()
) {
val formattedSpeed = formatter.formattedValue(locale, units)

Box(
modifier =
modifier
.height(84.dp)
.width(60.dp)
.background(color = Color.White, shape = RoundedCornerShape(8.dp))
.padding(2.dp)) {
Box(
modifier =
Modifier.height(80.dp)
.width(56.dp)
.background(color = Color.Black, shape = RoundedCornerShape(6.dp))
.padding(2.dp)) {
Box(
modifier =
Modifier.height(76.dp)
.width(52.dp)
.background(color = Color.White, shape = RoundedCornerShape(4.dp))
.padding(4.dp)) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Text(
text = stringResource(R.string.speed).uppercase(),
fontSize = 9.sp,
lineHeight = 10.sp,
fontWeight = FontWeight.Bold,
color = Color.Black)

Text(
text = stringResource(R.string.limit).uppercase(),
fontSize = 9.sp,
lineHeight = 10.sp,
fontWeight = FontWeight.Bold,
color = Color.Black)

Spacer(modifier = Modifier.height(6.dp))

Text(
text = formattedSpeed,
fontSize = if (formattedSpeed.length > 3) 18.sp else 24.sp,
lineHeight = if (formattedSpeed.length > 3) 20.sp else 26.sp,
fontWeight = FontWeight.ExtraBold,
color = Color.Black,
textAlign = TextAlign.Center)

Text(
text = units.localizedString(context),
fontSize = 9.sp,
lineHeight = 10.sp,
fontWeight = FontWeight.Bold,
color = Color.Gray)
}
}
}
}
}

@GreenScreenPreview
@Composable
fun USStyleSpeedLimitViewLowSpeedPreview() {
USStyleSpeedLimitView(
modifier = Modifier.padding(16.dp).shadow(4.dp),
speedLimit = MeasurementSpeed(55.0, MeasurementSpeedUnit.MilesPerHour))
}

@GreenScreenPreview
@Composable
fun USStyleSpeedLimitViewModerateSpeedPreview() {
USStyleSpeedLimitView(
modifier = Modifier.padding(16.dp).shadow(4.dp),
speedLimit = MeasurementSpeed(100.0, MeasurementSpeedUnit.MilesPerHour))
}

@GreenScreenPreview
@Composable
fun USStyleSpeedLimitViewHighSpeedPreview() {
USStyleSpeedLimitView(
modifier = Modifier.padding(16.dp).shadow(4.dp),
speedLimit = MeasurementSpeed(1000.0, MeasurementSpeedUnit.MilesPerHour))
}
Loading
Loading