From 0730fdea68604c5e2bc37df1bc1c7a494c84948d Mon Sep 17 00:00:00 2001 From: AllenTom Date: Tue, 30 Jul 2024 14:28:13 +0800 Subject: [PATCH] =?UTF-8?q?=E7=82=B9=E8=B5=9E=E5=92=8C=E8=AF=84=E8=AE=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiosman/riderpro/data/CommentService.kt | 20 +- .../aiosman/riderpro/data/MomentService.kt | 31 ++- .../com/aiosman/riderpro/model/MomentItem.kt | 3 +- .../com/aiosman/riderpro/test/TestDatabase.kt | 23 +- .../riderpro/ui/comment/CommentModal.kt | 16 +- .../ui/composables/AnimatedLikeButton.kt | 4 +- .../ui/composables/EditCommentBottomModal.kt | 22 +- .../riderpro/ui/index/tabs/moment/Moment.kt | 75 +++++-- .../ui/index/tabs/moment/MomentViewModel.kt | 53 ++++- .../java/com/aiosman/riderpro/ui/post/Post.kt | 203 ++++++++++++++---- 10 files changed, 357 insertions(+), 93 deletions(-) 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 dcfc230..246a69e 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt @@ -12,6 +12,7 @@ interface CommentService { suspend fun getComments(pageNumber: Int, postId: Int? = null): ListContainer suspend fun createComment(postId: Int, content: String, authorId: Int): Comment suspend fun likeComment(commentId: Int) + suspend fun dislikeComment(commentId: Int) } @@ -25,7 +26,7 @@ data class Comment( val postId: Int = 0, val avatar: String, val author: Int, - val liked: Boolean, + var liked: Boolean, ) class CommentPagingSource( @@ -82,6 +83,12 @@ class TestCommentServiceImpl : CommentService { ) } rawList = rawList.sortedBy { -it.id } + rawList.forEach { + val myLikeIdList = TestDatabase.likeCommentList.filter { it.second == 1 }.map { it.first } + if (myLikeIdList.contains(it.id)) { + it.liked = true + } + } val currentSublist = rawList.subList(from, min(to, rawList.size)) return ListContainer( total = rawList.size, @@ -121,7 +128,18 @@ class TestCommentServiceImpl : CommentService { it } } + TestDatabase.likeCommentList += Pair(commentId, 1) + } + override suspend fun dislikeComment(commentId: Int) { + TestDatabase.comment = TestDatabase.comment.map { + if (it.id == commentId) { + it.copy(likes = it.likes - 1) + } else { + it + } + } + TestDatabase.likeCommentList = TestDatabase.likeCommentList.filter { it.first != commentId } } companion object { diff --git a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt index 7766ed1..0de1b98 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt @@ -10,6 +10,7 @@ import kotlin.math.min interface MomentService { suspend fun getMomentById(id: Int): MomentItem suspend fun likeMoment(id: Int) + suspend fun dislikeMoment(id: Int) suspend fun getMoments( pageNumber: Int, author: Int? = null, @@ -81,6 +82,10 @@ class TestMomentServiceImpl() : MomentService { testMomentBackend.likeMoment(id) } + override suspend fun dislikeMoment(id: Int) { + testMomentBackend.dislikeMoment(id) + } + } class TestMomentBackend( @@ -113,6 +118,12 @@ class TestMomentBackend( ) } val currentSublist = rawList.subList(from, min(to, rawList.size)) + currentSublist.forEach { + val myLikeIdList = TestDatabase.likeMomentList.filter { it.second == 1 }.map { it.first } + if (myLikeIdList.contains(it.id)) { + it.liked = true + } + } // delay kotlinx.coroutines.delay(loadDelay) return ListContainer( @@ -124,7 +135,14 @@ class TestMomentBackend( } suspend fun getMomentById(id: Int): MomentItem { - return TestDatabase.momentData[id] + var moment = TestDatabase.momentData.first { + it.id == id + } + val isLike = TestDatabase.likeMomentList.any { + it.first == id && it.second == 1 + } + moment = moment.copy(liked = isLike) + return moment } suspend fun likeMoment(id: Int) { @@ -133,6 +151,17 @@ class TestMomentBackend( } val newMoment = oldMoment.copy(likeCount = oldMoment.likeCount + 1) TestDatabase.updateMomentById(id, newMoment) + TestDatabase.likeMomentList += Pair(id, 1) + } + suspend fun dislikeMoment(id: Int) { + val oldMoment = TestDatabase.momentData.first { + it.id == id + } + val newMoment = oldMoment.copy(likeCount = oldMoment.likeCount - 1) + TestDatabase.updateMomentById(id, newMoment) + TestDatabase.likeMomentList = TestDatabase.likeMomentList.filter { + it.first != id + } } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/model/MomentItem.kt b/app/src/main/java/com/aiosman/riderpro/model/MomentItem.kt index 323b07c..35aaff3 100644 --- a/app/src/main/java/com/aiosman/riderpro/model/MomentItem.kt +++ b/app/src/main/java/com/aiosman/riderpro/model/MomentItem.kt @@ -17,5 +17,6 @@ data class MomentItem( val shareCount: Int, val favoriteCount: Int, val images: List = emptyList(), - val authorId: Int = 0 + val authorId: Int = 0, + var liked: Boolean = false, ) diff --git a/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt b/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt index 1c1b4cf..93d4d90 100644 --- a/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt +++ b/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt @@ -39,13 +39,14 @@ object TestDatabase { ) var followList = emptyList>() var likeCommentList = emptyList>() + var likeMomentList = emptyList>() init { val faker = faker { this.fakerConfig { locale = "en" } } - accountData = (0..100).toList().mapIndexed { idx, _ -> + accountData = (0..20).toList().mapIndexed { idx, _ -> AccountProfile( id = idx, followerCount = 0, @@ -58,7 +59,7 @@ object TestDatabase { ) } // make a random follow rel - for (i in 0..500) { + for (i in 0..100) { var person1 = accountData.random() var persion2 = accountData.random() followList += Pair(person1.id, persion2.id) @@ -74,13 +75,14 @@ object TestDatabase { } } - momentData = (0..200).toList().mapIndexed { idx, _ -> + momentData = (0..60).toList().mapIndexed { idx, _ -> val person = accountData.random() // make fake comment - for (i in 0..faker.random.nextInt(0, 5)) { + val commentCount = faker.random.nextInt(0, 50) + for (i in 0..commentCount) { commentIdCounter += 1 val commentPerson = accountData.random() - var newComment = Comment( + var newComment = Comment( name = commentPerson.nickName, comment = "this is comment ${commentIdCounter}", date = "2023-02-02 11:23", @@ -94,12 +96,17 @@ object TestDatabase { ) // generate like comment list for (likeIdx in 0..faker.random.nextInt(0, 5)) { - val likePerson = accountData.random() + val likePerson = accountData.random() likeCommentList += Pair(commentIdCounter, likePerson.id) newComment = newComment.copy(likes = newComment.likes + 1) } comment += newComment } + val likeCount = faker.random.nextInt(0, 5) + for (i in 0..likeCount) { + val likePerson = accountData.random() + likeMomentList += Pair(idx, likePerson.id) + } MomentItem( id = idx, avatar = person.avatar, @@ -109,8 +116,8 @@ object TestDatabase { followStatus = false, momentTextContent = "By strongarming Ducati into giving him the factory seat.Marquez effectively …", momentPicture = R.drawable.default_moment_img, - likeCount = faker.random.nextInt(0, 100), - commentCount = faker.random.nextInt(0, 100), + likeCount = likeCount, + commentCount = commentCount + 1, shareCount = faker.random.nextInt(0, 100), favoriteCount = faker.random.nextInt(0, 100), images = imageList.shuffled().take(3), diff --git a/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt b/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt index 90e3552..0ebc788 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt @@ -58,8 +58,8 @@ import kotlinx.coroutines.launch class CommentModalViewModel( postId: Int? -):ViewModel(){ - val commentService:CommentService = TestCommentServiceImpl() +) : ViewModel() { + val commentService: CommentService = TestCommentServiceImpl() val commentsFlow: Flow> = Pager( config = PagingConfig(pageSize = 20, enablePlaceholders = false), pagingSourceFactory = { @@ -70,10 +70,16 @@ class CommentModalViewModel( } ).flow.cachedIn(viewModelScope) } + @Preview @Composable -fun CommentModalContent(postId: Int? = null, onDismiss: () -> Unit = {}) { +fun CommentModalContent( + postId: Int? = null, + onCommentAdded: () -> Unit = {}, + onDismiss: () -> Unit = {} +) { val model = viewModel( + key = "CommentModalViewModel_$postId", factory = object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return CommentModalViewModel(postId) as T @@ -102,6 +108,7 @@ fun CommentModalContent(postId: Int? = null, onDismiss: () -> Unit = {}) { commentText = "" } comments.refresh() + onCommentAdded() } Column( modifier = Modifier @@ -129,8 +136,7 @@ fun CommentModalContent(postId: Int? = null, onDismiss: () -> Unit = {}) { .padding(horizontal = 16.dp) .weight(1f) ) { - CommentsSection(lazyPagingItems = comments, onLike = { - comment: Comment -> + CommentsSection(lazyPagingItems = comments, onLike = { comment: Comment -> scope.launch { model.commentService.likeComment(comment.id) comments.refresh() diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/AnimatedLikeButton.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/AnimatedLikeButton.kt index b8c05b4..e679b54 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/AnimatedLikeButton.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/AnimatedLikeButton.kt @@ -24,9 +24,9 @@ import kotlinx.coroutines.launch @Composable fun AnimatedLikeIcon( modifier: Modifier = Modifier, + liked: Boolean = false, onClick: (() -> Unit)? = null ) { - var liked by remember { mutableStateOf(false) } val animatableRotation = remember { Animatable(0f) } val animatedColor by animateColorAsState(targetValue = if (liked) Color(0xFFd83737) else Color.Black) val scope = rememberCoroutineScope() @@ -52,13 +52,11 @@ fun AnimatedLikeIcon( ) } Box(contentAlignment = Alignment.Center, modifier = Modifier.noRippleClickable { - liked = !liked onClick?.invoke() // Trigger shake animation scope.launch { shake() } - }) { Image( painter = painterResource(id = R.drawable.rider_pro_like), diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/EditCommentBottomModal.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/EditCommentBottomModal.kt index d9e4a98..903ec8a 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/EditCommentBottomModal.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/EditCommentBottomModal.kt @@ -6,13 +6,16 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -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.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -22,9 +25,11 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.aiosman.riderpro.R +import com.aiosman.riderpro.ui.modifiers.noRippleClickable @Composable -fun EditCommentBottomModal() { +fun EditCommentBottomModal(onSend: (String) -> Unit = {}) { + var text by remember { mutableStateOf("") } Box( modifier = Modifier .fillMaxWidth() @@ -49,8 +54,10 @@ fun EditCommentBottomModal() { ) { BasicTextField( - value = "", - onValueChange = { }, + value = text, + onValueChange = { + text = it + }, modifier = Modifier .fillMaxWidth(), textStyle = TextStyle( @@ -67,10 +74,11 @@ fun EditCommentBottomModal() { contentDescription = "Send", modifier = Modifier .size(32.dp) - + .noRippleClickable { + onSend(text) + text = "" + }, ) } - - } } \ 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 b377b08..7210494 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 @@ -58,23 +58,30 @@ import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.launch -@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun MomentsList() { val model = MomentViewModel var dataFlow = model.momentsFlow var moments = dataFlow.collectAsLazyPagingItems() val scope = rememberCoroutineScope() - LazyColumn { items(moments.itemCount) { idx -> val momentItem = moments[idx] ?: return@items - MomentCard(momentItem = momentItem, onLikeClick = { - scope.launch { - model.likeMoment(momentItem.id) -// moments.refresh() - } - }) + MomentCard(momentItem = momentItem, + onAddComment = { + scope.launch { + model.onAddComment(momentItem.id) + } + }, + onLikeClick = { + scope.launch { + if (momentItem.liked) { + model.dislikeMoment(momentItem.id) + } else { + model.likeMoment(momentItem.id) + } + } + }) } } @@ -83,7 +90,8 @@ fun MomentsList() { @Composable fun MomentCard( momentItem: MomentItem, - onLikeClick: () -> Unit + onLikeClick: () -> Unit, + onAddComment: () -> Unit = {} ) { val navController = LocalNavController.current Column( @@ -103,7 +111,12 @@ fun MomentCard( .fillMaxHeight() .weight(1f) ModificationListHeader() - MomentBottomOperateRowGroup(momentOperateBtnBoxModifier, momentItem = momentItem, onLikeClick = onLikeClick) + MomentBottomOperateRowGroup( + momentOperateBtnBoxModifier, + momentItem = momentItem, + onLikeClick = onLikeClick, + onAddComment = onAddComment + ) } } @@ -214,9 +227,16 @@ fun MomentTopRowGroup(momentItem: MomentItem) { AsyncImage( momentItem.avatar, contentDescription = "", - modifier = Modifier.size(40.dp).noRippleClickable { - navController.navigate(NavigationRoute.AccountProfile.route.replace("{id}", momentItem.authorId.toString())) - }, + modifier = Modifier + .size(40.dp) + .noRippleClickable { + navController.navigate( + NavigationRoute.AccountProfile.route.replace( + "{id}", + momentItem.authorId.toString() + ) + ) + }, contentScale = ContentScale.Crop ) Column( @@ -310,6 +330,7 @@ fun MomentOperateBtn(count: String, content: @Composable () -> Unit) { fun MomentBottomOperateRowGroup( modifier: Modifier, onLikeClick: () -> Unit = {}, + onAddComment: () -> Unit = {}, momentItem: MomentItem ) { var systemUiController = rememberSystemUiController() @@ -323,7 +344,10 @@ fun MomentBottomOperateRowGroup( ) ) { systemUiController.setNavigationBarColor(Color(0xfff7f7f7)) - CommentModalContent(postId = momentItem.id) { + CommentModalContent(postId = momentItem.id, onCommentAdded = { + showCommentModal = false + onAddComment() + }) { systemUiController.setNavigationBarColor(Color.Black) } } @@ -338,8 +362,14 @@ fun MomentBottomOperateRowGroup( contentAlignment = Alignment.Center ) { MomentOperateBtn(count = momentItem.likeCount.toString()) { - AnimatedLikeIcon(modifier = Modifier.size(24.dp)) { + AnimatedLikeIcon( + modifier = Modifier.size(24.dp), + liked = momentItem.liked + ) { + onLikeClick() + + } } } @@ -352,19 +382,28 @@ fun MomentBottomOperateRowGroup( }, contentAlignment = Alignment.Center ) { - MomentOperateBtn(icon = R.drawable.rider_pro_moment_comment, count = momentItem.commentCount.toString()) + MomentOperateBtn( + icon = R.drawable.rider_pro_moment_comment, + count = momentItem.commentCount.toString() + ) } Box( modifier = modifier, contentAlignment = Alignment.Center ) { - MomentOperateBtn(icon = R.drawable.rider_pro_share, count = momentItem.shareCount.toString()) + MomentOperateBtn( + icon = R.drawable.rider_pro_share, + count = momentItem.shareCount.toString() + ) } Box( modifier = modifier, contentAlignment = Alignment.Center ) { - MomentOperateBtn(icon = R.drawable.rider_pro_favoriate, count = momentItem.favoriteCount.toString()) + MomentOperateBtn( + icon = R.drawable.rider_pro_favoriate, + count = momentItem.favoriteCount.toString() + ) } } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/MomentViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/MomentViewModel.kt index c84bd7b..c924b9f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/MomentViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/MomentViewModel.kt @@ -31,22 +31,23 @@ object MomentViewModel : ViewModel() { val profile = accountService.getMyAccountProfile() Pager( config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { MomentPagingSource( - MomentRemoteDataSource(momentService), - timelineId = profile.id - ) } + pagingSourceFactory = { + MomentPagingSource( + MomentRemoteDataSource(momentService), + timelineId = profile.id + ) + } ).flow.cachedIn(viewModelScope).collectLatest { _momentsFlow.value = it } } } - suspend fun likeMoment(id: Int) { - momentService.likeMoment(id) + fun updateLikeCount(id: Int) { val currentPagingData = _momentsFlow.value val updatedPagingData = currentPagingData.map { momentItem -> if (momentItem.id == id) { - momentItem.copy(likeCount = momentItem.likeCount + 1) + momentItem.copy(likeCount = momentItem.likeCount + 1, liked = true) } else { momentItem } @@ -54,4 +55,42 @@ object MomentViewModel : ViewModel() { _momentsFlow.value = updatedPagingData } + suspend fun likeMoment(id: Int) { + momentService.likeMoment(id) + updateLikeCount(id) + } + + fun updateCommentCount(id: Int) { + val currentPagingData = _momentsFlow.value + val updatedPagingData = currentPagingData.map { momentItem -> + if (momentItem.id == id) { + momentItem.copy(commentCount = momentItem.commentCount + 1) + } else { + momentItem + } + } + _momentsFlow.value = updatedPagingData + } + suspend fun onAddComment(id: Int) { + val currentPagingData = _momentsFlow.value + updateCommentCount(id) + } + + fun updateDislikeMomentById(id: Int) { + val currentPagingData = _momentsFlow.value + val updatedPagingData = currentPagingData.map { momentItem -> + if (momentItem.id == id) { + momentItem.copy(likeCount = momentItem.likeCount - 1, liked = false) + } else { + momentItem + } + } + _momentsFlow.value = updatedPagingData + } + + suspend fun dislikeMoment(id: Int) { + momentService.dislikeMoment(id) + updateDislikeMomentById(id) + } + } \ 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 37620c5..3b828e9 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 @@ -58,11 +58,17 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import androidx.paging.cachedIn import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.map import coil.compose.AsyncImage import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R @@ -71,66 +77,154 @@ import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.Comment import com.aiosman.riderpro.data.CommentPagingSource import com.aiosman.riderpro.data.CommentRemoteDataSource +import com.aiosman.riderpro.data.CommentService import com.aiosman.riderpro.data.TestCommentServiceImpl import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.TestAccountServiceImpl import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.model.MomentItem +import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder import com.aiosman.riderpro.ui.composables.EditCommentBottomModal +import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.google.accompanist.systemuicontroller.rememberSystemUiController -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch - -@OptIn(ExperimentalSharedTransitionApi::class) -@Composable -fun PostScreen( - id: String, -) { +class PostViewModel( + val postId: String +) : ViewModel() { var service: MomentService = TestMomentServiceImpl() + var commentService: CommentService = TestCommentServiceImpl() + private var _commentsFlow = MutableStateFlow>(PagingData.empty()) + val commentsFlow = _commentsFlow.asStateFlow() - var commentSource = CommentPagingSource( - CommentRemoteDataSource(TestCommentServiceImpl()) - ) - val commentsFlow: Flow> = Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { commentSource } - ).flow - val lazyPagingItems = commentsFlow.collectAsLazyPagingItems() - var showCollapseContent by remember { mutableStateOf(true) } - val scrollState = rememberLazyListState() - val uiController = rememberSystemUiController() - var moment by remember { mutableStateOf(null) } - var accountProfile by remember { mutableStateOf(null) } + init { + viewModelScope.launch { + Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + CommentPagingSource( + CommentRemoteDataSource(commentService), + postId = postId.toInt() + ) + } + ).flow.cachedIn(viewModelScope).collectLatest { + _commentsFlow.value = it + } + } + + } + + var accountProfile by mutableStateOf(null) + var moment by mutableStateOf(null) var accountService: AccountService = TestAccountServiceImpl() - LaunchedEffect(Unit) { - uiController.setNavigationBarColor(Color.White) - moment = service.getMomentById(id.toInt()) + + suspend fun initData() { + moment = service.getMomentById(postId.toInt()) moment?.let { accountProfile = accountService.getAccountProfileById(it.authorId) } } + + suspend fun likeComment(commentId: Int) { + commentService.likeComment(commentId) + val currentPagingData = commentsFlow.value + val updatedPagingData = currentPagingData.map { comment -> + if (comment.id == commentId) { + comment.copy(liked = !comment.liked) + } else { + comment + } + } + _commentsFlow.value = updatedPagingData + } + + suspend fun createComment(content: String) { + commentService.createComment(postId.toInt(), content, 1) + MomentViewModel.updateCommentCount(postId.toInt()) + } + + suspend fun likeMoment() { + moment?.let { + service.likeMoment(it.id) + moment = moment?.copy(likeCount = moment?.likeCount?.plus(1) ?: 0, liked = true) + MomentViewModel.updateLikeCount(it.id) + } + } + + suspend fun dislikeMoment() { + moment?.let { + service.dislikeMoment(it.id) + moment = moment?.copy(likeCount = moment?.likeCount?.minus(1) ?: 0, liked = false) + // update home list + MomentViewModel.updateDislikeMomentById(it.id) + } + } +} + +@Composable +fun PostScreen( + id: String, +) { + val viewModel = viewModel( + key = "PostViewModel_$id", + factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return PostViewModel(id) as T + } + } + ) + val scope = rememberCoroutineScope() + + val commentsPagging = viewModel.commentsFlow.collectAsLazyPagingItems() + var showCollapseContent by remember { mutableStateOf(true) } + val scrollState = rememberLazyListState() + val uiController = rememberSystemUiController() + LaunchedEffect(Unit) { + uiController.setNavigationBarColor(Color.White) + viewModel.initData() + } StatusBarMaskLayout { Scaffold( modifier = Modifier.fillMaxSize(), - bottomBar = { BottomNavigationBar() } + bottomBar = { + BottomNavigationBar( + onLikeClick = { + scope.launch { + if (viewModel.moment?.liked == true) { + viewModel.dislikeMoment() + } else { + viewModel.likeMoment() + } + } + }, + onCreateComment = { + scope.launch { + viewModel.createComment(it) + commentsPagging.refresh() + } + }, + momentItem = viewModel.moment + ) + } ) { it Column( modifier = Modifier .fillMaxSize() ) { - Header(accountProfile) + Header(viewModel.accountProfile) Column(modifier = Modifier.animateContentSize()) { AnimatedVisibility(visible = showCollapseContent) { // collapse content Column( modifier = Modifier .fillMaxWidth() - ) { Box( modifier = Modifier @@ -140,13 +234,13 @@ fun PostScreen( ) { PostImageView( id, - moment?.images ?: emptyList() + viewModel.moment?.images ?: emptyList() ) } PostDetails( id, - moment + viewModel.moment ) } } @@ -156,7 +250,14 @@ fun PostScreen( .fillMaxWidth() ) { - CommentsSection(lazyPagingItems = lazyPagingItems, scrollState, onLike = {}) { + CommentsSection( + lazyPagingItems = commentsPagging, + scrollState, + onLike = { comment: Comment -> + scope.launch { + viewModel.likeComment(comment.id) + } + }) { showCollapseContent = it } } @@ -182,7 +283,6 @@ fun Header(accountProfile: AccountProfile?) { navController.popBackStack() } .size(32.dp) - ) Spacer(modifier = Modifier.width(8.dp)) accountProfile?.let { @@ -191,7 +291,15 @@ fun Header(accountProfile: AccountProfile?) { contentDescription = "Profile Picture", modifier = Modifier .size(40.dp) - .clip(CircleShape), + .clip(CircleShape) + .noRippleClickable { + navController.navigate( + NavigationRoute.AccountProfile.route.replace( + "{id}", + accountProfile.id.toString() + ) + ) + }, contentScale = ContentScale.Crop ) } @@ -298,11 +406,9 @@ fun PostDetails( ) Text(text = "12-11 发布") Spacer(modifier = Modifier.height(8.dp)) - Text(text = "共231条评论") + Text(text = "${momentItem?.commentCount ?: 0} Comments") } - - } @Composable @@ -319,7 +425,7 @@ fun CommentsSection( ) { items(lazyPagingItems.itemCount) { idx -> val item = lazyPagingItems[idx] ?: return@items - CommentItem(item,onLike={ + CommentItem(item, onLike = { onLike(item) }) } @@ -339,7 +445,7 @@ fun CommentsSection( @Composable -fun CommentItem(comment: Comment,onLike:()->Unit = {}) { +fun CommentItem(comment: Comment, onLike: () -> Unit = {}) { Column { Row(modifier = Modifier.padding(vertical = 8.dp)) { AsyncImage( @@ -361,7 +467,11 @@ fun CommentItem(comment: Comment,onLike:()->Unit = {}) { IconButton(onClick = { onLike() }) { - Icon(Icons.Filled.Favorite, contentDescription = "Like") + Icon( + Icons.Filled.Favorite, + contentDescription = "Like", + tint = if (comment.liked) Color.Red else Color.Gray + ) } Text(text = comment.likes.toString()) } @@ -379,7 +489,11 @@ fun CommentItem(comment: Comment,onLike:()->Unit = {}) { @OptIn(ExperimentalMaterial3Api::class) @Composable -fun BottomNavigationBar() { +fun BottomNavigationBar( + onCreateComment: (String) -> Unit = {}, + onLikeClick: () -> Unit = {}, + momentItem: MomentItem? +) { val systemUiController = rememberSystemUiController() var showCommentModal by remember { mutableStateOf(false) } if (showCommentModal) { @@ -392,7 +506,10 @@ fun BottomNavigationBar() { dragHandle = {}, shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), ) { - EditCommentBottomModal() + EditCommentBottomModal() { + onCreateComment(it) + showCommentModal = false + } } } Column( @@ -427,10 +544,12 @@ fun BottomNavigationBar() { } IconButton( - onClick = { /*TODO*/ }) { - Icon(Icons.Filled.Favorite, contentDescription = "Send") + onClick = { + onLikeClick() + }) { + Icon(Icons.Filled.Favorite, contentDescription = "like", tint = if (momentItem?.liked == true) Color.Red else Color.Gray) } - Text(text = "2077") + Text(text = momentItem?.likeCount.toString()) IconButton( onClick = { /*TODO*/ }) { Icon(Icons.Filled.Star, contentDescription = "Send")