UX: seconds flash interval UI, hidden active controls, back-to-setup flow

This commit is contained in:
Tretzi
2026-03-10 23:34:07 -05:00
parent bb5d77fea3
commit e5117d2346
7 changed files with 91 additions and 12 deletions

View File

@@ -4,6 +4,7 @@ import android.app.Activity
import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
@@ -41,6 +42,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
@@ -72,6 +74,9 @@ import com.mindmachine.mvp.session.FlashColor
import com.mindmachine.mvp.session.MainViewModel
import com.mindmachine.mvp.session.SplitFlashFrame
import com.mindmachine.mvp.session.computeSplitFlashFrame
import com.mindmachine.mvp.session.flashIntervalMsToSeconds
import com.mindmachine.mvp.session.flashIntervalSecondsToMs
import com.mindmachine.mvp.session.shouldShowActiveControlsByDefault
import kotlin.math.roundToInt
private val MindMachineColors = darkColorScheme(
@@ -174,7 +179,11 @@ fun App(vm: MainViewModel = viewModel()) {
}, onHolder = { nav.navigate("holder") })
}
composable("active") {
ActiveSessionScreen(vm = vm, onFinish = { nav.navigate("complete") { popUpTo("setup") } })
ActiveSessionScreen(
vm = vm,
onFinish = { nav.navigate("complete") { popUpTo("setup") } },
onBackToSetup = { nav.popBackStack("setup", inclusive = false) }
)
}
composable("complete") {
SimpleScreen(
@@ -250,13 +259,13 @@ fun SetupScreen(vm: MainViewModel, onStart: () -> Unit, onHolder: () -> Unit) {
)
}
}
Text("Flash interval: ${cfg.flashIntervalMs} ms", color = MaterialTheme.colorScheme.onBackground)
Text("Flash interval: ${"%.2f".format(flashIntervalMsToSeconds(cfg.flashIntervalMs))} s", color = MaterialTheme.colorScheme.onBackground)
Slider(
value = cfg.flashIntervalMs.toFloat(),
onValueChange = { vm.setFlashIntervalMs(it.roundToInt()) },
valueRange = 50f..2000f
value = flashIntervalMsToSeconds(cfg.flashIntervalMs),
onValueChange = { vm.setFlashIntervalMs(flashIntervalSecondsToMs(it)) },
valueRange = 0.05f..2.0f
)
Text("Controls how long each frame is shown (red/green/black pattern).", color = MaterialTheme.colorScheme.onBackground)
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)
Slider(value = cfg.carrierFrequencyHz, onValueChange = vm::setCarrier, valueRange = 80f..400f)
Text("Difference ${cfg.binauralDifferenceHz} Hz", color = MaterialTheme.colorScheme.onBackground)
@@ -269,9 +278,14 @@ fun SetupScreen(vm: MainViewModel, onStart: () -> Unit, onHolder: () -> Unit) {
}
@Composable
fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit) {
fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup: () -> Unit) {
val ui by vm.ui.collectAsStateWithLifecycle()
if (ui.runtimeState == RuntimeState.COMPLETED || ui.runtimeState == RuntimeState.STOPPED) onFinish()
if (ui.runtimeState == RuntimeState.COMPLETED) onFinish()
BackHandler {
vm.stop()
onBackToSetup()
}
val isVisualMode = ui.config.mode != SessionMode.AUDIO_ONLY
val context = LocalContext.current
@@ -288,7 +302,10 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit) {
}
}
var showOverlay by remember { mutableStateOf(true) }
var showOverlay by remember(ui.runtimeState) { mutableStateOf(shouldShowActiveControlsByDefault(ui.runtimeState)) }
LaunchedEffect(ui.runtimeState) {
showOverlay = shouldShowActiveControlsByDefault(ui.runtimeState)
}
val flashFrame by rememberSplitFlashFrame(
isRunning = ui.runtimeState == RuntimeState.RUNNING,
mode = ui.config.mode,
@@ -340,7 +357,7 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit) {
} else {
Button(onClick = { vm.resume() }, modifier = Modifier.fillMaxWidth()) { Text("Resume") }
}
OutlinedButton(onClick = { vm.stop() }, modifier = Modifier.fillMaxWidth()) { Text("Stop") }
OutlinedButton(onClick = { vm.stop(); onFinish() }, modifier = Modifier.fillMaxWidth()) { Text("Stop") }
if (ui.interruptionReason != null) {
Text(ui.interruptionReason!!, color = Color.White)
if (ui.runtimeState == RuntimeState.INTERRUPTED) {