optimize transfer ui

This commit is contained in:
2026-10-03 23:20:16 +08:00
parent 7b856f574e
commit fa61bcca38
3 changed files with 86 additions and 50 deletions
@@ -22,6 +22,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import com.zhushenwudi.lovelivemusicplayer.domain.Music import com.zhushenwudi.lovelivemusicplayer.domain.Music
@@ -88,12 +89,13 @@ fun DriveModeCarousel(
} }
} }
/** 驾驶模式与歌曲快传共用的封面 Pager,保证两处运动效果完全一致。 */ /** 共用封面 Pager;传输页可关闭驾驶模式的纵向偏移,让封面中心水平对齐。 */
@Composable @Composable
internal fun CoverCarouselPager( internal fun CoverCarouselPager(
state: PagerState, state: PagerState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
userScrollEnabled: Boolean = true, userScrollEnabled: Boolean = true,
verticalOffset: Dp = (-40).dp,
pageContent: @Composable (Int) -> Unit, pageContent: @Composable (Int) -> Unit,
) { ) {
BoxWithConstraints(modifier.fillMaxSize()) { BoxWithConstraints(modifier.fillMaxSize()) {
@@ -124,7 +126,7 @@ internal fun CoverCarouselPager(
scaleX = scale scaleX = scale
scaleY = scale scaleY = scale
this.alpha = alpha this.alpha = alpha
translationY = scale * -40.dp.toPx() translationY = scale * verticalOffset.toPx()
}, },
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
@@ -32,7 +32,8 @@ import com.zhushenwudi.lovelivemusicplayer.ui.theme.LocalIsLandscape
internal fun PairingPageLayout( internal fun PairingPageLayout(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
illustration: @Composable (Dp) -> Unit, illustration: @Composable (Dp) -> Unit,
content: @Composable ColumnScope.() -> Unit, landscapeHeader: (@Composable () -> Unit)? = null,
content: @Composable ColumnScope.(isSplitLayout: Boolean) -> Unit,
) { ) {
val landscape = LocalIsLandscape.current val landscape = LocalIsLandscape.current
BoxWithConstraints( BoxWithConstraints(
@@ -41,6 +42,9 @@ internal fun PairingPageLayout(
), ),
) { ) {
if (landscape && maxWidth >= 560.dp) { if (landscape && maxWidth >= 560.dp) {
Column(Modifier.fillMaxSize()) {
landscapeHeader?.invoke()
BoxWithConstraints(Modifier.weight(1f).fillMaxWidth()) {
val viewportHeight = maxHeight val viewportHeight = maxHeight
val illustrationSize = minOf(maxHeight * 0.8f, maxWidth * 0.4f, 280.dp) val illustrationSize = minOf(maxHeight * 0.8f, maxWidth * 0.4f, 280.dp)
Row(Modifier.fillMaxSize().padding(horizontal = 24.dp)) { Row(Modifier.fillMaxSize().padding(horizontal = 24.dp)) {
@@ -56,8 +60,11 @@ internal fun PairingPageLayout(
.heightIn(min = viewportHeight).padding(24.dp), .heightIn(min = viewportHeight).padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
content = content, ) {
) content(true)
}
}
}
} }
} else { } else {
val illustrationSize = minOf(maxWidth - 48.dp, 343.dp).coerceAtLeast(1.dp) val illustrationSize = minOf(maxWidth - 48.dp, 343.dp).coerceAtLeast(1.dp)
@@ -70,7 +77,7 @@ internal fun PairingPageLayout(
illustration(illustrationSize) illustration(illustrationSize)
} }
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(32.dp))
content() content(false)
Spacer(Modifier.height(24.dp)) Spacer(Modifier.height(24.dp))
} }
} }
@@ -80,6 +80,8 @@ import org.jetbrains.compose.resources.painterResource
import com.zhushenwudi.lovelivemusicplayer.AppContainer import com.zhushenwudi.lovelivemusicplayer.AppContainer
import com.zhushenwudi.lovelivemusicplayer.Const import com.zhushenwudi.lovelivemusicplayer.Const
import com.zhushenwudi.lovelivemusicplayer.generated.resources.Res import com.zhushenwudi.lovelivemusicplayer.generated.resources.Res
import com.zhushenwudi.lovelivemusicplayer.generated.resources.llmp_logo_dark
import com.zhushenwudi.lovelivemusicplayer.generated.resources.llmp_logo_light
import com.zhushenwudi.lovelivemusicplayer.generated.resources.logo_logo import com.zhushenwudi.lovelivemusicplayer.generated.resources.logo_logo
import com.zhushenwudi.lovelivemusicplayer.lyric.LoadedLyric import com.zhushenwudi.lovelivemusicplayer.lyric.LoadedLyric
import com.zhushenwudi.lovelivemusicplayer.lyric.LrcParser import com.zhushenwudi.lovelivemusicplayer.lyric.LrcParser
@@ -504,34 +506,23 @@ fun TransferScreen(onBack: () -> Unit, onOpenScan: () -> Unit) {
}, },
) )
} else { } else {
SvgIcon(asset = SvgAsset("drawable/sync_icon_data_sync.svg"), size = minOf(size, 300.dp)) TransferDefaultLogo(
Modifier.size(minOf(size, 300.dp)).clip(RoundedCornerShape(24.dp)),
)
} }
}, },
) { landscapeHeader = {
if (connected || state.completed) { if (connected || state.completed) {
Column( TransferSongInfo(displayedSong, displayedArtist)
Modifier.height(60.dp), Spacer(Modifier.height(6.dp))
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
if (displayedSong.isNotBlank()) {
Text(
displayedSong,
color = if (dark) ColorMs.White else ColorMs.Text333,
fontSize = hSp(15f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
displayedArtist,
color = ColorMs.Gray999,
fontSize = hSp(12f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
} }
},
) { isSplitLayout ->
if (connected || state.completed) {
if (!isSplitLayout) {
TransferSongInfo(displayedSong, displayedArtist)
Spacer(Modifier.height(24.dp)) Spacer(Modifier.height(24.dp))
}
TransferProgressRing( TransferProgressRing(
progress = state.progress, progress = state.progress,
total = state.total, total = state.total,
@@ -566,6 +557,44 @@ fun TransferScreen(onBack: () -> Unit, onOpenScan: () -> Unit) {
} }
} }
@Composable
private fun TransferSongInfo(song: String, artist: String) {
val dark = LocalDarkTheme.current
Column(
Modifier.fillMaxWidth().height(60.dp).padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
if (song.isNotBlank()) {
Text(
song,
color = if (dark) ColorMs.White else ColorMs.Text333,
fontSize = hSp(15f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
artist,
color = ColorMs.Gray999,
fontSize = hSp(12f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Composable
private fun TransferDefaultLogo(modifier: Modifier = Modifier) {
val dark = LocalDarkTheme.current
Image(
painter = painterResource(if (dark) Res.drawable.llmp_logo_dark else Res.drawable.llmp_logo_light),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = modifier,
)
}
@Composable @Composable
private fun KeepScreenOn() { private fun KeepScreenOn() {
DisposableEffect(Unit) { DisposableEffect(Unit) {
@@ -636,6 +665,7 @@ private fun TransferCoverBlock(
CoverCarouselPager( CoverCarouselPager(
state = pagerState, state = pagerState,
userScrollEnabled = false, userScrollEnabled = false,
verticalOffset = 0.dp,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(size), .height(size),
@@ -677,13 +707,10 @@ private fun TransferCoverCircle(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
// 默认图始终垫在底层,即使图片请求重建或超时也不会露出白色空帧。 // 默认图始终垫在底层,即使图片请求重建或超时也不会露出白色空帧。
Image( TransferDefaultLogo(
painter = painterResource(Res.drawable.logo_logo), Modifier
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(36.dp), .padding(size * 0.15f),
) )
if (cover.url.isBlank() || loadFailed) { if (cover.url.isBlank() || loadFailed) {
LaunchedEffect(cover.key, loadFailed) { latestOnReady() } LaunchedEffect(cover.key, loadFailed) { latestOnReady() }