首页新增推荐

This commit is contained in:
2024-10-25 22:01:58 +08:00
parent 5e65f217bb
commit d3518b1a82
14 changed files with 1084 additions and 697 deletions

View File

@@ -16,6 +16,7 @@ import com.aiosman.riderpro.ui.follower.FollowingListViewModel
import com.aiosman.riderpro.ui.index.IndexViewModel import com.aiosman.riderpro.ui.index.IndexViewModel
import com.aiosman.riderpro.ui.index.tabs.message.MessageListViewModel import com.aiosman.riderpro.ui.index.tabs.message.MessageListViewModel
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel
import com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.riderpro.ui.index.tabs.search.DiscoverViewModel import com.aiosman.riderpro.ui.index.tabs.search.DiscoverViewModel
import com.aiosman.riderpro.ui.index.tabs.search.SearchViewModel import com.aiosman.riderpro.ui.index.tabs.search.SearchViewModel
@@ -117,7 +118,7 @@ object AppState {
fun ReloadAppState(context: Context) { fun ReloadAppState(context: Context) {
// 重置动态列表页面 // 重置动态列表页面
MomentViewModel.ResetModel() TimelineMomentViewModel.ResetModel()
// 重置我的页面 // 重置我的页面
MyProfileViewModel.ResetModel() MyProfileViewModel.ResetModel()
// 重置发现页面 // 重置发现页面

View File

@@ -0,0 +1,507 @@
package com.aiosman.riderpro.ui.composables
import androidx.annotation.DrawableRes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
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
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
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 com.aiosman.riderpro.AppColors
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentImageEntity
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.CommentModalContent
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.navigateToPost
@Composable
fun MomentCard(
momentEntity: MomentEntity,
onLikeClick: () -> Unit = {},
onFavoriteClick: () -> Unit = {},
onAddComment: () -> Unit = {},
hideAction: Boolean = false
) {
var imageIndex by remember { mutableStateOf(0) }
val navController = LocalNavController.current
Column(
modifier = Modifier.fillMaxWidth().background(AppColors.background)
) {
Box(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
) {
MomentTopRowGroup(momentEntity = momentEntity)
}
Column(
modifier = Modifier
.fillMaxWidth()
.noRippleClickable {
navController.navigateToPost(
momentEntity.id,
highlightCommentId = 0,
initImagePagerIndex = imageIndex
)
}
) {
MomentContentGroup(
momentEntity = momentEntity,
onPageChange = { index -> imageIndex = index }
)
}
if (!hideAction) {
MomentBottomOperateRowGroup(
momentEntity = momentEntity,
onLikeClick = onLikeClick,
onAddComment = onAddComment,
onFavoriteClick = onFavoriteClick,
imageIndex = imageIndex,
onCommentClick = {
navController.navigateToPost(
momentEntity.id,
highlightCommentId = 0,
initImagePagerIndex = imageIndex
)
}
)
}
}
}
@Composable
fun ModificationListHeader() {
val navController = LocalNavController.current
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color(0xFFF8F8F8))
.padding(4.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
navController.navigate("ModificationList")
}
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.background(Color(0xFFEB4869))
.padding(8.dp)
) {
Icon(
Icons.Filled.Build,
contentDescription = "Modification Icon",
tint = Color.White, // Assuming the icon should be white
modifier = Modifier.size(12.dp)
)
}
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "Modification List",
color = Color(0xFF333333),
fontSize = 14.sp,
textAlign = TextAlign.Left
)
}
}
}
}
@Composable
fun MomentName(name: String) {
Text(
modifier = Modifier,
textAlign = TextAlign.Start,
text = name,
color = AppColors.text,
fontSize = 16.sp, style = TextStyle(fontWeight = FontWeight.Bold)
)
}
@Composable
fun MomentFollowBtn() {
Box(
modifier = Modifier
.size(width = 53.dp, height = 18.dp)
.padding(start = 8.dp),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier
.fillMaxSize(),
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = ""
)
Text(
text = "Follow",
color = Color.White,
fontSize = 12.sp
)
}
}
@Composable
fun MomentPostLocation(location: String) {
Text(
text = location,
color = AppColors.secondaryText,
fontSize = 12.sp
)
}
@Composable
fun MomentPostTime(time: String) {
Text(
modifier = Modifier,
text = time, color = AppColors.text,
fontSize = 12.sp
)
}
@Composable
fun MomentTopRowGroup(momentEntity: MomentEntity) {
val navController = LocalNavController.current
val context = LocalContext.current
Row(
modifier = Modifier
) {
CustomAsyncImage(
context,
momentEntity.avatar,
contentDescription = "",
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(40.dp))
.noRippleClickable {
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
momentEntity.authorId.toString()
)
)
},
contentScale = ContentScale.Crop
)
Column(
modifier = Modifier
.defaultMinSize()
.padding(start = 12.dp, end = 12.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(22.dp),
verticalAlignment = Alignment.CenterVertically
) {
MomentName(momentEntity.nickname)
// MomentFollowBtn()
}
Row(
modifier = Modifier
.fillMaxWidth()
.height(21.dp),
verticalAlignment = Alignment.CenterVertically
) {
MomentPostTime(momentEntity.time.timeAgo(context))
Spacer(modifier = Modifier.width(8.dp))
MomentPostLocation(momentEntity.location)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostImageView(
images: List<MomentImageEntity>,
onPageChange: (Int) -> Unit = {}
) {
val pagerState = rememberPagerState(pageCount = { images.size })
LaunchedEffect(pagerState.currentPage) {
onPageChange(pagerState.currentPage)
}
val context = LocalContext.current
Column(
modifier = Modifier.fillMaxWidth()
) {
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f),
) { page ->
val image = images[page]
CustomAsyncImage(
context,
image.thumbnail,
contentDescription = "Image",
blurHash = image.blurHash,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
)
}
}
}
@Composable
fun MomentContentGroup(
momentEntity: MomentEntity,
onPageChange: (Int) -> Unit = {}
) {
if (momentEntity.momentTextContent.isNotEmpty()) {
Text(
text = momentEntity.momentTextContent,
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
fontSize = 16.sp,
color = AppColors.text
)
}
if (momentEntity.relMoment != null) {
RelPostCard(
momentEntity = momentEntity.relMoment!!,
modifier = Modifier.background(Color(0xFFF8F8F8))
)
} else {
Box(
modifier = Modifier.fillMaxWidth()
) {
PostImageView(
images = momentEntity.images,
onPageChange = onPageChange
)
}
}
}
@Composable
fun MomentOperateBtn(@DrawableRes icon: Int, count: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = Modifier
.size(width = 24.dp, height = 24.dp),
painter = painterResource(id = icon),
contentDescription = "",
colorFilter = ColorFilter.tint(AppColors.text)
)
Text(
text = count,
modifier = Modifier.padding(start = 7.dp),
fontSize = 12.sp,
color = AppColors.text
)
}
}
@Composable
fun MomentOperateBtn(count: String, content: @Composable () -> Unit) {
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically
) {
content()
AnimatedCounter(
count = count.toInt(),
fontSize = 14,
modifier = Modifier
.padding(start = 7.dp)
.width(24.dp)
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MomentBottomOperateRowGroup(
onLikeClick: () -> Unit = {},
onAddComment: () -> Unit = {},
onCommentClick: () -> Unit = {},
onFavoriteClick: () -> Unit = {},
momentEntity: MomentEntity,
imageIndex: Int = 0
) {
var showCommentModal by remember { mutableStateOf(false) }
if (showCommentModal) {
ModalBottomSheet(
onDismissRequest = { showCommentModal = false },
containerColor = Color.White,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
windowInsets = WindowInsets(0),
dragHandle = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clip(CircleShape)
) {
}
}
) {
CommentModalContent(
postId = momentEntity.id,
commentCount = momentEntity.commentCount,
onCommentAdded = {
onAddComment()
}
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.padding(start = 16.dp, end = 0.dp)
) {
Row(
modifier = Modifier.fillMaxSize()
) {
Box(
modifier = Modifier.fillMaxHeight(),
contentAlignment = Alignment.Center
) {
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
AnimatedLikeIcon(
modifier = Modifier.size(24.dp),
liked = momentEntity.liked
) {
onLikeClick()
}
}
}
Spacer(modifier = Modifier.width(4.dp))
Box(
modifier = Modifier
.fillMaxHeight()
.noRippleClickable {
onCommentClick()
},
contentAlignment = Alignment.Center
) {
MomentOperateBtn(
icon = R.drawable.rider_pro_comment,
count = momentEntity.commentCount.toString()
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.noRippleClickable {
onFavoriteClick()
},
contentAlignment = Alignment.CenterEnd
) {
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
AnimatedFavouriteIcon(
modifier = Modifier.size(24.dp),
isFavourite = momentEntity.isFavorite
) {
onFavoriteClick()
}
}
}
}
if (momentEntity.images.size > 1) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
momentEntity.images.forEachIndexed { index, _ ->
Box(
modifier = Modifier
.size(4.dp)
.clip(CircleShape)
.background(
if (imageIndex == index) Color.Red else Color.Gray.copy(
alpha = 0.5f
)
)
.padding(1.dp)
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}
}
@Composable
fun MomentListLoading() {
CircularProgressIndicator(
modifier =
Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally),
color = Color.Red
)
}

View File

@@ -9,9 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.index.tabs.moment.MomentTopRowGroup
@Composable @Composable
fun RelPostCard( fun RelPostCard(

View File

@@ -1,13 +1,7 @@
package com.aiosman.riderpro.ui.index.tabs.moment package com.aiosman.riderpro.ui.index.tabs.moment
import androidx.annotation.DrawableRes
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -15,590 +9,124 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi 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
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text import androidx.compose.material3.Text
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
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
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.AppColors import com.aiosman.riderpro.AppColors
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.ui.index.tabs.moment.tabs.expolre.ExploreMomentsList
import com.aiosman.riderpro.R import com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline.TimelineMomentsList
import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentImageEntity
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.CommentModalContent
import com.aiosman.riderpro.ui.composables.AnimatedCounter
import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon
import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.RelPostCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.navigateToPost
import com.aiosman.riderpro.ui.post.NewPostViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
* 动态列表 * 动态列表
*/ */
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun MomentsList() { fun MomentsList() {
val model = MomentViewModel val model = MomentViewModel
var dataFlow = model.momentsFlow
var moments = dataFlow.collectAsLazyPagingItems()
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(
pullRefresh = true
)
})
val navigationBarPaddings = val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
LaunchedEffect(Unit) { var pagerState = rememberPagerState { 2 }
model.refreshPager() var scope = rememberCoroutineScope()
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding( .padding(
top = statusBarPaddingValues.calculateTopPadding(), top = statusBarPaddingValues.calculateTopPadding(),
bottom = navigationBarPaddings bottom = navigationBarPaddings
) ),
) { horizontalAlignment = Alignment.CenterHorizontally,
Box(Modifier.pullRefresh(state)) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(
moments.itemCount,
key = { idx -> moments[idx]?.id ?: idx }
) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem,
onAddComment = {
scope.launch {
model.onAddComment(momentItem.id)
}
},
onLikeClick = {
scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
}
}
},
onFavoriteClick = {
scope.launch {
if (momentItem.isFavorite) {
model.unfavoriteMoment(momentItem.id)
} else {
model.favoriteMoment(momentItem.id)
}
}
}
)
// Box(
// modifier = Modifier
// .height(4.dp)
// .fillMaxWidth()
// .background(Color(0xFFF0F2F5))
// )
}
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
}
@Composable
fun MomentCard(
momentEntity: MomentEntity,
onLikeClick: () -> Unit = {},
onFavoriteClick: () -> Unit = {},
onAddComment: () -> Unit = {},
hideAction: Boolean = false
) {
var imageIndex by remember { mutableStateOf(0) }
val navController = LocalNavController.current
Column(
modifier = Modifier.fillMaxWidth().background(AppColors.background)
) {
Box(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
) {
MomentTopRowGroup(momentEntity = momentEntity)
}
Column(
modifier = Modifier
.fillMaxWidth()
.noRippleClickable {
navController.navigateToPost(
momentEntity.id,
highlightCommentId = 0,
initImagePagerIndex = imageIndex
)
}
) {
MomentContentGroup(
momentEntity = momentEntity,
onPageChange = { index -> imageIndex = index }
)
}
if (!hideAction) {
MomentBottomOperateRowGroup(
momentEntity = momentEntity,
onLikeClick = onLikeClick,
onAddComment = onAddComment,
onFavoriteClick = onFavoriteClick,
imageIndex = imageIndex,
onCommentClick = {
navController.navigateToPost(
momentEntity.id,
highlightCommentId = 0,
initImagePagerIndex = imageIndex
)
}
)
}
}
}
@Composable
fun ModificationListHeader() {
val navController = LocalNavController.current
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color(0xFFF8F8F8))
.padding(4.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
navController.navigate("ModificationList")
}
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.background(Color(0xFFEB4869))
.padding(8.dp)
) {
Icon(
Icons.Filled.Build,
contentDescription = "Modification Icon",
tint = Color.White, // Assuming the icon should be white
modifier = Modifier.size(12.dp)
)
}
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "Modification List",
color = Color(0xFF333333),
fontSize = 14.sp,
textAlign = TextAlign.Left
)
}
}
}
}
@Composable
fun MomentName(name: String) {
Text(
modifier = Modifier,
textAlign = TextAlign.Start,
text = name,
color = AppColors.text,
fontSize = 16.sp, style = TextStyle(fontWeight = FontWeight.Bold)
)
}
@Composable
fun MomentFollowBtn() {
Box(
modifier = Modifier
.size(width = 53.dp, height = 18.dp)
.padding(start = 8.dp),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier
.fillMaxSize(),
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = ""
)
Text(
text = "Follow",
color = Color.White,
fontSize = 12.sp
)
}
}
@Composable
fun MomentPostLocation(location: String) {
Text(
text = location,
color = AppColors.secondaryText,
fontSize = 12.sp
)
}
@Composable
fun MomentPostTime(time: String) {
Text(
modifier = Modifier,
text = time, color = AppColors.text,
fontSize = 12.sp
)
}
@Composable
fun MomentTopRowGroup(momentEntity: MomentEntity) {
val navController = LocalNavController.current
val context = LocalContext.current
Row(
modifier = Modifier
) {
CustomAsyncImage(
context,
momentEntity.avatar,
contentDescription = "",
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(40.dp))
.noRippleClickable {
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
momentEntity.authorId.toString()
)
)
},
contentScale = ContentScale.Crop
)
Column(
modifier = Modifier
.defaultMinSize()
.padding(start = 12.dp, end = 12.dp)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier.fillMaxWidth().height(44.dp),
.fillMaxWidth() // center the tabs
.height(22.dp), horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
MomentName(momentEntity.nickname)
// MomentFollowBtn()
}
Row(
modifier = Modifier
.fillMaxWidth()
.height(21.dp),
verticalAlignment = Alignment.CenterVertically
) {
MomentPostTime(momentEntity.time.timeAgo(context))
Spacer(modifier = Modifier.width(8.dp))
MomentPostLocation(momentEntity.location)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostImageView(
images: List<MomentImageEntity>,
onPageChange: (Int) -> Unit = {}
) {
val pagerState = rememberPagerState(pageCount = { images.size })
LaunchedEffect(pagerState.currentPage) {
onPageChange(pagerState.currentPage)
}
val context = LocalContext.current
Column( Column(
modifier = Modifier.fillMaxWidth() modifier = Modifier
.noRippleClickable {
scope.launch {
pagerState.animateScrollToPage(0)
}
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text(text = "Worldwide", fontSize = 16.sp, color = AppColors.text,fontWeight = FontWeight.W600)
Spacer(modifier = Modifier.height(4.dp))
Box(
modifier = Modifier
.width(48.dp)
.height(4.dp)
.clip(RoundedCornerShape(16.dp))
.background(if (pagerState.currentPage == 0) AppColors.text else AppColors.background)
)
}
Spacer(modifier = Modifier.width(32.dp))
Column(
modifier = Modifier
.noRippleClickable {
scope.launch {
pagerState.animateScrollToPage(1)
}
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Following", fontSize = 16.sp, color = AppColors.text, fontWeight = FontWeight.W600)
Spacer(modifier = Modifier.height(4.dp))
Box(
modifier = Modifier
.width(48.dp)
.height(4.dp)
.clip(RoundedCornerShape(16.dp))
.background(if (pagerState.currentPage == 1) AppColors.text else AppColors.background)
)
}
}
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(1f), .weight(1f)
) { page ->
val image = images[page]
CustomAsyncImage(
context,
image.thumbnail,
contentDescription = "Image",
blurHash = image.blurHash,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
)
}
}
}
@Composable
fun MomentContentGroup(
momentEntity: MomentEntity,
onPageChange: (Int) -> Unit = {}
) {
if (momentEntity.momentTextContent.isNotEmpty()) {
Text(
text = momentEntity.momentTextContent,
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
fontSize = 16.sp,
color = AppColors.text
)
}
if (momentEntity.relMoment != null) {
RelPostCard(
momentEntity = momentEntity.relMoment!!,
modifier = Modifier.background(Color(0xFFF8F8F8))
)
} else {
Box(
modifier = Modifier.fillMaxWidth()
) { ) {
PostImageView( when (it) {
images = momentEntity.images, 0 -> {
onPageChange = onPageChange ExploreMomentsList()
)
}
} }
1 -> {
TimelineMomentsList()
}
}
}
}
} }
@Composable
fun MomentOperateBtn(@DrawableRes icon: Int, count: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = Modifier
.size(width = 24.dp, height = 24.dp),
painter = painterResource(id = icon),
contentDescription = "",
colorFilter = ColorFilter.tint(AppColors.text)
)
Text(
text = count,
modifier = Modifier.padding(start = 7.dp),
fontSize = 12.sp,
color = AppColors.text
)
}
}
@Composable
fun MomentOperateBtn(count: String, content: @Composable () -> Unit) {
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically
) {
content()
AnimatedCounter(
count = count.toInt(),
fontSize = 14,
modifier = Modifier
.padding(start = 7.dp)
.width(24.dp)
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MomentBottomOperateRowGroup(
onLikeClick: () -> Unit = {},
onAddComment: () -> Unit = {},
onCommentClick: () -> Unit = {},
onFavoriteClick: () -> Unit = {},
momentEntity: MomentEntity,
imageIndex: Int = 0
) {
var showCommentModal by remember { mutableStateOf(false) }
if (showCommentModal) {
ModalBottomSheet(
onDismissRequest = { showCommentModal = false },
containerColor = Color.White,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
windowInsets = WindowInsets(0),
dragHandle = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clip(CircleShape)
) {
}
}
) {
CommentModalContent(
postId = momentEntity.id,
commentCount = momentEntity.commentCount,
onCommentAdded = {
onAddComment()
}
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.padding(start = 16.dp, end = 0.dp)
) {
Row(
modifier = Modifier.fillMaxSize()
) {
Box(
modifier = Modifier.fillMaxHeight(),
contentAlignment = Alignment.Center
) {
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
AnimatedLikeIcon(
modifier = Modifier.size(24.dp),
liked = momentEntity.liked
) {
onLikeClick()
}
}
}
Spacer(modifier = Modifier.width(4.dp))
Box(
modifier = Modifier
.fillMaxHeight()
.noRippleClickable {
onCommentClick()
},
contentAlignment = Alignment.Center
) {
MomentOperateBtn(
icon = R.drawable.rider_pro_comment,
count = momentEntity.commentCount.toString()
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.noRippleClickable {
onFavoriteClick()
},
contentAlignment = Alignment.CenterEnd
) {
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
AnimatedFavouriteIcon(
modifier = Modifier.size(24.dp),
isFavourite = momentEntity.isFavorite
) {
onFavoriteClick()
}
}
}
}
if (momentEntity.images.size > 1) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
momentEntity.images.forEachIndexed { index, _ ->
Box(
modifier = Modifier
.size(4.dp)
.clip(CircleShape)
.background(
if (imageIndex == index) Color.Red else Color.Gray.copy(
alpha = 0.5f
)
)
.padding(1.dp)
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}
}
@Composable
fun MomentListLoading() {
CircularProgressIndicator(
modifier =
Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally),
color = Color.Red
)
}

View File

@@ -26,157 +26,5 @@ import kotlinx.coroutines.launch
object MomentViewModel : ViewModel() { object MomentViewModel : ViewModel() {
private val momentService: MomentService = MomentServiceImpl()
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
val momentsFlow = _momentsFlow.asStateFlow()
var existsMoment = mutableStateOf(false)
var refreshing by mutableStateOf(false)
var isFirstLoad = true
fun refreshPager(pullRefresh: Boolean = false) {
if (!isFirstLoad && !pullRefresh) {
return
}
isFirstLoad = false
viewModelScope.launch {
if (pullRefresh) {
refreshing = true
}
// 检查是否有动态
val existMoments =
momentService.getMoments(timelineId = AppState.UserId, pageNumber = 1)
if (existMoments.list.isEmpty()) {
existsMoment.value = true
}
if (pullRefresh) {
refreshing = false
}
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
MomentPagingSource(
MomentRemoteDataSource(momentService),
// 如果没有动态,则显示热门动态
timelineId = if (existMoments.list.isEmpty()) null else AppState.UserId,
trend = if (existMoments.list.isEmpty()) true else null
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_momentsFlow.value = it
}
}
}
fun updateLikeCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(likeCount = momentItem.likeCount + 1, liked = true)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun likeMoment(id: Int) {
momentService.likeMoment(id)
updateLikeCount(id)
}
fun updateCommentCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(commentCount = momentItem.commentCount + 1)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun onAddComment(id: Int) {
val currentPagingData = _momentsFlow.value
updateCommentCount(id)
}
fun updateDislikeMomentById(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(likeCount = momentItem.likeCount - 1, liked = false)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun dislikeMoment(id: Int) {
momentService.dislikeMoment(id)
updateDislikeMomentById(id)
}
fun updateFavoriteCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(favoriteCount = momentItem.favoriteCount + 1, isFavorite = true)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun favoriteMoment(id: Int) {
momentService.favoriteMoment(id)
updateFavoriteCount(id)
}
fun updateUnfavoriteCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(favoriteCount = momentItem.favoriteCount - 1, isFavorite = false)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun unfavoriteMoment(id: Int) {
momentService.unfavoriteMoment(id)
updateUnfavoriteCount(id)
}
fun deleteMoment(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.filter { momentItem ->
momentItem.id != id
}
_momentsFlow.value = updatedPagingData
}
/**
* 更新动态评论数
*/
fun updateMomentCommentCount(id: Int, delta: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(commentCount = momentItem.commentCount + delta)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
fun ResetModel() {
_momentsFlow.value = PagingData.empty()
isFirstLoad = true
}
} }

View File

@@ -0,0 +1,82 @@
package com.aiosman.riderpro.ui.index.tabs.moment.tabs.expolre
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.ui.composables.MomentCard
import kotlinx.coroutines.launch
/**
* 动态列表
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ExploreMomentsList() {
val model = MomentExploreViewModel
var dataFlow = model.momentsFlow
var moments = dataFlow.collectAsLazyPagingItems()
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(
pullRefresh = true
)
})
LaunchedEffect(Unit) {
model.refreshPager()
}
Column(
modifier = Modifier
.fillMaxSize()
) {
Box(Modifier.pullRefresh(state)) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(
moments.itemCount,
key = { idx -> moments[idx]?.id ?: idx }
) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem,
onAddComment = {
scope.launch {
model.onAddComment(momentItem.id)
}
},
onLikeClick = {
scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
}
}
},
onFavoriteClick = {
scope.launch {
if (momentItem.isFavorite) {
model.unfavoriteMoment(momentItem.id)
} else {
model.favoriteMoment(momentItem.id)
}
}
}
)
}
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
}

View File

@@ -0,0 +1,155 @@
package com.aiosman.riderpro.ui.index.tabs.moment.tabs.expolre
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import com.aiosman.riderpro.AppState
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentPagingSource
import com.aiosman.riderpro.entity.MomentRemoteDataSource
import com.aiosman.riderpro.entity.MomentServiceImpl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object MomentExploreViewModel : ViewModel() {
private val momentService: MomentService = MomentServiceImpl()
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
val momentsFlow = _momentsFlow.asStateFlow()
var existsMoment = mutableStateOf(false)
var refreshing by mutableStateOf(false)
var isFirstLoad = true
fun refreshPager(pullRefresh: Boolean = false) {
if (!isFirstLoad && !pullRefresh) {
return
}
isFirstLoad = false
viewModelScope.launch {
if (pullRefresh) {
refreshing = true
}
// 检查是否有动态
val existMoments =
momentService.getMoments(timelineId = AppState.UserId, pageNumber = 1)
if (existMoments.list.isEmpty()) {
existsMoment.value = true
}
if (pullRefresh) {
refreshing = false
}
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
MomentPagingSource(
MomentRemoteDataSource(momentService),
// 如果没有动态,则显示热门动态
trend = true
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_momentsFlow.value = it
}
}
}
fun updateLikeCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(likeCount = momentItem.likeCount + 1, liked = true)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun likeMoment(id: Int) {
momentService.likeMoment(id)
updateLikeCount(id)
}
fun updateCommentCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(commentCount = momentItem.commentCount + 1)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun onAddComment(id: Int) {
val currentPagingData = _momentsFlow.value
updateCommentCount(id)
}
fun updateDislikeMomentById(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(likeCount = momentItem.likeCount - 1, liked = false)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun dislikeMoment(id: Int) {
momentService.dislikeMoment(id)
updateDislikeMomentById(id)
}
fun updateFavoriteCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(favoriteCount = momentItem.favoriteCount + 1, isFavorite = true)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun favoriteMoment(id: Int) {
momentService.favoriteMoment(id)
updateFavoriteCount(id)
}
fun updateUnfavoriteCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(favoriteCount = momentItem.favoriteCount - 1, isFavorite = false)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun unfavoriteMoment(id: Int) {
momentService.unfavoriteMoment(id)
updateUnfavoriteCount(id)
}
fun ResetModel() {
_momentsFlow.value = PagingData.empty()
isFirstLoad = true
}
}

View File

@@ -0,0 +1,87 @@
package com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.ui.composables.MomentCard
import kotlinx.coroutines.launch
/**
* 动态列表
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun TimelineMomentsList() {
val model = TimelineMomentViewModel
var dataFlow = model.momentsFlow
var moments = dataFlow.collectAsLazyPagingItems()
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(
pullRefresh = true
)
})
LaunchedEffect(Unit) {
model.refreshPager()
}
Column(
modifier = Modifier
.fillMaxSize()
) {
Box(Modifier.pullRefresh(state)) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(
moments.itemCount,
key = { idx -> moments[idx]?.id ?: idx }
) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem,
onAddComment = {
scope.launch {
model.onAddComment(momentItem.id)
}
},
onLikeClick = {
scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
}
}
},
onFavoriteClick = {
scope.launch {
if (momentItem.isFavorite) {
model.unfavoriteMoment(momentItem.id)
} else {
model.favoriteMoment(momentItem.id)
}
}
}
)
// Box(
// modifier = Modifier
// .height(4.dp)
// .fillMaxWidth()
// .background(Color(0xFFF0F2F5))
// )
}
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
}

View File

@@ -0,0 +1,180 @@
package com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.filter
import androidx.paging.map
import com.aiosman.riderpro.AppState
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentPagingSource
import com.aiosman.riderpro.entity.MomentRemoteDataSource
import com.aiosman.riderpro.entity.MomentServiceImpl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object TimelineMomentViewModel : ViewModel() {
private val momentService: MomentService = MomentServiceImpl()
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
val momentsFlow = _momentsFlow.asStateFlow()
var existsMoment = mutableStateOf(false)
var refreshing by mutableStateOf(false)
var isFirstLoad = true
fun refreshPager(pullRefresh: Boolean = false) {
if (!isFirstLoad && !pullRefresh) {
return
}
isFirstLoad = false
viewModelScope.launch {
if (pullRefresh) {
refreshing = true
}
// 检查是否有动态
val existMoments =
momentService.getMoments(timelineId = AppState.UserId, pageNumber = 1)
if (existMoments.list.isEmpty()) {
existsMoment.value = true
}
if (pullRefresh) {
refreshing = false
}
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
MomentPagingSource(
MomentRemoteDataSource(momentService),
// 如果没有动态,则显示热门动态
timelineId = if (existMoments.list.isEmpty()) null else AppState.UserId,
trend = if (existMoments.list.isEmpty()) true else null
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_momentsFlow.value = it
}
}
}
fun updateLikeCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(likeCount = momentItem.likeCount + 1, liked = true)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun likeMoment(id: Int) {
momentService.likeMoment(id)
updateLikeCount(id)
}
fun updateCommentCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(commentCount = momentItem.commentCount + 1)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun onAddComment(id: Int) {
val currentPagingData = _momentsFlow.value
updateCommentCount(id)
}
fun updateDislikeMomentById(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(likeCount = momentItem.likeCount - 1, liked = false)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun dislikeMoment(id: Int) {
momentService.dislikeMoment(id)
updateDislikeMomentById(id)
}
fun updateFavoriteCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(favoriteCount = momentItem.favoriteCount + 1, isFavorite = true)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun favoriteMoment(id: Int) {
momentService.favoriteMoment(id)
updateFavoriteCount(id)
}
fun updateUnfavoriteCount(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(favoriteCount = momentItem.favoriteCount - 1, isFavorite = false)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
suspend fun unfavoriteMoment(id: Int) {
momentService.unfavoriteMoment(id)
updateUnfavoriteCount(id)
}
fun deleteMoment(id: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.filter { momentItem ->
momentItem.id != id
}
_momentsFlow.value = updatedPagingData
}
/**
* 更新动态评论数
*/
fun updateMomentCommentCount(id: Int, delta: Int) {
val currentPagingData = _momentsFlow.value
val updatedPagingData = currentPagingData.map { momentItem ->
if (momentItem.id == id) {
momentItem.copy(commentCount = momentItem.commentCount + delta)
} else {
momentItem
}
}
_momentsFlow.value = updatedPagingData
}
fun ResetModel() {
_momentsFlow.value = PagingData.empty()
isFirstLoad = true
}
}

View File

@@ -2,10 +2,8 @@ package com.aiosman.riderpro.ui.index.tabs.search
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -60,9 +58,8 @@ import com.aiosman.riderpro.AppState
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.ui.composables.ActionButton
import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.index.tabs.moment.MomentCard import com.aiosman.riderpro.ui.composables.MomentCard
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.launch import kotlinx.coroutines.launch

View File

@@ -33,7 +33,7 @@ import androidx.paging.LoadState
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.ui.index.tabs.moment.MomentListLoading import com.aiosman.riderpro.ui.composables.MomentListLoading
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.model.ChatNotificationData import com.aiosman.riderpro.model.ChatNotificationData
import com.aiosman.riderpro.model.TestChatBackend import com.aiosman.riderpro.model.TestChatBackend

View File

@@ -17,6 +17,7 @@ import com.aiosman.riderpro.data.CommentServiceImpl
import com.aiosman.riderpro.entity.CommentEntity import com.aiosman.riderpro.entity.CommentEntity
import com.aiosman.riderpro.entity.CommentPagingSource import com.aiosman.riderpro.entity.CommentPagingSource
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel
import com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@@ -200,7 +201,7 @@ class CommentsViewModel(
replyUserId = replyUserId, replyUserId = replyUserId,
replyCommentId = replyCommentId replyCommentId = replyCommentId
) )
MomentViewModel.updateCommentCount(postId.toInt()) TimelineMomentViewModel.updateCommentCount(postId.toInt())
// add to first // add to first
addedCommentList = listOf(comment) + addedCommentList addedCommentList = listOf(comment) + addedCommentList
} }

