feat(app,android,ios): a blurred poster where a title has no wide art

A title with no fanart, banner or backdrop drew its poster cropped to the
band's shape across the title page and the library's banner: a blown-up
slice of the middle of it, which read as broken. Now the poster is
squeezed to 64x36 and blurred once when it is fetched, the cache keeps
that wash, and it is stretched across the band. The library's banner
stands the sharp poster at its right over it. Android blurs the poster
it already had (Android 12 and later; older versions decode it tiny),
and iOS likewise.

METADATA.md also records the quality badges of the commit before.
This commit is contained in:
nvrl committed 2026-10-29 16:59:46 +01:00
1 parent f4c08d03c7
commit 3ebfd1a57b
12 files changed
+182 -19

No files matched your search

@@ -628,13 +628,7 @@ private fun featured(titles: List<TitleRecord>, resumable: List<Resumable>): Tit
private fun FeaturedBanner(title: TitleRecord, resume: Resumable?, onPlay: (Int) -> Unit, onOpen: () -> Unit) {
val playlist = remember(title) { title.seasons.flatMap(SeasonRecord::episodes) }
Box(Modifier.fillMaxWidth().aspectRatio(4f / 3f).clip(MaterialTheme.shapes.large)) {
RemoteArtwork(
title.backdropUrl ?: title.posterUrl,
title.displayName,
Modifier.fillMaxSize(),
fallback = title.thumbnailSource,
labelled = false,
)
Backdrop(title, Modifier.fillMaxSize())
Box(
Modifier.fillMaxSize().background(
Brush.verticalGradient(0.35f to Color.Transparent, 1f to Color.Black.copy(alpha = 0.85f)),
@@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
@@ -34,6 +35,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.font.FontWeight
import io.narl.protonstream.native.NativeRuntime
import kotlinx.coroutines.Dispatchers
@@ -112,6 +114,29 @@ internal fun RemoteArtwork(
}
}
/**
* A title's art across the top of a page: its backdrop, or with none, its
* poster as a wash of its own colours — decoded tiny and blurred, so it reads as
* the poster's light rather than a blown-up slice of it. The desktop's
* `Fit::Ambient`. Android 11 and older cannot blur; there the tiny decode,
* stretched, is what softens it.
*/
@Composable
internal fun Backdrop(title: TitleRecord, modifier: Modifier = Modifier) {
val wash = title.backdropUrl == null && title.posterUrl != null
RemoteArtwork(
title.backdropUrl ?: title.posterUrl,
title.displayName,
if (wash) modifier.blur(WASH_BLUR) else modifier,
fallback = title.thumbnailSource,
labelled = false,
maxEdge = if (wash) WASH_EDGE else null,
)
}
private val WASH_EDGE = 48.dp
private val WASH_BLUR = 24.dp
/**
* A title's logo — its name as lettering — in place of [fallback], once it has
* loaded. [fallback] stands until then, and for good when there is none, so a
@@ -183,13 +183,7 @@ internal fun TitleScreen(
// the page says, and a framed thumbnail with a margin round it read
// as one card among the many below it.
Box(Modifier.sharedArt(title.key).fillMaxWidth().height(backdropHeight)) {
RemoteArtwork(
title.backdropUrl ?: title.posterUrl,
title.displayName,
Modifier.fillMaxSize(),
fallback = title.thumbnailSource,
labelled = false,
)
Backdrop(title, Modifier.fillMaxSize())
Box(
Modifier.fillMaxSize().background(
Brush.verticalGradient(