账户编辑、评论点赞功能优化和UI调整
**功能优化:**
* **账户编辑:**
* 昵称和个人简介输入时自动去除换行符。
* 修复了进入编辑页面时可能未正确加载或重置用户资料的问题。
* 保存资料时,确保昵称和个人简介中的换行符被移除。
* 清除裁剪的头像图片,避免重复使用。
* **评论点赞/取消点赞:**
* 引入乐观更新策略,提升用户体验,点赞/取消点赞操作会立即反映在UI上,然后进行后台API调用。
* 增加防抖机制,防止用户快速重复点击点赞/取消点赞按钮导致多次API请求。
**UI调整:**
* **账户编辑页面:**
* 页面切换动画调整为iOS风格的底部滑入滑出效果。
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -55,11 +55,13 @@ fun AccountEditScreen2() {
|
||||
var usernameError by remember { mutableStateOf<String?>(null) }
|
||||
var bioError by remember { mutableStateOf<String?>(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),
|
||||
|
||||
@@ -152,38 +152,96 @@ class CommentsViewModel(
|
||||
_commentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
// 用于防止重复点赞的状态集合
|
||||
private val _pendingLikeOperations = mutableSetOf<Int>()
|
||||
|
||||
/**
|
||||
* 点赞评论
|
||||
* 点赞评论 - 使用乐观更新策略
|
||||
*/
|
||||
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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user