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 c19aa659..acf7ce33 100644 --- a/app/src/main/kotlin/com/subtracks/ui/components/Components.kt +++ b/app/src/main/kotlin/com/subtracks/ui/components/Components.kt @@ -1,6 +1,7 @@ package com.subtracks.ui.components import android.content.Context +import androidx.collection.LruCache import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -36,6 +37,21 @@ import coil3.request.ImageRequest import coil3.request.crossfade import com.subtracks.data.model.CoverArtRef +private const val MAX_CACHED_RATIOS = 256 + +private object ArtworkRatioCache { + private val ratios = LruCache(MAX_CACHED_RATIOS) + + fun get(cacheKey: String): Float? = ratios.get(cacheKey) + + fun put( + cacheKey: String, + ratio: Float, + ) { + if (ratio > 0f && ratio.isFinite()) ratios.put(cacheKey, ratio) + } +} + @Composable fun CoverArt( ref: CoverArtRef?, @@ -49,7 +65,7 @@ fun CoverArt( val context = LocalPlatformContext.current var failed by remember(ref, thumbnailRef) { mutableStateOf(false) } var thumbnailLoaded by remember(ref, thumbnailRef) { mutableStateOf(false) } - var thumbnailRatio by remember(ref, thumbnailRef) { mutableStateOf(null) } + var thumbnailRatio by remember(ref, thumbnailRef) { mutableStateOf(thumbnailRef?.cacheKey?.let(ArtworkRatioCache::get)) } val model = remember(ref, thumbnailRef) { ref?.let { imageRequest(context, it, crossfade = thumbnailRef != null) } } val frameModifier = Modifier @@ -74,6 +90,11 @@ fun CoverArt( model = model, contentDescription = null, contentScale = ContentScale.Crop, + onSuccess = { success -> + success.painter.intrinsicSize + .ratioOrNull() + ?.let { ArtworkRatioCache.put(ref.cacheKey, it) } + }, onError = { failed = true }, modifier = Modifier.fillMaxSize(), ) @@ -150,7 +171,10 @@ private fun BoxScope.CoverArtContent( onThumbnailLoaded() success.painter.intrinsicSize .ratioOrNull() - ?.let(onThumbnailRatio) + ?.let { + onThumbnailRatio(it) + ArtworkRatioCache.put(thumbnailRef.cacheKey, it) + } }, modifier = Modifier.fillMaxSize(), ) diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt index 0a3ea0c8..ce3d1d0f 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -25,8 +25,8 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Sort import androidx.compose.material.icons.automirrored.rounded.PlaylistPlay +import androidx.compose.material.icons.automirrored.rounded.Segment import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.Album import androidx.compose.material.icons.rounded.ArrowDownward @@ -480,7 +480,7 @@ fun LibraryScreen( ) { Box { Icon( - Icons.AutoMirrored.Filled.Sort, + Icons.AutoMirrored.Rounded.Segment, contentDescription = "List options", modifier = Modifier.size(28.dp), ) 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 96f24ad5..857e8602 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt @@ -51,9 +51,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.SingletonImageLoader import coil3.compose.LocalPlatformContext @@ -175,11 +177,21 @@ fun NowPlayingScreen( topBar = { TopAppBar( title = { - Text( - text = title, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) + Column { + Text( + text = "Now playing: ${state.context?.kind?.label() ?: "library"}".uppercase(), + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.SemiBold, + letterSpacing = 1.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + ) + Text( + text = title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } }, navigationIcon = { IconButton(onClick = onBack) { @@ -385,6 +397,14 @@ fun NowPlayingScreen( } } +private fun QueueKind.label(): String = + when (this) { + QueueKind.Album -> "album" + QueueKind.Playlist -> "playlist" + QueueKind.Song -> "song" + QueueKind.Songs -> "library" + } + private fun formatTime(milliseconds: Long): String { val totalSeconds = (milliseconds / 1000).coerceAtLeast(0) val minutes = totalSeconds / 60