Merge pull request #65 from Kevinlinpr/nagisa

修复若干bug
This commit is contained in:
2025-11-11 14:23:16 +08:00
committed by GitHub
10 changed files with 243 additions and 119 deletions

View File

@@ -37,6 +37,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@@ -110,7 +111,11 @@ fun EditCommentBottomModal(
innerTextField()
if (text.isEmpty()) {
Text(
text = if (replyComment == null) "快来互动吧..." else "回复@${replyComment.name}",
text = if (replyComment == null) {
stringResource(R.string.post_comment_hint)
} else {
stringResource(R.string.reply_to_user, replyComment.name ?: "")
},
color = AppColors.text.copy(alpha = 0.3f), // 30%透明度
)
}

View File

@@ -111,6 +111,7 @@ fun MomentCard(
) {
MomentContentGroup(
momentEntity = momentEntity,
imageIndex = imageIndex,
onPageChange = { index -> imageIndex = index }
)
}
@@ -120,7 +121,6 @@ fun MomentCard(
onLikeClick = onLikeClick,
onAddComment = onAddComment,
onFavoriteClick = onFavoriteClick,
imageIndex = imageIndex,
onCommentClick = {
navController.navigateToPost(
momentEntity.id,
@@ -327,6 +327,11 @@ fun PostImageView(
images: List<MomentImageEntity>,
onPageChange: (Int) -> Unit = {}
) {
// 如果图片列表为空,不渲染任何内容
if (images.isEmpty()) {
return
}
val pagerState = rememberPagerState(pageCount = { images.size })
LaunchedEffect(pagerState.currentPage) {
onPageChange(pagerState.currentPage)
@@ -361,22 +366,83 @@ fun PostImageView(
@Composable
fun MomentContentGroup(
momentEntity: MomentEntity,
imageIndex: Int = 0,
onPageChange: (Int) -> Unit = {}
) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
if (momentEntity.relMoment != null) {
RelPostCard(
momentEntity = momentEntity.relMoment!!,
modifier = Modifier.background(Color(0xFFF8F8F8))
)
} else {
Box(
Column(
modifier = Modifier.fillMaxWidth()
) {
PostImageView(
images = momentEntity.images,
onPageChange = onPageChange
)
Box(
modifier = Modifier.fillMaxWidth()
) {
// 优先显示图片,如果没有图片则显示视频缩略图
if (momentEntity.images.isNotEmpty()) {
PostImageView(
images = momentEntity.images,
onPageChange = onPageChange
)
} else if (momentEntity.videos != null && momentEntity.videos.isNotEmpty()) {
// 显示视频缩略图
val firstVideo = momentEntity.videos.first()
val thumbnailUrl = firstVideo.thumbnailUrl ?: firstVideo.thumbnailDirectUrl
if (thumbnailUrl != null) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(
if (firstVideo.width != null && firstVideo.height != null && firstVideo.height > 0) {
firstVideo.width.toFloat() / firstVideo.height.toFloat()
} else {
1f
}
)
) {
CustomAsyncImage(
context = context,
imageUrl = thumbnailUrl,
contentDescription = "Video thumbnail",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
}
}
}
// 图片指示器:显示在图片下方、文案上方
if (momentEntity.images.size > 1) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
momentEntity.images.forEachIndexed { index, _ ->
Box(
modifier = Modifier
.size(4.dp)
.clip(CircleShape)
.background(
if (imageIndex == index) Color.Red else Color.Gray.copy(
alpha = 0.5f
)
)
.padding(1.dp)
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}
}
if (momentEntity.momentTextContent.isNotEmpty()) {
@@ -437,8 +503,7 @@ fun MomentBottomOperateRowGroup(
onAddComment: () -> Unit = {},
onCommentClick: () -> Unit = {},
onFavoriteClick: () -> Unit = {},
momentEntity: MomentEntity,
imageIndex: Int = 0
momentEntity: MomentEntity
) {
val lastClickTime = remember { mutableStateOf(0L) }
val clickDelay = 500L
@@ -475,93 +540,65 @@ fun MomentBottomOperateRowGroup(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.padding(start = 16.dp, end = 0.dp)
.padding(start = 16.dp, end = 16.dp)
) {
Column(
modifier = Modifier.fillMaxSize()
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically
) {
if (momentEntity.images.size > 1) {
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
momentEntity.images.forEachIndexed { index, _ ->
Box(
modifier = Modifier
.size(4.dp)
.clip(CircleShape)
.background(
if (imageIndex == index) Color.Red else Color.Gray.copy(
alpha = 0.5f
)
)
.padding(1.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Row(
modifier = Modifier.weight(1f).fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically
) {
// 点赞按钮
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
AnimatedLikeIcon(
modifier = Modifier.size(24.dp),
liked = momentEntity.liked
) {
onLikeClick()
}
}
Spacer(modifier = Modifier.width(16.dp))
// 评论按钮
Box(
modifier = Modifier.noRippleClickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > clickDelay) {
lastClickTime.value = currentTime
onCommentClick()
}
}
) {
MomentOperateBtn(
icon = R.mipmap.icon_comment,
count = momentEntity.commentCount.toString()
)
}
Spacer(modifier = Modifier.width(16.dp))
// 转发按钮
Box(
modifier = Modifier.noRippleClickable {
// TODO: 实现转发功能
}
) {
MomentOperateBtn(
icon = R.mipmap.icon_share,
count = ""
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier = Modifier.weight(1f).fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically
Spacer(modifier = Modifier.width(16.dp))
// 收藏按钮
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
AnimatedFavouriteIcon(
modifier = Modifier.size(24.dp),
isFavourite = momentEntity.isFavorite
) {
// 点赞按钮
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
AnimatedLikeIcon(
modifier = Modifier.size(24.dp),
liked = momentEntity.liked
) {
onLikeClick()
}
}
Spacer(modifier = Modifier.width(10.dp))
// 评论按钮
Box(
modifier = Modifier.noRippleClickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > clickDelay) {
lastClickTime.value = currentTime
onCommentClick()
}
}
) {
MomentOperateBtn(
icon = R.mipmap.icon_comment,
count = momentEntity.commentCount.toString()
)
}
Spacer(modifier = Modifier.width(28.dp))
// 转发按钮
Box(
modifier = Modifier.noRippleClickable {
// TODO: 实现转发功能
}
) {
MomentOperateBtn(
icon = R.mipmap.icon_share,
count = ""
)
}
}
// 收藏按钮
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
AnimatedFavouriteIcon(
modifier = Modifier.size(24.dp),
isFavourite = momentEntity.isFavorite
) {
onFavoriteClick()
}
onFavoriteClick()
}
}
}

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -39,6 +40,7 @@ fun TabItem(
Column(
modifier = modifier
.wrapContentWidth()
.noRippleClickable { onClick() },
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally

View File

@@ -467,7 +467,7 @@ fun CreateGroupChatScreen() {
) {
if (CreateGroupChatViewModel.isLoading) {
Text(
text = "创建中...",
text = stringResource(R.string.agent_createing),
fontSize = 16.sp,
fontWeight = FontWeight.W600
)

View File

@@ -18,6 +18,7 @@ import com.aiosman.ravenow.data.AccountService
import com.aiosman.ravenow.data.AccountServiceImpl
import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.CommentEntity
import com.aiosman.ravenow.exp.formatChatTime
import com.aiosman.ravenow.ui.NavigationRoute
@@ -55,13 +56,13 @@ object CreateGroupChatViewModel : ViewModel() {
true
} else {
isLoading = false
val errorMsg = "创建群聊失败: ${response.message()}"
val errorMsg = context.getString(R.string.create_group_chat_failed, response.message() ?: "")
showToast(errorMsg)
false
}
} catch (e: Exception) {
isLoading = false
val errorMsg = "创建群聊失败: ${e.message}"
val errorMsg = context.getString(R.string.create_group_chat_failed, e.message ?: "")
showToast(errorMsg)
false
}

View File

@@ -34,6 +34,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
@@ -141,17 +143,25 @@ fun TimelineMomentsList() {
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = "连接世界,从关注开始",
text = stringResource(R.string.connect_world_start_following),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
fontWeight = FontWeight.W600,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "不如从一个 Agent 开始认识这世界?",
text = stringResource(R.string.why_not_start_with_agent),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W400
fontWeight = FontWeight.W400,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.size(16.dp))
ExploreButton(
@@ -260,7 +270,7 @@ fun ExploreButton(
contentAlignment = Alignment.Center
) {
Text(
text = "去探索",
text = stringResource(R.string.explore),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White

View File

@@ -35,6 +35,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -82,7 +83,7 @@ fun PointsBottomSheet(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = "My Pai Coin", color = AppColors.text, fontSize = 20.sp, fontWeight = FontWeight.Bold)
Text(text = stringResource(R.string.my_pai_coin), color = AppColors.text, fontSize = 20.sp, fontWeight = FontWeight.Bold)
Box(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
@@ -90,7 +91,7 @@ fun PointsBottomSheet(
.clickable { onRecharge() }
.padding(horizontal = 12.dp, vertical = 6.dp)
) {
Text(text = "Recharge", color = Color(0xFF6B46C1), fontSize = 14.sp, fontWeight = FontWeight.W600,
Text(text = stringResource(R.string.recharge), color = Color(0xFF6B46C1), fontSize = 14.sp, fontWeight = FontWeight.W600,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.padding(0.dp)
@@ -108,7 +109,7 @@ fun PointsBottomSheet(
.background(AppColors.nonActive)
.padding(16.dp)
) {
Text("Current Balance", color = AppColors.secondaryText, fontSize = 14.sp)
Text(stringResource(R.string.current_balance), color = AppColors.secondaryText, fontSize = 14.sp)
Spacer(Modifier.height(8.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
@@ -133,7 +134,7 @@ fun PointsBottomSheet(
) {
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 = "Total Earned", color = AppColors.secondaryText, fontSize = 12.sp)
Text(text = stringResource(R.string.total_earned), color = AppColors.secondaryText, fontSize = 12.sp)
}
Box(
modifier = Modifier
@@ -143,7 +144,7 @@ fun PointsBottomSheet(
)
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 = "Total Spent", color = AppColors.secondaryText, fontSize = 12.sp)
Text(text = stringResource(R.string.total_spent), color = AppColors.secondaryText, fontSize = 12.sp)
}
}
}
@@ -159,17 +160,15 @@ fun PointsBottomSheet(
verticalAlignment = Alignment.CenterVertically
) {
TabItem(
text = "Transaction History",
text = stringResource(R.string.transaction_history),
isSelected = tab == 0,
onClick = { tab = 0 },
modifier = Modifier.weight(1f)
onClick = { tab = 0 }
)
TabSpacer()
TabItem(
text = "How to Earn",
text = stringResource(R.string.how_to_earn),
isSelected = tab == 1,
onClick = { tab = 1 },
modifier = Modifier.weight(1f)
onClick = { tab = 1 }
)
}
@@ -251,7 +250,7 @@ private fun PointsHistoryList(
Button(onClick = onLoadMore, modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)) {
Text("Load More")
Text(stringResource(R.string.load_more))
}
}
}
@@ -290,11 +289,11 @@ private fun HowToEarnList() {
}
Column {
RowItem("New User Reward", "Register and get 500 Pai Coin", "+500")
RowItem("Daily Check-in", "Check in daily to earn Pai Coin", "+10-50")
RowItem("Invite Friends", "Earn Pai Coin for each friend invited", "+100")
RowItem("Complete Tasks", "Complete tasks to earn rewards", "+20-200")
RowItem("Recharge Pai Coin", "Multiple packages available, recharge now", ">")
RowItem(stringResource(R.string.new_user_reward), stringResource(R.string.new_user_reward_desc), "+500")
RowItem(stringResource(R.string.daily_check_in), stringResource(R.string.daily_check_in_desc), "+10-50")
RowItem(stringResource(R.string.invite_friends), stringResource(R.string.invite_friends_desc), "+100")
RowItem(stringResource(R.string.complete_tasks), stringResource(R.string.complete_tasks_desc), "+20-200")
RowItem(stringResource(R.string.recharge_pai_coin), stringResource(R.string.recharge_pai_coin_desc), ">")
}
}

View File

@@ -323,5 +323,30 @@
<string name="logout_confirm">ログアウト</string>
<string name="blocked_users">ブロックされたユーザー</string>
<string name="confirm_password_label">パスワードを確認</string>
<!-- Points Bottom Sheet -->
<string name="transaction_history">取引履歴</string>
<string name="how_to_earn">獲得方法</string>
<string name="total_earned">合計獲得</string>
<string name="total_spent">合計支出</string>
<string name="my_pai_coin">マイパイコイン</string>
<string name="recharge">チャージ</string>
<string name="current_balance">現在の残高</string>
<string name="load_more">さらに読み込む</string>
<string name="new_user_reward">新規ユーザー報酬</string>
<string name="new_user_reward_desc">登録して500パイコインを獲得</string>
<string name="daily_check_in">デイリーチェックイン</string>
<string name="daily_check_in_desc">毎日チェックインしてパイコインを獲得</string>
<string name="invite_friends">友達を招待</string>
<string name="invite_friends_desc">友達を招待するたびにパイコインを獲得</string>
<string name="complete_tasks">タスクを完了</string>
<string name="complete_tasks_desc">タスクを完了して報酬を獲得</string>
<string name="recharge_pai_coin">パイコインをチャージ</string>
<string name="recharge_pai_coin_desc">複数のパッケージが利用可能、今すぐチャージ</string>
<string name="create_group_chat_failed">グループチャットの作成に失敗しました: %1$s</string>
<string name="connect_world_start_following">世界をつなぐ、フォローから始めましょう</string>
<string name="why_not_start_with_agent">エージェントから世界を知り始めませんか?</string>
<string name="explore">探検する</string>
<string name="reply_to_user">返信@%1$s</string>
</resources>

View File

@@ -347,9 +347,29 @@
<string name="blocked_users">被屏蔽的用户</string>
<string name="confirm_password_label">确认密码</string>
<!-- Edit Profile Extras -->
<string name="mbti_type">MBTI</string>
<string name="zodiac">星座</string>
<string name="save">保存</string>
<string name="choose_mbti">选择 MBTI</string>
<string name="choose_zodiac">选择 Zodiac</string>
<!-- Points Bottom Sheet -->
<string name="transaction_history">交易历史</string>
<string name="how_to_earn">如何赚取</string>
<string name="total_earned">总获得</string>
<string name="total_spent">总支出</string>
<string name="my_pai_coin">我的派币</string>
<string name="recharge">充值</string>
<string name="current_balance">当前余额</string>
<string name="load_more">加载更多</string>
<string name="new_user_reward">新用户奖励</string>
<string name="new_user_reward_desc">注册即可获得500派币</string>
<string name="daily_check_in">每日签到</string>
<string name="daily_check_in_desc">每日签到可获得派币</string>
<string name="invite_friends">邀请好友</string>
<string name="invite_friends_desc">每邀请一位好友可获得派币</string>
<string name="complete_tasks">完成任务</string>
<string name="complete_tasks_desc">完成任务可获得奖励</string>
<string name="recharge_pai_coin">充值派币</string>
<string name="recharge_pai_coin_desc">多种套餐可选,立即充值</string>
<string name="create_group_chat_failed">创建群聊失败: %1$s</string>
<string name="connect_world_start_following">连接世界,从关注开始</string>
<string name="why_not_start_with_agent">不如从一个 Agent 开始认识这世界?</string>
<string name="explore">去探索</string>
<string name="reply_to_user">回复@%1$s</string>
</resources>

View File

@@ -340,4 +340,29 @@
<string name="logout_confirm">Logout</string>
<string name="blocked_users">Blocked Users</string>
<string name="confirm_password_label">Confirm password</string>
<!-- Points Bottom Sheet -->
<string name="transaction_history">Transaction History</string>
<string name="how_to_earn">How to Earn</string>
<string name="total_earned">Total Earned</string>
<string name="total_spent">Total Spent</string>
<string name="my_pai_coin">My Pai Coin</string>
<string name="recharge">Recharge</string>
<string name="current_balance">Current Balance</string>
<string name="load_more">Load More</string>
<string name="new_user_reward">New User Reward</string>
<string name="new_user_reward_desc">Register and get 500 Pai Coin</string>
<string name="daily_check_in">Daily Check-in</string>
<string name="daily_check_in_desc">Check in daily to earn Pai Coin</string>
<string name="invite_friends">Invite Friends</string>
<string name="invite_friends_desc">Earn Pai Coin for each friend invited</string>
<string name="complete_tasks">Complete Tasks</string>
<string name="complete_tasks_desc">Complete tasks to earn rewards</string>
<string name="recharge_pai_coin">Recharge Pai Coin</string>
<string name="recharge_pai_coin_desc">Multiple packages available, recharge now</string>
<string name="create_group_chat_failed">Failed to create group chat: %1$s</string>
<string name="connect_world_start_following">Connect the world, start by following</string>
<string name="why_not_start_with_agent">Why not start exploring the world with an Agent?</string>
<string name="explore">Explore</string>
<string name="reply_to_user">Reply @%1$s</string>
</resources>