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.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.distinctUntilChanged
import com.zhushenwudi.lovelivemusicplayer.domain.Music
@@ -88,12 +89,13 @@ fun DriveModeCarousel(
}
}
/** 驾驶模式与歌曲快传共用的封面 Pager,保证两处运动效果完全一致。 */
/** 共用封面 Pager;传输页可关闭驾驶模式的纵向偏移,让封面中心水平对齐。 */
@Composable
internal fun CoverCarouselPager(
state: PagerState,
modifier: Modifier = Modifier,
userScrollEnabled: Boolean = true,
verticalOffset: Dp = (-40).dp,
pageContent: @Composable (Int) -> Unit,
) {
BoxWithConstraints(modifier.fillMaxSize()) {
@@ -124,7 +126,7 @@ internal fun CoverCarouselPager(
scaleX = scale
scaleY = scale
this.alpha = alpha
translationY = scale * -40.dp.toPx()
translationY = scale * verticalOffset.toPx()
},
contentAlignment = Alignment.Center,
) {
@@ -32,7 +32,8 @@ import com.zhushenwudi.lovelivemusicplayer.ui.theme.LocalIsLandscape
internal fun PairingPageLayout(
modifier: Modifier = Modifier,
illustration: @Composable (Dp) -> Unit,
content: @Composable ColumnScope.() -> Unit,
landscapeHeader: (@Composable () -> Unit)? = null,
content: @Composable ColumnScope.(isSplitLayout: Boolean) -> Unit,
) {
val landscape = LocalIsLandscape.current
BoxWithConstraints(
@@ -41,23 +42,29 @@ internal fun PairingPageLayout(
),
) {
if (landscape && maxWidth >= 560.dp) {
val viewportHeight = maxHeight
val illustrationSize = minOf(maxHeight * 0.8f, maxWidth * 0.4f, 280.dp)
Row(Modifier.fillMaxSize().padding(horizontal = 24.dp)) {
Box(
Modifier.weight(1f).fillMaxHeight(),
contentAlignment = Alignment.Center,
) {
illustration(illustrationSize)
Column(Modifier.fillMaxSize()) {
landscapeHeader?.invoke()
BoxWithConstraints(Modifier.weight(1f).fillMaxWidth()) {
val viewportHeight = maxHeight
val illustrationSize = minOf(maxHeight * 0.8f, maxWidth * 0.4f, 280.dp)
Row(Modifier.fillMaxSize().padding(horizontal = 24.dp)) {
Box(
Modifier.weight(1f).fillMaxHeight(),
contentAlignment = Alignment.Center,
) {
illustration(illustrationSize)
}
Column(
Modifier.weight(1f).fillMaxHeight()
.verticalScroll(rememberScrollState())
.heightIn(min = viewportHeight).padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
content(true)
}
}
}
Column(
Modifier.weight(1f).fillMaxHeight()
.verticalScroll(rememberScrollState())
.heightIn(min = viewportHeight).padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
content = content,
)
}
} else {
val illustrationSize = minOf(maxWidth - 48.dp, 343.dp).coerceAtLeast(1.dp)
@@ -70,7 +77,7 @@ internal fun PairingPageLayout(
illustration(illustrationSize)
}
Spacer(Modifier.height(32.dp))
content()
content(false)
Spacer(Modifier.height(24.dp))
}
}
@@ -80,6 +80,8 @@ import org.jetbrains.compose.resources.painterResource
import com.zhushenwudi.lovelivemusicplayer.AppContainer
import com.zhushenwudi.lovelivemusicplayer.Const
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.lyric.LoadedLyric
import com.zhushenwudi.lovelivemusicplayer.lyric.LrcParser
@@ -504,34 +506,23 @@ fun TransferScreen(onBack: () -> Unit, onOpenScan: () -> Unit) {
},
)
} 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) {
TransferSongInfo(displayedSong, displayedArtist)
Spacer(Modifier.height(6.dp))
}
},
) { isSplitLayout ->
if (connected || state.completed) {
Column(
Modifier.height(60.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,
)
}
if (!isSplitLayout) {
TransferSongInfo(displayedSong, displayedArtist)
Spacer(Modifier.height(24.dp))
}
Spacer(Modifier.height(24.dp))
TransferProgressRing(
progress = state.progress,
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
private fun KeepScreenOn() {
DisposableEffect(Unit) {
@@ -636,6 +665,7 @@ private fun TransferCoverBlock(
CoverCarouselPager(
state = pagerState,
userScrollEnabled = false,
verticalOffset = 0.dp,
modifier = Modifier
.fillMaxWidth()
.height(size),
@@ -677,13 +707,10 @@ private fun TransferCoverCircle(
contentAlignment = Alignment.Center,
) {
// 默认图始终垫在底层,即使图片请求重建或超时也不会露出白色空帧。
Image(
painter = painterResource(Res.drawable.logo_logo),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
TransferDefaultLogo(
Modifier
.fillMaxSize()
.padding(36.dp),
.padding(size * 0.15f),
)
if (cover.url.isBlank() || loadFailed) {
LaunchedEffect(cover.key, loadFailed) { latestOnReady() }