新增动态

This commit is contained in:
2024-07-30 15:49:59 +08:00
parent 0730fdea68
commit f28236b365
7 changed files with 161 additions and 43 deletions

View File

@@ -82,6 +82,7 @@ dependencies {
implementation("io.coil-kt:coil:2.7.0") implementation("io.coil-kt:coil:2.7.0")
implementation("com.google.android.gms:play-services-auth:21.2.0") implementation("com.google.android.gms:play-services-auth:21.2.0")
implementation("io.github.serpro69:kotlin-faker:2.0.0-rc.5") implementation("io.github.serpro69:kotlin-faker:2.0.0-rc.5")
implementation("androidx.compose.material:material:1.6.8")
} }

View File

@@ -2,6 +2,7 @@ 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
@@ -16,6 +17,12 @@ interface MomentService {
author: Int? = null, author: Int? = null,
timelineId: Int? = null timelineId: Int? = null
): ListContainer<MomentItem> ): ListContainer<MomentItem>
suspend fun createMoment(
content: String,
authorId: Int,
imageUriList: List<String>
): MomentItem
} }
@@ -86,6 +93,14 @@ class TestMomentServiceImpl() : MomentService {
testMomentBackend.dislikeMoment(id) testMomentBackend.dislikeMoment(id)
} }
override suspend fun createMoment(
content: String,
authorId: Int,
imageUriList: List<String>
): MomentItem {
return testMomentBackend.createMoment(content, authorId, imageUriList)
}
} }
class TestMomentBackend( class TestMomentBackend(
@@ -98,6 +113,7 @@ class TestMomentBackend(
timelineId: Int? timelineId: Int?
): ListContainer<MomentItem> { ): ListContainer<MomentItem> {
var rawList = TestDatabase.momentData var rawList = TestDatabase.momentData
rawList = rawList.sortedBy { it.id }.reversed()
if (author != null) { if (author != null) {
rawList = rawList.filter { it.authorId == author } rawList = rawList.filter { it.authorId == author }
} }
@@ -105,7 +121,7 @@ class TestMomentBackend(
val followIdList = TestDatabase.followList.filter { val followIdList = TestDatabase.followList.filter {
it.first == timelineId it.first == timelineId
}.map { it.second } }.map { it.second }
rawList = rawList.filter { it.authorId in followIdList } rawList = rawList.filter { it.authorId in followIdList || it.authorId == 1 }
} }
val from = (pageNumber - 1) * DataBatchSize val from = (pageNumber - 1) * DataBatchSize
val to = (pageNumber) * DataBatchSize val to = (pageNumber) * DataBatchSize
@@ -119,11 +135,13 @@ class TestMomentBackend(
} }
val currentSublist = rawList.subList(from, min(to, rawList.size)) val currentSublist = rawList.subList(from, min(to, rawList.size))
currentSublist.forEach { currentSublist.forEach {
val myLikeIdList = TestDatabase.likeMomentList.filter { it.second == 1 }.map { it.first } val myLikeIdList =
TestDatabase.likeMomentList.filter { it.second == 1 }.map { it.first }
if (myLikeIdList.contains(it.id)) { if (myLikeIdList.contains(it.id)) {
it.liked = true it.liked = true
} }
} }
// delay // delay
kotlinx.coroutines.delay(loadDelay) kotlinx.coroutines.delay(loadDelay)
return ListContainer( return ListContainer(
@@ -153,6 +171,7 @@ class TestMomentBackend(
TestDatabase.updateMomentById(id, newMoment) TestDatabase.updateMomentById(id, newMoment)
TestDatabase.likeMomentList += Pair(id, 1) TestDatabase.likeMomentList += Pair(id, 1)
} }
suspend fun dislikeMoment(id: Int) { suspend fun dislikeMoment(id: Int) {
val oldMoment = TestDatabase.momentData.first { val oldMoment = TestDatabase.momentData.first {
it.id == id it.id == id
@@ -164,4 +183,33 @@ class TestMomentBackend(
} }
} }
suspend fun createMoment(
content: String,
authorId: Int,
imageUriList: List<String>
): MomentItem {
TestDatabase.momentIdCounter += 1
val person = TestDatabase.accountData.first {
it.id == authorId
}
val newMoment = MomentItem(
id = TestDatabase.momentIdCounter,
avatar = person.avatar,
nickname = person.nickName,
location = person.country,
time = "2023.02.02 11:23",
followStatus = false,
momentTextContent = content,
momentPicture = R.drawable.default_moment_img,
likeCount = 0,
commentCount = 0,
shareCount = 0,
favoriteCount = 0,
images = imageUriList,
authorId = person.id
)
TestDatabase.momentData += newMoment
return newMoment
}
} }

View File

@@ -14,6 +14,7 @@ object TestDatabase {
var accountData = emptyList<AccountProfile>() var accountData = emptyList<AccountProfile>()
var comment = emptyList<Comment>() var comment = emptyList<Comment>()
var commentIdCounter = 0 var commentIdCounter = 0
var momentIdCounter = 0
var selfId = 1 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",
@@ -76,6 +77,7 @@ object TestDatabase {
} }
momentData = (0..60).toList().mapIndexed { idx, _ -> momentData = (0..60).toList().mapIndexed { idx, _ ->
momentIdCounter += 1
val person = accountData.random() val person = accountData.random()
// make fake comment // make fake comment
val commentCount = faker.random.nextInt(0, 50) val commentCount = faker.random.nextInt(0, 50)
@@ -88,7 +90,7 @@ object TestDatabase {
date = "2023-02-02 11:23", date = "2023-02-02 11:23",
likes = 0, likes = 0,
replies = emptyList(), replies = emptyList(),
postId = idx, postId = momentIdCounter,
avatar = commentPerson.avatar, avatar = commentPerson.avatar,
author = commentPerson.id, author = commentPerson.id,
id = commentIdCounter, id = commentIdCounter,
@@ -105,10 +107,10 @@ object TestDatabase {
val likeCount = faker.random.nextInt(0, 5) val likeCount = faker.random.nextInt(0, 5)
for (i in 0..likeCount) { for (i in 0..likeCount) {
val likePerson = accountData.random() val likePerson = accountData.random()
likeMomentList += Pair(idx, likePerson.id) likeMomentList += Pair(momentIdCounter, likePerson.id)
} }
MomentItem( MomentItem(
id = idx, id = momentIdCounter,
avatar = person.avatar, avatar = person.avatar,
nickname = person.nickName, nickname = person.nickName,
location = person.country, location = person.country,

View File

@@ -21,8 +21,12 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Build
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -30,6 +34,7 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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
@@ -45,6 +50,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
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.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
@@ -56,33 +62,53 @@ import com.aiosman.riderpro.ui.composables.AnimatedCounter
import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun MomentsList() { fun MomentsList() {
val model = MomentViewModel val model = MomentViewModel
var dataFlow = model.momentsFlow var dataFlow = model.momentsFlow
var moments = dataFlow.collectAsLazyPagingItems() var moments = dataFlow.collectAsLazyPagingItems()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LazyColumn { var refreshing by remember { mutableStateOf(false) }
items(moments.itemCount) { idx -> moments.loadState
val momentItem = moments[idx] ?: return@items val state = rememberPullRefreshState(refreshing, onRefresh = {
MomentCard(momentItem = momentItem, model.refreshPager()
onAddComment = { })
scope.launch { LaunchedEffect(moments.loadState) {
model.onAddComment(momentItem.id) if (moments.loadState.refresh is LoadState.Loading) {
} refreshing = true
}, } else {
onLikeClick = { refreshing = false
scope.launch { }
if (momentItem.liked) { }
model.dislikeMoment(momentItem.id)
} else { Box(Modifier.pullRefresh(state)) {
model.likeMoment(momentItem.id) LazyColumn {
items(moments.itemCount) { idx ->
val momentItem = moments[idx] ?: return@items
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)
}
} }
} }
}) )
}
} }
PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
} }
} }
@@ -366,10 +392,7 @@ fun MomentBottomOperateRowGroup(
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
liked = momentItem.liked liked = momentItem.liked
) { ) {
onLikeClick() onLikeClick()
} }
} }
} }

