diff --git a/app/src/main/kotlin/com/subtracks/ui/components/Components.kt b/app/src/main/kotlin/com/subtracks/ui/components/Components.kt index a4a55f0d..4ea8303e 100644 --- a/app/src/main/kotlin/com/subtracks/ui/components/Components.kt +++ b/app/src/main/kotlin/com/subtracks/ui/components/Components.kt @@ -4,9 +4,11 @@ import android.content.Context import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme @@ -19,12 +21,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Size import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage -import coil3.compose.AsyncImagePainter import coil3.compose.LocalPlatformContext import coil3.compose.rememberAsyncImagePainter import coil3.request.ImageRequest @@ -42,52 +44,107 @@ fun CoverArt( elevation: Dp = 0.dp, ) { val context = LocalPlatformContext.current + var failed by remember(ref, thumbnailRef) { mutableStateOf(false) } var thumbnailLoaded by remember(ref, thumbnailRef) { mutableStateOf(false) } val model = remember(ref, thumbnailRef) { ref?.let { imageRequest(context, it, crossfade = thumbnailRef != null) } } - val painter = rememberAsyncImagePainter(model) - val failed = painter.state is AsyncImagePainter.State.Error - val intrinsic = painter.intrinsicSize - val ratio = - if (!square && intrinsic.width > 0f && intrinsic.height > 0f && intrinsic.width.isFinite() && intrinsic.height.isFinite()) { - intrinsic.width / intrinsic.height - } else { - 1f + val frameModifier = + Modifier + .shadow(elevation, RoundedCornerShape(2.dp), clip = elevation > 0.dp) + .background(MaterialTheme.colorScheme.surfaceVariant) + + if (square) { + Box(modifier.then(frameModifier)) { + CoverArtContent( + ref = ref, + name = name, + thumbnailRef = thumbnailRef, + showPlaceholder = showPlaceholder, + failed = failed, + thumbnailLoaded = thumbnailLoaded, + onThumbnailLoaded = { thumbnailLoaded = true }, + contentScale = ContentScale.Crop, + ) { + if (ref != null) { + AsyncImage( + model = model, + contentDescription = null, + contentScale = ContentScale.Crop, + onError = { failed = true }, + modifier = Modifier.fillMaxSize(), + ) + } + } } - Box( - modifier = - modifier - .aspectRatio(ratio) - .shadow(elevation, RoundedCornerShape(2.dp), clip = elevation > 0.dp) - .background(MaterialTheme.colorScheme.surfaceVariant), - ) { - if (showPlaceholder || (failed && !thumbnailLoaded)) { - Text( - text = name.trim().take(1).uppercase(), - style = MaterialTheme.typography.headlineMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.align(Alignment.Center), - ) - } - if (thumbnailRef != null && thumbnailRef != ref) { - AsyncImage( - model = remember(thumbnailRef) { imageRequest(context, thumbnailRef, crossfade = false) }, - contentDescription = null, - contentScale = if (square) ContentScale.Crop else ContentScale.Fit, - onSuccess = { thumbnailLoaded = true }, - modifier = Modifier.fillMaxSize(), - ) - } - if (ref != null) { - Image( - painter = painter, - contentDescription = null, - contentScale = if (square) ContentScale.Crop else ContentScale.Fit, - modifier = Modifier.fillMaxSize(), + } else { + val painter = + rememberAsyncImagePainter( + model = model, + onError = { failed = true }, + contentScale = ContentScale.Fit, ) + BoxWithConstraints(modifier, contentAlignment = Alignment.Center) { + val ratio = painter.intrinsicSize.ratioOrOne() + val width = if (ratio > maxWidth.value / maxHeight.value) maxWidth else maxHeight * ratio + Box(Modifier.size(width, width / ratio).then(frameModifier)) { + CoverArtContent( + ref = ref, + name = name, + thumbnailRef = thumbnailRef, + showPlaceholder = showPlaceholder, + failed = failed, + thumbnailLoaded = thumbnailLoaded, + onThumbnailLoaded = { thumbnailLoaded = true }, + contentScale = ContentScale.Fit, + ) { + if (ref != null) { + Image( + painter = painter, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize(), + ) + } + } + } } } } +@Composable +private fun BoxScope.CoverArtContent( + ref: CoverArtRef?, + name: String, + thumbnailRef: CoverArtRef?, + showPlaceholder: Boolean, + failed: Boolean, + thumbnailLoaded: Boolean, + onThumbnailLoaded: () -> Unit, + contentScale: ContentScale, + main: @Composable () -> Unit, +) { + val context = LocalPlatformContext.current + if (showPlaceholder || (failed && !thumbnailLoaded)) { + Text( + text = name.trim().take(1).uppercase(), + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.align(Alignment.Center), + ) + } + if (thumbnailRef != null && thumbnailRef != ref) { + AsyncImage( + model = remember(thumbnailRef) { imageRequest(context, thumbnailRef, crossfade = false) }, + contentDescription = null, + contentScale = contentScale, + onSuccess = { onThumbnailLoaded() }, + modifier = Modifier.fillMaxSize(), + ) + } + main() +} + +private fun Size.ratioOrOne(): Float = if (width > 0f && height > 0f && width.isFinite() && height.isFinite()) width / height else 1f + private fun imageRequest( context: Context, ref: CoverArtRef, diff --git a/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt b/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt index 9527b357..aa1186f8 100644 --- a/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt +++ b/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Download @@ -47,9 +46,7 @@ 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 import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer diff --git a/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt b/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt index d125c800..839e144d 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt @@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -17,7 +15,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.QueueMusic @@ -46,7 +43,6 @@ 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 import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity @@ -203,7 +199,7 @@ fun NowPlayingScreen( thumbnailRef = thumbnailRef, square = false, elevation = 3.dp, - modifier = Modifier.fillMaxHeight(), + modifier = Modifier.fillMaxSize(), ) } val density = LocalDensity.current @@ -256,6 +252,7 @@ fun NowPlayingScreen( dragging = false }, valueRange = 0f..state.durationMs.toFloat().coerceAtLeast(1f), + enabled = state.durationMs > 0, modifier = Modifier.padding(top = 28.dp), ) Row(