فهرست منبع

Add landscape mode to PlayerView

vfsfitvnm 3 سال پیش
والد
کامیت
c55bccc7aa
1فایلهای تغییر یافته به همراه512 افزوده شده و 417 حذف شده
  1. 512 417
      app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/PlayerView.kt

+ 512 - 417
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/PlayerView.kt

@@ -1,6 +1,7 @@
 package it.vfsfitvnm.vimusic.ui.views
 
 import android.content.Intent
+import android.content.res.Configuration
 import android.media.audiofx.AudioEffect
 import android.text.format.DateUtils
 import android.text.format.Formatter
@@ -12,12 +13,10 @@ import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.BasicText
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.*
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Alignment
@@ -30,6 +29,7 @@ import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.style.TextOverflow
@@ -50,11 +50,8 @@ import it.vfsfitvnm.vimusic.ui.components.themed.BaseMediaItemMenu
 import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError
 import it.vfsfitvnm.vimusic.ui.styling.*
 import it.vfsfitvnm.vimusic.utils.*
-import it.vfsfitvnm.youtubemusic.YouTube
-import it.vfsfitvnm.youtubemusic.models.PlayerResponse
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.launch
 import kotlin.math.roundToInt
 
 
@@ -65,11 +62,12 @@ fun PlayerView(
     modifier: Modifier = Modifier,
 ) {
     val menuState = LocalMenuState.current
-    val preferences = LocalPreferences.current
+
     val colorPalette = LocalColorPalette.current
     val typography = LocalTypography.current
     val binder = LocalPlayerServiceBinder.current
     val context = LocalContext.current
+    val configuration = LocalConfiguration.current
 
     val player = binder?.player
     val playerState = rememberPlayerState(player)
@@ -77,11 +75,6 @@ fun PlayerView(
     player ?: return
     playerState?.mediaItem ?: return
 
-    val coroutineScope = rememberCoroutineScope()
-
-    val (thumbnailSizeDp, thumbnailSizePx) = Dimensions.thumbnails.player.song.let {
-        it to (it - 64.dp).px
-    }
 
     BottomSheet(
         state = layoutState,
@@ -177,477 +170,579 @@ fun PlayerView(
             playerState.mediaItem.mediaId.let(Database::song).distinctUntilChanged()
         }.collectAsState(initial = null, context = Dispatchers.IO)
 
-        var isShowingStatsForNerds by rememberSaveable {
-            mutableStateOf(false)
+        when (configuration.orientation) {
+            Configuration.ORIENTATION_LANDSCAPE -> {
+                Row(
+                    verticalAlignment = Alignment.CenterVertically,
+                    modifier = Modifier
+                        .padding(bottom = 64.dp)
+                        .background(colorPalette.background)
+                        .padding(top = 16.dp)
+                ) {
+                    Box(
+                        contentAlignment = Alignment.Center,
+                        modifier = Modifier
+                            .weight(0.66f)
+                            .padding(horizontal = 16.dp)
+                            .padding(bottom = 16.dp)
+                    ) {
+                        Thumbnail(
+                            playerState = playerState,
+                            song = song,
+                            modifier = Modifier
+                        )
+                    }
+
+                    Controls(
+                        playerState = playerState,
+                        song = song,
+                        modifier = Modifier
+                            .padding(vertical = 8.dp)
+                            .fillMaxHeight()
+                            .weight(1f)
+                    )
+                }
+            }
+            else -> {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier
+                        .padding(bottom = 64.dp)
+                        .background(colorPalette.background)
+                        .padding(top = 16.dp)
+                ) {
+                    Box(
+                        contentAlignment = Alignment.Center,
+                        modifier = Modifier
+                            .weight(1.25f)
+                            .padding(horizontal = 32.dp, vertical = 8.dp)
+                    ) {
+                        Thumbnail(
+                            playerState = playerState,
+                            song = song,
+                            modifier = Modifier
+                        )
+                    }
+
+                    Controls(
+                        playerState = playerState,
+                        song = song,
+                        modifier = Modifier
+                            .padding(vertical = 8.dp)
+                            .fillMaxWidth()
+                            .weight(1f)
+                    )
+                }
+            }
+        }
+
+        TopAppBar {
+            Spacer(
+                modifier = Modifier
+                    .padding(horizontal = 16.dp, vertical = 8.dp)
+                    .size(24.dp)
+            )
+
+            Image(
+                painter = painterResource(R.drawable.ellipsis_horizontal),
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(colorPalette.text),
+                modifier = Modifier
+                    .clickable {
+                        menuState.display {
+                            val resultRegistryOwner = LocalActivityResultRegistryOwner.current
+
+                            BaseMediaItemMenu(
+                                mediaItem = playerState.mediaItem,
+                                onGoToEqualizer = {
+                                    val intent =
+                                        Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL).apply {
+                                            putExtra(
+                                                AudioEffect.EXTRA_AUDIO_SESSION,
+                                                player.audioSessionId
+                                            )
+                                            putExtra(
+                                                AudioEffect.EXTRA_PACKAGE_NAME,
+                                                context.packageName
+                                            )
+                                            putExtra(
+                                                AudioEffect.EXTRA_CONTENT_TYPE,
+                                                AudioEffect.CONTENT_TYPE_MUSIC
+                                            )
+                                        }
+
+                                    if (intent.resolveActivity(context.packageManager) != null) {
+                                        val contract =
+                                            ActivityResultContracts.StartActivityForResult()
+
+                                        resultRegistryOwner?.activityResultRegistry
+                                            ?.register("", contract) {}
+                                            ?.launch(intent)
+                                    } else {
+                                        Toast
+                                            .makeText(
+                                                context,
+                                                "No equalizer app found!",
+                                                Toast.LENGTH_SHORT
+                                            )
+                                            .show()
+                                    }
+                                },
+                                onDismiss = menuState::hide,
+                                onGlobalRouteEmitted = layoutState.collapse,
+                            )
+                        }
+                    }
+                    .padding(horizontal = 16.dp, vertical = 8.dp)
+                    .size(24.dp)
+            )
         }
 
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally,
+
+        PlayerBottomSheet(
+            playerState = playerState,
+            layoutState = rememberBottomSheetState(64.dp, layoutState.upperBound * 0.9f),
+            onGlobalRouteEmitted = layoutState.collapse,
+            padding  = layoutState.upperBound * 0.1f,
+            song = song,
             modifier = Modifier
-                .background(colorPalette.background)
-                .padding(bottom = 72.dp)
-                .fillMaxSize()
+                .align(Alignment.BottomCenter)
+        )
+    }
+}
+
+@ExperimentalAnimationApi
+@Composable
+private fun Thumbnail(
+    playerState: PlayerState,
+    song: Song?,
+    modifier: Modifier = Modifier
+) {
+    val typography = LocalTypography.current
+    val context = LocalContext.current
+    val binder = LocalPlayerServiceBinder.current
+    val player = binder?.player ?: return
+
+    playerState.mediaItem ?: return
+
+    val (thumbnailSizeDp, thumbnailSizePx) = Dimensions.thumbnails.player.song.let {
+        it to (it - 64.dp).px
+    }
+
+    var isShowingStatsForNerds by rememberSaveable {
+        mutableStateOf(false)
+    }
+
+    if (playerState.error == null) {
+        AnimatedContent(
+            targetState = playerState.mediaItemIndex,
+            transitionSpec = {
+                val slideDirection =
+                    if (targetState > initialState) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right
+
+                (slideIntoContainer(slideDirection) + fadeIn() with
+                        slideOutOfContainer(slideDirection) + fadeOut()).using(
+                    SizeTransform(clip = false)
+                )
+            },
+            modifier = modifier
+                .aspectRatio(1f)
         ) {
-            var scrubbingPosition by remember(playerState.mediaItemIndex) {
-                mutableStateOf<Long?>(null)
+            val artworkUri = remember(it) {
+                player.getMediaItemAt(it).mediaMetadata.artworkUri.thumbnail(
+                    thumbnailSizePx
+                )
             }
 
-            TopAppBar {
-                Spacer(
+            Box(
+                modifier = Modifier
+                    .clip(ThumbnailRoundness.shape)
+                    .size(thumbnailSizeDp)
+            ) {
+                AsyncImage(
+                    model = artworkUri,
+                    contentDescription = null,
+                    contentScale = ContentScale.Crop,
                     modifier = Modifier
-                        .padding(horizontal = 16.dp, vertical = 8.dp)
-                        .size(24.dp)
+                        .pointerInput(Unit) {
+                            detectTapGestures(
+                                onLongPress = {
+                                    isShowingStatsForNerds = true
+                                }
+                            )
+                        }
+                        .fillMaxSize()
                 )
 
-                Image(
-                    painter = painterResource(R.drawable.ellipsis_horizontal),
-                    contentDescription = null,
-                    colorFilter = ColorFilter.tint(colorPalette.text),
-                    modifier = Modifier
-                        .clickable {
-                            menuState.display {
-                                val resultRegistryOwner = LocalActivityResultRegistryOwner.current
-
-                                BaseMediaItemMenu(
-                                    mediaItem = playerState.mediaItem,
-                                    onGoToEqualizer = {
-                                        val intent = Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL).apply {
-                                            putExtra(AudioEffect.EXTRA_AUDIO_SESSION, player.audioSessionId)
-                                            putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName)
-                                            putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
-                                        }
+                AnimatedVisibility(
+                    visible = isShowingStatsForNerds,
+                    enter = fadeIn(),
+                    exit = fadeOut(),
+                ) {
+                    var cachedBytes by remember(song?.id) {
+                        mutableStateOf(binder.cache.getCachedBytes(playerState.mediaItem.mediaId, 0, -1))
+                    }
 
-                                        if (intent.resolveActivity(context.packageManager) != null) {
-                                            val contract = ActivityResultContracts.StartActivityForResult()
+                    val loudnessDb by remember {
+                        derivedStateOf {
+                            song?.loudnessDb ?: playerState.mediaMetadata.extras?.getFloatOrNull("loudnessDb")
+                        }
+                    }
 
-                                            resultRegistryOwner?.activityResultRegistry?.register("", contract) {}?.launch(intent)
-                                        } else {
-                                            Toast.makeText(context, "No equalizer app found!", Toast.LENGTH_SHORT).show()
-                                        }
-                                    },
-                                    onDismiss = menuState::hide,
-                                    onGlobalRouteEmitted = layoutState.collapse,
-                                )
+                    val contentLength by remember {
+                        derivedStateOf {
+                            song?.contentLength ?: playerState.mediaMetadata.extras?.getLongOrNull("contentLength")
+                        }
+                    }
+
+                    DisposableEffect(song?.id) {
+                        val key = playerState.mediaItem.mediaId
+
+                        val listener = object : Cache.Listener {
+                            override fun onSpanAdded(cache: Cache, span: CacheSpan) {
+                                cachedBytes += span.length
                             }
+
+                            override fun onSpanRemoved(cache: Cache, span: CacheSpan) {
+                                cachedBytes -= span.length
+                            }
+
+                            override fun onSpanTouched(
+                                cache: Cache,
+                                oldSpan: CacheSpan,
+                                newSpan: CacheSpan
+                            ) = Unit
                         }
-                        .padding(horizontal = 16.dp, vertical = 8.dp)
-                        .size(24.dp)
-                )
-            }
 
-            if (playerState.error == null) {
-                AnimatedContent(
-                    targetState = playerState.mediaItemIndex,
-                    transitionSpec = {
-                        val slideDirection =
-                            if (targetState > initialState) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right
+                        binder.cache.addListener(key, listener)
 
-                        (slideIntoContainer(slideDirection) + fadeIn() with
-                                slideOutOfContainer(slideDirection) + fadeOut()).using(
-                            SizeTransform(clip = false)
-                        )
-                    },
-                    modifier = Modifier
-                        .weight(1f)
-                        .align(Alignment.CenterHorizontally)
-                ) {
-                    val artworkUri = remember(it) {
-                        player.getMediaItemAt(it).mediaMetadata.artworkUri.thumbnail(
-                            thumbnailSizePx
-                        )
+                        onDispose {
+                            binder.cache.removeListener(key, listener)
+                        }
                     }
 
-                    Box(
+                    Column(
+                        verticalArrangement = Arrangement.SpaceBetween,
                         modifier = Modifier
-                            .padding(bottom = 32.dp)
-                            .padding(horizontal = 32.dp)
-                            .aspectRatio(1f)
-                            .clip(ThumbnailRoundness.shape)
-                            .size(thumbnailSizeDp)
+                            .pointerInput(Unit) {
+                                detectTapGestures(
+                                    onPress = {
+                                        isShowingStatsForNerds = false
+                                    }
+                                )
+                            }
+                            .background(Color.Black.copy(alpha = 0.8f))
+                            .fillMaxSize()
                     ) {
-                        AsyncImage(
-                            model = artworkUri,
-                            contentDescription = null,
-                            contentScale = ContentScale.Crop,
+                        Row(
+                            horizontalArrangement = Arrangement.spacedBy(16.dp),
                             modifier = Modifier
-                                .pointerInput(Unit) {
-                                    detectTapGestures(
-                                        onLongPress = {
-                                            isShowingStatsForNerds = true
-                                        }
-                                    )
-                                }
-                                .fillMaxSize()
-                        )
-
-                        androidx.compose.animation.AnimatedVisibility(
-                            visible = isShowingStatsForNerds,
-                            enter = fadeIn(),
-                            exit = fadeOut(),
+                                .padding(all = 16.dp)
                         ) {
-                            var cachedBytes by remember(song?.id) {
-                                mutableStateOf(binder.cache.getCachedBytes(playerState.mediaItem.mediaId, 0, -1))
+                            Column {
+                                BasicText(
+                                    text = "Id",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = "Volume",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = "Loudness",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = "Size",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = "Cached",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
                             }
 
-                            val loudnessDb by remember {
-                                derivedStateOf {
-                                    song?.loudnessDb ?: playerState.mediaMetadata.extras?.getFloatOrNull("loudnessDb")
-                                }
-                            }
+                            Column {
+                                BasicText(
+                                    text = playerState.mediaItem.mediaId,
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = "${playerState.volume.times(100).roundToInt()}%",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = loudnessDb?.let { loudnessDb ->
+                                        "%.2f dB".format(loudnessDb)
+                                    } ?: "Unknown",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = contentLength?.let { contentLength ->
+                                        Formatter.formatShortFileSize(
+                                            context,
+                                            contentLength
+                                        )
+                                    } ?: "Unknown",
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
+                                BasicText(
+                                    text = buildString {
+                                        append(Formatter.formatShortFileSize(context, cachedBytes))
 
-                            val contentLength by remember {
-                                derivedStateOf {
-                                    song?.contentLength ?: playerState.mediaMetadata.extras?.getLongOrNull("contentLength")
-                                }
+                                        contentLength?.let { contentLength ->
+                                            append(" (${(cachedBytes.toFloat() / contentLength * 100).roundToInt()}%)")
+                                        }
+                                    },
+                                    style = typography.xs.semiBold.color(BlackColorPalette.text)
+                                )
                             }
+                        }
+                    }
+                }
+            }
+        }
+    } else {
+        Box(
+            contentAlignment = Alignment.Center,
+            modifier = modifier
+                .padding(bottom = 32.dp)
+                .padding(horizontal = 32.dp)
+                .size(thumbnailSizeDp)
+        ) {
+            LoadingOrError(
+                errorMessage = playerState.error.javaClass.canonicalName,
+                onRetry = {
+                    player.playWhenReady = true
+                    player.prepare()
+                }
+            ) {}
+        }
+    }
+}
 
-                            DisposableEffect(song?.id) {
-                                val key = playerState.mediaItem.mediaId
-
-                                val listener = object : Cache.Listener {
-                                    override fun onSpanAdded(cache: Cache, span: CacheSpan) {
-                                        cachedBytes += span.length
-                                    }
+@Composable
+private fun Controls(
+    playerState: PlayerState,
+    song: Song?,
+    modifier: Modifier = Modifier
+) {
+    val typography = LocalTypography.current
+    val colorPalette = LocalColorPalette.current
+    val preferences = LocalPreferences.current
 
-                                    override fun onSpanRemoved(cache: Cache, span: CacheSpan) {
-                                        cachedBytes -= span.length
-                                    }
+    val binder = LocalPlayerServiceBinder.current
+    val player = binder?.player ?: return
 
-                                    override fun onSpanTouched(
-                                        cache: Cache,
-                                        oldSpan: CacheSpan,
-                                        newSpan: CacheSpan
-                                    ) = Unit
-                                }
+    var scrubbingPosition by remember(playerState.mediaItemIndex) {
+        mutableStateOf<Long?>(null)
+    }
 
-                                binder.cache.addListener(key, listener)
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(horizontal = 32.dp)
+    ) {
+        Spacer(
+            modifier = Modifier
+                .weight(1f)
+        )
 
-                                onDispose {
-                                    binder.cache.removeListener(key, listener)
-                                }
-                            }
+        BasicText(
+            text = playerState.mediaMetadata.title?.toString() ?: "",
+            style = typography.l.bold,
+            maxLines = 1,
+            overflow = TextOverflow.Ellipsis
+        )
 
-                            Column(
-                                verticalArrangement = Arrangement.SpaceBetween,
-                                modifier = Modifier
-                                    .pointerInput(Unit) {
-                                        detectTapGestures(
-                                            onPress = {
-                                                isShowingStatsForNerds = false
-                                            }
-                                        )
-                                    }
-                                    .background(Color.Black.copy(alpha = 0.8f))
-                                    .fillMaxSize()
-                            ) {
-                                Row(
-                                    horizontalArrangement = Arrangement.spacedBy(16.dp),
-                                    modifier = Modifier
-                                        .padding(all = 16.dp)
-                                ) {
-                                    Column {
-                                        BasicText(
-                                            text = "Id",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = "Volume",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = "Loudness",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = "Size",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = "Cached",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                    }
+        BasicText(
+            text = playerState.mediaMetadata.artist?.toString() ?: "",
+            style = typography.s.semiBold.secondary,
+            maxLines = 1,
+            overflow = TextOverflow.Ellipsis
+        )
 
-                                    Column {
-                                        BasicText(
-                                            text = playerState.mediaItem.mediaId,
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = "${playerState.volume.times(100).roundToInt()}%",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = loudnessDb?.let { loudnessDb ->
-                                                "%.2f dB".format(loudnessDb)
-                                            } ?: "Unknown",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = contentLength?.let { contentLength ->
-                                                Formatter.formatShortFileSize(
-                                                    context,
-                                                    contentLength
-                                                )
-                                            } ?: "Unknown",
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                        BasicText(
-                                            text = buildString {
-                                                append(Formatter.formatShortFileSize(context, cachedBytes))
-
-                                                contentLength?.let { contentLength ->
-                                                    append(" (${(cachedBytes.toFloat() / contentLength * 100).roundToInt()}%)")
-                                                }
-                                            },
-                                            style = typography.xs.semiBold.color(BlackColorPalette.text)
-                                        )
-                                    }
-                                }
+        Spacer(
+            modifier = Modifier
+                .weight(0.5f)
+        )
 
-                                if (song != null && (contentLength == null || loudnessDb == null)) {
-                                    BasicText(
-                                        text = "FILL MISSING DATA",
-                                        style = typography.xxs.semiBold.color(BlackColorPalette.text),
-                                        modifier = Modifier
-                                            .clickable(
-                                                indication = rememberRipple(bounded = true),
-                                                interactionSource = remember { MutableInteractionSource() },
-                                                onClick = {
-                                                    song?.let { song ->
-                                                        coroutineScope.launch(Dispatchers.IO) {
-                                                            YouTube
-                                                                .player(song.id)
-                                                                ?.map { body ->
-                                                                    Database.update(
-                                                                        song.copy(
-                                                                            loudnessDb = body.playerConfig?.audioConfig?.loudnessDb?.toFloat(),
-                                                                            contentLength = body.streamingData?.adaptiveFormats
-                                                                                ?.findLast { format ->
-                                                                                    format.itag == 251
-                                                                                }
-                                                                                ?.let(PlayerResponse.StreamingData.AdaptiveFormat::contentLength)
-                                                                        )
-                                                                    )
-                                                                }
-                                                        }
-                                                    }
-                                                }
-                                            )
-                                            .padding(all = 16.dp)
-                                            .align(Alignment.End)
-                                    )
-                                }
-                            }
-                        }
-                    }
+        SeekBar(
+            value = scrubbingPosition ?: playerState.currentPosition,
+            minimumValue = 0,
+            maximumValue = playerState.duration,
+            onDragStart = {
+                scrubbingPosition = it
+            },
+            onDrag = { delta ->
+                scrubbingPosition = if (playerState.duration != C.TIME_UNSET) {
+                    scrubbingPosition?.plus(delta)?.coerceIn(0, playerState.duration)
+                } else {
+                    null
                 }
-            } else {
-                Box(
-                    contentAlignment = Alignment.Center,
-                    modifier = Modifier
-                        .weight(1f)
-                        .align(Alignment.CenterHorizontally)
-                        .padding(bottom = 32.dp)
-                        .padding(horizontal = 32.dp)
-                        .size(thumbnailSizeDp)
-                ) {
-                    LoadingOrError(
-                        errorMessage = playerState.error.javaClass.canonicalName,
-                        onRetry = {
-                            player.playWhenReady = true
-                            player.prepare()
-                        }
-                    ) {}
-                }
-            }
+            },
+            onDragEnd = {
+                scrubbingPosition?.let(player::seekTo)
+                scrubbingPosition = null
+            },
+            color = colorPalette.text,
+            backgroundColor = colorPalette.textDisabled,
+            shape = RoundedCornerShape(8.dp)
+        )
 
-            BasicText(
-                text = playerState.mediaMetadata.title?.toString() ?: "",
-                style = typography.l.bold,
-                maxLines = 1,
-                overflow = TextOverflow.Ellipsis,
-                modifier = Modifier
-                    .padding(horizontal = 32.dp)
-            )
+        Spacer(
+            modifier = Modifier
+                .height(8.dp)
+        )
 
+        Row(
+            horizontalArrangement = Arrangement.SpaceBetween,
+            verticalAlignment = Alignment.CenterVertically,
+            modifier = Modifier
+                .fillMaxWidth()
+        ) {
             BasicText(
-                text = playerState.mediaMetadata.artist?.toString() ?: "",
-                style = typography.s.semiBold.secondary,
+                text = DateUtils.formatElapsedTime(
+                    (scrubbingPosition ?: playerState.currentPosition) / 1000
+                ),
+                style = typography.xxs.semiBold,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis,
-                modifier = Modifier
-                    .padding(horizontal = 32.dp)
-            )
-
-            SeekBar(
-                value = scrubbingPosition ?: playerState.currentPosition,
-                minimumValue = 0,
-                maximumValue = playerState.duration,
-                onDragStart = {
-                    scrubbingPosition = it
-                },
-                onDrag = { delta ->
-                    scrubbingPosition = if (playerState.duration != C.TIME_UNSET) {
-                        scrubbingPosition?.plus(delta)?.coerceIn(0, playerState.duration)
-                    } else {
-                        null
-                    }
-                },
-                onDragEnd = {
-                    scrubbingPosition?.let(player::seekTo)
-                    scrubbingPosition = null
-                },
-                color = colorPalette.text,
-                backgroundColor = colorPalette.textDisabled,
-                shape = RoundedCornerShape(8.dp),
-                modifier = Modifier
-                    .padding(top = 24.dp, bottom = 12.dp)
-                    .padding(horizontal = 32.dp)
-                    .fillMaxWidth()
             )
 
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                verticalAlignment = Alignment.CenterVertically,
-                modifier = Modifier
-                    .padding(horizontal = 32.dp)
-                    .fillMaxWidth()
-                    .padding(bottom = 16.dp)
-            ) {
+            if (playerState.duration != C.TIME_UNSET) {
                 BasicText(
-                    text = DateUtils.formatElapsedTime(
-                        (scrubbingPosition ?: playerState.currentPosition) / 1000
-                    ),
+                    text = DateUtils.formatElapsedTime(playerState.duration / 1000),
                     style = typography.xxs.semiBold,
                     maxLines = 1,
                     overflow = TextOverflow.Ellipsis,
                 )
-
-                if (playerState.duration != C.TIME_UNSET) {
-                    BasicText(
-                        text = DateUtils.formatElapsedTime(playerState.duration / 1000),
-                        style = typography.xxs.semiBold,
-                        maxLines = 1,
-                        overflow = TextOverflow.Ellipsis,
-                    )
-                }
             }
+        }
 
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                verticalAlignment = Alignment.CenterVertically,
+
+        Spacer(
+            modifier = Modifier
+                .weight(1f)
+        )
+
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            modifier = Modifier
+                .fillMaxWidth()
+        ) {
+            Image(
+                painter = painterResource(R.drawable.heart),
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(
+                    song?.likedAt?.let { colorPalette.red } ?: colorPalette.textDisabled
+                ),
                 modifier = Modifier
-                    .padding(vertical = 32.dp)
-            ) {
-                Image(
-                    painter = painterResource(R.drawable.heart),
-                    contentDescription = null,
-                    colorFilter = ColorFilter.tint(
-                        song?.likedAt?.let { colorPalette.red } ?: colorPalette.textDisabled
-                    ),
-                    modifier = Modifier
-                        .clickable {
-                            query {
-                                song?.let { song ->
-                                    Database.update(song.toggleLike())
-                                } ?: Database.insert(playerState.mediaItem, Song::toggleLike)
-                            }
+                    .clickable {
+                        query {
+                            song?.let { song ->
+                                Database.update(song.toggleLike())
+                            } ?: Database.insert(playerState.mediaItem!!, Song::toggleLike)
                         }
-                        .padding(horizontal = 16.dp)
-                        .size(28.dp)
-                )
+                    }
+                    .weight(1f)
+                    .size(28.dp)
+            )
 
-                Image(
-                    painter = painterResource(R.drawable.play_skip_back),
-                    contentDescription = null,
-                    colorFilter = ColorFilter.tint(colorPalette.text),
-                    modifier = Modifier
-                        .clickable(onClick = player::seekToPrevious)
-                        .padding(horizontal = 16.dp)
-                        .size(28.dp)
-                )
+            Image(
+                painter = painterResource(R.drawable.play_skip_back),
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(colorPalette.text),
+                modifier = Modifier
+                    .clickable(onClick = player::seekToPrevious)
+                    .weight(1f)
+                    .size(28.dp)
+            )
 
-                val isPaused = playerState.playbackState == Player.STATE_ENDED || !playerState.playWhenReady
+            Spacer(
+                modifier = Modifier
+                    .width(8.dp)
+            )
 
-                Box(
-                    modifier = Modifier
-                        .padding(horizontal = 8.dp)
-                        .clickable {
-                            if (isPaused) {
-                                if (player.playbackState == Player.STATE_IDLE) {
-                                    player.prepare()
-                                }
+            val isPaused =
+                playerState.playbackState == Player.STATE_ENDED || !playerState.playWhenReady
 
-                                player.play()
-                            } else {
-                                player.pause()
+            Box(
+                modifier = Modifier
+                    .clickable {
+                        if (isPaused) {
+                            if (player.playbackState == Player.STATE_IDLE) {
+                                player.prepare()
                             }
-                        }
-                        .background(color = colorPalette.text, shape = CircleShape)
-                        .size(64.dp)
-                ) {
-                    Image(
-                        painter = painterResource(if (isPaused) R.drawable.play else R.drawable.pause),
-                        contentDescription = null,
-                        colorFilter = ColorFilter.tint(colorPalette.background),
-                        modifier = Modifier
-                            .align(Alignment.Center)
-                            .size(28.dp)
-                    )
-                }
 
-                Image(
-                    painter = painterResource(R.drawable.play_skip_forward),
-                    contentDescription = null,
-                    colorFilter = ColorFilter.tint(colorPalette.text),
-                    modifier = Modifier
-                        .clickable(onClick = player::seekToNext)
-                        .padding(horizontal = 16.dp)
-                        .size(28.dp)
-                )
-
-                Image(
-                    painter = painterResource(
-                        if (playerState.repeatMode == Player.REPEAT_MODE_ONE) {
-                            R.drawable.repeat_one
+                            player.play()
                         } else {
-                            R.drawable.repeat
+                            player.pause()
                         }
-                    ),
+                    }
+                    .background(color = colorPalette.text, shape = CircleShape)
+                    .size(64.dp)
+            ) {
+                Image(
+                    painter = painterResource(if (isPaused) R.drawable.play else R.drawable.pause),
                     contentDescription = null,
-                    colorFilter = ColorFilter.tint(
-                        if (playerState.repeatMode == Player.REPEAT_MODE_OFF) {
-                            colorPalette.textDisabled
-                        } else {
-                            colorPalette.text
-                        }
-                    ),
+                    colorFilter = ColorFilter.tint(colorPalette.background),
                     modifier = Modifier
-                        .clickable {
-                            player.repeatMode
-                                .plus(2)
-                                .mod(3)
-                                .let { repeatMode ->
-                                    player.repeatMode = repeatMode
-                                    preferences.repeatMode = repeatMode
-                                }
-                        }
-                        .padding(horizontal = 16.dp)
+                        .align(Alignment.Center)
                         .size(28.dp)
                 )
             }
+
+            Spacer(
+                modifier = Modifier
+                    .width(8.dp)
+            )
+
+            Image(
+                painter = painterResource(R.drawable.play_skip_forward),
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(colorPalette.text),
+                modifier = Modifier
+                    .clickable(onClick = player::seekToNext)
+                    .weight(1f)
+                    .size(28.dp)
+            )
+
+            Image(
+                painter = painterResource(
+                    if (playerState.repeatMode == Player.REPEAT_MODE_ONE) {
+                        R.drawable.repeat_one
+                    } else {
+                        R.drawable.repeat
+                    }
+                ),
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(
+                    if (playerState.repeatMode == Player.REPEAT_MODE_OFF) {
+                        colorPalette.textDisabled
+                    } else {
+                        colorPalette.text
+                    }
+                ),
+                modifier = Modifier
+                    .clickable {
+                        player.repeatMode
+                            .plus(2)
+                            .mod(3)
+                            .let { repeatMode ->
+                                player.repeatMode = repeatMode
+                                preferences.repeatMode = repeatMode
+                            }
+                    }
+                    .weight(1f)
+                    .size(28.dp)
+            )
         }
 
-        PlayerBottomSheet(
-            playerState = playerState,
-            layoutState = rememberBottomSheetState(64.dp, layoutState.upperBound * 0.9f),
-            onGlobalRouteEmitted = layoutState.collapse,
-            padding  = layoutState.upperBound * 0.1f,
-            song = song,
+        Spacer(
             modifier = Modifier
-                .align(Alignment.BottomCenter)
+                .weight(1f)
         )
     }
-}
-
+}