From 054dd68b893c5c7400919ceb81d3d4a3948cc916 Mon Sep 17 00:00:00 2001 From: AllenTom Date: Fri, 20 Sep 2024 17:10:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/aiosman/riderpro/JpushReciver.kt | 20 +- .../java/com/aiosman/riderpro/MainActivity.kt | 26 ++- .../aiosman/riderpro/data/AccountService.kt | 15 ++ .../aiosman/riderpro/data/CommentService.kt | 14 +- .../aiosman/riderpro/data/api/RiderProAPI.kt | 16 ++ .../com/aiosman/riderpro/entity/Comment.kt | 1 - .../main/java/com/aiosman/riderpro/ui/Navi.kt | 13 +- .../ui/favourite/FavouriteListPage.kt | 2 +- .../ui/index/tabs/message/MessageList.kt | 50 ++++- .../tabs/message/MessageListViewModel.kt | 6 + .../riderpro/ui/index/tabs/moment/Moment.kt | 7 +- .../riderpro/ui/index/tabs/profile/Profile.kt | 2 +- .../ui/index/tabs/search/DiscoverScreen.kt | 2 +- .../com/aiosman/riderpro/ui/like/LikePage.kt | 4 +- .../aiosman/riderpro/ui/login/emailsignup.kt | 4 + .../com/aiosman/riderpro/ui/login/signup.kt | 4 + .../riderpro/ui/post/CommentsViewModel.kt | 194 ++++++++++++------ .../java/com/aiosman/riderpro/ui/post/Post.kt | 131 ++++++++---- .../aiosman/riderpro/ui/post/PostViewModel.kt | 12 +- .../java/com/aiosman/riderpro/utils/Utils.kt | 5 + 20 files changed, 390 insertions(+), 138 deletions(-) diff --git a/app/src/main/java/com/aiosman/riderpro/JpushReciver.kt b/app/src/main/java/com/aiosman/riderpro/JpushReciver.kt index c10940f..34e3f90 100644 --- a/app/src/main/java/com/aiosman/riderpro/JpushReciver.kt +++ b/app/src/main/java/com/aiosman/riderpro/JpushReciver.kt @@ -13,6 +13,8 @@ data class ActionExtra( val action: String, @SerializedName("postId") val postId: String?, + @SerializedName("commentId") + val commentId: String? ) class JpushReciver : JPushMessageReceiver() { @@ -30,12 +32,18 @@ class JpushReciver : JPushMessageReceiver() { val actionExtra = message.notificationExtras?.let { gson.fromJson(it, ActionExtra::class.java) } - actionExtra?.postId?.let { - val intent = Intent(context, MainActivity::class.java).apply { - putExtra("POST_ID", it) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - context?.startActivity(intent) + val intent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } + actionExtra?.postId?.let { + intent.putExtra("POST_ID", it) + } + actionExtra?.commentId?.let { + intent.putExtra("COMMENT_ID", it) + } + actionExtra?.action?.let { + intent.putExtra("ACTION", it) + } + context?.startActivity(intent) } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt index f467337..6a7fd17 100644 --- a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt +++ b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt @@ -29,6 +29,7 @@ import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.ui.Navigation import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.post.NewPostViewModel +import com.aiosman.riderpro.utils.Utils import com.google.android.libraries.places.api.Places import com.google.firebase.analytics.FirebaseAnalytics import kotlinx.coroutines.CoroutineScope @@ -58,6 +59,9 @@ class MainActivity : ComponentActivity() { val accountService: AccountService = AccountServiceImpl() try { val resp = accountService.getMyAccount() + accountService.updateUserLanguage( + Utils.getCurrentLanguage() + ) // 设置当前登录用户 ID AppState.UserId = resp.id return true @@ -105,20 +109,32 @@ class MainActivity : ComponentActivity() { var startDestination = NavigationRoute.Login.route // 如果有登录态,且记住登录状态,且账号有效,则初始化 FCM,下一步进入首页 if (AppStore.token != null && AppStore.rememberMe && isAccountValidate) { - Messaging.RegistDevice(scope,this@MainActivity) + Messaging.RegistDevice(scope, this@MainActivity) startDestination = NavigationRoute.Index.route } setContent { Navigation(startDestination) { navController -> // 处理带有 postId 的通知点击 val postId = intent.getStringExtra("POST_ID") + var commentId = intent.getStringExtra("COMMENT_ID") + var action = intent.getStringExtra("ACTION") + if (action == "newFollow") { + navController.navigate(NavigationRoute.Followers.route) + return@Navigation + } + if (commentId == null) { + commentId = "0" + } + if (postId != null) { Log.d("MainActivity", "Navigation to Post$postId") navController.navigate( - NavigationRoute.Post.route.replace( - "{id}", - postId - ) + NavigationRoute.Post.route + .replace( + "{id}", + postId + ) + .replace("{highlightCommentId}", commentId) ) } // 处理分享过来的图片 diff --git a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt index fc9a8d2..b89732e 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt @@ -8,6 +8,7 @@ import com.aiosman.riderpro.data.api.RegisterMessageChannelRequestBody import com.aiosman.riderpro.data.api.RegisterRequestBody import com.aiosman.riderpro.data.api.ResetPasswordRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody +import com.aiosman.riderpro.data.api.UpdateUserLangRequestBody import com.aiosman.riderpro.entity.AccountFavouriteEntity import com.aiosman.riderpro.entity.AccountLikeEntity import com.aiosman.riderpro.entity.AccountProfileEntity @@ -339,10 +340,20 @@ interface AccountService { */ suspend fun updateNotice(payload: UpdateNoticeRequestBody) + /** + * 注册消息通道 + */ suspend fun registerMessageChannel(client: String, identifier: String) + /** + * 重置密码 + */ suspend fun resetPassword(email: String) + /** + * 更新用户语言 + */ + suspend fun updateUserLanguage(language: String) } class AccountServiceImpl : AccountService { @@ -473,4 +484,8 @@ class AccountServiceImpl : AccountService { } } + override suspend fun updateUserLanguage(language: String) { + ApiClient.api.updateUserLang(UpdateUserLangRequestBody(language)) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt b/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt index cf290b9..c65aab0 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt @@ -68,6 +68,13 @@ interface CommentService { * @param commentId 评论ID */ suspend fun DeleteComment(commentId: Int) + + /** + * 获取评论 + * @param commentId 评论ID + */ + suspend fun getCommentById(commentId: Int): CommentEntity + } /** @@ -120,7 +127,6 @@ data class Comment( comment = content, date = ApiClient.dateFromApiString(createdAt), likes = likeCount, - replies = emptyList(), postId = postId, avatar = "${ApiClient.BASE_SERVER}${user.avatar}", author = user.id, @@ -240,4 +246,10 @@ class CommentServiceImpl : CommentService { val resp = ApiClient.api.deleteComment(commentId) return } + + override suspend fun getCommentById(commentId: Int): CommentEntity { + val resp = ApiClient.api.getComment(commentId) + val body = resp.body() ?: throw ServiceException("Failed to get comment") + return body.data.toCommentEntity() + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt index 476bc02..10a954c 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt @@ -98,6 +98,11 @@ data class ResetPasswordRequestBody( val username: String, ) +data class UpdateUserLangRequestBody( + @SerializedName("language") + val lang: String, +) + interface RiderProAPI { @POST("register") suspend fun register(@Body body: RegisterRequestBody): Response @@ -280,4 +285,15 @@ interface RiderProAPI { suspend fun resetPassword( @Body body: ResetPasswordRequestBody ): Response + + @GET("comment/{id}") + suspend fun getComment( + @Path("id") id: Int + ): Response> + + @PATCH("account/my/lang") + suspend fun updateUserLang( + @Body body: UpdateUserLangRequestBody + ): Response + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt b/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt index f734c0e..9fe080a 100644 --- a/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt +++ b/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt @@ -13,7 +13,6 @@ data class CommentEntity( val comment: String, val date: Date, val likes: Int, - val replies: List, val postId: Int = 0, val avatar: String, val author: Long, diff --git a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt index 1cb9859..c2bf1fc 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -61,7 +61,7 @@ sealed class NavigationRoute( data object LocationDetail : NavigationRoute("LocationDetail/{x}/{y}") data object OfficialPhoto : NavigationRoute("OfficialPhoto") data object OfficialPhotographer : NavigationRoute("OfficialPhotographer") - data object Post : NavigationRoute("Post/{id}") + data object Post : NavigationRoute("Post/{id}/{highlightCommentId}") data object ModificationList : NavigationRoute("ModificationList") data object MyMessage : NavigationRoute("MyMessage") data object Comments : NavigationRoute("Comments") @@ -135,14 +135,21 @@ fun NavigationController( } composable( route = NavigationRoute.Post.route, - arguments = listOf(navArgument("id") { type = NavType.StringType }), + arguments = listOf( + navArgument("id") { type = NavType.StringType }, + navArgument("highlightCommentId") { type = NavType.IntType } + ), ) { backStackEntry -> CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { val id = backStackEntry.arguments?.getString("id") + val highlightCommentId = backStackEntry.arguments?.getInt("highlightCommentId")?.let { + if (it == 0) null else it + } PostScreen( - id!! + id!!, + highlightCommentId ) } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt index 511d38b..512dc1b 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt @@ -78,7 +78,7 @@ fun FavouriteListPage() { NavigationRoute.Post.route.replace( "{id}", momentItem.id.toString() - ) + ).replace("{highlightCommentId}", "0") ) } ) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt index 43aaec1..8f6e728 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt @@ -112,7 +112,6 @@ fun NotificationsScreen() { // 清除点赞消息数量 MessageListViewModel.clearLikeNoticeCount() } - navController.navigate(NavigationRoute.Likes.route) } NotificationIndicator( @@ -147,8 +146,7 @@ fun NotificationsScreen() { modifier = Modifier .fillMaxWidth() .weight(1f) - .padding(bottom = 48.dp) - , + .padding(bottom = 48.dp), contentAlignment = Alignment.Center ) { Column( @@ -177,10 +175,17 @@ fun NotificationsScreen() { CommentNoticeItem(comment) { MessageListViewModel.updateReadStatus(comment.id) MessageListViewModel.viewModelScope.launch { + var highlightCommentId = comment.id + comment.parentCommentId?.let { + highlightCommentId = it + } navController.navigate( NavigationRoute.Post.route.replace( "{id}", comment.postId.toString() + ).replace( + "{highlightCommentId}", + highlightCommentId.toString() ) ) } @@ -387,8 +392,12 @@ fun CommentNoticeItem( ) Spacer(modifier = Modifier.height(4.dp)) Row { + var text = commentItem.comment + if (commentItem.parentCommentId != null) { + text = "Reply you: $text" + } Text( - text = commentItem.comment, + text = text, fontSize = 14.sp, maxLines = 1, color = Color(0x99000000), @@ -406,13 +415,32 @@ fun CommentNoticeItem( } Spacer(modifier = Modifier.width(24.dp)) commentItem.post?.let { - CustomAsyncImage( - context = context, - imageUrl = it.images[0].thumbnail, - contentDescription = "Post Image", - modifier = Modifier - .size(48.dp) - ) + Box { + Box( + modifier = Modifier.padding(4.dp) + ) { + CustomAsyncImage( + context = context, + imageUrl = it.images[0].thumbnail, + contentDescription = "Post Image", + modifier = Modifier + .size(48.dp) + ) + // unread indicator + + } + + if (commentItem.unread) { + Box( + modifier = Modifier + .background(Color(0xFFE53935), CircleShape) + .size(12.dp) + .align(Alignment.TopEnd) + ) + } + } + + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt index bd9fee6..d3ca975 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt @@ -85,6 +85,7 @@ object MessageListViewModel : ViewModel() { viewModelScope.launch { commentService.updateReadStatus(id) updateIsRead(id) + updateUnReadCount(-1) } } @@ -98,4 +99,9 @@ object MessageListViewModel : ViewModel() { fun clearFavouriteNoticeCount() { noticeInfo = noticeInfo?.copy(favoriteCount = 0) } + fun updateUnReadCount(delta : Int) { + noticeInfo?.let { + noticeInfo = it.copy(commentCount = it.commentCount + delta) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt index 4932d3f..db82ca9 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt @@ -175,8 +175,11 @@ fun MomentCard( modifier = Modifier .fillMaxWidth() .noRippleClickable { -// PostViewModel.preTransit(momentEntity) - navController.navigate("Post/${momentEntity.id}") + navController.navigate( + route = NavigationRoute.Post.route + .replace("{id}", momentEntity.id.toString()) + .replace("{highlightCommentId}", "0") + ) } ) { MomentContentGroup( diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt index 7b482a3..26e28a0 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt @@ -813,7 +813,7 @@ fun MomentCardPicture(imageUrl: String, momentEntity: MomentEntity) { NavigationRoute.Post.route.replace( "{id}", momentEntity.id.toString() - ) + ).replace("{highlightCommentId}", "0") ) }, contentDescription = "", diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverScreen.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverScreen.kt index 5f8f1cb..11a5cdb 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverScreen.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/DiscoverScreen.kt @@ -163,7 +163,7 @@ fun DiscoverView() { NavigationRoute.Post.route.replace( "{id}", momentItem.id.toString() - ) + ).replace("{highlightCommentId}", "0") ) } ) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt b/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt index 265996f..033ec16 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt @@ -158,7 +158,7 @@ fun ActionPostNoticeItem( NavigationRoute.Post.route.replace( "{id}", postId.toString() - ) + ).replace("{highlightCommentId}", "0") ) } ) { @@ -196,7 +196,7 @@ fun LikeCommentNoticeItem( NavigationRoute.Post.route.replace( "{id}", it.toString() - ) + ).replace("{highlightCommentId}", "0") ) } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt index 0228133..8949499 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt @@ -38,6 +38,7 @@ import com.aiosman.riderpro.ui.composables.ActionButton import com.aiosman.riderpro.ui.composables.CheckboxWithLabel import com.aiosman.riderpro.ui.composables.StatusBarSpacer import com.aiosman.riderpro.ui.composables.TextInputField +import com.aiosman.riderpro.utils.Utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -155,6 +156,9 @@ fun EmailSignupScreen() { try { val resp = accountService.getMyAccount() AppState.UserId = resp.id + accountService.updateUserLanguage( + Utils.getCurrentLanguage() + ) Messaging.RegistDevice(scope, context) } catch (e: ServiceException) { scope.launch(Dispatchers.Main) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt index bfeb637..5cf0800 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt @@ -40,6 +40,7 @@ import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.composables.ActionButton import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.utils.GoogleLogin +import com.aiosman.riderpro.utils.Utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -84,6 +85,9 @@ fun SignupScreen() { // 获取token 信息 try { val resp = accountService.getMyAccount() + accountService.updateUserLanguage( + Utils.getCurrentLanguage() + ) AppState.UserId = resp.id Messaging.RegistDevice(coroutineScope, context) } catch (e: ServiceException) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt index 0645221..1544b82 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt @@ -31,6 +31,8 @@ class CommentsViewModel( var order: String by mutableStateOf("like") var addedCommentList by mutableStateOf>(emptyList()) var subCommentLoadingMap by mutableStateOf(mutableMapOf()) + var highlightCommentId by mutableStateOf(null) + var highlightComment by mutableStateOf(null) /** * 预加载,在跳转到 PostScreen 之前设置好内容 @@ -49,6 +51,9 @@ class CommentsViewModel( } } + /** + * 加载评论 + */ fun reloadComment() { viewModelScope.launch { try { @@ -68,18 +73,75 @@ class CommentsViewModel( } } - suspend fun likeComment(commentId: Int) { - commentService.likeComment(commentId) + + suspend fun highlightComment(commentId: Int) { + highlightCommentId = commentId + val resp = commentService.getCommentById(commentId) + highlightComment = resp + } + + /** + * 更新高亮评论点赞状态 + */ + private fun updateHighlightCommentLike(commentId: Int, isLike: Boolean): Boolean { + var isUpdate = false + highlightComment?.let { + if (it.id == commentId) { + highlightComment = + it.copy(liked = isLike, likes = if (isLike) it.likes + 1 else it.likes - 1) + isUpdate = true + } + highlightComment = it.copy( + reply = it.reply.map { replyComment -> + if (replyComment.id == commentId) { + isUpdate = true + replyComment.copy( + liked = isLike, + likes = if (isLike) replyComment.likes + 1 else replyComment.likes - 1 + ) + } else { + replyComment + } + } + ) + } + return isUpdate + } + + /** + * 更新添加的评论点赞状态 + */ + private fun updateAddedCommentLike(commentId: Int, isLike: Boolean): Boolean { + var isUpdate = false + addedCommentList = addedCommentList.map { + if (it.id == commentId) { + isUpdate = true + it.copy(liked = isLike, likes = if (isLike) it.likes + 1 else it.likes - 1) + } else { + it + } + } + return isUpdate + } + + /** + * 更新评论点赞状态 + */ + private fun updateCommentLike(commentId: Int, isLike: Boolean) { val currentPagingData = commentsFlow.value val updatedPagingData = currentPagingData.map { comment -> if (comment.id == commentId) { - comment.copy(liked = !comment.liked, likes = comment.likes + 1) + comment.copy( + liked = isLike, + likes = if (isLike) comment.likes + 1 else comment.likes - 1 + ) } else { // 可能是回复的评论 comment.copy(reply = comment.reply.map { replyComment -> if (replyComment.id == commentId) { replyComment.copy( - liked = !replyComment.liked, likes = replyComment.likes + 1 + liked = isLike, + likes = if (isLike) replyComment.likes + 1 else replyComment.likes - 1 ) } else { replyComment @@ -88,45 +150,40 @@ class CommentsViewModel( } } _commentsFlow.value = updatedPagingData - // 更新addCommentList - addedCommentList = addedCommentList.map { - if (it.id == commentId) { - it.copy(liked = !it.liked, likes = it.likes + 1) - } else { - it + } + + /** + * 点赞评论 + */ + suspend fun likeComment(commentId: Int) { + try { + commentService.likeComment(commentId) + // 更新addCommentList + if (updateHighlightCommentLike(commentId, true)) { + return } + if (updateAddedCommentLike(commentId, true)) { + return + } + updateCommentLike(commentId, true) + } catch (e: Exception) { + e.printStackTrace() } } + // 取消点赞评论 suspend fun unlikeComment(commentId: Int) { commentService.dislikeComment(commentId) - val currentPagingData = commentsFlow.value - val updatedPagingData = currentPagingData.map { comment -> - if (comment.id == commentId) { - comment.copy(liked = !comment.liked, likes = comment.likes - 1) - } else { - // 可能是回复的评论 - comment.copy(reply = comment.reply.map { replyComment -> - if (replyComment.id == commentId) { - replyComment.copy( - liked = !replyComment.liked, likes = replyComment.likes - 1 - ) - } else { - replyComment - } - }) - } + // 更新高亮评论点赞状态 + if (updateHighlightCommentLike(commentId, false)) { + return } - _commentsFlow.value = updatedPagingData - - // 更新addCommentList - addedCommentList = addedCommentList.map { - if (it.id == commentId) { - it.copy(liked = !it.liked, likes = it.likes - 1) - } else { - it - } + // 更新添加的评论点赞状态 + if (updateAddedCommentLike(commentId, false)) { + return } + // 更新评论点赞状态 + updateCommentLike(commentId, false) } suspend fun createComment( @@ -163,31 +220,52 @@ class CommentsViewModel( } fun loadMoreSubComments(commentId: Int) { - viewModelScope.launch { - val currentPagingData = commentsFlow.value - val updatedPagingData = currentPagingData.map { comment -> - if (comment.id == commentId) { - try { - subCommentLoadingMap[commentId] = true - val subCommentList = commentService.getComments( - postId = postId.toInt(), - parentCommentId = commentId, - pageNumber = comment.replyPage + 1, - pageSize = 3, - ).list - return@map comment.copy( - reply = comment.reply.plus(subCommentList), - replyPage = comment.replyPage + 1 - ) - } catch (e: Exception) { - return@map comment.copy() - } finally { - subCommentLoadingMap[commentId] = false - } + if (highlightComment?.id == commentId) { + // 高亮的评论,更新高亮评论的回复 + highlightComment?.let { + viewModelScope.launch { + val subCommentList = commentService.getComments( + postId = postId.toInt(), + parentCommentId = commentId, + pageNumber = it.replyPage + 1, + pageSize = 3, + ).list + highlightComment = it.copy( + reply = it.reply.plus(subCommentList), + replyPage = it.replyPage + 1 + ) } - comment } - _commentsFlow.value = updatedPagingData + } else { + // 普通评论 + viewModelScope.launch { + val currentPagingData = commentsFlow.value + val updatedPagingData = currentPagingData.map { comment -> + if (comment.id == commentId) { + try { + subCommentLoadingMap[commentId] = true + val subCommentList = commentService.getComments( + postId = postId.toInt(), + parentCommentId = commentId, + pageNumber = comment.replyPage + 1, + pageSize = 3, + order = "earliest" + ).list + return@map comment.copy( + reply = comment.reply.plus(subCommentList), + replyPage = comment.replyPage + 1 + ) + } catch (e: Exception) { + return@map comment.copy() + } finally { + subCommentLoadingMap[commentId] = false + } + } + comment + } + _commentsFlow.value = updatedPagingData + } } + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt index c502db9..807ec6d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt @@ -3,7 +3,6 @@ package com.aiosman.riderpro.ui.post import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -28,7 +27,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState @@ -51,7 +49,6 @@ 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.focus.focusModifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale @@ -103,6 +100,7 @@ import kotlinx.coroutines.launch @Composable fun PostScreen( id: String, + highlightCommentId: Int? ) { val viewModel = viewModel( key = "PostViewModel_$id", @@ -124,7 +122,11 @@ fun PostScreen( skipPartiallyExpanded = true ) LaunchedEffect(Unit) { - viewModel.initData() + viewModel.initData( + highlightCommentId = highlightCommentId.let { + if (it == 0) null else it + } + ) } if (showCommentMenu) { @@ -147,7 +149,6 @@ fun PostScreen( contextComment?.let { viewModel.deleteComment(it.id) } - }, commentEntity = contextComment, onCloseClick = { @@ -405,7 +406,44 @@ fun CommentContent( val addedTopLevelComment = viewModel.addedCommentList.filter { it.parentCommentId == null } + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .animateContentSize(animationSpec = tween(durationMillis = 500)) + ) { + viewModel.highlightComment?.let { + CommentItem( + it, + onLike = { comment -> + viewModel.viewModelScope.launch { + if (comment.liked) { + viewModel.unlikeComment(comment.id) + } else { + viewModel.likeComment(comment.id) + } + } + }, + onLongClick = { comment -> + onLongClick(comment) + }, + onReply = { parentComment, _, _, _ -> + onReply( + parentComment, + parentComment.author, + parentComment.name, + parentComment.avatar + ) + }, + onLoadMoreSubComments = { + viewModel.viewModelScope.launch { + viewModel.loadMoreSubComments(it.id) + } + }, + ) + } + + } Column( modifier = Modifier .padding(horizontal = 16.dp) @@ -454,45 +492,48 @@ fun CommentContent( for (idx in 0 until commentsPagging.itemCount) { val item = commentsPagging[idx] ?: return - AnimatedVisibility( - visible = true, - enter = slideInVertically(), - exit = slideOutVertically() - ) { - Box( - modifier = Modifier.padding(horizontal = 16.dp) + if (item.id != viewModel.highlightCommentId) { + AnimatedVisibility( + visible = true, + enter = slideInVertically(), + exit = slideOutVertically() ) { - CommentItem( - item, - onLike = { comment -> - viewModel.viewModelScope.launch { - if (comment.liked) { - viewModel.unlikeComment(comment.id) - } else { - viewModel.likeComment(comment.id) + Box( + modifier = Modifier.padding(horizontal = 16.dp) + ) { + CommentItem( + item, + onLike = { comment -> + viewModel.viewModelScope.launch { + if (comment.liked) { + viewModel.unlikeComment(comment.id) + } else { + viewModel.likeComment(comment.id) + } } - } - }, - onLongClick = { comment -> - onLongClick(comment) - }, - onReply = { parentComment, _, _, _ -> - onReply( - parentComment, - parentComment.author, - parentComment.name, - parentComment.avatar - ) - }, - onLoadMoreSubComments = { - viewModel.viewModelScope.launch { - viewModel.loadMoreSubComments(it.id) - } - }, - addedCommentList = viewModel.addedCommentList - ) + }, + onLongClick = { comment -> + onLongClick(comment) + }, + onReply = { parentComment, _, _, _ -> + onReply( + parentComment, + parentComment.author, + parentComment.name, + parentComment.avatar + ) + }, + onLoadMoreSubComments = { + viewModel.viewModelScope.launch { + viewModel.loadMoreSubComments(it.id) + } + }, + addedCommentList = viewModel.addedCommentList + ) + } } } + } // Handle loading and error states as before @@ -932,11 +973,13 @@ fun CommentItem( } Spacer(modifier = Modifier.height(8.dp)) Column( - modifier = Modifier.padding(start = 12.dp + 40.dp).animateContentSize( - animationSpec = tween( - durationMillis = 500 + modifier = Modifier + .padding(start = 12.dp + 40.dp) + .animateContentSize( + animationSpec = tween( + durationMillis = 500 + ) ) - ) ) { val addedCommentList = addedCommentList.filter { it.parentCommentId == commentEntity.id } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt index 4ec60ba..0bc4301 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt @@ -29,20 +29,28 @@ class PostViewModel( var accountService: AccountService = AccountServiceImpl() var commentsViewModel: CommentsViewModel = CommentsViewModel(postId) var isError by mutableStateOf(false) - + var isFirstLoad by mutableStateOf(true) fun reloadComment() { commentsViewModel.reloadComment() } - suspend fun initData() { + suspend fun initData(highlightCommentId: Int? = null) { + if (!isFirstLoad) { + return + } + isFirstLoad = false try { moment = service.getMomentById(postId.toInt()) } catch (e: Exception) { isError = true return } + highlightCommentId?.let { + commentsViewModel.highlightComment(it) + } commentsViewModel.reloadComment() + } suspend fun likeComment(commentId: Int) { diff --git a/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt b/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt index 1d749b0..ddec716 100644 --- a/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt +++ b/app/src/main/java/com/aiosman/riderpro/utils/Utils.kt @@ -8,6 +8,7 @@ import coil.request.CachePolicy import com.aiosman.riderpro.data.api.AuthInterceptor import com.aiosman.riderpro.data.api.getUnsafeOkHttpClient import java.util.Date +import java.util.Locale import java.util.concurrent.TimeUnit object Utils { @@ -56,4 +57,8 @@ object Utils { else -> "$years years ago" } } + + fun getCurrentLanguage(): String { + return Locale.getDefault().language + } } \ No newline at end of file