账户编辑、评论点赞功能优化和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.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.animation.SharedTransitionLayout
|
import androidx.compose.animation.SharedTransitionLayout
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.slideInHorizontally
|
import androidx.compose.animation.slideInHorizontally
|
||||||
import androidx.compose.animation.slideOutHorizontally
|
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.Box
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
@@ -364,10 +367,40 @@ fun NavigationController(
|
|||||||
composable(
|
composable(
|
||||||
route = NavigationRoute.AccountEdit.route,
|
route = NavigationRoute.AccountEdit.route,
|
||||||
enterTransition = {
|
enterTransition = {
|
||||||
fadeIn(animationSpec = tween(durationMillis = 0))
|
// iOS风格:从底部向上滑入
|
||||||
|
slideInVertically(
|
||||||
|
initialOffsetY = { fullHeight -> fullHeight },
|
||||||
|
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
|
||||||
|
) + fadeIn(
|
||||||
|
animationSpec = tween(durationMillis = 300)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
exitTransition = {
|
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()
|
AccountEditScreen2()
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ object AccountEditViewModel : ViewModel() {
|
|||||||
profile = it
|
profile = it
|
||||||
name = it.nickName
|
name = it.nickName
|
||||||
bio = it.bio
|
bio = it.bio
|
||||||
|
// 清除之前裁剪的图片
|
||||||
|
croppedBitmap = null
|
||||||
if (updateTrtcProfile) {
|
if (updateTrtcProfile) {
|
||||||
TrtcHelper.updateTrtcProfile(
|
TrtcHelper.updateTrtcProfile(
|
||||||
it.nickName,
|
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) {
|
suspend fun updateUserProfile(context: Context) {
|
||||||
val newAvatar = croppedBitmap?.let {
|
val newAvatar = croppedBitmap?.let {
|
||||||
@@ -44,12 +55,16 @@ object AccountEditViewModel : ViewModel() {
|
|||||||
it.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream())
|
it.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream())
|
||||||
UploadImage(file, "avatar.jpg", "", "jpg")
|
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(
|
accountService.updateProfile(
|
||||||
avatar = newAvatar,
|
avatar = newAvatar,
|
||||||
banner = null,
|
banner = null,
|
||||||
nickName = newName,
|
nickName = newName,
|
||||||
bio = bio
|
bio = cleanBio
|
||||||
)
|
)
|
||||||
// 刷新用户资料
|
// 刷新用户资料
|
||||||
reloadProfile()
|
reloadProfile()
|
||||||
|
|||||||
@@ -55,11 +55,13 @@ fun AccountEditScreen2() {
|
|||||||
var usernameError by remember { mutableStateOf<String?>(null) }
|
var usernameError by remember { mutableStateOf<String?>(null) }
|
||||||
var bioError by remember { mutableStateOf<String?>(null) }
|
var bioError by remember { mutableStateOf<String?>(null) }
|
||||||
fun onNicknameChange(value: String) {
|
fun onNicknameChange(value: String) {
|
||||||
model.name = value
|
// 去除换行符,确保昵称不包含换行
|
||||||
|
val cleanValue = value.replace("\n", "").replace("\r", "")
|
||||||
|
model.name = cleanValue
|
||||||
usernameError = when {
|
usernameError = when {
|
||||||
value.trim().isEmpty() -> "昵称不能为空"
|
cleanValue.trim().isEmpty() -> "昵称不能为空"
|
||||||
value.length < 3 -> "昵称长度不能小于3"
|
cleanValue.length < 3 -> "昵称长度不能小于3"
|
||||||
value.length > 20 -> "昵称长度不能大于20"
|
cleanValue.length > 20 -> "昵称长度不能大于20"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,9 +69,11 @@ fun AccountEditScreen2() {
|
|||||||
val appColors = LocalAppTheme.current
|
val appColors = LocalAppTheme.current
|
||||||
|
|
||||||
fun onBioChange(value: String) {
|
fun onBioChange(value: String) {
|
||||||
model.bio = value
|
// 去除换行符,确保个人简介不包含换行
|
||||||
|
val cleanValue = value.replace("\n", "").replace("\r", "")
|
||||||
|
model.bio = cleanValue
|
||||||
bioError = when {
|
bioError = when {
|
||||||
value.length > 100 -> "个人简介长度不能大于24"
|
cleanValue.length > 100 -> "个人简介长度不能大于100"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,10 +83,13 @@ fun AccountEditScreen2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
// 先初始化显示当前资料,避免重置画面
|
||||||
if (model.profile == null) {
|
if (model.profile == null) {
|
||||||
model.reloadProfile()
|
model.reloadProfile()
|
||||||
|
} else {
|
||||||
|
// 重置编辑状态为原始资料数据
|
||||||
|
model.resetToOriginalData()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
StatusBarMaskLayout(
|
StatusBarMaskLayout(
|
||||||
modifier = Modifier.background(color = appColors.background).padding(horizontal = 16.dp),
|
modifier = Modifier.background(color = appColors.background).padding(horizontal = 16.dp),
|
||||||
|
|||||||
@@ -152,38 +152,96 @@ class CommentsViewModel(
|
|||||||
_commentsFlow.value = updatedPagingData
|
_commentsFlow.value = updatedPagingData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用于防止重复点赞的状态集合
|
||||||
|
private val _pendingLikeOperations = mutableSetOf<Int>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点赞评论
|
* 点赞评论 - 使用乐观更新策略
|
||||||
*/
|
*/
|
||||||
suspend fun likeComment(commentId: Int) {
|
suspend fun likeComment(commentId: Int) {
|
||||||
|
// 防止重复操作
|
||||||
|
if (_pendingLikeOperations.contains(commentId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingLikeOperations.add(commentId)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 乐观更新:先更新UI状态
|
||||||
|
val previousState = getCurrentCommentLikeState(commentId)
|
||||||
|
updateCommentLikeState(commentId, true)
|
||||||
|
|
||||||
|
// 然后调用API
|
||||||
commentService.likeComment(commentId)
|
commentService.likeComment(commentId)
|
||||||
// 更新addCommentList
|
|
||||||
if (updateHighlightCommentLike(commentId, true)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (updateAddedCommentLike(commentId, true)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updateCommentLike(commentId, true)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
// 如果API调用失败,回滚UI状态
|
||||||
|
updateCommentLikeState(commentId, false)
|
||||||
|
} finally {
|
||||||
|
_pendingLikeOperations.remove(commentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消点赞评论
|
/**
|
||||||
|
* 取消点赞评论 - 使用乐观更新策略
|
||||||
|
*/
|
||||||
suspend fun unlikeComment(commentId: Int) {
|
suspend fun unlikeComment(commentId: Int) {
|
||||||
|
// 防止重复操作
|
||||||
|
if (_pendingLikeOperations.contains(commentId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingLikeOperations.add(commentId)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 乐观更新:先更新UI状态
|
||||||
|
val previousState = getCurrentCommentLikeState(commentId)
|
||||||
|
updateCommentLikeState(commentId, false)
|
||||||
|
|
||||||
|
// 然后调用API
|
||||||
commentService.dislikeComment(commentId)
|
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
|
return
|
||||||
}
|
}
|
||||||
// 更新添加的评论点赞状态
|
// 更新添加的评论点赞状态
|
||||||
if (updateAddedCommentLike(commentId, false)) {
|
if (updateAddedCommentLike(commentId, isLike)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 更新评论点赞状态
|
// 更新评论点赞状态
|
||||||
updateCommentLike(commentId, false)
|
updateCommentLike(commentId, isLike)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createComment(
|
suspend fun createComment(
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ fun PostScreen(
|
|||||||
showCommentMenu = false
|
showCommentMenu = false
|
||||||
}
|
}
|
||||||
contextComment?.let {
|
contextComment?.let {
|
||||||
|
// 防抖机制已在ViewModel中实现
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
if (it.liked) {
|
if (it.liked) {
|
||||||
viewModel.unlikeComment(it.id)
|
viewModel.unlikeComment(it.id)
|
||||||
@@ -523,6 +524,7 @@ fun CommentContent(
|
|||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
} else {
|
} else {
|
||||||
|
// 使用防抖机制避免重复点击
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
if (comment.liked) {
|
if (comment.liked) {
|
||||||
viewModel.unlikeComment(comment.id)
|
viewModel.unlikeComment(comment.id)
|
||||||
@@ -577,6 +579,7 @@ fun CommentContent(
|
|||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
} else {
|
} else {
|
||||||
|
// 防抖机制已在ViewModel中实现
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
if (comment.liked) {
|
if (comment.liked) {
|
||||||
viewModel.unlikeComment(comment.id)
|
viewModel.unlikeComment(comment.id)
|
||||||
@@ -630,6 +633,7 @@ fun CommentContent(
|
|||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
} else {
|
} else {
|
||||||
|
// 防抖机制已在ViewModel中实现
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
if (comment.liked) {
|
if (comment.liked) {
|
||||||
viewModel.unlikeComment(comment.id)
|
viewModel.unlikeComment(comment.id)
|
||||||
|
|||||||
Reference in New Issue
Block a user