Auto commit (MindMachine) Mon Apr 13 07:01:01 PM CDT 2026
This commit is contained in:
@@ -519,6 +519,14 @@ fun SetupScreen(
|
||||
onCurveGranularityChanged = vm::setCurveGranularitySec,
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = ui.config.monochromeFlashMode,
|
||||
onCheckedChange = vm::setMonochromeFlashMode,
|
||||
)
|
||||
Text("Use black/white flashes for this session", color = MaterialTheme.colorScheme.onBackground)
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
|
||||
if (hasUnsavedChanges) {
|
||||
OutlinedButton(onClick = { vm.saveCurrentProgram(programName) }, modifier = Modifier.weight(1f).height(52.dp)) {
|
||||
@@ -587,6 +595,7 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToEntry:
|
||||
}
|
||||
var brightnessOverlayVisible by remember { mutableStateOf(false) }
|
||||
var brightnessOverlayToken by remember { mutableIntStateOf(0) }
|
||||
val timingLogTag = "MindMachineTiming"
|
||||
|
||||
LaunchedEffect(brightnessOverlayToken) {
|
||||
if (!brightnessOverlayVisible) return@LaunchedEffect
|
||||
@@ -594,6 +603,17 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToEntry:
|
||||
brightnessOverlayVisible = false
|
||||
}
|
||||
|
||||
LaunchedEffect(ui.runtimeState) {
|
||||
when (ui.runtimeState) {
|
||||
RuntimeState.COUNTDOWN -> Log.d(timingLogTag, "COUNTDOWN_START")
|
||||
RuntimeState.RUNNING -> Log.d(timingLogTag, "IMMERSIVE_START")
|
||||
RuntimeState.STOPPED, RuntimeState.COMPLETED, RuntimeState.PAUSED, RuntimeState.INTERRUPTED, RuntimeState.ERROR -> {
|
||||
Log.d(timingLogTag, "IMMERSIVE_STOP state=${ui.runtimeState}")
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
ApplyKeepScreenOn(enabled = true)
|
||||
|
||||
ApplyScreenBrightnessOverride(
|
||||
@@ -601,6 +621,11 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToEntry:
|
||||
brightnessPercent = ui.settings.immersiveBrightnessPercent,
|
||||
)
|
||||
|
||||
ApplyPreferredRefreshRate(
|
||||
enabled = true,
|
||||
preferLowest = ui.settings.preferLowestRefreshRate,
|
||||
)
|
||||
|
||||
val progress = sessionProgressFraction(
|
||||
durationSec = ui.timeline.durationSec,
|
||||
remainingSec = ui.remainingSec,
|
||||
@@ -628,10 +653,12 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToEntry:
|
||||
SplitFlashRenderView(ctx).apply {
|
||||
setRunning(ui.runtimeState == RuntimeState.RUNNING)
|
||||
updateIntervals(ui.runtimeParams.flashOnMs, ui.runtimeParams.flashOffMs)
|
||||
setMonochrome(ui.config.monochromeFlashMode)
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
view.updateIntervals(ui.runtimeParams.flashOnMs, ui.runtimeParams.flashOffMs)
|
||||
view.setMonochrome(ui.config.monochromeFlashMode)
|
||||
view.setRunning(ui.runtimeState == RuntimeState.RUNNING)
|
||||
}
|
||||
)
|
||||
@@ -759,6 +786,40 @@ private fun ApplyScreenBrightnessOverride(enabled: Boolean, brightnessPercent: I
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ApplyPreferredRefreshRate(enabled: Boolean, preferLowest: Boolean) {
|
||||
val activity = LocalContext.current as? Activity ?: return
|
||||
val window = activity.window
|
||||
val timingLogTag = "MindMachineTiming"
|
||||
|
||||
DisposableEffect(window) {
|
||||
onDispose {
|
||||
val params = window.attributes
|
||||
params.preferredRefreshRate = 0f
|
||||
params.preferredDisplayModeId = 0
|
||||
window.attributes = params
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(enabled, preferLowest) {
|
||||
val params = window.attributes
|
||||
if (enabled && preferLowest) {
|
||||
val display = activity.display
|
||||
val chosenMode = display?.supportedModes?.minByOrNull { it.refreshRate }
|
||||
params.preferredDisplayModeId = chosenMode?.modeId ?: 0
|
||||
params.preferredRefreshRate = chosenMode?.refreshRate ?: 0f
|
||||
Log.d(
|
||||
timingLogTag,
|
||||
"DISPLAY_MODE_REQUEST modeId=${chosenMode?.modeId ?: 0} refreshHz=${chosenMode?.refreshRate ?: 0f} supported=${display?.supportedModes?.joinToString { "${it.modeId}:${it.refreshRate}" }}"
|
||||
)
|
||||
} else {
|
||||
params.preferredDisplayModeId = 0
|
||||
params.preferredRefreshRate = 0f
|
||||
}
|
||||
window.attributes = params
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ApplyImmersiveMode(enabled: Boolean, revealSignal: Int) {
|
||||
val context = LocalContext.current
|
||||
@@ -806,6 +867,7 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
private var rawOffMs = 167L
|
||||
private var onMs = 167L
|
||||
private var offMs = 167L
|
||||
private var monochrome = false
|
||||
private var started = false
|
||||
private var startUptimeMs = 0L
|
||||
private var nextToggleUptimeMs = 0L
|
||||
@@ -855,10 +917,12 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
onMs = snapToFrameMs(rawOnMs, frameMs)
|
||||
offMs = snapToFrameMs(rawOffMs, frameMs)
|
||||
|
||||
Log.d(
|
||||
timingLogTag,
|
||||
"config onMs=$rawOnMs offMs=$rawOffMs refreshHz=${"%.2f".format(refreshRate())} frameMs=$frameMs snappedOnMs=$onMs snappedOffMs=$offMs"
|
||||
)
|
||||
if (isRunning) {
|
||||
Log.d(
|
||||
timingLogTag,
|
||||
"config onMs=$rawOnMs offMs=$rawOffMs refreshHz=${"%.2f".format(refreshRate())} frameMs=$frameMs snappedOnMs=$onMs snappedOffMs=$offMs"
|
||||
)
|
||||
}
|
||||
|
||||
if (isRunning && started) {
|
||||
scheduleFromNow()
|
||||
@@ -886,12 +950,18 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
maxLatenessMs = 0L
|
||||
Log.d(
|
||||
timingLogTag,
|
||||
"session-start phase=${phaseName(phase)} refreshHz=${"%.2f".format(refreshRate())} frameMs=${framePeriodMs()} nowMs=$startUptimeMs"
|
||||
"session-start phase=${phaseName(phase)} modeId=${display?.mode?.modeId ?: 0} refreshHz=${"%.2f".format(refreshRate())} frameMs=${framePeriodMs()} nowMs=$startUptimeMs"
|
||||
)
|
||||
invalidate()
|
||||
scheduleNext()
|
||||
}
|
||||
|
||||
fun setMonochrome(enabled: Boolean) {
|
||||
if (monochrome == enabled) return
|
||||
monochrome = enabled
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
handler.removeCallbacks(toggleRunnable)
|
||||
super.onDetachedFromWindow()
|
||||
@@ -919,8 +989,8 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
}
|
||||
|
||||
private fun colorsForPhase(phase: Int): Pair<Int, Int> = when (phase) {
|
||||
0 -> Pair(android.graphics.Color.RED, android.graphics.Color.GREEN)
|
||||
2 -> Pair(android.graphics.Color.GREEN, android.graphics.Color.RED)
|
||||
0 -> if (monochrome) Pair(android.graphics.Color.WHITE, android.graphics.Color.WHITE) else Pair(android.graphics.Color.RED, android.graphics.Color.GREEN)
|
||||
2 -> if (monochrome) Pair(android.graphics.Color.WHITE, android.graphics.Color.WHITE) else Pair(android.graphics.Color.GREEN, android.graphics.Color.RED)
|
||||
else -> Pair(android.graphics.Color.BLACK, android.graphics.Color.BLACK)
|
||||
}
|
||||
|
||||
@@ -1121,6 +1191,11 @@ fun SettingsScreen(vm: MainViewModel, onRemoveAds: () -> Unit) {
|
||||
Text("Show immersive bottom progress bar", color = MaterialTheme.colorScheme.onBackground)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = ui.settings.preferLowestRefreshRate, onCheckedChange = vm::updatePreferLowestRefreshRate)
|
||||
Text("Prefer lowest display refresh rate during sessions", color = MaterialTheme.colorScheme.onBackground)
|
||||
}
|
||||
|
||||
Button(onClick = onRemoveAds) {
|
||||
Text(if (ui.settings.noAds) "Ads removed" else "Remove ads")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user