package com.suno.android.common_ui.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade import com.suno.android.common_core_utils.model.Url import com.suno.android.common_ui.R import com.suno.android.common_ui.theme.ExtendedTheme import com.suno.android.common_ui.theme.SunoTheme @Composable private fun CountersLayout( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { Layout( content = content, modifier = modifier, ) { measurables, constraints -> val placeables = measurables.map { it.measure(constraints) } val contentWidth = placeables.sumOf { it.width } val contentHeight = placeables.sumOf { it.height } if (contentWidth <= constraints.maxWidth) { layout( width = contentWidth, height = placeables.maxOf { it.height }, ) { var x = 0 placeables.forEach { it.place(x, 0) x += it.width } } } else { layout( width = placeables.maxOf { it.width }, height = contentHeight, ) { var y = 0 placeables.forEach { it.place(0, y) y += it.height } } } } } @Composable fun Track( modifier: Modifier = Modifier, title: String, trackImageUrl: Url?, previewUrl: Url?, creatorAvatarUrl: Url?, creatorName: String, playCount: Int, upvoteCount: Int, commentCount: Int? = 0, tags: String = "", videoPreviewPlayer: ExoPlayer? = null, onClick: () -> Unit, onArtistClicked: () -> Unit, ) { LaunchedEffect(videoPreviewPlayer, previewUrl) { if (previewUrl != null && videoPreviewPlayer != null) { videoPreviewPlayer.clearMediaItems() videoPreviewPlayer.addMediaItem(MediaItem.fromUri(previewUrl.toUri())) } } Column( modifier = modifier .width(180.dp) .clip(RoundedCornerShape(8.dp)) .background( color = Color.Transparent, ) .clickable { onClick() }, ) { Box( modifier = Modifier .clip(RoundedCornerShape(8.dp)) .width(180.dp) .height(230.dp), ) { AsyncImage( modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, placeholder = ColorPainter(Color.White), model = ImageRequest.Builder(LocalContext.current).data(trackImageUrl).crossfade(true).build(), contentDescription = title, ) SongVideoCoverPlayer(videoPreviewPlayer) CountersLayout( modifier = Modifier.align(Alignment.BottomStart), ) { IconCounter( modifier = Modifier.padding(4.dp), iconRes = R.drawable.play, count = playCount, ) IconCounter( modifier = Modifier.padding(4.dp), iconRes = R.drawable.thumbs, count = upvoteCount, ) if (commentCount != null && commentCount != 0) { IconCounter( modifier = Modifier.padding(4.dp), iconRes = R.drawable.comments, count = commentCount, ) } } } Text( modifier = Modifier.padding(top = 16.dp), text = title, color = ExtendedTheme.colors.textPrimary, style = ExtendedTheme.typography.body, fontSize = 15.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, ) Text( modifier = Modifier.padding(top = 4.dp, bottom = 6.dp), text = tags, color = ExtendedTheme.colors.textTertiary, style = ExtendedTheme.typography.body, fontSize = 15.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, ) ArtistMiniDetail( creatorAvatarUrl = creatorAvatarUrl, creatorName = creatorName, onClick = { onArtistClicked() }, ) } } @Preview(showBackground = true) @Composable private fun Track_Preview() { SunoTheme { Track( trackImageUrl = Url("https://cdn2.suno.ai/image_7f9a5f44-d766-4724-a510-20142dc59ab2.jpeg?width=360"), playCount = 100, upvoteCount = 200, tags = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v", title = "Test Title That is Loooooooooooooooooooooong", creatorName = "ZeroNanachi \uD83D\uDE3C\uD83C\uDF63", previewUrl = null, creatorAvatarUrl = null, onClick = {}, onArtistClicked = {}, ) } }