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 06e8c15..8c2eba2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
+ android:theme="@style/Theme.WidgetConfigDialog"
+ android:exported="true" />
@@ -80,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 ed2b99b..8a9335f 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,8 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
+import android.os.PowerManager
+import android.provider.Settings
import android.text.SpannableString
import android.text.Spanned
import android.text.style.RelativeSizeSpan
@@ -75,6 +79,8 @@ 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 var isSnoozing = false
private val imageRunnable = object : Runnable {
override fun run() {
if (isImageTimerRunning) {
@@ -97,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
@@ -488,6 +500,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)
@@ -518,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
@@ -607,6 +620,7 @@ class MainActivity : AppCompatActivity() {
}
)
}
+ applyScreenTimeout()
}
private fun onSettingsLoaded() {
@@ -770,39 +784,137 @@ class MainActivity : AppCompatActivity() {
}
}
- private fun screenDim(dim: Boolean) {
- val lp = window.attributes
- if (dim) {
- lp.screenBrightness = 0.01f
- window.attributes = lp
- if (dimOverlay.visibility != View.VISIBLE) {
- dimOverlay.apply {
- visibility = View.VISIBLE
- alpha = 0f
- if (useWebView) {
- webView.loadUrl("about:blank")
- } else {
- stopImageTimer()
- stopWeatherTimer()
+ // 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}")
+ }
+ }
+
+ // 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)
+ }
+ }
+
+ // 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)
}
- animate()
- .alpha(0.99f)
- .setDuration(500L)
- .start()
}
+ .start()
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun screenDim(dim: Boolean) {
+ if (dim) {
+ // save user's screen timeout and set to 3s to show "going to sleep" message
+ 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
+ // wait for screen to go to sleep after inactivity
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ applyDimOverlay()
+
} else {
- lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
- window.attributes = lp
- if (dimOverlay.isVisible) {
- dimOverlay.animate()
- .alpha(0f)
- .setDuration(500L)
- .withEndAction {
- dimOverlay.visibility = View.GONE
- loadSettings()
- }
- .start()
+ // 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
+ // FLAG_KEEP_SCREEN_ON will keep it on afterwards
+ val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
+ val wakeLock = powerManager.newWakeLock(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
+ "ImmichFrame:WakeLockTag"
+ )
+ wakeLock.acquire(10 * 1000L) // 10 second timeout
+ wakeLock.release()
+
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ removeDimOverlay()
+
+ // 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)
}
}
}
@@ -827,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)
}
}
@@ -841,21 +955,33 @@ class MainActivity : AppCompatActivity() {
}
}
+ // called when user returns to app
override fun onResume() {
super.onResume()
- if (keepScreenOn) {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+
+ // 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 {
- 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..6698fed 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,21 @@ 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
+ if (!keepScreenOn) {
+ chkScreenDim?.isChecked = false
+ txtDimTime?.isVisible = false
+ }
+
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 +61,40 @@ class SettingsFragment : PreferenceFragmentCompat() {
//add android settings button
true
}
+
+ // 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 in 1..1440) {
+ true
+ } else {
+ Toast.makeText(requireContext(), "Please enter a value between 1 and 1440 minutes (24 hours)", 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 +115,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
true
}
-
+ // close settings view
val btnClose = findPreference("closeSettings")
btnClose?.setOnPreferenceClickListener {
val url = PreferenceManager.getDefaultSharedPreferences(requireContext())
@@ -88,6 +131,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
+ // launch Android settings selection
val btnAndroidSettings = findPreference("androidSettings")
btnAndroidSettings?.setOnPreferenceClickListener {
val context = requireContext()
@@ -111,9 +155,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
true
}
-
- val timePref = findPreference("dim_time_range")
- timePref?.setOnPreferenceChangeListener { _, newValue ->
+ // get dimming time range setting from input string
+ 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()
@@ -138,4 +181,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
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 {