@@ -4,73 +4,117 @@ import android.app.Activity
44import android.provider.Settings
55import android.view.WindowManager
66import androidx.compose.runtime.Composable
7- import androidx.compose.runtime.LaunchedEffect
7+ import androidx.compose.runtime.DisposableEffect
8+ import androidx.compose.runtime.getValue
9+ import androidx.compose.runtime.mutableFloatStateOf
10+ import androidx.compose.runtime.mutableStateOf
11+ import androidx.compose.runtime.remember
12+ import androidx.compose.runtime.rememberUpdatedState
13+ import androidx.compose.runtime.setValue
814import androidx.compose.ui.platform.LocalContext
9- import kotlinx.coroutines.delay
15+ import androidx.compose.ui.platform.LocalLifecycleOwner
16+ import androidx.lifecycle.Lifecycle
17+ import androidx.lifecycle.LifecycleEventObserver
18+ import kotlin.math.min
1019
1120/* *
12- * A composable function that keeps the screen on for a specified duration when enabled.
13- * It can also optionally increase the screen brightness to a target level if it's below a minimum threshold.
21+ * A composable function that keeps the screen on while enabled and optionally adjusts brightness.
1422 *
1523 * This is useful for scenarios like displaying a QR code where the user needs the screen to
1624 * stay on and be bright enough to be scanned.
1725 *
18- * The effect is launched whenever the `isEnabled` parameter changes to `true`. After the specified
19- * `timeoutMs`, the screen-on flag and any brightness changes are reverted.
26+ * The keep-screen-on flag and brightness adjustment remain active for the entire duration that
27+ * [isEnabled] is `true` and the app is in the foreground. When [isEnabled] flips to `false`,
28+ * the composable leaves composition, or the app is backgrounded, the flag is cleared and
29+ * brightness is restored. Returning to the foreground re-applies the overrides.
30+ *
31+ * When [useBrightness] is enabled and the screen is very dim (below [minBrightness]), brightness
32+ * is boosted by [brightnessBoost] relative to the current level (capped at [maxBrightness]).
33+ * This avoids jarring jumps on OLED panels with non-linear brightness curves.
2034 *
2135 * @param isEnabled A boolean flag to enable or disable the keep-screen-on functionality.
22- * @param timeoutMs The duration in milliseconds for which to keep the screen on. Defaults to 10 seconds.
2336 * @param useBrightness A boolean flag to control whether the screen brightness should be adjusted.
24- * @param minBrightness The minimum brightness threshold. If the current brightness is below this value
25- * (or is set to automatic), the brightness will be adjusted. Brightness is a value from 0.0 to 1.0.
26- * @param targetBrightness The brightness level to set the screen to if adjustment is needed. Brightness is a value from 0.0 to 1.0.
37+ * @param minBrightness The minimum brightness threshold below which a boost is applied. 0.0 to 1.0.
38+ * @param brightnessBoost The amount to add to current brightness when boosting. 0.0 to 1.0.
39+ * @param maxBrightness The maximum brightness cap when boosting. 0.0 to 1.0.
2740 */
2841@Composable
2942fun KeepScreenOn (
3043 isEnabled : Boolean ,
31- timeoutMs : Long = 10_000L,
3244 useBrightness : Boolean = false,
33- minBrightness : Float = 0.4f,
34- targetBrightness : Float = 0.6f
45+ minBrightness : Float = 0.1f,
46+ brightnessBoost : Float = 0.3f,
47+ maxBrightness : Float = 0.8f
3548) {
3649 val context = LocalContext .current
50+ val lifecycleOwner = LocalLifecycleOwner .current
51+
52+ val currentIsEnabled by rememberUpdatedState(isEnabled)
53+ var originalBrightness by remember { mutableFloatStateOf(WindowManager .LayoutParams .BRIGHTNESS_OVERRIDE_NONE ) }
54+ var brightnessAdjusted by remember { mutableStateOf(false ) }
3755
38- LaunchedEffect (isEnabled) {
39- if (! isEnabled) return @LaunchedEffect
56+ DisposableEffect (isEnabled, lifecycleOwner ) {
57+ if (! isEnabled) return @DisposableEffect onDispose { }
4058
4159 val window = (context as Activity ).window
42- val layoutParams = window.attributes
43- val originalBrightness = layoutParams.screenBrightness
4460
45- // When brightness is system-managed (adaptive brightness), the window
46- // reports BRIGHTNESS_OVERRIDE_NONE (-1.0). We must query the actual
47- // system brightness rather than treating -1.0 as "below threshold" —
48- // otherwise we unconditionally override to targetBrightness which on
49- // modern Pixel panels with non-linear curves appears as near-max.
50- val effectiveBrightness = if (originalBrightness == WindowManager .LayoutParams .BRIGHTNESS_OVERRIDE_NONE ) {
51- Settings .System .getInt(
52- context.contentResolver,
53- Settings .System .SCREEN_BRIGHTNESS ,
54- 128 // ~50% fallback if unreadable
55- ) / 255f
56- } else {
57- originalBrightness
61+ fun clearOverrides () {
62+ window.clearFlags(WindowManager .LayoutParams .FLAG_KEEP_SCREEN_ON )
63+ if (brightnessAdjusted) {
64+ val layoutParams = window.attributes
65+ layoutParams.screenBrightness = originalBrightness
66+ window.attributes = layoutParams
67+ brightnessAdjusted = false
68+ }
5869 }
5970
60- if (useBrightness && effectiveBrightness < minBrightness) {
61- layoutParams.screenBrightness = targetBrightness
62- window.attributes = layoutParams
63- }
71+ fun applyOverrides () {
72+ if (! currentIsEnabled) {
73+ clearOverrides()
74+ return
75+ }
6476
65- try {
66- window.addFlags(WindowManager .LayoutParams .FLAG_KEEP_SCREEN_ON )
67- delay(timeoutMs)
68- } finally {
69- window.clearFlags(WindowManager .LayoutParams .FLAG_KEEP_SCREEN_ON )
70- if (useBrightness) {
71- layoutParams.screenBrightness = originalBrightness
77+ val layoutParams = window.attributes
78+
79+ // Only capture original brightness on the first apply — not on
80+ // re-apply from ON_RESUME — to avoid saving the boosted value.
81+ if (! brightnessAdjusted) {
82+ originalBrightness = layoutParams.screenBrightness
83+ }
84+
85+ val effectiveBrightness = if (originalBrightness == WindowManager .LayoutParams .BRIGHTNESS_OVERRIDE_NONE ) {
86+ Settings .System .getInt(
87+ context.contentResolver,
88+ Settings .System .SCREEN_BRIGHTNESS ,
89+ 128
90+ ) / 255f
91+ } else {
92+ originalBrightness
93+ }
94+
95+ brightnessAdjusted = useBrightness && effectiveBrightness < minBrightness
96+ if (brightnessAdjusted) {
97+ layoutParams.screenBrightness = min(effectiveBrightness + brightnessBoost, maxBrightness)
7298 window.attributes = layoutParams
7399 }
100+
101+ window.addFlags(WindowManager .LayoutParams .FLAG_KEEP_SCREEN_ON )
102+ }
103+
104+ applyOverrides()
105+
106+ val observer = LifecycleEventObserver { _, event ->
107+ when (event) {
108+ Lifecycle .Event .ON_PAUSE -> clearOverrides()
109+ Lifecycle .Event .ON_RESUME -> applyOverrides()
110+ else -> Unit
111+ }
112+ }
113+ lifecycleOwner.lifecycle.addObserver(observer)
114+
115+ onDispose {
116+ lifecycleOwner.lifecycle.removeObserver(observer)
117+ clearOverrides()
74118 }
75119 }
76- }
120+ }
0 commit comments