添加更新数据

This commit is contained in:
2024-07-29 16:50:07 +08:00
parent d23c5f5c7e
commit 53c71973ae
11 changed files with 398 additions and 189 deletions

View File

@@ -1,5 +1,7 @@
package com.aiosman.riderpro.data package com.aiosman.riderpro.data
import com.aiosman.riderpro.test.TestDatabase
data class AccountProfile( data class AccountProfile(
val id: Int, val id: Int,
val followerCount: Int, val followerCount: Int,
@@ -11,19 +13,16 @@ data class AccountProfile(
) )
interface AccountService { interface AccountService {
suspend fun getAccountProfile(): AccountProfile suspend fun getMyAccountProfile(): AccountProfile
suspend fun getAccountProfileById(id: Int): AccountProfile
} }
class TestAccountServiceImpl : AccountService { class TestAccountServiceImpl : AccountService {
override suspend fun getAccountProfile(): AccountProfile { override suspend fun getMyAccountProfile(): AccountProfile {
return AccountProfile( return TestDatabase.accountData.first { it.id == 0 }
id = 1, }
followerCount = 100,
followingCount = 200, override suspend fun getAccountProfileById(id: Int): AccountProfile {
nickName = "Aiosman", return TestDatabase.accountData.first { it.id == id }
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"
)
} }
} }

View File

@@ -2,28 +2,43 @@ package com.aiosman.riderpro.data
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.aiosman.riderpro.test.TestDatabase
import java.io.IOException import java.io.IOException
import java.util.Calendar
import kotlin.math.min
import kotlin.random.Random import kotlin.random.Random
interface CommentService {
suspend fun getComments(pageNumber: Int, postId: Int? = null): ListContainer<Comment>
suspend fun createComment(postId: Int, content: String, authorId: Int): Comment
suspend fun likeComment(commentId: Int)
}
data class Comment( data class Comment(
val id: Int,
val name: String, val name: String,
val comment: String, val comment: String,
val date: String, val date: String,
val likes: Int, val likes: Int,
val replies: List<Comment> val replies: List<Comment>,
val postId: Int = 0,
val avatar: String,
val author: Int,
val liked: Boolean,
) )
class CommentPagingSource( class CommentPagingSource(
private val remoteDataSource: CommentRemoteDataSource, private val remoteDataSource: CommentRemoteDataSource,
private val postId: Int? = null
) : PagingSource<Int, Comment>() { ) : PagingSource<Int, Comment>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Comment> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Comment> {
return try { return try {
val currentPage = params.key ?: 1 val currentPage = params.key ?: 1
val comments = remoteDataSource.getComments( val comments = remoteDataSource.getComments(
pageNumber = currentPage pageNumber = currentPage,
postId = postId
) )
LoadResult.Page( LoadResult.Page(
data = comments.list, data = comments.list,
prevKey = if (currentPage == 1) null else currentPage - 1, prevKey = if (currentPage == 1) null else currentPage - 1,
@@ -43,52 +58,70 @@ class CommentPagingSource(
class CommentRemoteDataSource( class CommentRemoteDataSource(
private val commentService: CommentService, private val commentService: CommentService,
) { ) {
suspend fun getComments(pageNumber: Int): ListContainer<Comment> { suspend fun getComments(pageNumber: Int, postId: Int?): ListContainer<Comment> {
return commentService.getComments(pageNumber) return commentService.getComments(pageNumber, postId)
} }
} }
interface CommentService {
suspend fun getComments(pageNumber: Int): ListContainer<Comment>
}
class TestCommentServiceImpl : CommentService { class TestCommentServiceImpl : CommentService {
private val mockData = generateMockComments(100) override suspend fun getComments(pageNumber: Int, postId: Int?): ListContainer<Comment> {
override suspend fun getComments(pageNumber: Int): ListContainer<Comment> { var rawList = TestDatabase.comment
val from = pageNumber * DataBatchSize if (postId != null) {
val to = (pageNumber + 1) * DataBatchSize rawList = rawList.filter { it.postId == postId }
val currentSublist = mockData.subList(from, to) }
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( return ListContainer(
total = mockData.size, total = rawList.size,
page = pageNumber, page = pageNumber,
pageSize = DataBatchSize, pageSize = DataBatchSize,
list = currentSublist list = currentSublist
) )
} }
private fun generateMockComments(count: Int): List<Comment> { override suspend fun createComment(postId: Int, content: String, authorId: Int): Comment {
return (0 until count).map { var author = TestDatabase.accountData.find { it.id == authorId }
Comment( if (author == null) {
name = "User $it", author = TestDatabase.accountData.random()
comment = "This is comment $it",
date = "2023-02-02 11:23",
likes = Random.nextInt(0, 100),
replies = generateMockReplies()
)
} }
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<Comment> { override suspend fun likeComment(commentId: Int) {
val replyCount = Random.nextInt(0, 6) TestDatabase.comment = TestDatabase.comment.map {
return (0 until replyCount).map { if (it.id == commentId) {
Comment( it.copy(likes = it.likes + 1)
name = "Reply User $it", } else {
comment = "This is reply $it", it
date = "2023-02-02 11:23", }
likes = Random.nextInt(0, 100),
replies = emptyList()
)
} }
} }
companion object { companion object {

View File

@@ -2,19 +2,34 @@ package com.aiosman.riderpro.data
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.aiosman.riderpro.R
import com.aiosman.riderpro.model.MomentItem import com.aiosman.riderpro.model.MomentItem
import com.aiosman.riderpro.test.TestDatabase import com.aiosman.riderpro.test.TestDatabase
import java.io.IOException 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<MomentItem>
}
class MomentPagingSource( class MomentPagingSource(
private val remoteDataSource: MomentRemoteDataSource, private val remoteDataSource: MomentRemoteDataSource,
private val author: Int? = null,
private val timelineId: Int? = null
) : PagingSource<Int, MomentItem>() { ) : PagingSource<Int, MomentItem>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentItem> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentItem> {
return try { return try {
val currentPage = params.key ?: 1 val currentPage = params.key ?: 1
val moments = remoteDataSource.getMoments( val moments = remoteDataSource.getMoments(
pageNumber = currentPage pageNumber = currentPage,
author = author,
timelineId = timelineId
) )
LoadResult.Page( LoadResult.Page(
@@ -36,63 +51,88 @@ class MomentPagingSource(
class MomentRemoteDataSource( class MomentRemoteDataSource(
private val momentService: MomentService, private val momentService: MomentService,
) { ) {
suspend fun getMoments(pageNumber: Int): ListContainer<MomentItem> { suspend fun getMoments(
return momentService.getMoments(pageNumber) pageNumber: Int,
author: Int?,
timelineId: Int?
): ListContainer<MomentItem> {
return momentService.getMoments(pageNumber, author, timelineId)
} }
} }
interface MomentService {
suspend fun getMoments(pageNumber: Int): ListContainer<MomentItem>
suspend fun getMomentById(id: Int): MomentItem
suspend fun likeMoment(id: Int)
}
class TestMomentServiceImpl() : MomentService { class TestMomentServiceImpl() : MomentService {
var imageList = listOf( val testMomentBackend = TestMomentBackend()
"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", override suspend fun getMoments(
"https://img.freepik.com/free-photo/marketing-strategy-planning-strategy-concept_53876-42950.jpg" pageNumber: Int,
) author: Int?,
var mockData = TestDatabase.momentData timelineId: Int?
val testMomentBackend = TestMomentBackend(mockData) ): ListContainer<MomentItem> {
override suspend fun getMoments(pageNumber: Int): ListContainer<MomentItem> { return testMomentBackend.fetchMomentItems(pageNumber, author, timelineId)
return testMomentBackend.fetchMomentItems(pageNumber)
} }
override suspend fun getMomentById(id: Int): MomentItem { override suspend fun getMomentById(id: Int): MomentItem {
return mockData[id] return testMomentBackend.getMomentById(id)
} }
override suspend fun likeMoment(id: Int) { override suspend fun likeMoment(id: Int) {
// mockData = mockData.map { testMomentBackend.likeMoment(id)
// if (it.id == id) {
// it.copy(likeCount = it.likeCount + 1)
// } else {
// it
// }
// }
// mockData
} }
} }
class TestMomentBackend( class TestMomentBackend(
private val mockData: List<MomentItem>,
private val loadDelay: Long = 500, private val loadDelay: Long = 500,
) { ) {
val DataBatchSize = 5 val DataBatchSize = 5
suspend fun fetchMomentItems(pageNumber: Int): ListContainer<MomentItem> { suspend fun fetchMomentItems(
val from = pageNumber * DataBatchSize pageNumber: Int,
val to = (pageNumber + 1) * DataBatchSize author: Int? = null,
val currentSublist = mockData.subList(from, to) timelineId: Int?
): ListContainer<MomentItem> {
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 // delay
kotlinx.coroutines.delay(loadDelay) kotlinx.coroutines.delay(loadDelay)
return ListContainer( return ListContainer(
total = mockData.size, total = rawList.size,
page = pageNumber, page = pageNumber,
pageSize = DataBatchSize, pageSize = DataBatchSize,
list = currentSublist 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)
}
} }

View File

@@ -2,18 +2,24 @@ package com.aiosman.riderpro.test
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountProfile import com.aiosman.riderpro.data.AccountProfile
import com.aiosman.riderpro.data.Comment
import com.aiosman.riderpro.model.MomentItem import com.aiosman.riderpro.model.MomentItem
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import io.github.serpro69.kfaker.faker import io.github.serpro69.kfaker.faker
import java.io.File
object TestDatabase { object TestDatabase {
var momentData = emptyList<MomentItem>() var momentData = emptyList<MomentItem>()
var accountData = emptyList<AccountProfile>() var accountData = emptyList<AccountProfile>()
var comment = emptyList<Comment>()
var commentIdCounter = 0
var selfId = 1
var imageList = listOf( 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/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/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://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/02/27/00/89/240_F_227008949_5O7yXuEqTwUgs3BGqdcvrNutM5MSxs1t.jpg",
"https://t4.ftcdn.net/jpg/01/86/86/49/240_F_186864971_NixcoDg1zBjjN7soUNhpEVraI4vdzOFD.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://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", "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" "https://t3.ftcdn.net/jpg/02/65/43/04/240_F_265430460_DIHqnrziar7WL2rmW0qbDO07TbxjlPQo.jpg"
) )
var followList = emptyList<Pair<Int, Int>>() var followList = emptyList<Pair<Int, Int>>()
var likeCommentList = emptyList<Pair<Int, Int>>()
init { init {
val faker = faker { val faker = faker {
this.fakerConfig { this.fakerConfig {
locale = "en" locale = "en"
} }
} }
accountData = (0..300).toList().mapIndexed { idx, _ -> accountData = (0..100).toList().mapIndexed { idx, _ ->
AccountProfile( AccountProfile(
id = idx, id = idx,
followerCount = 0, followerCount = 0,
@@ -52,7 +58,7 @@ object TestDatabase {
) )
} }
// make a random follow rel // make a random follow rel
for (i in 0..10000) { for (i in 0..500) {
var person1 = accountData.random() var person1 = accountData.random()
var persion2 = accountData.random() var persion2 = accountData.random()
followList += Pair(person1.id, persion2.id) followList += Pair(person1.id, persion2.id)
@@ -66,11 +72,34 @@ object TestDatabase {
it it
} }
} }
} }
momentData = (0..300).toList().mapIndexed { idx, _ -> momentData = (0..200).toList().mapIndexed { idx, _ ->
val person = accountData.random() 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( MomentItem(
id = idx, id = idx,
avatar = person.avatar, 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
}
} }

View File

@@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.ui.post.CommentsSection import com.aiosman.riderpro.ui.post.CommentsSection
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.Comment import com.aiosman.riderpro.data.Comment
import com.aiosman.riderpro.data.CommentPagingSource import com.aiosman.riderpro.data.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService
import com.aiosman.riderpro.data.TestCommentServiceImpl import com.aiosman.riderpro.data.TestCommentServiceImpl
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class CommentModalViewModel(
postId: Int?
):ViewModel(){
val commentService:CommentService = TestCommentServiceImpl()
val commentsFlow: Flow<PagingData<Comment>> = Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false),
pagingSourceFactory = {
CommentPagingSource(
CommentRemoteDataSource(commentService),
postId
)
}
).flow.cachedIn(viewModelScope)
}
@Preview @Preview
@Composable @Composable
fun CommentModalContent(onDismiss: () -> Unit = {}) { fun CommentModalContent(postId: Int? = null, onDismiss: () -> Unit = {}) {
var commentSource = CommentPagingSource( val model = viewModel<CommentModalViewModel>(
CommentRemoteDataSource(TestCommentServiceImpl()) factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CommentModalViewModel(postId) as T
}
}
) )
val commentsFlow: Flow<PagingData<Comment>> = Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = { commentSource } val scope = rememberCoroutineScope()
).flow val comments = model.commentsFlow.collectAsLazyPagingItems()
val comments = commentsFlow.collectAsLazyPagingItems()
val insets = WindowInsets val insets = WindowInsets
val imePadding = insets.ime.getBottom(density = LocalDensity.current) val imePadding = insets.ime.getBottom(density = LocalDensity.current)
var bottomPadding by remember { mutableStateOf(0.dp) } var bottomPadding by remember { mutableStateOf(0.dp) }
@@ -70,7 +95,14 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) {
onDismiss() onDismiss()
} }
} }
var commentText by remember { mutableStateOf("") }
suspend fun sendComment() {
if (commentText.isNotEmpty()) {
model.commentService.createComment(postId!!, commentText, 1)
commentText = ""
}
comments.refresh()
}
Column( Column(
modifier = Modifier modifier = Modifier
) { ) {
@@ -97,8 +129,13 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) {
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.weight(1f) .weight(1f)
) { ) {
CommentsSection(lazyPagingItems = comments, onLike = {
CommentsSection(lazyPagingItems = comments) { comment: Comment ->
scope.launch {
model.commentService.likeComment(comment.id)
comments.refresh()
}
}) {
} }
@@ -127,8 +164,8 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) {
) { ) {
BasicTextField( BasicTextField(
value = "", value = commentText,
onValueChange = { }, onValueChange = { text -> commentText = text },
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
textStyle = TextStyle( textStyle = TextStyle(
@@ -144,7 +181,11 @@ fun CommentModalContent(onDismiss: () -> Unit = {}) {
contentDescription = "Send", contentDescription = "Send",
modifier = Modifier modifier = Modifier
.size(32.dp) .size(32.dp)
.noRippleClickable {
scope.launch {
sendComment()
}
}
) )
} }

View File

@@ -72,7 +72,7 @@ fun MomentsList() {
MomentCard(momentItem = momentItem, onLikeClick = { MomentCard(momentItem = momentItem, onLikeClick = {
scope.launch { scope.launch {
model.likeMoment(momentItem.id) model.likeMoment(momentItem.id)
moments.refresh() // moments.refresh()
} }
}) })
} }
@@ -253,7 +253,7 @@ fun MomentContentGroup(
) { ) {
val displayImageUrl = momentItem.images.firstOrNull() val displayImageUrl = momentItem.images.firstOrNull()
Text( Text(
text = momentItem.momentTextContent, text = "${momentItem.id} ${momentItem.momentTextContent}",
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 22.dp, bottom = 16.dp, start = 24.dp, end = 24.dp), .padding(top = 22.dp, bottom = 16.dp, start = 24.dp, end = 24.dp),
@@ -323,7 +323,7 @@ fun MomentBottomOperateRowGroup(
) )
) { ) {
systemUiController.setNavigationBarColor(Color(0xfff7f7f7)) systemUiController.setNavigationBarColor(Color(0xfff7f7f7))
CommentModalContent() { CommentModalContent(postId = momentItem.id) {
systemUiController.setNavigationBarColor(Color.Black) systemUiController.setNavigationBarColor(Color.Black)
} }
} }

View File

@@ -1,39 +1,57 @@
package com.aiosman.riderpro.ui.index.tabs.moment 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.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData 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.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource import com.aiosman.riderpro.data.MomentRemoteDataSource
import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.model.MomentItem 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() { object MomentViewModel : ViewModel() {
val momentService: MomentService = TestMomentServiceImpl() private val momentService: MomentService = TestMomentServiceImpl()
var momentListPagingSource = MomentPagingSource( private val _momentsFlow = MutableStateFlow<PagingData<MomentItem>>(PagingData.empty())
MomentRemoteDataSource(momentService) val momentsFlow = _momentsFlow.asStateFlow()
) val accountService: AccountService = TestAccountServiceImpl()
var momentsFlow: Flow<PagingData<MomentItem>> = Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false), init {
pagingSourceFactory = { viewModelScope.launch {
MomentPagingSource( val profile = accountService.getMyAccountProfile()
MomentRemoteDataSource(momentService) 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) { suspend fun likeMoment(id: Int) {
momentService.likeMoment(id) 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
} }
} }

View File

@@ -18,18 +18,20 @@ import kotlinx.coroutines.flow.Flow
object MyProfileViewModel { object MyProfileViewModel {
val service: AccountService = TestAccountServiceImpl() val service: AccountService = TestAccountServiceImpl()
var profile by mutableStateOf<AccountProfile?>(null) var profile by mutableStateOf<AccountProfile?>(null)
var momentsFlow by mutableStateOf<Flow<PagingData<MomentItem>>?>(null)
suspend fun loadProfile() { 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<PagingData<MomentItem>> = Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = { momentListPagingSource }
).flow
val followerCount get() = profile?.followerCount ?: 0 val followerCount get() = profile?.followerCount ?: 0
val followingCount get() = profile?.followingCount ?: 0 val followingCount get() = profile?.followingCount ?: 0
val bio get() = profile?.bio ?: "" val bio get() = profile?.bio ?: ""

View File

@@ -19,9 +19,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -51,7 +49,7 @@ fun ProfilePage() {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
model.loadProfile() model.loadProfile()
} }
val profile = model.momentsFlow.collectAsLazyPagingItems() val moments = model.momentsFlow?.collectAsLazyPagingItems()
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -67,10 +65,13 @@ fun ProfilePage() {
RidingStyle() RidingStyle()
} }
items(profile.itemCount) { idx -> moments?.let {
val momentItem = profile[idx] ?: return@items items(it.itemCount) { idx ->
MomentPostUnit(momentItem) val momentItem = it[idx] ?: return@items
MomentPostUnit(momentItem)
}
} }
} }
} }
@@ -409,7 +410,7 @@ fun MomentPostUnit(momentItem: MomentItem) {
TimeGroup(momentItem.time) TimeGroup(momentItem.time)
MomentCard( MomentCard(
momentItem.momentTextContent, momentItem.momentTextContent,
momentItem.momentPicture, momentItem.images[0],
momentItem.likeCount.toString(), momentItem.likeCount.toString(),
momentItem.commentCount.toString() momentItem.commentCount.toString()
) )
@@ -439,7 +440,7 @@ fun TimeGroup(time: String = "2024.06.08 12:23") {
} }
@Composable @Composable
fun MomentCard(content: String, @DrawableRes picture: Int, like: String, comment: String) { fun MomentCard(content: String, imageUrl: String, like: String, comment: String) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .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)) .border(width = 1.dp, color = Color(0f, 0f, 0f, 0.1f), shape = RoundedCornerShape(6.dp))
) { ) {
MomentCardTopContent(content) MomentCardTopContent(content)
MomentCardPicture(picture) MomentCardPicture(imageUrl)
MomentCardOperation(like, comment) MomentCardOperation(like, comment)
} }
} }
@@ -468,13 +469,15 @@ fun MomentCardTopContent(content: String) {
} }
@Composable @Composable
fun MomentCardPicture(@DrawableRes drawable: Int) { fun MomentCardPicture(imageUrl:String) {
Image( AsyncImage(
imageUrl,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), painter = painterResource(id = drawable), contentDescription = "" .padding(16.dp),
contentDescription = "",
contentScale = ContentScale.FillWidth
) )
} }
@Composable @Composable

View File

@@ -66,11 +66,14 @@ import androidx.paging.compose.collectAsLazyPagingItems
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R 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.Comment
import com.aiosman.riderpro.data.CommentPagingSource import com.aiosman.riderpro.data.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.TestCommentServiceImpl import com.aiosman.riderpro.data.TestCommentServiceImpl
import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.model.MomentItem import com.aiosman.riderpro.model.MomentItem
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
@@ -101,9 +104,14 @@ fun PostScreen(
val scrollState = rememberLazyListState() val scrollState = rememberLazyListState()
val uiController = rememberSystemUiController() val uiController = rememberSystemUiController()
var moment by remember { mutableStateOf<MomentItem?>(null) } var moment by remember { mutableStateOf<MomentItem?>(null) }
var accountProfile by remember { mutableStateOf<AccountProfile?>(null) }
var accountService: AccountService = TestAccountServiceImpl()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
uiController.setNavigationBarColor(Color.White) uiController.setNavigationBarColor(Color.White)
moment = service.getMomentById(id.toInt()) moment = service.getMomentById(id.toInt())
moment?.let {
accountProfile = accountService.getAccountProfileById(it.authorId)
}
} }
StatusBarMaskLayout { StatusBarMaskLayout {
Scaffold( Scaffold(
@@ -115,7 +123,7 @@ fun PostScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { ) {
Header() Header(accountProfile)
Column(modifier = Modifier.animateContentSize()) { Column(modifier = Modifier.animateContentSize()) {
AnimatedVisibility(visible = showCollapseContent) { AnimatedVisibility(visible = showCollapseContent) {
// collapse content // collapse content
@@ -148,7 +156,7 @@ fun PostScreen(
.fillMaxWidth() .fillMaxWidth()
) { ) {
CommentsSection(lazyPagingItems = lazyPagingItems, scrollState) { CommentsSection(lazyPagingItems = lazyPagingItems, scrollState, onLike = {}) {
showCollapseContent = it showCollapseContent = it
} }
} }
@@ -158,7 +166,7 @@ fun PostScreen(
} }
@Composable @Composable
fun Header() { fun Header(accountProfile: AccountProfile?) {
val navController = LocalNavController.current val navController = LocalNavController.current
Row( Row(
modifier = Modifier modifier = Modifier
@@ -177,15 +185,22 @@ fun Header() {
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Image( accountProfile?.let {
painter = painterResource(id = R.drawable.default_avatar), // Replace with your image resource AsyncImage(
contentDescription = "Profile Picture", accountProfile.avatar,
modifier = Modifier contentDescription = "Profile Picture",
.size(40.dp) modifier = Modifier
.clip(CircleShape) .size(40.dp)
) .clip(CircleShape),
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text(text = "Diego Morata", fontWeight = FontWeight.Bold) accountProfile?.let {
Text(text = accountProfile.nickName, fontWeight = FontWeight.Bold)
}
Box( Box(
modifier = Modifier modifier = Modifier
.height(20.dp) .height(20.dp)
@@ -269,25 +284,23 @@ fun PostDetails(
momentItem: MomentItem? momentItem: MomentItem?
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp)
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
) { ) {
Text(
text = momentItem?.momentTextContent ?:"",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
)
Text(text = "12-11 发布")
Spacer(modifier = Modifier.height(8.dp))
Text(text = "共231条评论")
}
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( fun CommentsSection(
lazyPagingItems: LazyPagingItems<Comment>, lazyPagingItems: LazyPagingItems<Comment>,
scrollState: LazyListState = rememberLazyListState(), scrollState: LazyListState = rememberLazyListState(),
onLike: (Comment) -> Unit,
onWillCollapse: (Boolean) -> Unit onWillCollapse: (Boolean) -> Unit
) { ) {
LazyColumn( LazyColumn(
@@ -305,7 +319,9 @@ fun CommentsSection(
) { ) {
items(lazyPagingItems.itemCount) { idx -> items(lazyPagingItems.itemCount) { idx ->
val item = lazyPagingItems[idx] ?: return@items val item = lazyPagingItems[idx] ?: return@items
CommentItem(item) CommentItem(item,onLike={
onLike(item)
})
} }
} }
@@ -323,15 +339,16 @@ fun CommentsSection(
@Composable @Composable
fun CommentItem(comment: Comment) { fun CommentItem(comment: Comment,onLike:()->Unit = {}) {
Column { Column {
Row(modifier = Modifier.padding(vertical = 8.dp)) { Row(modifier = Modifier.padding(vertical = 8.dp)) {
Image( AsyncImage(
painter = painterResource(id = R.drawable.default_avatar), // Replace with your image resource comment.avatar,
contentDescription = "Comment Profile Picture", contentDescription = "Comment Profile Picture",
modifier = Modifier modifier = Modifier
.size(40.dp) .size(40.dp)
.clip(CircleShape) .clip(CircleShape),
contentScale = ContentScale.Crop
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Column { Column {
@@ -341,7 +358,9 @@ fun CommentItem(comment: Comment) {
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
IconButton(onClick = { /*TODO*/ }) { IconButton(onClick = {
onLike()
}) {
Icon(Icons.Filled.Favorite, contentDescription = "Like") Icon(Icons.Filled.Favorite, contentDescription = "Like")
} }
Text(text = comment.likes.toString()) Text(text = comment.likes.toString())

View File

@@ -37,18 +37,22 @@ fun AccountProfile(id:String) {
// val model = MyProfileViewModel // val model = MyProfileViewModel
val userService: UserService = TestUserServiceImpl() val userService: UserService = TestUserServiceImpl()
var userProfile by remember { mutableStateOf<AccountProfile?>(null) } var userProfile by remember { mutableStateOf<AccountProfile?>(null) }
var momentListPagingSource = MomentPagingSource( val momentService = TestMomentServiceImpl()
MomentRemoteDataSource(TestMomentServiceImpl()) var momentsFlow by remember { mutableStateOf<Flow<PagingData<MomentItem>>?>(null) }
)
val momentsFlow: Flow<PagingData<MomentItem>> = Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = { momentListPagingSource }
).flow
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
userProfile = userService.getUserProfile(id) 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() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor( systemUiController.setNavigationBarColor(
@@ -70,13 +74,14 @@ fun AccountProfile(id:String) {
CarGroup() CarGroup()
userProfile?.let { userProfile?.let {
UserInformation(isSelf = false, accountProfile = it) UserInformation(isSelf = false, accountProfile = it)
} }
RidingStyle() RidingStyle()
} }
items(items.itemCount) { idx -> if (items != null) {
val momentItem = items[idx] ?: return@items items(items.itemCount) { idx ->
MomentPostUnit(momentItem) val momentItem = items[idx] ?: return@items
MomentPostUnit(momentItem)
}
} }
} }
} }