package com.mindmachine.mvp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.background import androidx.compose.foundation.clickable 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenu import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.mindmachine.mvp.audio.BinauralAudioEngine import com.mindmachine.mvp.audio.HeadsetMonitor import com.mindmachine.mvp.data.SettingsRepository import com.mindmachine.mvp.domain.CountdownPreference import com.mindmachine.mvp.domain.RuntimeState import com.mindmachine.mvp.domain.SessionMode import com.mindmachine.mvp.session.MainViewModel import kotlin.math.roundToInt class MainActivity : ComponentActivity() { private val vm: MainViewModel by viewModels { MainViewModel.Factory(SettingsRepository(applicationContext), HeadsetMonitor(applicationContext), BinauralAudioEngine()) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { App(vm) } } } @Composable fun App(vm: MainViewModel = viewModel()) { val nav = rememberNavController() val ui by vm.ui.collectAsStateWithLifecycle() val lifecycle = LocalLifecycleOwner.current.lifecycle androidx.compose.runtime.DisposableEffect(lifecycle) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_STOP && ui.runtimeState == RuntimeState.RUNNING) { vm.pause("Session paused because app moved to background.") } } lifecycle.addObserver(observer) onDispose { lifecycle.removeObserver(observer) } } val startRoute = if (ui.settings.safetyAcknowledged) "home" else "welcome" NavHost(navController = nav, startDestination = startRoute) { 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") { Column(Modifier.fillMaxSize()) { TopAppBar(title = { Text("MindMachine") }, actions = { TextButton(onClick = { nav.navigate("settings") }) { Text("Settings") } }) LazyColumn(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { item { TextButton(onClick = { nav.navigate("holder") }) { Text("Holder Guidance") } } items(ui.presets) { p -> Card(Modifier.fillMaxWidth().clickable { vm.choosePreset(p.id) nav.navigate("setup") }.padding(4.dp)) { Column(Modifier.padding(12.dp)) { Text(p.name, style = MaterialTheme.typography.titleMedium) Text(p.description) Text("${p.defaultDurationSec / 60} min • ${p.visualPatternType} • ${p.binauralDifferenceHz} Hz") } } } } } } composable("setup") { SetupScreen(vm = vm, onStart = { vm.startSession() nav.navigate("active") }, onHolder = { nav.navigate("holder") }) } composable("active") { ActiveSessionScreen(vm = vm, onFinish = { nav.navigate("complete") { popUpTo("setup") } }) } composable("complete") { SimpleScreen( if (ui.endedEarly) "Session ended" else "Session complete.", ui.selectedPreset.name ) { Button(onClick = { vm.startSession() nav.navigate("active") }) { Text("Repeat Session") } OutlinedButton(onClick = { nav.navigate("home") { popUpTo(0) } }) { Text("Return Home") } } } composable("settings") { SettingsScreen(vm) { nav.popBackStack() } } composable("holder") { HolderScreen { nav.popBackStack() } } } } @Composable fun SimpleScreen(title: String, subtitle: String, actions: @Composable ColumnScope.() -> Unit) { Column( modifier = Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text(title, style = MaterialTheme.typography.headlineMedium) Text(subtitle) actions() } } @Composable fun SafetyScreen(onAck: () -> Unit, onHolder: () -> Unit) { var checked by remember { mutableStateOf(false) } Column(Modifier.fillMaxSize().padding(16.dp)) { Text("Read before using MindMachine", style = MaterialTheme.typography.headlineSmall) 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.") Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(checked = checked, onCheckedChange = { checked = it }) Text("I understand the risks and will stop immediately if I feel discomfort.") } 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, onHolder: () -> Unit) { val ui by vm.ui.collectAsStateWithLifecycle() val cfg = ui.config Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Text(ui.selectedPreset.name, style = MaterialTheme.typography.headlineSmall) Text("Duration: ${cfg.durationSec / 60} min") Slider(value = (cfg.durationSec / 60).toFloat(), onValueChange = { vm.setDurationMin(it.roundToInt()) }, valueRange = 1f..30f) Text("Mode") Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { SessionMode.values().forEach { mode -> OutlinedButton(onClick = { vm.setMode(mode) }) { Text(mode.name.replace("_", " ")) } } } Text("Blink ${cfg.blinkFrequencyHz} Hz") Slider(value = cfg.blinkFrequencyHz, onValueChange = vm::setBlinkFrequency, valueRange = 1f..20f) Text("Carrier ${cfg.carrierFrequencyHz.roundToInt()} Hz") Slider(value = cfg.carrierFrequencyHz, onValueChange = vm::setCarrier, valueRange = 80f..400f) Text("Difference ${cfg.binauralDifferenceHz} Hz") Slider(value = cfg.binauralDifferenceHz, onValueChange = vm::setDifference, valueRange = 0.5f..20f) Text("Brightness recommendation: keep screen comfortable and avoid eye strain.") TextButton(onClick = onHolder) { Text("Holder Guidance") } if (ui.error != null) Text(ui.error!!, color = Color.Red) Button(onClick = onStart, modifier = Modifier.fillMaxWidth().height(52.dp)) { Text("Start") } } } @Composable fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit) { val ui by vm.ui.collectAsStateWithLifecycle() if (ui.runtimeState == RuntimeState.COMPLETED || ui.runtimeState == RuntimeState.STOPPED) onFinish() var showOverlay by remember { mutableStateOf(true) } val intensity = (ui.config.intensityPercent / 100f) val flashing = if (ui.config.visualPatternType.name == "FLASH") { if ((ui.remainingSec % 2) == 0) intensity else 0f } else intensity * 0.5f Box( modifier = Modifier.fillMaxSize().background(Color.White.copy(alpha = if (ui.config.mode == SessionMode.AUDIO_ONLY) 0f else flashing)) .clickable { showOverlay = !showOverlay } ) { if (ui.runtimeState == RuntimeState.COUNTDOWN) { Text( "${ui.countdownSec}", modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.displayLarge, color = Color.Black ) } if (showOverlay) { Column(Modifier.align(Alignment.BottomCenter).fillMaxWidth().background(Color.Black.copy(alpha = 0.6f)).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() }, 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") } } } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen(vm: MainViewModel, onBack: () -> Unit) { val ui by vm.ui.collectAsStateWithLifecycle() var expanded by remember { mutableStateOf(false) } Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Text("Settings", style = MaterialTheme.typography.headlineSmall) Text("Countdown") ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { TextButton(onClick = { expanded = true }) { Text(ui.settings.countdownPreference.name) } ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { CountdownPreference.values().forEach { DropdownMenuItem(text = { Text(it.name) }, onClick = { vm.updateCountdown(it); expanded = false }) } } } Text("Default mode") 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") } HorizontalDivider() Text("About/Disclaimer: MindMachine is a prototype and not a medical device.") OutlinedButton(onClick = onBack) { Text("Back") } } } @Composable fun HolderScreen(onBack: () -> Unit) { Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Holder Guidance", style = MaterialTheme.typography.headlineSmall) Text("• Use a simple cardboard visor/holder.") Text("• Keep phone stable and hands-free.") Text("• Do not press device against eyes/face.") Text("• Allow airflow and comfort.") Text("• Test fit before session.") Text("• Sit or lie down in a safe place.") OutlinedButton(onClick = onBack) { Text("Back") } } }