View File

@@ -25,7 +25,6 @@ object MomentViewModel : ViewModel() {
private val _momentsFlow = MutableStateFlow<PagingData<MomentItem>>(PagingData.empty()) private val _momentsFlow = MutableStateFlow<PagingData<MomentItem>>(PagingData.empty())
val momentsFlow = _momentsFlow.asStateFlow() val momentsFlow = _momentsFlow.asStateFlow()
val accountService: AccountService = TestAccountServiceImpl() val accountService: AccountService = TestAccountServiceImpl()
init { init {
viewModelScope.launch { viewModelScope.launch {
val profile = accountService.getMyAccountProfile() val profile = accountService.getMyAccountProfile()
@@ -42,6 +41,22 @@ object MomentViewModel : ViewModel() {
} }
} }
} }
fun refreshPager() {
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
}
}
}
fun updateLikeCount(id: Int) { fun updateLikeCount(id: Int) {
val currentPagingData = _momentsFlow.value val currentPagingData = _momentsFlow.value

View File

@@ -38,15 +38,20 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight 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.lifecycle.viewModelScope
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.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch
@Preview @Preview
@@ -54,6 +59,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
fun NewPostScreen() { fun NewPostScreen() {
val model = NewPostViewModel val model = NewPostViewModel
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val navController = LocalNavController.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(color = Color.Transparent) systemUiController.setNavigationBarColor(color = Color.Transparent)
} }
@@ -64,7 +70,12 @@ fun NewPostScreen() {
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { ) {
NewPostTopBar() NewPostTopBar {
model.viewModelScope.launch {
model.createMoment()
navController.popBackStack()
}
}
NewPostTextField("Share your adventure…", NewPostViewModel.textContent) { NewPostTextField("Share your adventure…", NewPostViewModel.textContent) {
NewPostViewModel.textContent = it NewPostViewModel.textContent = it
} }
@@ -72,11 +83,10 @@ fun NewPostScreen() {
AdditionalPostItem() AdditionalPostItem()
} }
} }
} }
@Composable @Composable
fun NewPostTopBar() { fun NewPostTopBar(onSendClick: () -> Unit = {}) {
val navController = LocalNavController.current val navController = LocalNavController.current
Box( Box(
modifier = Modifier modifier = Modifier
@@ -89,18 +99,21 @@ fun NewPostTopBar() {
Image( Image(
painter = painterResource(id = R.drawable.rider_pro_close), painter = painterResource(id = R.drawable.rider_pro_close),
contentDescription = "Back", contentDescription = "Back",
modifier = Modifier.size(24.dp).clickable( modifier = Modifier
indication = null, .size(24.dp)
interactionSource = remember { MutableInteractionSource() } .noRippleClickable {
) { navController.popBackStack()
navController.popBackStack() }
}
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Image( Image(
painter = painterResource(id = R.drawable.rider_pro_send_post), painter = painterResource(id = R.drawable.rider_pro_send_post),
contentDescription = "Send", contentDescription = "Send",
modifier = Modifier.size(24.dp) modifier = Modifier
.size(24.dp)
.noRippleClickable {
onSendClick()
}
) )
} }
} }
@@ -134,14 +147,15 @@ fun NewPostTextField(hint: String, value: String, onValueChange: (String) -> Uni
@Composable @Composable
fun AddImageGrid() { fun AddImageGrid() {
val context = LocalContext.current val context = LocalContext.current
var imageUriList by remember { mutableStateOf(listOf<String>()) } val model = NewPostViewModel
val pickImageLauncher = rememberLauncherForActivityResult( val pickImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data val uri = result.data?.data
if (uri != null) { if (uri != null) {
imageUriList = imageUriList + uri.toString() model.imageUriList += uri.toString()
} }
} }
} }
@@ -159,16 +173,17 @@ fun AddImageGrid() {
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
imageUriList.forEach { model.imageUriList.forEach {
Image( AsyncImage(
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic), it,
contentDescription = "Add Image", contentDescription = "Image",
modifier = Modifier modifier = Modifier
.size(110.dp) .size(110.dp)
.background(Color(0xFFFFFFFF)) .background(Color(0xFFFFFFFF))
.drawBehind { .drawBehind {
drawRoundRect(color = Color(0xFF999999), style = stroke) drawRoundRect(color = Color(0xFF999999), style = stroke)
} },
contentScale = ContentScale.Crop
) )
} }
Box( Box(

View File

@@ -2,19 +2,33 @@ package com.aiosman.riderpro.ui.post
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.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.ui.modification.Modification import com.aiosman.riderpro.ui.modification.Modification
import kotlinx.coroutines.launch
object NewPostViewModel : ViewModel() { object NewPostViewModel : ViewModel() {
var momentService: MomentService = TestMomentServiceImpl()
var textContent by mutableStateOf("") var textContent by mutableStateOf("")
var searchPlaceAddressResult by mutableStateOf<SearchPlaceAddressResult?>(null) var searchPlaceAddressResult by mutableStateOf<SearchPlaceAddressResult?>(null)
var modificationList by mutableStateOf<List<Modification>>(listOf()) var modificationList by mutableStateOf<List<Modification>>(listOf())
var imageUriList by mutableStateOf(listOf<String>())
fun asNewPost() { fun asNewPost() {
textContent = "" textContent = ""
searchPlaceAddressResult = null searchPlaceAddressResult = null
modificationList = listOf() modificationList = listOf()
} }
suspend fun createMoment() {
momentService.createMoment(
content = textContent,
authorId = 1,
imageUriList = imageUriList
)
}
} }