调整我的派币界面ui,添加缺省图,修复bug,以及其他更改

移除其他用户界面右上角的积分显示

修复暂停视频后点赞/关注视频动态,暂停图标会消失以及关注/取消关注、收藏/取消收藏视频动态时用户头像会闪烁

修复发布动态/评论时' " & < >符号会变为&#39; &#34; &amp; &lt; &gt

修复短视频/新闻/推荐界面点击评论图标打开评论弹框显示异常

删除我的界面点壁纸可以更换壁纸的功能

根据设计图调整我的派币界面,增加返回按钮,添加缺省图,优化标签间的切换
This commit is contained in:
2025-11-13 17:02:32 +08:00
parent 83ef3e8dce
commit dbaa2f4c22
37 changed files with 1082 additions and 269 deletions

View File

@@ -447,7 +447,7 @@ fun MomentContentGroup(
} }
if (!momentEntity.momentTextContent.isNullOrEmpty()) { if (!momentEntity.momentTextContent.isNullOrEmpty()) {
Text( Text(
text = momentEntity.momentTextContent ?: "", text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentEntity.momentTextContent),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, top = 8.dp), .padding(start = 16.dp, end = 16.dp, top = 8.dp),

View File

@@ -216,7 +216,7 @@ fun DiscoverView() {
// 文本内容区域,限制最大高度 // 文本内容区域,限制最大高度
if (!momentItem.momentTextContent.isNullOrEmpty()) { if (!momentItem.momentTextContent.isNullOrEmpty()) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = momentItem.momentTextContent ?: "", text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentItem.momentTextContent),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
fontSize = 12.sp, fontSize = 12.sp,
color = AppColors.text, color = AppColors.text,

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@@ -47,27 +48,38 @@ fun FullArticleModal(
val context = LocalContext.current val context = LocalContext.current
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp val screenHeight = configuration.screenHeightDp.dp
val sheetHeight = screenHeight * 0.9f // 90% 高度 // 根据屏幕高度计算最大高度,用于限制弹窗最大高度
// 小屏幕(< 600dp75%中等屏幕600-800dp70%,大屏幕(> 800dp65%
val maxSheetHeight = when {
screenHeight.value < 600 -> screenHeight * 0.75f
screenHeight.value <= 800 -> screenHeight * 0.7f
else -> screenHeight * 0.65f
}
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
// 确保弹窗从下往上展开
androidx.compose.runtime.LaunchedEffect(Unit) {
sheetState.expand()
}
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
sheetState = rememberModalBottomSheetState( sheetState = sheetState,
skipPartiallyExpanded = true
),
modifier = Modifier
.fillMaxWidth()
.height(sheetHeight),
containerColor = appColors.background, containerColor = appColors.background,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
) { ) {
Column( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxWidth()
.heightIn(max = maxSheetHeight)
) { ) {
// 滚动内容 // 滚动内容
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
// 新闻图片区域 - 固定高度和宽度 // 新闻图片区域 - 固定高度和宽度
@@ -75,7 +87,6 @@ fun FullArticleModal(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(250.dp) .height(250.dp)
.background(color = appColors.secondaryBackground) .background(color = appColors.secondaryBackground)
) { ) {
if (moment.images.isNotEmpty()) { if (moment.images.isNotEmpty()) {
@@ -149,11 +160,13 @@ fun FullArticleModal(
// 帖子内容 // 帖子内容
NewsContent( NewsContent(
content = if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: ""), content = com.aiosman.ravenow.utils.Utils.unescapeHtml(
if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: "")
),
images = moment.images, images = moment.images,
context = context context = context
) )
Spacer(modifier = Modifier.height(200.dp)) Spacer(modifier = Modifier.height(200.dp))
} }
} }
} }

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
@@ -200,7 +201,9 @@ fun NewsCommentModal(
} }
Column( Column(
modifier = Modifier.background(AppColors.background) modifier = Modifier
.fillMaxSize()
.background(AppColors.background)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -229,33 +232,29 @@ fun NewsCommentModal(
} }
// 评论列表 // 评论列表
Column( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
) { ) {
Box( LazyColumn {
modifier = Modifier.fillMaxWidth() item {
) { CommentContent(
LazyColumn { viewModel = commentViewModel,
item { onLongClick = { comment ->
CommentContent( showCommentMenu = true
viewModel = commentViewModel, contextComment = comment
onLongClick = { comment -> },
showCommentMenu = true onReply = { parentComment, _, _, _ ->
contextComment = comment if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
}, debouncedNavigation {
onReply = { parentComment, _, _, _ -> navController.navigate(NavigationRoute.Login.route)
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
debouncedNavigation {
navController.navigate(NavigationRoute.Login.route)
}
} else {
replyComment = parentComment
} }
} else {
replyComment = parentComment
} }
) }
} )
} }
} }
} }

