新增动态

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

@@ -2,6 +2,7 @@ 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
@@ -16,6 +17,12 @@ interface MomentService {
author: Int? = null,
timelineId: Int? = null
): ListContainer<MomentItem>
suspend fun createMoment(
content: String,
authorId: Int,
imageUriList: List<String>
): MomentItem
}
@@ -86,6 +93,14 @@ class TestMomentServiceImpl() : MomentService {
testMomentBackend.dislikeMoment(id)
}
override suspend fun createMoment(
content: String,
authorId: Int,
imageUriList: List<String>
): MomentItem {
return testMomentBackend.createMoment(content, authorId, imageUriList)
}
}
class TestMomentBackend(
@@ -98,6 +113,7 @@ class TestMomentBackend(
timelineId: Int?
): ListContainer<MomentItem> {
var rawList = TestDatabase.momentData
rawList = rawList.sortedBy { it.id }.reversed()
if (author != null) {
rawList = rawList.filter { it.authorId == author }
}
@@ -105,7 +121,7 @@ class TestMomentBackend(
val followIdList = TestDatabase.followList.filter {
it.first == timelineId
}.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 to = (pageNumber) * DataBatchSize
@@ -119,11 +135,13 @@ class TestMomentBackend(
}
val currentSublist = rawList.subList(from, min(to, rawList.size))
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)) {
it.liked = true
}
}
// delay
kotlinx.coroutines.delay(loadDelay)
return ListContainer(
@@ -153,6 +171,7 @@ class TestMomentBackend(
TestDatabase.updateMomentById(id, newMoment)
TestDatabase.likeMomentList += Pair(id, 1)
}
suspend fun dislikeMoment(id: Int) {
val oldMoment = TestDatabase.momentData.first {
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 comment = emptyList<Comment>()
var commentIdCounter = 0
var momentIdCounter = 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",
@@ -76,6 +77,7 @@ object TestDatabase {
}
momentData = (0..60).toList().mapIndexed { idx, _ ->
momentIdCounter += 1
val person = accountData.random()
// make fake comment
val commentCount = faker.random.nextInt(0, 50)
@@ -88,7 +90,7 @@ object TestDatabase {
date = "2023-02-02 11:23",
likes = 0,
replies = emptyList(),
postId = idx,
postId = momentIdCounter,
avatar = commentPerson.avatar,
author = commentPerson.id,
id = commentIdCounter,
@@ -105,10 +107,10 @@ object TestDatabase {
val likeCount = faker.random.nextInt(0, 5)
for (i in 0..likeCount) {
val likePerson = accountData.random()
likeMomentList += Pair(idx, likePerson.id)
likeMomentList += Pair(momentIdCounter, likePerson.id)
}
MomentItem(
id = idx,
id = momentIdCounter,
avatar = person.avatar,
nickname = person.nickName,
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.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
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.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@@ -30,6 +34,7 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import coil.compose.AsyncImage
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.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MomentsList() {
val model = MomentViewModel
var dataFlow = model.momentsFlow
var moments = dataFlow.collectAsLazyPagingItems()
val scope = rememberCoroutineScope()
LazyColumn {
items(moments.itemCount) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentItem = momentItem,
onAddComment = {
scope.launch {
model.onAddComment(momentItem.id)
}
},
onLikeClick = {
scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
var refreshing by remember { mutableStateOf(false) }
moments.loadState
val state = rememberPullRefreshState(refreshing, onRefresh = {
model.refreshPager()
})
LaunchedEffect(moments.loadState) {
if (moments.loadState.refresh is LoadState.Loading) {
refreshing = true
} else {
refreshing = false
}
}
Box(Modifier.pullRefresh(state)) {
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),
liked = momentItem.liked
) {
onLikeClick()
}
}
}

View File

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

View File

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