更新
This commit is contained in:
@@ -159,7 +159,11 @@ fun NavigationController(
|
||||
route = NavigationRoute.AccountProfile.route,
|
||||
arguments = listOf(navArgument("id") { type = NavType.StringType })
|
||||
) {
|
||||
AccountProfile(it.arguments?.getString("id")!!)
|
||||
CompositionLocalProvider(
|
||||
LocalAnimatedContentScope provides this,
|
||||
) {
|
||||
AccountProfile(it.arguments?.getString("id")!!)
|
||||
}
|
||||
}
|
||||
composable(route = NavigationRoute.SignUp.route) {
|
||||
SignupScreen()
|
||||
|
||||
@@ -31,7 +31,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.aiosman.riderpro.data.AccountProfile
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.data.AccountService
|
||||
import com.aiosman.riderpro.data.TestAccountServiceImpl
|
||||
import com.aiosman.riderpro.data.TestUserServiceImpl
|
||||
@@ -47,7 +47,7 @@ fun AccountEditScreen() {
|
||||
var name by remember { mutableStateOf("") }
|
||||
var bio by remember { mutableStateOf("") }
|
||||
var profile by remember {
|
||||
mutableStateOf<AccountProfile?>(
|
||||
mutableStateOf<AccountProfileEntity?>(
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import androidx.paging.cachedIn
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.aiosman.riderpro.ui.post.CommentsSection
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.Comment
|
||||
import com.aiosman.riderpro.data.CommentEntity
|
||||
import com.aiosman.riderpro.data.CommentPagingSource
|
||||
import com.aiosman.riderpro.data.CommentRemoteDataSource
|
||||
import com.aiosman.riderpro.data.CommentService
|
||||
@@ -57,10 +57,10 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CommentModalViewModel(
|
||||
postId: Int?
|
||||
val postId: Int?
|
||||
) : ViewModel() {
|
||||
val commentService: CommentService = TestCommentServiceImpl()
|
||||
val commentsFlow: Flow<PagingData<Comment>> = Pager(
|
||||
val commentsFlow: Flow<PagingData<CommentEntity>> = Pager(
|
||||
config = PagingConfig(pageSize = 20, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
CommentPagingSource(
|
||||
@@ -69,6 +69,12 @@ class CommentModalViewModel(
|
||||
)
|
||||
}
|
||||
).flow.cachedIn(viewModelScope)
|
||||
|
||||
suspend fun createComment(content: String) {
|
||||
postId?.let {
|
||||
commentService.createComment(postId, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@@ -104,8 +110,7 @@ fun CommentModalContent(
|
||||
var commentText by remember { mutableStateOf("") }
|
||||
suspend fun sendComment() {
|
||||
if (commentText.isNotEmpty()) {
|
||||
model.commentService.createComment(postId!!, commentText, 1)
|
||||
commentText = ""
|
||||
model.createComment(commentText)
|
||||
}
|
||||
comments.refresh()
|
||||
onCommentAdded()
|
||||
@@ -136,13 +141,16 @@ fun CommentModalContent(
|
||||
.padding(horizontal = 16.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
CommentsSection(lazyPagingItems = comments, onLike = { comment: Comment ->
|
||||
CommentsSection(lazyPagingItems = comments, onLike = { commentEntity: CommentEntity ->
|
||||
scope.launch {
|
||||
model.commentService.likeComment(comment.id)
|
||||
if (commentEntity.liked) {
|
||||
model.commentService.dislikeComment(commentEntity.id)
|
||||
} else {
|
||||
model.commentService.likeComment(commentEntity.id)
|
||||
}
|
||||
comments.refresh()
|
||||
}
|
||||
}) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.aiosman.riderpro.ui.composables
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun AnimatedFavouriteIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
isFavourite: Boolean = false,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
val animatableRotation = remember { Animatable(0f) }
|
||||
val animatedColor by animateColorAsState(targetValue = if (isFavourite) Color(0xFFd83737) else Color.Black)
|
||||
val scope = rememberCoroutineScope()
|
||||
suspend fun shake() {
|
||||
repeat(2) {
|
||||
animatableRotation.animateTo(
|
||||
targetValue = 10f,
|
||||
animationSpec = tween(100)
|
||||
) {
|
||||
|
||||
}
|
||||
animatableRotation.animateTo(
|
||||
targetValue = -10f,
|
||||
animationSpec = tween(100)
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
animatableRotation.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(100)
|
||||
)
|
||||
}
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.noRippleClickable {
|
||||
onClick?.invoke()
|
||||
// Trigger shake animation
|
||||
scope.launch {
|
||||
shake()
|
||||
}
|
||||
}) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_favoriate),
|
||||
contentDescription = "Like",
|
||||
modifier = modifier.graphicsLayer {
|
||||
rotationZ = animatableRotation.value
|
||||
},
|
||||
colorFilter = ColorFilter.tint(animatedColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,19 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.aiosman.riderpro.model.MomentItem
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.ui.index.tabs.moment.MomentTopRowGroup
|
||||
|
||||
@Composable
|
||||
fun RelPostCard(
|
||||
momentItem: MomentItem,
|
||||
momentEntity: MomentEntity,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val image = momentItem.images.firstOrNull()
|
||||
val image = momentEntity.images.firstOrNull()
|
||||
Column(
|
||||
modifier = modifier
|
||||
) {
|
||||
MomentTopRowGroup(momentItem = momentItem)
|
||||
MomentTopRowGroup(momentEntity = momentEntity)
|
||||
Box(
|
||||
modifier=Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
|
||||
@@ -53,18 +53,20 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import coil.compose.AsyncImage
|
||||
import com.aiosman.riderpro.LocalAnimatedContentScope
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.model.MomentItem
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
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.RelPostCard
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.riderpro.ui.post.NewPostViewModel
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@@ -88,10 +90,12 @@ fun MomentsList() {
|
||||
}
|
||||
|
||||
Box(Modifier.pullRefresh(state)) {
|
||||
LazyColumn {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(moments.itemCount) { idx ->
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
MomentCard(momentItem = momentItem,
|
||||
MomentCard(momentEntity = momentItem,
|
||||
onAddComment = {
|
||||
scope.launch {
|
||||
model.onAddComment(momentItem.id)
|
||||
@@ -105,6 +109,15 @@ fun MomentsList() {
|
||||
model.likeMoment(momentItem.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFavoriteClick = {
|
||||
scope.launch {
|
||||
if (momentItem.isFavorite) {
|
||||
model.unfavoriteMoment(momentItem.id)
|
||||
} else {
|
||||
model.favoriteMoment(momentItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -115,38 +128,40 @@ fun MomentsList() {
|
||||
|
||||
@Composable
|
||||
fun MomentCard(
|
||||
momentItem: MomentItem,
|
||||
momentEntity: MomentEntity,
|
||||
onLikeClick: () -> Unit,
|
||||
onFavoriteClick: () -> Unit = {},
|
||||
onAddComment: () -> Unit = {}
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
MomentTopRowGroup(momentItem = momentItem)
|
||||
MomentTopRowGroup(momentEntity = momentEntity)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.noRippleClickable {
|
||||
navController.navigate("Post/${momentItem.id}")
|
||||
navController.navigate("Post/${momentEntity.id}")
|
||||
}
|
||||
) {
|
||||
MomentContentGroup(momentItem = momentItem)
|
||||
MomentContentGroup(momentEntity = momentEntity)
|
||||
}
|
||||
val momentOperateBtnBoxModifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
ModificationListHeader()
|
||||
// ModificationListHeader()
|
||||
MomentBottomOperateRowGroup(
|
||||
momentOperateBtnBoxModifier,
|
||||
momentItem = momentItem,
|
||||
momentEntity = momentEntity,
|
||||
onLikeClick = onLikeClick,
|
||||
onAddComment = onAddComment,
|
||||
onShareClick = {
|
||||
NewPostViewModel.asNewPost()
|
||||
NewPostViewModel.relPostId = momentItem.id
|
||||
NewPostViewModel.relPostId = momentEntity.id
|
||||
navController.navigate(NavigationRoute.NewPost.route)
|
||||
}
|
||||
},
|
||||
onFavoriteClick = onFavoriteClick
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -248,7 +263,7 @@ fun MomentPostTime(time: String) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MomentTopRowGroup(momentItem: MomentItem) {
|
||||
fun MomentTopRowGroup(momentEntity: MomentEntity) {
|
||||
val navController = LocalNavController.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -256,7 +271,7 @@ fun MomentTopRowGroup(momentItem: MomentItem) {
|
||||
.padding(top = 0.dp, bottom = 0.dp, start = 24.dp, end = 24.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
momentItem.avatar,
|
||||
momentEntity.avatar,
|
||||
contentDescription = "",
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
@@ -264,7 +279,7 @@ fun MomentTopRowGroup(momentItem: MomentItem) {
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route.replace(
|
||||
"{id}",
|
||||
momentItem.authorId.toString()
|
||||
momentEntity.authorId.toString()
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -281,7 +296,7 @@ fun MomentTopRowGroup(momentItem: MomentItem) {
|
||||
.height(22.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MomentName(momentItem.nickname)
|
||||
MomentName(momentEntity.nickname)
|
||||
MomentFollowBtn()
|
||||
}
|
||||
Row(
|
||||
@@ -290,8 +305,8 @@ fun MomentTopRowGroup(momentItem: MomentItem) {
|
||||
.height(21.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MomentPostLocation(momentItem.location)
|
||||
MomentPostTime(momentItem.time)
|
||||
MomentPostLocation(momentEntity.location)
|
||||
MomentPostTime(momentEntity.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,28 +315,40 @@ fun MomentTopRowGroup(momentItem: MomentItem) {
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun MomentContentGroup(
|
||||
momentItem: MomentItem,
|
||||
momentEntity: MomentEntity,
|
||||
) {
|
||||
val displayImageUrl = momentItem.images.firstOrNull()
|
||||
val displayImageUrl = momentEntity.images.firstOrNull()
|
||||
val sharedTransitionScope = LocalSharedTransitionScope.current
|
||||
val animatedVisibilityScope = LocalAnimatedContentScope.current
|
||||
Text(
|
||||
text = "${momentItem.id} ${momentItem.momentTextContent}",
|
||||
text = "${momentEntity.momentTextContent}",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 22.dp, bottom = 16.dp, start = 24.dp, end = 24.dp),
|
||||
fontSize = 16.sp
|
||||
)
|
||||
if (momentItem.relMoment != null) {
|
||||
RelPostCard(momentItem = momentItem.relMoment!!, modifier = Modifier.background(Color(0xFFF8F8F8)))
|
||||
}else{
|
||||
if (momentEntity.relMoment != null) {
|
||||
RelPostCard(
|
||||
momentEntity = momentEntity.relMoment!!,
|
||||
modifier = Modifier.background(Color(0xFFF8F8F8))
|
||||
)
|
||||
} else {
|
||||
displayImageUrl?.let {
|
||||
AsyncImage(
|
||||
it,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f),
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = ""
|
||||
)
|
||||
with(sharedTransitionScope) {
|
||||
AsyncImage(
|
||||
it,
|
||||
modifier = Modifier
|
||||
.sharedElement(
|
||||
rememberSharedContentState(key = it),
|
||||
animatedVisibilityScope = animatedVisibilityScope
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f),
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,8 +393,9 @@ fun MomentBottomOperateRowGroup(
|
||||
modifier: Modifier,
|
||||
onLikeClick: () -> Unit = {},
|
||||
onAddComment: () -> Unit = {},
|
||||
onFavoriteClick: () -> Unit = {},
|
||||
onShareClick: () -> Unit = {},
|
||||
momentItem: MomentItem
|
||||
momentEntity: MomentEntity
|
||||
) {
|
||||
var systemUiController = rememberSystemUiController()
|
||||
var showCommentModal by remember { mutableStateOf(false) }
|
||||
@@ -380,7 +408,7 @@ fun MomentBottomOperateRowGroup(
|
||||
)
|
||||
) {
|
||||
systemUiController.setNavigationBarColor(Color(0xfff7f7f7))
|
||||
CommentModalContent(postId = momentItem.id, onCommentAdded = {
|
||||
CommentModalContent(postId = momentEntity.id, onCommentAdded = {
|
||||
showCommentModal = false
|
||||
onAddComment()
|
||||
}) {
|
||||
@@ -397,10 +425,10 @@ fun MomentBottomOperateRowGroup(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
MomentOperateBtn(count = momentItem.likeCount.toString()) {
|
||||
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
|
||||
AnimatedLikeIcon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
liked = momentItem.liked
|
||||
liked = momentEntity.liked
|
||||
) {
|
||||
onLikeClick()
|
||||
}
|
||||
@@ -417,28 +445,34 @@ fun MomentBottomOperateRowGroup(
|
||||
) {
|
||||
MomentOperateBtn(
|
||||
icon = R.drawable.rider_pro_moment_comment,
|
||||
count = momentItem.commentCount.toString()
|
||||
count = momentEntity.commentCount.toString()
|
||||
)
|
||||
}
|
||||
// Box(
|
||||
// modifier = modifier.noRippleClickable {
|
||||
// onShareClick()
|
||||
// },
|
||||
// contentAlignment = Alignment.Center
|
||||
// ) {
|
||||
// MomentOperateBtn(
|
||||
// icon = R.drawable.rider_pro_share,
|
||||
// count = momentEntity.shareCount.toString()
|
||||
// )
|
||||
// }
|
||||
Box(
|
||||
modifier = modifier.noRippleClickable {
|
||||
onShareClick()
|
||||
onFavoriteClick()
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
MomentOperateBtn(
|
||||
icon = R.drawable.rider_pro_share,
|
||||
count = momentItem.shareCount.toString()
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
MomentOperateBtn(
|
||||
icon = R.drawable.rider_pro_favoriate,
|
||||
count = momentItem.favoriteCount.toString()
|
||||
)
|
||||
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
|
||||
AnimatedFavouriteIcon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
isFavourite = momentEntity.isFavorite
|
||||
) {
|
||||
onFavoriteClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.aiosman.riderpro.data.MomentRemoteDataSource
|
||||
import com.aiosman.riderpro.data.MomentService
|
||||
import com.aiosman.riderpro.data.TestAccountServiceImpl
|
||||
import com.aiosman.riderpro.data.TestMomentServiceImpl
|
||||
import com.aiosman.riderpro.model.MomentItem
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -22,7 +22,7 @@ import kotlinx.coroutines.launch
|
||||
|
||||
object MomentViewModel : ViewModel() {
|
||||
private val momentService: MomentService = TestMomentServiceImpl()
|
||||
private val _momentsFlow = MutableStateFlow<PagingData<MomentItem>>(PagingData.empty())
|
||||
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
|
||||
val momentsFlow = _momentsFlow.asStateFlow()
|
||||
val accountService: AccountService = TestAccountServiceImpl()
|
||||
init {
|
||||
@@ -91,6 +91,7 @@ object MomentViewModel : ViewModel() {
|
||||
updateCommentCount(id)
|
||||
}
|
||||
|
||||
|
||||
fun updateDislikeMomentById(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
@@ -108,4 +109,34 @@ object MomentViewModel : ViewModel() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,25 @@ package com.aiosman.riderpro.ui.index.tabs.profile
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import com.aiosman.riderpro.AppStore
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.data.AccountProfile
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.data.AccountService
|
||||
import com.aiosman.riderpro.data.TestAccountServiceImpl
|
||||
import com.aiosman.riderpro.data.MomentPagingSource
|
||||
import com.aiosman.riderpro.data.MomentRemoteDataSource
|
||||
import com.aiosman.riderpro.data.TestMomentServiceImpl
|
||||
import com.aiosman.riderpro.data.TestUserServiceImpl
|
||||
import com.aiosman.riderpro.model.MomentItem
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
object MyProfileViewModel {
|
||||
val service: AccountService = TestAccountServiceImpl()
|
||||
val userService = TestUserServiceImpl()
|
||||
var profile by mutableStateOf<AccountProfile?>(null)
|
||||
var momentsFlow by mutableStateOf<Flow<PagingData<MomentItem>>?>(null)
|
||||
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var momentsFlow by mutableStateOf<Flow<PagingData<MomentEntity>>?>(null)
|
||||
suspend fun loadProfile() {
|
||||
profile = service.getMyAccountProfile()
|
||||
momentsFlow = Pager(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.aiosman.riderpro.ui.index.tabs.profile
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -46,10 +47,12 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import coil.compose.AsyncImage
|
||||
import com.aiosman.riderpro.LocalAnimatedContentScope
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.AccountProfile
|
||||
import com.aiosman.riderpro.model.MomentItem
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -119,9 +122,8 @@ fun ProfilePage() {
|
||||
}
|
||||
CarGroup()
|
||||
model.profile?.let {
|
||||
UserInformation(accountProfile = it)
|
||||
UserInformation(accountProfileEntity = it)
|
||||
}
|
||||
|
||||
RidingStyle()
|
||||
}
|
||||
moments?.let {
|
||||
@@ -184,7 +186,11 @@ fun CarTopPicture() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserInformation(isSelf: Boolean = true, accountProfile: AccountProfile) {
|
||||
fun UserInformation(
|
||||
isSelf: Boolean = true,
|
||||
accountProfileEntity: AccountProfileEntity,
|
||||
onFollowClick: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -193,21 +199,25 @@ fun UserInformation(isSelf: Boolean = true, accountProfile: AccountProfile) {
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
val userInfoModifier = Modifier.weight(1f)
|
||||
UserInformationFollowers(userInfoModifier, accountProfile)
|
||||
UserInformationBasic(userInfoModifier, accountProfile)
|
||||
UserInformationFollowing(userInfoModifier, accountProfile)
|
||||
UserInformationFollowers(userInfoModifier, accountProfileEntity)
|
||||
UserInformationBasic(userInfoModifier, accountProfileEntity)
|
||||
UserInformationFollowing(userInfoModifier, accountProfileEntity)
|
||||
}
|
||||
UserInformationSlogan()
|
||||
CommunicationOperatorGroup(isSelf = isSelf)
|
||||
CommunicationOperatorGroup(
|
||||
isSelf = isSelf,
|
||||
isFollowing = accountProfileEntity.isFollowing,
|
||||
onFollowClick = onFollowClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserInformationFollowers(modifier: Modifier, accountProfile: AccountProfile) {
|
||||
fun UserInformationFollowers(modifier: Modifier, accountProfileEntity: AccountProfileEntity) {
|
||||
Column(modifier = modifier.padding(top = 31.dp)) {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 5.dp),
|
||||
text = accountProfile.followerCount.toString(),
|
||||
text = accountProfileEntity.followerCount.toString(),
|
||||
fontSize = 24.sp,
|
||||
color = Color.Black,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
@@ -229,7 +239,7 @@ fun UserInformationFollowers(modifier: Modifier, accountProfile: AccountProfile)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserInformationBasic(modifier: Modifier, accountProfile: AccountProfile) {
|
||||
fun UserInformationBasic(modifier: Modifier, accountProfileEntity: AccountProfileEntity) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
@@ -242,7 +252,7 @@ fun UserInformationBasic(modifier: Modifier, accountProfile: AccountProfile) {
|
||||
painter = painterResource(id = R.drawable.avatar_bold), contentDescription = ""
|
||||
)
|
||||
AsyncImage(
|
||||
accountProfile.avatar,
|
||||
accountProfileEntity.avatar,
|
||||
modifier = Modifier
|
||||
.size(width = 88.dp, height = 88.dp)
|
||||
.clip(
|
||||
@@ -257,7 +267,7 @@ fun UserInformationBasic(modifier: Modifier, accountProfile: AccountProfile) {
|
||||
modifier = Modifier
|
||||
.widthIn(max = 220.dp)
|
||||
.padding(top = 8.dp),
|
||||
text = accountProfile.nickName,
|
||||
text = accountProfileEntity.nickName,
|
||||
fontSize = 32.sp,
|
||||
color = Color.Black,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
@@ -265,7 +275,7 @@ fun UserInformationBasic(modifier: Modifier, accountProfile: AccountProfile) {
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = accountProfile.country,
|
||||
text = accountProfileEntity.country,
|
||||
fontSize = 12.sp,
|
||||
color = Color.Gray
|
||||
)
|
||||
@@ -279,14 +289,14 @@ fun UserInformationBasic(modifier: Modifier, accountProfile: AccountProfile) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserInformationFollowing(modifier: Modifier, accountProfile: AccountProfile) {
|
||||
fun UserInformationFollowing(modifier: Modifier, accountProfileEntity: AccountProfileEntity) {
|
||||
Column(
|
||||
modifier = modifier.padding(top = 6.dp),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 5.dp),
|
||||
text = accountProfile.followingCount.toString(),
|
||||
text = accountProfileEntity.followingCount.toString(),
|
||||
fontSize = 24.sp,
|
||||
color = Color.Black,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
@@ -320,7 +330,11 @@ fun UserInformationSlogan() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CommunicationOperatorGroup(isSelf: Boolean = true) {
|
||||
fun CommunicationOperatorGroup(
|
||||
isSelf: Boolean = true,
|
||||
isFollowing: Boolean = false,
|
||||
onFollowClick: () -> Unit
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -329,7 +343,11 @@ fun CommunicationOperatorGroup(isSelf: Boolean = true) {
|
||||
) {
|
||||
if (!isSelf) {
|
||||
Box(
|
||||
modifier = Modifier.size(width = 142.dp, height = 40.dp),
|
||||
modifier = Modifier
|
||||
.size(width = 142.dp, height = 40.dp)
|
||||
.noRippleClickable {
|
||||
onFollowClick()
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
@@ -338,7 +356,7 @@ fun CommunicationOperatorGroup(isSelf: Boolean = true) {
|
||||
contentDescription = ""
|
||||
)
|
||||
Text(
|
||||
text = "FOLLOW",
|
||||
text = if (isFollowing) "FOLLOWING" else "FOLLOW",
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
@@ -472,13 +490,14 @@ fun UserMoment(scope: LazyListScope) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MomentPostUnit(momentItem: MomentItem) {
|
||||
TimeGroup(momentItem.time)
|
||||
MomentCard(
|
||||
momentItem.momentTextContent,
|
||||
momentItem.images[0],
|
||||
momentItem.likeCount.toString(),
|
||||
momentItem.commentCount.toString()
|
||||
fun MomentPostUnit(momentEntity: MomentEntity) {
|
||||
TimeGroup(momentEntity.time)
|
||||
ProfileMomentCard(
|
||||
momentEntity.momentTextContent,
|
||||
momentEntity.images[0],
|
||||
momentEntity.likeCount.toString(),
|
||||
momentEntity.commentCount.toString(),
|
||||
momentId = momentEntity.id
|
||||
)
|
||||
}
|
||||
|
||||
@@ -506,7 +525,13 @@ fun TimeGroup(time: String = "2024.06.08 12:23") {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MomentCard(content: String, imageUrl: String, like: String, comment: String) {
|
||||
fun ProfileMomentCard(
|
||||
content: String,
|
||||
imageUrl: String,
|
||||
like: String,
|
||||
comment: String,
|
||||
momentId: Int
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -514,7 +539,7 @@ fun MomentCard(content: String, imageUrl: String, like: String, comment: String)
|
||||
.border(width = 1.dp, color = Color(0f, 0f, 0f, 0.1f), shape = RoundedCornerShape(6.dp))
|
||||
) {
|
||||
MomentCardTopContent(content)
|
||||
MomentCardPicture(imageUrl)
|
||||
MomentCardPicture(imageUrl, momentId)
|
||||
MomentCardOperation(like, comment)
|
||||
}
|
||||
}
|
||||
@@ -534,16 +559,35 @@ fun MomentCardTopContent(content: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun MomentCardPicture(imageUrl: String) {
|
||||
AsyncImage(
|
||||
imageUrl,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentDescription = "",
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
fun MomentCardPicture(imageUrl: String, momentId: Int) {
|
||||
val navController = LocalNavController.current
|
||||
val sharedTransitionScope = LocalSharedTransitionScope.current
|
||||
val animatedVisibilityScope = LocalAnimatedContentScope.current
|
||||
with(sharedTransitionScope) {
|
||||
AsyncImage(
|
||||
imageUrl,
|
||||
modifier = Modifier
|
||||
.sharedElement(
|
||||
rememberSharedContentState(key = imageUrl),
|
||||
animatedVisibilityScope = animatedVisibilityScope
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.noRippleClickable {
|
||||
navController.navigate(
|
||||
NavigationRoute.Post.route.replace(
|
||||
"{id}",
|
||||
momentId.toString()
|
||||
)
|
||||
)
|
||||
},
|
||||
contentDescription = "",
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aiosman.riderpro.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -15,23 +16,106 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.riderpro.AppStore
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.AccountService
|
||||
import com.aiosman.riderpro.data.ServiceException
|
||||
import com.aiosman.riderpro.data.TestAccountServiceImpl
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
|
||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun EmailSignupScreen() {
|
||||
var email by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf("") }
|
||||
var confirmPassword by remember { mutableStateOf("") }
|
||||
var rememberMe by remember { mutableStateOf(false) }
|
||||
var acceptTerms by remember { mutableStateOf(false) }
|
||||
var acceptPromotions by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
val accountService: AccountService = TestAccountServiceImpl()
|
||||
fun validateForm(): Boolean {
|
||||
if (email.isEmpty()) {
|
||||
Toast.makeText(context, "Email is required", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
if (password.isEmpty()) {
|
||||
Toast.makeText(context, "Password is required", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
if (confirmPassword.isEmpty()) {
|
||||
Toast.makeText(context, "Confirm password is required", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
if (password != confirmPassword) {
|
||||
Toast.makeText(context, "Password does not match", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
if (!acceptTerms) {
|
||||
Toast.makeText(context, "You must accept terms", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
if (!acceptPromotions) {
|
||||
Toast.makeText(context, "You must accept promotions", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun registerUser() {
|
||||
if (!validateForm()) return
|
||||
// 注册
|
||||
try {
|
||||
accountService.registerUserWithPassword(email, password)
|
||||
} catch (e: ServiceException) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Failed to register", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
// 获取 token
|
||||
val authResp = accountService.loginUserWithPassword(email, password)
|
||||
if (authResp.token != null) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Successfully registered", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
AppStore.apply {
|
||||
token = authResp.token
|
||||
this.rememberMe = rememberMe
|
||||
saveData()
|
||||
}
|
||||
// 获取token 信息
|
||||
try {
|
||||
accountService.getMyAccount()
|
||||
} catch (e: ServiceException) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigate(NavigationRoute.Index.route) {
|
||||
popUpTo(NavigationRoute.Login.route) { inclusive = true }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
StatusBarMaskLayout {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@@ -74,9 +158,9 @@ fun EmailSignupScreen() {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
text = password,
|
||||
text = confirmPassword,
|
||||
onValueChange = {
|
||||
password = it
|
||||
confirmPassword = it
|
||||
},
|
||||
password = true,
|
||||
label = "Confirm password",
|
||||
@@ -149,7 +233,11 @@ fun EmailSignupScreen() {
|
||||
.height(48.dp),
|
||||
text = "LET'S RIDE".uppercase(),
|
||||
backgroundImage = R.mipmap.rider_pro_signup_red_bg
|
||||
)
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
registerUser()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aiosman.riderpro.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -26,6 +27,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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
|
||||
@@ -38,6 +40,7 @@ import com.aiosman.riderpro.AppStore
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.AccountService
|
||||
import com.aiosman.riderpro.data.ServiceException
|
||||
import com.aiosman.riderpro.data.TestAccountServiceImpl
|
||||
import com.aiosman.riderpro.data.TestUserServiceImpl
|
||||
import com.aiosman.riderpro.data.UserService
|
||||
@@ -57,18 +60,24 @@ fun UserAuthScreen() {
|
||||
var accountService: AccountService = TestAccountServiceImpl()
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
fun onLogin() {
|
||||
scope.launch {
|
||||
val authResp = accountService.loginUserWithPassword(email, password)
|
||||
if (authResp.token != null) {
|
||||
AppStore.apply {
|
||||
token = authResp.token
|
||||
this.rememberMe = rememberMe
|
||||
saveData()
|
||||
}
|
||||
navController.navigate(NavigationRoute.Index.route) {
|
||||
popUpTo(NavigationRoute.Login.route) { inclusive = true }
|
||||
try {
|
||||
val authResp = accountService.loginUserWithPassword(email, password)
|
||||
if (authResp.token != null) {
|
||||
AppStore.apply {
|
||||
token = authResp.token
|
||||
this.rememberMe = rememberMe
|
||||
saveData()
|
||||
}
|
||||
navController.navigate(NavigationRoute.Index.route) {
|
||||
popUpTo(NavigationRoute.Login.route) { inclusive = true }
|
||||
}
|
||||
}
|
||||
} catch (e: ServiceException) {
|
||||
// handle error
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.aiosman.riderpro.ui.post
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Image
|
||||
@@ -63,6 +64,7 @@ fun NewPostScreen() {
|
||||
val model = NewPostViewModel
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
systemUiController.setNavigationBarColor(color = Color.Transparent)
|
||||
model.init()
|
||||
@@ -76,7 +78,7 @@ fun NewPostScreen() {
|
||||
) {
|
||||
NewPostTopBar {
|
||||
model.viewModelScope.launch {
|
||||
model.createMoment()
|
||||
model.createMoment(context = context)
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
@@ -93,7 +95,7 @@ fun NewPostScreen() {
|
||||
modifier = Modifier.clip(RoundedCornerShape(8.dp)).background(color = Color(0xFFEEEEEE)).padding(24.dp)
|
||||
) {
|
||||
RelPostCard(
|
||||
momentItem = it,
|
||||
momentEntity = it,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
@@ -177,7 +179,9 @@ fun AddImageGrid() {
|
||||
val uri = result.data?.data
|
||||
if (uri != null) {
|
||||
model.imageUriList += uri.toString()
|
||||
// get filename and extension
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val stroke = Stroke(
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.aiosman.riderpro.ui.post
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
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.model.MomentItem
|
||||
import com.aiosman.riderpro.data.UploadImage
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.ui.modification.Modification
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
object NewPostViewModel : ViewModel() {
|
||||
@@ -20,7 +26,7 @@ object NewPostViewModel : ViewModel() {
|
||||
var modificationList by mutableStateOf<List<Modification>>(listOf())
|
||||
var imageUriList by mutableStateOf(listOf<String>())
|
||||
var relPostId by mutableStateOf<Int?>(null)
|
||||
var relMoment by mutableStateOf<MomentItem?>(null)
|
||||
var relMoment by mutableStateOf<MomentEntity?>(null)
|
||||
fun asNewPost() {
|
||||
textContent = ""
|
||||
searchPlaceAddressResult = null
|
||||
@@ -29,16 +35,39 @@ object NewPostViewModel : ViewModel() {
|
||||
relPostId = null
|
||||
}
|
||||
|
||||
suspend fun createMoment() {
|
||||
momentService.createMoment(
|
||||
content = textContent,
|
||||
authorId = 1,
|
||||
imageUriList = imageUriList,
|
||||
relPostId = relPostId
|
||||
)
|
||||
suspend fun uriToFile(context: Context, uri: Uri): File {
|
||||
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||
val tempFile = withContext(Dispatchers.IO) {
|
||||
File.createTempFile("temp", null, context.cacheDir)
|
||||
}
|
||||
inputStream?.use { input ->
|
||||
FileOutputStream(tempFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
return tempFile
|
||||
}
|
||||
|
||||
suspend fun init(){
|
||||
suspend fun createMoment(context: Context) {
|
||||
val uploadImageList = emptyList<UploadImage>().toMutableList()
|
||||
for (uri in imageUriList) {
|
||||
val cursor = context.contentResolver.query(Uri.parse(uri), null, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val displayName = it.getString(it.getColumnIndex("_display_name"))
|
||||
val extension = displayName.substringAfterLast(".")
|
||||
Log.d("NewPost", "File name: $displayName, extension: $extension")
|
||||
// read as file
|
||||
val file = uriToFile(context, Uri.parse(uri))
|
||||
Log.d("NewPost", "File size: ${file.length()}")
|
||||
uploadImageList += UploadImage(file, displayName, uri, extension)
|
||||
}
|
||||
}
|
||||
}
|
||||
momentService.createMoment(textContent, 1, uploadImageList, relPostId)
|
||||
}
|
||||
|
||||
suspend fun init() {
|
||||
relPostId?.let {
|
||||
val moment = momentService.getMomentById(it)
|
||||
relMoment = moment
|
||||
|
||||
@@ -74,9 +74,9 @@ import com.aiosman.riderpro.LocalAnimatedContentScope
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.AccountProfile
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.data.AccountService
|
||||
import com.aiosman.riderpro.data.Comment
|
||||
import com.aiosman.riderpro.data.CommentEntity
|
||||
import com.aiosman.riderpro.data.CommentPagingSource
|
||||
import com.aiosman.riderpro.data.CommentRemoteDataSource
|
||||
import com.aiosman.riderpro.data.CommentService
|
||||
@@ -84,7 +84,7 @@ import com.aiosman.riderpro.data.TestCommentServiceImpl
|
||||
import com.aiosman.riderpro.data.MomentService
|
||||
import com.aiosman.riderpro.data.TestAccountServiceImpl
|
||||
import com.aiosman.riderpro.data.TestMomentServiceImpl
|
||||
import com.aiosman.riderpro.model.MomentItem
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
|
||||
@@ -103,7 +103,7 @@ class PostViewModel(
|
||||
) : ViewModel() {
|
||||
var service: MomentService = TestMomentServiceImpl()
|
||||
var commentService: CommentService = TestCommentServiceImpl()
|
||||
private var _commentsFlow = MutableStateFlow<PagingData<Comment>>(PagingData.empty())
|
||||
private var _commentsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
|
||||
val commentsFlow = _commentsFlow.asStateFlow()
|
||||
|
||||
init {
|
||||
@@ -120,17 +120,16 @@ class PostViewModel(
|
||||
_commentsFlow.value = it
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var accountProfile by mutableStateOf<AccountProfile?>(null)
|
||||
var moment by mutableStateOf<MomentItem?>(null)
|
||||
var accountProfileEntity by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var moment by mutableStateOf<MomentEntity?>(null)
|
||||
var accountService: AccountService = TestAccountServiceImpl()
|
||||
|
||||
suspend fun initData() {
|
||||
moment = service.getMomentById(postId.toInt())
|
||||
moment?.let {
|
||||
accountProfile = accountService.getAccountProfileById(it.authorId)
|
||||
accountProfileEntity = accountService.getAccountProfileById(it.authorId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +146,21 @@ class PostViewModel(
|
||||
_commentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun unlikeComment(commentId: Int) {
|
||||
commentService.dislikeComment(commentId)
|
||||
val currentPagingData = commentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { comment ->
|
||||
if (comment.id == commentId) {
|
||||
comment.copy(liked = !comment.liked)
|
||||
} else {
|
||||
comment
|
||||
}
|
||||
}
|
||||
_commentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun createComment(content: String) {
|
||||
commentService.createComment(postId.toInt(), content, 1)
|
||||
commentService.createComment(postId.toInt(), content)
|
||||
MomentViewModel.updateCommentCount(postId.toInt())
|
||||
}
|
||||
|
||||
@@ -212,7 +224,7 @@ fun PostScreen(
|
||||
commentsPagging.refresh()
|
||||
}
|
||||
},
|
||||
momentItem = viewModel.moment
|
||||
momentEntity = viewModel.moment
|
||||
)
|
||||
}
|
||||
) {
|
||||
@@ -221,7 +233,11 @@ fun PostScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Header(viewModel.accountProfile)
|
||||
Header(
|
||||
avatar = viewModel.moment?.avatar,
|
||||
nickname = viewModel.moment?.nickname,
|
||||
userId = viewModel.moment?.authorId
|
||||
)
|
||||
Column(modifier = Modifier.animateContentSize()) {
|
||||
AnimatedVisibility(visible = showCollapseContent) {
|
||||
// collapse content
|
||||
@@ -256,9 +272,13 @@ fun PostScreen(
|
||||
CommentsSection(
|
||||
lazyPagingItems = commentsPagging,
|
||||
scrollState,
|
||||
onLike = { comment: Comment ->
|
||||
onLike = { commentEntity: CommentEntity ->
|
||||
scope.launch {
|
||||
viewModel.likeComment(comment.id)
|
||||
if (commentEntity.liked) {
|
||||
viewModel.unlikeComment(commentEntity.id)
|
||||
} else {
|
||||
viewModel.likeComment(commentEntity.id)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
showCollapseContent = it
|
||||
@@ -270,7 +290,7 @@ fun PostScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Header(accountProfile: AccountProfile?) {
|
||||
fun Header(avatar: String?, nickname: String?, userId: Int?) {
|
||||
val navController = LocalNavController.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -288,30 +308,30 @@ fun Header(accountProfile: AccountProfile?) {
|
||||
.size(32.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
accountProfile?.let {
|
||||
avatar?.let {
|
||||
AsyncImage(
|
||||
accountProfile.avatar,
|
||||
it,
|
||||
contentDescription = "Profile Picture",
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.noRippleClickable {
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route.replace(
|
||||
"{id}",
|
||||
accountProfile.id.toString()
|
||||
userId?.let {
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route.replace(
|
||||
"{id}",
|
||||
userId.toString()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
accountProfile?.let {
|
||||
Text(text = accountProfile.nickName, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = nickname ?: "", fontWeight = FontWeight.Bold)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(20.dp)
|
||||
@@ -350,7 +370,7 @@ fun PostImageView(
|
||||
state = pagerState,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth().background(Color.Black),
|
||||
.fillMaxWidth(),
|
||||
) { page ->
|
||||
val image = images[page]
|
||||
with(sharedTransitionScope) {
|
||||
@@ -407,7 +427,7 @@ fun PostImageView(
|
||||
@Composable
|
||||
fun PostDetails(
|
||||
postId: String,
|
||||
momentItem: MomentItem?
|
||||
momentEntity: MomentEntity?
|
||||
) {
|
||||
|
||||
Column(
|
||||
@@ -418,22 +438,22 @@ fun PostDetails(
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = momentItem?.momentTextContent ?: "",
|
||||
text = momentEntity?.momentTextContent ?: "",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(text = "12-11 发布")
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(text = "${momentItem?.commentCount ?: 0} Comments")
|
||||
Text(text = "${momentEntity?.commentCount ?: 0} Comments")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CommentsSection(
|
||||
lazyPagingItems: LazyPagingItems<Comment>,
|
||||
lazyPagingItems: LazyPagingItems<CommentEntity>,
|
||||
scrollState: LazyListState = rememberLazyListState(),
|
||||
onLike: (Comment) -> Unit,
|
||||
onLike: (CommentEntity) -> Unit,
|
||||
onWillCollapse: (Boolean) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
@@ -463,11 +483,11 @@ fun CommentsSection(
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommentItem(comment: Comment, onLike: () -> Unit = {}) {
|
||||
fun CommentItem(commentEntity: CommentEntity, onLike: () -> Unit = {}) {
|
||||
Column {
|
||||
Row(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||
AsyncImage(
|
||||
comment.avatar,
|
||||
commentEntity.avatar,
|
||||
contentDescription = "Comment Profile Picture",
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
@@ -476,9 +496,9 @@ fun CommentItem(comment: Comment, onLike: () -> Unit = {}) {
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(text = comment.name, fontWeight = FontWeight.Bold)
|
||||
Text(text = comment.comment)
|
||||
Text(text = comment.date, fontSize = 12.sp, color = Color.Gray)
|
||||
Text(text = commentEntity.name, fontWeight = FontWeight.Bold)
|
||||
Text(text = commentEntity.comment)
|
||||
Text(text = commentEntity.date, fontSize = 12.sp, color = Color.Gray)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
@@ -488,17 +508,17 @@ fun CommentItem(comment: Comment, onLike: () -> Unit = {}) {
|
||||
Icon(
|
||||
Icons.Filled.Favorite,
|
||||
contentDescription = "Like",
|
||||
tint = if (comment.liked) Color.Red else Color.Gray
|
||||
tint = if (commentEntity.liked) Color.Red else Color.Gray
|
||||
)
|
||||
}
|
||||
Text(text = comment.likes.toString())
|
||||
Text(text = commentEntity.likes.toString())
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
) {
|
||||
comment.replies.forEach { reply ->
|
||||
commentEntity.replies.forEach { reply ->
|
||||
CommentItem(reply)
|
||||
}
|
||||
}
|
||||
@@ -510,7 +530,7 @@ fun CommentItem(comment: Comment, onLike: () -> Unit = {}) {
|
||||
fun BottomNavigationBar(
|
||||
onCreateComment: (String) -> Unit = {},
|
||||
onLikeClick: () -> Unit = {},
|
||||
momentItem: MomentItem?
|
||||
momentEntity: MomentEntity?
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
var showCommentModal by remember { mutableStateOf(false) }
|
||||
@@ -568,10 +588,10 @@ fun BottomNavigationBar(
|
||||
Icon(
|
||||
Icons.Filled.Favorite,
|
||||
contentDescription = "like",
|
||||
tint = if (momentItem?.liked == true) Color.Red else Color.Gray
|
||||
tint = if (momentEntity?.liked == true) Color.Red else Color.Gray
|
||||
)
|
||||
}
|
||||
Text(text = momentItem?.likeCount.toString())
|
||||
Text(text = momentEntity?.likeCount.toString())
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ }) {
|
||||
Icon(Icons.Filled.Star, contentDescription = "Send")
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -17,13 +18,13 @@ import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.aiosman.riderpro.data.AccountProfile
|
||||
import com.aiosman.riderpro.data.AccountProfileEntity
|
||||
import com.aiosman.riderpro.data.MomentPagingSource
|
||||
import com.aiosman.riderpro.data.MomentRemoteDataSource
|
||||
import com.aiosman.riderpro.data.TestMomentServiceImpl
|
||||
import com.aiosman.riderpro.data.TestUserServiceImpl
|
||||
import com.aiosman.riderpro.data.UserService
|
||||
import com.aiosman.riderpro.model.MomentItem
|
||||
import com.aiosman.riderpro.model.MomentEntity
|
||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||
import com.aiosman.riderpro.ui.index.tabs.profile.CarGroup
|
||||
import com.aiosman.riderpro.ui.index.tabs.profile.MomentPostUnit
|
||||
@@ -31,15 +32,15 @@ import com.aiosman.riderpro.ui.index.tabs.profile.RidingStyle
|
||||
import com.aiosman.riderpro.ui.index.tabs.profile.UserInformation
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun AccountProfile(id:String) {
|
||||
// val model = MyProfileViewModel
|
||||
val userService: UserService = TestUserServiceImpl()
|
||||
var userProfile by remember { mutableStateOf<AccountProfile?>(null) }
|
||||
var userProfile by remember { mutableStateOf<AccountProfileEntity?>(null) }
|
||||
val momentService = TestMomentServiceImpl()
|
||||
var momentsFlow by remember { mutableStateOf<Flow<PagingData<MomentItem>>?>(null) }
|
||||
|
||||
var momentsFlow by remember { mutableStateOf<Flow<PagingData<MomentEntity>>?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
LaunchedEffect(Unit) {
|
||||
userProfile = userService.getUserProfile(id)
|
||||
momentsFlow = Pager(
|
||||
@@ -73,7 +74,21 @@ fun AccountProfile(id:String) {
|
||||
item {
|
||||
CarGroup()
|
||||
userProfile?.let {
|
||||
UserInformation(isSelf = false, accountProfile = it)
|
||||
UserInformation(
|
||||
isSelf = false,
|
||||
accountProfileEntity = it,
|
||||
onFollowClick = {
|
||||
scope.launch {
|
||||
if (it.isFollowing) {
|
||||
userService.unFollowUser(id)
|
||||
userProfile = userProfile?.copy(isFollowing = false)
|
||||
} else {
|
||||
userService.followUser(id)
|
||||
userProfile = userProfile?.copy(isFollowing = true)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
RidingStyle()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user