From 6cdef22a87448464c10d2078137c7b51e3514503 Mon Sep 17 00:00:00 2001 From: Davide Palchetti Date: Wed, 27 May 2026 19:42:09 +1000 Subject: [PATCH 1/4] Update API models to match latest ImmichFrame server, fix retry logic for issue #55 - Sync ServerSettings with latest ClientSettingsDto: remove margin and unattendedMode, add clockDateFormat, showProgressBar, showAlbumName, weatherIconUrl, imagePan fields; make appropriate fields nullable - Make ImageResponse and Weather fields nullable with Kotlin defaults - Add api/Config/Version endpoint to ApiService - Fix getServerSettings() retry logic: 4xx errors (404, 401, 403) now fail immediately with descriptive messages instead of infinite retries - Add null safety to all downstream code in MainActivity, ScreenSaverService, and WidgetProvider --- .../com/immichframe/immichframe/Helpers.kt | 72 +++++++++--------- .../immichframe/immichframe/MainActivity.kt | 73 +++++++++++++------ .../immichframe/ScreenSaverService.kt | 72 ++++++++++++------ .../immichframe/immichframe/WidgetProvider.kt | 29 ++++---- 4 files changed, 152 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/Helpers.kt b/app/src/main/java/com/immichframe/immichframe/Helpers.kt index 1ff4786..02b659c 100644 --- a/app/src/main/java/com/immichframe/immichframe/Helpers.kt +++ b/app/src/main/java/com/immichframe/immichframe/Helpers.kt @@ -114,45 +114,48 @@ object Helpers { } data class ImageResponse( - val randomImageBase64: String, - val thumbHashImageBase64: String, - val photoDate: String, - val imageLocation: String + val randomImageBase64: String? = null, + val thumbHashImageBase64: String? = null, + val photoDate: String? = null, + val imageLocation: String? = null ) data class ServerSettings( - val margin: String, - val interval: Int, - val transitionDuration: Double, - val downloadImages: Boolean, - val renewImagesDuration: Int, - val showClock: Boolean, - val clockFormat: String, - val showPhotoDate: Boolean, - val photoDateFormat: String, - val showImageDesc: Boolean, - val showPeopleDesc: Boolean, - val showImageLocation: Boolean, - val imageLocationFormat: String, - val primaryColor: String?, - val secondaryColor: String, - val style: String, - val baseFontSize: String?, - val showWeatherDescription: Boolean, - val unattendedMode: Boolean, - val imageZoom: Boolean, - val imageFill: Boolean, - val layout: String, - val language: String + val interval: Int = 10, + val transitionDuration: Double = 1.0, + val downloadImages: Boolean = false, + val renewImagesDuration: Int = 0, + val showClock: Boolean = false, + val clockFormat: String? = null, + val clockDateFormat: String? = null, + val showPhotoDate: Boolean = false, + val showProgressBar: Boolean = false, + val photoDateFormat: String? = null, + val showImageDesc: Boolean = false, + val showPeopleDesc: Boolean = false, + val showAlbumName: Boolean = false, + val showImageLocation: Boolean = false, + val imageLocationFormat: String? = null, + val primaryColor: String? = null, + val secondaryColor: String? = null, + val style: String? = null, + val baseFontSize: String? = null, + val showWeatherDescription: Boolean = false, + val weatherIconUrl: String? = null, + val imageZoom: Boolean = false, + val imagePan: Boolean = false, + val imageFill: Boolean = false, + val layout: String? = null, + val language: String? = null ) data class Weather( - val location: String, - val temperature: Double, - val unit: String, - val temperatureUnit: String, - val description: String, - val iconId: String + val location: String? = null, + val temperature: Double = 0.0, + val unit: String? = null, + val temperatureUnit: String? = null, + val description: String? = null, + val iconId: String? = null ) interface ApiService { @@ -162,6 +165,9 @@ object Helpers { @GET("api/Config") fun getServerSettings(): Call + @GET("api/Config/Version") + fun getVersion(): Call + @GET("api/Weather") fun getWeather(): Call } diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 4cde6f2..59b2a53 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -178,21 +178,26 @@ class MainActivity : AppCompatActivity() { private fun showImage(imageResponse: Helpers.ImageResponse) { CoroutineScope(Dispatchers.IO).launch { - //get the window size + val imageBase64 = imageResponse.randomImageBase64 + val thumbHashBase64 = imageResponse.thumbHashImageBase64 + if (imageBase64 == null || thumbHashBase64 == null) { + return@launch + } + val decorView = window.decorView val width = decorView.width val height = decorView.height val maxSize = maxOf(width, height) - var randomBitmap = Helpers.decodeBitmapFromBytes(imageResponse.randomImageBase64) - val thumbHashBitmap = Helpers.decodeBitmapFromBytes(imageResponse.thumbHashImageBase64) + var randomBitmap = Helpers.decodeBitmapFromBytes(imageBase64) + val thumbHashBitmap = Helpers.decodeBitmapFromBytes(thumbHashBase64) var isMerged = false val isPortrait = randomBitmap.height > randomBitmap.width if (isPortrait && serverSettings.layout == "splitview") { - if (portraitCache != null) { + if (portraitCache != null && portraitCache!!.randomImageBase64 != null) { var decodedPortraitImageBitmap = - Helpers.decodeBitmapFromBytes(portraitCache!!.randomImageBase64) + Helpers.decodeBitmapFromBytes(portraitCache!!.randomImageBase64!!) decodedPortraitImageBitmap = Helpers.reduceBitmapQuality(decodedPortraitImageBitmap, maxSize) randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, maxSize) @@ -265,16 +270,21 @@ class MainActivity : AppCompatActivity() { isShowingFirst = !isShowingFirst if (isMerged) { + val cachedPhotoDate = portraitCache?.photoDate.orEmpty() + val cachedImageLocation = portraitCache?.imageLocation.orEmpty() + val responsePhotoDate = imageResponse.photoDate.orEmpty() + val responseImageLocation = imageResponse.imageLocation.orEmpty() + val mergedPhotoDate = - if (portraitCache!!.photoDate.isNotEmpty() || imageResponse.photoDate.isNotEmpty()) { - "${portraitCache!!.photoDate} | ${imageResponse.photoDate}" + if (cachedPhotoDate.isNotEmpty() || responsePhotoDate.isNotEmpty()) { + "$cachedPhotoDate | $responsePhotoDate" } else { "" } val mergedImageLocation = - if (portraitCache!!.imageLocation.isNotEmpty() || imageResponse.imageLocation.isNotEmpty()) { - "${portraitCache!!.imageLocation} | ${imageResponse.imageLocation}" + if (cachedImageLocation.isNotEmpty() || responseImageLocation.isNotEmpty()) { + "$cachedImageLocation | $responseImageLocation" } else { "" } @@ -282,7 +292,7 @@ class MainActivity : AppCompatActivity() { updatePhotoInfo(mergedPhotoDate, mergedImageLocation) portraitCache = null } else { - updatePhotoInfo(imageResponse.photoDate, imageResponse.imageLocation) + updatePhotoInfo(imageResponse.photoDate.orEmpty(), imageResponse.imageLocation.orEmpty()) } updateDateTimeWeather() @@ -308,17 +318,17 @@ class MainActivity : AppCompatActivity() { val currentDateTime = Calendar.getInstance().time val formattedDate = try { - SimpleDateFormat(serverSettings.photoDateFormat, Locale.getDefault()).format( - currentDateTime - ) + serverSettings.photoDateFormat?.let { + SimpleDateFormat(it, Locale.getDefault()).format(currentDateTime) + } ?: "" } catch (_: Exception) { "" } val formattedTime = try { - SimpleDateFormat(serverSettings.clockFormat, Locale.getDefault()).format( - currentDateTime - ) + serverSettings.clockFormat?.let { + SimpleDateFormat(it, Locale.getDefault()).format(currentDateTime) + } ?: "" } catch (_: Exception) { "" } @@ -418,8 +428,11 @@ class MainActivity : AppCompatActivity() { if (response.isSuccessful) { val weatherResponse = response.body() if (weatherResponse != null) { - currentWeather = - "\n ${weatherResponse.location}, ${"%.1f".format(weatherResponse.temperature)}${weatherResponse.unit} \n ${weatherResponse.description}" + val loc = weatherResponse.location ?: "Unknown" + val temp = weatherResponse.temperature + val unit = weatherResponse.unit ?: "" + val desc = weatherResponse.description ?: "" + currentWeather = "\n $loc, ${"%.1f".format(temp)}$unit \n $desc" } } } @@ -452,26 +465,38 @@ class MainActivity : AppCompatActivity() { if (serverSettingsResponse != null) { onSuccess(serverSettingsResponse) } else { - handleFailure(Exception("Empty response body")) + handleFailure(Exception("Empty response body"), retryable = true) } } else { - handleFailure(Exception("HTTP ${response.code()}: ${response.message()}")) + val code = response.code() + val retryable = code !in 400..499 + val hint = when (code) { + 404 -> "Endpoint not found. Make sure the URL points to an ImmichFrame server, not the Immich server directly." + 401, 403 -> "Authentication failed. Check your Authorization Secret in settings." + else -> null + } + val msg = if (hint != null) "$hint (HTTP $code)" else "HTTP $code: ${response.message()}" + handleFailure(Exception(msg), retryable = retryable) } } override fun onFailure(call: Call, t: Throwable) { - handleFailure(t) + handleFailure(t, retryable = true) } - private fun handleFailure(t: Throwable) { + private fun handleFailure(t: Throwable, retryable: Boolean) { if (useWebView) { return } + if (!retryable) { + onFailure(t) + return + } if (retryCount < maxRetries) { retryCount++ Toast.makeText( this@MainActivity, - "Retrying to fetch server settings... Attempt $retryCount of $maxRetries", + "Connecting to server... Attempt $retryCount of $maxRetries", Toast.LENGTH_SHORT ).show() Handler(Looper.getMainLooper()).postDelayed({ @@ -596,7 +621,7 @@ class MainActivity : AppCompatActivity() { Toast.makeText( this, "Failed to load server settings: ${error.localizedMessage}", - Toast.LENGTH_SHORT + Toast.LENGTH_LONG ).show() } ) diff --git a/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt b/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt index 97cf85e..3b9d021 100644 --- a/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt +++ b/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt @@ -168,21 +168,26 @@ class ScreenSaverService : DreamService() { private fun showImage(imageResponse: Helpers.ImageResponse) { CoroutineScope(Dispatchers.IO).launch { - //get the window size + val imageBase64 = imageResponse.randomImageBase64 + val thumbHashBase64 = imageResponse.thumbHashImageBase64 + if (imageBase64 == null || thumbHashBase64 == null) { + return@launch + } + val decorView = window.decorView val width = decorView.width val height = decorView.height val maxSize = maxOf(width, height) - var randomBitmap = Helpers.decodeBitmapFromBytes(imageResponse.randomImageBase64) - val thumbHashBitmap = Helpers.decodeBitmapFromBytes(imageResponse.thumbHashImageBase64) + var randomBitmap = Helpers.decodeBitmapFromBytes(imageBase64) + val thumbHashBitmap = Helpers.decodeBitmapFromBytes(thumbHashBase64) var isMerged = false val isPortrait = randomBitmap.height > randomBitmap.width if (isPortrait && serverSettings.layout == "splitview") { - if (portraitCache != null) { + if (portraitCache != null && portraitCache!!.randomImageBase64 != null) { var decodedPortraitImageBitmap = - Helpers.decodeBitmapFromBytes(portraitCache!!.randomImageBase64) + Helpers.decodeBitmapFromBytes(portraitCache!!.randomImageBase64!!) decodedPortraitImageBitmap = Helpers.reduceBitmapQuality(decodedPortraitImageBitmap, maxSize) randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, maxSize) @@ -255,16 +260,21 @@ class ScreenSaverService : DreamService() { isShowingFirst = !isShowingFirst if (isMerged) { + val cachedPhotoDate = portraitCache?.photoDate.orEmpty() + val cachedImageLocation = portraitCache?.imageLocation.orEmpty() + val responsePhotoDate = imageResponse.photoDate.orEmpty() + val responseImageLocation = imageResponse.imageLocation.orEmpty() + val mergedPhotoDate = - if (portraitCache!!.photoDate.isNotEmpty() || imageResponse.photoDate.isNotEmpty()) { - "${portraitCache!!.photoDate} | ${imageResponse.photoDate}" + if (cachedPhotoDate.isNotEmpty() || responsePhotoDate.isNotEmpty()) { + "$cachedPhotoDate | $responsePhotoDate" } else { "" } val mergedImageLocation = - if (portraitCache!!.imageLocation.isNotEmpty() || imageResponse.imageLocation.isNotEmpty()) { - "${portraitCache!!.imageLocation} | ${imageResponse.imageLocation}" + if (cachedImageLocation.isNotEmpty() || responseImageLocation.isNotEmpty()) { + "$cachedImageLocation | $responseImageLocation" } else { "" } @@ -272,7 +282,7 @@ class ScreenSaverService : DreamService() { updatePhotoInfo(mergedPhotoDate, mergedImageLocation) portraitCache = null } else { - updatePhotoInfo(imageResponse.photoDate, imageResponse.imageLocation) + updatePhotoInfo(imageResponse.photoDate.orEmpty(), imageResponse.imageLocation.orEmpty()) } updateDateTimeWeather() @@ -298,17 +308,17 @@ class ScreenSaverService : DreamService() { val currentDateTime = Calendar.getInstance().time val formattedDate = try { - SimpleDateFormat(serverSettings.photoDateFormat, Locale.getDefault()).format( - currentDateTime - ) + serverSettings.photoDateFormat?.let { + SimpleDateFormat(it, Locale.getDefault()).format(currentDateTime) + } ?: "" } catch (_: Exception) { "" } val formattedTime = try { - SimpleDateFormat(serverSettings.clockFormat, Locale.getDefault()).format( - currentDateTime - ) + serverSettings.clockFormat?.let { + SimpleDateFormat(it, Locale.getDefault()).format(currentDateTime) + } ?: "" } catch (_: Exception) { "" } @@ -396,8 +406,10 @@ class ScreenSaverService : DreamService() { if (response.isSuccessful) { val weatherResponse = response.body() if (weatherResponse != null) { - currentWeather = - "\n ${weatherResponse.location}, ${weatherResponse.temperatureUnit} \n ${weatherResponse.description}" + val loc = weatherResponse.location ?: "Unknown" + val unit = weatherResponse.temperatureUnit ?: "" + val desc = weatherResponse.description ?: "" + currentWeather = "\n $loc, $unit \n $desc" } } } @@ -427,23 +439,35 @@ class ScreenSaverService : DreamService() { if (serverSettingsResponse != null) { onSuccess(serverSettingsResponse) } else { - handleFailure(Exception("Empty response body")) + handleFailure(Exception("Empty response body"), retryable = true) } } else { - handleFailure(Exception("HTTP ${response.code()}: ${response.message()}")) + val code = response.code() + val retryable = code !in 400..499 + val hint = when (code) { + 404 -> "Endpoint not found. Make sure the URL points to an ImmichFrame server, not the Immich server directly." + 401, 403 -> "Authentication failed. Check your Authorization Secret in settings." + else -> null + } + val msg = if (hint != null) "$hint (HTTP $code)" else "HTTP $code: ${response.message()}" + handleFailure(Exception(msg), retryable = retryable) } } override fun onFailure(call: Call, t: Throwable) { - handleFailure(t) + handleFailure(t, retryable = true) } - private fun handleFailure(t: Throwable) { + private fun handleFailure(t: Throwable, retryable: Boolean) { + if (!retryable) { + onFailure(t) + return + } if (retryCount < maxRetries) { retryCount++ Toast.makeText( this@ScreenSaverService, - "Retrying to fetch server settings... Attempt $retryCount of $maxRetries", + "Connecting to server... Attempt $retryCount of $maxRetries", Toast.LENGTH_SHORT ).show() Handler(Looper.getMainLooper()).postDelayed({ @@ -538,7 +562,7 @@ class ScreenSaverService : DreamService() { Toast.makeText( this, "Failed to load server settings: ${error.localizedMessage}", - Toast.LENGTH_SHORT + Toast.LENGTH_LONG ).show() } ) diff --git a/app/src/main/java/com/immichframe/immichframe/WidgetProvider.kt b/app/src/main/java/com/immichframe/immichframe/WidgetProvider.kt index 9770612..8d6dd95 100644 --- a/app/src/main/java/com/immichframe/immichframe/WidgetProvider.kt +++ b/app/src/main/java/com/immichframe/immichframe/WidgetProvider.kt @@ -84,19 +84,22 @@ class WidgetProvider : AppWidgetProvider() { CoroutineScope(Dispatchers.IO).launch { getNextImage(apiService) { imageResponse -> imageResponse?.let { - CoroutineScope(Dispatchers.IO).launch { - var randomBitmap = - Helpers.decodeBitmapFromBytes(it.randomImageBase64) - - //randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, maxSize) - randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, 1000) - - withContext(Dispatchers.Main) { - views.setImageViewBitmap( - R.id.widgetImageView, - randomBitmap - ) - appWidgetManager.updateAppWidget(appWidgetId, views) + val base64 = it.randomImageBase64 + if (base64 != null) { + CoroutineScope(Dispatchers.IO).launch { + var randomBitmap = + Helpers.decodeBitmapFromBytes(base64) + + //randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, maxSize) + randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, 1000) + + withContext(Dispatchers.Main) { + views.setImageViewBitmap( + R.id.widgetImageView, + randomBitmap + ) + appWidgetManager.updateAppWidget(appWidgetId, views) + } } } } From 9c85c07327e46d95052893984263ccfc35d858aa Mon Sep 17 00:00:00 2001 From: Davide Palchetti Date: Wed, 27 May 2026 20:15:13 +1000 Subject: [PATCH 2/4] Fix TOCTOU race on portraitCache in showImage coroutine Capture mutable portraitCache field into a local val at the start of the splitview branch to avoid racing with main-thread updates from updateUI(). Eliminates forced !! operator dereferences. --- .../main/java/com/immichframe/immichframe/MainActivity.kt | 5 +++-- .../java/com/immichframe/immichframe/ScreenSaverService.kt | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 59b2a53..7476c09 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -195,9 +195,10 @@ class MainActivity : AppCompatActivity() { val isPortrait = randomBitmap.height > randomBitmap.width if (isPortrait && serverSettings.layout == "splitview") { - if (portraitCache != null && portraitCache!!.randomImageBase64 != null) { + val localPortrait = portraitCache + if (localPortrait?.randomImageBase64 != null) { var decodedPortraitImageBitmap = - Helpers.decodeBitmapFromBytes(portraitCache!!.randomImageBase64!!) + Helpers.decodeBitmapFromBytes(localPortrait.randomImageBase64) decodedPortraitImageBitmap = Helpers.reduceBitmapQuality(decodedPortraitImageBitmap, maxSize) randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, maxSize) diff --git a/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt b/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt index 3b9d021..f95c6cb 100644 --- a/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt +++ b/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt @@ -185,9 +185,10 @@ class ScreenSaverService : DreamService() { val isPortrait = randomBitmap.height > randomBitmap.width if (isPortrait && serverSettings.layout == "splitview") { - if (portraitCache != null && portraitCache!!.randomImageBase64 != null) { + val localPortrait = portraitCache + if (localPortrait?.randomImageBase64 != null) { var decodedPortraitImageBitmap = - Helpers.decodeBitmapFromBytes(portraitCache!!.randomImageBase64!!) + Helpers.decodeBitmapFromBytes(localPortrait.randomImageBase64) decodedPortraitImageBitmap = Helpers.reduceBitmapQuality(decodedPortraitImageBitmap, maxSize) randomBitmap = Helpers.reduceBitmapQuality(randomBitmap, maxSize) From 1d0b8ee6a6d06e12ed3ad73630c59e813bbbd4b0 Mon Sep 17 00:00:00 2001 From: Davide Palchetti Date: Thu, 28 May 2026 05:43:30 +1000 Subject: [PATCH 3/4] Remove unused ServerSettings fields per maintainer feedback --- app/src/main/java/com/immichframe/immichframe/Helpers.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/Helpers.kt b/app/src/main/java/com/immichframe/immichframe/Helpers.kt index 02b659c..f0b052d 100644 --- a/app/src/main/java/com/immichframe/immichframe/Helpers.kt +++ b/app/src/main/java/com/immichframe/immichframe/Helpers.kt @@ -127,13 +127,10 @@ object Helpers { val renewImagesDuration: Int = 0, val showClock: Boolean = false, val clockFormat: String? = null, - val clockDateFormat: String? = null, val showPhotoDate: Boolean = false, - val showProgressBar: Boolean = false, val photoDateFormat: String? = null, val showImageDesc: Boolean = false, val showPeopleDesc: Boolean = false, - val showAlbumName: Boolean = false, val showImageLocation: Boolean = false, val imageLocationFormat: String? = null, val primaryColor: String? = null, @@ -141,9 +138,7 @@ object Helpers { val style: String? = null, val baseFontSize: String? = null, val showWeatherDescription: Boolean = false, - val weatherIconUrl: String? = null, val imageZoom: Boolean = false, - val imagePan: Boolean = false, val imageFill: Boolean = false, val layout: String? = null, val language: String? = null From ee52b7152a0c2b8fcd5969358b929e97547716ed Mon Sep 17 00:00:00 2001 From: Davide Palchetti Date: Thu, 28 May 2026 05:56:12 +1000 Subject: [PATCH 4/4] Fix dangling | separators in merged metadata and use class handler for retries --- .../immichframe/immichframe/MainActivity.kt | 24 +++++++------------ .../immichframe/ScreenSaverService.kt | 24 +++++++------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index 7476c09..93ae9e9 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -276,19 +276,13 @@ class MainActivity : AppCompatActivity() { val responsePhotoDate = imageResponse.photoDate.orEmpty() val responseImageLocation = imageResponse.imageLocation.orEmpty() - val mergedPhotoDate = - if (cachedPhotoDate.isNotEmpty() || responsePhotoDate.isNotEmpty()) { - "$cachedPhotoDate | $responsePhotoDate" - } else { - "" - } + val mergedPhotoDate = listOf(cachedPhotoDate, responsePhotoDate) + .filter { it.isNotEmpty() } + .joinToString(" | ") - val mergedImageLocation = - if (cachedImageLocation.isNotEmpty() || responseImageLocation.isNotEmpty()) { - "$cachedImageLocation | $responseImageLocation" - } else { - "" - } + val mergedImageLocation = listOf(cachedImageLocation, responseImageLocation) + .filter { it.isNotEmpty() } + .joinToString(" | ") updatePhotoInfo(mergedPhotoDate, mergedImageLocation) portraitCache = null @@ -500,7 +494,7 @@ class MainActivity : AppCompatActivity() { "Connecting to server... Attempt $retryCount of $maxRetries", Toast.LENGTH_SHORT ).show() - Handler(Looper.getMainLooper()).postDelayed({ + handler.postDelayed({ attemptFetch() }, retryDelayMillis) } else { @@ -582,13 +576,13 @@ class MainActivity : AppCompatActivity() { if (request?.isForMainFrame == true && error != null) { view?.loadUrl("file:///android_asset/error_page.html") - Handler(Looper.getMainLooper()).postDelayed({ + handler.postDelayed({ val errorCode = error.errorCode val errorDescription = error.description.toString().replace("'", "\\'") view?.evaluateJavascript("showError('$errorCode', '$errorDescription')", null) }, 500) } - Handler(Looper.getMainLooper()).postDelayed({ + handler.postDelayed({ //check url again in case the user has changed it var currentUrl = prefs.getString("webview_url", "")?.trim() ?: "" currentUrl = if (authSecret.isNotEmpty()) { diff --git a/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt b/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt index f95c6cb..40cea90 100644 --- a/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt +++ b/app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt @@ -266,19 +266,13 @@ class ScreenSaverService : DreamService() { val responsePhotoDate = imageResponse.photoDate.orEmpty() val responseImageLocation = imageResponse.imageLocation.orEmpty() - val mergedPhotoDate = - if (cachedPhotoDate.isNotEmpty() || responsePhotoDate.isNotEmpty()) { - "$cachedPhotoDate | $responsePhotoDate" - } else { - "" - } + val mergedPhotoDate = listOf(cachedPhotoDate, responsePhotoDate) + .filter { it.isNotEmpty() } + .joinToString(" | ") - val mergedImageLocation = - if (cachedImageLocation.isNotEmpty() || responseImageLocation.isNotEmpty()) { - "$cachedImageLocation | $responseImageLocation" - } else { - "" - } + val mergedImageLocation = listOf(cachedImageLocation, responseImageLocation) + .filter { it.isNotEmpty() } + .joinToString(" | ") updatePhotoInfo(mergedPhotoDate, mergedImageLocation) portraitCache = null @@ -471,7 +465,7 @@ class ScreenSaverService : DreamService() { "Connecting to server... Attempt $retryCount of $maxRetries", Toast.LENGTH_SHORT ).show() - Handler(Looper.getMainLooper()).postDelayed({ + handler.postDelayed({ attemptFetch() }, retryDelayMillis) } else { @@ -536,13 +530,13 @@ class ScreenSaverService : DreamService() { if (request?.isForMainFrame == true && error != null) { view?.loadUrl("file:///android_asset/error_page.html") - Handler(Looper.getMainLooper()).postDelayed({ + handler.postDelayed({ val errorCode = error.errorCode val errorDescription = error.description.toString().replace("'", "\\'") view?.evaluateJavascript("showError('$errorCode', '$errorDescription')", null) }, 500) } - Handler(Looper.getMainLooper()).postDelayed({ + handler.postDelayed({ webView.loadUrl(savedUrl) }, 5000) }