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

View File

@@ -142,25 +142,13 @@ class MainViewModel(
fun acknowledgeSafety() = viewModelScope.launch { settingsRepository.acknowledgeSafety(SAFETY_VERSION) } fun acknowledgeSafety() = viewModelScope.launch { settingsRepository.acknowledgeSafety(SAFETY_VERSION) }
fun choosePreset(id: String) = viewModelScope.launch { fun choosePreset(id: String) {
val state = _ui.value applyPresetSelection(id)
val preset = state.presets.first { it.id == id } }
settingsRepository.updateLastPreset(id)
_ui.update { fun startSessionFromProgramSelection(id: String): Boolean {
val cfg = preset.toConfig(SessionMode.AUDIO_VISUAL) applyPresetSelection(id)
val timeline = timelineForProgramId(preset.id, cachedPrograms) return startSession()
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 createNewProgramDraft() { fun createNewProgramDraft() {
@@ -442,6 +430,31 @@ class MainViewModel(
return settings.forceAudioWithoutHeadphones || headsetAvailable 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 { private fun currentElapsedSec(): Float {
val state = _ui.value val state = _ui.value
val total = state.timeline.durationSec.toFloat().coerceAtLeast(1f) val total = state.timeline.durationSec.toFloat().coerceAtLeast(1f)