From dbaa2f4c2258470d464e40dcbc06a32629c3214a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=B8=86?= <3031465419@qq.com> Date: Thu, 13 Nov 2025 17:02:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=88=91=E7=9A=84?= =?UTF-8?q?=E6=B4=BE=E5=B8=81=E7=95=8C=E9=9D=A2ui=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=BC=BA=E7=9C=81=E5=9B=BE=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?bug=EF=BC=8C=E4=BB=A5=E5=8F=8A=E5=85=B6=E4=BB=96=E6=9B=B4?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除其他用户界面右上角的积分显示 修复暂停视频后点赞/关注视频动态,暂停图标会消失以及关注/取消关注、收藏/取消收藏视频动态时用户头像会闪烁 修复发布动态/评论时' " & < >符号会变为' " & < > 修复短视频/新闻/推荐界面点击评论图标打开评论弹框显示异常 删除我的界面点壁纸可以更换壁纸的功能 根据设计图调整我的派币界面,增加返回按钮,添加缺省图,优化标签间的切换 --- .../aiosman/ravenow/ui/composables/Moment.kt | 2 +- .../ui/index/tabs/moment/tabs/hot/Moment.kt | 2 +- .../tabs/moment/tabs/news/FullArticleModal.kt | 39 +- .../tabs/moment/tabs/news/NewsCommentModal.kt | 43 +- .../index/tabs/moment/tabs/news/NewsScreen.kt | 42 +- .../tabs/recommend/PostRecommendationItem.kt | 2 +- .../tabs/recommend/VideoRecommendationItem.kt | 2 +- .../ui/index/tabs/profile/ProfileV3.kt | 84 +- .../ui/index/tabs/shorts/ShortViewCompose.kt | 176 ++-- .../ravenow/ui/points/PointsBottomSheet.kt | 937 ++++++++++++++++-- .../java/com/aiosman/ravenow/ui/post/Post.kt | 4 +- .../java/com/aiosman/ravenow/utils/Utils.kt | 18 + .../main/res/mipmap-hdpi/group_427319679.png | Bin 0 -> 801 bytes .../main/res/mipmap-hdpi/icons_calendar.png | Bin 0 -> 631 bytes .../res/mipmap-hdpi/icons_credit_card.png | Bin 0 -> 572 bytes app/src/main/res/mipmap-hdpi/icons_task.png | Bin 0 -> 637 bytes app/src/main/res/mipmap-hdpi/icons_users.png | Bin 0 -> 644 bytes .../main/res/mipmap-mdpi/group_427319679.png | Bin 0 -> 495 bytes .../main/res/mipmap-mdpi/icons_calendar.png | Bin 0 -> 459 bytes .../res/mipmap-mdpi/icons_credit_card.png | Bin 0 -> 474 bytes app/src/main/res/mipmap-mdpi/icons_task.png | Bin 0 -> 458 bytes app/src/main/res/mipmap-mdpi/icons_users.png | Bin 0 -> 479 bytes .../main/res/mipmap-xhdpi/group_427319679.png | Bin 0 -> 726 bytes .../main/res/mipmap-xhdpi/icons_calendar.png | Bin 0 -> 754 bytes .../res/mipmap-xhdpi/icons_credit_card.png | Bin 0 -> 730 bytes app/src/main/res/mipmap-xhdpi/icons_task.png | Bin 0 -> 800 bytes app/src/main/res/mipmap-xhdpi/icons_users.png | Bin 0 -> 830 bytes .../res/mipmap-xxhdpi/group_427319679.png | Bin 0 -> 1065 bytes .../main/res/mipmap-xxhdpi/icons_calendar.png | Bin 0 -> 1035 bytes .../res/mipmap-xxhdpi/icons_credit_card.png | Bin 0 -> 1007 bytes app/src/main/res/mipmap-xxhdpi/icons_task.png | Bin 0 -> 1047 bytes .../main/res/mipmap-xxhdpi/icons_users.png | Bin 0 -> 1032 bytes .../res/mipmap-xxxhdpi/group_427319679.png | Bin 0 -> 1290 bytes .../res/mipmap-xxxhdpi/icons_calendar.png | Bin 0 -> 1204 bytes .../res/mipmap-xxxhdpi/icons_credit_card.png | Bin 0 -> 1260 bytes .../main/res/mipmap-xxxhdpi/icons_task.png | Bin 0 -> 1367 bytes .../main/res/mipmap-xxxhdpi/icons_users.png | Bin 0 -> 1438 bytes 37 files changed, 1082 insertions(+), 269 deletions(-) create mode 100644 app/src/main/res/mipmap-hdpi/group_427319679.png create mode 100644 app/src/main/res/mipmap-hdpi/icons_calendar.png create mode 100644 app/src/main/res/mipmap-hdpi/icons_credit_card.png create mode 100644 app/src/main/res/mipmap-hdpi/icons_task.png create mode 100644 app/src/main/res/mipmap-hdpi/icons_users.png create mode 100644 app/src/main/res/mipmap-mdpi/group_427319679.png create mode 100644 app/src/main/res/mipmap-mdpi/icons_calendar.png create mode 100644 app/src/main/res/mipmap-mdpi/icons_credit_card.png create mode 100644 app/src/main/res/mipmap-mdpi/icons_task.png create mode 100644 app/src/main/res/mipmap-mdpi/icons_users.png create mode 100644 app/src/main/res/mipmap-xhdpi/group_427319679.png create mode 100644 app/src/main/res/mipmap-xhdpi/icons_calendar.png create mode 100644 app/src/main/res/mipmap-xhdpi/icons_credit_card.png create mode 100644 app/src/main/res/mipmap-xhdpi/icons_task.png create mode 100644 app/src/main/res/mipmap-xhdpi/icons_users.png create mode 100644 app/src/main/res/mipmap-xxhdpi/group_427319679.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icons_calendar.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icons_credit_card.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icons_task.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icons_users.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/group_427319679.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icons_calendar.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icons_credit_card.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icons_task.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icons_users.png diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/Moment.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/Moment.kt index 05a026f..2d27d4b 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/Moment.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/Moment.kt @@ -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), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt index d7f74c0..a62aedd 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt @@ -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, diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/FullArticleModal.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/FullArticleModal.kt index 15d8c1d..34c83c8 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/FullArticleModal.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/FullArticleModal.kt @@ -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)) } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsCommentModal.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsCommentModal.kt index 8453000..8e0f4ff 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsCommentModal.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsCommentModal.kt @@ -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 } - ) - } + } + ) } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt index 3137ff9..f7fec19 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/news/NewsScreen.kt @@ -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), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/PostRecommendationItem.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/PostRecommendationItem.kt index 5f3e721..d1e0016 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/PostRecommendationItem.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/PostRecommendationItem.kt @@ -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), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/VideoRecommendationItem.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/VideoRecommendationItem.kt index 857b713..5225b01 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/VideoRecommendationItem.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/recommend/VideoRecommendationItem.kt @@ -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), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt index d70e6cc..0553cd8 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt @@ -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,48 +761,44 @@ 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( painter = painterResource(id = R.mipmap.menu_icon), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt index d1cd9e6..39d0018 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt @@ -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, - pauseIconVisibleState: MutableState, 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 = "用户头像" + ) + } } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/points/PointsBottomSheet.kt b/app/src/main/java/com/aiosman/ravenow/ui/points/PointsBottomSheet.kt index 5661431..a4d9862 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/points/PointsBottomSheet.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/points/PointsBottomSheet.kt @@ -2,11 +2,13 @@ package com.aiosman.ravenow.ui.points import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.padding @@ -18,11 +20,17 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -33,7 +41,22 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -64,37 +87,134 @@ fun PointsBottomSheet( LaunchedEffect(Unit) { PointsViewModel.initLoad() + // 确保弹窗始终展开 + sheetState.expand() + } + + // 监听状态变化,确保弹窗始终展开(防止拖拽关闭和滑动) + LaunchedEffect(sheetState.currentValue, sheetState.targetValue, sheetState.isVisible) { + // 如果弹窗被拖拽关闭或位置发生变化,立即重新展开 + if (!sheetState.isVisible || sheetState.targetValue != androidx.compose.material3.SheetValue.Expanded) { + kotlinx.coroutines.delay(10) // 短暂延迟确保状态更新 + sheetState.expand() + } } + val statusBarPadding = WindowInsets.systemBars.asPaddingValues() + val configuration = LocalConfiguration.current + val screenHeight = configuration.screenHeightDp.dp + val offsetY = screenHeight * 0.07f - statusBarPadding.calculateTopPadding() + ModalBottomSheet( - onDismissRequest = onClose, + onDismissRequest = onClose, // 允许通过代码关闭(如返回按钮) sheetState = sheetState, - containerColor = AppColors.background + containerColor = AppColors.background, + dragHandle = null // 移除拖动手柄 ) { Column( modifier = Modifier .fillMaxWidth() - .fillMaxHeight(0.9f) - .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxHeight(0.95f) + .offset(y = offsetY) + .padding( + start = 16.dp, + end = 16.dp, + bottom = 8.dp + ) ) { - // 头部 - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween + // 头部 - 使用 Box 实现绝对居中布局 + Box( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + contentAlignment = Alignment.Center ) { - Text(text = stringResource(R.string.my_pai_coin), color = AppColors.text, fontSize = 20.sp, fontWeight = FontWeight.Bold) + // 左上角返回按钮 - 圆形白色背景,渐变边框,深灰色 × Box( modifier = Modifier + .align(Alignment.CenterStart) + .size(32.dp) + .clip(CircleShape) + .drawWithCache { + val borderWidth = 1.dp.toPx() + // 使用线性渐变,从顶部浅灰到底部白色 + val gradientBrush = Brush.verticalGradient( + colors = listOf( + Color(0xFFE8E8E8), + Color(0xFFF0F0F0), + Color(0xFFF8F8F8), + AppColors.background + ) + ) + onDrawWithContent { + drawContent() + drawCircle( + brush = gradientBrush, + radius = size.width / 2 - borderWidth / 2, + center = androidx.compose.ui.geometry.Offset(size.width / 2, size.height / 2), + style = Stroke(width = borderWidth) + ) + } + } + .background(AppColors.background) + .noRippleClickable { onClose() }, + contentAlignment = Alignment.Center + ) { + Text( + text = "×", + color = AppColors.text, + fontSize = 24.sp, + fontWeight = FontWeight.Normal + ) + } + + // 中间标题 - 绝对居中 + Text( + text = stringResource(R.string.my_pai_coin), + color = AppColors.text, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + + // 右侧充值按钮 - 白色背景,紫色文字,圆角,渐变边框 + Box( + modifier = Modifier + .align(Alignment.CenterEnd) .clip(RoundedCornerShape(16.dp)) - .background(Color(0xFFF1E9FF)) - .clickable { onRecharge() } + .drawWithCache { + val borderWidth = 1.dp.toPx() + val gradientBrush = Brush.linearGradient( + colors = listOf( + Color(0xFFE8E8E8), + Color(0xFFF0F0F0), + Color(0xFFF8F8F8), + AppColors.background + ) + ) + onDrawWithContent { + drawContent() + drawRoundRect( + brush = gradientBrush, + topLeft = androidx.compose.ui.geometry.Offset(borderWidth / 2, borderWidth / 2), + size = androidx.compose.ui.geometry.Size( + size.width - borderWidth, + size.height - borderWidth + ), + cornerRadius = CornerRadius(16.dp.toPx()), + style = Stroke(width = borderWidth) + ) + } + } + .background(AppColors.background) + .noRippleClickable { onRecharge() } .padding(horizontal = 12.dp, vertical = 6.dp) ) { - Text(text = stringResource(R.string.recharge), color = Color(0xFF6B46C1), fontSize = 14.sp, fontWeight = FontWeight.W600, - modifier = Modifier - .clip(RoundedCornerShape(16.dp)) - .padding(0.dp) + Text( + text = stringResource(R.string.recharge), + color = Color(0xFF6B46C1), + fontSize = 14.sp, + fontWeight = FontWeight.W600 ) } } @@ -107,11 +227,22 @@ fun PointsBottomSheet( .fillMaxWidth() .clip(RoundedCornerShape(16.dp)) .background(AppColors.nonActive) - .padding(16.dp) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - Text(stringResource(R.string.current_balance), color = AppColors.secondaryText, fontSize = 14.sp) + // Current Balance 标签 - 居中 + Text( + text = stringResource(R.string.current_balance), + color = AppColors.secondaryText, + fontSize = 14.sp + ) Spacer(Modifier.height(8.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { + + // 余额数值 - 居中,图标在左侧 + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { Image( painter = painterResource(id = R.mipmap.paip_coin_img), contentDescription = "coin", @@ -127,14 +258,30 @@ fun PointsBottomSheet( fontWeight = FontWeight.Bold ) } + Spacer(Modifier.height(12.dp)) + + // Total Earned 和 Total Spent - 居中排列 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { - Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f)) { - Text(text = numberFormat.format(balanceState.value?.totalEarned ?: 0), color = AppColors.text, fontSize = 18.sp, fontWeight = FontWeight.W700) - Text(text = stringResource(R.string.total_earned), color = AppColors.secondaryText, fontSize = 12.sp) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.weight(1f) + ) { + Text( + text = numberFormat.format(balanceState.value?.totalEarned ?: 0), + color = AppColors.text, + fontSize = 18.sp, + fontWeight = FontWeight.W700 + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.total_earned), + color = AppColors.secondaryText, + fontSize = 12.sp + ) } Box( modifier = Modifier @@ -142,9 +289,22 @@ fun PointsBottomSheet( .background(AppColors.divider) .width(1.dp) ) - Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f)) { - Text(text = numberFormat.format(balanceState.value?.totalSpent ?: 0), color = AppColors.text, fontSize = 18.sp, fontWeight = FontWeight.W700) - Text(text = stringResource(R.string.total_spent), color = AppColors.secondaryText, fontSize = 12.sp) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.weight(1f) + ) { + Text( + text = numberFormat.format(balanceState.value?.totalSpent ?: 0), + color = AppColors.text, + fontSize = 18.sp, + fontWeight = FontWeight.W700 + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.total_spent), + color = AppColors.secondaryText, + fontSize = 12.sp + ) } } } @@ -152,27 +312,10 @@ fun PointsBottomSheet( Spacer(Modifier.height(12.dp)) // 分段切换 - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - TabItem( - text = stringResource(R.string.transaction_history), - isSelected = tab == 0, - onClick = { tab = 0 }, - modifier = Modifier.weight(1f) - ) - TabSpacer() - TabItem( - text = stringResource(R.string.how_to_earn), - isSelected = tab == 1, - onClick = { tab = 1 }, - modifier = Modifier.weight(1f) - ) - } + CustomTabBar( + selectedTab = tab, + onTabSelected = { tab = it } + ) Spacer(Modifier.height(8.dp)) @@ -184,22 +327,111 @@ fun PointsBottomSheet( hasNext = PointsViewModel.hasNext ) } else { - HowToEarnList() + HowToEarnList(onRecharge = onRecharge) } } } } @Composable -private fun SegmentItem(selected: Boolean, text: String, onClick: () -> Unit) { +private fun CustomTabBar( + selectedTab: Int, + onTabSelected: (Int) -> Unit +) { val AppColors = LocalAppTheme.current + val density = LocalDensity.current + + // 外层容器:灰色背景,圆角 Box( modifier = Modifier - .clip(RoundedCornerShape(20.dp)) - .background(if (selected) AppColors.background else Color.Transparent) - .padding(horizontal = 12.dp, vertical = 8.dp) + .fillMaxWidth() + .height(28.dp) + .clip(RoundedCornerShape(16.dp)) + .background(Color(0xFFE5E5E5)) ) { - Text(text = text, color = if (selected) AppColors.text else AppColors.secondaryText, fontSize = 14.sp, fontWeight = FontWeight.W600) + // 可滑动的白色背景 + val tabWidth = remember { mutableStateOf(0.dp) } + val gapWidth = 8.dp + val animatedOffset by animateDpAsState( + targetValue = if (selectedTab == 0) 0.dp else tabWidth.value + gapWidth, + animationSpec = tween(durationMillis = 300), + label = "tabOffset" + ) + + // 白色滑动背景(两侧圆角) + Box( + modifier = Modifier + .fillMaxHeight() + .width(tabWidth.value) + .offset(x = animatedOffset) + .clip(RoundedCornerShape(16.dp)) + .background(AppColors.background) + .border( + width = 1.dp, + color = Color(0xFFE5E5E5), + shape = RoundedCornerShape(16.dp) + ) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + ) { + // 交易历史标签(左侧) + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .onSizeChanged { size -> + tabWidth.value = with(density) { size.width.toDp() } + } + .noRippleClickable { onTabSelected(0) }, + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.transaction_history), + color = AppColors.text, + fontSize = 15.sp, + fontWeight = FontWeight.W600 + ) + } + + // 中间灰色填充区域 + Box( + modifier = Modifier + .width(gapWidth) + .fillMaxHeight() + ) + + // 如何赚取标签(右侧) + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .noRippleClickable { onTabSelected(1) }, + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.how_to_earn), + color = AppColors.text, + fontSize = 15.sp, + fontWeight = FontWeight.W600 + ) + } + } + } +} + +// 根据交易原因获取对应的图标资源ID +private fun getIconForReason(reason: String): Int { + return when (reason) { + PointService.ChangeReason.EARN_REGISTER -> R.mipmap.group_427319679 // 新用户注册奖励 + PointService.ChangeReason.EARN_DAILY -> R.mipmap.icons_calendar // 每日签到奖励 + PointService.ChangeReason.EARN_INVITE -> R.mipmap.icons_users // 邀请好友奖励 + PointService.ChangeReason.EARN_TASK -> R.mipmap.icons_task // 任务完成奖励 + PointService.ChangeReason.EARN_RECHARGE -> R.mipmap.icons_credit_card // 充值获得 + else -> R.mipmap.paip_coin_img // 其他类型使用默认图标 } } @@ -211,9 +443,25 @@ private fun PointsHistoryList( ) { val AppColors = LocalAppTheme.current val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) } + + // 创建 NestedScrollConnection 来阻止滚动事件传播到弹窗 + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource + ): Offset { + // 消费剩余的滚动事件,防止传播到 ModalBottomSheet 导致弹窗关闭 + return available + } + } + } LazyColumn( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .nestedScroll(nestedScrollConnection) ) { items(items) { item -> Row( @@ -224,8 +472,10 @@ private fun PointsHistoryList( horizontalArrangement = Arrangement.SpaceBetween ) { Row(verticalAlignment = Alignment.CenterVertically) { + // 根据交易类型显示对应图标 + val iconResId = getIconForReason(item.reason ?: "") Image( - painter = painterResource(id = R.mipmap.paip_coin_img), + painter = painterResource(id = iconResId), contentDescription = "reason", modifier = Modifier .size(40.dp) @@ -261,55 +511,552 @@ private fun PointsHistoryList( } } +// 图标类型枚举 +private enum class EarnIconType { + GIFT_BOX, // 新用户奖励 - 橙色礼物盒 + CALENDAR, // 每日签到 - 蓝色日历 + PEOPLE, // 邀请好友 - 蓝色人物 + CHECKMARK, // 完成任务 - 紫色对勾 + CARDS // 充值派币 - 紫色卡片 +} + +// 赚取方式数据类 +private data class EarnMethod( + val title: String, + val desc: String, + val reward: String, + val iconColor: Color, + val iconBackgroundColor: Color, + val iconType: EarnIconType, + val isClickable: Boolean = false, + val onClick: (() -> Unit)? = null +) + @Composable -private fun HowToEarnList() { +private fun HowToEarnList(onRecharge: () -> Unit) { val AppColors = LocalAppTheme.current - @Composable - fun RowItem(title: String, desc: String, amount: String) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Image( - painter = painterResource(id = R.mipmap.paip_coin_img), - contentDescription = "earn", - modifier = Modifier - .size(40.dp) - .clip(RoundedCornerShape(12.dp)) - ) - Spacer(Modifier.size(12.dp)) - Column { - Text(text = title, color = AppColors.text, fontSize = 16.sp, fontWeight = FontWeight.W600) - Spacer(Modifier.height(4.dp)) - Text(text = desc, color = AppColors.secondaryText, fontSize = 12.sp) - } + val earnMethods = listOf( + EarnMethod( + title = stringResource(R.string.new_user_reward), + desc = stringResource(R.string.new_user_reward_desc), + reward = "+500", + iconColor = Color(0xFFFF8D28), // RGB(255, 141, 40) + iconBackgroundColor = Color(0xFFFF8D28).copy(alpha = 0.06f), + iconType = EarnIconType.GIFT_BOX + ), + EarnMethod( + title = stringResource(R.string.daily_check_in), + desc = stringResource(R.string.daily_check_in_desc), + reward = "+10-50", + iconColor = Color(0xFF00C0E8), // RGB(0, 192, 232) + iconBackgroundColor = Color(0xFF00C0E8).copy(alpha = 0.06f), + iconType = EarnIconType.CALENDAR + ), + EarnMethod( + title = stringResource(R.string.invite_friends), + desc = stringResource(R.string.invite_friends_desc), + reward = "+100", + iconColor = Color(0xFF00C0E8), // RGB(0, 192, 232) + iconBackgroundColor = Color(0xFF00C0E8).copy(alpha = 0.06f), + iconType = EarnIconType.PEOPLE + ), + EarnMethod( + title = stringResource(R.string.complete_tasks), + desc = stringResource(R.string.complete_tasks_desc), + reward = "+20-200", + iconColor = Color(0xFFCB30E0), // RGB(203, 48, 224) + iconBackgroundColor = Color(0xFFCB30E0).copy(alpha = 0.06f), + iconType = EarnIconType.CHECKMARK + ), + EarnMethod( + title = stringResource(R.string.recharge_pai_coin), + desc = stringResource(R.string.recharge_pai_coin_desc), + reward = ">", + iconColor = Color(0xFF6155F5), // RGB(97, 85, 245) + iconBackgroundColor = Color(0xFF6155F5).copy(alpha = 0.06f), + iconType = EarnIconType.CARDS, + isClickable = true, + onClick = onRecharge + ) + ) + + // 创建 NestedScrollConnection 来阻止滚动事件传播到弹窗 + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource + ): Offset { + // 消费剩余的滚动事件,防止传播到 ModalBottomSheet 导致弹窗关闭 + return available } - Text(text = amount, color = Color(0xFF00C853), fontSize = 16.sp, fontWeight = FontWeight.Bold) } } - + LazyColumn( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .nestedScroll(nestedScrollConnection), + verticalArrangement = Arrangement.spacedBy(12.dp) ) { - item { - RowItem(stringResource(R.string.new_user_reward), stringResource(R.string.new_user_reward_desc), "+500") - } - item { - RowItem(stringResource(R.string.daily_check_in), stringResource(R.string.daily_check_in_desc), "+10-50") - } - item { - RowItem(stringResource(R.string.invite_friends), stringResource(R.string.invite_friends_desc), "+100") - } - item { - RowItem(stringResource(R.string.complete_tasks), stringResource(R.string.complete_tasks_desc), "+20-200") - } - item { - RowItem(stringResource(R.string.recharge_pai_coin), stringResource(R.string.recharge_pai_coin_desc), ">") + items(earnMethods) { method -> + EarnMethodCard( + method = method, + AppColors = AppColors + ) } } } + +@Composable +private fun EarnMethodCard( + method: EarnMethod, + AppColors: com.aiosman.ravenow.AppThemeData +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(AppColors.nonActive) + .then( + if (method.isClickable) { + Modifier.clickable { method.onClick?.invoke() } + } else { + Modifier + } + ) + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.weight(1f) + ) { + // 图标背景 + Box( + modifier = Modifier + .size(50.dp) + .clip(RoundedCornerShape(16.dp)) + .background(method.iconBackgroundColor), + contentAlignment = Alignment.Center + ) { + CustomIcon( + iconType = method.iconType, + color = method.iconColor + ) + } + Spacer(modifier = Modifier.size(12.dp)) + Column { + Text( + text = method.title, + color = AppColors.text, + fontSize = 16.sp, + fontWeight = FontWeight.W600 + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = method.desc, + color = AppColors.secondaryText, + fontSize = 12.sp + ) + } + } + + // 奖励或箭头 + if (method.reward == ">") { + Image( + painter = painterResource(id = R.drawable.rider_pro_back_icon), + contentDescription = "Navigate", + modifier = Modifier + .size(16.dp) + .rotate(180f), + colorFilter = ColorFilter.tint(AppColors.secondaryText) + ) + } else { + Text( + text = method.reward, + color = Color(0xFF00C853), + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Composable +private fun CustomIcon( + iconType: EarnIconType, + color: Color, + modifier: Modifier = Modifier +) { + val iconResId = when (iconType) { + EarnIconType.GIFT_BOX -> R.mipmap.group_427319679 + EarnIconType.CALENDAR -> R.mipmap.icons_calendar + EarnIconType.PEOPLE -> R.mipmap.icons_users + EarnIconType.CHECKMARK -> R.mipmap.icons_task + EarnIconType.CARDS -> R.mipmap.icons_credit_card + } + + Image( + painter = painterResource(id = iconResId), + contentDescription = null, + modifier = modifier + ) +} + +@Composable +private fun GiftBoxIcon(color: Color) { + androidx.compose.foundation.Canvas( + modifier = Modifier.fillMaxSize() + ) { + val strokeWidth = 2.dp.toPx() + val size = size.width + + // 礼物盒主体 (17.1 x 14.0) + val boxWidth = size * 0.71f + val boxHeight = size * 0.58f + val boxLeft = (size - boxWidth) / 2 + val boxTop = size * 0.14f + + // 填充背景 + drawRect( + color = color.copy(alpha = 0.2f), + topLeft = androidx.compose.ui.geometry.Offset(boxLeft, boxTop), + size = androidx.compose.ui.geometry.Size(boxWidth, boxHeight) + ) + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(boxLeft, boxTop), + size = androidx.compose.ui.geometry.Size(boxWidth, boxHeight), + style = Stroke(width = strokeWidth) + ) + + // 顶部横线 (17.1 x 2.0) + val lineWidth = boxWidth + val lineHeight = size * 0.08f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(boxLeft, boxTop), + size = androidx.compose.ui.geometry.Size(lineWidth, lineHeight) + ) + + // 左侧竖线 (2.0 x 14.0) + val verticalLineWidth = size * 0.08f + val verticalLineHeight = boxHeight + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(boxLeft, boxTop), + size = androidx.compose.ui.geometry.Size(verticalLineWidth, verticalLineHeight) + ) + + // 顶部两个小盒子 (7.2 x 5.3) + val smallBoxWidth = size * 0.3f + val smallBoxHeight = size * 0.22f + val smallBoxY = size * 0.29f + + // 右侧小盒子 + val rightBoxLeft = boxLeft + boxWidth * 0.21f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(rightBoxLeft, smallBoxY), + size = androidx.compose.ui.geometry.Size(smallBoxWidth, smallBoxHeight), + style = Stroke(width = strokeWidth) + ) + + // 左侧小盒子 + val leftBoxLeft = boxLeft - boxWidth * 0.21f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(leftBoxLeft, smallBoxY), + size = androidx.compose.ui.geometry.Size(smallBoxWidth, smallBoxHeight), + style = Stroke(width = strokeWidth) + ) + } +} + +@Composable +private fun CalendarIcon(color: Color) { + androidx.compose.foundation.Canvas( + modifier = Modifier.fillMaxSize() + ) { + val strokeWidth = 2.dp.toPx() + val size = size.width + + // 日历主体 (19.4 x 18.2) + val calendarWidth = size * 0.81f + val calendarHeight = size * 0.76f + val calendarLeft = (size - calendarWidth) / 2 + val calendarTop = size * 0.05f + + // 顶部横条 (19.1 x 5.8) + val topBarWidth = size * 0.8f + val topBarHeight = size * 0.24f + val topBarTop = size * 0.22f + + // 填充背景 + drawRect( + color = color.copy(alpha = 0.2f), + topLeft = androidx.compose.ui.geometry.Offset(calendarLeft, topBarTop), + size = androidx.compose.ui.geometry.Size(topBarWidth, topBarHeight) + ) + + // 日历主体填充 + drawRect( + color = color.copy(alpha = 0.2f), + topLeft = androidx.compose.ui.geometry.Offset(calendarLeft, calendarTop), + size = androidx.compose.ui.geometry.Size(calendarWidth, calendarHeight) + ) + + // 日历主体描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(calendarLeft, calendarTop), + size = androidx.compose.ui.geometry.Size(calendarWidth, calendarHeight), + style = Stroke(width = strokeWidth) + ) + + // 顶部横条描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(calendarLeft, topBarTop), + size = androidx.compose.ui.geometry.Size(topBarWidth, topBarHeight), + style = Stroke(width = strokeWidth) + ) + + // 顶部横线 (19.1 x 2.0) + val lineWidth = topBarWidth + val lineHeight = size * 0.1f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(calendarLeft, topBarTop + topBarHeight - lineHeight), + size = androidx.compose.ui.geometry.Size(lineWidth, lineHeight) + ) + + // 两个小圆点 (2.0 x 4.0) + val dotWidth = size * 0.08f + val dotHeight = size * 0.17f + val dotY = size * 0.17f + + // 左侧圆点 + val leftDotLeft = calendarLeft - size * 0.19f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(leftDotLeft, dotY), + size = androidx.compose.ui.geometry.Size(dotWidth, dotHeight) + ) + + // 右侧圆点 + val rightDotLeft = calendarLeft + calendarWidth - dotWidth + size * 0.175f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(rightDotLeft, dotY), + size = androidx.compose.ui.geometry.Size(dotWidth, dotHeight) + ) + } +} + +@Composable +private fun PeopleIcon(color: Color) { + androidx.compose.foundation.Canvas( + modifier = Modifier.fillMaxSize() + ) { + val strokeWidth = 2.dp.toPx() + val size = size.width + + // 左侧人物头部 (8.0 x 8.0) + val headSize = size * 0.33f + val headLeft = size * 0.1f + val headTop = size * 0.23f + + // 填充背景 + drawRect( + color = color.copy(alpha = 0.2f), + topLeft = androidx.compose.ui.geometry.Offset(headLeft, headTop), + size = androidx.compose.ui.geometry.Size(headSize, headSize) + ) + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(headLeft, headTop), + size = androidx.compose.ui.geometry.Size(headSize, headSize), + style = Stroke(width = strokeWidth) + ) + + // 左侧身体 (3.0 x 6.0) + val bodyWidth = size * 0.125f + val bodyHeight = size * 0.25f + val leftBodyLeft = headLeft + headSize + size * 0.1f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(leftBodyLeft, headTop), + size = androidx.compose.ui.geometry.Size(bodyWidth, bodyHeight), + style = Stroke(width = strokeWidth) + ) + + // 右侧人物头部 (14.0 x 8.0) + val rightHeadWidth = size * 0.58f + val rightHeadHeight = headSize + val rightHeadLeft = headLeft + val rightHeadTop = headTop + headSize + size * 0.1f + + // 填充背景 + drawRect( + color = color.copy(alpha = 0.2f), + topLeft = androidx.compose.ui.geometry.Offset(rightHeadLeft, rightHeadTop), + size = androidx.compose.ui.geometry.Size(rightHeadWidth, rightHeadHeight) + ) + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(rightHeadLeft, rightHeadTop), + size = androidx.compose.ui.geometry.Size(rightHeadWidth, rightHeadHeight), + style = Stroke(width = strokeWidth) + ) + + // 右侧身体 (3.0 x 4.9) + val rightBodyWidth = bodyWidth + val rightBodyHeight = size * 0.2f + val rightBodyLeft = rightHeadLeft + rightHeadWidth - rightBodyWidth + size * 0.1f + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(rightBodyLeft, rightHeadTop), + size = androidx.compose.ui.geometry.Size(rightBodyWidth, rightBodyHeight), + style = Stroke(width = strokeWidth) + ) + } +} + +@Composable +private fun CheckmarkIcon(color: Color) { + androidx.compose.foundation.Canvas( + modifier = Modifier.fillMaxSize() + ) { + val strokeWidth = 2.dp.toPx() + val size = size.width + + // 方框 (15.6 x 17.1) + val boxWidth = size * 0.65f + val boxHeight = size * 0.71f + val boxLeft = (size - boxWidth) / 2 + val boxTop = size * 0.05f + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(boxLeft, boxTop), + size = androidx.compose.ui.geometry.Size(boxWidth, boxHeight), + style = Stroke(width = strokeWidth) + ) + + // 顶部横条 (9.0 x 4.7) + val topBarWidth = size * 0.375f + val topBarHeight = size * 0.2f + val topBarLeft = (size - topBarWidth) / 2 + val topBarTop = size * 0.31f + + // 填充背景 + drawRect( + color = color.copy(alpha = 0.2f), + topLeft = androidx.compose.ui.geometry.Offset(topBarLeft, topBarTop), + size = androidx.compose.ui.geometry.Size(topBarWidth, topBarHeight) + ) + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(topBarLeft, topBarTop), + size = androidx.compose.ui.geometry.Size(topBarWidth, topBarHeight), + style = Stroke(width = strokeWidth) + ) + + // 对勾圆圈 (6.0 x 6.0) + val circleSize = size * 0.25f + val circleLeft = (size - circleSize) / 2 + val circleTop = boxTop + boxHeight * 0.12f + + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(circleLeft, circleTop), + size = androidx.compose.ui.geometry.Size(circleSize, circleSize), + style = Stroke(width = strokeWidth) + ) + } +} + +@Composable +private fun CardsIcon(color: Color) { + androidx.compose.foundation.Canvas( + modifier = Modifier.fillMaxSize() + ) { + val strokeWidth = 2.dp.toPx() + val size = size.width + + // 底层卡片 (16.1 x 13.0) + val bottomCardWidth = size * 0.67f + val bottomCardHeight = size * 0.54f + val bottomCardLeft = size * 0.067f + val bottomCardTop = size * 0.08f + + // 填充背景 + drawRect( + color = color.copy(alpha = 0.2f), + topLeft = androidx.compose.ui.geometry.Offset(bottomCardLeft, bottomCardTop), + size = androidx.compose.ui.geometry.Size(bottomCardWidth, bottomCardHeight) + ) + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(bottomCardLeft, bottomCardTop), + size = androidx.compose.ui.geometry.Size(bottomCardWidth, bottomCardHeight), + style = Stroke(width = strokeWidth) + ) + + // 顶层卡片 (15.8 x 11.8) + val topCardWidth = size * 0.66f + val topCardHeight = size * 0.49f + val topCardLeft = size * 0.075f + val topCardTop = size * 0.1f + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(topCardLeft, topCardTop), + size = androidx.compose.ui.geometry.Size(topCardWidth, topCardHeight), + style = Stroke(width = strokeWidth) + ) + + // 顶部横条 (16.1 x 4.6) + val topBarWidth = bottomCardWidth + val topBarHeight = size * 0.19f + val topBarLeft = bottomCardLeft + val topBarTop = bottomCardTop + + // 描边 + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(topBarLeft, topBarTop), + size = androidx.compose.ui.geometry.Size(topBarWidth, topBarHeight), + style = Stroke(width = strokeWidth) + ) + + // 小圆点 (2.7 x 2.0) + val dotWidth = size * 0.11f + val dotHeight = size * 0.08f + val dotLeft = bottomCardLeft + bottomCardWidth * 0.32f + val dotTop = bottomCardTop + bottomCardHeight * 0.2f + + drawRect( + color = color, + topLeft = androidx.compose.ui.geometry.Offset(dotLeft, dotTop), + size = androidx.compose.ui.geometry.Size(dotWidth, dotHeight) + ) + } +} diff --git a/app/src/main/java/com/aiosman/ravenow/ui/post/Post.kt b/app/src/main/java/com/aiosman/ravenow/ui/post/Post.kt index c022305..74e148f 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/post/Post.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/post/Post.kt @@ -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, diff --git a/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt b/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt index f47c465..b5d03e1 100644 --- a/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt +++ b/app/src/main/java/com/aiosman/ravenow/utils/Utils.kt @@ -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("'", "'") + } } \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/group_427319679.png b/app/src/main/res/mipmap-hdpi/group_427319679.png new file mode 100644 index 0000000000000000000000000000000000000000..cc7b0cb6ba1743329cbf03a1d2704b67fd7cdaf1 GIT binary patch literal 801 zcmV++1K#|JP)Px#Fi=cXMMrQ<_xJbr>)-!}Blq|B_ww!c@$2{Z_y3J3 z`P{?)mp%H_wEVJh`^A>{=h^+CQ29(l6aWAK26R$RQviR54uF8U56El)00MnUL_t(Y z$DNkJZqq;zhSN&i^DI1ppJpsnWw<%7FgPx`p@vFubtwtA3fcqL2nFRz#FWHxc7#ZONbiotfRtPVa1ZiuHSz%d!;2FZFX799Zh%z-tE_KpfOu7UEKe z3t}zUa1b%UX*mXT2O(H<07nHYu9ia&!B(*1B3ft!k8mKk%uIpxr5?>CQe(K7X|7fC z2Ext6{oXTx6C6B=ODpc&riz;&Y8O~IBDj#%pBfHr@|`EN4)%M`o%~;ObL$4;a=50V z@^4q)LL9@Eh-P4Ja)mXgJ(<%d8UvKHr_0E^sNP=D|woQdm#{=k4{TY7|- z^hQK2_0*>?6o+Lo&p~l_v3W-TpQ#x+l9wHQ->iwIo1av8o#$T+cU>0Yc+1O);U=rx$6=Gc zSC)Hg63DI|R?BhI;n)|iTHe2wdoLHx_3wgiAC%3DYW&)8?ByK03*|4sdGzuR%8075 z^m6s)Jw1+_4rfy1a(IxxjvgCMtjAT$@qetZ-loU*OI^>lG&S5>7XKefBhmEoy@7%*n*4s2XbSOuj-dEYHhWApUdp;P6cCdCrdGZ1c%37d+f#e~@+a<>`nL*}rhOe@S#d z#14ttJ3Qb#ZlUb2l5W4o{(i0DhJk*=Hi}8CpG4bF8ZHywi)0VGoK-|`znh5uId!)^ f$o^S!F-!gdBGmqxyK_!500000NkvXXu0mjfIzo(3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/icons_calendar.png b/app/src/main/res/mipmap-hdpi/icons_calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..ce8d4504edafe746d00fb5a9ff7c87be60e445f5 GIT binary patch literal 631 zcmV--0*L*IP)Px#Fi=cXMF7n2M{rC4!sh_N=>XjI0Kn(~z~}(L>Hxs$ z0Kn(~!s!6P=m5m*0Kw=0!RG+L=u?HD{r~^~4|GyaQx5?P!W_0K@=UDK>q`AaySo_3qwdnI_&CxjJNxkanWsR05UsC^tmRkObK#bLz8S~K5eEI`Kb&&?VtJOWE ziC+|E5u_X`P!}SIZ$z%)1VVhO0`z_8IU?KO2k?fX1vqfTD?y>N3j-9rg6~}T3EKH^ zoK}pYcIZfHMkuQG&!sd&#gEGdMZpY07d;3MrEplnP~&@9$~xSksFy`s7;0tEDvtPo zfJigZzBmO1zcO0E*QpuHr~>G&yZn(+8~^XBk&LQ+6jeY*U3@>~;Yvchr#Mm4l5Z-` zRFO}oT5I78&e$`+ZGD(i4zc2J7Pz!8KjkO#{WO=V@QkStD?X0S)82;erc+#p|9AV6 zE_+hhpcNoeDI*m{QhB8nYEsE3H4n5lgw)W`S|eJMRjv`*HCp3`OlD16$Uki*{c&H> RPmBNn002ovPDHLkV1h=W73BZ` literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/icons_credit_card.png b/app/src/main/res/mipmap-hdpi/icons_credit_card.png new file mode 100644 index 0000000000000000000000000000000000000000..99cc7513cc983da5d63047a800bdc3efd222ccc9 GIT binary patch literal 572 zcmV-C0>k}@P)Px#Bv4FLMP^n1M{rDGRrgs(|6o=2VN~^ERQX_3_F+}^ zVO92ERQ6$2^?CbW;s5{u3v^OWQx5NklzK%A{(BfJ}~T8i^DcpolX^ zwhD$RB*O3c z6Jjl=-?;b5K<`mikIpegRuE8Vr=Bj?LOdi8K#Jn?GS;Hke!otlBG$8wwJEd>>MYAWb=MS2lPB zh*ip1MUhord4-x)@>$ITuMJV{O8l-h;x$>Uc5Ss1OO2nph5Q2TD9FB8ETj7X0000< KMNUMnLSTX=bp4$G literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/icons_task.png b/app/src/main/res/mipmap-hdpi/icons_task.png new file mode 100644 index 0000000000000000000000000000000000000000..1baac4a18eccf18254643f965fc88c60ecba2560 GIT binary patch literal 637 zcmV-@0)qXCP)Px#El^BUMa(nZM{rEkEY`~~;LR}K$}ZpY9`nmD;L0xD z%P`=|FW|~8-^no8$}Zo_FyNvE>=*z501k9gPE!v62;@F&1HLVcrAr=JP@I{)0005h zNkl#;A~P8m>LHMT?~nF(hy*9W-uXfAPkx?8CMtL z;N)P~j0?eqxc7h1z z#E}gP(gOz!Q3XD>0m1hPEj@w*8WPaj1p)aY3pw|Oui%;q8P z?(@|APc=G5dlCKlc+(2Wo8!uZuK zY$?!i4Dj;Ea|RnEin5A3dX!b7+RqXw7Z{)?1i)_^<-+f)MnSY(ZVA*6WX<7&{HKrZ zx~Q3~$*T;pt3`fClushQlqOol7hX&M#%p4^W$6!Wm5o<{dOh)#xfHULXoRN+a+6*? zhu?2u*2E@oU2$j(R40Llv&2t>`J?B$?aD3XUJjH<)YI!FcX%3#CdXmkLfzw3Vk!FGX!p<`=cmm2cEMn6)8B4GlHHC7Ue9ng;C}W9Oi6{P^2yCR@nTtcCmq X&V$f!5;msf00000NkvXXu0mjfKW!d6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/icons_users.png b/app/src/main/res/mipmap-hdpi/icons_users.png new file mode 100644 index 0000000000000000000000000000000000000000..1d88d17937493613d1c81287776e4d7da4ce35ec GIT binary patch literal 644 zcmV-~0(Px#Do{*RMF7n1M{rC4%Dw>H^#IAp0LZfd$g}{+vjE7q z0LZlf$+ZBfAb%*1Qfd_O5oltFGCGv!D5XOYjw~HAI09{?A)`lm zNa<08hAcfYXb66fs_xE-?wu7x?AvC`Rv5Fw5gx{Ipipt#?L50)oI^RP^*W3_;@KT~ za^wscW6M~A8p^-tesYwMytEQfqZo_KTmyip2ivX zRQs~Vji=c=+9@D~`P6P@HD=wP_=a*uMGRtKo8=3MiMrT3t~VUSgCydm7_FjrI^5tY z_OcaziiN1s_YGsWSc+bW&p{OT0>g4{7ZUs|2qK9uA!4r|VDeKXh{Oz9;Ri+#bJL&W ze?al}IQ?dT&T-$tcX0|9(QAe&u9{zgM{on0Ny6j11gI0h@rHoV*N#&JRs7WQC)I8AI5K==!YmI14R=PlF*JzEO eU@&9SLVf}61;Jqi=syqu0000Px#Do{*RMMrQ<|A!*~jU@N*>-YEf|Bfm7+`{|5iT$Zw z{hLMnv~~K4W=>Pj2gK%wR&!P@VxPT1a=XYnbi&< zA$*x(7MAI3_Y85|kpzv<$ObK}np&c)BtTJ-jmc63K&ItnQ)HC{K&RCt8i%4Sc^_9) z1SEBd`$Z80futRE$(D0~-O;K=MS(Bw36}ClA}gJwPY2$$i|L4qb>BZ_d<6^hV)(1MpSpq}N;Vt1Oh3KqTZyFuXhgJFD&vi$p!@)OZwEa_~3D14E0 z;b59XQRqa`RZgr$Tm_LeOOn`4lBrG%J&REt5^YrLI>+n!K9Tf%hv<+$WTBJ2@j3n| zpT;*2u)N8#*OAaSn=ov)u3Co{%&@V=277*^{|l2{HNnc0HGbG$ox^@Bn1=)WKT`f0 lMA8^FzFK*fJ`br;`2#A3Mq`$u1^@s6002ovPDHLkV1g2^-o5|; literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/icons_calendar.png b/app/src/main/res/mipmap-mdpi/icons_calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..3f4313689b06b98b2ed66dab1e8eb27a632c61f9 GIT binary patch literal 459 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaNL1AIbU8P2?CIPiku&~t|0|3Qq)9~ln3bT9A$i5z?Z5`mBq#*tS*5xcGF zhk@D!N`m}?`R@iY@8X!pV4wD}y=0*7nhMXjv% z|Nn1YEmM1KB|N3__UtV$58Jx(EGYPMs`7`%dp6-Ucl@963cOV=V&*e5VTi6{_|9bZ zx4h>cQ-HvRiSOs1NvwA)=eWS~Lh%8^RK5bM1C7@i=f6>(`dfMCj<1uCRJdio?VMxw zMyzr|q|(mY9X`SrTW!QnC$8yvDOAL9M1!d<`_$W=A)3pba#-tAye3cRo?;k0qb264 z$i3D_a*}h7y0JLTN%=1wu;ZMAVEIbXReTvA(-jH?8rUb#ZEy{KFvmsvdoWXy_VY`- z)*jD%FEZovqK9WfJS(pjr60SOyZ8F6rn|GGAIje@_beo)G#qG2~lf z=vT3%&JdtRzLFrnV15RBj{SMei@RT`d0&az^!GSWhSAW|#WBRoqaib!MS>p- zw_!s^3Y$m*$7F%DgacC;y*dmN7&=^-ma#~CFm%n|Bw;AGAbI@81x;mb7Z zs6tQdp$U!*yIGW-naViL*qGl4o#A0&oSv7F@kP()qv+>*jRz*PzIgR~uT4-~+@qg) zVzXFYd$StsWs7Zks>^)+>bFmy`Z7#5N5mP=Wsd&zj&;|OQ`6nGtgKGe-eX0$}J<;+i1nvH)- zJn$;8n5=QmrPo;c!5NDko2Aa3=`^-}tjACwCb@%Ka!V4YjvfcY$DG2&D_HB60)v&o M)78&qol`;+04=byt^fc4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/icons_task.png b/app/src/main/res/mipmap-mdpi/icons_task.png new file mode 100644 index 0000000000000000000000000000000000000000..ca4596dad3be1dc08afc3f7e27b3d9c9dc72695f GIT binary patch literal 458 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)ISkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?nV(;1l9{#`vy#fzNf#8>jW}pEh`KTK~Z*{rjhYq~4vg20)R!KsHePxZ!n( z0`rum4M4s8B|(0{{0!__4-Ymk)wT1R_EGNA^GACp1Fewtba4!^IGrrO!l=l~W|0uY z!fn`aR5GAJ$%n~TMu~Y+!T|;WPDUS2w+$@J%x1bgH%uC~7;tDMOip0(GDu);;&Hm6 zDk90U!P~${BVl^NAzeoHIfnoL?>0{ey!=_7gUR5i<)g)E=eGQ;VT_$){;&SonVG@= z>z$`R`1kqV%^Ma^U;kL&Z;UBjCj8|)dKo079LG9;YscCno1=a{>nvAw01(TtN% z+=ufYzr&)Z+W63M?AUC4j)|dsV&mos2cvWt8O%eY?r)VyeGCjk22WQ%mvv4FO#pBSx+wqv literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/icons_users.png b/app/src/main/res/mipmap-mdpi/icons_users.png new file mode 100644 index 0000000000000000000000000000000000000000..922f2031168703f3915f32e2f2c58a81557e968c GIT binary patch literal 479 zcmV<50U-W~P)Px#Fi=cXMF7n1M{rC4%Dw>8@c_!a0Q>&{$g=>+v;fJ* z0LZie$g}{+wE)Su0Lirg%F+PHv)e5#4gdfE4|GyaQx5hNCJxn zkQN3(0c;YCKnVkE63l`OSWN<&#UPAb!jM4#y96Uc0Covh1_SI8%nXEq`}`WL;@toL z|KE_m0#oArUzht|@_#m9NYr0a(*4e{z5qkwzmt;6PlopsFeK&!CH^z?f52vx_IJMZ z3os=7ftLTlW;xionH5+(a{Olr14%%;?Emx|Sj1ob2Z0rAS!lL%y+I7 VVZsn4vr7N~002ovPDHLkV1nC_!9M^1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/group_427319679.png b/app/src/main/res/mipmap-xhdpi/group_427319679.png new file mode 100644 index 0000000000000000000000000000000000000000..befebcd271dda0d3f437d40d850c20fde7de4935 GIT binary patch literal 726 zcmV;{0xA88P)Px#Ay7@@ap&X_y3J3`Q60&%AowXeEp(Q z{+K@glQRy6I~@Q300MMUPE!C5kt^Uo0006-Nklmz`3`fhjO^*?dlxswbd$ruA=ZZ-6wIb?kc6V_SV6k@=VoS*!@FwDC#>;+t-pDz; z-iF#jh&_gO#x4^OG0~IROJss=hAh!yi1=u00=p3n!ypw@8e&Dw1Uw_EhREqbPDVVN zpo1ZDOHP`AZ$uwMO7Pw{|LK2^eol31S=>!|CIxV> z3YW5Q!7^u1A-~_(g%hPp83m>lg|JR{yMhnoqFz zQ#I>UO=`@JE#?dGZ}Dj6%k9)^owMXnz4-%dHpO;kwT`2g_15;l8GHS-HuO$;YYH{u zmJxE-6e`MhMmpDV|bXH@lyB+6M++(DkXIv`3fFWMu%#IiT0YS`L>lj+ASpWb407*qo IM6N<$g84LBDgXcg literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/icons_calendar.png b/app/src/main/res/mipmap-xhdpi/icons_calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..6fe64e0ab948b80cb8169b5b612627e764dfd97e GIT binary patch literal 754 zcmVPx#Cs0gOMF7n1M{rC4!sh_M=m6650Kw=0!RG+L=m5d! z0K(@0z~}(O=>Wj!P6Qv50000CbW%=J4*)-92itPNmsRU1@kkyO0006}Nkl5gg5QfL8>U{O!fPO#-^ed-=XhYi^B*+g4EhT~KP#Q2f^iT-JE%m`r3ZeL%(vm~S z*@s+m%PGOOth_%evSh{5YBRgjHmAO)70koV?5=iahL#F)H%Oz6plCydu}?S1(E=_p z3aS8WUm(cNeo9OL*Ea!F2F&((;M)UE3J^(rBKtMU)`=t`jDsLa2;(3~6hab&9B^eoKn)pClLNjCa5TW; zr%pDgZ=F~HbGXa~XXt2vs%A0D28Z})<>o8~UaBtM+Svc>VXEyDvpq_ROvD@%9UGRp zJ2r}2sX`>)#mg*k2N-@wln4F#2MmFqBL4QY0C;UgEU=y|={4{n0oDLE>=1k2v)*0? zti?N?7izY9su^I%E31o52AD@*u}}QEWOrex0xxjR0{u6N!u%-SaPBm^V1TQ!Ks{io*)r&_2Fdp#`Ke>GDY{yvJ?f?J)07*qoM6N<$f}y8L0RR91 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/icons_credit_card.png b/app/src/main/res/mipmap-xhdpi/icons_credit_card.png new file mode 100644 index 0000000000000000000000000000000000000000..bccfcc81486ee15b8369659765342eaef4b6db07 GIT binary patch literal 730 zcmV<00ww*4P)Px#Cs0gOMP^n1M{rDGRrh5~|6x`3U{v;DRQF(1_Fz=? zWK;NHRQ6z1^b2rt0000CbW%=J4*)+0Ysyiqy=cI{R0y4KrD9gAIM@6EJS>0kwStjL{o^M;!%hYP)L!+v$6;tXd!|K zmZEVr^P}9&;l7P==ZzZCsWvcQ@{Tb3=0T4X`Uo_M5mU|q>Dho!LTd_i2nq8e2+fxS zsO9^j4uFgxFleC3KnH~|C19uk%?EK%ipV=K)PPn8Iw*!&1BNQlY5z?p(K31CgZodh4~JYYhc2O@l6@PPIU;QO(kToocG#6~e) zP)I+XK|h=oGC!8bKwMMcDwvODBEQqk$*mV{d*8N`XTtr|8xA_qxsZJxXT!lTJ_J4w zJ`qF8;zac!dSu&nrVu4Y1mn2q?ZjDqZJp`+3=PhM3$;dpy@r)_3Is@1(sqOpGkJ3J zT+r@*=f-KG8vV&z#Fuc;fjF9wB8!jVpc~6_eqw+Ik6cjVg2wodpw!A$%Hl2$9O5$% z#1Ib~Kwl_`rW^^(S=Jcw3vjUH52@c<(;ET+_+Y>T+YB(|fk_4M{`bBWVmjT2rEt)N zPk~h$Z>Vj}%Ij3x&tE{?B8pk6QooAUm}Onwd3A#*ukLOK{9pE<{I%f)BYMS_M z8N2agx0d|omE9im8*p}u&%Z?AUsJFzLikrLuK4^i$83K4qjX*L2D5pC-TG@q82|tP M07*qoM6N<$f^?5V(*OVf literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/icons_task.png b/app/src/main/res/mipmap-xhdpi/icons_task.png new file mode 100644 index 0000000000000000000000000000000000000000..9af007ec75c4ca1f8a0ebe76d7afc4a58fb83ad0 GIT binary patch literal 800 zcmV+*1K<3KP)Px#Do{*RMMrQ<%ro88Fxty7;KeS_&N1K2F5Jv7-ODfF z%P-){FW}2B-peoG%P`>S|IeBL000eiQchC<4+i86J}6PA$cJl|4sw$80007eNklRu0cq^7Z7nl)`4URf)C(=cp5y2NDev0i-+8@uyYVmcG=Cr zn~cmsgk2%{0DJHtgT|=!E_P3kaVFDFS4IC2NJ9UpkN&FaTrMVSzQ$6jeJ$nmvgX+v zs%a6c%H9;$5Ejc)n#dlnA*#$oB*&MTQF)3(p-L2ohMXE_C{<_%VkRtxR0*G=WTF^a zq>x|3F5`hbB}W6FEW-FOPh%8df);`bf)auZf)s)bf((q)fC*ZXBv7USc`dNFD7Ad9 z1vFXR0i~d!+e8|`jsWQ4fa1$|2prAKrT?0Q;9pbd65t#IZ2Hh!59Pw|h8frpt{f@A z7fzj+fGHXAk^-*9*9Q|Y8D>WTVYV9C0N;oHe}F%2q3-~vHna>DuAntNNb5b@hXn)R z!-@Dr`poJ^;93C1XsY{;0B#I`D<^B(lR9&%d=3ZjSkmqcKqr~Uj|cb=@Cj}W7Hk+S z>5DHC=%ImG2xx#$1jGV)WNZx&Y=!%6Mp8Jy4u|4_S+${Y0N?#9Ji2Fi3vSOdeT0d0ZH!&Ui?w6H3++#S+_TCHN%TFBL^fPx*Za`Ogq)ySPEh6WNR z_8c9!K^$Z;)lnxO3Alpe*sp>Q2L_-nE|KM*?H``kZvR>YNT1$Am*Na0mAV&)fkGz< z^o-YT@i0Dq#Yq9_(xI^3>~=k`+iiYxkhR7@RSiHce~1NEg<8T@=rtd$4CSK~x?{8u zMkv;E#1KYkILIvxxoM)eW#q;WYl4s7yy8r7Y6D(*Pm51~M96#wNxcYQh|>F?TCyH< eGTwj8&qdFFz&aE>GXPWo0000P)Px#Cs0gOMMrQ<0L<9^8m@T0LJkE$+ZBIS{sAmN!9y$*evDK|v}zYe5k*m#L=`M7kt~GRV2MB- zQKyclv^4LJ;?7p$_(y#21^OMVD7ugDy?5`P-<@SG$=>NgLEYz`RW5r6R;XKw*Z}~C zu%SxAp3@4seU-LgEC+1)bz97qkG; zDEzgBvED#`_OmQs3$HjzfGPNh2L#|Q;-g;U3~{J{+1L!+K{;~WrjNV**f1k-f>*B6 zDPBU|$15{%hCejW!fOzb^27*SU{3=LlmM^v!vr9@u@C`X>jwt_3gFL8ngP`H!|e3l z+-ZpPgBaG&Dy(09{9^{9`NBOUE?gb)y4QG%7!p$hxXA}E7U?bbDd`PD+m27--vI@H zU~nNP6+o>3x}1f0HYLW>Js4O>p%q%7Y>6-{<2BiYOIPC-RTpYZW+QV@?pKNHaKhH z?y1xWs(|?ObACK(emc}y0nV>sA^EZ|c)6T>!+`J=&89cyvzu-pQ4SPZhuiP<7M+Z{ z^j;&V>^0c!54*v#Zy50SZ79Fd-Qc;fMzd})U0CD5!9LQk4^8}I8T;TzU2L)b;gvRp z;2yv`|8m6VUm{dqgA{HAD1^tmY5|XTnNwSR|5H-#ME1JqFXu2K*%IXj7XSbN07*qo IM6N<$f`npo+5i9m literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/group_427319679.png b/app/src/main/res/mipmap-xxhdpi/group_427319679.png new file mode 100644 index 0000000000000000000000000000000000000000..bcd43f9c26a4f3db000dd826f7067d8b24541159 GIT binary patch literal 1065 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 R`uP9=002ovPDHLkV1m#p===Zx literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/icons_users.png b/app/src/main/res/mipmap-xxhdpi/icons_users.png new file mode 100644 index 0000000000000000000000000000000000000000..476a589e463249344e7b8ddb0b1d30b6cf7b92f6 GIT binary patch literal 1032 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 A4FCWD literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/icons_calendar.png b/app/src/main/res/mipmap-xxxhdpi/icons_calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..8b81f10bdf90c329f11108e46b567254063d8644 GIT binary patch literal 1204 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 Date: Thu, 13 Nov 2025 17:41:49 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=87=EE=86=BC=EE=98=B2PointsBottomShee?= =?UTF-8?q?t.kt=E6=B6=93=EE=85=A0=E6=AE=91=E9=96=B2=E5=B6=85=EE=98=B2?= =?UTF-8?q?=E7=80=B5=E7=85=8E=E5=8F=86=E9=8D=90=E8=8C=AC=E7=8D=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/deploymentTargetSelector.xml | 4 +-- .idea/gradle.xml | 31 +++++++++---------- .../ravenow/ui/points/PointsBottomSheet.kt | 5 --- gradle.properties | 3 +- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index fc1e130..b54b2fe 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,10 +4,10 @@