Auto commit Sun Mar 22 01:01:14 AM CDT 2026
This commit is contained in:
@@ -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") }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user