Auto commit Tue Mar 24 07:01:02 PM CDT 2026
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user