diff --git a/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt b/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt index b3a8bbb..3a56c49 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt @@ -6,10 +6,13 @@ import ModificationListScreen import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.core.tween +import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.navigationBars @@ -364,10 +367,40 @@ fun NavigationController( composable( route = NavigationRoute.AccountEdit.route, enterTransition = { - fadeIn(animationSpec = tween(durationMillis = 0)) + // iOS风格:从底部向上滑入 + slideInVertically( + initialOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) + ) + fadeIn( + animationSpec = tween(durationMillis = 300) + ) }, exitTransition = { - fadeOut(animationSpec = tween(durationMillis = 0)) + // iOS风格:向底部滑出 + slideOutVertically( + targetOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) + ) + fadeOut( + animationSpec = tween(durationMillis = 300) + ) + }, + popEnterTransition = { + // 返回时从底部滑入 + slideInVertically( + initialOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) + ) + fadeIn( + animationSpec = tween(durationMillis = 300) + ) + }, + popExitTransition = { + // 返回时向底部滑出 + slideOutVertically( + targetOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) + ) + fadeOut( + animationSpec = tween(durationMillis = 300) + ) } ) { AccountEditScreen2() diff --git a/app/src/main/java/com/aiosman/ravenow/ui/account/AccountEditViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/account/AccountEditViewModel.kt index 1e111d7..b36dada 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/account/AccountEditViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/account/AccountEditViewModel.kt @@ -28,6 +28,8 @@ object AccountEditViewModel : ViewModel() { profile = it name = it.nickName bio = it.bio + // 清除之前裁剪的图片 + croppedBitmap = null if (updateTrtcProfile) { TrtcHelper.updateTrtcProfile( it.nickName, @@ -37,6 +39,15 @@ object AccountEditViewModel : ViewModel() { } } + fun resetToOriginalData() { + profile?.let { + name = it.nickName + bio = it.bio + // 清除之前裁剪的图片 + croppedBitmap = null + } + } + suspend fun updateUserProfile(context: Context) { val newAvatar = croppedBitmap?.let { @@ -44,12 +55,16 @@ object AccountEditViewModel : ViewModel() { it.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream()) UploadImage(file, "avatar.jpg", "", "jpg") } - val newName = if (name == profile?.nickName) null else name + // 去除换行符,确保昵称和个人简介不包含换行 + val cleanName = name.trim().replace("\n", "").replace("\r", "") + val cleanBio = bio.trim().replace("\n", "").replace("\r", "") + + val newName = if (cleanName == profile?.nickName) null else cleanName accountService.updateProfile( avatar = newAvatar, banner = null, nickName = newName, - bio = bio + bio = cleanBio ) // 刷新用户资料 reloadProfile() diff --git a/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt b/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt index 7910490..4a8dde1 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/account/edit2.kt @@ -55,11 +55,13 @@ fun AccountEditScreen2() { var usernameError by remember { mutableStateOf(null) } var bioError by remember { mutableStateOf(null) } fun onNicknameChange(value: String) { - model.name = value + // 去除换行符,确保昵称不包含换行 + val cleanValue = value.replace("\n", "").replace("\r", "") + model.name = cleanValue usernameError = when { - value.trim().isEmpty() -> "昵称不能为空" - value.length < 3 -> "昵称长度不能小于3" - value.length > 20 -> "昵称长度不能大于20" + cleanValue.trim().isEmpty() -> "昵称不能为空" + cleanValue.length < 3 -> "昵称长度不能小于3" + cleanValue.length > 20 -> "昵称长度不能大于20" else -> null } } @@ -67,9 +69,11 @@ fun AccountEditScreen2() { val appColors = LocalAppTheme.current fun onBioChange(value: String) { - model.bio = value + // 去除换行符,确保个人简介不包含换行 + val cleanValue = value.replace("\n", "").replace("\r", "") + model.bio = cleanValue bioError = when { - value.length > 100 -> "个人简介长度不能大于24" + cleanValue.length > 100 -> "个人简介长度不能大于100" else -> null } } @@ -79,10 +83,13 @@ fun AccountEditScreen2() { } LaunchedEffect(Unit) { + // 先初始化显示当前资料,避免重置画面 if (model.profile == null) { model.reloadProfile() + } else { + // 重置编辑状态为原始资料数据 + model.resetToOriginalData() } - } StatusBarMaskLayout( modifier = Modifier.background(color = appColors.background).padding(horizontal = 16.dp), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/post/CommentsViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/post/CommentsViewModel.kt index 7d044c5..6f717fc 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/post/CommentsViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/post/CommentsViewModel.kt @@ -152,38 +152,96 @@ class CommentsViewModel( _commentsFlow.value = updatedPagingData } + // 用于防止重复点赞的状态集合 + private val _pendingLikeOperations = mutableSetOf() + /** - * 点赞评论 + * 点赞评论 - 使用乐观更新策略 */ suspend fun likeComment(commentId: Int) { + // 防止重复操作 + if (_pendingLikeOperations.contains(commentId)) { + return + } + + _pendingLikeOperations.add(commentId) + try { + // 乐观更新:先更新UI状态 + val previousState = getCurrentCommentLikeState(commentId) + updateCommentLikeState(commentId, true) + + // 然后调用API commentService.likeComment(commentId) - // 更新addCommentList - if (updateHighlightCommentLike(commentId, true)) { - return - } - if (updateAddedCommentLike(commentId, true)) { - return - } - updateCommentLike(commentId, true) } catch (e: Exception) { e.printStackTrace() + // 如果API调用失败,回滚UI状态 + updateCommentLikeState(commentId, false) + } finally { + _pendingLikeOperations.remove(commentId) } } - // 取消点赞评论 + /** + * 取消点赞评论 - 使用乐观更新策略 + */ suspend fun unlikeComment(commentId: Int) { - commentService.dislikeComment(commentId) + // 防止重复操作 + if (_pendingLikeOperations.contains(commentId)) { + return + } + + _pendingLikeOperations.add(commentId) + + try { + // 乐观更新:先更新UI状态 + val previousState = getCurrentCommentLikeState(commentId) + updateCommentLikeState(commentId, false) + + // 然后调用API + commentService.dislikeComment(commentId) + } catch (e: Exception) { + e.printStackTrace() + // 如果API调用失败,回滚UI状态 + updateCommentLikeState(commentId, true) + } finally { + _pendingLikeOperations.remove(commentId) + } + } + + /** + * 获取当前评论的点赞状态 + */ + private fun getCurrentCommentLikeState(commentId: Int): Boolean { + // 检查高亮评论 + highlightComment?.let { comment -> + if (comment.id == commentId) { + return comment.liked + } + comment.reply.find { it.id == commentId }?.let { return it.liked } + } + + // 检查添加的评论列表 + addedCommentList.find { it.id == commentId }?.let { return it.liked } + + // 检查分页数据(这里简化处理,实际项目中可能需要更复杂的逻辑) + return false + } + + /** + * 统一的评论点赞状态更新方法 + */ + private fun updateCommentLikeState(commentId: Int, isLike: Boolean) { // 更新高亮评论点赞状态 - if (updateHighlightCommentLike(commentId, false)) { + if (updateHighlightCommentLike(commentId, isLike)) { return } // 更新添加的评论点赞状态 - if (updateAddedCommentLike(commentId, false)) { + if (updateAddedCommentLike(commentId, isLike)) { return } // 更新评论点赞状态 - updateCommentLike(commentId, false) + updateCommentLike(commentId, isLike) } suspend fun createComment( 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 6b70a91..23f86c6 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 @@ -209,6 +209,7 @@ fun PostScreen( showCommentMenu = false } contextComment?.let { + // 防抖机制已在ViewModel中实现 viewModel.viewModelScope.launch { if (it.liked) { viewModel.unlikeComment(it.id) @@ -523,6 +524,7 @@ fun CommentContent( if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) { navController.navigate(NavigationRoute.Login.route) } else { + // 使用防抖机制避免重复点击 viewModel.viewModelScope.launch { if (comment.liked) { viewModel.unlikeComment(comment.id) @@ -577,6 +579,7 @@ fun CommentContent( if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) { navController.navigate(NavigationRoute.Login.route) } else { + // 防抖机制已在ViewModel中实现 viewModel.viewModelScope.launch { if (comment.liked) { viewModel.unlikeComment(comment.id) @@ -630,6 +633,7 @@ fun CommentContent( if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) { navController.navigate(NavigationRoute.Login.route) } else { + // 防抖机制已在ViewModel中实现 viewModel.viewModelScope.launch { if (comment.liked) { viewModel.unlikeComment(comment.id)