View File

@@ -11,7 +11,9 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@@ -180,23 +182,38 @@ fun NewsScreen() {
if (showCommentModal && selectedMoment != null) { if (showCommentModal && selectedMoment != null) {
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp val screenHeight = configuration.screenHeightDp.dp
val sheetHeight = screenHeight * 0.67f // 三分之二高度 // 根据屏幕高度计算最大高度,用于限制弹窗最大高度
// 小屏幕(< 600dp75%中等屏幕600-800dp70%,大屏幕(> 800dp65%
val maxSheetHeight = when {
screenHeight.value < 600 -> screenHeight * 0.75f
screenHeight.value <= 800 -> screenHeight * 0.7f
else -> screenHeight * 0.65f
}
val commentSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
// 确保弹窗从下往上展开
LaunchedEffect(Unit) {
commentSheetState.expand()
}
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { onDismissRequest = {
showCommentModal = false showCommentModal = false
}, },
sheetState = rememberModalBottomSheetState( sheetState = commentSheetState,
skipPartiallyExpanded = true
),
modifier = Modifier
.fillMaxWidth()
.height(sheetHeight),
containerColor = AppColors.background, containerColor = AppColors.background,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
) { ) {
NewsCommentModal( Box(
postId = selectedMoment?.id, modifier = Modifier
.fillMaxWidth()
.heightIn(max = maxSheetHeight)
) {
NewsCommentModal(
postId = selectedMoment?.id,
commentCount = selectedMoment?.commentCount ?: 0, commentCount = selectedMoment?.commentCount ?: 0,
onDismiss = { onDismiss = {
showCommentModal = false showCommentModal = false
@@ -207,7 +224,8 @@ fun NewsScreen() {
onCommentDeleted = { onCommentDeleted = {
selectedMoment?.id?.let { model.onDeleteComment(it) } selectedMoment?.id?.let { model.onDeleteComment(it) }
} }
) )
}
} }
} }
} }
@@ -287,7 +305,9 @@ fun NewsItem(
// 新闻内容(超出使用省略号) // 新闻内容(超出使用省略号)
Text( Text(
text = if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: ""), text = com.aiosman.ravenow.utils.Utils.unescapeHtml(
if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: "")
),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),

View File

@@ -137,7 +137,7 @@ fun PostRecommendationItem(
modifier = Modifier modifier = Modifier
.fillMaxWidth(0.8f) .fillMaxWidth(0.8f)
.padding(top = 4.dp), .padding(top = 4.dp),
text = moment.momentTextContent ?: "", text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
fontSize = 16.sp, fontSize = 16.sp,
color = Color.White, color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold), style = TextStyle(fontWeight = FontWeight.Bold),

View File

@@ -283,7 +283,7 @@ fun VideoRecommendationItem(
modifier = Modifier modifier = Modifier
.fillMaxWidth(0.8f) .fillMaxWidth(0.8f)
.padding(top = 4.dp), .padding(top = 4.dp),
text = moment.momentTextContent ?: "", text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
fontSize = 16.sp, fontSize = 16.sp,
color = Color.White, color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold), style = TextStyle(fontWeight = FontWeight.Bold),

View File

