Auto commit Tue Mar 24 07:01:02 PM CDT 2026

This commit is contained in:
Tretzi
2026-03-24 19:01:02 -05:00
parent 1d4a599dd6
commit 11445d10dd
2 changed files with 94 additions and 46 deletions

View File

@@ -3,6 +3,7 @@ package com.mindmachine.mvp
import android.app.Activity
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
@@ -85,9 +86,11 @@ 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.rememberNavController
import androidx.navigation.navArgument
import com.mindmachine.mvp.audio.BinauralAudioEngine
import com.mindmachine.mvp.audio.HeadsetMonitor
import com.mindmachine.mvp.data.SettingsRepository
@@ -107,6 +110,9 @@ import kotlinx.coroutines.delay
import kotlin.math.roundToInt
import java.util.Locale
private const val SESSION_ENTRY_HOME = "home"
private const val SESSION_ENTRY_SETUP = "setup"
private val MindMachineColors = darkColorScheme(
primary = Color(0xFF8AB4FF),
onPrimary = Color(0xFF001B3D),
@@ -241,12 +247,22 @@ fun App(vm: MainViewModel = viewModel()) {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = null,
tint = Color(0xFF4CAF50),
modifier = Modifier.size(30.dp)
)
IconButton(
onClick = {
val started = vm.startSessionFromProgramSelection(p.id)
if (started) {
nav.navigate("active/$SESSION_ENTRY_HOME")
}
},
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = "Play ${p.name}",
tint = Color(0xFF4CAF50),
modifier = Modifier.size(38.dp)
)
}
Box(
modifier = Modifier
.size(24.dp)
@@ -302,31 +318,32 @@ fun App(vm: MainViewModel = viewModel()) {
onStart = {
val started = vm.startSession()
if (started) {
nav.navigate("active")
nav.navigate("active/$SESSION_ENTRY_SETUP")
}
},
)
}
composable("active") {
composable(
route = "active/{entryPoint}",
arguments = listOf(navArgument("entryPoint") { type = NavType.StringType })
) { backStackEntry ->
val entryPoint = backStackEntry.arguments?.getString("entryPoint") ?: SESSION_ENTRY_SETUP
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")
onFinish = {
nav.navigate(entryPoint) {
popUpTo("active/$entryPoint") { inclusive = true }
launchSingleTop = true
}
}) { Text("Repeat Session") }
OutlinedButton(onClick = { nav.navigate("home") { popUpTo(0) } }) { Text("Return Home") }
}
},
onBackToEntry = {
vm.stop()
nav.navigate(entryPoint) {
popUpTo("active/$entryPoint") { inclusive = true }
launchSingleTop = true
}
}
)
}
composable("settings") {
SettingsScreen(vm)
@@ -462,13 +479,12 @@ internal fun setupScreenHasUnsavedChanges(
internal fun setupScreenUsesScrollableContainer(): Boolean = true
@Composable
fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup: () -> Unit) {
fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToEntry: () -> Unit) {
val ui by vm.ui.collectAsStateWithLifecycle()
if (ui.runtimeState == RuntimeState.COMPLETED) onFinish()
BackHandler {
vm.stop()
onBackToSetup()
onBackToEntry()
}
val isVisualMode = true
@@ -506,6 +522,8 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup:
brightnessOverlayVisible = false
}
ApplyKeepScreenOn(enabled = true)
ApplyScreenBrightnessOverride(
enabled = true,
brightnessPercent = ui.settings.immersiveBrightnessPercent,
@@ -629,6 +647,23 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToSetup:
}
}
@Composable
private fun ApplyKeepScreenOn(enabled: Boolean) {
val activity = LocalContext.current as? Activity ?: return
val window = activity.window
DisposableEffect(window, enabled) {
if (enabled) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
onDispose {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}
@Composable
private fun ApplyScreenBrightnessOverride(enabled: Boolean, brightnessPercent: Int) {
val activity = LocalContext.current as? Activity ?: return

View File

@@ -142,25 +142,13 @@ class MainViewModel(
fun acknowledgeSafety() = viewModelScope.launch { settingsRepository.acknowledgeSafety(SAFETY_VERSION) }
fun choosePreset(id: String) = viewModelScope.launch {
val state = _ui.value
val preset = state.presets.first { it.id == id }
settingsRepository.updateLastPreset(id)
_ui.update {
val cfg = preset.toConfig(SessionMode.AUDIO_VISUAL)
val timeline = timelineForProgramId(preset.id, cachedPrograms)
val savedSignature = timelineProgramSignature(timeline)
it.copy(
selectedPreset = preset,
config = cfg.copy(durationSec = cfg.durationSec.coerceIn(60, 8 * 60 * 60)),
timeline = timeline,
savedProgramName = preset.name,
savedTimelineSignature = savedSignature,
hasUnsavedChanges = false,
error = null,
)
}
elapsedBeforePauseSec = 0f
fun choosePreset(id: String) {
applyPresetSelection(id)
}
fun startSessionFromProgramSelection(id: String): Boolean {
applyPresetSelection(id)
return startSession()
}
fun createNewProgramDraft() {
@@ -442,6 +430,31 @@ class MainViewModel(
return settings.forceAudioWithoutHeadphones || headsetAvailable
}
private fun applyPresetSelection(id: String) {
val state = _ui.value
val preset = state.presets.firstOrNull { it.id == id } ?: return
_ui.update {
val cfg = preset.toConfig(SessionMode.AUDIO_VISUAL)
val timeline = timelineForProgramId(preset.id, cachedPrograms)
val savedSignature = timelineProgramSignature(timeline)
it.copy(
selectedPreset = preset,
config = cfg.copy(durationSec = cfg.durationSec.coerceIn(60, 8 * 60 * 60)),
timeline = timeline,
savedProgramName = preset.name,
savedTimelineSignature = savedSignature,
hasUnsavedChanges = false,
error = null,
)
}
elapsedBeforePauseSec = 0f
viewModelScope.launch {
settingsRepository.updateLastPreset(id)
}
}
private fun currentElapsedSec(): Float {
val state = _ui.value
val total = state.timeline.durationSec.toFloat().coerceAtLeast(1f)