package com.mindmachine.mvp 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 import com.mindmachine.mvp.session.DurationSliderCard import com.mindmachine.mvp.session.SetupTimelineEditor import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField 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.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.mindmachine.mvp.audio.BinauralAudioEngine 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.session.FlashColor import com.mindmachine.mvp.session.MainViewModel import com.mindmachine.mvp.session.SplitFlashFrame import com.mindmachine.mvp.session.SplitFlashSequencer import com.mindmachine.mvp.session.flashIntervalMsToSeconds import com.mindmachine.mvp.session.flashIntervalSecondsToMs import com.mindmachine.mvp.session.formatDuration import com.mindmachine.mvp.session.sessionProgressFraction import com.mindmachine.mvp.session.shouldShowActiveControlsByDefault import com.mindmachine.mvp.session.shouldUseImmersiveFullscreen import kotlinx.coroutines.delay import kotlin.math.roundToInt import java.util.Locale private val MindMachineColors = darkColorScheme( primary = Color(0xFF8AB4FF), onPrimary = Color(0xFF001B3D), primaryContainer = Color(0xFF004689), onPrimaryContainer = Color(0xFFD7E3FF), secondary = Color(0xFFFFD166), onSecondary = Color(0xFF2E2300), background = Color(0xFF05070D), onBackground = Color(0xFFF3F6FF), surface = Color(0xFF121621), onSurface = Color(0xFFF3F6FF), error = Color(0xFFFF6B6B), onError = Color(0xFF3A0002) ) class MainActivity : ComponentActivity() { private val vm: MainViewModel by viewModels { MainViewModel.Factory( SettingsRepository(applicationContext), UserProgramRepository(applicationContext), HeadsetMonitor(applicationContext), BinauralAudioEngine(), ) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme(colorScheme = MindMachineColors) { App(vm) } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun App(vm: MainViewModel = viewModel()) { val nav = rememberNavController() val ui by vm.ui.collectAsStateWithLifecycle() val lifecycle = LocalLifecycleOwner.current.lifecycle val activity = LocalContext.current as? Activity DisposableEffect(lifecycle, activity, ui.runtimeState) { val observer = LifecycleEventObserver { _, event -> val appActuallyBackgrounded = activity?.isChangingConfigurations != true if ( event == Lifecycle.Event.ON_STOP && ui.runtimeState == RuntimeState.RUNNING && appActuallyBackgrounded ) { vm.pause("Session paused because app moved to background.") } } lifecycle.addObserver(observer) 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") { SimpleScreen("MindMachine", "Blinking light + binaural audio. Stereo headphones required for binaural mode. Not a medical device.") { Button(onClick = { nav.navigate("safety") }) { Text("Continue") } } } composable("safety") { SafetyScreen( onAck = { vm.acknowledgeSafety() nav.navigate("home") { popUpTo(0) } }, onHolder = { nav.navigate("holder") } ) } composable("home") { var confirmDeleteProgramId by remember { mutableStateOf(null) } var confirmDeleteProgramName by remember { mutableStateOf("") } Column( Modifier .fillMaxSize() .safeDrawingPadding() ) { TopAppBar(title = { Text("MindMachine") }, actions = { IconButton( onClick = { vm.createNewProgramDraft() nav.navigate("setup") } ) { Icon(Icons.Filled.Add, contentDescription = "Add") } IconButton(onClick = { nav.navigate("settings") }) { Icon(Icons.Filled.Settings, contentDescription = "Settings") } }) LazyColumn(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { item { TextButton(onClick = { nav.navigate("holder") }) { Text("User Guidance/Disclaimer") } } items(ui.presets) { p -> Card( colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface ), modifier = Modifier.fillMaxWidth().clickable { vm.choosePreset(p.id) nav.navigate("setup") }.padding(4.dp) ) { Row( modifier = Modifier.fillMaxWidth().padding(12.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Column(Modifier.weight(1f)) { Text(p.name, style = MaterialTheme.typography.titleMedium) Text(p.description) Text("${formatDuration(p.defaultDurationSec)} • ${p.visualPatternType} • binaural ${formatMaxTwoDecimals(p.binauralDifferenceHz)}") } Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( imageVector = Icons.Filled.PlayArrow, contentDescription = null, tint = Color(0xFF4CAF50), modifier = Modifier.size(30.dp) ) Box( modifier = Modifier .size(24.dp) .clickable { confirmDeleteProgramId = p.id confirmDeleteProgramName = p.name }, contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Filled.Delete, contentDescription = "Delete ${p.name}", tint = Color(0xFFFF5252), modifier = Modifier.fillMaxSize() ) } } } } } } } if (confirmDeleteProgramId != null) { AlertDialog( onDismissRequest = { confirmDeleteProgramId = null confirmDeleteProgramName = "" }, title = { Text("Delete program?") }, text = { Text("Delete \"$confirmDeleteProgramName\"?") }, confirmButton = { TextButton(onClick = { confirmDeleteProgramId?.let(vm::deleteProgram) confirmDeleteProgramId = null confirmDeleteProgramName = "" }) { Text("Delete", color = Color(0xFFFF5252)) } }, dismissButton = { TextButton(onClick = { confirmDeleteProgramId = null confirmDeleteProgramName = "" }) { Text("Cancel") } } ) } } composable("setup") { SetupScreen( vm = vm, onStart = { val started = vm.startSession() if (started) { nav.navigate("active") } }, ) } composable("active") { ActiveSessionScreen( vm = vm, onFinish = { nav.navigate("complete") { popUpTo("setup") } }, onBackToSetup = { nav.popBackStack("setup", inclusive = false) } ) } composable("complete") { SimpleScreen( if (ui.endedEarly) "Session ended" else "Session complete.", ui.selectedPreset.name ) { Button(onClick = { val started = vm.startSession() if (started) { nav.navigate("active") } }) { Text("Repeat Session") } OutlinedButton(onClick = { nav.navigate("home") { popUpTo(0) } }) { Text("Return Home") } } } composable("settings") { SettingsScreen(vm) } composable("holder") { HolderScreen() } } } @Composable fun SimpleScreen(title: String, subtitle: String, actions: @Composable ColumnScope.() -> Unit) { Column( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .safeDrawingPadding() .padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text(title, style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.onBackground) Text(subtitle, color = MaterialTheme.colorScheme.onBackground) actions() } } @Composable fun SafetyScreen(onAck: () -> Unit, onHolder: () -> Unit) { var checked by remember { mutableStateOf(false) } Column( Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .safeDrawingPadding() .padding(16.dp) ) { Text("Read before using MindMachine", style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground) Spacer(Modifier.height(12.dp)) Text( "Flashing lights may be unsafe for people with epilepsy, seizure sensitivity, or migraine triggers.\n\nDo not use while driving, walking, cycling, or operating machinery.\n\nStop immediately for discomfort, dizziness, headache, nausea, anxiety, or eye strain.\n\nBinaural mode requires stereo headphones.\n\nThis app is not a medical device.", color = MaterialTheme.colorScheme.onBackground ) Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(checked = checked, onCheckedChange = { checked = it }) Text("I understand the risks and will stop immediately if I feel discomfort.", color = MaterialTheme.colorScheme.onBackground) } Button(onClick = onAck, enabled = checked, modifier = Modifier.semantics { contentDescription = "I Understand" }) { Text("I Understand") } TextButton(onClick = onHolder) { Text("Holder Guidance") } } } @Composable fun SetupScreen( vm: MainViewModel, onStart: () -> Unit, ) { val ui by vm.ui.collectAsStateWithLifecycle() val scrollState = rememberScrollState() val containerModifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .windowInsetsPadding(WindowInsets.statusBars) .windowInsetsPadding(WindowInsets.navigationBars) .then(if (setupScreenUsesScrollableContainer()) Modifier.verticalScroll(scrollState) else Modifier) .padding(horizontal = 16.dp, vertical = 12.dp) 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), ) { Text( ui.selectedPreset.name, style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground, ) TextField( value = programName, onValueChange = { programName = it }, label = { Text("Program name") }, modifier = Modifier.fillMaxWidth(), ) DurationSliderCard( durationSec = ui.timeline.durationSec, onDurationSecChanged = { vm.setDurationSec(it) }, ) // Integrated timeline editor (no separate screen). SetupTimelineEditor( state = ui.timeline, onStateChanged = vm::updateTimeline, onCurveGranularityChanged = vm::setCurveGranularitySec, ) Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { 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") } } } if (ui.error != null) Text(ui.error!!, color = if (ui.error!!.startsWith("Saved")) Color(0xFF72E39A) else MaterialTheme.colorScheme.error) Spacer(Modifier.height(8.dp)) } } internal fun setupScreenFlashIntervalLabel(flashIntervalMs: Int): String = "Flash interval: ${"%.2f".format(flashIntervalMsToSeconds(flashIntervalMs))} s" internal fun formatMaxTwoDecimals(value: Float): String = String.format(Locale.US, "%.2f", value).trimEnd('0').trimEnd('.') 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 fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup: () -> Unit) { val ui by vm.ui.collectAsStateWithLifecycle() if (ui.runtimeState == RuntimeState.COMPLETED) onFinish() BackHandler { vm.stop() onBackToSetup() } val isVisualMode = true val context = LocalContext.current DisposableEffect(isVisualMode) { val activity = context as? Activity val previous = activity?.requestedOrientation if (isVisualMode) { activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE } onDispose { if (isVisualMode) { activity?.requestedOrientation = previous ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } } } val immersiveFullscreen = shouldUseImmersiveFullscreen(isVisualMode, ui.runtimeState) var revealSystemBarsSignal by remember { mutableIntStateOf(0) } ApplyImmersiveMode( enabled = immersiveFullscreen, revealSignal = revealSystemBarsSignal, ) var showOverlay by remember(ui.runtimeState) { mutableStateOf(shouldShowActiveControlsByDefault(ui.runtimeState)) } LaunchedEffect(ui.runtimeState) { showOverlay = shouldShowActiveControlsByDefault(ui.runtimeState) } var brightnessOverlayVisible by remember { mutableStateOf(false) } var brightnessOverlayToken by remember { mutableIntStateOf(0) } LaunchedEffect(brightnessOverlayToken) { if (!brightnessOverlayVisible) return@LaunchedEffect delay(2500) brightnessOverlayVisible = false } ApplyScreenBrightnessOverride( enabled = true, brightnessPercent = ui.settings.immersiveBrightnessPercent, ) val flashFrame by rememberSplitFlashFrame( isRunning = ui.runtimeState == RuntimeState.RUNNING, flashOnMs = ui.runtimeParams.flashOnMs, flashOffMs = ui.runtimeParams.flashOffMs, ) val progress = sessionProgressFraction( durationSec = ui.timeline.durationSec, remainingSec = ui.remainingSec, ) Box( modifier = Modifier .fillMaxSize() .background(Color.Black) .then(if (immersiveFullscreen) Modifier else Modifier.safeDrawingPadding()) .pointerInput(immersiveFullscreen) { detectTapGestures { brightnessOverlayVisible = true brightnessOverlayToken++ if (!immersiveFullscreen) { showOverlay = !showOverlay } } } ) { if (isVisualMode) { Row(Modifier.fillMaxSize()) { Box( modifier = Modifier .weight(1f) .fillMaxHeight() .background(flashFrame.left.toComposeColor()) ) Box( modifier = Modifier .weight(1f) .fillMaxHeight() .background(flashFrame.right.toComposeColor()) ) } } if (ui.runtimeState == RuntimeState.COUNTDOWN) { Text( "${ui.countdownSec}", modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.displayLarge, color = Color.White ) } if (immersiveFullscreen && ui.settings.showImmersiveProgressBar) { LinearProgressIndicator( progress = { progress }, modifier = Modifier .align(Alignment.BottomCenter) .fillMaxWidth() .height(5.dp), trackColor = Color.White.copy(alpha = 0.2f), color = Color.White, ) } if (brightnessOverlayVisible) { Column( Modifier .align(Alignment.TopCenter) .padding(top = 24.dp) .fillMaxWidth(0.45f) .background(Color.Black.copy(alpha = 0.72f)) .padding(horizontal = 12.dp, vertical = 10.dp) ) { Text( text = "Brightness ${ui.settings.immersiveBrightnessPercent}%", color = Color.White, style = MaterialTheme.typography.bodyMedium, ) Slider( value = ui.settings.immersiveBrightnessPercent.toFloat(), onValueChange = { vm.updateImmersiveBrightnessPercent(it.roundToInt()) brightnessOverlayVisible = true brightnessOverlayToken++ }, valueRange = 5f..100f, ) } } if (showOverlay) { Column( Modifier .align(Alignment.BottomCenter) .fillMaxWidth() .background(Color.Black.copy(alpha = 0.72f)) .padding(16.dp) ) { Text("${ui.selectedPreset.name} • ${ui.remainingSec}s", color = Color.White) if (ui.runtimeState == RuntimeState.RUNNING) { Button(onClick = { vm.pause() }, modifier = Modifier.fillMaxWidth()) { Text("Pause") } } else { Button(onClick = { vm.resume() }, modifier = Modifier.fillMaxWidth()) { Text("Resume") } } 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) { OutlinedButton(onClick = { vm.switchToVisualOnlyAndResume() }, modifier = Modifier.fillMaxWidth()) { Text("Resume Visual-only") } } } } } } } @Composable private fun ApplyScreenBrightnessOverride(enabled: Boolean, brightnessPercent: Int) { val activity = LocalContext.current as? Activity ?: return val window = activity.window DisposableEffect(window) { onDispose { val reset = window.attributes reset.screenBrightness = -1f window.attributes = reset } } LaunchedEffect(enabled, brightnessPercent) { val params = window.attributes params.screenBrightness = if (enabled) { (brightnessPercent.coerceIn(5, 100) / 100f) } else { -1f } window.attributes = params } } @Composable private fun ApplyImmersiveMode(enabled: Boolean, revealSignal: Int) { val context = LocalContext.current val activity = context as? Activity ?: return val window = activity.window val controller = remember(window) { WindowInsetsControllerCompat(window, window.decorView) } DisposableEffect(window) { onDispose { controller.show(WindowInsetsCompat.Type.systemBars()) WindowCompat.setDecorFitsSystemWindows(window, true) } } LaunchedEffect(enabled) { if (enabled) { WindowCompat.setDecorFitsSystemWindows(window, false) controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE controller.hide(WindowInsetsCompat.Type.systemBars()) } else { controller.show(WindowInsetsCompat.Type.systemBars()) WindowCompat.setDecorFitsSystemWindows(window, true) } } LaunchedEffect(revealSignal, enabled) { if (!enabled || revealSignal == 0) return@LaunchedEffect controller.show(WindowInsetsCompat.Type.systemBars()) delay(2500) if (enabled) { controller.hide(WindowInsetsCompat.Type.systemBars()) } } } private fun FlashColor.toComposeColor(): Color = when (this) { FlashColor.RED -> Color.Red FlashColor.GREEN -> Color.Green FlashColor.BLACK -> Color.Black } @Composable private fun rememberSplitFlashFrame( isRunning: Boolean, flashOnMs: Int, flashOffMs: Int, ): androidx.compose.runtime.State { val latestFlashOnMs = rememberUpdatedState(flashOnMs) val latestFlashOffMs = rememberUpdatedState(flashOffMs) return produceState(initialValue = SplitFlashFrame(FlashColor.BLACK, FlashColor.BLACK), isRunning) { if (!isRunning) { value = SplitFlashFrame(FlashColor.BLACK, FlashColor.BLACK) return@produceState } val sequencer = SplitFlashSequencer( startTimeNanos = System.nanoTime(), flashOnMs = latestFlashOnMs.value, flashOffMs = latestFlashOffMs.value, ) while (true) { withFrameNanos { frameTimeNanos -> sequencer.updateIntervals(latestFlashOnMs.value, latestFlashOffMs.value) value = sequencer.frameAt(frameTimeNanos) } } } } @Composable fun SettingsScreen(vm: MainViewModel) { val ui by vm.ui.collectAsStateWithLifecycle() val scrollState = rememberScrollState() Column( Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .safeDrawingPadding() .verticalScroll(scrollState) .padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text("Settings", style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground) Text("Countdown: ${ui.settings.countdownSeconds}s", color = MaterialTheme.colorScheme.onBackground) Slider( value = ui.settings.countdownSeconds.toFloat(), onValueChange = { vm.updateCountdownSeconds(it.roundToInt()) }, valueRange = 0f..10f, steps = 9, ) Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( checked = ui.settings.forceAudioWithoutHeadphones, onCheckedChange = vm::updateForceAudioWithoutHeadphones, ) Text("force audio without headphones", color = MaterialTheme.colorScheme.onBackground) } Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(checked = ui.settings.showImmersiveProgressBar, onCheckedChange = vm::updateShowImmersiveProgressBar) Text("Show immersive bottom progress bar", color = MaterialTheme.colorScheme.onBackground) } HorizontalDivider() Text("About/Disclaimer: MindMachine is a prototype and not a medical device.", color = MaterialTheme.colorScheme.onBackground) } } @Composable fun HolderScreen() { Column( Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .safeDrawingPadding() .padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Text("Holder Guidance", style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground) Text("• Use a simple cardboard visor/holder.", color = MaterialTheme.colorScheme.onBackground) Text("• Keep phone stable and hands-free.", color = MaterialTheme.colorScheme.onBackground) Text("• Do not press device against eyes/face.", color = MaterialTheme.colorScheme.onBackground) 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) } }