Auto commit Sun Mar 22 01:01:14 AM CDT 2026

This commit is contained in:
Tretzi
2026-03-22 01:01:14 -05:00
parent 88723bf214
commit ab5f4d5acf
7 changed files with 200 additions and 92 deletions

View File

@@ -79,7 +79,6 @@ import com.mindmachine.mvp.audio.HeadsetMonitor
import com.mindmachine.mvp.data.SettingsRepository
import com.mindmachine.mvp.data.UserProgramRepository
import com.mindmachine.mvp.domain.RuntimeState
import com.mindmachine.mvp.domain.SessionMode
import com.mindmachine.mvp.session.FlashColor
import com.mindmachine.mvp.session.MainViewModel
import com.mindmachine.mvp.session.SplitFlashFrame
@@ -144,6 +143,15 @@ fun App(vm: MainViewModel = viewModel()) {
onDispose { lifecycle.removeObserver(observer) }
}
if (!ui.settingsInitialized) {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
)
return
}
val startRoute = if (ui.settings.safetyAcknowledged) "home" else "welcome"
NavHost(navController = nav, startDestination = startRoute, modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
composable("welcome") {
@@ -235,10 +243,10 @@ fun App(vm: MainViewModel = viewModel()) {
}
}
composable("settings") {
SettingsScreen(vm) { nav.popBackStack() }
SettingsScreen(vm)
}
composable("holder") {
HolderScreen { nav.popBackStack() }
HolderScreen()
}
}
}
@@ -281,7 +289,6 @@ fun SetupScreen(
onHolder: () -> Unit,
) {
val ui by vm.ui.collectAsStateWithLifecycle()
val cfg = ui.config
val scrollState = rememberScrollState()
val containerModifier = Modifier
.fillMaxSize()
@@ -292,6 +299,12 @@ fun SetupScreen(
var programName by remember(ui.selectedPreset.id) { mutableStateOf(ui.selectedPreset.name) }
val hasUnsavedChanges = setupScreenHasUnsavedChanges(
hasUnsavedTimelineChanges = ui.hasUnsavedChanges,
editedProgramName = programName,
savedProgramName = ui.savedProgramName,
)
Column(
modifier = containerModifier,
verticalArrangement = Arrangement.spacedBy(14.dp),
@@ -323,10 +336,13 @@ fun SetupScreen(
TextButton(onClick = onHolder) { Text("Holder Guidance") }
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
OutlinedButton(onClick = { vm.saveCurrentProgram(programName) }, modifier = Modifier.weight(1f).height(52.dp)) {
Text("Save")
if (hasUnsavedChanges) {
OutlinedButton(onClick = { vm.saveCurrentProgram(programName) }, modifier = Modifier.weight(1f).height(52.dp)) {
Text("Save")
}
} else {
Button(onClick = onStart, modifier = Modifier.weight(1f).height(52.dp)) { Text("Start") }
}
Button(onClick = onStart, modifier = Modifier.weight(1f).height(52.dp)) { Text("Start") }
}
if (ui.error != null) Text(ui.error!!, color = if (ui.error!!.startsWith("Saved")) Color(0xFF72E39A) else MaterialTheme.colorScheme.error)
Spacer(Modifier.height(8.dp))
@@ -336,6 +352,15 @@ fun SetupScreen(
internal fun setupScreenFlashIntervalLabel(flashIntervalMs: Int): String =
"Flash interval: ${"%.2f".format(flashIntervalMsToSeconds(flashIntervalMs))} s"
internal fun setupScreenHasUnsavedChanges(
hasUnsavedTimelineChanges: Boolean,
editedProgramName: String,
savedProgramName: String,
): Boolean {
val nameChanged = editedProgramName.trim() != savedProgramName.trim()
return hasUnsavedTimelineChanges || nameChanged
}
internal fun setupScreenUsesScrollableContainer(): Boolean = true
@Composable
@@ -348,7 +373,7 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup:
onBackToSetup()
}
val isVisualMode = ui.config.mode != SessionMode.AUDIO_ONLY
val isVisualMode = true
val context = LocalContext.current
DisposableEffect(isVisualMode) {
val activity = context as? Activity
@@ -376,7 +401,6 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup:
}
val flashFrame by rememberSplitFlashFrame(
isRunning = ui.runtimeState == RuntimeState.RUNNING,
mode = ui.config.mode,
flashOnMs = ui.runtimeParams.flashOnMs,
flashOffMs = ui.runtimeParams.flashOffMs,
)
@@ -506,15 +530,14 @@ private fun FlashColor.toComposeColor(): Color = when (this) {
@Composable
private fun rememberSplitFlashFrame(
isRunning: Boolean,
mode: SessionMode,
flashOnMs: Int,
flashOffMs: Int,
): androidx.compose.runtime.State<SplitFlashFrame> {
val latestFlashOnMs = rememberUpdatedState(flashOnMs)
val latestFlashOffMs = rememberUpdatedState(flashOffMs)
return produceState(initialValue = SplitFlashFrame(FlashColor.BLACK, FlashColor.BLACK), isRunning, mode) {
if (!isRunning || mode == SessionMode.AUDIO_ONLY) {
return produceState(initialValue = SplitFlashFrame(FlashColor.BLACK, FlashColor.BLACK), isRunning) {
if (!isRunning) {
value = SplitFlashFrame(FlashColor.BLACK, FlashColor.BLACK)
return@produceState
}
@@ -535,7 +558,7 @@ private fun rememberSplitFlashFrame(
}
@Composable
fun SettingsScreen(vm: MainViewModel, onBack: () -> Unit) {
fun SettingsScreen(vm: MainViewModel) {
val ui by vm.ui.collectAsStateWithLifecycle()
Column(
Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background).padding(16.dp),
@@ -551,16 +574,12 @@ fun SettingsScreen(vm: MainViewModel, onBack: () -> Unit) {
steps = 9,
)
Text("Default mode", color = MaterialTheme.colorScheme.onBackground)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
SessionMode.values().forEach { mode ->
OutlinedButton(onClick = { vm.updateDefaultMode(mode) }) { Text(mode.name) }
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = ui.settings.showHolderGuidanceBeforeSession, onCheckedChange = vm::updateGuidance)
Text("Show holder guidance before session", color = MaterialTheme.colorScheme.onBackground)
Checkbox(
checked = ui.settings.forceAudioWithoutHeadphones,
onCheckedChange = vm::updateForceAudioWithoutHeadphones,
)
Text("force audio without headphones", color = MaterialTheme.colorScheme.onBackground)
}
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -570,12 +589,11 @@ fun SettingsScreen(vm: MainViewModel, onBack: () -> Unit) {
HorizontalDivider()
Text("About/Disclaimer: MindMachine is a prototype and not a medical device.", color = MaterialTheme.colorScheme.onBackground)
OutlinedButton(onClick = onBack) { Text("Back") }
}
}
@Composable
fun HolderScreen(onBack: () -> Unit) {
fun HolderScreen() {
Column(
Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background).padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -587,6 +605,5 @@ fun HolderScreen(onBack: () -> Unit) {
Text("• Allow airflow and comfort.", color = MaterialTheme.colorScheme.onBackground)
Text("• Test fit before session.", color = MaterialTheme.colorScheme.onBackground)
Text("• Sit or lie down in a safe place.", color = MaterialTheme.colorScheme.onBackground)
OutlinedButton(onClick = onBack) { Text("Back") }
}
}