调整我的派币界面ui,添加缺省图,修复bug,以及其他更改
移除其他用户界面右上角的积分显示 修复暂停视频后点赞/关注视频动态,暂停图标会消失以及关注/取消关注、收藏/取消收藏视频动态时用户头像会闪烁 修复发布动态/评论时' " & < >符号会变为' " & < > 修复短视频/新闻/推荐界面点击评论图标打开评论弹框显示异常 删除我的界面点壁纸可以更换壁纸的功能 根据设计图调整我的派币界面,增加返回按钮,添加缺省图,优化标签间的切换
@@ -447,7 +447,7 @@ fun MomentContentGroup(
|
||||
}
|
||||
if (!momentEntity.momentTextContent.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = momentEntity.momentTextContent ?: "",
|
||||
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentEntity.momentTextContent),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
||||
|
||||
@@ -216,7 +216,7 @@ fun DiscoverView() {
|
||||
// 文本内容区域,限制最大高度
|
||||
if (!momentItem.momentTextContent.isNullOrEmpty()) {
|
||||
androidx.compose.material3.Text(
|
||||
text = momentItem.momentTextContent ?: "",
|
||||
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentItem.momentTextContent),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
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.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -47,27 +48,38 @@ fun FullArticleModal(
|
||||
val context = LocalContext.current
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenHeight = configuration.screenHeightDp.dp
|
||||
val sheetHeight = screenHeight * 0.9f // 90% 高度
|
||||
// 根据屏幕高度计算最大高度,用于限制弹窗最大高度
|
||||
// 小屏幕(< 600dp):75%,中等屏幕(600-800dp):70%,大屏幕(> 800dp):65%
|
||||
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(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(sheetHeight),
|
||||
sheetState = sheetState,
|
||||
containerColor = appColors.background,
|
||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||
) {
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = maxSheetHeight)
|
||||
) {
|
||||
// 滚动内容
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
// 新闻图片区域 - 固定高度和宽度
|
||||
@@ -75,7 +87,6 @@ fun FullArticleModal(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(250.dp)
|
||||
|
||||
.background(color = appColors.secondaryBackground)
|
||||
) {
|
||||
if (moment.images.isNotEmpty()) {
|
||||
@@ -149,11 +160,13 @@ fun FullArticleModal(
|
||||
|
||||
// 帖子内容
|
||||
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,
|
||||
context = context
|
||||
)
|
||||
Spacer(modifier = Modifier.height(200.dp))
|
||||
Spacer(modifier = Modifier.height(200.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
@@ -200,7 +201,9 @@ fun NewsCommentModal(
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.background(AppColors.background)
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(AppColors.background)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -229,33 +232,29 @@ fun NewsCommentModal(
|
||||
}
|
||||
|
||||
// 评论列表
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
LazyColumn {
|
||||
item {
|
||||
CommentContent(
|
||||
viewModel = commentViewModel,
|
||||
onLongClick = { comment ->
|
||||
showCommentMenu = true
|
||||
contextComment = comment
|
||||
},
|
||||
onReply = { parentComment, _, _, _ ->
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||
debouncedNavigation {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
}
|
||||
} else {
|
||||
replyComment = parentComment
|
||||
LazyColumn {
|
||||
item {
|
||||
CommentContent(
|
||||
viewModel = commentViewModel,
|
||||
onLongClick = { comment ->
|
||||
showCommentMenu = true
|
||||
contextComment = comment
|
||||
},
|
||||
onReply = { parentComment, _, _, _ ->
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||
debouncedNavigation {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
}
|
||||
} else {
|
||||
replyComment = parentComment
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import androidx.compose.foundation.layout.Spacer
|
||||
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.statusBars
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
@@ -180,23 +182,38 @@ fun NewsScreen() {
|
||||
if (showCommentModal && selectedMoment != null) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenHeight = configuration.screenHeightDp.dp
|
||||
val sheetHeight = screenHeight * 0.67f // 三分之二高度
|
||||
// 根据屏幕高度计算最大高度,用于限制弹窗最大高度
|
||||
// 小屏幕(< 600dp):75%,中等屏幕(600-800dp):70%,大屏幕(> 800dp):65%
|
||||
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(
|
||||
onDismissRequest = {
|
||||
showCommentModal = false
|
||||
},
|
||||
sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(sheetHeight),
|
||||
sheetState = commentSheetState,
|
||||
containerColor = AppColors.background,
|
||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||
) {
|
||||
NewsCommentModal(
|
||||
postId = selectedMoment?.id,
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = maxSheetHeight)
|
||||
) {
|
||||
NewsCommentModal(
|
||||
postId = selectedMoment?.id,
|
||||
commentCount = selectedMoment?.commentCount ?: 0,
|
||||
onDismiss = {
|
||||
showCommentModal = false
|
||||
@@ -207,7 +224,8 @@ fun NewsScreen() {
|
||||
onCommentDeleted = {
|
||||
selectedMoment?.id?.let { model.onDeleteComment(it) }
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,7 +305,9 @@ fun NewsItem(
|
||||
|
||||
// 新闻内容(超出使用省略号)
|
||||
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
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
|
||||
@@ -137,7 +137,7 @@ fun PostRecommendationItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.8f)
|
||||
.padding(top = 4.dp),
|
||||
text = moment.momentTextContent ?: "",
|
||||
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
|
||||
@@ -283,7 +283,7 @@ fun VideoRecommendationItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.8f)
|
||||
.padding(top = 4.dp),
|
||||
text = moment.momentTextContent ?: "",
|
||||
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
|
||||
@@ -409,18 +409,6 @@ fun ProfileV3(
|
||||
bottomEnd = 32.dp
|
||||
)
|
||||
)
|
||||
.let {
|
||||
if (isSelf && isMain) {
|
||||
it.noRippleClickable {
|
||||
Intent(Intent.ACTION_PICK).apply {
|
||||
type = "image/*"
|
||||
pickBannerImageLauncher.launch(this)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
LocalContext.current,
|
||||
@@ -773,47 +761,43 @@ fun TopNavigationBar(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 左侧:互动数据卡片
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.background(
|
||||
color = Color.White.copy(alpha = 0.52f),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
// 左侧:互动数据卡片(仅本人主页显示)
|
||||
if (isSelf) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.background(
|
||||
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(
|
||||
width = 0.5.dp,
|
||||
color = cardBorderColor, // 根据背景透明度改变边框颜色
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = pointsBalanceState?.value?.balance?.let { numberFormat.format(it) } ?: "--",
|
||||
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
|
||||
},
|
||||
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))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
|
||||
// 中间:分享图标
|
||||
Image(
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Row
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -252,9 +253,6 @@ fun ShortViewCompose(
|
||||
val initialLayout = remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
val pauseIconVisibleState = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Pager(
|
||||
modifier = Modifier
|
||||
@@ -266,7 +264,6 @@ fun ShortViewCompose(
|
||||
) {
|
||||
// 使用 key 确保每个页面独立,避免状态混乱
|
||||
androidx.compose.runtime.key(page) {
|
||||
pauseIconVisibleState.value = false
|
||||
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
|
||||
videoMoments[page]
|
||||
} else {
|
||||
@@ -284,7 +281,6 @@ fun ShortViewCompose(
|
||||
pagerState = pagerState,
|
||||
pager = page,
|
||||
initialLayout = initialLayout,
|
||||
pauseIconVisibleState = pauseIconVisibleState,
|
||||
VideoHeader = videoHeader,
|
||||
VideoBottom = videoBottom,
|
||||
onLikeClick = onLikeClick,
|
||||
@@ -312,7 +308,6 @@ private fun SingleVideoItemContent(
|
||||
pagerState: PagerState,
|
||||
pager: Int,
|
||||
initialLayout: MutableState<Boolean>,
|
||||
pauseIconVisibleState: MutableState<Boolean>,
|
||||
VideoHeader: @Composable() () -> Unit = {},
|
||||
VideoBottom: @Composable ((MomentEntity) -> Unit)? = null,
|
||||
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
||||
@@ -323,6 +318,18 @@ private fun SingleVideoItemContent(
|
||||
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
||||
isPageVisible: Boolean = true
|
||||
) {
|
||||
// 将暂停状态移到每个视频项内部,使用 remember 保存,避免在点赞/关注时被重置
|
||||
val pauseIconVisibleState = remember(pager) {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
// 当页面切换时,重置暂停状态
|
||||
LaunchedEffect(pager, pagerState.currentPage) {
|
||||
if (pager != pagerState.currentPage) {
|
||||
pauseIconVisibleState.value = false
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -379,7 +386,13 @@ fun VideoPlayer(
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
|
||||
val screenHeight = configuration.screenHeightDp.dp
|
||||
val sheetHeight = screenHeight * 0.7f // 屏幕的70%高度
|
||||
// 根据屏幕高度计算最大高度,用于限制弹窗最大高度
|
||||
// 小屏幕(< 600dp):75%,中等屏幕(600-800dp):70%,大屏幕(> 800dp):65%
|
||||
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 sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
@@ -417,18 +430,34 @@ fun VideoPlayer(
|
||||
}
|
||||
|
||||
// 根据页面状态控制播放
|
||||
// 使用 remember 保存上一次的当前页面,只在页面真正切换时才重置暂停状态
|
||||
val previousCurrentPage = remember(pager) { mutableStateOf(pagerState.currentPage) }
|
||||
LaunchedEffect(pager, pagerState.currentPage, isPageVisible) {
|
||||
val isCurrentPage = pager == pagerState.currentPage && isPageVisible
|
||||
val pageChanged = previousCurrentPage.value != pagerState.currentPage
|
||||
|
||||
if (isCurrentPage) {
|
||||
// 当前页面且页面可见:恢复播放
|
||||
exoPlayer.playWhenReady = true
|
||||
exoPlayer.play()
|
||||
pauseIconVisibleState.value = false
|
||||
// 当前页面且页面可见
|
||||
if (pageChanged) {
|
||||
// 页面切换了(从其他页面切换到当前页面),恢复播放并重置暂停状态
|
||||
exoPlayer.playWhenReady = true
|
||||
exoPlayer.play()
|
||||
pauseIconVisibleState.value = false
|
||||
previousCurrentPage.value = pagerState.currentPage
|
||||
} else {
|
||||
// 页面未切换,保持当前的播放/暂停状态
|
||||
// 如果用户手动暂停了,不要自动恢复播放;如果正在播放,保持播放
|
||||
if (!pauseIconVisibleState.value && !exoPlayer.isPlaying) {
|
||||
exoPlayer.playWhenReady = true
|
||||
exoPlayer.play()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 非当前页面或页面不可见:立即暂停并停止准备播放
|
||||
exoPlayer.playWhenReady = false
|
||||
exoPlayer.pause()
|
||||
pauseIconVisibleState.value = false
|
||||
previousCurrentPage.value = pagerState.currentPage
|
||||
}
|
||||
}
|
||||
// 视频播放器容器 - 确保每个视频页面都被正确裁剪
|
||||
@@ -519,39 +548,38 @@ fun VideoPlayer(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
if (moment != null) {
|
||||
// 使用 key 确保状态变化时重新组合
|
||||
androidx.compose.runtime.key(moment.id, moment.isFavorite) {
|
||||
UserAvatar(
|
||||
avatarUrl = moment.avatar,
|
||||
onClick = { onAvatarClick?.invoke(moment) }
|
||||
)
|
||||
VideoBtn(
|
||||
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
|
||||
text = formatCount(moment.likeCount),
|
||||
isActive = moment.liked,
|
||||
onClick = { onLikeClick?.invoke(moment) }
|
||||
)
|
||||
VideoBtn(
|
||||
icon = R.mipmap.icon_comment,
|
||||
text = formatCount(moment.commentCount),
|
||||
onClick = {
|
||||
showCommentModal = true
|
||||
onCommentClick?.invoke(moment)
|
||||
}
|
||||
)
|
||||
VideoBtn(
|
||||
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
|
||||
text = formatCount(moment.favoriteCount),
|
||||
isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色
|
||||
keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色
|
||||
onClick = { onFavoriteClick?.invoke(moment) }
|
||||
)
|
||||
VideoBtn(
|
||||
icon = R.mipmap.icon_share,
|
||||
text = formatCount(moment.shareCount),
|
||||
onClick = { onShareClick?.invoke(moment) }
|
||||
)
|
||||
}
|
||||
// 使用 key 确保状态变化时重新组合,包含所有可能变化的状态以避免不必要的重组
|
||||
// 移除 key,让 Compose 自动处理重组,避免头像闪烁
|
||||
UserAvatar(
|
||||
avatarUrl = moment.avatar,
|
||||
onClick = { onAvatarClick?.invoke(moment) }
|
||||
)
|
||||
VideoBtn(
|
||||
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
|
||||
text = formatCount(moment.likeCount),
|
||||
isActive = moment.liked,
|
||||
onClick = { onLikeClick?.invoke(moment) }
|
||||
)
|
||||
VideoBtn(
|
||||
icon = R.mipmap.icon_comment,
|
||||
text = formatCount(moment.commentCount),
|
||||
onClick = {
|
||||
showCommentModal = true
|
||||
onCommentClick?.invoke(moment)
|
||||
}
|
||||
)
|
||||
VideoBtn(
|
||||
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
|
||||
text = formatCount(moment.favoriteCount),
|
||||
isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色
|
||||
keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色
|
||||
onClick = { onFavoriteClick?.invoke(moment) }
|
||||
)
|
||||
VideoBtn(
|
||||
icon = R.mipmap.icon_share,
|
||||
text = formatCount(moment.shareCount),
|
||||
onClick = { onShareClick?.invoke(moment) }
|
||||
)
|
||||
} else {
|
||||
UserAvatar()
|
||||
VideoBtn(icon = R.drawable.rider_pro_moment_like, text = "0")
|
||||
@@ -609,7 +637,7 @@ fun VideoPlayer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp),
|
||||
text = moment.momentTextContent ?: "",
|
||||
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
@@ -637,7 +665,7 @@ fun VideoPlayer(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(sheetHeight)
|
||||
.heightIn(max = maxSheetHeight)
|
||||
) {
|
||||
CommentModalContent(
|
||||
postId = moment.id,
|
||||
@@ -656,32 +684,36 @@ fun UserAvatar(
|
||||
avatarUrl: String? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.size(40.dp)
|
||||
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
|
||||
.clip(RoundedCornerShape(40.dp))
|
||||
.then(
|
||||
if (onClick != null) {
|
||||
Modifier.noRippleClickable { onClick() }
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
) {
|
||||
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = avatarUrl,
|
||||
contentDescription = "用户头像",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
defaultRes = R.drawable.default_avatar
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.default_avatar),
|
||||
contentDescription = "用户头像"
|
||||
)
|
||||
// 使用 key 确保当 avatarUrl 不变时不会重新创建组件,避免闪烁
|
||||
androidx.compose.runtime.key(avatarUrl) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.size(40.dp)
|
||||
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
|
||||
.clip(RoundedCornerShape(40.dp))
|
||||
.then(
|
||||
if (onClick != null) {
|
||||
Modifier.noRippleClickable { onClick() }
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
) {
|
||||
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = avatarUrl,
|
||||
contentDescription = "用户头像",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
defaultRes = R.drawable.default_avatar,
|
||||
showShimmer = false // 禁用 shimmer 效果,避免闪烁
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.default_avatar),
|
||||
contentDescription = "用户头像"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1281,7 +1281,7 @@ fun PostDetails(
|
||||
) {
|
||||
if (!momentEntity?.momentTextContent.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = momentEntity?.momentTextContent ?: "",
|
||||
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentEntity?.momentTextContent),
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.text,
|
||||
)
|
||||
@@ -1396,7 +1396,7 @@ fun CommentItem(
|
||||
|
||||
|
||||
Text(
|
||||
text = commentEntity.comment,
|
||||
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(commentEntity.comment),
|
||||
fontSize = 13.sp,
|
||||
maxLines = Int.MAX_VALUE,
|
||||
softWrap = true,
|
||||
|
||||
@@ -116,4 +116,22 @@ object Utils {
|
||||
|
||||
return compressedFile
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 反转义函数
|
||||
* 将 HTML 实体编码转换为对应的字符
|
||||
* 例如:' -> ', " -> ", & -> &, < -> <, > -> >
|
||||
*/
|
||||
fun unescapeHtml(html: String?): String {
|
||||
if (html == null) return ""
|
||||
|
||||
return html
|
||||
.replace("'", "'")
|
||||
.replace(""", "\"")
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace(""", "\"")
|
||||
.replace("'", "'")
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/mipmap-hdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 801 B |
BIN
app/src/main/res/mipmap-hdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
app/src/main/res/mipmap-hdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
app/src/main/res/mipmap-hdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 637 B |
BIN
app/src/main/res/mipmap-hdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 644 B |
BIN
app/src/main/res/mipmap-mdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 495 B |
BIN
app/src/main/res/mipmap-mdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 459 B |
BIN
app/src/main/res/mipmap-mdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 474 B |
BIN
app/src/main/res/mipmap-mdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 458 B |
BIN
app/src/main/res/mipmap-mdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 479 B |
BIN
app/src/main/res/mipmap-xhdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 726 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 754 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 730 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 800 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
app/src/main/res/mipmap-xxhdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 1007 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |