From 51016f0b380dec63f2ea2653698c70e7ecb7576b Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Sat, 28 Feb 2026 19:38:13 +0900 Subject: [PATCH 01/12] Changed screenDim() function to turn screen off and on to save power. This works by changing the screen timeout setting to a very short value, and clearing the FLAG_KEEP_SCREEN_ON flag, so the power manager turns the screen off. When the screen is to turn on, a wake activity turns it back on, and the keyguard lockscreen is dismissed, and the user's original screen timeout value is restored. --- app/src/main/AndroidManifest.xml | 10 ++- .../immichframe/immichframe/MainActivity.kt | 68 +++++++++++++++++-- settings.gradle.kts | 3 + 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 06e8c15..3f998d8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + + + android:theme="@style/Theme.WidgetConfigDialog" + android:exported="true" /> diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index ed2b99b..ed31ea1 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -3,6 +3,8 @@ package com.immichframe.immichframe import android.animation.ObjectAnimator import android.animation.PropertyValuesHolder import android.annotation.SuppressLint +import android.app.KeyguardManager +import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.Color @@ -10,6 +12,7 @@ import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper +import android.provider.Settings import android.text.SpannableString import android.text.Spanned import android.text.style.RelativeSizeSpan @@ -75,6 +78,7 @@ class MainActivity : AppCompatActivity() { private var previousImage: Helpers.ImageResponse? = null private var currentImage: Helpers.ImageResponse? = null private var portraitCache: Helpers.ImageResponse? = null + private var originalScreenTimeout: Int = -1 private val imageRunnable = object : Runnable { override fun run() { if (isImageTimerRunning) { @@ -770,11 +774,31 @@ class MainActivity : AppCompatActivity() { } } + // set screen timeout in Android settings in microseconds + private fun setScreenTimeout(timeout: Int) { + try { + Settings.System.putInt( + contentResolver, + Settings.System.SCREEN_OFF_TIMEOUT, + timeout + ) + } catch (e: Exception) { + Log.e("Settings", "Could not set screen timeout: ${e.message}") + } + } + + @Suppress("DEPRECATION") private fun screenDim(dim: Boolean) { - val lp = window.attributes if (dim) { - lp.screenBrightness = 0.01f - window.attributes = lp + // save user's screen timeout and set to 3s to show "going to sleep" message + originalScreenTimeout = Settings.System.getInt( + contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 30000 + ) + setScreenTimeout(3000) + + // create black overlay, set webview to blank to reduce activity, and + // wait for screen to go to sleep after inactivity + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) if (dimOverlay.visibility != View.VISIBLE) { dimOverlay.apply { visibility = View.VISIBLE @@ -791,9 +815,22 @@ class MainActivity : AppCompatActivity() { .start() } } + + // display message to user that screen is going to sleep + Toast.makeText( + this@MainActivity, + "Going to sleep", + Toast.LENGTH_LONG + ).show() + } else { - lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE - window.attributes = lp + // restore user's screen timeout value + if (originalScreenTimeout != -1) { + setScreenTimeout(originalScreenTimeout) + } + + // remove black overlay and restore webview settings + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) if (dimOverlay.isVisible) { dimOverlay.animate() .alpha(0f) @@ -801,9 +838,30 @@ class MainActivity : AppCompatActivity() { .withEndAction { dimOverlay.visibility = View.GONE loadSettings() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(false) + } } .start() } + + // turn screen back on, dismissing keyguard lockscreen + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(true) + setTurnScreenOn(true) + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + keyguardManager.requestDismissKeyguard(this, null) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + keyguardManager.requestDismissKeyguard(this, null) + window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or + WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2c9f205..205baf6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,9 @@ pluginManagement { gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { From 1f65e55d030cd18e520134997b2932dd49b70761 Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Sat, 28 Feb 2026 22:03:19 +0900 Subject: [PATCH 02/12] Replaced WakeActivity with acquisition of a wakeLock --- app/src/main/AndroidManifest.xml | 5 ----- .../java/com/immichframe/immichframe/MainActivity.kt | 11 +++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3f998d8..42c38a7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,11 +65,6 @@ - Date: Fri, 6 Mar 2026 20:34:02 +0900 Subject: [PATCH 03/12] Removed unused REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission --- app/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 42c38a7..e6e8779 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ - Date: Wed, 25 Mar 2026 22:48:28 +0900 Subject: [PATCH 04/12] Changed setup screen so screen dimming function only available if selection to keep screen on is set, and if it is not set, added selection to set timeout value in any number of minutes rather than requiring user to set it in Android settings. Fix so user timeout value not lost --- app/build.gradle.kts | 4 +- app/src/main/AndroidManifest.xml | 2 +- .../immichframe/immichframe/MainActivity.kt | 45 +++++++++++++++---- .../immichframe/SettingsFragment.kt | 44 +++++++++++++++--- app/src/main/res/xml/device_admin.xml | 5 +++ app/src/main/res/xml/settings_view.xml | 26 +++++++---- gradlew | 0 7 files changed, 100 insertions(+), 26 deletions(-) create mode 100644 app/src/main/res/xml/device_admin.xml mode change 100644 => 100755 gradlew diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4f7d508..d206dd6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,7 +32,7 @@ android { minSdk = 23 targetSdk = 36 versionCode = 48 - versionName = "1.0.48.0" + versionName = "1.0.48.2" } buildTypes { @@ -86,4 +86,4 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e6e8779..8c2eba2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -82,4 +82,4 @@ - \ No newline at end of file + diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 316c244..0bca42f 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -493,6 +493,7 @@ class MainActivity : AppCompatActivity() { attemptFetch() } + // called when app starts and when user returns from settings screen @SuppressLint("SetJavaScriptEnabled") private fun loadSettings() { val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext) @@ -612,6 +613,7 @@ class MainActivity : AppCompatActivity() { } ) } + applyScreenTimeout() } private fun onSettingsLoaded() { @@ -788,13 +790,36 @@ class MainActivity : AppCompatActivity() { } } + // apply user-entered screen timeout value + private fun applyScreenTimeout() { + val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext) + + if (keepScreenOn) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + val timeoutMinutes = prefs.getString("screenTimeout", "10")?.toIntOrNull() ?: 10 + // capture original system timeout once if we haven't yet + if (originalScreenTimeout == -1) { + originalScreenTimeout = Settings.System.getInt( + contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 30000 + ) + } + setScreenTimeout(timeoutMinutes * 60 * 1000) + } + } + @Suppress("DEPRECATION") private fun screenDim(dim: Boolean) { + if (dim == dimOverlay.isVisible) return + if (dim) { // save user's screen timeout and set to 3s to show "going to sleep" message - originalScreenTimeout = Settings.System.getInt( - contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 30000 - ) + if (originalScreenTimeout == -1) { + originalScreenTimeout = Settings.System.getInt( + contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 30000 + ) + } setScreenTimeout(3000) // create black overlay, set webview to blank to reduce activity, and @@ -828,6 +853,7 @@ class MainActivity : AppCompatActivity() { // restore user's screen timeout value if (originalScreenTimeout != -1) { setScreenTimeout(originalScreenTimeout) + originalScreenTimeout = -1 } // acquire WakeLock to turn screen on for short time, just to wake device @@ -910,21 +936,22 @@ class MainActivity : AppCompatActivity() { } } + // called when user returns to app override fun onResume() { super.onResume() - if (keepScreenOn) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } + applyScreenTimeout() hideSystemUI() - } + // called when app is closed override fun onDestroy() { super.onDestroy() rcpServer.stop() handler.removeCallbacksAndMessages(null) + // restore timeout if we changed it + if (originalScreenTimeout != -1) { + setScreenTimeout(originalScreenTimeout) + } } private fun loadWebViewWithRetry( diff --git a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt index d2f52ad..be6ef8a 100644 --- a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt +++ b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt @@ -12,6 +12,7 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.preference.EditTextPreference import androidx.preference.Preference +import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import androidx.preference.SwitchPreferenceCompat @@ -22,11 +23,13 @@ class SettingsFragment : PreferenceFragmentCompat() { val chkUseWebView = findPreference("useWebView") val chkBlurredBackground = findPreference("blurredBackground") val chkShowCurrentDate = findPreference("showCurrentDate") + val chkKeepScreenOn = findPreference("keepScreenOn") + val txtScreenTimeout = findPreference("screenTimeout") + val screenDimmingCategory = findPreference("screenDimmingCategory") val chkScreenDim = findPreference("screenDim") val txtDimTime = findPreference("dim_time_range") - - //obfuscate the authSecret + // obfuscate the authSecret val authPref = findPreference("authSecret") authPref?.setOnBindEditTextListener { editText -> editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD @@ -36,10 +39,17 @@ class SettingsFragment : PreferenceFragmentCompat() { val useWebView = chkUseWebView?.isChecked ?: false chkBlurredBackground?.isVisible = !useWebView chkShowCurrentDate?.isVisible = !useWebView + + val keepScreenOn = chkKeepScreenOn?.isChecked ?: false + screenDimmingCategory?.isVisible = keepScreenOn + txtScreenTimeout?.isVisible = !keepScreenOn + val screenDim = chkScreenDim?.isChecked ?: false txtDimTime?.isVisible = screenDim // React to changes + + // use Webview setting chkUseWebView?.setOnPreferenceChangeListener { _, newValue -> val value = newValue as Boolean chkBlurredBackground?.isVisible = !value @@ -47,11 +57,34 @@ class SettingsFragment : PreferenceFragmentCompat() { //add android settings button true } + + // keep screen on setting - toggles screen dimming category visibility + chkKeepScreenOn?.setOnPreferenceChangeListener { _, newValue -> + val value = newValue as Boolean + screenDimmingCategory?.isVisible = value + txtScreenTimeout?.isVisible = !value + true + } + + // validate screen on timeout value + txtScreenTimeout?.setOnPreferenceChangeListener { _, newValue -> + val value = newValue.toString().toIntOrNull() + if (value != null && value > 0) { + true + } else { + Toast.makeText(requireContext(), "Please enter a valid number of minutes", Toast.LENGTH_SHORT).show() + false + } + } + + // screen dimming setting - dimming time becomes visible if set chkScreenDim?.setOnPreferenceChangeListener { _, newValue -> val value = newValue as Boolean txtDimTime?.isVisible = value true } + + // settings lock setting: prevent further access to settings screen val chkSettingsLock = findPreference("settingsLock") chkSettingsLock?.setOnPreferenceChangeListener { _, newValue -> val enabling = newValue as Boolean @@ -72,7 +105,7 @@ class SettingsFragment : PreferenceFragmentCompat() { true } - + // close settings view val btnClose = findPreference("closeSettings") btnClose?.setOnPreferenceClickListener { val url = PreferenceManager.getDefaultSharedPreferences(requireContext()) @@ -88,6 +121,7 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + // launch Android settings selection val btnAndroidSettings = findPreference("androidSettings") btnAndroidSettings?.setOnPreferenceClickListener { val context = requireContext() @@ -111,7 +145,7 @@ class SettingsFragment : PreferenceFragmentCompat() { true } - + // get dimming time range setting from input string val timePref = findPreference("dim_time_range") timePref?.setOnPreferenceChangeListener { _, newValue -> val timeRange = newValue.toString().trim() @@ -138,4 +172,4 @@ class SettingsFragment : PreferenceFragmentCompat() { } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/xml/device_admin.xml b/app/src/main/res/xml/device_admin.xml new file mode 100644 index 0000000..17e5710 --- /dev/null +++ b/app/src/main/res/xml/device_admin.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/xml/settings_view.xml b/app/src/main/res/xml/settings_view.xml index 75112b5..63c838b 100644 --- a/app/src/main/res/xml/settings_view.xml +++ b/app/src/main/res/xml/settings_view.xml @@ -16,36 +16,44 @@ + android:title="Use WebView" /> + android:title="Keep Screen On" /> + + android:title="Blurred Background" /> + android:title="Show Current Date" /> + android:title="Lock access to settings" /> - + + android:title="Set Screen Dimming Hours" /> + android:summary="e.g. 22:00-07:00" + android:title="Dimming Time Range" /> diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 2d83d95589d2f8dbec0a42f8c3f11cae00c1f1c3 Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Sun, 29 Mar 2026 19:37:36 +0900 Subject: [PATCH 05/12] Fixes for errors found by CodeRabbit --- .../java/com/immichframe/immichframe/MainActivity.kt | 2 ++ .../java/com/immichframe/immichframe/SettingsFragment.kt | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 0bca42f..394b30a 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -799,6 +799,7 @@ class MainActivity : AppCompatActivity() { } else { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) val timeoutMinutes = prefs.getString("screenTimeout", "10")?.toIntOrNull() ?: 10 + // capture original system timeout once if we haven't yet if (originalScreenTimeout == -1) { originalScreenTimeout = Settings.System.getInt( @@ -948,6 +949,7 @@ class MainActivity : AppCompatActivity() { super.onDestroy() rcpServer.stop() handler.removeCallbacksAndMessages(null) + // restore timeout if we changed it if (originalScreenTimeout != -1) { setScreenTimeout(originalScreenTimeout) diff --git a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt index be6ef8a..58e956e 100644 --- a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt +++ b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt @@ -59,20 +59,25 @@ class SettingsFragment : PreferenceFragmentCompat() { } // keep screen on setting - toggles screen dimming category visibility + // show screen dimming only if keep-on is set + // show screen timeout setting only if keep-on is not set chkKeepScreenOn?.setOnPreferenceChangeListener { _, newValue -> val value = newValue as Boolean screenDimmingCategory?.isVisible = value txtScreenTimeout?.isVisible = !value + if (!value) { + chkScreenDim?.isChecked = false + txtDimTime?.isVisible = false true } // validate screen on timeout value txtScreenTimeout?.setOnPreferenceChangeListener { _, newValue -> val value = newValue.toString().toIntOrNull() - if (value != null && value > 0) { + if (value != null && value in 1..1440) { true } else { - Toast.makeText(requireContext(), "Please enter a valid number of minutes", Toast.LENGTH_SHORT).show() + Toast.makeText(requireContext(), "Please enter a value between 1 and 1440 minutes (24 hours)", Toast.LENGTH_SHORT).show() false } } From 0be4a562ab906c8ba5e7d0df08356d737acf794c Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Sat, 4 Apr 2026 18:26:22 +0900 Subject: [PATCH 06/12] Added isSnoozing and refactored code so that a "snooze" mode is added where the user can press the power button while the screen is turned off in dimming mode, and the screen will come back on for 30s, then turn back off. --- .../immichframe/immichframe/MainActivity.kt | 112 +++++++++++------- .../immichframe/SettingsFragment.kt | 1 + 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 394b30a..8a9335f 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -80,6 +80,7 @@ class MainActivity : AppCompatActivity() { private var currentImage: Helpers.ImageResponse? = null private var portraitCache: Helpers.ImageResponse? = null private var originalScreenTimeout: Int = -1 + private var isSnoozing = false private val imageRunnable = object : Runnable { override fun run() { if (isImageTimerRunning) { @@ -102,6 +103,12 @@ class MainActivity : AppCompatActivity() { handler.postDelayed(this, 30000) } } + private val redimRunnable = object : Runnable { + override fun run() { + isSnoozing = false + screenDim(true) + } + } private var isShowingFirst = true private var zoomAnimator: ObjectAnimator? = null @@ -524,7 +531,7 @@ class MainActivity : AppCompatActivity() { handler.post(dimCheckRunnable) } else { handler.removeCallbacks(dimCheckRunnable) - dimOverlay.visibility = View.GONE + removeDimOverlay() val lp = WindowManager.LayoutParams() lp.copyFrom(window.attributes) lp.screenBrightness = 1f @@ -810,10 +817,54 @@ class MainActivity : AppCompatActivity() { } } + // show the black overlay and pause activity + private fun applyDimOverlay() { + if (dimOverlay.visibility != View.VISIBLE || dimOverlay.alpha < 0.5f) { + dimOverlay.apply { + visibility = View.VISIBLE + alpha = 0f + if (useWebView) { + webView.loadUrl("about:blank") + } else { + stopImageTimer() + stopWeatherTimer() + } + animate() + .alpha(0.99f) + .setDuration(500L) + .start() + } + + // display message to user that screen is going to sleep + Toast.makeText( + this@MainActivity, + "Going to sleep", + Toast.LENGTH_LONG + ).show() + } + } + + // hide the black overlay and resume activity + private fun removeDimOverlay() { + // remove black overlay and restore webview settings + handler.removeCallbacks(redimRunnable) + if (dimOverlay.isVisible) { + dimOverlay.animate() + .alpha(0f) + .setDuration(500L) + .withEndAction { + dimOverlay.visibility = View.GONE + loadSettings() // Restores WebView and timers + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(false) + } + } + .start() + } + } + @Suppress("DEPRECATION") private fun screenDim(dim: Boolean) { - if (dim == dimOverlay.isVisible) return - if (dim) { // save user's screen timeout and set to 3s to show "going to sleep" message if (originalScreenTimeout == -1) { @@ -826,29 +877,7 @@ class MainActivity : AppCompatActivity() { // create black overlay, set webview to blank to reduce activity, and // wait for screen to go to sleep after inactivity window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - if (dimOverlay.visibility != View.VISIBLE) { - dimOverlay.apply { - visibility = View.VISIBLE - alpha = 0f - if (useWebView) { - webView.loadUrl("about:blank") - } else { - stopImageTimer() - stopWeatherTimer() - } - animate() - .alpha(0.99f) - .setDuration(500L) - .start() - } - } - - // display message to user that screen is going to sleep - Toast.makeText( - this@MainActivity, - "Going to sleep", - Toast.LENGTH_LONG - ).show() + applyDimOverlay() } else { // restore user's screen timeout value @@ -867,21 +896,8 @@ class MainActivity : AppCompatActivity() { wakeLock.acquire(10 * 1000L) // 10 second timeout wakeLock.release() - // remove black overlay and restore webview settings window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - if (dimOverlay.isVisible) { - dimOverlay.animate() - .alpha(0f) - .setDuration(500L) - .withEndAction { - dimOverlay.visibility = View.GONE - loadSettings() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - setShowWhenLocked(false) - } - } - .start() - } + removeDimOverlay() // turn screen back on, dismissing keyguard lockscreen if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { @@ -923,9 +939,11 @@ class MainActivity : AppCompatActivity() { } val isOverlayVisible = dimOverlay.isVisible - if (shouldDim && !isOverlayVisible) { + if (shouldDim && !isOverlayVisible && !isSnoozing) { + isSnoozing = false screenDim(true) } else if (!shouldDim && isOverlayVisible) { + isSnoozing = false screenDim(false) } } @@ -940,7 +958,17 @@ class MainActivity : AppCompatActivity() { // called when user returns to app override fun onResume() { super.onResume() - applyScreenTimeout() + + // if we are already in dim state, but screen just woke up, + // hide overlay and schedule screen to re-dim in 30 seconds + if (dimOverlay.isVisible) { + isSnoozing = true + setScreenTimeout(40000) + removeDimOverlay() + handler.postDelayed(redimRunnable, 30000) + } else { + applyScreenTimeout() + } hideSystemUI() } diff --git a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt index 58e956e..10b1722 100644 --- a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt +++ b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt @@ -68,6 +68,7 @@ class SettingsFragment : PreferenceFragmentCompat() { if (!value) { chkScreenDim?.isChecked = false txtDimTime?.isVisible = false + } true } From 0d11e2d637d5a1bf91daefdb61bccaa1f42ac220 Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Sat, 4 Apr 2026 22:55:29 +0900 Subject: [PATCH 07/12] Fixes for issues found by CodeRabbit --- .../java/com/immichframe/immichframe/SettingsFragment.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt index 10b1722..ddcbcdc 100644 --- a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt +++ b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt @@ -43,6 +43,10 @@ class SettingsFragment : PreferenceFragmentCompat() { val keepScreenOn = chkKeepScreenOn?.isChecked ?: false screenDimmingCategory?.isVisible = keepScreenOn txtScreenTimeout?.isVisible = !keepScreenOn + if (!keepScreenOn) { + chkScreenDim?.isChecked = false + txtDimTime?.isVisible = false + } val screenDim = chkScreenDim?.isChecked ?: false txtDimTime?.isVisible = screenDim @@ -152,8 +156,7 @@ class SettingsFragment : PreferenceFragmentCompat() { } // get dimming time range setting from input string - val timePref = findPreference("dim_time_range") - timePref?.setOnPreferenceChangeListener { _, newValue -> + txtFimeTime?.setOnPreferenceChangeListener { _, newValue -> val timeRange = newValue.toString().trim() val regex = "^([01]?[0-9]|2[0-3]):([0-5][0-9])-([01]?[0-9]|2[0-3]):([0-5][0-9])$".toRegex() From 42e8d213b1bf81d7067ef3fbb61068cb814b7750 Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Sun, 5 Apr 2026 23:10:31 +0900 Subject: [PATCH 08/12] Fixed typo --- .../main/java/com/immichframe/immichframe/SettingsFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt index ddcbcdc..6698fed 100644 --- a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt +++ b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt @@ -156,7 +156,7 @@ class SettingsFragment : PreferenceFragmentCompat() { } // get dimming time range setting from input string - txtFimeTime?.setOnPreferenceChangeListener { _, newValue -> + txtDimTime?.setOnPreferenceChangeListener { _, newValue -> val timeRange = newValue.toString().trim() val regex = "^([01]?[0-9]|2[0-3]):([0-5][0-9])-([01]?[0-9]|2[0-3]):([0-5][0-9])$".toRegex() From 1666ae9c2a7e3c129f91bff3f386cf0f90abcb53 Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Sat, 11 Apr 2026 12:20:53 +0900 Subject: [PATCH 09/12] Remove dimCheckRunnable callbacks in loadSettings() to avoid multiple self-rescheduling loops --- .../main/java/com/immichframe/immichframe/MainActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 8a9335f..4900b77 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -522,21 +522,24 @@ class MainActivity : AppCompatActivity() { swipeRefreshLayout.isEnabled = !settingsLock txtPhotoInfo.visibility = View.GONE //enabled in onSettingsLoaded based on server settings txtDateTime.visibility = View.GONE //enabled in onSettingsLoaded based on server settings + if (keepScreenOn) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } else { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } + + handler.removeCallbacks(dimCheckRunnable) if (screenDim) { handler.post(dimCheckRunnable) } else { - handler.removeCallbacks(dimCheckRunnable) removeDimOverlay() val lp = WindowManager.LayoutParams() lp.copyFrom(window.attributes) lp.screenBrightness = 1f window.attributes = lp } + if (useWebView) { savedUrl = if (authSecret.isNotEmpty()) { savedUrl.toUri() From 2f3b7236c75ab050512c876a0fe63b67ba33cf7a Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Tue, 14 Apr 2026 00:06:58 +0900 Subject: [PATCH 10/12] Changed "dim" to "dimming" for screen dimming setting related variables --- .../java/com/immichframe/immichframe/MainActivity.kt | 6 +++--- .../com/immichframe/immichframe/SettingsFragment.kt | 12 ++++++------ app/src/main/res/xml/settings_view.xml | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 4900b77..92d20c1 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -120,7 +120,7 @@ class MainActivity : AppCompatActivity() { } override fun onCreate(savedInstanceState: Bundle?) { - //force dark mode + // force dark mode AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) super.onCreate(savedInstanceState) @@ -510,7 +510,7 @@ class MainActivity : AppCompatActivity() { useWebView = prefs.getBoolean("useWebView", true) keepScreenOn = prefs.getBoolean("keepScreenOn", true) val authSecret = prefs.getString("authSecret", "") ?: "" - val screenDim = prefs.getBoolean("screenDim", false) + val screenDimming = prefs.getBoolean("screenDim", false) val settingsLock = prefs.getBoolean("settingsLock", false) webView.visibility = if (useWebView) View.VISIBLE else View.GONE @@ -530,7 +530,7 @@ class MainActivity : AppCompatActivity() { } handler.removeCallbacks(dimCheckRunnable) - if (screenDim) { + if (screenDimming) { handler.post(dimCheckRunnable) } else { removeDimOverlay() diff --git a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt index 6698fed..20a56e0 100644 --- a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt +++ b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt @@ -26,7 +26,7 @@ class SettingsFragment : PreferenceFragmentCompat() { val chkKeepScreenOn = findPreference("keepScreenOn") val txtScreenTimeout = findPreference("screenTimeout") val screenDimmingCategory = findPreference("screenDimmingCategory") - val chkScreenDim = findPreference("screenDim") + val chkScreenDimming = findPreference("screenDimming") val txtDimTime = findPreference("dim_time_range") // obfuscate the authSecret @@ -44,12 +44,12 @@ class SettingsFragment : PreferenceFragmentCompat() { screenDimmingCategory?.isVisible = keepScreenOn txtScreenTimeout?.isVisible = !keepScreenOn if (!keepScreenOn) { - chkScreenDim?.isChecked = false + chkScreenDimming?.isChecked = false txtDimTime?.isVisible = false } - val screenDim = chkScreenDim?.isChecked ?: false - txtDimTime?.isVisible = screenDim + val screenDimming = chkScreenDimming?.isChecked ?: false + txtDimTime?.isVisible = screenDimming // React to changes @@ -70,7 +70,7 @@ class SettingsFragment : PreferenceFragmentCompat() { screenDimmingCategory?.isVisible = value txtScreenTimeout?.isVisible = !value if (!value) { - chkScreenDim?.isChecked = false + chkScreenDimming?.isChecked = false txtDimTime?.isVisible = false } true @@ -88,7 +88,7 @@ class SettingsFragment : PreferenceFragmentCompat() { } // screen dimming setting - dimming time becomes visible if set - chkScreenDim?.setOnPreferenceChangeListener { _, newValue -> + chkScreenDimming?.setOnPreferenceChangeListener { _, newValue -> val value = newValue as Boolean txtDimTime?.isVisible = value true diff --git a/app/src/main/res/xml/settings_view.xml b/app/src/main/res/xml/settings_view.xml index 63c838b..56c6368 100644 --- a/app/src/main/res/xml/settings_view.xml +++ b/app/src/main/res/xml/settings_view.xml @@ -46,11 +46,11 @@ android:title="Screen Dimming"> From a2b5ff8e488c71a338a57c0c82512457aabb3bb6 Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Thu, 16 Apr 2026 15:42:09 +0900 Subject: [PATCH 11/12] Changed so isSnoozing is reset and redimRunnable is cleared only when returning from settings screen, not in loadSettings() Fixed missed instance of last "dim" to "dimming" change screen dimming works again, snoozing (brief wake-up from dim state) works, and changing dimming hours from snoozing state works too --- .../com/immichframe/immichframe/MainActivity.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 92d20c1..cdd7189 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -104,6 +104,7 @@ class MainActivity : AppCompatActivity() { } } private val redimRunnable = object : Runnable { + // redim the screen and turn off the snooze flag when the snooze period is over override fun run() { isSnoozing = false screenDim(true) @@ -114,7 +115,10 @@ class MainActivity : AppCompatActivity() { private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + // Always reset snooze and reload settings when returning from settings screen if (result.resultCode == RESULT_OK) { + isSnoozing = false + handler.removeCallbacks(redimRunnable) loadSettings() } } @@ -510,7 +514,7 @@ class MainActivity : AppCompatActivity() { useWebView = prefs.getBoolean("useWebView", true) keepScreenOn = prefs.getBoolean("keepScreenOn", true) val authSecret = prefs.getString("authSecret", "") ?: "" - val screenDimming = prefs.getBoolean("screenDim", false) + val screenDimming = prefs.getBoolean("screenDimming", false) val settingsLock = prefs.getBoolean("settingsLock", false) webView.visibility = if (useWebView) View.VISIBLE else View.GONE @@ -922,18 +926,22 @@ class MainActivity : AppCompatActivity() { } } + // compare the current time with the dimming time setting and dim/undim the screen if necessary private fun checkDimTime() { + // get the dimming time setting from preferences val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext) val startHour = prefs.getInt("dimStartHour", 22) val startMinute = prefs.getInt("dimStartMinute", 0) val endHour = prefs.getInt("dimEndHour", 6) val endMinute = prefs.getInt("dimEndMinute", 0) + // find starting and ending dimming times in total minutes (out of 24h) and the current minutes val now = Calendar.getInstance() val nowMinutes = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE) val startMinutes = startHour * 60 + startMinute val endMinutes = endHour * 60 + endMinute + // compare dimming window start/end with current minutes to see if screen should be dimmed val shouldDim = if (startMinutes < endMinutes) { nowMinutes in startMinutes until endMinutes @@ -941,9 +949,9 @@ class MainActivity : AppCompatActivity() { nowMinutes !in endMinutes until startMinutes } + // dim or undim as necessary val isOverlayVisible = dimOverlay.isVisible if (shouldDim && !isOverlayVisible && !isSnoozing) { - isSnoozing = false screenDim(true) } else if (!shouldDim && isOverlayVisible) { isSnoozing = false @@ -966,8 +974,8 @@ class MainActivity : AppCompatActivity() { // hide overlay and schedule screen to re-dim in 30 seconds if (dimOverlay.isVisible) { isSnoozing = true + screenDim(false) setScreenTimeout(40000) - removeDimOverlay() handler.postDelayed(redimRunnable, 30000) } else { applyScreenTimeout() From 367e13701bb1f32467990c13c22fd876d77c666d Mon Sep 17 00:00:00 2001 From: Dan Wolstenholme Date: Thu, 16 Apr 2026 22:25:54 +0900 Subject: [PATCH 12/12] Fixed version number and deleted unnecessary plugin added by agent --- app/build.gradle.kts | 2 +- settings.gradle.kts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d206dd6..d7aac2a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,7 +32,7 @@ android { minSdk = 23 targetSdk = 36 versionCode = 48 - versionName = "1.0.48.2" + versionName = "1.0.48.1" } buildTypes { diff --git a/settings.gradle.kts b/settings.gradle.kts index 205baf6..2c9f205 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,9 +11,6 @@ pluginManagement { gradlePluginPortal() } } -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" -} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories {