Fix setup UX: remove Hz wording and make setup scrollable

This commit is contained in:
Tretzi
2026-03-10 23:42:55 -05:00
parent e5117d2346
commit fecd29ac3d
4 changed files with 45 additions and 9 deletions

View File

@@ -20,6 +20,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
@@ -165,7 +169,7 @@ fun App(vm: MainViewModel = viewModel()) {
Column(Modifier.padding(12.dp)) {
Text(p.name, style = MaterialTheme.typography.titleMedium)
Text(p.description)
Text("${p.defaultDurationSec / 60} min • ${p.visualPatternType}${p.binauralDifferenceHz} Hz")
Text("${p.defaultDurationSec / 60} min • ${p.visualPatternType} binaural ${p.binauralDifferenceHz}")
}
}
}
@@ -241,8 +245,16 @@ fun SafetyScreen(onAck: () -> Unit, onHolder: () -> Unit) {
fun SetupScreen(vm: MainViewModel, onStart: () -> Unit, onHolder: () -> Unit) {
val ui by vm.ui.collectAsStateWithLifecycle()
val cfg = ui.config
val scrollState = rememberScrollState()
val containerModifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.then(if (setupScreenUsesScrollableContainer()) Modifier.verticalScroll(scrollState) else Modifier)
.safeDrawingPadding()
.padding(horizontal = 16.dp, vertical = 12.dp)
Column(
Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background).padding(16.dp),
containerModifier,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(ui.selectedPreset.name, style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground)
@@ -259,24 +271,30 @@ fun SetupScreen(vm: MainViewModel, onStart: () -> Unit, onHolder: () -> Unit) {
)
}
}
Text("Flash interval: ${"%.2f".format(flashIntervalMsToSeconds(cfg.flashIntervalMs))} s", color = MaterialTheme.colorScheme.onBackground)
Text(setupScreenFlashIntervalLabel(cfg.flashIntervalMs), color = MaterialTheme.colorScheme.onBackground)
Slider(
value = flashIntervalMsToSeconds(cfg.flashIntervalMs),
onValueChange = { vm.setFlashIntervalMs(flashIntervalSecondsToMs(it)) },
valueRange = 0.05f..2.0f
)
Text("Controls how long each frame is shown in the red/green/black pattern.", color = MaterialTheme.colorScheme.onBackground)
Text("Carrier ${cfg.carrierFrequencyHz.roundToInt()} Hz", color = MaterialTheme.colorScheme.onBackground)
Text("Carrier frequency: ${cfg.carrierFrequencyHz.roundToInt()}", color = MaterialTheme.colorScheme.onBackground)
Slider(value = cfg.carrierFrequencyHz, onValueChange = vm::setCarrier, valueRange = 80f..400f)
Text("Difference ${cfg.binauralDifferenceHz} Hz", color = MaterialTheme.colorScheme.onBackground)
Text("Binaural difference: ${cfg.binauralDifferenceHz}", color = MaterialTheme.colorScheme.onBackground)
Slider(value = cfg.binauralDifferenceHz, onValueChange = vm::setDifference, valueRange = 0.5f..20f)
Text("Brightness recommendation: keep screen comfortable and avoid eye strain.", color = MaterialTheme.colorScheme.onBackground)
TextButton(onClick = onHolder) { Text("Holder Guidance") }
if (ui.error != null) Text(ui.error!!, color = MaterialTheme.colorScheme.error)
Button(onClick = onStart, modifier = Modifier.fillMaxWidth().height(52.dp)) { Text("Start") }
Spacer(Modifier.height(8.dp))
}
}
internal fun setupScreenFlashIntervalLabel(flashIntervalMs: Int): String =
"Flash interval: ${"%.2f".format(flashIntervalMsToSeconds(flashIntervalMs))} s"
internal fun setupScreenUsesScrollableContainer(): Boolean = true
@Composable
fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup: () -> Unit) {
val ui by vm.ui.collectAsStateWithLifecycle()

View File

@@ -7,8 +7,8 @@ object SessionValidator {
fun validate(config: SessionConfig, headsetAvailable: Boolean): String? {
if (config.durationSec !in 60..(30 * 60)) return "Duration must be 1 to 30 minutes."
if (config.flashIntervalMs !in 50..2000) return "Flash interval must be 0.05 to 2.00 seconds."
if (config.carrierFrequencyHz !in 80f..400f) return "Carrier frequency must be 80 to 400 Hz."
if (config.binauralDifferenceHz !in 0.5f..20f) return "Binaural difference must be 0.5 to 20 Hz."
if (config.carrierFrequencyHz !in 80f..400f) return "Carrier frequency must be between 80 and 400."
if (config.binauralDifferenceHz !in 0.5f..20f) return "Binaural difference must be between 0.5 and 20."
if (config.mode != SessionMode.VISUAL_ONLY && !headsetAvailable) {
return "Stereo headphones are required for binaural audio. Connect headphones or switch to Visual-only."
}