Skip to content

Commit b9d8dae

Browse files
eriedclaude
andcommitted
Motor sound: gate previews on parked + translate generic preset names
Preview safety - SettingsViewModel exposes engineParked (StateFlow<Boolean>) derived from WheelRepository.wheelData.speed and ConnectionState: disconnected = allowed, connected + |speed| < 0.5 km/h = allowed, connected + moving = blocked. Speed is the canonical telemetry field on every adapter (InMotion V1/V2, Begode, KingSong, Veteran, P6), so the gate works across the whole wheel catalog. - EngineSoundSection now takes a parked flag and nulls each section's onPreview lambda when the rider is riding. SegmentedChoice already hides the ▶ when onPreview is null, so the title-level previews silently disappear. EngineTypePicker got the same treatment. - When moving, a small hint sits under the engine-type field: "Previews disabled while moving — stop first to test sounds." Translations — generic preset names - 24 strings × 13 locales = 312 new entries. Translated names that have natural localizations: Tractor, Lawn mower, Helicopter, Steam locomotive, 2-stroke, 4-stroke single, Inline-4/6 silky, Quad bike, Quad ATV, City car, Diesel truck, Motorcycle, Big diesel, Car cruise, Broken exhaust, Jet turbine, Hover bike, F1 electric whine, UFO hover, Lightsaber. Plus the new engine_preview_parked_only hint. - Proper nouns stay English (V8 Cobra, V-twin Ducati, Aston Martin, Tron whine, Pod racer, Boxer 6). Storage stability — the AppSettings.engineType field stores the stable key like "SAMPLED_TRACTOR" or "JET_TURBINE". The picker resolves the display name at render time via engine_preset_<key.lowercase()> string resource lookup, so translations do NOT change what's persisted. A user switching device language picks up new labels with no migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 83e421f commit b9d8dae

17 files changed

Lines changed: 401 additions & 10 deletions

File tree

