@@ -6,16 +6,20 @@ import android.widget.Toast
66import androidx.activity.compose.BackHandler
77import androidx.annotation.OptIn
88import androidx.annotation.RequiresApi
9+ import androidx.compose.animation.AnimatedContent
910import androidx.compose.animation.AnimatedVisibility
1011import androidx.compose.animation.core.Spring
1112import androidx.compose.animation.core.animateDpAsState
1213import androidx.compose.animation.core.animateFloat
1314import androidx.compose.animation.core.spring
15+ import androidx.compose.animation.core.tween
1416import androidx.compose.animation.core.updateTransition
1517import androidx.compose.animation.fadeIn
1618import androidx.compose.animation.fadeOut
19+ import androidx.compose.animation.scaleIn
1720import androidx.compose.animation.slideInVertically
1821import androidx.compose.animation.slideOutVertically
22+ import androidx.compose.animation.togetherWith
1923import androidx.compose.foundation.background
2024import androidx.compose.foundation.clickable
2125import androidx.compose.foundation.isSystemInDarkTheme
@@ -28,15 +32,13 @@ import androidx.compose.foundation.layout.WindowInsetsSides
2832import androidx.compose.foundation.layout.fillMaxSize
2933import androidx.compose.foundation.layout.fillMaxWidth
3034import androidx.compose.foundation.layout.height
31- import androidx.compose.foundation.layout.navigationBars
3235import androidx.compose.foundation.layout.offset
3336import androidx.compose.foundation.layout.only
3437import androidx.compose.foundation.layout.padding
3538import androidx.compose.foundation.layout.size
3639import androidx.compose.foundation.layout.statusBars
3740import androidx.compose.foundation.layout.systemBars
3841import androidx.compose.foundation.layout.width
39- import androidx.compose.foundation.layout.windowInsetsBottomHeight
4042import androidx.compose.foundation.layout.windowInsetsPadding
4143import androidx.compose.foundation.shape.RoundedCornerShape
4244import androidx.compose.material3.MaterialTheme
@@ -70,25 +72,19 @@ import coil3.size.Precision
7072import coil3.size.Size
7173import com.ljyh.mei.constants.DebugKey
7274import com.ljyh.mei.constants.PlayerHorizontalPadding
73- import com.ljyh.mei.constants.ProgressBarStyle
74- import com.ljyh.mei.constants.ProgressBarStyleKey
7575import com.ljyh.mei.constants.ThumbnailCornerRadius
7676import com.ljyh.mei.constants.UseQQMusicLyricKey
7777import com.ljyh.mei.data.network.Resource
78- import com.ljyh.mei.playback.PlayerConnection
79- import com.ljyh.mei.ui.component.player.component.sheet.AlbumArtistBottomSheet
8078import com.ljyh.mei.ui.component.player.component.AppleMusicFluidBackground
81- import com.ljyh.mei.ui.component.player.component.AppleStyleProgressSlider
82- import com.ljyh.mei.ui.component.player.component.Controls
8379import com.ljyh.mei.ui.component.player.component.LyricScreen
84- import com.ljyh.mei.ui.component.player.component.PlayerActionToolbar
85- import com.ljyh.mei.ui.component.player.component.PlayerProgressSlider
86- import com.ljyh.mei.ui.component.player.component.sheet.PlaylistBottomSheet
87- import com.ljyh.mei.ui.component.player.component.sheet.QQMusicSelectSheet
88- import com.ljyh.mei.ui.component.player.component.sheet.SleepTimerSheet
80+ import com.ljyh.mei.ui.component.player.component.PlayerControlsSection
8981import com.ljyh.mei.ui.component.player.component.Title
82+ import com.ljyh.mei.ui.component.player.component.sheet.AlbumArtistBottomSheet
9083import com.ljyh.mei.ui.component.player.component.sheet.MoreActionsSheet
9184import com.ljyh.mei.ui.component.player.component.sheet.PlayerActionSettingsSheet
85+ import com.ljyh.mei.ui.component.player.component.sheet.PlaylistBottomSheet
86+ import com.ljyh.mei.ui.component.player.component.sheet.QQMusicSelectSheet
87+ import com.ljyh.mei.ui.component.player.component.sheet.SleepTimerSheet
9288import com.ljyh.mei.ui.component.playlist.AddToPlaylistSheet
9389import com.ljyh.mei.ui.component.playlist.CreatePlaylistSheet
9490import com.ljyh.mei.ui.component.sheet.BottomSheet
@@ -105,7 +101,6 @@ import com.ljyh.mei.utils.UnitUtils.toPx
105101import com.ljyh.mei.utils.encrypt.QRCUtils
106102import com.ljyh.mei.utils.lyric.createDefaultLyricData
107103import com.ljyh.mei.utils.lyric.mergeLyrics
108- import com.ljyh.mei.utils.rememberEnumPreference
109104import com.ljyh.mei.utils.rememberPreference
110105import kotlinx.coroutines.Dispatchers
111106import kotlinx.coroutines.delay
@@ -540,6 +535,7 @@ fun BottomSheetPlayer(
540535 currentOverlay = OverlayState .AddToPlaylist (it.id)
541536 }
542537 },
538+ onMoreClick = { currentOverlay = OverlayState .MoreAction },
543539 isCompact = isCompactHeight || isLandscape
544540 )
545541 }
@@ -548,44 +544,59 @@ fun BottomSheetPlayer(
548544 }
549545 }
550546
551- // --- Shared Element (Cover) ---
552- if (mediaMetadata != null ) {
553- // 修改 BottomSheetPlayer 里的 AsyncImage 部分
554- AsyncImage (
555- model = ImageRequest .Builder (LocalContext .current)
556- .data(mediaMetadata?.coverUrl)
557- .size(Size .ORIGINAL )
558- .precision(Precision .EXACT )
559- .crossfade(true )
560- .build(),
561- contentDescription = " Cover" ,
562- contentScale = ContentScale .Crop ,
563- onError = {
564- },
565-
566- modifier = Modifier
567- .graphicsLayer {
568- translationX = finalStart
569- translationY = finalTop
570- shadowElevation = mShadowElevation.toPx()
571- shape = RoundedCornerShape (finalRadius)
572- clip = true
573- }
574- .size(
575- width = with (density) { finalSize.toDp() },
576- height = with (density) { finalSize.toDp() }
547+ AnimatedContent (
548+ targetState = mediaMetadata, // 监听歌曲元数据变化
549+ transitionSpec = {
550+ // 定义切换动画:
551+ // 进入的图片:淡入 + 从 0.9 倍大小放大到 1.0 (这也是 Apple Music 的专辑切换效果之一)
552+ (fadeIn(animationSpec = tween(durationMillis = 400 )) +
553+ scaleIn(initialScale = 0.92f , animationSpec = tween(durationMillis = 400 )))
554+ .togetherWith(
555+ // 离开的图片:淡出
556+ fadeOut(animationSpec = tween(durationMillis = 400 ))
577557 )
578- .clickable {
579- if (! state.isExpanded) {
580- state.expandSoft()
581- } else {
582- showLyrics = ! showLyrics
583- }
558+ },
559+ label = " CoverTransition" ,
560+ modifier = Modifier
561+ .graphicsLayer {
562+ translationX = finalStart
563+ translationY = finalTop
564+ shadowElevation = mShadowElevation.toPx()
565+ shape = RoundedCornerShape (finalRadius)
566+ clip = true
567+ }
568+ .size(
569+ width = with (density) { finalSize.toDp() },
570+ height = with (density) { finalSize.toDp() }
571+ )
572+ .clickable {
573+ if (! state.isExpanded) {
574+ state.expandSoft()
575+ } else {
576+ showLyrics = ! showLyrics
584577 }
585- .background(MaterialTheme .colorScheme.surfaceVariant)
586- )
578+ }
579+ .background(MaterialTheme .colorScheme.surfaceVariant) // 占位背景色
580+ ) { currentMetadata ->
581+ // 这里是动画的内容部分
582+ if (currentMetadata != null ) {
583+ AsyncImage (
584+ model = ImageRequest .Builder (LocalContext .current)
585+ .data(currentMetadata.coverUrl)
586+ .size(Size .ORIGINAL )
587+ .precision(Precision .EXACT )
588+ .crossfade(true )
589+ .build(),
590+ contentDescription = " Cover" ,
591+ contentScale = ContentScale .Crop ,
592+ modifier = Modifier .fillMaxSize()
593+ )
594+ } else {
595+ Box (Modifier .fillMaxSize().background(MaterialTheme .colorScheme.surfaceVariant))
596+ }
587597 }
588598
599+
589600 when (val overlay = currentOverlay) {
590601 OverlayState .None -> {}
591602 OverlayState .Playlist -> {
@@ -665,8 +676,8 @@ fun BottomSheetPlayer(
665676 )
666677 }
667678
668- OverlayState .BottomAction -> {
669- PlayerActionSettingsSheet (onDismiss = {currentOverlay = OverlayState .None })
679+ OverlayState .BottomAction -> {
680+ PlayerActionSettingsSheet (onDismiss = { currentOverlay = OverlayState .None })
670681 }
671682
672683 OverlayState .MoreAction -> {
@@ -675,31 +686,37 @@ fun BottomSheetPlayer(
675686 currentOverlay = OverlayState .None
676687 },
677688 onActionClick = { action ->
678- when (action){
689+ when (action) {
679690 MoreAction .ADD_TO_PLAYLIST -> {
680691 mediaMetadata?.let {
681692 currentOverlay = OverlayState .AddToPlaylist (it.id)
682693 }
683694 }
695+
684696 MoreAction .SHARE -> {
685697 currentOverlay = OverlayState .None
686698 Toast .makeText(context, " 暂未实现" , Toast .LENGTH_SHORT ).show()
687699 }
700+
688701 MoreAction .DOWNLOAD -> {
689702 currentOverlay = OverlayState .None
690703 Toast .makeText(context, " 暂未实现" , Toast .LENGTH_SHORT ).show()
691704 }
705+
692706 MoreAction .DELETE -> {
693707 mediaMetadata?.let {
694708 playerViewModel.deleteSongById(it.id.toString())
695709 }
696710 }
711+
697712 MoreAction .VIEW_PLAYLIST -> {
698713 currentOverlay = OverlayState .Playlist
699714 }
715+
700716 MoreAction .SLEEP_TIMER -> {
701717 currentOverlay = OverlayState .SleepTimer
702718 }
719+
703720 MoreAction .BOTTOM_ACTION -> {
704721 currentOverlay = OverlayState .BottomAction
705722 }
@@ -713,92 +730,4 @@ fun BottomSheetPlayer(
713730 else -> {}
714731 }
715732 }
716- }
717-
718- // 稍微修改 Controls Section,让它支持紧凑模式
719- @OptIn(UnstableApi ::class )
720- @Composable
721- fun PlayerControlsSection (
722- sliderPosition : Float ,
723- duration : Long ,
724- isPlaying : Boolean ,
725- playbackState : Int ,
726- playerConnection : PlayerConnection ,
727- onLyricClick : () -> Unit ,
728- onPlaylistClick : () -> Unit ,
729- onSleepTimerClick : () -> Unit ,
730- onAddToPlaylistClick : () -> Unit ,
731- isCompact : Boolean = false // 新增参数
732- ) {
733- // 紧凑模式下间距减半
734- val spacerHeight = if (isCompact) 8 .dp else 24 .dp
735-
736- val (progressBarStyle, _) = rememberEnumPreference(
737- key = ProgressBarStyleKey ,
738- defaultValue = ProgressBarStyle .WAVE
739- )
740-
741- Column (
742- modifier = Modifier .fillMaxWidth()
743- ) {
744- if (progressBarStyle == ProgressBarStyle .LINEAR ) {
745- AppleStyleProgressSlider (
746- position = sliderPosition.toLong(),
747- duration = duration,
748- onPositionChange = { newPosition ->
749- playerConnection.player.seekTo(newPosition)
750- },
751- modifier = Modifier
752- .fillMaxWidth()
753- .padding(horizontal = PlayerHorizontalPadding + 8 .dp)
754- )
755- } else {
756- PlayerProgressSlider (
757- position = sliderPosition.toLong(),
758- duration = duration,
759- isPlaying = isPlaying, // 波浪进度条需要这个参数
760- onPositionChange = { newPosition ->
761- playerConnection.player.seekTo(newPosition)
762- },
763- modifier = Modifier
764- .fillMaxWidth()
765- .padding(horizontal = PlayerHorizontalPadding + 8 .dp)
766- )
767- }
768-
769-
770- Spacer (Modifier .height(spacerHeight))
771-
772- Box (
773- modifier = Modifier
774- .fillMaxWidth()
775- .padding(horizontal = PlayerHorizontalPadding )
776- ) {
777- Controls (
778- playerConnection = playerConnection,
779- canSkipPrevious = true ,
780- canSkipNext = true ,
781- isPlaying = isPlaying,
782- playbackState = playbackState,
783- modifier = Modifier .align(Alignment .Center )
784- )
785- }
786-
787- // 紧凑模式下可能需要隐藏底部 Toolbar 或者减小间距
788- if (! isCompact) {
789- Spacer (Modifier .height(spacerHeight))
790- PlayerActionToolbar (
791- modifier = Modifier .padding(horizontal = PlayerHorizontalPadding ),
792- onLyricClick = onLyricClick,
793- onPlaylistClick = onPlaylistClick,
794- onSleepTimerClick = onSleepTimerClick,
795- onAddToPlaylistClick = onAddToPlaylistClick
796- )
797- Spacer (Modifier .windowInsetsBottomHeight(WindowInsets .navigationBars))
798- Spacer (Modifier .height(16 .dp))
799- } else {
800- // 紧凑模式底部留白少一点
801- Spacer (Modifier .height(16 .dp))
802- }
803- }
804733}
0 commit comments