From 2497698f279362c95e7a229b6bf1baf556e99532 Mon Sep 17 00:00:00 2001 From: AllenTom Date: Fri, 27 Sep 2024 21:29:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/aiosman/riderpro/ui/Navi.kt | 9 + .../notice/CommentNoticeListViewModel.kt | 82 ++ .../com/aiosman/riderpro/ui/index/Index.kt | 12 +- .../ui/index/tabs/message/MessageList.kt | 294 ++---- .../tabs/message/MessageListViewModel.kt | 126 ++- .../tabs/profile/v2/MyProfileViewModel2.kt | 116 +++ .../ui/index/tabs/profile/v2/Profile2.kt | 542 +++++++++++ .../ui/index/tabs/profile/v2/Profile3.kt | 134 +++ .../ui/index/tabs/profile/v2/Profile4.kt | 134 +++ .../ui/index/tabs/profile/v2/Profile5.kt | 879 ++++++++++++++++++ .../com/aiosman/riderpro/utils/TrtcHelper.kt | 22 + 11 files changed, 2122 insertions(+), 228 deletions(-) create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/comment/notice/CommentNoticeListViewModel.kt create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/MyProfileViewModel2.kt create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile2.kt create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile3.kt create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile4.kt create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile5.kt create mode 100644 app/src/main/java/com/aiosman/riderpro/utils/TrtcHelper.kt diff --git a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt index 4db61a4..71ef5bc 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -31,6 +31,7 @@ import com.aiosman.riderpro.ui.account.AccountEditScreen2 import com.aiosman.riderpro.ui.account.ResetPasswordScreen import com.aiosman.riderpro.ui.chat.ChatScreen import com.aiosman.riderpro.ui.comment.CommentsScreen +import com.aiosman.riderpro.ui.comment.notice.CommentNoticeScreen import com.aiosman.riderpro.ui.favourite.FavouriteListPage import com.aiosman.riderpro.ui.favourite.FavouriteNoticeScreen import com.aiosman.riderpro.ui.follower.FollowerListScreen @@ -86,6 +87,7 @@ sealed class NavigationRoute( data object ResetPassword : NavigationRoute("ResetPassword") data object FavouriteList : NavigationRoute("FavouriteList") data object Chat : NavigationRoute("Chat/{id}") + data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen") } @@ -352,6 +354,13 @@ fun NavigationController( ChatScreen(it.arguments?.getString("id")!!) } } + composable(route = NavigationRoute.CommentNoticeScreen.route) { + CompositionLocalProvider( + LocalAnimatedContentScope provides this, + ) { + CommentNoticeScreen() + } + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/comment/notice/CommentNoticeListViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/comment/notice/CommentNoticeListViewModel.kt new file mode 100644 index 0000000..7aa44f8 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/comment/notice/CommentNoticeListViewModel.kt @@ -0,0 +1,82 @@ +package com.aiosman.riderpro.ui.comment.notice + +import android.content.Context +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.AccountService +import com.aiosman.riderpro.data.AccountServiceImpl +import com.aiosman.riderpro.data.CommentRemoteDataSource +import com.aiosman.riderpro.data.CommentService +import com.aiosman.riderpro.data.CommentServiceImpl +import com.aiosman.riderpro.data.UserService +import com.aiosman.riderpro.data.UserServiceImpl +import com.aiosman.riderpro.entity.CommentEntity +import com.aiosman.riderpro.entity.CommentPagingSource + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class CommentNoticeListViewModel : ViewModel() { + val accountService: AccountService = AccountServiceImpl() + val userService: UserService = UserServiceImpl() + private val commentService: CommentService = CommentServiceImpl() + private val _commentItemsFlow = MutableStateFlow>(PagingData.empty()) + val commentItemsFlow = _commentItemsFlow.asStateFlow() + var isLoading by mutableStateOf(false) + var isFirstLoad = true + fun initData(context: Context, force: Boolean = false) { + if (!isFirstLoad && !force) { + return + } + if (force) { + isLoading = true + } + isFirstLoad = false + viewModelScope.launch { + Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + CommentPagingSource( + CommentRemoteDataSource(commentService), + selfNotice = true, + order = "latest" + ) + } + ).flow.cachedIn(viewModelScope).collectLatest { + _commentItemsFlow.value = it + } + } + + isLoading = false + + } + + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt index b2f0274..0dd154c 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt @@ -2,12 +2,9 @@ package com.aiosman.riderpro.ui.index import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween -import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize @@ -31,18 +28,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp -import androidx.navigation.navOptions import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.index.tabs.add.AddPage import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen import com.aiosman.riderpro.ui.index.tabs.moment.MomentsList -import com.aiosman.riderpro.ui.index.tabs.profile.ProfilePage +import com.aiosman.riderpro.ui.index.tabs.profile.v2.Profile2 +import com.aiosman.riderpro.ui.index.tabs.profile.v2.Profile4 +import com.aiosman.riderpro.ui.index.tabs.profile.v2.ProfilePage import com.aiosman.riderpro.ui.index.tabs.search.DiscoverScreen -import com.aiosman.riderpro.ui.index.tabs.search.SearchScreen import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo import com.aiosman.riderpro.ui.index.tabs.street.StreetPage -import com.aiosman.riderpro.ui.message.MessagePage import com.aiosman.riderpro.ui.post.NewPostViewModel import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.launch @@ -213,7 +209,7 @@ fun Profile() { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - ProfilePage() + Profile2() } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt index 226e867..1ceaaff 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt @@ -50,6 +50,7 @@ import com.aiosman.riderpro.ui.favourite.FavouriteNoticeViewModel import com.aiosman.riderpro.ui.follower.FollowerNoticeViewModel import com.aiosman.riderpro.ui.like.LikeNoticeViewModel import com.aiosman.riderpro.ui.modifiers.noRippleClickable +import com.aiosman.riderpro.ui.navigateToChat import com.aiosman.riderpro.ui.navigateToPost import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.launch @@ -63,16 +64,15 @@ import kotlinx.coroutines.launch fun NotificationsScreen() { val navController = LocalNavController.current val systemUiController = rememberSystemUiController() - var dataFlow = MessageListViewModel.commentItemsFlow - var comments = dataFlow.collectAsLazyPagingItems() + val context = LocalContext.current val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = { MessageListViewModel.viewModelScope.launch { - MessageListViewModel.initData(force = true) + MessageListViewModel.initData(context, force = true) } }) LaunchedEffect(Unit) { systemUiController.setNavigationBarColor(Color.Transparent) - MessageListViewModel.initData() + MessageListViewModel.initData(context) } Column( modifier = Modifier.fillMaxSize() @@ -118,108 +118,40 @@ fun NotificationsScreen() { } navController.navigate(NavigationRoute.Followers.route) } +// NotificationIndicator( +// MessageListViewModel.favouriteNoticeCount, +// R.drawable.rider_pro_favoriate, +// stringResource(R.string.favourites_upper) +// ) { +// if (MessageListViewModel.favouriteNoticeCount > 0) { +// // 刷新收藏消息列表 +// FavouriteNoticeViewModel.isFirstLoad = true +// MessageListViewModel.clearFavouriteNoticeCount() +// } +// navController.navigate(NavigationRoute.FavouritesScreen.route) +// } NotificationIndicator( - MessageListViewModel.favouriteNoticeCount, - R.drawable.rider_pro_favoriate, - stringResource(R.string.favourites_upper) + MessageListViewModel.commentNoticeCount, + R.drawable.rider_pro_comment, + stringResource(R.string.comment).uppercase() ) { - if (MessageListViewModel.favouriteNoticeCount > 0) { - // 刷新收藏消息列表 - FavouriteNoticeViewModel.isFirstLoad = true - MessageListViewModel.clearFavouriteNoticeCount() - } - navController.navigate(NavigationRoute.FavouritesScreen.route) + navController.navigate(NavigationRoute.CommentNoticeScreen.route) } } HorizontalDivider(color = Color(0xFFEbEbEb), modifier = Modifier.padding(16.dp)) - NotificationCounterItem(MessageListViewModel.commentNoticeCount) - if (comments.loadState.refresh is LoadState.Loading) { - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .padding(bottom = 48.dp), - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "Loading", - fontSize = 18.sp - ) - Spacer(modifier = Modifier.height(16.dp)) - LinearProgressIndicator( - modifier = Modifier.width(160.dp), - color = Color(0xFFDA3832) - ) - } - - } - } else { - LazyColumn( - modifier = Modifier - .weight(1f) - .fillMaxSize() - ) { - items(comments.itemCount) { index -> - comments[index]?.let { comment -> - CommentNoticeItem(comment) { - MessageListViewModel.updateReadStatus(comment.id) - MessageListViewModel.viewModelScope.launch { - var highlightCommentId = comment.id - comment.parentCommentId?.let { - highlightCommentId = it - } - navController.navigateToPost( - id = comment.post!!.id, - highlightCommentId = highlightCommentId, - initImagePagerIndex = 0 - ) - } - } - } - } - // handle load error - when { - comments.loadState.append is LoadState.Loading -> { - item { - Box( - modifier = Modifier - .fillMaxWidth() - .height(64.dp) - .padding(16.dp), - contentAlignment = Alignment.Center - ) { - LinearProgressIndicator( - modifier = Modifier.width(160.dp), - color = Color(0xFFDA3832) - ) - } - } - } - - comments.loadState.append is LoadState.Error -> { - item { - Box( - modifier = Modifier - .fillMaxWidth() - .height(64.dp) - .noRippleClickable { - comments.retry() - }, - contentAlignment = Alignment.Center - ) { - Text( - text = "Load comment error, click to retry", - ) - } - } - } - } - item { - Spacer(modifier = Modifier.height(72.dp)) - } + NotificationCounterItem(MessageListViewModel.unReadConversationCount.toInt()) + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + ) { + ChatMessageList( + MessageListViewModel.chatList, + onUserAvatarClick = { conv -> + MessageListViewModel.goToUserDetail(conv, navController) + }, + ) { conv -> + MessageListViewModel.goToChat(conv, navController) } } } @@ -230,10 +162,7 @@ fun NotificationsScreen() { ) } - } - - } @Composable @@ -257,7 +186,7 @@ fun NotificationIndicator( if (notificationCount > 0) { Box( modifier = Modifier - .background(Color(0xFFE53935), RoundedCornerShape(8.dp)) + .background(Color(0xFFE53935), RoundedCornerShape(16.dp)) .padding(4.dp) .align(Alignment.TopEnd) ) { @@ -326,7 +255,6 @@ fun NotificationCounterItem(count: Int) { color = Color.White, fontSize = 12.sp, fontWeight = FontWeight.Bold, - modifier = Modifier.padding(4.dp) ) } } @@ -335,103 +263,83 @@ fun NotificationCounterItem(count: Int) { @Composable -fun CommentNoticeItem( - commentItem: CommentEntity, - onPostClick: () -> Unit = {}, +fun ChatMessageList( + items: List, + onUserAvatarClick: (Conversation) -> Unit = {}, + onChatClick: (Conversation) -> Unit = {} ) { - val navController = LocalNavController.current - val context = LocalContext.current - Row( - modifier = Modifier.padding(vertical = 20.dp, horizontal = 16.dp) + LazyColumn( + modifier = Modifier.fillMaxSize() ) { - Box { - CustomAsyncImage( - context = context, - imageUrl = commentItem.avatar, - contentDescription = commentItem.name, - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .noRippleClickable { - navController.navigate( - NavigationRoute.AccountProfile.route.replace( - "{id}", - commentItem.author.toString() - ) - ) - } - ) - } - Row( - modifier = Modifier - .weight(1f) - .padding(start = 12.dp) - .noRippleClickable { - onPostClick() - } - ) { - Column( - modifier = Modifier.weight(1f) + items(items.size) { index -> + val item = items[index] + Row( + modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp) ) { - Text( - text = commentItem.name, - fontSize = 18.sp, - modifier = Modifier - ) - Spacer(modifier = Modifier.height(4.dp)) - Row { - var text = commentItem.comment - if (commentItem.parentCommentId != null) { - text = "Reply you: $text" - } - Text( - text = text, - fontSize = 14.sp, - maxLines = 1, - color = Color(0x99000000), - modifier = Modifier.weight(1f), - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = commentItem.date.timeAgo(context), - fontSize = 14.sp, - color = Color(0x66000000) - ) - } - - } - Spacer(modifier = Modifier.width(24.dp)) - commentItem.post?.let { Box { - Box( - modifier = Modifier.padding(4.dp) - ) { - CustomAsyncImage( - context = context, - imageUrl = it.images[0].thumbnail, - contentDescription = "Post Image", - modifier = Modifier - .size(48.dp) + CustomAsyncImage( + context = LocalContext.current, + imageUrl = item.avatar, + contentDescription = item.nickname, + modifier = Modifier + .size(48.dp) + .noRippleClickable { + onUserAvatarClick(item) + } + ) + } + Column( + modifier = Modifier + .weight(1f) + .padding(start = 12.dp) + .noRippleClickable { + onChatClick(item) + } + ) { + Row { + Text( + text = item.nickname, + fontSize = 16.sp, + modifier = Modifier, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + text = item.lastMessageTime, + fontSize = 14.sp, + color = Color(0x66000000) ) - // unread indicator - } - - if (commentItem.unread) { - Box( - modifier = Modifier - .background(Color(0xFFE53935), CircleShape) - .size(12.dp) - .align(Alignment.TopEnd) + Spacer(modifier = Modifier.height(6.dp)) + Row { + Text( + text = item.lastMessage, + fontSize = 14.sp, + maxLines = 1, + color = Color(0x99000000), + modifier = Modifier.weight(1f), + overflow = TextOverflow.Ellipsis ) + Spacer(modifier = Modifier.width(4.dp)) + if (item.unreadCount > 0) { + Box( + modifier = Modifier + .background(Color(0xFFE53935), CircleShape) + .padding(horizontal = 8.dp, vertical = 2.dp) + ) { + Text( + text = item.unreadCount.toString(), + color = Color.White, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.align(Alignment.Center) + ) + } + Spacer(modifier = Modifier.width(8.dp)) + } } } - - } } - - } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt index 4a2ab5e..71fd14a 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt @@ -1,10 +1,14 @@ package com.aiosman.riderpro.ui.index.tabs.message +import android.content.Context +import android.icu.util.Calendar 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.navigation.NavController +import androidx.navigation.NavHostController import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData @@ -18,21 +22,43 @@ import com.aiosman.riderpro.data.CommentRemoteDataSource import com.aiosman.riderpro.data.CommentService import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.CommentServiceImpl +import com.aiosman.riderpro.data.UserService +import com.aiosman.riderpro.data.UserServiceImpl +import com.aiosman.riderpro.exp.formatChatTime +import com.aiosman.riderpro.ui.NavigationRoute +import com.aiosman.riderpro.ui.navigateToChat +import com.aiosman.riderpro.utils.TrtcHelper +import com.tencent.imsdk.v2.V2TIMConversation +import com.tencent.imsdk.v2.V2TIMConversationResult +import com.tencent.imsdk.v2.V2TIMManager +import com.tencent.imsdk.v2.V2TIMValueCallback import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlin.coroutines.suspendCoroutine + +data class Conversation( + val trtcUserId: String, + val nickname: String, + val lastMessage: String, + val lastMessageTime: String, + val avatar: String = "", + val unreadCount: Int = 0 +) object MessageListViewModel : ViewModel() { val accountService: AccountService = AccountServiceImpl() + val userService: UserService = UserServiceImpl() var noticeInfo by mutableStateOf(null) - - private val commentService: CommentService = CommentServiceImpl() + var chatList by mutableStateOf>(emptyList()) private val _commentItemsFlow = MutableStateFlow>(PagingData.empty()) - val commentItemsFlow = _commentItemsFlow.asStateFlow() var isLoading by mutableStateOf(false) + var unReadConversationCount by mutableStateOf(0L) var isFirstLoad = true - suspend fun initData(force: Boolean = false) { + suspend fun initData(context: Context, force: Boolean = false) { + loadChatList(context) + loadUnreadCount() if (!isFirstLoad && !force) { return } @@ -42,20 +68,7 @@ object MessageListViewModel : ViewModel() { isFirstLoad = false val info = accountService.getMyNoticeInfo() noticeInfo = info - viewModelScope.launch { - Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { - CommentPagingSource( - CommentRemoteDataSource(commentService), - selfNotice = true, - order = "latest" - ) - } - ).flow.cachedIn(viewModelScope).collectLatest { - _commentItemsFlow.value = it - } - } + isLoading = false } @@ -81,13 +94,6 @@ object MessageListViewModel : ViewModel() { _commentItemsFlow.value = updatedPagingData } - fun updateReadStatus(id: Int) { - viewModelScope.launch { - commentService.updateReadStatus(id) - updateIsRead(id) - updateUnReadCount(-1) - } - } fun clearLikeNoticeCount() { noticeInfo = noticeInfo?.copy(likeCount = 0) @@ -96,18 +102,84 @@ object MessageListViewModel : ViewModel() { fun clearFollowNoticeCount() { noticeInfo = noticeInfo?.copy(followCount = 0) } + fun clearFavouriteNoticeCount() { noticeInfo = noticeInfo?.copy(favoriteCount = 0) } - fun updateUnReadCount(delta : Int) { + + fun updateUnReadCount(delta: Int) { noticeInfo?.let { noticeInfo = it.copy(commentCount = it.commentCount + delta) } } - fun ResetModel(){ + + fun ResetModel() { _commentItemsFlow.value = PagingData.empty() noticeInfo = null isLoading = false isFirstLoad = true } + + suspend fun loadChatList(context: Context) { + val result = suspendCoroutine { continuation -> + V2TIMManager.getConversationManager().getConversationList( + 0, + Int.MAX_VALUE, + object : V2TIMValueCallback { + override fun onSuccess(t: V2TIMConversationResult?) { + continuation.resumeWith(Result.success(t)) + } + + override fun onError(code: Int, desc: String?) { + continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) + } + } + ) + } + + chatList = result?.conversationList?.map { msg: V2TIMConversation -> + val lastMessage = Calendar.getInstance().apply { + timeInMillis = msg.lastMessage?.timestamp ?: 0 + timeInMillis *= 1000 + } + Conversation( + nickname = msg.showName, + lastMessage = msg.lastMessage?.textElem?.text ?: "", + lastMessageTime = lastMessage.time.formatChatTime(context), + avatar = msg.faceUrl, + unreadCount = msg.unreadCount, + trtcUserId = msg.userID + ) + } ?: emptyList() + } + + suspend fun loadUnreadCount() { + try { + this.unReadConversationCount = TrtcHelper.loadUnreadCount() + } catch (e: Exception) { + e.printStackTrace() + this.unReadConversationCount = 0 + } + } + + fun goToChat( + conversation: Conversation, + navController: NavHostController + ) { + viewModelScope.launch { + val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId) + navController.navigateToChat(profile.id.toString()) + } + } + + fun goToUserDetail( + conversation: Conversation, + navController: NavController + ) { + viewModelScope.launch { + val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId) + navController.navigate(NavigationRoute.AccountProfile.route.replace("{id}", profile.id.toString())) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/MyProfileViewModel2.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/MyProfileViewModel2.kt new file mode 100644 index 0000000..8c569e0 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/MyProfileViewModel2.kt @@ -0,0 +1,116 @@ +package com.aiosman.riderpro.ui.index.tabs.profile.v2 + +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.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 com.aiosman.riderpro.AppState +import com.aiosman.riderpro.AppStore +import com.aiosman.riderpro.data.AccountService +import com.aiosman.riderpro.data.AccountServiceImpl +import com.aiosman.riderpro.data.MomentService +import com.aiosman.riderpro.data.UploadImage +import com.aiosman.riderpro.entity.AccountProfileEntity +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 com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +object MyProfileViewModel2 : ViewModel() { + val accountService: AccountService = AccountServiceImpl() + val momentService: MomentService = MomentServiceImpl() + var profile by mutableStateOf(null) + private var _sharedFlow = MutableStateFlow>(PagingData.empty()) + var sharedFlow = _sharedFlow.asStateFlow() + + var refreshing by mutableStateOf(false) + var firstLoad = true + + fun loadProfile(pullRefresh: Boolean = false) { + viewModelScope.launch { + if (pullRefresh) { + refreshing = true + } + firstLoad = false + val profile = accountService.getMyAccountProfile() + MyProfileViewModel2.profile = profile + refreshing = false + try { + // Collect shared flow + Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + MomentPagingSource( + MomentRemoteDataSource(momentService), + author = profile.id + ) + } + ).flow.cachedIn(viewModelScope).collectLatest { + _sharedFlow.value = it + } + } catch (e: Exception) { + Log.e("MyProfileViewModel", "loadProfile: ", e) + } + } + } + + suspend fun logout() { + AppStore.apply { + token = null + rememberMe = false + saveData() + } + AppState.ReloadAppState() + } + + fun updateUserProfileBanner(bannerImageUrl: Uri?, context: Context) { + viewModelScope.launch { + var newBanner = bannerImageUrl?.let { + val cursor = context.contentResolver.query(it, null, null, null, null) + var newBanner: UploadImage? = null + cursor?.use { cur -> + if (cur.moveToFirst()) { + val displayName = cur.getString(cur.getColumnIndex("_display_name")) + val extension = displayName.substringAfterLast(".") + Log.d("NewPost", "File name: $displayName, extension: $extension") + // read as file + val file = uriToFile(context, it) + Log.d("NewPost", "File size: ${file.length()}") + newBanner = UploadImage(file, displayName, it.toString(), extension) + } + } + newBanner + } + accountService.updateProfile( + banner = newBanner, + avatar = null, + nickName = null, + bio = null + ) + profile = accountService.getMyAccountProfile() + } + } + + val bio get() = profile?.bio ?: "" + val nickName get() = profile?.nickName ?: "" + val avatar get() = profile?.avatar + + fun ResetModel() { + profile = null + _sharedFlow.value = PagingData.empty() + firstLoad = true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile2.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile2.kt new file mode 100644 index 0000000..5fa526a --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile2.kt @@ -0,0 +1,542 @@ +package com.aiosman.riderpro.ui.index.tabs.profile.v2 + +import android.app.Activity +import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +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.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.paging.compose.collectAsLazyPagingItems +import com.aiosman.riderpro.LocalNavController +import com.aiosman.riderpro.R +import com.aiosman.riderpro.entity.AccountProfileEntity +import com.aiosman.riderpro.ui.NavigationRoute +import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder +import com.aiosman.riderpro.ui.composables.CustomAsyncImage +import com.aiosman.riderpro.ui.composables.MenuItem +import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel +import com.aiosman.riderpro.ui.modifiers.noRippleClickable +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun Profile2() { + val model = MyProfileViewModel2 + var expanded by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + MyProfileViewModel2.loadProfile() + } + val navController: NavController = LocalNavController.current + val scope = rememberCoroutineScope() + val state = rememberPullRefreshState(MyProfileViewModel2.refreshing, onRefresh = { + MyProfileViewModel2.loadProfile(pullRefresh = true) + }) + val context = LocalContext.current + val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() + val pickBannerImageLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val uri = result.data?.data + uri?.let { + MyProfileViewModel2.updateUserProfileBanner(it, context = context) + } + } + } + + + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + // 处理滚动事件之前的逻辑 + return Offset.Zero + } + + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + // 处理滚动事件之后的逻辑 + return Offset.Zero + } + } + } + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + .padding(bottom = with(LocalDensity.current) { + val da = WindowInsets.navigationBars + .getBottom(this) + .toDp() + 48.dp + da + }) + .pullRefresh(state) + ) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection), + ) { + item { + Column( + modifier = Modifier + .fillMaxWidth() + + ) { + // banner + Box( + modifier = Modifier + .fillMaxWidth() + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(500.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .noRippleClickable { + Intent(Intent.ACTION_PICK).apply { + type = "image/*" + pickBannerImageLauncher.launch(this) + } + } + .shadow( + elevation = 4.dp, + shape = RoundedCornerShape( + bottomStart = 32.dp, + bottomEnd = 32.dp + ) + ) // 添加阴影 + + + ) { + val banner = MyProfileViewModel2.profile?.banner + + if (banner != null) { + CustomAsyncImage( + LocalContext.current, + banner, + modifier = Modifier + .fillMaxSize(), + contentDescription = "", + contentScale = ContentScale.Crop + ) + } else { + Image( + painter = painterResource(id = R.drawable.rider_pro_moment_demo_2), + modifier = Modifier + .fillMaxSize(), + contentDescription = "", + contentScale = ContentScale.Crop + ) + } + } + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding( + top = statusBarPaddingValues.calculateTopPadding(), + start = 8.dp, + end = 8.dp + ) + ) { + Box( + modifier = Modifier + .padding(16.dp) + .clip(RoundedCornerShape(8.dp)) + .shadow( + elevation = 20.dp + ) + .background(Color.White.copy(alpha = 0.7f)) + ) { + Icon( + painter = painterResource(id = R.drawable.rider_pro_more_horizon), + contentDescription = "", + modifier = Modifier.noRippleClickable { + expanded = true + }, + tint = Color.Black + ) + } + + com.aiosman.riderpro.ui.composables.DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + width = 250, + menuItems = listOf( + MenuItem( + stringResource(R.string.logout), + R.mipmap.rider_pro_logout + ) { + expanded = false + scope.launch { + MyProfileViewModel2.logout() + navController.navigate(NavigationRoute.Login.route) { + popUpTo(NavigationRoute.Index.route) { + inclusive = true + } + } + } + }, + MenuItem( + stringResource(R.string.change_password), + R.mipmap.rider_pro_change_password + ) { + expanded = false + scope.launch { + navController.navigate(NavigationRoute.ChangePasswordScreen.route) + } + }, + MenuItem( + stringResource(R.string.favourites), + R.drawable.rider_pro_favourite + ) { + expanded = false + scope.launch { + navController.navigate(NavigationRoute.FavouriteList.route) + } + } + ), + + ) + } + } + } + Spacer(modifier = Modifier.height(32.dp)) + // 个人信息 + Box( + modifier = Modifier.padding(horizontal = 16.dp) + ) { + MyProfileViewModel2.profile?.let { + UserItem(it) + } + } + Spacer(modifier = Modifier.height(16.dp)) + MyProfileViewModel2.profile?.let { + Box( + modifier = Modifier.padding(horizontal = 16.dp) + ) { + SelfProfileAction { + navController.navigate(NavigationRoute.AccountEdit.route) + } + } + + } + Spacer(modifier = Modifier.height(16.dp)) + // 动态列表 + } + UserContentTab(nestedScrollConnection) + } + + + } + PullRefreshIndicator(MyProfileViewModel2.refreshing, state, Modifier.align(Alignment.TopCenter)) + } +} + +@Composable +fun UserItem(accountProfileEntity: AccountProfileEntity) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + // 头像 + CustomAsyncImage( + LocalContext.current, + accountProfileEntity.avatar, + modifier = Modifier + .clip(CircleShape) + .size(48.dp), + contentDescription = "", + contentScale = ContentScale.Crop + ) + Spacer(modifier = Modifier.width(32.dp)) + //个人统计 + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.weight(1f) + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.weight(1f) + ) { + Text( + text = accountProfileEntity.followerCount.toString(), + fontWeight = FontWeight.W600, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.followers_upper), + ) + } + Column( + verticalArrangement = Arrangement.Center, + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = accountProfileEntity.followingCount.toString(), + fontWeight = FontWeight.W600, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.following_upper), + ) + } + Column( + verticalArrangement = Arrangement.Center, + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + + } + } + } + Spacer(modifier = Modifier.height(12.dp)) + // 昵称 + Text( + text = accountProfileEntity.nickName, + fontWeight = FontWeight.W600, + fontSize = 16.sp, + ) + Spacer(modifier = Modifier.height(4.dp)) + // 个人简介 + Text( + text = accountProfileEntity.bio, + fontSize = 14.sp, + color = Color.Gray + ) + + } + +} + +@Composable +fun SelfProfileAction( + onEditProfile: () -> Unit +) { + // 按钮 + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clip(RoundedCornerShape(32.dp)) + .background(Color(0xffebebeb)) + .padding(horizontal = 16.dp, vertical = 4.dp) + .noRippleClickable { + onEditProfile() + } + ) { + Icon( + Icons.Default.Edit, + contentDescription = "", + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.edit_profile), + fontSize = 14.sp, + fontWeight = FontWeight.W600, + color = Color.Black, + modifier = Modifier.padding(8.dp) + ) + + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun UserContentTab(nestedScrollConnection: NestedScrollConnection) { + val pagerState = rememberPagerState(pageCount = { 2 }) + val scope = rememberCoroutineScope() + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clip(RoundedCornerShape(32.dp)) + .background(if (pagerState.currentPage == 0) Color(0xFFFFFFFF) else Color.Transparent) + .padding(horizontal = 16.dp, vertical = 4.dp) + .noRippleClickable { + // switch to gallery + scope.launch { + pagerState.scrollToPage(0) + } + + } + ) { + Text( + text = "Gallery", + fontSize = 14.sp, + fontWeight = FontWeight.W600, + color = Color.Black, + modifier = Modifier.padding(8.dp) + ) + + } + Spacer(modifier = Modifier.width(4.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clip(RoundedCornerShape(32.dp)) + .background(if (pagerState.currentPage == 1) Color(0xFFFFFFFF) else Color.Transparent) + .padding(horizontal = 16.dp, vertical = 8.dp) + .noRippleClickable { + // switch to moments + scope.launch { + pagerState.scrollToPage(1) + } + } + ) { + Text( + text = "Moments", + fontSize = 16.sp, + fontWeight = FontWeight.W600, + color = Color.Black, + modifier = Modifier.padding(8.dp) + ) + } + } + Spacer(modifier = Modifier.height(16.dp)) + HorizontalPager( + state = pagerState, + modifier = Modifier + .padding(0.dp) + .height(900.dp), + beyondBoundsPageCount = 2, + verticalAlignment = Alignment.Top + ) { page -> + when (page) { + 0 -> Gallery() + 1 -> MomentsList(nestedScrollConnection) + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun Gallery() { + val moments = MyProfileViewModel2.sharedFlow.collectAsLazyPagingItems() + val screenWidth = LocalConfiguration.current.screenWidthDp.dp + val itemWidth = (screenWidth / 2) - 16.dp - 2.dp + FlowRow( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + maxItemsInEachRow = 2, + ) { + for (idx in 0 until moments.itemCount) { + val moment = moments[idx] + moment?.let { + Box( + modifier = Modifier + .width(itemWidth) + .aspectRatio(1f) + .background(Color.Gray) + ) { + CustomAsyncImage( + LocalContext.current, + it.images[0].thumbnail, + modifier = Modifier + .fillMaxSize(), + contentDescription = "", + contentScale = ContentScale.Crop + ) + } + } + } + BottomNavigationPlaceholder() + } +} + +@Composable +fun MomentsList(nestedScrollConnection: NestedScrollConnection) { + val moments = MyProfileViewModel2.sharedFlow.collectAsLazyPagingItems() + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .height(900.dp).nestedScroll(nestedScrollConnection), + + ) { + item { + for (idx in 0 until moments.itemCount) { + val moment = moments[idx] + moment?.let { + MomentPostUnit(it) + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile3.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile3.kt new file mode 100644 index 0000000..38a57f1 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile3.kt @@ -0,0 +1,134 @@ +package com.aiosman.riderpro.ui.index.tabs.profile.v2 + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp + + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@Composable +fun Profile3() { + Scaffold( + + ) { it + val screenHeight = LocalConfiguration.current.screenHeightDp.dp + var headHeight by remember { mutableStateOf(400.dp) } + val speedFactor = 2 // 调整速度因子 +// val animatedHeadHeight by animateDpAsState( +// targetValue = headHeight, +// animationSpec = tween(durationMillis = 0) +// ) + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.y + val newHeight = (headHeight + delta.dp).coerceIn(0.dp, 400.dp) + headHeight = newHeight + return Offset.Zero + } + } + } + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection) + ) { + item { + // head + Box( + modifier = Modifier + .fillMaxWidth() + .height(headHeight) + .background(Color.DarkGray) + ) + } + item { + LazyColumn( + modifier = Modifier.fillMaxWidth().height(screenHeight), + ) { + items(100) { idx -> + Box( + modifier = Modifier + .fillMaxWidth() + .height(64.dp) + .background(Color.Green) + ) { + Text("Item $idx") + } + Spacer(modifier = Modifier.height(8.dp)) + } + } + } +// item { +// HorizontalPager( +// state = rememberPagerState(pageCount = { 2 }), +// modifier = Modifier +// .fillMaxWidth() +// .height(screenHeight) +// ) { page -> +// when (page) { +// 0 -> { +// LazyVerticalGrid( +// columns = GridCells.Fixed(2), +// verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), +// horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), +// modifier = Modifier.fillMaxSize(), +//// userScrollEnabled = headHeight < 2.dp +// ) { +// items(100) { idx -> +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .height(64.dp) +// ) { +// Text("Item $idx") +// } +// } +// } +// } +// 1 -> { +// LazyColumn( +// modifier = Modifier.fillMaxSize(), +//// userScrollEnabled = headHeight < 2.dp +// ) { +// items(100) { idx -> +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .height(64.dp) +// .background(Color.Green) +// ) { +// Text("Item $idx") +// } +// Spacer(modifier = Modifier.height(8.dp)) +// } +// } +// } +// } +// } +// } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile4.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile4.kt new file mode 100644 index 0000000..04a0332 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile4.kt @@ -0,0 +1,134 @@ +package com.aiosman.riderpro.ui.index.tabs.profile.v2 + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp + + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@Composable +fun Profile4() { + Scaffold( + + ) { it + val screenHeight = LocalConfiguration.current.screenHeightDp.dp + var headHeight by remember { mutableStateOf(400.dp) } + val speedFactor = 2 // 调整速度因子 +// val animatedHeadHeight by animateDpAsState( +// targetValue = headHeight, +// animationSpec = tween(durationMillis = 0) +// ) + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.y + val newHeight = (headHeight + delta.dp).coerceIn(0.dp, 400.dp) + headHeight = newHeight + return Offset.Zero + } + } + } + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection) + ) { + item { + // head + Box( + modifier = Modifier + .fillMaxWidth() + .height(headHeight) + .background(Color.DarkGray) + ) + } + item { + LazyColumn( + modifier = Modifier.fillMaxWidth().height(screenHeight), + ) { + items(100) { idx -> + Box( + modifier = Modifier + .fillMaxWidth() + .height(64.dp) + .background(Color.Green) + ) { + Text("Item $idx") + } + Spacer(modifier = Modifier.height(8.dp)) + } + } + } +// item { +// HorizontalPager( +// state = rememberPagerState(pageCount = { 2 }), +// modifier = Modifier +// .fillMaxWidth() +// .height(screenHeight) +// ) { page -> +// when (page) { +// 0 -> { +// LazyVerticalGrid( +// columns = GridCells.Fixed(2), +// verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), +// horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), +// modifier = Modifier.fillMaxSize(), +//// userScrollEnabled = headHeight < 2.dp +// ) { +// items(100) { idx -> +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .height(64.dp) +// ) { +// Text("Item $idx") +// } +// } +// } +// } +// 1 -> { +// LazyColumn( +// modifier = Modifier.fillMaxSize(), +//// userScrollEnabled = headHeight < 2.dp +// ) { +// items(100) { idx -> +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .height(64.dp) +// .background(Color.Green) +// ) { +// Text("Item $idx") +// } +// Spacer(modifier = Modifier.height(8.dp)) +// } +// } +// } +// } +// } +// } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile5.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile5.kt new file mode 100644 index 0000000..398f993 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile5.kt @@ -0,0 +1,879 @@ +package com.aiosman.riderpro.ui.index.tabs.profile.v2 + +import android.app.Activity +import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.aiosman.riderpro.AppState +import com.aiosman.riderpro.LocalNavController +import com.aiosman.riderpro.R +import com.aiosman.riderpro.entity.AccountProfileEntity +import com.aiosman.riderpro.entity.MomentEntity +import com.aiosman.riderpro.exp.formatPostTime2 +import com.aiosman.riderpro.ui.NavigationRoute +import com.aiosman.riderpro.ui.composables.CustomAsyncImage +import com.aiosman.riderpro.ui.composables.MenuItem +import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel +import com.aiosman.riderpro.ui.modifiers.noRippleClickable +import com.aiosman.riderpro.ui.navigateToChat +import com.aiosman.riderpro.ui.navigateToPost +import com.aiosman.riderpro.ui.post.NewPostViewModel +import kotlinx.coroutines.launch + + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ProfilePage() { + val model = MyProfileViewModel2 + var expanded by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + MyProfileViewModel.loadProfile() + } + val navController: NavController = LocalNavController.current + val scope = rememberCoroutineScope() + val state = rememberPullRefreshState(MyProfileViewModel.refreshing, onRefresh = { + MyProfileViewModel.loadProfile(pullRefresh = true) + }) + val context = LocalContext.current + val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() + val pickBannerImageLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val uri = result.data?.data + uri?.let { + MyProfileViewModel.updateUserProfileBanner(it, context = context) + } + } + } + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + .padding(bottom = with(LocalDensity.current) { + val da = WindowInsets.navigationBars + .getBottom(this) + .toDp() + 48.dp + da + }) + .pullRefresh(state) + ) { + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(400.dp) + .noRippleClickable { + Intent(Intent.ACTION_PICK).apply { + type = "image/*" + pickBannerImageLauncher.launch(this) + } + } + ) { + val banner = MyProfileViewModel.profile?.banner + + if (banner != null) { + CustomAsyncImage( + LocalContext.current, + banner, + modifier = Modifier + .fillMaxSize(), + contentDescription = "", + contentScale = ContentScale.Crop + ) + } else { + Image( + painter = painterResource(id = R.drawable.rider_pro_moment_demo_2), + modifier = Modifier + .fillMaxSize(), + contentDescription = "", + contentScale = ContentScale.Crop + ) + } + } + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding( + top = statusBarPaddingValues.calculateTopPadding(), + start = 8.dp, + end = 8.dp + ) + ) { + Box( + modifier = Modifier + .padding(16.dp) + .clip(RoundedCornerShape(8.dp)) + .shadow( + elevation = 20.dp + ) + .background(Color.White.copy(alpha = 0.7f)) + ) { + Icon( + painter = painterResource(id = R.drawable.rider_pro_more_horizon), + contentDescription = "", + modifier = Modifier.noRippleClickable { + expanded = true + }, + tint = Color.Black + ) + } + + com.aiosman.riderpro.ui.composables.DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + width = 250, + menuItems = listOf( + MenuItem( + stringResource(R.string.logout), + R.mipmap.rider_pro_logout + ) { + expanded = false + scope.launch { + MyProfileViewModel.logout() + navController.navigate(NavigationRoute.Login.route) { + popUpTo(NavigationRoute.Index.route) { + inclusive = true + } + } + } + }, + MenuItem( + stringResource(R.string.change_password), + R.mipmap.rider_pro_change_password + ) { + expanded = false + scope.launch { + navController.navigate(NavigationRoute.ChangePasswordScreen.route) + } + }, + MenuItem( + stringResource(R.string.favourites), + R.drawable.rider_pro_favourite + ) { + expanded = false + scope.launch { + navController.navigate(NavigationRoute.FavouriteList.route) + } + } + ), + + ) + } + } + Spacer(modifier = Modifier.height(32.dp)) + MyProfileViewModel.profile?.let { + UserInformation( + accountProfileEntity = it, + onEditProfileClick = { + navController.navigate(NavigationRoute.AccountEdit.route) + } + ) + } +// if (moments.itemCount == 0) { +// EmptyMomentPostUnit() +// } + } + +// items(moments.itemCount) { idx -> +// val momentItem = moments[idx] ?: return@items +// MomentPostUnit(momentItem) +// } + item { + Spacer(modifier = Modifier.height(48.dp)) + } + + + } + PullRefreshIndicator(MyProfileViewModel.refreshing, state, Modifier.align(Alignment.TopCenter)) + } + + +} + +@Composable +fun CarGroup() { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 54.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CarTopInformation() + CarTopPicture() + } +} + +@Composable +fun CarTopInformation() { + Row { + Text( + text = "BMW", + color = Color.Black, + fontSize = 12.sp, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + Text( + modifier = Modifier.padding(start = 4.dp), + text = "/", + color = Color.Gray, + fontSize = 12.sp + ) + Text( + modifier = Modifier.padding(start = 4.dp), + text = "M1000RR", + color = Color.Gray, + fontSize = 12.sp + ) + } +} + +@Composable +fun CarTopPicture() { + Image( + modifier = Modifier + .size(width = 336.dp, height = 224.dp) + .padding(top = 42.dp), + painter = painterResource(id = R.drawable.default_profile_moto), contentDescription = "" + ) +} + +@Composable +fun UserInformation( + isSelf: Boolean = true, + accountProfileEntity: AccountProfileEntity, + onFollowClick: () -> Unit = {}, + onEditProfileClick: () -> Unit = {} +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp, start = 33.dp, end = 33.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(modifier = Modifier.fillMaxWidth()) { + val userInfoModifier = Modifier.weight(1f) + UserInformationFollowers(userInfoModifier, accountProfileEntity) + UserInformationBasic(userInfoModifier, accountProfileEntity) + UserInformationFollowing(userInfoModifier, accountProfileEntity) + } + UserInformationSlogan(accountProfileEntity) + CommunicationOperatorGroup( + isSelf = isSelf, + isFollowing = accountProfileEntity.isFollowing, + onFollowClick = onFollowClick, + onEditProfileClick = onEditProfileClick, + accountProfileEntity = accountProfileEntity + ) + } +} + +@Composable +fun UserInformationFollowers(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { + val navController = LocalNavController.current + Column(modifier = modifier.padding(top = 31.dp)) { + Text( + modifier = Modifier + .padding(bottom = 5.dp) + .noRippleClickable { + navController.navigate( + NavigationRoute.FollowerList.route.replace( + "{id}", + accountProfileEntity.id.toString() + ) + ) + }, + text = accountProfileEntity.followerCount.toString(), + fontSize = 24.sp, + color = Color.Black, + style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold) + ) + Canvas( + modifier = Modifier + .size(width = 88.83.dp, height = 1.dp) + .padding(top = 2.dp, bottom = 5.dp) + ) { + drawLine( + color = Color(0xFFCCCCCC), + start = Offset(0f, 0f), + end = Offset(size.width, 0f), + strokeWidth = 1f, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) + ) + } + Text( + modifier = Modifier.padding(top = 5.dp), + text = stringResource(R.string.followers_upper), + fontSize = 12.sp, + color = Color.Black, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + } +} + +@Composable +fun UserInformationBasic(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { + val context = LocalContext.current + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier.size(width = 112.dp, height = 112.dp), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.fillMaxSize(), + painter = painterResource(id = R.drawable.avatar_bold), contentDescription = "" + ) + CustomAsyncImage( + context, + accountProfileEntity.avatar, + modifier = Modifier + .size(width = 88.dp, height = 88.dp) + .clip( + RoundedCornerShape(88.dp) + ), + contentDescription = "", + contentScale = ContentScale.Crop + ) + + } + Text( + modifier = Modifier + .widthIn(max = 220.dp) + .padding(top = 8.dp), + text = accountProfileEntity.nickName, + fontSize = 32.sp, + color = Color.Black, + style = TextStyle(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.padding(top = 4.dp), + text = accountProfileEntity.country, + fontSize = 12.sp, + color = Color.Gray + ) + } +} + +@Composable +fun UserInformationFollowing(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { + val navController = LocalNavController.current + Column( + modifier = modifier.padding(top = 6.dp), + horizontalAlignment = Alignment.End + ) { + Text( + modifier = Modifier + .padding(bottom = 5.dp) + .noRippleClickable { + navController.navigate( + NavigationRoute.FollowingList.route.replace( + "{id}", + accountProfileEntity.id.toString() + ) + ) + }, + text = accountProfileEntity.followingCount.toString(), + fontSize = 24.sp, + color = Color.Black, + style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold) + ) + Canvas( + modifier = Modifier + .size(width = 88.83.dp, height = 1.dp) + .padding(top = 2.dp, bottom = 5.dp) + ) { + drawLine( + color = Color(0xFFCCCCCC), + start = Offset(0f, 0f), + end = Offset(size.width, 0f), + strokeWidth = 1f, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) + ) + } + Text( + modifier = Modifier.padding(top = 5.dp), + text = stringResource(R.string.following_upper), + fontSize = 12.sp, + color = Color.Black, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + } +} + +@Composable +fun UserInformationSlogan(accountProfileEntity: AccountProfileEntity) { + Text( + modifier = Modifier.padding(top = 23.dp), + text = accountProfileEntity.bio, + fontSize = 13.sp, + color = Color.Black, + style = TextStyle(fontWeight = FontWeight.Bold) + ) +} + +@Composable +fun CommunicationOperatorGroup( + accountProfileEntity: AccountProfileEntity, + isSelf: Boolean = true, + isFollowing: Boolean = false, + onFollowClick: () -> Unit = {}, + onEditProfileClick: () -> Unit = {} +) { + val navController = LocalNavController.current + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), horizontalArrangement = Arrangement.Center + ) { + if (!isSelf && AppState.UserId != accountProfileEntity.id) { + Box( + modifier = Modifier + .size(width = 142.dp, height = 40.dp) + .noRippleClickable { + onFollowClick() + }, + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.fillMaxSize(), + painter = if (isFollowing) painterResource(id = R.mipmap.rider_pro_follow_grey) else painterResource( + id = R.mipmap.rider_pro_follow_red + ), + contentDescription = "" + ) + Text( + text = if (isFollowing) stringResource(R.string.following_upper) else stringResource( + R.string.follow_upper + ), + fontSize = 14.sp, + color = if (isFollowing) Color.Black else Color.White, + style = TextStyle(fontWeight = FontWeight.W600, fontStyle = FontStyle.Italic), + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Box( + modifier = Modifier + .size(width = 142.dp, height = 40.dp) + .noRippleClickable { + navController.navigateToChat(accountProfileEntity.id.toString()) + }, + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.fillMaxSize(), + painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey), + contentDescription = "" + ) + Text( + text = "CHAT", + fontSize = 14.sp, + color = Color.Black, + fontWeight = FontWeight.Bold, + style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic) + ) + } + } + + if (isSelf) { + Box( + modifier = Modifier + .size(width = 142.dp, height = 40.dp) + .noRippleClickable { + onEditProfileClick() + }, + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.fillMaxSize(), + painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey), + contentDescription = "" + ) + Text( + text = stringResource(R.string.edit_profile), + fontSize = 14.sp, + color = Color.Black, + fontWeight = FontWeight.Bold, + style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic) + ) + } + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun RidingStyle() { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, top = 40.dp, end = 24.dp), + horizontalAlignment = Alignment.Start + ) { + Text( + text = "RIDING STYLES", + fontSize = 18.sp, + color = Color.Black, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + Image( + modifier = Modifier + .padding(top = 4.dp) + .height(8.dp), + painter = painterResource(id = R.drawable.rider_pro_profile_line), + contentDescription = "" + ) + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp) + ) { + RidingStyleItem(styleContent = "Cruiser") + RidingStyleItem(styleContent = "Bobber") + RidingStyleItem(styleContent = "Cafe") + RidingStyleItem(styleContent = "Chopper") + RidingStyleItem(styleContent = "Sport") + RidingStyleItem(styleContent = "Vintage") + RidingStyleItem(styleContent = "Trike") + RidingStyleItem(styleContent = "Touring") + } + } +} + +@Composable +fun RidingStyleItem(styleContent: String) { + Box( + modifier = Modifier.padding(bottom = 8.dp, end = 8.dp), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.shadow( + ambientColor = Color.Gray, + spotColor = Color(0f, 0f, 0f, 0.2f), + elevation = 20.dp, + ), + painter = painterResource(id = R.drawable.rider_pro_style_wrapper), + contentDescription = "" + ) + Text( + modifier = Modifier.padding(start = 5.dp, end = 5.dp), + text = styleContent, + fontSize = 12.sp, + color = Color.Gray, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + } +} + +@Composable +fun EmptyMomentPostUnit() { + TimeGroup(stringResource(R.string.empty_my_post_title)) + ProfileEmptyMomentCard() +} + +@Composable +fun ProfileEmptyMomentCard( + +) { + var columnHeight by remember { mutableStateOf(0) } + val navController = LocalNavController.current + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, top = 18.dp, end = 24.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + ) { + Canvas( + modifier = Modifier + .height(with(LocalDensity.current) { columnHeight.toDp() }) + .width(14.dp) + ) { + drawLine( + color = Color(0xff899DA9), + start = Offset(0f, 0f), + end = Offset(0f, size.height), + strokeWidth = 4f, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) + ) + } + Spacer(modifier = Modifier.width(10.dp)) + Column( + modifier = Modifier + .weight(1f) + .onGloballyPositioned { coordinates -> + columnHeight = coordinates.size.height + } + ) { + Text(stringResource(R.string.empty_my_post_content), fontSize = 16.sp) + Spacer(modifier = Modifier.height(24.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(3f / 2f) + .background(Color.White) + .padding(16.dp) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + .noRippleClickable { + NewPostViewModel.asNewPost() + navController.navigate(NavigationRoute.NewPost.route) + } + ) { + Icon( + Icons.Default.Add, + tint = Color(0xFFD8D8D8), + contentDescription = "New post", + modifier = Modifier + .size(32.dp) + .align(Alignment.Center) + ) + } + } + } + } + } +} + +@Composable +fun MomentPostUnit(momentEntity: MomentEntity) { + TimeGroup(momentEntity.time.formatPostTime2()) + ProfileMomentCard( + momentEntity.momentTextContent, + momentEntity.images[0].thumbnail, + momentEntity.likeCount.toString(), + momentEntity.commentCount.toString(), + momentEntity = momentEntity + ) +} + +@Composable +fun TimeGroup(time: String = "2024.06.08 12:23") { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, top = 40.dp, end = 24.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Image( + modifier = Modifier + .height(16.dp) + .width(14.dp), + painter = painterResource(id = R.drawable.rider_pro_moment_time_flag), + contentDescription = "" + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = time, + fontSize = 16.sp, + color = Color.Black, + style = TextStyle(fontWeight = FontWeight.W600) + ) + } +} + +@Composable +fun ProfileMomentCard( + content: String, + imageUrl: String, + like: String, + comment: String, + momentEntity: MomentEntity +) { + var columnHeight by remember { mutableStateOf(0) } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, top = 18.dp, end = 24.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + ) { + Canvas( + modifier = Modifier + .height(with(LocalDensity.current) { columnHeight.toDp() }) + .width(14.dp) + ) { + drawLine( + color = Color(0xff899DA9), + start = Offset(0f, 0f), + end = Offset(0f, size.height), + strokeWidth = 4f, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) + ) + } + Spacer(modifier = Modifier.width(10.dp)) + Column( + modifier = Modifier + .background(Color.White) + .weight(1f) + .onGloballyPositioned { coordinates -> + columnHeight = coordinates.size.height + } + ) { + if (content.isNotEmpty()) { + MomentCardTopContent(content) + } + MomentCardPicture(imageUrl, momentEntity = momentEntity) + MomentCardOperation(like, comment) + } + } + } +} + +@Composable +fun MomentCardTopContent(content: String) { + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.padding(top = 16.dp, bottom = 0.dp, start = 16.dp, end = 16.dp), + text = content, fontSize = 16.sp, color = Color.Black + ) + } +} + +@Composable +fun MomentCardPicture(imageUrl: String, momentEntity: MomentEntity) { + val navController = LocalNavController.current + val context = LocalContext.current + CustomAsyncImage( + context, + imageUrl, + modifier = Modifier + .fillMaxSize() + .aspectRatio(3f / 2f) + .padding(top = 16.dp) + .noRippleClickable { + navController.navigateToPost( + id = momentEntity.id, + highlightCommentId = 0, + initImagePagerIndex = 0 + ) + }, + contentDescription = "", + contentScale = ContentScale.Crop + ) + + +} + +@Composable +fun MomentCardOperation(like: String, comment: String) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { +// Spacer(modifier = Modifier.weight(1f)) + MomentCardOperationItem( + drawable = R.drawable.rider_pro_like, + number = like, + modifier = Modifier.padding(end = 32.dp) + ) + MomentCardOperationItem( + drawable = R.drawable.rider_pro_moment_comment, + number = comment, + modifier = Modifier.padding(end = 32.dp) + ) + } +} + +@Composable +fun MomentCardOperationItem(@DrawableRes drawable: Int, number: String, modifier: Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Image( + modifier = Modifier.padding(start = 16.dp, end = 8.dp), + painter = painterResource(id = drawable), contentDescription = "" + ) + Text(text = number) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/utils/TrtcHelper.kt b/app/src/main/java/com/aiosman/riderpro/utils/TrtcHelper.kt new file mode 100644 index 0000000..f127200 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/utils/TrtcHelper.kt @@ -0,0 +1,22 @@ +package com.aiosman.riderpro.utils + +import com.tencent.imsdk.v2.V2TIMManager +import com.tencent.imsdk.v2.V2TIMValueCallback +import kotlin.coroutines.suspendCoroutine + +object TrtcHelper { + suspend fun loadUnreadCount(): Long { + return suspendCoroutine { continuation -> + V2TIMManager.getConversationManager() + .getTotalUnreadMessageCount(object : V2TIMValueCallback { + override fun onSuccess(t: Long?) { + continuation.resumeWith(Result.success(t ?: 0)) + } + + override fun onError(code: Int, desc: String?) { + continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) + } + }); + } + } +} \ No newline at end of file