更新消息功能

This commit is contained in:
2024-08-20 19:48:12 +08:00
parent 6137e1c3b5
commit 5228fde035
18 changed files with 1077 additions and 259 deletions

View File

@@ -25,6 +25,7 @@ import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.ui.account.AccountEditScreen
import com.aiosman.riderpro.ui.comment.CommentsScreen
import com.aiosman.riderpro.ui.favourite.FavouriteScreen
import com.aiosman.riderpro.ui.follower.FollowerScreen
import com.aiosman.riderpro.ui.gallery.OfficialGalleryScreen
import com.aiosman.riderpro.ui.gallery.OfficialPhotographerScreen
@@ -66,6 +67,7 @@ sealed class NavigationRoute(
data object AccountEdit : NavigationRoute("AccountEditScreen")
data object ImageViewer : NavigationRoute("ImageViewer")
data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen")
data object FavouritesScreen : NavigationRoute("FavouritesScreen")
}
@@ -189,6 +191,9 @@ fun NavigationController(
composable(route = NavigationRoute.ChangePasswordScreen.route) {
ChangePasswordScreen()
}
composable(route = NavigationRoute.FavouritesScreen.route) {
FavouriteScreen()
}
}

View File

@@ -0,0 +1,53 @@
package com.aiosman.riderpro.ui.favourite
import android.icu.util.Calendar
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 com.aiosman.riderpro.data.AccountFavourite
import com.aiosman.riderpro.data.AccountFavouriteEntity
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FavoriteItemPagingSource
import com.aiosman.riderpro.data.LikeItemPagingSource
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import com.aiosman.riderpro.ui.like.LikePageViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object FavouritePageViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl()
private val _favouriteItemsFlow =
MutableStateFlow<PagingData<AccountFavouriteEntity>>(PagingData.empty())
val favouriteItemsFlow = _favouriteItemsFlow.asStateFlow()
init {
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
FavoriteItemPagingSource(
accountService
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_favouriteItemsFlow.value = it
}
}
}
suspend fun updateNotice() {
var now = Calendar.getInstance().time
accountService.updateNotice(
UpdateNoticeRequestBody(
lastLookFavouriteTime = ApiClient.formatTime(now)
)
)
}
}

View File

@@ -0,0 +1,93 @@
package com.aiosman.riderpro.ui.favourite
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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.aiosman.riderpro.ui.like.ActionNoticeItem
import com.aiosman.riderpro.ui.like.LikePageViewModel
@Preview
@Composable
fun FavouriteScreen() {
val model = FavouritePageViewModel
val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState()
var dataFlow = model.favouriteItemsFlow
var favourites = dataFlow.collectAsLazyPagingItems()
LaunchedEffect(Unit) {
model.updateNotice()
}
StatusBarMaskLayout(
darkIcons = true,
maskBoxBackgroundColor = Color(0xFFFFFFFF)
) {
Column(
modifier = Modifier
.weight(1f)
.background(color = Color(0xFFFFFFFF))
.padding(horizontal = 16.dp)
) {
NoticeScreenHeader(
"FAVOURITE",
moreIcon = false
)
Spacer(modifier = Modifier.height(28.dp))
LazyColumn(
modifier = Modifier.weight(1f),
state = listState,
) {
items(favourites.itemCount) {
val favouriteItem = favourites[it]
if (favouriteItem != null) {
ActionNoticeItem(
avatar = favouriteItem.user.avatar,
nickName = favouriteItem.user.nickName,
likeTime = favouriteItem.favoriteTime,
thumbnail = favouriteItem.post.images[0].thumbnail,
action = "favourite",
userId = favouriteItem.user.id,
postId = favouriteItem.post.id,
)
}
}
item {
BottomNavigationPlaceholder()
}
}
}
}
}

View File