app/src/main/java/com/eried/eucplanet/ui/settings/SettingsScreen.kt

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ fun SettingsScreen(
147147
val maxSpeedCap by viewModel.maxSpeedCap.collectAsState()
148148
val ttsSwitchPrompt by viewModel.ttsSwitchPrompt.collectAsState()
149149
val isConnected by viewModel.isConnected.collectAsState()
150+
val engineParked by viewModel.engineParked.collectAsState()
150151
var searchQuery by rememberSaveable { mutableStateOf("") }
151152
val expandedSections = rememberSaveable(
152153
saver = androidx.compose.runtime.saveable.listSaver(
@@ -349,7 +350,7 @@ fun SettingsScreen(
349350
VoiceTab(settings, viewModel)
350351
},
351352
SectionDef("motor", titleMotor, Icons.Default.GraphicEq, corpusMotor) {
352-
EngineSoundSection(settings, viewModel)
353+
EngineSoundSection(settings, viewModel, engineParked)
353354
},
354355
SectionDef("cloud", titleCloud, Icons.Default.Archive, corpusCloud) {
355356
CloudTab(settings, viewModel)
@@ -1906,7 +1907,8 @@ private fun ButtonConfig(
19061907
@Composable
19071908
private fun EngineSoundSection(
19081909
settings: com.eried.eucplanet.data.model.AppSettings,
1909-
viewModel: SettingsViewModel
1910+
viewModel: SettingsViewModel,
1911+
parked: Boolean
19101912
) {
19111913
var showSafety by remember { mutableStateOf(false) }
19121914

@@ -1927,12 +1929,22 @@ private fun EngineSoundSection(
19271929
)
19281930

19291931
if (settings.engineSoundEnabled) {
1932+
// Previews push synthetic telemetry, which would clash with a live ride.
1933+
// Only allow ▶ while the wheel is disconnected or parked.
19301934
EngineTypePicker(
19311935
currentKey = settings.engineType,
19321936
onSelect = { viewModel.updateEngineType(it) },
1933-
onPreview = { viewModel.previewEngine(it) }
1937+
onPreview = if (parked) { { viewModel.previewEngine(it) } } else null
19341938
)
19351939

1940+
if (!parked) {
1941+
Text(
1942+
stringResource(R.string.engine_preview_parked_only),
1943+
style = MaterialTheme.typography.labelSmall,
1944+
color = MaterialTheme.colorScheme.onSurfaceVariant
1945+
)
1946+
}
1947+
19361948
SliderSetting(
19371949
label = stringResource(R.string.engine_volume),
19381950
value = settings.engineVolume * 100f,
@@ -1952,7 +1964,7 @@ private fun EngineSoundSection(
19521964
),
19531965
current = settings.engineMuffler,
19541966
onChange = { viewModel.updateEngineMuffler(it) },
1955-
onPreview = { viewModel.previewEngineSection("DEFAULT") }
1967+
onPreview = if (parked) { { viewModel.previewEngineSection("DEFAULT") } } else null
19561968
)
19571969

19581970
SegmentedChoice(
@@ -1964,7 +1976,7 @@ private fun EngineSoundSection(
19641976
),
19651977
current = settings.engineGearbox,
19661978
onChange = { viewModel.updateEngineGearbox(it) },
1967-
onPreview = { viewModel.previewEngineSection("GEARBOX") }
1979+
onPreview = if (parked) { { viewModel.previewEngineSection("GEARBOX") } } else null
19681980
)
19691981

19701982
SegmentedChoice(
@@ -1987,7 +1999,7 @@ private fun EngineSoundSection(
19871999
),
19882000
current = settings.engineDecelChar,
19892001
onChange = { viewModel.updateEngineDecelChar(it) },
1990-
onPreview = { viewModel.previewEngineSection("DECEL") }
2002+
onPreview = if (parked) { { viewModel.previewEngineSection("DECEL") } } else null
19912003
)
19922004

19932005
SegmentedChoice(
@@ -1999,7 +2011,7 @@ private fun EngineSoundSection(
19992011
),
20002012
current = settings.engineBrake,
20012013
onChange = { viewModel.updateEngineBrake(it) },
2002-
onPreview = { viewModel.previewEngineSection("BRAKE") }
2014+
onPreview = if (parked) { { viewModel.previewEngineSection("BRAKE") } } else null
20032015
)
20042016

20052017
SegmentedChoice(
@@ -2048,7 +2060,7 @@ private fun EngineSoundSection(
20482060
private fun EngineTypePicker(
20492061
currentKey: String,
20502062
onSelect: (String) -> Unit,
2051-
onPreview: (String) -> Unit
2063+
onPreview: ((String) -> Unit)? = null
20522064
) {
20532065
val ctx = androidx.compose.ui.platform.LocalContext.current
20542066
val res = ctx.resources
@@ -2079,8 +2091,10 @@ private fun EngineTypePicker(
20792091
style = MaterialTheme.typography.bodyMedium,
20802092
color = MaterialTheme.colorScheme.onSurfaceVariant
20812093
)
2082-
Spacer(Modifier.width(4.dp))
2083-
PlayButton(onClick = { onPreview(currentKey) })
2094+
if (onPreview != null) {
2095+
Spacer(Modifier.width(4.dp))
2096+
PlayButton(onClick = { onPreview(currentKey) })
2097+
}
20842098
}
20852099
Spacer(Modifier.height(6.dp))
20862100
ExposedDropdownMenuBox(

app/src/main/java/com/eried/eucplanet/ui/settings/SettingsViewModel.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
2020
import kotlinx.coroutines.flow.SharingStarted
2121
import kotlinx.coroutines.flow.StateFlow
2222
import kotlinx.coroutines.flow.asStateFlow
23+
import kotlinx.coroutines.flow.combine
2324
import kotlinx.coroutines.flow.map
2425
import kotlinx.coroutines.flow.stateIn
2526
import kotlinx.coroutines.launch
@@ -53,6 +54,17 @@ class SettingsViewModel @Inject constructor(
5354
.map { it == ConnectionState.CONNECTED }
5455
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
5556

57+
/**
58+
* Engine-sound preview buttons should only fire while the rider is parked,
59+
* because previewing pushes synthetic telemetry through the same audio path
60+
* as the real ride and would clash. Disconnected = no ride happening, so
61+
* preview is allowed. Connected + sub-walking-pace = parked.
62+
*/
63+
val engineParked: StateFlow<Boolean> = wheelRepository.wheelData
64+
.map { kotlin.math.abs(it.speed) < 0.5f }
65+
.combine(isConnected) { speedParked, conn -> !conn || speedParked }
66+
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), true)
67+
5668
val availableVoices: StateFlow<List<VoiceOption>> = voiceService.availableVoices
5769

5870
val currentLocation: StateFlow<Location?> = tripRepository.currentLocation

app/src/main/res/values-b+es+419/strings.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,30 @@
498498
<string name="engine_duck_mix">Mezclar</string>
499499
<string name="engine_headphones_only">Solo con auriculares</string>
500500
<string name="engine_headphones_only_hint">Omite el altavoz del teléfono. El motor suena solo cuando hay audio por cable o Bluetooth.</string>
501+
502+
<!-- Motor sound: generic preset names + parked hint -->
503+
<string name="engine_preview_parked_only">Vista previa desactivada mientras te mueves — detente para probar los sonidos.</string>
504+
<string name="engine_preset_two_stroke">2 tiempos</string>
505+
<string name="engine_preset_four_stroke_single">4 tiempos monocilíndrico</string>
506+
<string name="engine_preset_quad_bike">Cuatrimoto</string>
507+
<string name="engine_preset_inline_4">4 en línea</string>
508+
<string name="engine_preset_inline_6_silky">6 en línea (suave)</string>
509+
<string name="engine_preset_tractor">Tractor</string>
510+
<string name="engine_preset_steam_loco">Locomotora a vapor</string>
511+
<string name="engine_preset_jet_turbine">Turbina a reacción</string>
512+
<string name="engine_preset_ufo_hover">OVNI flotando</string>
513+
<string name="engine_preset_f1_electric">F1 eléctrico</string>
514+
<string name="engine_preset_hover_bike">Moto flotante</string>
515+
<string name="engine_preset_lightsaber">Sable de luz</string>
516+
<string name="engine_preset_sampled_diesel_iveco">Camión diésel</string>
517+
<string name="engine_preset_sampled_motorcycle">Motocicleta</string>
518+
<string name="engine_preset_sampled_city_car">Auto urbano</string>
519+
<string name="engine_preset_sampled_helicopter">Helicóptero</string>
520+
<string name="engine_preset_sampled_tractor">Tractor</string>
521+
<string name="engine_preset_sampled_lawnmower">Cortadora de césped</string>
522+
<string name="engine_preset_sampled_steam_loco">Locomotora a vapor</string>
523+
<string name="engine_preset_sampled_big_diesel">Diésel grande</string>
524+
<string name="engine_preset_sampled_car_cruise">Auto en ruta</string>
525+
<string name="engine_preset_sampled_broken_exhaust">Escape roto</string>
526+
<string name="engine_preset_sampled_quad_atv">Cuatrimoto</string>
501527
</resources>

app/src/main/res/values-da/strings.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,4 +462,30 @@
462462
<string name="engine_duck_mix">Miks</string>
463463
<string name="engine_headphones_only">Kun med hovedtelefoner</string>
464464
<string name="engine_headphones_only_hint">Springer telefonens højttaler over. Motor spiller kun, når kablet eller Bluetooth-lyd er tilsluttet.</string>
465+
466+
<!-- Motor sound: generic preset names + parked hint -->
467+
<string name="engine_preview_parked_only">Forhåndsvisning slået fra i bevægelse — stop for at teste lyde.</string>
468+
<string name="engine_preset_two_stroke">Totakt</string>
469+
<string name="engine_preset_four_stroke_single">Firtakt encylindret</string>
470+
<string name="engine_preset_quad_bike">Quad</string>
471+
<string name="engine_preset_inline_4">Række-4</string>
472+
<string name="engine_preset_inline_6_silky">Række-6 (silkeblød)</string>
473+
<string name="engine_preset_tractor">Traktor</string>
474+
<string name="engine_preset_steam_loco">Damplokomotiv</string>
475+
<string name="engine_preset_jet_turbine">Jet-turbine</string>
476+
<string name="engine_preset_ufo_hover">UFO hængende</string>
477+
<string name="engine_preset_f1_electric">F1 elektrisk</string>
478+
<string name="engine_preset_hover_bike">Svævecykel</string>
479+
<string name="engine_preset_lightsaber">Lyssværd</string>
480+
<string name="engine_preset_sampled_diesel_iveco">Dieselbil</string>
481+
<string name="engine_preset_sampled_motorcycle">Motorcykel</string>
482+
<string name="engine_preset_sampled_city_car">Bybil</string>
483+
<string name="engine_preset_sampled_helicopter">Helikopter</string>
484+
<string name="engine_preset_sampled_tractor">Traktor</string>
485+
<string name="engine_preset_sampled_lawnmower">Plæneklipper</string>
486+
<string name="engine_preset_sampled_steam_loco">Damplokomotiv</string>
487+
<string name="engine_preset_sampled_big_diesel">Stor diesel</string>
488+
<string name="engine_preset_sampled_car_cruise">Bil på tur</string>
489+
<string name="engine_preset_sampled_broken_exhaust">Defekt udstødning</string>
490+
<string name="engine_preset_sampled_quad_atv">Quad</string>
465491
</resources>

app/src/main/res/values-de/strings.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,4 +478,30 @@
478478
<string name="engine_duck_mix">Mischen</string>
479479
<string name="engine_headphones_only">Nur mit Kopfhörern</string>
480480
<string name="engine_headphones_only_hint">Spielt nicht über den Lautsprecher. Motorklang läuft nur, wenn kabelgebundenes oder Bluetooth-Audio verbunden ist.</string>
481+
482+
<!-- Motor sound: generic preset names + parked hint -->
483+
<string name="engine_preview_parked_only">Vorschau in Bewegung deaktiviert — zuerst anhalten, um Klänge zu testen.</string>
484+
<string name="engine_preset_two_stroke">2-Takt</string>
485+
<string name="engine_preset_four_stroke_single">4-Takt Einzylinder</string>
486+
<string name="engine_preset_quad_bike">Quad</string>
487+
<string name="engine_preset_inline_4">Reihen-4</string>
488+
<string name="engine_preset_inline_6_silky">Reihen-6 (seidig)</string>
489+
<string name="engine_preset_tractor">Traktor</string>
490+
<string name="engine_preset_steam_loco">Dampflok</string>
491+
<string name="engine_preset_jet_turbine">Jet-Turbine</string>
492+
<string name="engine_preset_ufo_hover">UFO schwebend</string>
493+
<string name="engine_preset_f1_electric">F1 elektrisch</string>
494+
<string name="engine_preset_hover_bike">Schwebebike</string>
495+
<string name="engine_preset_lightsaber">Lichtschwert</string>
496+
<string name="engine_preset_sampled_diesel_iveco">Diesel-LKW</string>
497+
<string name="engine_preset_sampled_motorcycle">Motorrad</string>
498+
<string name="engine_preset_sampled_city_car">Kleinwagen</string>
499+
<string name="engine_preset_sampled_helicopter">Hubschrauber</string>
500+
<string name="engine_preset_sampled_tractor">Traktor</string>
501+
<string name="engine_preset_sampled_lawnmower">Rasenmäher</string>
502+
<string name="engine_preset_sampled_steam_loco">Dampflok</string>
503+
<string name="engine_preset_sampled_big_diesel">Großer Diesel</string>
504+
<string name="engine_preset_sampled_car_cruise">Auto auf Reise</string>
505+
<string name="engine_preset_sampled_broken_exhaust">Kaputter Auspuff</string>
506+
<string name="engine_preset_sampled_quad_atv">Quad</string>
481507
</resources>

app/src/main/res/values-es/strings.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,4 +479,30 @@
479479
<string name="engine_duck_mix">Mezclar</string>
480480
<string name="engine_headphones_only">Solo con auriculares</string>
481481
<string name="engine_headphones_only_hint">Omite el altavoz del teléfono. El motor solo suena cuando hay audio por cable o Bluetooth conectado.</string>
482+
483+
<!-- Motor sound: generic preset names + parked hint -->
484+
<string name="engine_preview_parked_only">Vista previa deshabilitada mientras te mueves — detente para probar los sonidos.</string>
485+
<string name="engine_preset_two_stroke">2 tiempos</string>
486+
<string name="engine_preset_four_stroke_single">4 tiempos monocilíndrico</string>
487+
<string name="engine_preset_quad_bike">Quad</string>
488+
<string name="engine_preset_inline_4">4 en línea</string>
489+
<string name="engine_preset_inline_6_silky">6 en línea (suave)</string>
490+
<string name="engine_preset_tractor">Tractor</string>
491+
<string name="engine_preset_steam_loco">Locomotora de vapor</string>
492+
<string name="engine_preset_jet_turbine">Turbina a reacción</string>
493+
<string name="engine_preset_ufo_hover">OVNI flotando</string>
494+
<string name="engine_preset_f1_electric">F1 eléctrico</string>
495+
<string name="engine_preset_hover_bike">Moto flotante</string>
496+
<string name="engine_preset_lightsaber">Sable láser</string>
497+
<string name="engine_preset_sampled_diesel_iveco">Camión diésel</string>
498+
<string name="engine_preset_sampled_motorcycle">Motocicleta</string>
499+
<string name="engine_preset_sampled_city_car">Coche urbano</string>
500+
<string name="engine_preset_sampled_helicopter">Helicóptero</string>
501+
<string name="engine_preset_sampled_tractor">Tractor</string>
502+
<string name="engine_preset_sampled_lawnmower">Cortacésped</string>
503+
<string name="engine_preset_sampled_steam_loco">Locomotora de vapor</string>
504+
<string name="engine_preset_sampled_big_diesel">Diésel grande</string>
505+
<string name="engine_preset_sampled_car_cruise">Coche en ruta</string>
506+
<string name="engine_preset_sampled_broken_exhaust">Escape roto</string>
507+
<string name="engine_preset_sampled_quad_atv">Cuatrimoto</string>
482508
</resources>

app/src/main/res/values-fr/strings.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,4 +462,30 @@
462462
<string name="engine_duck_mix">Mixer</string>
463463
<string name="engine_headphones_only">Uniquement au casque</string>
464464
<string name="engine_headphones_only_hint">Ignore le haut-parleur du téléphone. Le moteur ne sonne que si un audio filaire ou Bluetooth est connecté.</string>
465+
466+
<!-- Motor sound: generic preset names + parked hint -->
467+
<string name="engine_preview_parked_only">Aperçu désactivé en mouvement — arrête-toi pour tester les sons.</string>
468+
<string name="engine_preset_two_stroke">2 temps</string>
469+
<string name="engine_preset_four_stroke_single">4 temps monocylindre</string>
470+
<string name="engine_preset_quad_bike">Quad</string>
471+
<string name="engine_preset_inline_4">4 en ligne</string>
472+
<string name="engine_preset_inline_6_silky">6 en ligne (soyeux)</string>
473+
<string name="engine_preset_tractor">Tracteur</string>
474+
<string name="engine_preset_steam_loco">Locomotive à vapeur</string>
475+
<string name="engine_preset_jet_turbine">Turbine à réaction</string>
476+
<string name="engine_preset_ufo_hover">OVNI en vol stationnaire</string>
477+
<string name="engine_preset_f1_electric">F1 électrique</string>
478+
<string name="engine_preset_hover_bike">Moto volante</string>
479+
<string name="engine_preset_lightsaber">Sabre laser</string>
480+
<string name="engine_preset_sampled_diesel_iveco">Camion diesel</string>
481+
<string name="engine_preset_sampled_motorcycle">Moto</string>
482+
<string name="engine_preset_sampled_city_car">Citadine</string>
483+
<string name="engine_preset_sampled_helicopter">Hélicoptère</string>
484+
<string name="engine_preset_sampled_tractor">Tracteur</string>
485+
<string name="engine_preset_sampled_lawnmower">Tondeuse</string>
486+
<string name="engine_preset_sampled_steam_loco">Locomotive à vapeur</string>
487+
<string name="engine_preset_sampled_big_diesel">Gros diesel</string>
488+
<string name="engine_preset_sampled_car_cruise">Voiture en croisière</string>
489+
<string name="engine_preset_sampled_broken_exhaust">Échappement cassé</string>
490+
<string name="engine_preset_sampled_quad_atv">Quad</string>
465491
</resources>

app/src/main/res/values-it/strings.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,30 @@
498498
<string name="engine_duck_mix">Mixa</string>
499499
<string name="engine_headphones_only">Solo con cuffie</string>
500500
<string name="engine_headphones_only_hint">Esclude lo speaker del telefono. Il motore suona solo se è collegato un audio cablato o Bluetooth.</string>
501+
502+
<!-- Motor sound: generic preset names + parked hint -->
503+
<string name="engine_preview_parked_only">Anteprime disattivate in movimento — fermati per provare i suoni.</string>
504+
<string name="engine_preset_two_stroke">2 tempi</string>
505+
<string name="engine_preset_four_stroke_single">4 tempi monocilindrico</string>
506+
<string name="engine_preset_quad_bike">Quad</string>
507+
<string name="engine_preset_inline_4">4 in linea</string>
508+
<string name="engine_preset_inline_6_silky">6 in linea (vellutato)</string>
509+
<string name="engine_preset_tractor">Trattore</string>
510+
<string name="engine_preset_steam_loco">Locomotiva a vapore</string>
511+
<string name="engine_preset_jet_turbine">Turbina a reazione</string>
512+
<string name="engine_preset_ufo_hover">UFO in stazionamento</string>
513+
<string name="engine_preset_f1_electric">F1 elettrica</string>
514+
<string name="engine_preset_hover_bike">Moto volante</string>
515+
<string name="engine_preset_lightsaber">Spada laser</string>
516+
<string name="engine_preset_sampled_diesel_iveco">Camion diesel</string>
517+
<string name="engine_preset_sampled_motorcycle">Motocicletta</string>
518+
<string name="engine_preset_sampled_city_car">Citycar</string>
519+
<string name="engine_preset_sampled_helicopter">Elicottero</string>
520+
<string name="engine_preset_sampled_tractor">Trattore</string>
521+
<string name="engine_preset_sampled_lawnmower">Tagliaerba</string>
522+
<string name="engine_preset_sampled_steam_loco">Locomotiva a vapore</string>
523+
<string name="engine_preset_sampled_big_diesel">Grosso diesel</string>
524+
<string name="engine_preset_sampled_car_cruise">Auto in viaggio</string>
525+
<string name="engine_preset_sampled_broken_exhaust">Marmitta rotta</string>
526+
<string name="engine_preset_sampled_quad_atv">Quad</string>
501527
</resources>

0 commit comments

Comments
 (0)