Skip to content

Commit

Permalink
Android speed limit view (#397)
Browse files Browse the repository at this point in the history
* feat: android speed limit view

* feat: android speed limit view

* feat: android speed limit view
  • Loading branch information
Archdoog authored Dec 17, 2024
1 parent 62d39df commit 7c39ea6
Show file tree
Hide file tree
Showing 38 changed files with 641 additions and 110 deletions.
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,
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

0 comments on commit 7c39ea6

Please sign in to comment.