From 8a8f0ea403b41e91708f91837fddfd7bb32701bb Mon Sep 17 00:00:00 2001 From: flazepe <51487578+flazepe@users.noreply.github.com> Date: Wed, 16 Sep 2026 02:32:29 +0800 Subject: [PATCH 1/2] Handle missing/invalid API key in `LazyLargeImageView` --- .../java/moe/apex/breadboard/MainActivity.kt | 2 + .../largeimageview/LargeImageView.kt | 41 +++++++++++++++---- .../main/java/moe/apex/breadboard/util/Ui.kt | 37 ++++++++++++----- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/moe/apex/breadboard/MainActivity.kt b/app/src/main/java/moe/apex/breadboard/MainActivity.kt index 313d2f6..5a438ca 100644 --- a/app/src/main/java/moe/apex/breadboard/MainActivity.kt +++ b/app/src/main/java/moe/apex/breadboard/MainActivity.kt @@ -30,6 +30,7 @@ import coil3.gif.AnimatedImageDecoder import coil3.gif.GifDecoder import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking +import moe.apex.breadboard.navigation.ApiKeysSettings import moe.apex.breadboard.navigation.ArtistProfile import moe.apex.breadboard.navigation.Favourites import moe.apex.breadboard.navigation.Home @@ -107,6 +108,7 @@ class MainActivity : SingletonImageLoader.Factory, ComponentActivity(), VolumeBu return when (intent.getStringExtra("destination")) { "artist" -> maybePrepareArtistDestination(intent) "search" -> maybePrepareResultsDestination(intent) + "api_key_settings" -> ApiKeysSettings else -> null } } diff --git a/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt b/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt index 24a1448..31ed174 100644 --- a/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt +++ b/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt @@ -1,6 +1,7 @@ package moe.apex.breadboard.largeimageview import android.annotation.SuppressLint +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.res.Configuration @@ -138,9 +139,11 @@ import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.ZoomableState import me.saket.telephoto.zoomable.rememberZoomableState import me.saket.telephoto.zoomable.zoomable +import moe.apex.breadboard.MainActivity import moe.apex.breadboard.R import moe.apex.breadboard.VolumeButtonHandler import moe.apex.breadboard.image.Image +import moe.apex.breadboard.image.ImageBoardRequirement import moe.apex.breadboard.preferences.AutoplayVideosMode import moe.apex.breadboard.preferences.DataSaver import moe.apex.breadboard.preferences.Experiment @@ -150,6 +153,7 @@ import moe.apex.breadboard.preferences.ToolbarAction import moe.apex.breadboard.prefs import moe.apex.breadboard.ui.theme.BreadboardTheme import moe.apex.breadboard.ui.theme.Typography +import moe.apex.breadboard.util.ApiKeyPrompt import moe.apex.breadboard.util.CombinedClickableAction import moe.apex.breadboard.util.showToast import moe.apex.breadboard.util.FullscreenLoadingSpinner @@ -640,18 +644,44 @@ fun LazyLargeImageView( ) { val context = LocalContext.current val prefs = LocalPreferences.current + val auth = prefs.authFor(imageSource, context) var image by remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(true) } + val needsAuth = remember { + imageSource.imageBoard.apiKeyRequirement == ImageBoardRequirement.REQUIRED && auth == null + } + var failedAuth by remember { mutableStateOf(false) } + + if (needsAuth || failedAuth) { + return Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + ApiKeyPrompt( + title = "API key missing or invalid", + summary = "To view a post from ${imageSource.label}, you must first set a valid API key." + ) { + val intent = Intent(Intent.ACTION_VIEW) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.component = ComponentName(context, MainActivity::class.java) + intent.putExtra("destination", "api_key_settings") + context.startActivity(intent) + } + } + } LaunchedEffect(Unit) { try { - val auth = prefs.authFor(imageSource, context) image = if (isMd5) imageSource.imageBoard.loadImageMd5(id, auth) else imageSource.imageBoard.loadImage(id, auth) - } catch (e: ExecutionException) { - if (e.cause is SocketTimeoutException) { + } catch (e: Exception) { + if (e is ExecutionException && e.cause is SocketTimeoutException) { showToast(context, "Connection timed out") } + /* Set `failedAuth` to `true` if the image failed to load despite + the image board requiring an API key (and an API key is already set). */ + failedAuth = imageSource.imageBoard.apiKeyRequirement == ImageBoardRequirement.REQUIRED && auth != null + Log.e("LazyLargeImageView", "Error loading image", e) } isLoading = false } @@ -667,10 +697,7 @@ fun LazyLargeImageView( listOf(image!!), onImageUpdate = { if (image?.hasGroupedTags == false) { - refreshImageMetadata( - image = image!!, - auth = prefs.authFor(image!!.imageSource, context) - ) { newImage -> + refreshImageMetadata(image!!, auth) { newImage -> image = newImage } } diff --git a/app/src/main/java/moe/apex/breadboard/util/Ui.kt b/app/src/main/java/moe/apex/breadboard/util/Ui.kt index 6560b9b..e834537 100644 --- a/app/src/main/java/moe/apex/breadboard/util/Ui.kt +++ b/app/src/main/java/moe/apex/breadboard/util/Ui.kt @@ -1654,24 +1654,22 @@ fun ExpressivePromptWithActions( @Composable -fun ApiKeyRequiredPrompt( +fun ApiKeyPrompt( + title: String, + summary: String, modifier: Modifier = Modifier, - source: ImageSource, - navController: NavController, - beforeActions: (@Composable RowScope.() -> Unit)? = null + beforeActions: (@Composable RowScope.() -> Unit)? = null, + onSettingsClick: () -> Unit ) { ExpressivePromptWithActions( modifier = modifier, - title = "API key needed", - summary = "To use ${source.label}, you must first set an API key.\n" + - "Alternatively, choose a different source." + title = title, + summary = summary ) { beforeActions?.invoke(this) Button( - onClick = { - navController.navigate(ApiKeysSettings) - }, + onClick = onSettingsClick, shapes = ButtonDefaults.shapes() ) { Text("API key settings") @@ -1680,6 +1678,25 @@ fun ApiKeyRequiredPrompt( } +@Composable +fun ApiKeyRequiredPrompt( + modifier: Modifier = Modifier, + source: ImageSource, + navController: NavController, + beforeActions: (@Composable RowScope.() -> Unit)? = null +) { + ApiKeyPrompt( + title = "API key needed", + summary = "To use ${source.label}, you must first set an API key.\n" + + "Alternatively, choose a different source.", + modifier = modifier, + beforeActions = beforeActions + ) { + navController.navigate(ApiKeysSettings) + } +} + + fun generateColours( darkTheme: Boolean, seed: Any From d37720123284b20b8a85c231ec06be65777422d1 Mon Sep 17 00:00:00 2001 From: flazepe <51487578+flazepe@users.noreply.github.com> Date: Wed, 16 Sep 2026 02:39:35 +0800 Subject: [PATCH 2/2] Properly match destination name --- app/src/main/java/moe/apex/breadboard/MainActivity.kt | 2 +- .../java/moe/apex/breadboard/largeimageview/LargeImageView.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/moe/apex/breadboard/MainActivity.kt b/app/src/main/java/moe/apex/breadboard/MainActivity.kt index 5a438ca..a4fb304 100644 --- a/app/src/main/java/moe/apex/breadboard/MainActivity.kt +++ b/app/src/main/java/moe/apex/breadboard/MainActivity.kt @@ -108,7 +108,7 @@ class MainActivity : SingletonImageLoader.Factory, ComponentActivity(), VolumeBu return when (intent.getStringExtra("destination")) { "artist" -> maybePrepareArtistDestination(intent) "search" -> maybePrepareResultsDestination(intent) - "api_key_settings" -> ApiKeysSettings + "api_keys_settings" -> ApiKeysSettings else -> null } } diff --git a/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt b/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt index 31ed174..d67e496 100644 --- a/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt +++ b/app/src/main/java/moe/apex/breadboard/largeimageview/LargeImageView.kt @@ -664,7 +664,7 @@ fun LazyLargeImageView( val intent = Intent(Intent.ACTION_VIEW) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.component = ComponentName(context, MainActivity::class.java) - intent.putExtra("destination", "api_key_settings") + intent.putExtra("destination", "api_keys_settings") context.startActivity(intent) } }