replace app icon and fix bugs
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
package com.zhushenwudi.lovelivemusicplayer.ui.player
|
||||
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
|
||||
internal actual fun emptyPlayerTitleFontFamily(): FontFamily = FontFamily.Default
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 724 KiB After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
+3
-18
@@ -87,16 +87,15 @@ import com.zhushenwudi.lovelivemusicplayer.theme.LocalDarkTheme
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.AddToMenuDialog
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.AppDrawer
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.BatchActionBar
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.DrawerLogoButton
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.LandscapeLibraryHeader
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.LibrarySelectMode
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.LlmpBottomBar
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.LoadingOverlay
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.NeumorphicGroupLogoButton
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.SongLibraryTopBar
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.TopLibraryTabs
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.TwoButtonDialog
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.UpdateVersionDialog
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.isDefaultGroupLogo
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.player.FullPlayerScreen
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.player.LocalMiniPlayerHaze
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.player.MiniPlayerBar
|
||||
@@ -761,7 +760,6 @@ fun MainShell(
|
||||
}
|
||||
}
|
||||
},
|
||||
group = currentGroup,
|
||||
onOpenDrawer = if (batchLock || detailOpen) {
|
||||
null
|
||||
} else {
|
||||
@@ -788,26 +786,13 @@ fun MainShell(
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
// 顶部团组 Logo 按钮,随 currentGroup 变化。
|
||||
NeumorphicGroupLogoButton(
|
||||
group = currentGroup,
|
||||
DrawerLogoButton(
|
||||
onClick = if (batchLock || detailOpen) {
|
||||
null
|
||||
} else {
|
||||
{ scope.launch { drawerState.open() } }
|
||||
},
|
||||
size = 36.dp,
|
||||
background = if (currentGroup.isDefaultGroupLogo()) {
|
||||
Color(0x4DFFAE00)
|
||||
} else {
|
||||
shellBg
|
||||
},
|
||||
contentPadding = if (currentGroup.isDefaultGroupLogo()) {
|
||||
3.dp
|
||||
} else {
|
||||
0.dp
|
||||
},
|
||||
hasShadow = true,
|
||||
background = shellBg,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-5
@@ -1,5 +1,6 @@
|
||||
package com.zhushenwudi.lovelivemusicplayer.ui.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -36,6 +37,9 @@ import coil3.compose.AsyncImage
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import com.zhushenwudi.lovelivemusicplayer.domain.GroupKey
|
||||
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 org.jetbrains.compose.resources.painterResource
|
||||
import com.zhushenwudi.lovelivemusicplayer.i18n.tr
|
||||
import com.zhushenwudi.lovelivemusicplayer.navigation.Routes
|
||||
import com.zhushenwudi.lovelivemusicplayer.theme.ColorMs
|
||||
@@ -61,7 +65,7 @@ enum class DrawerAction {
|
||||
/** 与 Flutter `DrawerLayout` 对齐的抽屉尺寸。 */
|
||||
private object DrawerLayout {
|
||||
val logoSlotHeight = 130.dp
|
||||
val defaultLogoSize = 165.dp
|
||||
val defaultLogoSize = 120.dp
|
||||
val groupLogoSize = 130.dp
|
||||
val logoToTitleGap = 5.dp
|
||||
val titleFontSize = 17.3.sp
|
||||
@@ -187,10 +191,21 @@ fun AppDrawer(
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
GroupLogoImage(
|
||||
group = currentGroup,
|
||||
modifier = Modifier.size(topLogoSize),
|
||||
)
|
||||
if (currentGroup.isDefaultGroupLogo()) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
if (dark) Res.drawable.llmp_logo_dark else Res.drawable.llmp_logo_light,
|
||||
),
|
||||
contentDescription = "LLMP",
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.size(topLogoSize).clip(RoundedCornerShape(24.dp)),
|
||||
)
|
||||
} else {
|
||||
GroupLogoImage(
|
||||
group = currentGroup,
|
||||
modifier = Modifier.size(topLogoSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(DrawerLayout.logoToTitleGap))
|
||||
Text(
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.zhushenwudi.lovelivemusicplayer.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.i18n.tr
|
||||
import com.zhushenwudi.lovelivemusicplayer.theme.LocalDarkTheme
|
||||
|
||||
/** 横竖屏共用的抽屉入口,跟随应用主题(包括手动主题设置)。 */
|
||||
@Composable
|
||||
fun DrawerLogoButton(
|
||||
onClick: (() -> Unit)?,
|
||||
background: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
NeumorphicDrawableButton(
|
||||
drawable = if (LocalDarkTheme.current) {
|
||||
Res.drawable.llmp_logo_dark
|
||||
} else {
|
||||
Res.drawable.llmp_logo_light
|
||||
},
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
size = 36.dp,
|
||||
radius = 8.dp,
|
||||
background = background,
|
||||
contentScale = ContentScale.Fit,
|
||||
contentDescription = tr("open_drawer"),
|
||||
)
|
||||
}
|
||||
+2
-1
@@ -136,6 +136,7 @@ fun NeumorphicDrawableButton(
|
||||
hasShadow: Boolean = true,
|
||||
contentPadding: Dp = 0.dp,
|
||||
contentScale: ContentScale = ContentScale.Crop,
|
||||
contentDescription: String? = null,
|
||||
) {
|
||||
val shape = if (radius >= size / 2) CircleShape else RoundedCornerShape(radius)
|
||||
NeumorphicSurface(
|
||||
@@ -151,7 +152,7 @@ fun NeumorphicDrawableButton(
|
||||
key(drawable) {
|
||||
androidx.compose.foundation.Image(
|
||||
painter = painterResource(drawable),
|
||||
contentDescription = null,
|
||||
contentDescription = contentDescription,
|
||||
contentScale = contentScale,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.zhushenwudi.lovelivemusicplayer.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.theme.LocalIsLandscape
|
||||
|
||||
/** 横屏图示与操作分栏;窄窗口仍单列滚动,避免操作被固定大图/间距挤出屏幕。 */
|
||||
@Composable
|
||||
internal fun PairingPageLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
illustration: @Composable (Dp) -> Unit,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val landscape = LocalIsLandscape.current
|
||||
BoxWithConstraints(
|
||||
modifier.fillMaxSize().windowInsetsPadding(
|
||||
WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom),
|
||||
),
|
||||
) {
|
||||
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.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)
|
||||
Column(
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp, vertical = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
illustration(illustrationSize)
|
||||
}
|
||||
Spacer(Modifier.height(32.dp))
|
||||
content()
|
||||
Spacer(Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -38,7 +38,6 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.LineHeightStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.zhushenwudi.lovelivemusicplayer.domain.GroupKey
|
||||
import com.zhushenwudi.lovelivemusicplayer.i18n.tr
|
||||
import com.zhushenwudi.lovelivemusicplayer.navigation.LibraryTab
|
||||
import com.zhushenwudi.lovelivemusicplayer.navigation.TopSection
|
||||
@@ -114,7 +113,6 @@ fun LandscapeLibraryHeader(
|
||||
interactionEnabled: Boolean,
|
||||
query: String,
|
||||
onQueryChange: (String) -> Unit,
|
||||
group: GroupKey,
|
||||
onOpenDrawer: (() -> Unit)?,
|
||||
shellBg: Color,
|
||||
showSearch: Boolean,
|
||||
@@ -219,13 +217,9 @@ fun LandscapeLibraryHeader(
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
NeumorphicGroupLogoButton(
|
||||
group = group,
|
||||
DrawerLogoButton(
|
||||
onClick = onOpenDrawer,
|
||||
size = 36.dp,
|
||||
background = if (group.isDefaultGroupLogo()) Color(0x4DFFAE00) else shellBg,
|
||||
contentPadding = if (group.isDefaultGroupLogo()) 3.dp else 0.dp,
|
||||
hasShadow = true,
|
||||
background = shellBg,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.zhushenwudi.lovelivemusicplayer.ui.player
|
||||
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
|
||||
/** 空状态标题字体;iOS 明确选择中文字族,避免粗体逐字回退造成视觉深浅不一。 */
|
||||
internal expect fun emptyPlayerTitleFontFamily(): FontFamily
|
||||
+4
-1
@@ -40,6 +40,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -166,6 +167,7 @@ fun MiniPlayerBar(
|
||||
MiniPlayerTitle(
|
||||
text = title,
|
||||
color = titleColor,
|
||||
fontFamily = if (current == null) emptyPlayerTitleFontFamily() else null,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.then(
|
||||
@@ -248,10 +250,11 @@ private const val MiniTitleMarqueeHoldMs = 2_000
|
||||
private fun MiniPlayerTitle(
|
||||
text: String,
|
||||
color: Color,
|
||||
fontFamily: FontFamily?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val style = MiniTitleStyle.copy(color = color)
|
||||
val style = MiniTitleStyle.copy(color = color, fontFamily = fontFamily)
|
||||
BoxWithConstraints(
|
||||
modifier = modifier.clipToBounds(),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
|
||||
+125
-133
@@ -8,6 +8,7 @@ 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.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
@@ -64,6 +65,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -99,6 +101,7 @@ import com.zhushenwudi.lovelivemusicplayer.ui.components.DriveModeCarousel
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.LoadingOverlay
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.NeumorphicIconButton
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.PairTipsBlock
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.PairingPageLayout
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.PinkFuncButton
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.ResetDataDialog
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.components.TextFieldDialog
|
||||
@@ -395,48 +398,47 @@ fun ScanScreen(onBack: () -> Unit, onConnectedHost: (String) -> Unit) {
|
||||
var host by remember { mutableStateOf(AppContainer.settings.lastPcHost) }
|
||||
val dark = LocalDarkTheme.current
|
||||
val bg = if (dark) ColorMs.NightPrimary else ColorMs.LightPrimary
|
||||
var active by remember { mutableStateOf(true) }
|
||||
DisposableEffect(Unit) { onDispose { active = false } }
|
||||
fun connect(raw: String) {
|
||||
val parsed = parseHost(raw)
|
||||
if (parsed.isBlank()) return
|
||||
host = parsed
|
||||
AppContainer.settings.lastPcHost = parsed
|
||||
onConnectedHost(parsed)
|
||||
}
|
||||
fun scan() {
|
||||
PlatformUi.openQrScanner { result ->
|
||||
if (active && result.isNotBlank()) connect(result)
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
containerColor = bg,
|
||||
topBar = { AppHeader(title = tr("scan_pair"), onBack = onBack) },
|
||||
) { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(250.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(if (dark) ColorMs.Dark1E2328 else ColorMs.E7F2FF)
|
||||
.clickable {
|
||||
com.zhushenwudi.lovelivemusicplayer.platform.PlatformUi.openQrScanner { result ->
|
||||
val parsed = parseHost(result)
|
||||
if (parsed.isNotBlank()) {
|
||||
host = parsed
|
||||
AppContainer.settings.lastPcHost = parsed
|
||||
onConnectedHost(parsed)
|
||||
}
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
SvgIcon(
|
||||
asset = SvgAsset("drawable/sync_icon_scan_qr.svg"),
|
||||
size = 64.dp,
|
||||
tint = ColorMs.PinkF940A7,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Text(tr("tap_to_scan"), color = ColorMs.Gray999, fontSize = hSp(13f))
|
||||
PairingPageLayout(
|
||||
modifier = Modifier.padding(padding).consumeWindowInsets(padding).imePadding(),
|
||||
illustration = { size ->
|
||||
Box(
|
||||
Modifier
|
||||
.size(minOf(size, 250.dp))
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(if (dark) ColorMs.Dark1E2328 else ColorMs.E7F2FF)
|
||||
.clickable { scan() },
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
SvgIcon(
|
||||
asset = SvgAsset("drawable/sync_icon_scan_qr.svg"),
|
||||
size = 64.dp,
|
||||
tint = ColorMs.PinkF940A7,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Text(tr("tap_to_scan"), color = ColorMs.Gray999, fontSize = hSp(13f))
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(24.dp))
|
||||
},
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = host,
|
||||
onValueChange = { host = it.trim() },
|
||||
@@ -447,10 +449,7 @@ fun ScanScreen(onBack: () -> Unit, onConnectedHost: (String) -> Unit) {
|
||||
PinkFuncButton(
|
||||
title = tr("confirm_pair"),
|
||||
icon = SvgAsset("drawable/sync_icon_scan_qr.svg"),
|
||||
onClick = {
|
||||
AppContainer.settings.lastPcHost = host
|
||||
onConnectedHost(host)
|
||||
},
|
||||
onClick = { connect(host) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -488,75 +487,69 @@ fun TransferScreen(onBack: () -> Unit, onOpenScan: () -> Unit) {
|
||||
.background(bg),
|
||||
) {
|
||||
AppHeader(title = tr("music_trans"), onBack = { requestBack() })
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
PairingPageLayout(
|
||||
modifier = Modifier.weight(1f),
|
||||
illustration = { size ->
|
||||
if (connected || state.completed) {
|
||||
TransferCoverBlock(
|
||||
coverUrl = state.coverUrl,
|
||||
coverKey = state.coverKey,
|
||||
song = state.current,
|
||||
artist = state.artist,
|
||||
size = size,
|
||||
onIdleChange = { coverAnimationsIdle = it },
|
||||
onDisplayedSongChange = { song, artist ->
|
||||
displayedSong = song
|
||||
displayedArtist = artist
|
||||
},
|
||||
)
|
||||
} else {
|
||||
SvgIcon(asset = SvgAsset("drawable/sync_icon_data_sync.svg"), size = minOf(size, 300.dp))
|
||||
}
|
||||
},
|
||||
) {
|
||||
if (connected || state.completed) {
|
||||
Spacer(Modifier.height(35.dp))
|
||||
TransferCoverBlock(
|
||||
coverUrl = state.coverUrl,
|
||||
coverKey = state.coverKey,
|
||||
song = state.current,
|
||||
artist = state.artist,
|
||||
onIdleChange = { coverAnimationsIdle = it },
|
||||
onDisplayedSongChange = { song, artist ->
|
||||
displayedSong = song
|
||||
displayedArtist = artist
|
||||
},
|
||||
)
|
||||
Spacer(Modifier.height(20.dp))
|
||||
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 (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,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(24.dp))
|
||||
TransferProgressRing(
|
||||
progress = state.progress,
|
||||
total = state.total,
|
||||
downloading = state.total > 0,
|
||||
)
|
||||
} else {
|
||||
PairTipsBlock()
|
||||
Spacer(Modifier.height(24.dp))
|
||||
PinkFuncButton(
|
||||
title = tr("device_pair"),
|
||||
icon = SvgAsset("drawable/sync_icon_scan_qr.svg"),
|
||||
onClick = onOpenScan,
|
||||
)
|
||||
state.error?.let {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(it, color = ColorMs.PinkF940A7, fontSize = hSp(12f))
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(40.dp))
|
||||
TransferProgressRing(
|
||||
progress = state.progress,
|
||||
total = state.total,
|
||||
downloading = state.total > 0,
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.height(15.dp))
|
||||
SvgIcon(
|
||||
asset = SvgAsset("drawable/sync_icon_data_sync.svg"),
|
||||
size = 300.dp,
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(Modifier.height(65.dp))
|
||||
PairTipsBlock()
|
||||
Spacer(Modifier.height(85.dp))
|
||||
PinkFuncButton(
|
||||
title = tr("device_pair"),
|
||||
icon = SvgAsset("drawable/sync_icon_scan_qr.svg"),
|
||||
onClick = onOpenScan,
|
||||
)
|
||||
state.error?.let {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(it, color = ColorMs.PinkF940A7, fontSize = hSp(12f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showLeaveConfirm) {
|
||||
@@ -588,6 +581,7 @@ private fun TransferCoverBlock(
|
||||
coverKey: String,
|
||||
song: String,
|
||||
artist: String,
|
||||
size: Dp,
|
||||
onIdleChange: (Boolean) -> Unit,
|
||||
onDisplayedSongChange: (String, String) -> Unit,
|
||||
) {
|
||||
@@ -644,11 +638,12 @@ private fun TransferCoverBlock(
|
||||
userScrollEnabled = false,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(343.dp),
|
||||
.height(size),
|
||||
) { page ->
|
||||
val cover = covers[page]
|
||||
TransferCoverCircle(
|
||||
cover = cover,
|
||||
size = minOf(CoverCarouselSize, size * 0.8f),
|
||||
onReady = {
|
||||
if (cover.pageId !in readyCoverPages) {
|
||||
readyCoverPages.add(cover.pageId)
|
||||
@@ -670,13 +665,14 @@ private data class TransferCoverPage(
|
||||
@Composable
|
||||
private fun TransferCoverCircle(
|
||||
cover: TransferCoverPage,
|
||||
size: Dp,
|
||||
onReady: () -> Unit,
|
||||
) {
|
||||
var loadFailed by remember(cover.url) { mutableStateOf(false) }
|
||||
val latestOnReady by rememberUpdatedState(onReady)
|
||||
Box(
|
||||
Modifier
|
||||
.size(CoverCarouselSize)
|
||||
.size(size)
|
||||
.clip(CircleShape),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
@@ -698,7 +694,7 @@ private fun TransferCoverCircle(
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
stableCacheKey = cover.key,
|
||||
decodeSize = CoverCarouselSize,
|
||||
decodeSize = size,
|
||||
onSuccess = { latestOnReady() },
|
||||
onError = {
|
||||
loadFailed = true
|
||||
@@ -781,27 +777,23 @@ fun DataSyncScreen(onBack: () -> Unit, onOpenScan: () -> Unit) {
|
||||
containerColor = bg,
|
||||
topBar = { AppHeader(title = tr("data_sync"), onBack = onBack) },
|
||||
) { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Spacer(Modifier.height(15.dp))
|
||||
Box(Modifier.size(343.dp), contentAlignment = Alignment.Center) {
|
||||
if (connected) {
|
||||
WaterRipplePlaceholder()
|
||||
} else {
|
||||
SvgIcon(
|
||||
asset = SvgAsset("drawable/sync_icon_data_sync.svg"),
|
||||
size = 300.dp,
|
||||
)
|
||||
PairingPageLayout(
|
||||
modifier = Modifier.padding(padding).consumeWindowInsets(padding),
|
||||
illustration = { size ->
|
||||
Box(Modifier.size(size), contentAlignment = Alignment.Center) {
|
||||
if (connected) {
|
||||
WaterRipplePlaceholder(size)
|
||||
} else {
|
||||
SvgIcon(
|
||||
asset = SvgAsset("drawable/sync_icon_data_sync.svg"),
|
||||
size = minOf(size, 300.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
},
|
||||
) {
|
||||
PairTipsBlock()
|
||||
Spacer(Modifier.height(60.dp))
|
||||
Spacer(Modifier.height(24.dp))
|
||||
if (connected) {
|
||||
PinkFuncButton(
|
||||
title = tr("phone2pc"),
|
||||
@@ -859,19 +851,19 @@ fun DataSyncScreen(onBack: () -> Unit, onOpenScan: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WaterRipplePlaceholder() {
|
||||
Box(Modifier.size(343.dp), contentAlignment = Alignment.Center) {
|
||||
private fun WaterRipplePlaceholder(size: Dp) {
|
||||
Box(Modifier.size(size), contentAlignment = Alignment.Center) {
|
||||
listOf(0.35f, 0.55f, 0.75f, 0.95f).forEach { scale ->
|
||||
Box(
|
||||
Modifier
|
||||
.size((343 * scale).dp)
|
||||
.size(size * scale)
|
||||
.clip(CircleShape)
|
||||
.background(ColorMs.PinkF940A7.copy(alpha = 0.08f + (1f - scale) * 0.08f)),
|
||||
)
|
||||
}
|
||||
SvgIcon(
|
||||
asset = SvgAsset("drawable/sync_icon_data_sync.svg"),
|
||||
size = 96.dp,
|
||||
size = size * (96f / 343f),
|
||||
tint = ColorMs.PinkF940A7,
|
||||
)
|
||||
}
|
||||
|
||||
+33
-12
@@ -1,11 +1,14 @@
|
||||
package com.zhushenwudi.lovelivemusicplayer.ui.screens
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -23,11 +26,14 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.delay
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import com.zhushenwudi.lovelivemusicplayer.AppContainer
|
||||
import com.zhushenwudi.lovelivemusicplayer.generated.resources.Res
|
||||
import com.zhushenwudi.lovelivemusicplayer.generated.resources.launch_background
|
||||
import com.zhushenwudi.lovelivemusicplayer.generated.resources.launch_background_dark
|
||||
import com.zhushenwudi.lovelivemusicplayer.i18n.tr
|
||||
import com.zhushenwudi.lovelivemusicplayer.theme.ColorMs
|
||||
import com.zhushenwudi.lovelivemusicplayer.theme.LocalDarkTheme
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.image.LlmpAsyncImage
|
||||
import com.zhushenwudi.lovelivemusicplayer.ui.theme.LocalIsLandscape
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
@@ -39,9 +45,9 @@ import kotlin.time.Duration.Companion.seconds
|
||||
* 2) 若有本地缓存则随机切换(不等待网络)
|
||||
* 3) 倒计时/跳过 → 主页;离开后后台继续网络同步
|
||||
*/
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
@Composable
|
||||
fun SplashScreen(onFinished: () -> Unit) {
|
||||
val dark = LocalDarkTheme.current
|
||||
var count by remember { mutableIntStateOf(3) }
|
||||
var photoPath by remember { mutableStateOf<String?>(null) }
|
||||
var finished by remember { mutableStateOf(false) }
|
||||
@@ -70,20 +76,35 @@ fun SplashScreen(onFinished: () -> Unit) {
|
||||
goHome()
|
||||
}
|
||||
|
||||
Box(
|
||||
BoxWithConstraints(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White),
|
||||
.background(if (dark) ColorMs.NightPrimary else Color.White),
|
||||
) {
|
||||
val landscape = LocalIsLandscape.current
|
||||
val model = photoPath ?: Res.getUri("drawable/launch_background.png")
|
||||
LlmpAsyncImage(
|
||||
model = model,
|
||||
contentDescription = null,
|
||||
contentScale = if (photoPath != null && !landscape) ContentScale.Crop else ContentScale.Fit,
|
||||
alignment = if (landscape) Alignment.Center else Alignment.TopCenter,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
if (photoPath == null) {
|
||||
// 独立方形 Logo,横竖屏均居中;不把 app 图标拉伸成全屏图片。
|
||||
val logoSize = minOf(maxWidth * 0.7f, maxHeight * 0.55f, 280.dp)
|
||||
Image(
|
||||
painter = painterResource(
|
||||
if (dark) Res.drawable.launch_background_dark else Res.drawable.launch_background,
|
||||
),
|
||||
contentDescription = "LLMP",
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(logoSize)
|
||||
.clip(RoundedCornerShape(32.dp)),
|
||||
)
|
||||
} else {
|
||||
LlmpAsyncImage(
|
||||
model = photoPath,
|
||||
contentDescription = null,
|
||||
contentScale = if (landscape) ContentScale.Fit else ContentScale.Crop,
|
||||
alignment = if (landscape) Alignment.Center else Alignment.TopCenter,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.zhushenwudi.lovelivemusicplayer.ui.player
|
||||
|
||||
import androidx.compose.ui.text.ExperimentalTextApi
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.platform.SystemFont
|
||||
|
||||
@OptIn(ExperimentalTextApi::class)
|
||||
private val EmptyPlayerTitleFont = FontFamily(
|
||||
SystemFont(identity = "PingFang SC", weight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
internal actual fun emptyPlayerTitleFontFamily(): FontFamily = EmptyPlayerTitleFont
|
||||
Reference in New Issue
Block a user