From f2c9978f76becb0f33f0eb500a8e766b4846ec9a Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:46:08 +0530 Subject: [PATCH] Fallback to title text when logos are unavailable --- .../components/DetailFloatingHeader.kt | 2 +- .../features/details/components/DetailHero.kt | 11 +++++-- .../home/components/HomeHeroSection.kt | 12 ++++++-- .../app/features/player/PlayerOverlays.kt | 20 +++++++++---- .../app/features/streams/StreamsScreen.kt | 29 +++++++++++++++---- .../features/streams/StreamsTabletLayout.kt | 19 ++++++++---- 6 files changed, 71 insertions(+), 22 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailFloatingHeader.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailFloatingHeader.kt index 88a40c556..1b7ecaf8a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailFloatingHeader.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailFloatingHeader.kt @@ -111,7 +111,7 @@ fun DetailFloatingHeader( .padding(horizontal = 10.dp), contentAlignment = Alignment.Center, ) { - if (meta.logo != null && !logoLoadError) { + if (!meta.logo.isNullOrBlank() && !logoLoadError) { AsyncImage( model = meta.logo, contentDescription = stringResource(Res.string.detail_logo_content_description, meta.name), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailHero.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailHero.kt index 0471ed11d..a97e8ee6d 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailHero.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailHero.kt @@ -32,7 +32,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush @@ -79,6 +81,10 @@ fun DetailHero( val heroChromeTopPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 8.dp + ((40.dp - muteIconSize) / 2) + var logoLoadError by remember(meta.id, meta.logo) { + mutableStateOf(false) + } + val logoUrl = meta.logo?.takeIf { it.isNotBlank() } Box( modifier = Modifier @@ -198,9 +204,9 @@ fun DetailHero( .padding(bottom = 8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - if (meta.logo != null) { + if (logoUrl != null && !logoLoadError) { AsyncImage( - model = meta.logo, + model = logoUrl, contentDescription = stringResource(Res.string.detail_logo_content_description, meta.name), modifier = Modifier .fillMaxWidth(if (isTablet) 0.56f else 0.6f) @@ -208,6 +214,7 @@ fun DetailHero( .height(if (isTablet) 72.dp else 80.dp), alignment = Alignment.Center, contentScale = ContentScale.Fit, + onError = { logoLoadError = true }, ) } else { Text( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt index 1095bf5bb..2cfebe197 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt @@ -30,8 +30,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -347,13 +349,18 @@ private fun HeroContentBlock( layout: HomeHeroLayout, onItemClick: ((MetaPreview) -> Unit)?, ) { + var logoLoadError by remember(item.type, item.id, item.logo) { + mutableStateOf(false) + } + val logoUrl = item.logo?.takeIf { it.isNotBlank() } + Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = if (layout.isTablet) Alignment.Start else Alignment.CenterHorizontally, ) { - if (item.logo != null) { + if (logoUrl != null && !logoLoadError) { AsyncImage( - model = item.logo, + model = logoUrl, contentDescription = item.name, modifier = Modifier .fillMaxWidth(layout.logoWidthFraction) @@ -363,6 +370,7 @@ private fun HeroContentBlock( }, alignment = if (layout.isTablet) Alignment.CenterStart else Alignment.Center, contentScale = ContentScale.Fit, + onError = { logoLoadError = true }, ) } else { Text( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerOverlays.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerOverlays.kt index 00405b6f0..6ff03c858 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerOverlays.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerOverlays.kt @@ -47,6 +47,9 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -122,6 +125,8 @@ internal fun OpeningOverlay( ), label = "openingOverlayContentScale", ) + var logoLoadError by remember(logo) { mutableStateOf(false) } + val logoUrl = logo?.takeIf { it.isNotBlank() } Box( modifier = modifier @@ -178,14 +183,14 @@ internal fun OpeningOverlay( label = "openingOverlayP2pProgress", ) val progressActive = targetProgress != null - if (logo != null) { + if (logoUrl != null && !logoLoadError) { Box( modifier = Modifier .width(300.dp) .height(180.dp), ) { AsyncImage( - model = logo, + model = logoUrl, contentDescription = null, modifier = Modifier .fillMaxSize() @@ -197,10 +202,11 @@ internal fun OpeningOverlay( } }, contentScale = ContentScale.Fit, + onError = { logoLoadError = true }, ) if (progressActive) { AsyncImage( - model = logo, + model = logoUrl, contentDescription = null, modifier = Modifier .fillMaxSize() @@ -377,6 +383,9 @@ internal fun PauseMetadataOverlay( horizontalSafePadding: Dp, modifier: Modifier = Modifier, ) { + var logoLoadError by remember(logo) { mutableStateOf(false) } + val logoUrl = logo?.takeIf { it.isNotBlank() } + BoxWithConstraints( modifier = modifier .background( @@ -429,13 +438,14 @@ internal fun PauseMetadataOverlay( ) androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(if (compactHeight) 8.dp else 12.dp)) - if (!logo.isNullOrBlank()) { + if (logoUrl != null && !logoLoadError) { AsyncImage( - model = logo, + model = logoUrl, contentDescription = title, contentScale = ContentScale.Fit, alignment = Alignment.BottomStart, modifier = Modifier.height(logoHeight), + onError = { logoLoadError = true }, ) } else { Text( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsScreen.kt index 3bdaa37ab..8a25bb772 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsScreen.kt @@ -152,6 +152,8 @@ fun StreamsScreen( val noDirectStreamLinkText = stringResource(Res.string.streams_no_direct_link) var streamActionsTarget by remember(videoId) { mutableStateOf(null) } var preferredFilterApplied by remember(videoId) { mutableStateOf(false) } + var autoPlayOverlayLogoLoadError by remember(logo) { mutableStateOf(false) } + val autoPlayOverlayLogoUrl = logo?.takeIf { it.isNotBlank() } val storedProgress = if (startFromBeginning) { null } else { @@ -316,13 +318,24 @@ fun StreamsScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp), ) { - if (!logo.isNullOrBlank()) { + if (autoPlayOverlayLogoUrl != null && !autoPlayOverlayLogoLoadError) { AsyncImage( - model = logo, - contentDescription = null, + model = autoPlayOverlayLogoUrl, + contentDescription = title, modifier = Modifier .height(48.dp), contentScale = ContentScale.Fit, + onError = { autoPlayOverlayLogoLoadError = true }, + ) + } else if (title.isNotBlank()) { + Text( + text = title, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Black), + color = Color.White, + textAlign = TextAlign.Center, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 24.dp), ) } CircularProgressIndicator( @@ -532,6 +545,9 @@ private fun MovieHeroBlock( logo: String?, modifier: Modifier = Modifier, ) { + var logoLoadError by remember(logo) { mutableStateOf(false) } + val logoUrl = logo?.takeIf { it.isNotBlank() } + Box( modifier = modifier .fillMaxWidth() @@ -539,14 +555,15 @@ private fun MovieHeroBlock( .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top)), contentAlignment = Alignment.Center, ) { - if (logo != null) { + if (logoUrl != null && !logoLoadError) { AsyncImage( - model = logo, - contentDescription = null, + model = logoUrl, + contentDescription = title, modifier = Modifier .height(80.dp) .fillMaxWidth(0.85f), contentScale = ContentScale.Fit, + onError = { logoLoadError = true }, ) } else { Text( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsTabletLayout.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsTabletLayout.kt index 5ed13ae77..98c225a65 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsTabletLayout.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsTabletLayout.kt @@ -222,19 +222,23 @@ private fun TabletMovieInfoPanel( logo: String?, modifier: Modifier = Modifier, ) { + var logoLoadError by remember(logo) { mutableStateOf(false) } + val logoUrl = logo?.takeIf { it.isNotBlank() } + Column( modifier = modifier.fillMaxWidth(0.8f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - if (!logo.isNullOrBlank()) { + if (logoUrl != null && !logoLoadError) { AsyncImage( - model = logo, - contentDescription = null, + model = logoUrl, + contentDescription = title, modifier = Modifier .fillMaxWidth() .height(120.dp), contentScale = ContentScale.Fit, + onError = { logoLoadError = true }, ) } else if (title.isNotBlank()) { Text( @@ -277,6 +281,8 @@ private fun TabletEpisodeInfoPanel( showTitle: String, modifier: Modifier = Modifier, ) { + var logoLoadError by remember(logo) { mutableStateOf(false) } + val logoUrl = logo?.takeIf { it.isNotBlank() } val textShadow = Shadow( color = Color.Black, offset = Offset(0f, 0f), @@ -288,14 +294,15 @@ private fun TabletEpisodeInfoPanel( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - if (!logo.isNullOrBlank()) { + if (logoUrl != null && !logoLoadError) { AsyncImage( - model = logo, - contentDescription = null, + model = logoUrl, + contentDescription = showTitle, modifier = Modifier .fillMaxWidth() .height(120.dp), contentScale = ContentScale.Fit, + onError = { logoLoadError = true }, ) } else { Text(