MindMachine: add timeline editor screen with pinch zoom and duration slider

This commit is contained in:
Tretzi
2026-03-19 15:56:50 -05:00
parent d441ead98b
commit a2669d6f59
5 changed files with 937 additions and 31 deletions

View File

@@ -8,6 +8,9 @@ import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
import com.mindmachine.mvp.session.TimelineEditorScreen
import com.mindmachine.mvp.session.TimelineModel
import com.mindmachine.mvp.session.TimelineParameterControl
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -184,10 +187,21 @@ fun App(vm: MainViewModel = viewModel()) {
}
}
composable("setup") {
SetupScreen(vm = vm, onStart = {
vm.startSession()
nav.navigate("active")
}, onHolder = { nav.navigate("holder") })
SetupScreen(
vm = vm,
onStart = {
vm.startSession()
nav.navigate("active")
},
onHolder = { nav.navigate("holder") },
onOpenTimelineEditor = { nav.navigate("timeline") },
)
}
composable("timeline") {
TimelineEditorScreen(
vm = vm,
onBack = { nav.popBackStack() }
)
}
composable("active") {
ActiveSessionScreen(
@@ -249,9 +263,17 @@ fun SafetyScreen(onAck: () -> Unit, onHolder: () -> Unit) {
}
@Composable
fun SetupScreen(vm: MainViewModel, onStart: () -> Unit, onHolder: () -> Unit) {
fun SetupScreen(
vm: MainViewModel,
onStart: () -> Unit,
onHolder: () -> Unit,
onOpenTimelineEditor: () -> Unit,
) {
val ui by vm.ui.collectAsStateWithLifecycle()
val cfg = ui.config
var model by remember(ui.selectedPreset.id, cfg.durationSec) {
mutableStateOf(TimelineModel.createForPreset(ui.selectedPreset, cfg.durationSec))
}
val scrollState = rememberScrollState()
val containerModifier = Modifier
.fillMaxSize()
@@ -260,36 +282,32 @@ fun SetupScreen(vm: MainViewModel, onStart: () -> Unit, onHolder: () -> Unit) {
.safeDrawingPadding()
.padding(horizontal = 16.dp, vertical = 12.dp)
TimelineParameterControl(
model = model,
onParameterChanged = { id: String, newValue: Float ->
model = model.updateParameterValue(id, newValue)
when (id) {
"flash_interval" -> vm.setFlashIntervalMs((newValue * 1000f).roundToInt())
"carrier_frequency" -> vm.setCarrier(newValue)
"binaural_difference" -> vm.setDifference(newValue)
}
},
modifier = containerModifier,
showTimeline = true,
onTimelineChanged = { newDuration ->
vm.setDurationMin((newDuration / 60f).roundToInt())
}
)
// Additional controls
Column(
containerModifier,
Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(ui.selectedPreset.name, style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground)
Text("Duration: ${cfg.durationSec / 60} min", color = MaterialTheme.colorScheme.onBackground)
Slider(value = (cfg.durationSec / 60).toFloat(), onValueChange = { vm.setDurationMin(it.roundToInt()) }, valueRange = 1f..30f)
Text("Mode", color = MaterialTheme.colorScheme.onBackground)
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
SessionMode.values().forEachIndexed { index, mode ->
SegmentedButton(
shape = androidx.compose.material3.SegmentedButtonDefaults.itemShape(index = index, count = SessionMode.values().size),
selected = cfg.mode == mode,
onClick = { vm.setMode(mode) },
label = { Text(mode.name.replace("_", " ")) }
)
}
OutlinedButton(onClick = onOpenTimelineEditor, modifier = Modifier.fillMaxWidth()) {
Text("Open Timeline Editor")
}
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 frequency: ${cfg.carrierFrequencyHz.roundToInt()}", color = MaterialTheme.colorScheme.onBackground)
Slider(value = cfg.carrierFrequencyHz, onValueChange = vm::setCarrier, valueRange = 80f..400f)
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") }