View File

@@ -16,6 +16,7 @@ import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.exp.rotate import com.aiosman.riderpro.exp.rotate
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel
import com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.riderpro.ui.modification.Modification import com.aiosman.riderpro.ui.modification.Modification
import com.aiosman.riderpro.utils.FileUtil import com.aiosman.riderpro.utils.FileUtil
@@ -160,7 +161,7 @@ object NewPostViewModel : ViewModel() {
momentService.createMoment(textContent, 1, uploadImageList, relPostId) momentService.createMoment(textContent, 1, uploadImageList, relPostId)
// 刷新个人动态 // 刷新个人动态
MyProfileViewModel.loadProfile(pullRefresh = true) MyProfileViewModel.loadProfile(pullRefresh = true)
MomentViewModel.refreshPager() TimelineMomentViewModel.refreshPager()
} }

View File

@@ -14,6 +14,8 @@ import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel
import com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
import com.aiosman.riderpro.ui.index.tabs.moment.tabs.timeline.TimelineMomentsList
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -80,7 +82,7 @@ class PostViewModel(
moment?.let { moment?.let {
service.likeMoment(it.id) service.likeMoment(it.id)
moment = moment?.copy(likeCount = moment?.likeCount?.plus(1) ?: 0, liked = true) moment = moment?.copy(likeCount = moment?.likeCount?.plus(1) ?: 0, liked = true)
MomentViewModel.updateLikeCount(it.id) TimelineMomentViewModel.updateLikeCount(it.id)
} }
} }
@@ -89,7 +91,7 @@ class PostViewModel(
service.dislikeMoment(it.id) service.dislikeMoment(it.id)
moment = moment?.copy(likeCount = moment?.likeCount?.minus(1) ?: 0, liked = false) moment = moment?.copy(likeCount = moment?.likeCount?.minus(1) ?: 0, liked = false)
// update home list // update home list
MomentViewModel.updateDislikeMomentById(it.id) TimelineMomentViewModel.updateDislikeMomentById(it.id)
} }
} }
@@ -130,7 +132,7 @@ class PostViewModel(
commentsViewModel.deleteComment(commentId) commentsViewModel.deleteComment(commentId)
moment = moment?.copy(commentCount = moment?.commentCount?.minus(1) ?: 0) moment = moment?.copy(commentCount = moment?.commentCount?.minus(1) ?: 0)
moment?.let { moment?.let {
MomentViewModel.updateMomentCommentCount(it.id, -1) TimelineMomentViewModel.updateMomentCommentCount(it.id, -1)
} }
} }
@@ -159,7 +161,7 @@ class PostViewModel(
viewModelScope.launch { viewModelScope.launch {
moment?.let { moment?.let {
service.deleteMoment(it.id) service.deleteMoment(it.id)
MomentViewModel.deleteMoment(it.id) TimelineMomentViewModel.deleteMoment(it.id)
} }
callback() callback()
} }