From 53c71973ae4f701f3dda484bedcc4861daf68052 Mon Sep 17 00:00:00 2001 From: AllenTom Date: Mon, 29 Jul 2024 16:50:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E6=96=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiosman/riderpro/data/AccountService.kt | 21 ++-- .../aiosman/riderpro/data/CommentService.kt | 101 +++++++++++------ .../aiosman/riderpro/data/MomentService.kt | 106 ++++++++++++------ .../com/aiosman/riderpro/test/TestDatabase.kt | 61 +++++++++- .../riderpro/ui/comment/CommentModal.kt | 71 +++++++++--- .../riderpro/ui/index/tabs/moment/Moment.kt | 6 +- .../ui/index/tabs/moment/MomentViewModel.kt | 54 ++++++--- .../index/tabs/profile/MyProfileViewModel.kt | 22 ++-- .../riderpro/ui/index/tabs/profile/Profile.kt | 29 ++--- .../java/com/aiosman/riderpro/ui/post/Post.kt | 87 ++++++++------ .../riderpro/ui/profile/AccountProfile.kt | 29 +++-- 11 files changed, 398 insertions(+), 189 deletions(-) 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 cd08805..0287636 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt @@ -1,5 +1,7 @@ package com.aiosman.riderpro.data +import com.aiosman.riderpro.test.TestDatabase + data class AccountProfile( val id: Int, val followerCount: Int, @@ -11,19 +13,16 @@ data class AccountProfile( ) interface AccountService { - suspend fun getAccountProfile(): AccountProfile + suspend fun getMyAccountProfile(): AccountProfile + suspend fun getAccountProfileById(id: Int): AccountProfile } class TestAccountServiceImpl : AccountService { - override suspend fun getAccountProfile(): AccountProfile { - return AccountProfile( - id = 1, - followerCount = 100, - followingCount = 200, - nickName = "Aiosman", - avatar = "https://img.freepik.com/free-photo/white-billboard-template_23-2147726635.jpg?t=st=1722150015~exp=1722153615~hmac=5540620196d7898215d822be26353c87a63d51bbfb2b814e032626e1948a1583&w=740", - bio = "I am a software engineer", - country = "Nigeria" - ) + override suspend fun getMyAccountProfile(): AccountProfile { + return TestDatabase.accountData.first { it.id == 0 } + } + + override suspend fun getAccountProfileById(id: Int): AccountProfile { + return TestDatabase.accountData.first { it.id == id } } } \ 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 f332855..dcfc230 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt @@ -2,28 +2,43 @@ package com.aiosman.riderpro.data import androidx.paging.PagingSource import androidx.paging.PagingState +import com.aiosman.riderpro.test.TestDatabase import java.io.IOException +import java.util.Calendar +import kotlin.math.min import kotlin.random.Random +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) +} + + data class Comment( + val id: Int, val name: String, val comment: String, val date: String, val likes: Int, - val replies: List + val replies: List, + val postId: Int = 0, + val avatar: String, + val author: Int, + val liked: Boolean, ) class CommentPagingSource( private val remoteDataSource: CommentRemoteDataSource, + private val postId: Int? = null ) : PagingSource() { - override suspend fun load(params: LoadParams): LoadResult { return try { val currentPage = params.key ?: 1 val comments = remoteDataSource.getComments( - pageNumber = currentPage + pageNumber = currentPage, + postId = postId ) - LoadResult.Page( data = comments.list, prevKey = if (currentPage == 1) null else currentPage - 1, @@ -43,52 +58,70 @@ class CommentPagingSource( class CommentRemoteDataSource( private val commentService: CommentService, ) { - suspend fun getComments(pageNumber: Int): ListContainer { - return commentService.getComments(pageNumber) + suspend fun getComments(pageNumber: Int, postId: Int?): ListContainer { + return commentService.getComments(pageNumber, postId) } } -interface CommentService { - suspend fun getComments(pageNumber: Int): ListContainer -} class TestCommentServiceImpl : CommentService { - private val mockData = generateMockComments(100) - override suspend fun getComments(pageNumber: Int): ListContainer { - val from = pageNumber * DataBatchSize - val to = (pageNumber + 1) * DataBatchSize - val currentSublist = mockData.subList(from, to) + override suspend fun getComments(pageNumber: Int, postId: Int?): ListContainer { + var rawList = TestDatabase.comment + if (postId != null) { + rawList = rawList.filter { it.postId == postId } + } + val from = (pageNumber - 1) * DataBatchSize + val to = (pageNumber) * DataBatchSize + rawList = rawList.sortedBy { -it.id } + if (from >= rawList.size) { + return ListContainer( + total = rawList.size, + page = pageNumber, + pageSize = DataBatchSize, + list = emptyList() + ) + } + rawList = rawList.sortedBy { -it.id } + val currentSublist = rawList.subList(from, min(to, rawList.size)) return ListContainer( - total = mockData.size, + total = rawList.size, page = pageNumber, pageSize = DataBatchSize, list = currentSublist ) } - private fun generateMockComments(count: Int): List { - return (0 until count).map { - Comment( - name = "User $it", - comment = "This is comment $it", - date = "2023-02-02 11:23", - likes = Random.nextInt(0, 100), - replies = generateMockReplies() - ) + override suspend fun createComment(postId: Int, content: String, authorId: Int): Comment { + var author = TestDatabase.accountData.find { it.id == authorId } + if (author == null) { + author = TestDatabase.accountData.random() } + TestDatabase.commentIdCounter += 1 + val newComment = Comment( + name = author.nickName, + comment = content, + date = Calendar.getInstance().time.toString(), + likes = 0, + replies = emptyList(), + postId = postId, + avatar = author.avatar, + author = author.id, + id = TestDatabase.commentIdCounter, + liked = false + ) + TestDatabase.comment += newComment + return newComment } - private fun generateMockReplies(): List { - val replyCount = Random.nextInt(0, 6) - return (0 until replyCount).map { - Comment( - name = "Reply User $it", - comment = "This is reply $it", - date = "2023-02-02 11:23", - likes = Random.nextInt(0, 100), - replies = emptyList() - ) + override suspend fun likeComment(commentId: Int) { + TestDatabase.comment = TestDatabase.comment.map { + if (it.id == commentId) { + it.copy(likes = it.likes + 1) + } else { + it + } } + } 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 c11f0a4..7766ed1 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt @@ -2,19 +2,34 @@ package com.aiosman.riderpro.data import androidx.paging.PagingSource import androidx.paging.PagingState -import com.aiosman.riderpro.R import com.aiosman.riderpro.model.MomentItem import com.aiosman.riderpro.test.TestDatabase import java.io.IOException +import kotlin.math.min + +interface MomentService { + suspend fun getMomentById(id: Int): MomentItem + suspend fun likeMoment(id: Int) + suspend fun getMoments( + pageNumber: Int, + author: Int? = null, + timelineId: Int? = null + ): ListContainer +} + class MomentPagingSource( private val remoteDataSource: MomentRemoteDataSource, + private val author: Int? = null, + private val timelineId: Int? = null ) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { return try { val currentPage = params.key ?: 1 val moments = remoteDataSource.getMoments( - pageNumber = currentPage + pageNumber = currentPage, + author = author, + timelineId = timelineId ) LoadResult.Page( @@ -36,63 +51,88 @@ class MomentPagingSource( class MomentRemoteDataSource( private val momentService: MomentService, ) { - suspend fun getMoments(pageNumber: Int): ListContainer { - return momentService.getMoments(pageNumber) + suspend fun getMoments( + pageNumber: Int, + author: Int?, + timelineId: Int? + ): ListContainer { + return momentService.getMoments(pageNumber, author, timelineId) } } -interface MomentService { - suspend fun getMoments(pageNumber: Int): ListContainer - suspend fun getMomentById(id: Int): MomentItem - suspend fun likeMoment(id: Int) -} class TestMomentServiceImpl() : MomentService { - var imageList = listOf( - "https://img.freepik.com/free-photo/white-billboard-template_23-2147726635.jpg?t=st=1722150015~exp=1722153615~hmac=5540620196d7898215d822be26353c87a63d51bbfb2b814e032626e1948a1583&w=740", - "https://img.freepik.com/free-photo/minimal-clothing-label-fashion-brands_53876-111053.jpg?w=1060&t=st=1722150122~exp=1722150722~hmac=67f8a2b6abfe3d08714cf0cc0085485c3221e1ba00dda14378b03753dce39153", - "https://img.freepik.com/free-photo/marketing-strategy-planning-strategy-concept_53876-42950.jpg" - ) - var mockData = TestDatabase.momentData - val testMomentBackend = TestMomentBackend(mockData) - override suspend fun getMoments(pageNumber: Int): ListContainer { - return testMomentBackend.fetchMomentItems(pageNumber) + val testMomentBackend = TestMomentBackend() + + override suspend fun getMoments( + pageNumber: Int, + author: Int?, + timelineId: Int? + ): ListContainer { + return testMomentBackend.fetchMomentItems(pageNumber, author, timelineId) } override suspend fun getMomentById(id: Int): MomentItem { - return mockData[id] + return testMomentBackend.getMomentById(id) } + override suspend fun likeMoment(id: Int) { -// mockData = mockData.map { -// if (it.id == id) { -// it.copy(likeCount = it.likeCount + 1) -// } else { -// it -// } -// } -// mockData + testMomentBackend.likeMoment(id) } } class TestMomentBackend( - private val mockData: List, private val loadDelay: Long = 500, ) { val DataBatchSize = 5 - suspend fun fetchMomentItems(pageNumber: Int): ListContainer { - val from = pageNumber * DataBatchSize - val to = (pageNumber + 1) * DataBatchSize - val currentSublist = mockData.subList(from, to) + suspend fun fetchMomentItems( + pageNumber: Int, + author: Int? = null, + timelineId: Int? + ): ListContainer { + var rawList = TestDatabase.momentData + if (author != null) { + rawList = rawList.filter { it.authorId == author } + } + if (timelineId != null) { + val followIdList = TestDatabase.followList.filter { + it.first == timelineId + }.map { it.second } + rawList = rawList.filter { it.authorId in followIdList } + } + val from = (pageNumber - 1) * DataBatchSize + val to = (pageNumber) * DataBatchSize + if (from >= rawList.size) { + return ListContainer( + total = rawList.size, + page = pageNumber, + pageSize = DataBatchSize, + list = emptyList() + ) + } + val currentSublist = rawList.subList(from, min(to, rawList.size)) // delay kotlinx.coroutines.delay(loadDelay) return ListContainer( - total = mockData.size, + total = rawList.size, page = pageNumber, pageSize = DataBatchSize, list = currentSublist ) } + suspend fun getMomentById(id: Int): MomentItem { + return TestDatabase.momentData[id] + } + + suspend fun likeMoment(id: Int) { + val oldMoment = TestDatabase.momentData.first { + it.id == id + } + val newMoment = oldMoment.copy(likeCount = oldMoment.likeCount + 1) + TestDatabase.updateMomentById(id, newMoment) + } + } \ No newline at end of file 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 3db939a..1c1b4cf 100644 --- a/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt +++ b/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt @@ -2,18 +2,24 @@ package com.aiosman.riderpro.test import com.aiosman.riderpro.R import com.aiosman.riderpro.data.AccountProfile +import com.aiosman.riderpro.data.Comment import com.aiosman.riderpro.model.MomentItem +import com.google.gson.Gson +import com.google.gson.GsonBuilder import io.github.serpro69.kfaker.faker +import java.io.File object TestDatabase { var momentData = emptyList() var accountData = emptyList() + var comment = emptyList() + var commentIdCounter = 0 + var selfId = 1 var imageList = listOf( "https://img.freepik.com/free-photo/white-billboard-template_23-2147726635.jpg?t=st=1722150015~exp=1722153615~hmac=5540620196d7898215d822be26353c87a63d51bbfb2b814e032626e1948a1583&w=740", "https://img.freepik.com/free-photo/minimal-clothing-label-fashion-brands_53876-111053.jpg?w=1060&t=st=1722150122~exp=1722150722~hmac=67f8a2b6abfe3d08714cf0cc0085485c3221e1ba00dda14378b03753dce39153", "https://img.freepik.com/free-photo/marketing-strategy-planning-strategy-concept_53876-42950.jpg", "https://t4.ftcdn.net/jpg/02/27/00/89/240_F_227008949_5O7yXuEqTwUgs3BGqdcvrNutM5MSxs1t.jpg", - "https://t4.ftcdn.net/jpg/01/86/86/49/240_F_186864971_NixcoDg1zBjjN7soUNhpEVraI4vdzOFD.jpg", "https://t3.ftcdn.net/jpg/00/84/01/30/240_F_84013057_fsOdzBgskSFUyWyD6YKjIAdtKdBPiKRD.jpg", "https://t4.ftcdn.net/jpg/00/93/89/23/240_F_93892312_SNyGGruVaWKpJQiVG314gIQmS4EAghdy.jpg", @@ -32,14 +38,14 @@ object TestDatabase { "https://t3.ftcdn.net/jpg/02/65/43/04/240_F_265430460_DIHqnrziar7WL2rmW0qbDO07TbxjlPQo.jpg" ) var followList = emptyList>() - + var likeCommentList = emptyList>() init { val faker = faker { this.fakerConfig { locale = "en" } } - accountData = (0..300).toList().mapIndexed { idx, _ -> + accountData = (0..100).toList().mapIndexed { idx, _ -> AccountProfile( id = idx, followerCount = 0, @@ -52,7 +58,7 @@ object TestDatabase { ) } // make a random follow rel - for (i in 0..10000) { + for (i in 0..500) { var person1 = accountData.random() var persion2 = accountData.random() followList += Pair(person1.id, persion2.id) @@ -66,11 +72,34 @@ object TestDatabase { it } } - } - momentData = (0..300).toList().mapIndexed { idx, _ -> + momentData = (0..200).toList().mapIndexed { idx, _ -> val person = accountData.random() + // make fake comment + for (i in 0..faker.random.nextInt(0, 5)) { + commentIdCounter += 1 + val commentPerson = accountData.random() + var newComment = Comment( + name = commentPerson.nickName, + comment = "this is comment ${commentIdCounter}", + date = "2023-02-02 11:23", + likes = 0, + replies = emptyList(), + postId = idx, + avatar = commentPerson.avatar, + author = commentPerson.id, + id = commentIdCounter, + liked = false + ) + // generate like comment list + for (likeIdx in 0..faker.random.nextInt(0, 5)) { + val likePerson = accountData.random() + likeCommentList += Pair(commentIdCounter, likePerson.id) + newComment = newComment.copy(likes = newComment.likes + 1) + } + comment += newComment + } MomentItem( id = idx, avatar = person.avatar, @@ -89,4 +118,24 @@ object TestDatabase { ) } } + + + fun updateMomentById(id: Int, momentItem: MomentItem) { + momentData = momentData.map { + if (it.id == id) { + momentItem + } else { + it + } + } + } + + fun saveResultToJsonFile() { + val gson: Gson = GsonBuilder().setPrettyPrinting().create() + + // save accountData to json file + File("accountData.json").writeText(accountData.toString()) + // save momentData to json file + // save comment to json file + } } \ No newline at end of file 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 5337b9a..90e3552 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 @@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -35,30 +36,54 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview 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.collectAsLazyPagingItems import com.aiosman.riderpro.ui.post.CommentsSection import com.aiosman.riderpro.R 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.ui.modifiers.noRippleClickable import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch - +class CommentModalViewModel( + postId: Int? +):ViewModel(){ + val commentService:CommentService = TestCommentServiceImpl() + val commentsFlow: Flow> = Pager( + config = PagingConfig(pageSize = 20, enablePlaceholders = false), + pagingSourceFactory = { + CommentPagingSource( + CommentRemoteDataSource(commentService), + postId + ) + } + ).flow.cachedIn(viewModelScope) +} @Preview @Composable -fun CommentModalContent(onDismiss: () -> Unit = {}) { - var commentSource = CommentPagingSource( - CommentRemoteDataSource(TestCommentServiceImpl()) +fun CommentModalContent(postId: Int? = null, onDismiss: () -> Unit = {}) { + val model = viewModel( + factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return CommentModalViewModel(postId) as T + } + } ) - val commentsFlow: Flow> = Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { commentSource } - ).flow - val comments = commentsFlow.collectAsLazyPagingItems() + + + val scope = rememberCoroutineScope() + val comments = model.commentsFlow.collectAsLazyPagingItems() val insets = WindowInsets val imePadding = insets.ime.getBottom(density = LocalDensity.current) var bottomPadding by remember { mutableStateOf(0.dp) } @@ -70,7 +95,14 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) { onDismiss() } } - + var commentText by remember { mutableStateOf("") } + suspend fun sendComment() { + if (commentText.isNotEmpty()) { + model.commentService.createComment(postId!!, commentText, 1) + commentText = "" + } + comments.refresh() + } Column( modifier = Modifier ) { @@ -97,8 +129,13 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) { .padding(horizontal = 16.dp) .weight(1f) ) { - - CommentsSection(lazyPagingItems = comments) { + CommentsSection(lazyPagingItems = comments, onLike = { + comment: Comment -> + scope.launch { + model.commentService.likeComment(comment.id) + comments.refresh() + } + }) { } @@ -127,8 +164,8 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) { ) { BasicTextField( - value = "", - onValueChange = { }, + value = commentText, + onValueChange = { text -> commentText = text }, modifier = Modifier .fillMaxWidth(), textStyle = TextStyle( @@ -144,7 +181,11 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) { contentDescription = "Send", modifier = Modifier .size(32.dp) - + .noRippleClickable { + scope.launch { + sendComment() + } + } ) } 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 ab4c8e0..b377b08 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 @@ -72,7 +72,7 @@ fun MomentsList() { MomentCard(momentItem = momentItem, onLikeClick = { scope.launch { model.likeMoment(momentItem.id) - moments.refresh() +// moments.refresh() } }) } @@ -253,7 +253,7 @@ fun MomentContentGroup( ) { val displayImageUrl = momentItem.images.firstOrNull() Text( - text = momentItem.momentTextContent, + text = "${momentItem.id} ${momentItem.momentTextContent}", modifier = Modifier .fillMaxWidth() .padding(top = 22.dp, bottom = 16.dp, start = 24.dp, end = 24.dp), @@ -323,7 +323,7 @@ fun MomentBottomOperateRowGroup( ) ) { systemUiController.setNavigationBarColor(Color(0xfff7f7f7)) - CommentModalContent() { + CommentModalContent(postId = momentItem.id) { systemUiController.setNavigationBarColor(Color.Black) } } 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 f2f89d5..c84bd7b 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 @@ -1,39 +1,57 @@ package com.aiosman.riderpro.ui.index.tabs.moment -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.runtime.toMutableStateList import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData -import com.aiosman.riderpro.R +import androidx.paging.cachedIn +import androidx.paging.map +import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.MomentPagingSource import com.aiosman.riderpro.data.MomentRemoteDataSource 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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch object MomentViewModel : ViewModel() { - val momentService: MomentService = TestMomentServiceImpl() - var momentListPagingSource = MomentPagingSource( - MomentRemoteDataSource(momentService) - ) - var momentsFlow: Flow> = Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { - MomentPagingSource( - MomentRemoteDataSource(momentService) - ) + private val momentService: MomentService = TestMomentServiceImpl() + private val _momentsFlow = MutableStateFlow>(PagingData.empty()) + val momentsFlow = _momentsFlow.asStateFlow() + val accountService: AccountService = TestAccountServiceImpl() + + init { + viewModelScope.launch { + val profile = accountService.getMyAccountProfile() + Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { MomentPagingSource( + MomentRemoteDataSource(momentService), + timelineId = profile.id + ) } + ).flow.cachedIn(viewModelScope).collectLatest { + _momentsFlow.value = it + } } - ).flow + } suspend fun likeMoment(id: Int) { momentService.likeMoment(id) + val currentPagingData = _momentsFlow.value + val updatedPagingData = currentPagingData.map { momentItem -> + if (momentItem.id == id) { + momentItem.copy(likeCount = momentItem.likeCount + 1) + } else { + momentItem + } + } + _momentsFlow.value = updatedPagingData } - } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt index 2c6648d..150188e 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt @@ -18,18 +18,20 @@ import kotlinx.coroutines.flow.Flow object MyProfileViewModel { val service: AccountService = TestAccountServiceImpl() var profile by mutableStateOf(null) + var momentsFlow by mutableStateOf>?>(null) suspend fun loadProfile() { - profile = service.getAccountProfile() + profile = service.getMyAccountProfile() + momentsFlow = Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + MomentPagingSource( + MomentRemoteDataSource(TestMomentServiceImpl()), + author = profile?.id ?: 0, + + ) + } + ).flow } - var momentListPagingSource = MomentPagingSource( - MomentRemoteDataSource(TestMomentServiceImpl()) - - ) - - val momentsFlow: Flow> = Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { momentListPagingSource } - ).flow val followerCount get() = profile?.followerCount ?: 0 val followingCount get() = profile?.followingCount ?: 0 val bio get() = profile?.bio ?: "" 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 e2699ba..104566e 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 @@ -19,9 +19,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -51,7 +49,7 @@ fun ProfilePage() { LaunchedEffect(Unit) { model.loadProfile() } - val profile = model.momentsFlow.collectAsLazyPagingItems() + val moments = model.momentsFlow?.collectAsLazyPagingItems() LazyColumn( modifier = Modifier .fillMaxSize() @@ -67,10 +65,13 @@ fun ProfilePage() { RidingStyle() } - items(profile.itemCount) { idx -> - val momentItem = profile[idx] ?: return@items - MomentPostUnit(momentItem) + moments?.let { + items(it.itemCount) { idx -> + val momentItem = it[idx] ?: return@items + MomentPostUnit(momentItem) + } } + } } @@ -409,7 +410,7 @@ fun MomentPostUnit(momentItem: MomentItem) { TimeGroup(momentItem.time) MomentCard( momentItem.momentTextContent, - momentItem.momentPicture, + momentItem.images[0], momentItem.likeCount.toString(), momentItem.commentCount.toString() ) @@ -439,7 +440,7 @@ fun TimeGroup(time: String = "2024.06.08 12:23") { } @Composable -fun MomentCard(content: String, @DrawableRes picture: Int, like: String, comment: String) { +fun MomentCard(content: String, imageUrl: String, like: String, comment: String) { Column( modifier = Modifier .fillMaxWidth() @@ -447,7 +448,7 @@ fun MomentCard(content: String, @DrawableRes picture: Int, like: String, comment .border(width = 1.dp, color = Color(0f, 0f, 0f, 0.1f), shape = RoundedCornerShape(6.dp)) ) { MomentCardTopContent(content) - MomentCardPicture(picture) + MomentCardPicture(imageUrl) MomentCardOperation(like, comment) } } @@ -468,13 +469,15 @@ fun MomentCardTopContent(content: String) { } @Composable -fun MomentCardPicture(@DrawableRes drawable: Int) { - Image( +fun MomentCardPicture(imageUrl:String) { + AsyncImage( + imageUrl, modifier = Modifier .fillMaxSize() - .padding(16.dp), painter = painterResource(id = drawable), contentDescription = "" + .padding(16.dp), + contentDescription = "", + contentScale = ContentScale.FillWidth ) - } @Composable 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 3a7ca36..37620c5 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 @@ -66,11 +66,14 @@ import androidx.paging.compose.collectAsLazyPagingItems import coil.compose.AsyncImage import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R +import com.aiosman.riderpro.data.AccountProfile +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.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.composables.StatusBarMaskLayout @@ -101,9 +104,14 @@ fun PostScreen( val scrollState = rememberLazyListState() val uiController = rememberSystemUiController() var moment by remember { mutableStateOf(null) } + var accountProfile by remember { mutableStateOf(null) } + var accountService: AccountService = TestAccountServiceImpl() LaunchedEffect(Unit) { uiController.setNavigationBarColor(Color.White) moment = service.getMomentById(id.toInt()) + moment?.let { + accountProfile = accountService.getAccountProfileById(it.authorId) + } } StatusBarMaskLayout { Scaffold( @@ -115,7 +123,7 @@ fun PostScreen( modifier = Modifier .fillMaxSize() ) { - Header() + Header(accountProfile) Column(modifier = Modifier.animateContentSize()) { AnimatedVisibility(visible = showCollapseContent) { // collapse content @@ -148,7 +156,7 @@ fun PostScreen( .fillMaxWidth() ) { - CommentsSection(lazyPagingItems = lazyPagingItems, scrollState) { + CommentsSection(lazyPagingItems = lazyPagingItems, scrollState, onLike = {}) { showCollapseContent = it } } @@ -158,7 +166,7 @@ fun PostScreen( } @Composable -fun Header() { +fun Header(accountProfile: AccountProfile?) { val navController = LocalNavController.current Row( modifier = Modifier @@ -177,15 +185,22 @@ fun Header() { ) Spacer(modifier = Modifier.width(8.dp)) - Image( - painter = painterResource(id = R.drawable.default_avatar), // Replace with your image resource - contentDescription = "Profile Picture", - modifier = Modifier - .size(40.dp) - .clip(CircleShape) - ) + accountProfile?.let { + AsyncImage( + accountProfile.avatar, + contentDescription = "Profile Picture", + modifier = Modifier + .size(40.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop + ) + } + Spacer(modifier = Modifier.width(8.dp)) - Text(text = "Diego Morata", fontWeight = FontWeight.Bold) + accountProfile?.let { + Text(text = accountProfile.nickName, fontWeight = FontWeight.Bold) + } + Box( modifier = Modifier .height(20.dp) @@ -269,25 +284,23 @@ fun PostDetails( momentItem: MomentItem? ) { - Column( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth() - .wrapContentHeight() - ) { - - Text( - text = momentItem?.momentTextContent ?:"", - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - ) - Text(text = "12-11 发布") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "共231条评论") - - } + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .wrapContentHeight() + ) { + Text( + text = momentItem?.momentTextContent ?: "", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + ) + Text(text = "12-11 发布") + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "共231条评论") + } } @@ -296,6 +309,7 @@ fun PostDetails( fun CommentsSection( lazyPagingItems: LazyPagingItems, scrollState: LazyListState = rememberLazyListState(), + onLike: (Comment) -> Unit, onWillCollapse: (Boolean) -> Unit ) { LazyColumn( @@ -305,7 +319,9 @@ fun CommentsSection( ) { items(lazyPagingItems.itemCount) { idx -> val item = lazyPagingItems[idx] ?: return@items - CommentItem(item) + CommentItem(item,onLike={ + onLike(item) + }) } } @@ -323,15 +339,16 @@ fun CommentsSection( @Composable -fun CommentItem(comment: Comment) { +fun CommentItem(comment: Comment,onLike:()->Unit = {}) { Column { Row(modifier = Modifier.padding(vertical = 8.dp)) { - Image( - painter = painterResource(id = R.drawable.default_avatar), // Replace with your image resource + AsyncImage( + comment.avatar, contentDescription = "Comment Profile Picture", modifier = Modifier .size(40.dp) - .clip(CircleShape) + .clip(CircleShape), + contentScale = ContentScale.Crop ) Spacer(modifier = Modifier.width(8.dp)) Column { @@ -341,7 +358,9 @@ fun CommentItem(comment: Comment) { } Spacer(modifier = Modifier.weight(1f)) Column(horizontalAlignment = Alignment.CenterHorizontally) { - IconButton(onClick = { /*TODO*/ }) { + IconButton(onClick = { + onLike() + }) { Icon(Icons.Filled.Favorite, contentDescription = "Like") } Text(text = comment.likes.toString()) diff --git a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfile.kt b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfile.kt index 3a27a7e..deb833d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfile.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfile.kt @@ -37,18 +37,22 @@ fun AccountProfile(id:String) { // val model = MyProfileViewModel val userService: UserService = TestUserServiceImpl() var userProfile by remember { mutableStateOf(null) } - var momentListPagingSource = MomentPagingSource( - MomentRemoteDataSource(TestMomentServiceImpl()) - ) - val momentsFlow: Flow> = Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { momentListPagingSource } - ).flow + val momentService = TestMomentServiceImpl() + var momentsFlow by remember { mutableStateOf>?>(null) } LaunchedEffect(Unit) { userProfile = userService.getUserProfile(id) + momentsFlow = Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + MomentPagingSource( + MomentRemoteDataSource(momentService), + author = id.toInt() + ) + } + ).flow } - val items = momentsFlow.collectAsLazyPagingItems() + val items = momentsFlow?.collectAsLazyPagingItems() val systemUiController = rememberSystemUiController() LaunchedEffect(Unit) { systemUiController.setNavigationBarColor( @@ -70,13 +74,14 @@ fun AccountProfile(id:String) { CarGroup() userProfile?.let { UserInformation(isSelf = false, accountProfile = it) - } RidingStyle() } - items(items.itemCount) { idx -> - val momentItem = items[idx] ?: return@items - MomentPostUnit(momentItem) + if (items != null) { + items(items.itemCount) { idx -> + val momentItem = items[idx] ?: return@items + MomentPostUnit(momentItem) + } } } }