package com.suno.android.omniplayer import androidx.annotation.OptIn import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf 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.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import com.suno.android.common_core_utils.constants.SunoMediaType import com.suno.android.common_core_utils.model.Url import com.suno.android.common_core_utils.model.UserHandle import com.suno.android.common_db.database.entities.CaptionMentionsEntity import com.suno.android.common_ui.components.list_items.comments.MentionState import com.suno.android.common_ui.components.omni.ImageColorAnalysisEffect import com.suno.android.common_ui.components.omni.OmniPlayerButtons import com.suno.android.common_ui.components.omni.OmniPlayerHeader import com.suno.android.common_ui.components.omni.OmniPlayerLyrics import com.suno.android.common_ui.components.omni.OmniPlayerPlaybackControls import com.suno.android.common_ui.components.omni.OmniPlayerTapControlsOverlay import com.suno.android.common_ui.components.omni.RepeatMode import com.suno.android.common_ui.extensions.collectIsCurrentMediaItemSeekableAsState import com.suno.android.common_ui.extensions.offsetForPage import com.suno.android.common_ui.extensions.pxToDp import com.suno.android.common_ui.theme.SunoTheme import kotlin.math.abs @OptIn(UnstableApi::class) @Composable fun OmniPlayerPage( page: Int, parentPagerState: PagerState, mediaType: SunoMediaType, exoPlayer: Player? = null, // needed for video?? artistName: String? = null, artistAvatarImageUrl: Url? = null, videoCoverUrl: Url? = null, albumArtImageUrl: Url? = null, commentCount: Int? = null, upvoteCount: Int? = null, onArtistHandleClicked: (UserHandle?) -> Unit, titleTextString: String? = null, liked: Boolean? = false, isCasting: Boolean = false, showCastButton: Boolean = false, onDismissClicked: () -> Unit = {}, onReplayClicked: () -> Unit = {}, onRemixClicked: (() -> Unit)? = null, isPlaying: Boolean = false, isShuffling: Boolean = false, repeatMode: RepeatMode = RepeatMode.REPEAT_MODE_NONE, timestamp: String = "1:23", percentageComplete: Float, lyricsString: String? = null, caption: String? = null, captionMentions: CaptionMentionsEntity? = null, onScrubbed: (Float) -> Unit = {}, onPlayPauseClicked: () -> Unit = {}, onShuffleClicked: () -> Unit = {}, onRepeatModeClicked: () -> Unit = {}, isShowRepeatGateEnabled: Boolean = false, isShowShuffleGateEnabled: Boolean = false, onLikeClicked: () -> Unit = {}, onShareClicked: () -> Unit = {}, onCommentsClicked: () -> Unit = {}, onOverflowMenuClicked: () -> Unit = {}, onCastClicked: () -> Unit = {}, onCenterTap: () -> Unit = {}, onCenterDoubleTap: () -> Unit = {}, onLeftTap: () -> Unit = {}, onRightTap: () -> Unit = {}, isPreviewClip: Boolean = false, onUpgradeClicked: () -> Unit = {}, clipModelName: String? = null, ) { var dominantColor by remember { mutableStateOf(null) } var buttonsContainerHeight by remember { mutableIntStateOf(0) } Box( modifier = Modifier .fillMaxHeight(fraction = 0.99f) .padding(horizontal = 4.dp) .background(Color.Black) .clip(RoundedCornerShape(36.dp)), ) { Box { when (mediaType) { SunoMediaType.AUDIO -> { albumArtImageUrl?.let { imageUrl -> ImageColorAnalysisEffect(imageUrl = albumArtImageUrl) { dominantColor = it.dominantColor } } AudioPlayerCover( page = page, parentPagerState = parentPagerState, albumArtImageUrl = albumArtImageUrl, videoCoverUrl = videoCoverUrl, dominantColor = dominantColor, ) } SunoMediaType.VIDEO -> { VideoPlayerCover( exoPlayer = exoPlayer, ) } } } Box( modifier = Modifier .fillMaxSize() .graphicsLayer { val offset = parentPagerState.offsetForPage(page) alpha = 1 - abs(offset) }, ) { OmniPlayerTapControlsOverlay( onLeftTap = onLeftTap, onRightTap = onRightTap, onCenterTap = onCenterTap, onCenterDoubleTap = onCenterDoubleTap, ) Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp), ) { OmniPlayerHeader( title = titleTextString, artistName = artistName, artistAvatarImageUrl = artistAvatarImageUrl, caption = caption, captionMentionsStates = captionMentions?.userMentions?.map { MentionState( start = it.start, end = it.end, handle = it.handle, displayName = it.displayName, ) }, clipModelName = clipModelName, onArtistHandleClicked = { onArtistHandleClicked(it) }, onDismissClicked = onDismissClicked, onUpgradeClicked = onUpgradeClicked, isPreviewClip = isPreviewClip, ) Spacer(modifier = Modifier.weight(1f)) Row( modifier = Modifier.wrapContentHeight(), horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.End), verticalAlignment = Alignment.Bottom, ) { OmniPlayerLyrics( modifier = Modifier .weight(1f) .height(buttonsContainerHeight.pxToDp()), lyricsString = lyricsString, ) if (lyricsString == null) { Spacer(modifier = Modifier.weight(1f)) } OmniPlayerButtons( modifier = Modifier.onSizeChanged { buttonsContainerHeight = it.height }, upvoteCount = upvoteCount, commentCount = commentCount, liked = liked, isCasting = isCasting, showCastButton = showCastButton, onLikeClicked = onLikeClicked, onCommentsClicked = onCommentsClicked, onShareClicked = onShareClicked, onCastClicked = onCastClicked, onOverflowMenuClicked = onOverflowMenuClicked, onRemixClicked = onRemixClicked, isPreviewClip = isPreviewClip, ) } val canSeek = exoPlayer?.collectIsCurrentMediaItemSeekableAsState()?.value ?: false OmniPlayerPlaybackControls( timestamp = timestamp, isPlaying = isPlaying, isShuffling = isShuffling, canSeek = canSeek, repeatMode = repeatMode, percentageComplete = percentageComplete, onReplayClicked = onReplayClicked, onPlayPauseClicked = onPlayPauseClicked, onScrubbed = onScrubbed, onShuffleClicked = onShuffleClicked, onRepeatModeClicked = onRepeatModeClicked, isShowRepeatGateEnabled = isShowRepeatGateEnabled, isShowShuffleGateEnabled = isShowShuffleGateEnabled, ) } } } } @Preview(showSystemUi = true) @Composable private fun OmniPlayerArtPage_Preview() { SunoTheme { Column(modifier = Modifier.fillMaxSize()) { OmniPlayerPage( page = 0, parentPagerState = rememberPagerState { 1 }, titleTextString = "Title Text That is a little bit long actually extremely long so much so that", artistName = "ZeroNanachi \uD83D\uDE3C\uD83C\uDF63", artistAvatarImageUrl = null, onArtistHandleClicked = {}, mediaType = SunoMediaType.AUDIO, // Only use AUDIO for preview percentageComplete = 0.5f, lyricsString = "These are some lyrics to display!\n\nParagraph break! " + "This is some text to make up the next paragraph", liked = false, onDismissClicked = {}, onReplayClicked = {}, onScrubbed = {}, onPlayPauseClicked = {}, onLikeClicked = {}, onShareClicked = {}, onCommentsClicked = {}, onOverflowMenuClicked = {}, onCenterTap = {}, onCenterDoubleTap = {}, onLeftTap = {}, onRightTap = {}, isPreviewClip = false, ) } } }