MindMachine: add timeline editor screen with pinch zoom and duration slider
This commit is contained in:
@@ -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") }
|
||||
|
||||
Reference in New Issue
Block a user