@@ -13,32 +13,53 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
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.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountFollow
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch
@Preview
@Composable
fun FollowerScreen() {
val scope = rememberCoroutineScope()
StatusBarMaskLayout(
modifier = Modifier.padding(horizontal = 16.dp)
) {
val model = FollowerViewModel
var dataFlow = model.followerItemsFlow
var followers = dataFlow.collectAsLazyPagingItems()
NoticeScreenHeader("FOLLOWERS")
Spacer(modifier = Modifier.height(28.dp))
LaunchedEffect(Unit) {
model.updateNotice()
}
LazyColumn(
modifier = Modifier.weight(1f)
) {
item {
repeat(20) {
FollowerItem()
items(followers.itemCount) { index ->
followers[index]?.let { follower ->
FollowerItem(follower) {
scope.launch {
model.followUser(follower.userId)
}
}
}
}
}
@@ -47,7 +68,12 @@ fun FollowerScreen() {
@Composable
fun FollowerItem() {
fun FollowerItem(
item: AccountFollow,
onFollow: () -> Unit = {}
) {
val context = LocalContext.current
val navController = LocalNavController.current
Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
) {
@@ -55,37 +81,52 @@ fun FollowerItem() {
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Follower",
modifier = Modifier.size(40.dp)
CustomAsyncImage(
context = context,
imageUrl = item.avatar,
contentDescription = item.nickname,
modifier = Modifier
.size(40.dp)
.noRippleClickable {
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
item.userId.toString()
)
)
}
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text("Username", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.height(5.dp))
Text("Username", fontSize = 12.sp, color = Color(0x99000000))
Text(item.nickname, fontWeight = FontWeight.Bold, fontSize = 16.sp)
}
Box {
Image(
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = "Like",
modifier = Modifier
.width(79.dp)
.height(24.dp)
)
Text(
"FOLLOW",
fontSize = 14.sp,
color = Color(0xFFFFFFFF),
modifier = Modifier.align(
Alignment.Center
if (!item.isFollowing) {
Box(
modifier = Modifier.noRippleClickable {
onFollow()
}
) {
Image(
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = "Follow",
modifier = Modifier
.width(79.dp)
.height(24.dp)
)
)
Text(
"FOLLOW",
fontSize = 14.sp,
color = Color(0xFFFFFFFF),
modifier = Modifier.align(
Alignment.Center
)
)
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
package com.aiosman.riderpro.ui.follower
import android.icu.util.Calendar
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.data.AccountFollow
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FollowItemPagingSource
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object FollowerViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl()
private val userService: UserService = TestUserServiceImpl()
private val _followerItemsFlow =
MutableStateFlow<PagingData<AccountFollow>>(PagingData.empty())
val followerItemsFlow = _followerItemsFlow.asStateFlow()
init {
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
FollowItemPagingSource(
accountService
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_followerItemsFlow.value = it
}
}
}
private fun updateIsFollow(id: Int) {
val currentPagingData = _followerItemsFlow.value
val updatedPagingData = currentPagingData.map { follow ->
if (follow.userId == id) {
follow.copy(isFollowing = true)
} else {
follow
}
}
_followerItemsFlow.value = updatedPagingData
}
suspend fun followUser(userId: Int) {
userService.followUser(userId.toString())
updateIsFollow(userId)
}
suspend fun updateNotice() {
var now = Calendar.getInstance().time
accountService.updateNotice(
UpdateNoticeRequestBody(
lastLookFollowTime = ApiClient.formatTime(now)
)
)
}
}

View File

@@ -1,7 +1,7 @@
package com.aiosman.riderpro.ui.like
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -12,47 +12,40 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
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.res.painterResource
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.R
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import java.util.Date
@Preview
@Composable
fun LikeScreen() {
val model = LikePageViewModel
val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState()
// observe list scrolling
val reachedBottom: Boolean by remember {
derivedStateOf {
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 3
}
}
var dataFlow = model.likeItemsFlow
var likes = dataFlow.collectAsLazyPagingItems()
LaunchedEffect(Unit) {
LikePageViewModel.loader.loadData()
}
LaunchedEffect(reachedBottom) {
if (reachedBottom) LikePageViewModel.loader.loadMore()
model.updateNotice()
}
StatusBarMaskLayout(
darkIcons = true,
@@ -64,69 +57,97 @@ fun LikeScreen() {
.background(color = Color(0xFFFFFFFF))
.padding(horizontal = 16.dp)
) {
NoticeScreenHeader("LIKES")
NoticeScreenHeader(
"LIKES",
moreIcon = false
)
Spacer(modifier = Modifier.height(28.dp))
LazyColumn(
modifier = Modifier.weight(1f),
state = listState,
) {
items(LikePageViewModel.loader.list, key = { it.id }) {
LikeItem(it)
) {
items(likes.itemCount) {
val likeItem = likes[it]
if (likeItem != null) {
ActionNoticeItem(
avatar = likeItem.user.avatar,
nickName = likeItem.user.nickName,
likeTime = likeItem.likeTime,
thumbnail = likeItem.post.images[0].thumbnail,
action = "like",
userId = likeItem.user.id,
postId = likeItem.post.id
)
}
}
item {
BottomNavigationPlaceholder()
}
}
}
}
}
@Composable
fun LikeItem(itemData: LikeItemData) {
fun ActionNoticeItem(
avatar: String,
nickName: String,
likeTime: Date,
thumbnail: String,
action: String,
userId: Int,
postId: Int
) {
val context = LocalContext.current
val navController = LocalNavController.current
Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.Top,
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Like",
modifier = Modifier.size(40.dp)
CustomAsyncImage(
context,
imageUrl = avatar,
modifier = Modifier
.size(40.dp)
.noRippleClickable {
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
userId.toString()
)
)
},
contentDescription = action,
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier.weight(1f)
modifier = Modifier
.weight(1f)
.noRippleClickable {
navController.navigate(
NavigationRoute.Post.route.replace(
"{id}",
postId.toString()
)
)
}
) {
Text(itemData.name, fontWeight = FontWeight.Bold, fontSize = 16.sp)
Text(nickName, fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.height(5.dp))
Text("Username", fontSize = 12.sp, color = Color(0x99000000))
Row {
Text(likeTime.timeAgo(), fontSize = 12.sp, color = Color(0x99000000))
}
}
Box {
Image(
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = "Like",
modifier = Modifier
.width(79.dp)
.height(24.dp)
)
Text(
"FOLLOW",
fontSize = 14.sp,
color = Color(0xFFFFFFFF),
modifier = Modifier.align(
Alignment.Center
)
)
}
CustomAsyncImage(
context,
imageUrl = thumbnail,
modifier = Modifier.size(64.dp),
contentDescription = action,
)
}
}
}

View File

@@ -1,68 +1,51 @@
package com.aiosman.riderpro.ui.like
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import android.icu.util.Calendar
import androidx.lifecycle.ViewModel
import com.aiosman.riderpro.test.MockDataContainer
import com.aiosman.riderpro.test.MockDataSource
import com.aiosman.riderpro.test.MockListContainer
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountLikeEntity
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.LikeItemPagingSource
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class LikeDataSource : MockDataSource<LikeItemData>() {
init {
var newList = mutableListOf<LikeItemData>()
for (i in 0..999) {
newList.add(LikeItemData(i, "Like $i"))
}
list = newList
}
}
abstract class DataLoader<T> {
var list = mutableListOf<T>()
var page by mutableStateOf(1)
var pageSize by mutableStateOf(20)
var total by mutableStateOf(0)
var noMoreData by mutableStateOf(false)
suspend fun loadData() {
val resp = fetchData(page, pageSize)
if (resp.success) {
resp.data?.let {
total = it.total
list = it.list.toMutableList()
if (it.list.size < pageSize) {
noMoreData = true
}
}
}
}
suspend fun loadMore(){
if (list.size >= total) return
page++
val resp = fetchData(page, pageSize)
if (resp.success) {
resp.data?.let {
total = it.total
list.addAll(it.list)
if (it.list.size < pageSize) {
noMoreData = true
}
}
}
}
abstract suspend fun fetchData(page: Int, pageSize: Int): MockDataContainer<MockListContainer<T>>
}
class LikeListLoader : DataLoader<LikeItemData>() {
var mockDataSource = LikeDataSource()
override suspend fun fetchData(page: Int, pageSize: Int): MockDataContainer<MockListContainer<LikeItemData>> {
return mockDataSource.fetchData(page, pageSize)
}
}
object LikePageViewModel : ViewModel() {
val loader = LikeListLoader()
}
private val accountService: AccountService = TestAccountServiceImpl()
private val _likeItemsFlow = MutableStateFlow<PagingData<AccountLikeEntity>>(PagingData.empty())
val likeItemsFlow = _likeItemsFlow.asStateFlow()
data class LikeItemData(
val id: Int,
val name: String,
)
init {
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
LikeItemPagingSource(
accountService
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_likeItemsFlow.value = it
}
}
}
suspend fun updateNotice() {
var now = Calendar.getInstance().time
accountService.updateNotice(
UpdateNoticeRequestBody(
lastLookLikeTime = ApiClient.formatTime(now)
)
)
}
}

View File

@@ -2,7 +2,6 @@ package com.aiosman.riderpro.ui.message
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -24,29 +23,43 @@ 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.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.Comment
import com.aiosman.riderpro.data.CommentEntity
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Preview(showBackground = true)
@Composable
fun NotificationsScreen() {
val model = MessageListViewModel
val navController = LocalNavController.current
val systemUiController = rememberSystemUiController()
var dataFlow = model.commentItemsFlow
var comments = dataFlow.collectAsLazyPagingItems()
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
model.initData()
}
StatusBarMaskLayout(darkIcons = true) {
Column(
modifier = Modifier.fillMaxWidth().weight(1f)
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
Box(
modifier = Modifier
@@ -65,42 +78,47 @@ fun NotificationsScreen() {
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
NotificationIndicator(10, R.drawable.rider_pro_like, "LIKE") {
navController.navigate("Likes")
NotificationIndicator(model.likeNoticeCount, R.drawable.rider_pro_like, "LIKE") {
navController.navigate(NavigationRoute.Likes.route)
}
NotificationIndicator(10, R.drawable.rider_pro_followers, "FOLLOWERS"){
navController.navigate("Followers")
NotificationIndicator(
model.followNoticeCount,
R.drawable.rider_pro_followers,
"FOLLOWERS"
) {
navController.navigate(NavigationRoute.Followers.route)
}
NotificationIndicator(10, R.drawable.rider_pro_comments, "COMMENTS"){
navController.navigate("Comments")
NotificationIndicator(
model.favouriteNoticeCount,
R.drawable.rider_pro_favoriate,
"Favourites"
) {
navController.navigate(NavigationRoute.FavouritesScreen.route)
}
}
HorizontalDivider(color = Color(0xFFEbEbEb), modifier = Modifier.padding(16.dp))
NotificationCounterItem(24)
NotificationCounterItem(model.commentNoticeCount)
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxSize()
) {
item {
repeat(20) {
MessageItem(
MessageItemData(
userName = "Onyama Limba",
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
timeAgo = "3 days ago",
profileImage = R.drawable.default_avatar
items(comments.itemCount) { index ->
comments[index]?.let { comment ->
CommentItem(comment) {
model.updateReadStatus(comment.id)
navController.navigate(
NavigationRoute.Post.route.replace(
"{id}",
comment.postId.toString()
)
)
)
}
}
BottomNavigationPlaceholder()
}
item {
BottomNavigationPlaceholder()
}
}
}
}
@@ -121,7 +139,7 @@ fun NotificationIndicator(
modifier = Modifier
.padding(16.dp)
.align(Alignment.TopCenter)
.clickable {
.noRippleClickable {
onClick()
}
) {
@@ -204,68 +222,101 @@ fun NotificationCounterItem(count: Int) {
}
}
data class MessageItemData(
val userName: String,
val message: String,
val timeAgo: String,
val profileImage: Int
)
@Composable
fun MessageItem(messageItemData: MessageItemData) {
fun CommentItem(
commentItem: CommentEntity,
onPostClick: () -> Unit = {},
) {
val navController = LocalNavController.current
val context = LocalContext.current
Row(
modifier = Modifier.padding(16.dp)
) {
Box() {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "",
Box {
CustomAsyncImage(
context = context,
imageUrl = commentItem.avatar,
contentDescription = commentItem.name,
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(4.dp))
.noRippleClickable {
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
commentItem.author.toString()
)
)
}
)
}
Column(
modifier = Modifier.weight(1f)
Row(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp)
.noRippleClickable {
onPostClick()
}
) {
Text(
text = "Onyama Limba",
fontSize = 16.sp,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
fontSize = 14.sp,
modifier = Modifier.padding(start = 16.dp),
maxLines = 1,
color = Color(0x99000000)
)
}
// Spacer(modifier = Modifier.weight(1f))
Column(
horizontalAlignment = Alignment.End
) {
Text(
text = "3 days ago",
fontSize = 14.sp,
color = Color(0x66000000)
)
Spacer(modifier = Modifier.height(6.dp))
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFE53935))
.padding(4.dp)
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = "24",
color = Color.White,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Center)
text = commentItem.name,
fontSize = 16.sp,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = commentItem.comment,
fontSize = 14.sp,
modifier = Modifier.padding(start = 16.dp),
maxLines = 1,
color = Color(0x99000000)
)
}
// Spacer(modifier = Modifier.weight(1f))
Column(
horizontalAlignment = Alignment.End
) {
Row {
if (commentItem.unread) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFE53935))
.padding(4.dp)
) {
Text(
text = "new",
color = Color.White,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Center)
)
}
Spacer(modifier = Modifier.width(8.dp))
}
Text(
text = commentItem.date.timeAgo(),
fontSize = 14.sp,
color = Color(0x66000000)
)
}
Spacer(modifier = Modifier.height(4.dp))
commentItem.post?.let {
CustomAsyncImage(
context = context,
imageUrl = it.images[0].thumbnail,
contentDescription = "Post Image",
modifier = Modifier
.size(32.dp)
.clip(RoundedCornerShape(4.dp)),
)
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
package com.aiosman.riderpro.ui.message
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.data.AccountNotice
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.CommentEntity
import com.aiosman.riderpro.data.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestCommentServiceImpl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object MessageListViewModel : ViewModel() {
val accountService: AccountService = TestAccountServiceImpl()
var noticeInfo by mutableStateOf<AccountNotice?>(null)
private val commentService: CommentService = TestCommentServiceImpl()
private val _commentItemsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
val commentItemsFlow = _commentItemsFlow.asStateFlow()
suspend fun initData() {
val info = accountService.getMyNoticeInfo()
noticeInfo = info
}
val likeNoticeCount
get() = noticeInfo?.likeCount ?: 0
val followNoticeCount
get() = noticeInfo?.followCount ?: 0
val favouriteNoticeCount
get() = noticeInfo?.favoriteCount ?: 0
val commentNoticeCount
get() = noticeInfo?.commentCount ?: 0
init {
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
CommentPagingSource(
CommentRemoteDataSource(commentService),
selfNotice = true
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_commentItemsFlow.value = it
}
}
}
private fun updateIsRead(id: Int) {
val currentPagingData = _commentItemsFlow.value
val updatedPagingData = currentPagingData.map { commentEntity ->
if (commentEntity.id == id) {
commentEntity.copy(unread = false)
} else {
commentEntity
}
}
_commentItemsFlow.value = updatedPagingData
}
fun updateReadStatus(id: Int) {
viewModelScope.launch {
commentService.updateReadStatus(id)
updateIsRead(id)
}
}
}

View File

@@ -87,6 +87,7 @@ import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.model.MomentImageEntity
import com.aiosman.riderpro.ui.NavigationRoute
@@ -571,7 +572,7 @@ fun CommentItem(commentEntity: CommentEntity, onLike: () -> Unit = {}) {
Column {
Text(text = commentEntity.name, fontWeight = FontWeight.Bold)
Text(text = commentEntity.comment)
Text(text = commentEntity.date, fontSize = 12.sp, color = Color.Gray)
Text(text = commentEntity.date.timeAgo(), fontSize = 12.sp, color = Color.Gray)
}
Spacer(modifier = Modifier.weight(1f))
Column(horizontalAlignment = Alignment.CenterHorizontally) {