package com.example.ui.components import android.net.Uri import androidx.annotation.OptIn import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource 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.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.MusicNote import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.QueueMusic import androidx.compose.material.icons.filled.Repeat import androidx.compose.material.icons.filled.RepeatOne import androidx.compose.material.icons.filled.Shuffle import androidx.compose.material.icons.filled.SkipNext import androidx.compose.material.icons.filled.SkipPrevious import androidx.compose.material.icons.filled.Speed import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue 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.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.media3.common.util.UnstableApi import coil.compose.AsyncImage import com.example.data.model.MediaItemData import com.example.data.model.RepeatMode import com.example.playback.PlaybackManager import com.example.ui.theme.AccentViolet import com.example.ui.theme.AccentVioletDim import com.example.ui.theme.AmoledBlack import com.example.ui.theme.SurfaceElevated import com.example.ui.theme.TextPrimary import com.example.ui.theme.TextSecondary import com.example.ui.theme.TextTertiary @OptIn(UnstableApi::class) @Composable fun FullAudioPlayerSheet( mediaItem: MediaItemData, isFavorite: Boolean, onToggleFavorite: () -> Unit, onDismiss: () -> Unit, onQueueClick: () -> Unit, modifier: Modifier = Modifier ) { val isPlaying by PlaybackManager.isPlaying.collectAsState() val positionMs by PlaybackManager.currentPositionMs.collectAsState() val durationMs by PlaybackManager.durationMs.collectAsState() val repeatMode by PlaybackManager.repeatMode.collectAsState() val isShuffle by PlaybackManager.isShuffleEnabled.collectAsState() val speed by PlaybackManager.playbackSpeed.collectAsState() val controlsVisible by PlaybackManager.isAudioControlsVisible.collectAsState() var isDraggingSlider by remember { mutableStateOf(false) } var dragPosition by remember { mutableStateOf(0f) } Box( modifier = modifier .fillMaxSize() .background(AmoledBlack) .testTag("full_audio_player") ) { // Subtle, lightweight background ambient gradient Box( modifier = Modifier .fillMaxSize() .background( Brush.radialGradient( colors = listOf( AccentVioletDim.copy(alpha = 0.35f), AmoledBlack ), radius = 1200f ) ) ) Column( modifier = Modifier .fillMaxSize() .statusBarsPadding() .navigationBarsPadding() .padding(horizontal = 24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { // Top Bar: Dismiss arrow, SwipePlay title, and Queue shortcut Row( modifier = Modifier .fillMaxWidth() .padding(top = 8.dp, bottom = 16.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { IconButton( onClick = onDismiss, modifier = Modifier.testTag("audio_player_dismiss") ) { Icon( imageVector = Icons.Default.KeyboardArrowDown, contentDescription = "Collapse", tint = TextPrimary, modifier = Modifier.size(32.dp) ) } Text( text = "NOW PLAYING", color = TextSecondary, fontSize = 12.sp, fontWeight = FontWeight.Bold, letterSpacing = 2.sp ) IconButton( onClick = onQueueClick, modifier = Modifier.testTag("audio_player_queue_btn") ) { Icon( imageVector = Icons.Default.QueueMusic, contentDescription = "Playback Queue", tint = TextPrimary ) } } Spacer(modifier = Modifier.weight(0.5f)) // Large Album Artwork or Fallback with tap to show/hide controls Box( modifier = Modifier .fillMaxWidth() .aspectRatio(1f) .clip(RoundedCornerShape(20.dp)) .background(SurfaceElevated) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { PlaybackManager.toggleAudioControlsVisibility() } .testTag("audio_artwork_container"), contentAlignment = Alignment.Center ) { if (!mediaItem.albumArtUri.isNullOrBlank()) { AsyncImage( model = Uri.parse(mediaItem.albumArtUri), contentDescription = "Album Artwork", contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize() ) } else { Column(horizontalAlignment = Alignment.CenterHorizontally) { Icon( imageVector = Icons.Default.MusicNote, contentDescription = "Music", tint = AccentViolet, modifier = Modifier.size(96.dp) ) Spacer(modifier = Modifier.height(12.dp)) Text( text = "Tap to reveal controls", color = TextTertiary, fontSize = 12.sp ) } } } Spacer(modifier = Modifier.height(28.dp)) // Song Title and Artist with Favorite Button Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Column(modifier = Modifier.weight(1f)) { Text( text = mediaItem.title, color = TextPrimary, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis ) Spacer(modifier = Modifier.height(4.dp)) Text( text = "${mediaItem.artist} • ${mediaItem.album}", color = TextSecondary, style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis ) } IconButton( onClick = onToggleFavorite, modifier = Modifier.testTag("audio_player_fav_btn") ) { Icon( imageVector = if (isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder, contentDescription = "Favorite", tint = if (isFavorite) AccentViolet else TextSecondary ) } } Spacer(modifier = Modifier.weight(0.5f)) // Always visible complete playback controls Column(modifier = Modifier.fillMaxWidth()) { // Seek bar & Timestamps val activePos = if (isDraggingSlider) dragPosition.toLong() else positionMs val totalDur = durationMs.coerceAtLeast(1L) Slider( value = activePos.coerceIn(0L, totalDur).toFloat(), onValueChange = { isDraggingSlider = true dragPosition = it }, onValueChangeFinished = { PlaybackManager.seekTo(dragPosition.toLong()) isDraggingSlider = false }, valueRange = 0f..totalDur.toFloat(), colors = SliderDefaults.colors( thumbColor = AccentViolet, activeTrackColor = AccentViolet, inactiveTrackColor = Color.White.copy(alpha = 0.2f) ), modifier = Modifier .fillMaxWidth() .testTag("audio_seek_slider") ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = formatTime(activePos), color = TextSecondary, fontSize = 12.sp ) Text( text = formatTime(totalDur), color = TextSecondary, fontSize = 12.sp ) } Spacer(modifier = Modifier.height(16.dp)) // Control Actions: Shuffle, Prev, Play/Pause, Next, Repeat Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically ) { IconButton( onClick = { PlaybackManager.toggleShuffle() }, modifier = Modifier.testTag("audio_shuffle_btn") ) { Icon( imageVector = Icons.Default.Shuffle, contentDescription = "Shuffle", tint = if (isShuffle) AccentViolet else TextSecondary ) } IconButton( onClick = { PlaybackManager.playPrevious() }, modifier = Modifier.testTag("audio_prev_btn") ) { Icon( imageVector = Icons.Default.SkipPrevious, contentDescription = "Previous", tint = TextPrimary, modifier = Modifier.size(32.dp) ) } IconButton( onClick = { PlaybackManager.togglePlayPause() }, modifier = Modifier .size(72.dp) .clip(CircleShape) .background(AccentViolet) .testTag("audio_play_pause_btn") ) { Icon( imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, contentDescription = if (isPlaying) "Pause" else "Play", tint = AmoledBlack, modifier = Modifier.size(42.dp) ) } IconButton( onClick = { PlaybackManager.playNext() }, modifier = Modifier.testTag("audio_next_btn") ) { Icon( imageVector = Icons.Default.SkipNext, contentDescription = "Next", tint = TextPrimary, modifier = Modifier.size(32.dp) ) } IconButton( onClick = { val nextMode = when (repeatMode) { RepeatMode.OFF -> RepeatMode.ALL RepeatMode.ALL -> RepeatMode.ONE RepeatMode.ONE -> RepeatMode.OFF } PlaybackManager.setRepeatMode(nextMode) }, modifier = Modifier.testTag("audio_repeat_btn") ) { Icon( imageVector = if (repeatMode == RepeatMode.ONE) Icons.Default.RepeatOne else Icons.Default.Repeat, contentDescription = "Repeat", tint = if (repeatMode != RepeatMode.OFF) AccentViolet else TextSecondary ) } } Spacer(modifier = Modifier.height(14.dp)) // Speed Pill centered Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { Surface( onClick = { val nextSpeed = when (speed) { 1.0f -> 1.25f 1.25f -> 1.5f 1.5f -> 2.0f 2.0f -> 0.75f else -> 1.0f } PlaybackManager.setPlaybackSpeed(nextSpeed) }, shape = RoundedCornerShape(8.dp), color = SurfaceElevated, modifier = Modifier.testTag("audio_speed_btn") ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp) ) { Icon( imageVector = Icons.Default.Speed, contentDescription = "Speed", tint = TextSecondary, modifier = Modifier.size(14.dp) ) Spacer(modifier = Modifier.width(6.dp)) Text( text = "Speed: ${speed}x", color = TextPrimary, fontSize = 12.sp, fontWeight = FontWeight.Bold ) } } } } Spacer(modifier = Modifier.height(24.dp)) } } }