@@ -409,18 +409,6 @@ fun ProfileV3(
bottomEnd = 32.dp bottomEnd = 32.dp
) )
) )
.let {
if (isSelf && isMain) {
it.noRippleClickable {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickBannerImageLauncher.launch(this)
}
}
} else {
it
}
}
) { ) {
CustomAsyncImage( CustomAsyncImage(
LocalContext.current, LocalContext.current,
@@ -773,48 +761,44 @@ fun TopNavigationBar(
horizontalArrangement = Arrangement.End, horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// 左侧:互动数据卡片 // 左侧:互动数据卡片(仅本人主页显示)
Row( if (isSelf) {
modifier = Modifier Row(
.height(24.dp) modifier = Modifier
.background( .height(24.dp)
color = Color.White.copy(alpha = 0.52f), .background(
shape = RoundedCornerShape(16.dp) color = Color.White.copy(alpha = 0.52f),
shape = RoundedCornerShape(16.dp)
)
.border(
width = 0.5.dp,
color = cardBorderColor, // 根据背景透明度改变边框颜色
shape = RoundedCornerShape(16.dp)
)
.padding(horizontal = 8.dp)
.noRippleClickable { onPointsClick?.invoke() },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
// 互动图标
Image(
painter = painterResource(id = R.mipmap.paip_coin_img),
contentDescription = "互动",
modifier = Modifier.size(24.dp),
) )
.border( Spacer(modifier = Modifier.width(4.dp))
width = 0.5.dp, Text(
color = cardBorderColor, // 根据背景透明度改变边框颜色 text = pointsBalanceState?.value?.balance?.let { numberFormat.format(it) } ?: "--",
shape = RoundedCornerShape(16.dp) fontSize = 14.sp,
fontWeight = FontWeight.W500,
color = if (AppState.darkMode) Color.White else Color.Black, // 暗色模式下为白色,亮色模式下为黑色
textAlign = TextAlign.Center
) )
.padding(horizontal = 8.dp) }
.let {
if (isSelf) it.noRippleClickable { onPointsClick?.invoke() } else it Spacer(modifier = Modifier.width(16.dp))
},
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
// 互动图标
Image(
painter = painterResource(id = R.mipmap.paip_coin_img),
contentDescription = "互动",
modifier = Modifier.size(24.dp),
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = if (isSelf) {
pointsBalanceState?.value?.balance?.let { numberFormat.format(it) } ?: "--"
} else {
numberFormat.format(interactionCount)
},
fontSize = 14.sp,
fontWeight = FontWeight.W500,
color = if (AppState.darkMode) Color.White else Color.Black, // 暗色模式下为白色,亮色模式下为黑色
textAlign = TextAlign.Center
)
} }
Spacer(modifier = Modifier.width(16.dp))
// 中间:分享图标 // 中间:分享图标
Image( Image(
painter = painterResource(id = R.mipmap.menu_icon), painter = painterResource(id = R.mipmap.menu_icon),

View File

@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@@ -252,9 +253,6 @@ fun ShortViewCompose(
val initialLayout = remember { val initialLayout = remember {
mutableStateOf(true) mutableStateOf(true)
} }
val pauseIconVisibleState = remember {
mutableStateOf(false)
}
Pager( Pager(
modifier = Modifier modifier = Modifier
@@ -266,7 +264,6 @@ fun ShortViewCompose(
) { ) {
// 使用 key 确保每个页面独立,避免状态混乱 // 使用 key 确保每个页面独立,避免状态混乱
androidx.compose.runtime.key(page) { androidx.compose.runtime.key(page) {
pauseIconVisibleState.value = false
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) { val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
videoMoments[page] videoMoments[page]
} else { } else {
@@ -284,7 +281,6 @@ fun ShortViewCompose(
pagerState = pagerState, pagerState = pagerState,
pager = page, pager = page,
initialLayout = initialLayout, initialLayout = initialLayout,
pauseIconVisibleState = pauseIconVisibleState,
VideoHeader = videoHeader, VideoHeader = videoHeader,
VideoBottom = videoBottom, VideoBottom = videoBottom,
onLikeClick = onLikeClick, onLikeClick = onLikeClick,
@@ -312,7 +308,6 @@ private fun SingleVideoItemContent(
pagerState: PagerState, pagerState: PagerState,
pager: Int, pager: Int,
initialLayout: MutableState<Boolean>, initialLayout: MutableState<Boolean>,
pauseIconVisibleState: MutableState<Boolean>,
VideoHeader: @Composable() () -> Unit = {}, VideoHeader: @Composable() () -> Unit = {},
VideoBottom: @Composable ((MomentEntity) -> Unit)? = null, VideoBottom: @Composable ((MomentEntity) -> Unit)? = null,
onLikeClick: ((MomentEntity) -> Unit)? = null, onLikeClick: ((MomentEntity) -> Unit)? = null,
@@ -323,6 +318,18 @@ private fun SingleVideoItemContent(
onAvatarClick: ((MomentEntity) -> Unit)? = null, onAvatarClick: ((MomentEntity) -> Unit)? = null,
isPageVisible: Boolean = true isPageVisible: Boolean = true
) { ) {
// 将暂停状态移到每个视频项内部,使用 remember 保存,避免在点赞/关注时被重置
val pauseIconVisibleState = remember(pager) {
mutableStateOf(false)
}
// 当页面切换时,重置暂停状态
LaunchedEffect(pager, pagerState.currentPage) {
if (pager != pagerState.currentPage) {
pauseIconVisibleState.value = false
}
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -379,7 +386,13 @@ fun VideoPlayer(
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
val configuration = androidx.compose.ui.platform.LocalConfiguration.current val configuration = androidx.compose.ui.platform.LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp val screenHeight = configuration.screenHeightDp.dp
val sheetHeight = screenHeight * 0.7f // 屏幕的70%高度 // 根据屏幕高度计算最大高度,用于限制弹窗最大高度
// 小屏幕(< 600dp75%中等屏幕600-800dp70%,大屏幕(> 800dp65%
val maxSheetHeight = when {
screenHeight.value < 600 -> screenHeight * 0.75f
screenHeight.value <= 800 -> screenHeight * 0.7f
else -> screenHeight * 0.65f
}
var showCommentModal by remember { mutableStateOf(false) } var showCommentModal by remember { mutableStateOf(false) }
var sheetState = rememberModalBottomSheetState( var sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true skipPartiallyExpanded = true
@@ -417,18 +430,34 @@ fun VideoPlayer(
} }
// 根据页面状态控制播放 // 根据页面状态控制播放
// 使用 remember 保存上一次的当前页面,只在页面真正切换时才重置暂停状态
val previousCurrentPage = remember(pager) { mutableStateOf(pagerState.currentPage) }
LaunchedEffect(pager, pagerState.currentPage, isPageVisible) { LaunchedEffect(pager, pagerState.currentPage, isPageVisible) {
val isCurrentPage = pager == pagerState.currentPage && isPageVisible val isCurrentPage = pager == pagerState.currentPage && isPageVisible
val pageChanged = previousCurrentPage.value != pagerState.currentPage
if (isCurrentPage) { if (isCurrentPage) {
// 当前页面且页面可见:恢复播放 // 当前页面且页面可见
exoPlayer.playWhenReady = true if (pageChanged) {
exoPlayer.play() // 页面切换了(从其他页面切换到当前页面),恢复播放并重置暂停状态
pauseIconVisibleState.value = false exoPlayer.playWhenReady = true
exoPlayer.play()
pauseIconVisibleState.value = false
previousCurrentPage.value = pagerState.currentPage
} else {
// 页面未切换,保持当前的播放/暂停状态
// 如果用户手动暂停了,不要自动恢复播放;如果正在播放,保持播放
if (!pauseIconVisibleState.value && !exoPlayer.isPlaying) {
exoPlayer.playWhenReady = true
exoPlayer.play()
}
}
} else { } else {
// 非当前页面或页面不可见:立即暂停并停止准备播放 // 非当前页面或页面不可见:立即暂停并停止准备播放
exoPlayer.playWhenReady = false exoPlayer.playWhenReady = false
exoPlayer.pause() exoPlayer.pause()
pauseIconVisibleState.value = false pauseIconVisibleState.value = false
previousCurrentPage.value = pagerState.currentPage
} }
} }
// 视频播放器容器 - 确保每个视频页面都被正确裁剪 // 视频播放器容器 - 确保每个视频页面都被正确裁剪
@@ -519,39 +548,38 @@ fun VideoPlayer(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
if (moment != null) { if (moment != null) {
// 使用 key 确保状态变化时重新组合 // 使用 key 确保状态变化时重新组合,包含所有可能变化的状态以避免不必要的重组
androidx.compose.runtime.key(moment.id, moment.isFavorite) { // 移除 key让 Compose 自动处理重组,避免头像闪烁
UserAvatar( UserAvatar(
avatarUrl = moment.avatar, avatarUrl = moment.avatar,
onClick = { onAvatarClick?.invoke(moment) } onClick = { onAvatarClick?.invoke(moment) }
) )
VideoBtn( VideoBtn(
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like, icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
text = formatCount(moment.likeCount), text = formatCount(moment.likeCount),
isActive = moment.liked, isActive = moment.liked,
onClick = { onLikeClick?.invoke(moment) } onClick = { onLikeClick?.invoke(moment) }
) )
VideoBtn( VideoBtn(
icon = R.mipmap.icon_comment, icon = R.mipmap.icon_comment,
text = formatCount(moment.commentCount), text = formatCount(moment.commentCount),
onClick = { onClick = {
showCommentModal = true showCommentModal = true
onCommentClick?.invoke(moment) onCommentClick?.invoke(moment)
} }
) )
VideoBtn( VideoBtn(
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect, icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
text = formatCount(moment.favoriteCount), text = formatCount(moment.favoriteCount),
isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色 isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色
keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色 keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色
onClick = { onFavoriteClick?.invoke(moment) } onClick = { onFavoriteClick?.invoke(moment) }
) )
VideoBtn( VideoBtn(
icon = R.mipmap.icon_share, icon = R.mipmap.icon_share,
text = formatCount(moment.shareCount), text = formatCount(moment.shareCount),
onClick = { onShareClick?.invoke(moment) } onClick = { onShareClick?.invoke(moment) }
) )
}
} else { } else {
UserAvatar() UserAvatar()
VideoBtn(icon = R.drawable.rider_pro_moment_like, text = "0") VideoBtn(icon = R.drawable.rider_pro_moment_like, text = "0")
@@ -609,7 +637,7 @@ fun VideoPlayer(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 4.dp), .padding(top = 4.dp),
text = moment.momentTextContent ?: "", text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
fontSize = 16.sp, fontSize = 16.sp,
color = Color.White, color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold), style = TextStyle(fontWeight = FontWeight.Bold),
@@ -637,7 +665,7 @@ fun VideoPlayer(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(sheetHeight) .heightIn(max = maxSheetHeight)
) { ) {
CommentModalContent( CommentModalContent(
postId = moment.id, postId = moment.id,
@@ -656,32 +684,36 @@ fun UserAvatar(
avatarUrl: String? = null, avatarUrl: String? = null,
onClick: (() -> Unit)? = null onClick: (() -> Unit)? = null
) { ) {
Box( // 使用 key 确保当 avatarUrl 不变时不会重新创建组件,避免闪烁
modifier = Modifier androidx.compose.runtime.key(avatarUrl) {
.padding(bottom = 16.dp) Box(
.size(40.dp) modifier = Modifier
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp)) .padding(bottom = 16.dp)
.clip(RoundedCornerShape(40.dp)) .size(40.dp)
.then( .border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
if (onClick != null) { .clip(RoundedCornerShape(40.dp))
Modifier.noRippleClickable { onClick() } .then(
} else { if (onClick != null) {
Modifier Modifier.noRippleClickable { onClick() }
} } else {
) Modifier
) { }
if (avatarUrl != null && avatarUrl.isNotEmpty()) { )
CustomAsyncImage( ) {
imageUrl = avatarUrl, if (avatarUrl != null && avatarUrl.isNotEmpty()) {
contentDescription = "用户头像", CustomAsyncImage(
modifier = Modifier.fillMaxSize(), imageUrl = avatarUrl,
defaultRes = R.drawable.default_avatar contentDescription = "用户头像",
) modifier = Modifier.fillMaxSize(),
} else { defaultRes = R.drawable.default_avatar,
Image( showShimmer = false // 禁用 shimmer 效果,避免闪烁
painter = painterResource(id = R.drawable.default_avatar), )
contentDescription = "用户头像" } else {
) Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "用户头像"
)
}
} }
} }
} }

View File

@@ -1281,7 +1281,7 @@ fun PostDetails(
) { ) {
if (!momentEntity?.momentTextContent.isNullOrEmpty()) { if (!momentEntity?.momentTextContent.isNullOrEmpty()) {
Text( Text(
text = momentEntity?.momentTextContent ?: "", text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentEntity?.momentTextContent),
fontSize = 14.sp, fontSize = 14.sp,
color = AppColors.text, color = AppColors.text,
) )
@@ -1396,7 +1396,7 @@ fun CommentItem(
Text( Text(
text = commentEntity.comment, text = com.aiosman.ravenow.utils.Utils.unescapeHtml(commentEntity.comment),
fontSize = 13.sp, fontSize = 13.sp,
maxLines = Int.MAX_VALUE, maxLines = Int.MAX_VALUE,
softWrap = true, softWrap = true,

View File

@@ -116,4 +116,22 @@ object Utils {
return compressedFile return compressedFile
} }
/**
* HTML 反转义函数
* 将 HTML 实体编码转换为对应的字符
* 例如:&#39; -> ', &#34; -> ", &amp; -> &, &lt; -> <, &gt; -> >
*/
fun unescapeHtml(html: String?): String {
if (html == null) return ""
return html
.replace("&#39;", "'")
.replace("&#34;", "\"")
.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&quot;", "\"")
.replace("&apos;", "'")
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB