Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,17 @@ fun AlbumScreen(
mutableStateOf<String?>(null)
}

LaunchedEffect(bitmap) {
val hasAmMotion = uiState.amArtworkData?.hasMotion == true && uiState.amArtworkData?.bestMotionUrl != null
val currentArtworkUrl = uiState.amArtworkData?.staticArtworkUrl ?: uiState.thumbnail
var resolvedArtworkUrl by remember(currentArtworkUrl) {
mutableStateOf(currentArtworkUrl)
}

LaunchedEffect(bitmap, resolvedArtworkUrl) {
val bm = bitmap
if (bm != null && paletteGeneratedFor != uiState.thumbnail) {
if (bm != null && paletteGeneratedFor != resolvedArtworkUrl) {
paletteState.generate(bm)
paletteGeneratedFor = uiState.thumbnail
paletteGeneratedFor = resolvedArtworkUrl
}
}

Expand Down Expand Up @@ -255,50 +261,72 @@ fun AlbumScreen(
horizontalAlignment = Alignment.Start,
) {
if (isPortrait) {
// Apple Music-style: edge-to-edge artwork (taller than square,
// ~half screen height) with title overlay + liquid glass buttons.
// Glass buttons MUST be siblings of the backdrop source (not children)
// to avoid render feedback loop / RuntimeShader crash.
// Apple Music-style: edge-to-edge artwork with title overlay + liquid glass buttons.
// When animated artwork is present, header is sized 10% less than full motion height
// so that the bottom edge of the video is covered and fades into the dominant page background.
val amArtwork = uiState.amArtworkData
val hasAmMotion = amArtwork?.hasMotion == true && amArtwork.bestMotionUrl != null
val motionArtworkHeight = screenInfo.wDP * (4f / 3f)
val headerHeight = if (hasAmMotion) (motionArtworkHeight * 0.90f).dp else (screenInfo.hDP / 2).dp
val artworkHeight = if (hasAmMotion) motionArtworkHeight.dp else (screenInfo.hDP / 2).dp

val artworkBackdrop = rememberBackdrop(Color.Black)
Box(
modifier =
Modifier
.fillMaxWidth()
.height((screenInfo.hDP / 2).dp),
.height(headerHeight),
) {
// Inner Box — backdrop SOURCE (artwork + overlays only, NO glass)
Box(modifier = Modifier.fillMaxSize().layerBackdrop(artworkBackdrop)) {
AsyncImage(
model =
ImageRequest
.Builder(LocalPlatformContext.current)
.data(uiState.thumbnail)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCacheKey(uiState.thumbnail)
.memoryCacheKey(uiState.thumbnail)
.crossfade(false)
.build(),
placeholder = rememberHolderPainter(),
error = rememberHolderPainter(),
contentDescription = null,
contentScale = ContentScale.Crop,
onSuccess = {
bitmap = it.result.image.toImageBitmap()
},
modifier = Modifier.fillMaxSize(),
)
// Subtle bottom gradient — keeps artwork visible behind
// the title text and blends artwork edge seamlessly into
// the muted palette page background (Apple Music style).
// Spans 70% of the artwork (not a fixed 200dp): the shorter
// the ramp, the steeper the alpha, and a steep ramp is what
// makes the fade read as an edge.
Box(
modifier =
Modifier
.fillMaxWidth()
.height((screenInfo.hDP * 0.35f).dp)
.height(artworkHeight)
.align(Alignment.TopCenter),
) {
AsyncImage(
model =
ImageRequest
.Builder(LocalPlatformContext.current)
.data(resolvedArtworkUrl)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCacheKey(resolvedArtworkUrl)
.memoryCacheKey(resolvedArtworkUrl)
.crossfade(false)
.build(),
placeholder = rememberHolderPainter(),
error = rememberHolderPainter(),
contentDescription = null,
contentScale = ContentScale.Crop,
onError = {
val fallback = resolvedArtworkUrl?.replace("maxresdefault", "hqdefault")
if (fallback != null && fallback != resolvedArtworkUrl) {
resolvedArtworkUrl = fallback
}
},
onSuccess = {
bitmap = it.result.image.toImageBitmap()
},
modifier = Modifier.fillMaxSize(),
)

if (hasAmMotion) {
com.maxrave.simpmusic.expect.ui.MediaPlayerView(
url = amArtwork!!.bestMotionUrl!!,
cropToBounds = true,
modifier = Modifier.fillMaxSize()
)
}
}
// Bottom gradient — melts artwork edge seamlessly into the dominant palette background
Box(
modifier =
Modifier
.fillMaxWidth()
.height(if (hasAmMotion) (motionArtworkHeight * 0.40f).dp else (screenInfo.hDP * 0.35f).dp)
.align(Alignment.BottomCenter)
.background(artworkScrimBrush(mutedPaletteBg)),
)
Expand Down Expand Up @@ -424,29 +452,48 @@ fun AlbumScreen(
horizontalArrangement = Arrangement.spacedBy(24.dp),
verticalAlignment = Alignment.Top,
) {
AsyncImage(
model =
ImageRequest
.Builder(LocalPlatformContext.current)
.data(uiState.thumbnail)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCacheKey(uiState.thumbnail)
.memoryCacheKey(uiState.thumbnail)
.crossfade(false)
.build(),
placeholder = rememberHolderPainter(),
error = rememberHolderPainter(),
contentDescription = null,
contentScale = ContentScale.Crop,
onSuccess = {
bitmap = it.result.image.toImageBitmap()
},
modifier =
Modifier
.size(280.dp)
.clip(RoundedCornerShape(8.dp)),
)
Box(
modifier = Modifier
.size(280.dp)
.clip(RoundedCornerShape(8.dp))
) {
AsyncImage(
model =
ImageRequest
.Builder(LocalPlatformContext.current)
.data(resolvedArtworkUrl)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCacheKey(resolvedArtworkUrl)
.memoryCacheKey(resolvedArtworkUrl)
.crossfade(false)
.build(),
placeholder = rememberHolderPainter(),
error = rememberHolderPainter(),
contentDescription = null,
contentScale = ContentScale.Crop,
onError = {
val fallback = resolvedArtworkUrl?.replace("maxresdefault", "hqdefault")
if (fallback != null && fallback != resolvedArtworkUrl) {
resolvedArtworkUrl = fallback
}
},
onSuccess = {
bitmap = it.result.image.toImageBitmap()
},
modifier = Modifier.fillMaxSize(),
)

val amArtwork = uiState.amArtworkData
val hasAmMotion = amArtwork?.hasMotion == true && amArtwork.bestMotionUrl != null
if (hasAmMotion) {
com.maxrave.simpmusic.expect.ui.MediaPlayerView(
url = amArtwork!!.bestMotionUrl!!,
cropToBounds = true,
modifier = Modifier.fillMaxSize()
)
}
}
Column(
modifier = Modifier.weight(1f),
) {
Expand Down
Loading
Loading