diff --git a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt index b982779..c2af4b6 100644 --- a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt +++ b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt @@ -101,9 +101,15 @@ class MainActivity : ComponentActivity() { val postId = intent.getStringExtra("POST_ID") if (postId != null) { Log.d("MainActivity", "Navigation to Post$postId") - navController.navigate(NavigationRoute.Post.route.replace("{id}", postId)) + navController.navigate( + NavigationRoute.Post.route.replace( + "{id}", + postId + ) + ) } } + } } diff --git a/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt b/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt index b8076e3..cf290b9 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/CommentService.kt @@ -155,7 +155,8 @@ class CommentRemoteDataSource( postUser: Int?, selfNotice: Boolean?, order: String?, - parentCommentId: Int? + parentCommentId: Int?, + pageSize: Int? = 20 ): ListContainer { return commentService.getComments( pageNumber, @@ -163,7 +164,8 @@ class CommentRemoteDataSource( postUser = postUser, selfNotice = selfNotice, order = order, - parentCommentId = parentCommentId + parentCommentId = parentCommentId, + pageSize = pageSize ) } } diff --git a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt index fea49c0..627aeef 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt @@ -29,7 +29,9 @@ data class Moment( @SerializedName("commentCount") val commentCount: Long, @SerializedName("time") - val time: String + val time: String, + @SerializedName("isFollowed") + val isFollowed: Boolean, ) { fun toMomentItem(): MomentEntity { return MomentEntity( @@ -38,7 +40,7 @@ data class Moment( nickname = user.nickName, location = "Worldwide", time = ApiClient.dateFromApiString(time), - followStatus = false, + followStatus = isFollowed, momentTextContent = textContent, momentPicture = R.drawable.default_moment_img, likeCount = likeCount.toInt(), diff --git a/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt b/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt index 73a737c..f734c0e 100644 --- a/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt +++ b/app/src/main/java/com/aiosman/riderpro/entity/Comment.kt @@ -46,14 +46,15 @@ class CommentPagingSource( postUser = postUser, selfNotice = selfNotice, order = order, - parentCommentId = parentCommentId + parentCommentId = parentCommentId, + pageSize = params.loadSize ) LoadResult.Page( data = comments.list, prevKey = if (currentPage == 1) null else currentPage - 1, nextKey = if (comments.list.isEmpty()) null else comments.page + 1 ) - } catch (exception: IOException) { + } catch (exception: Exception) { return LoadResult.Error(exception) } } diff --git a/app/src/main/java/com/aiosman/riderpro/entity/Moment.kt b/app/src/main/java/com/aiosman/riderpro/entity/Moment.kt index 5979544..04a8a28 100644 --- a/app/src/main/java/com/aiosman/riderpro/entity/Moment.kt +++ b/app/src/main/java/com/aiosman/riderpro/entity/Moment.kt @@ -8,6 +8,7 @@ import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.ServiceException import com.aiosman.riderpro.data.UploadImage import com.aiosman.riderpro.data.api.ApiClient +import com.aiosman.riderpro.data.parseErrorResponse import com.aiosman.riderpro.entity.MomentEntity import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody @@ -165,8 +166,13 @@ class MomentBackend { suspend fun getMomentById(id: Int): MomentEntity { var resp = ApiClient.api.getPost(id) - var body = resp.body()?.data ?: throw ServiceException("Failed to get moment") - return body.toMomentItem() + if (!resp.isSuccessful) { + parseErrorResponse(resp.errorBody())?.let { + throw it.toServiceException() + } + throw ServiceException("Failed to get moment") + } + return resp.body()?.data?.toMomentItem() ?: throw ServiceException("Failed to get moment") } suspend fun likeMoment(id: Int) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt b/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt index e2ebbbf..b277865 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentModal.kt @@ -1,6 +1,5 @@ package com.aiosman.riderpro.ui.comment -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -14,11 +13,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.navigationBars 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.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.ModalBottomSheet @@ -30,17 +26,13 @@ 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.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -48,15 +40,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.paging.compose.collectAsLazyPagingItems -import com.aiosman.riderpro.AppState import com.aiosman.riderpro.R import com.aiosman.riderpro.entity.CommentEntity import com.aiosman.riderpro.ui.composables.EditCommentBottomModal -import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.post.CommentContent import com.aiosman.riderpro.ui.post.CommentMenuModal -import com.aiosman.riderpro.ui.post.CommentsSection import com.aiosman.riderpro.ui.post.CommentsViewModel import com.aiosman.riderpro.ui.post.OrderSelectionComponent import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/CustomClickableText.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/CustomClickableText.kt new file mode 100644 index 0000000..b0c0d47 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/CustomClickableText.kt @@ -0,0 +1,50 @@ +package com.aiosman.riderpro.ui.composables + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow + +@Composable +fun CustomClickableText( + text: AnnotatedString, + modifier: Modifier = Modifier, + style: TextStyle = TextStyle.Default, + softWrap: Boolean = true, + overflow: TextOverflow = TextOverflow.Clip, + maxLines: Int = Int.MAX_VALUE, + onTextLayout: (TextLayoutResult) -> Unit = {}, + onLongPress: () -> Unit = {}, + onClick: (Int) -> Unit +) { + val layoutResult = remember { mutableStateOf(null) } + val pressIndicator = Modifier.pointerInput(onClick) { + detectTapGestures( + onLongPress = { onLongPress() } + ) { pos -> + layoutResult.value?.let { layoutResult -> + onClick(layoutResult.getOffsetForPosition(pos)) + } + } + } + + BasicText( + text = text, + modifier = modifier.then(pressIndicator), + style = style, + softWrap = softWrap, + overflow = overflow, + maxLines = maxLines, + onTextLayout = { + layoutResult.value = it + onTextLayout(it) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt index e7d1b2a..511d38b 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteListPage.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.paging.compose.collectAsLazyPagingItems import com.aiosman.riderpro.LocalNavController @@ -47,7 +48,8 @@ fun FavouriteListPage() { Box( modifier = Modifier .fillMaxWidth() - .weight(1f).pullRefresh(state) + .weight(1f) + .pullRefresh(state) ) { Column( modifier = Modifier.fillMaxSize() @@ -57,7 +59,7 @@ fun FavouriteListPage() { .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 16.dp), ) { - NoticeScreenHeader("Favourite") + NoticeScreenHeader(stringResource(R.string.favourites_upper), moreIcon = false) } LazyVerticalGrid( columns = GridCells.Fixed(3), diff --git a/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt b/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt index 06a7f66..07e168f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/imageviewer/imageviewer.kt @@ -20,11 +20,8 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon -import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Text import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -35,30 +32,28 @@ 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.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.aiosman.riderpro.LocalAnimatedContentScope import com.aiosman.riderpro.LocalNavController -import com.aiosman.riderpro.LocalSharedTransitionScope import com.aiosman.riderpro.R import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.imageviewer.ImageViewerViewModel import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.utils.File.saveImageToGallery -import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.launch import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable -@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class, - ExperimentalMaterial3Api::class +@OptIn( + ExperimentalFoundationApi::class, ) @Composable fun ImageViewer() { @@ -72,8 +67,11 @@ fun ImageViewer() { WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 16.dp val scope = rememberCoroutineScope() val showRawImageStates = remember { mutableStateListOf(*Array(images.size) { false }) } - var showBottomSheet by remember { mutableStateOf(false) } var isDownloading by remember { mutableStateOf(false) } + var currentPage by remember { mutableStateOf(model.initialIndex) } + LaunchedEffect(pagerState) { + currentPage = pagerState.currentPage + } StatusBarMaskLayout( modifier = Modifier.background(Color.Black), ) { @@ -84,7 +82,7 @@ fun ImageViewer() { ) { HorizontalPager( state = pagerState, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { page -> val zoomState = rememberZoomState() CustomAsyncImage( @@ -102,6 +100,23 @@ fun ImageViewer() { contentScale = ContentScale.Fit, ) } + if (images.size > 1) { + Box( + modifier = Modifier + .align(Alignment.TopCenter) + .clip(RoundedCornerShape(16.dp)) + .background(Color(0xff333333).copy(alpha = 0.6f)) + .padding(vertical = 4.dp, horizontal = 24.dp) + ) { + Text( + text = "${pagerState.currentPage + 1}/${images.size}", + color = Color.White, + + ) + } + } + + Box( modifier = Modifier @@ -142,7 +157,7 @@ fun ImageViewer() { modifier = Modifier.size(32.dp), color = Color.White ) - }else{ + } else { Icon( painter = painterResource(id = R.drawable.rider_pro_download), contentDescription = "", @@ -153,7 +168,7 @@ fun ImageViewer() { Spacer(modifier = Modifier.height(4.dp)) Text( - "Download", + stringResource(R.string.download), color = Color.White ) } @@ -174,7 +189,7 @@ fun ImageViewer() { ) Spacer(modifier = Modifier.height(4.dp)) Text( - "Original", + stringResource(R.string.original), color = Color.White ) } 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 a221b94..c822210 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 @@ -20,6 +20,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -43,6 +44,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewModelScope +import androidx.paging.LoadState import androidx.paging.compose.collectAsLazyPagingItems import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R @@ -81,10 +83,12 @@ fun NotificationsScreen() { modifier = Modifier.fillMaxSize() ) { StatusBarSpacer() - Box(modifier = Modifier - .fillMaxWidth() - .weight(1f) - .pullRefresh(state)) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .pullRefresh(state) + ) { Column( modifier = Modifier.fillMaxSize(), ) { @@ -118,31 +122,91 @@ fun NotificationsScreen() { } HorizontalDivider(color = Color(0xFFEbEbEb), modifier = Modifier.padding(16.dp)) NotificationCounterItem(MessageListViewModel.commentNoticeCount) - LazyColumn( - modifier = Modifier - .weight(1f) - .fillMaxSize() - ) { - items(comments.itemCount) { index -> - comments[index]?.let { comment -> - CommentNoticeItem(comment) { - MessageListViewModel.updateReadStatus(comment.id) - MessageListViewModel.viewModelScope.launch { -// PostViewModel.postId = comment.postId.toString() -// PostViewModel.initData() - navController.navigate( - NavigationRoute.Post.route.replace( - "{id}", - comment.postId.toString() - ) - ) - } + 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 { + navController.navigate( + NavigationRoute.Post.route.replace( + "{id}", + comment.postId.toString() + ) + ) + } + } } } - } - item { - Spacer(modifier = Modifier.height(72.dp)) + // 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)) + } } } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt index de97358..0c633b9 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt @@ -45,18 +45,23 @@ object MyProfileViewModel : ViewModel() { profile = accountService.getMyAccountProfile() val profile = accountService.getMyAccountProfile() refreshing = false - Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { - MomentPagingSource( - MomentRemoteDataSource(momentService), - author = profile.id - ) + try { + Pager( + config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + MomentPagingSource( + MomentRemoteDataSource(momentService), + author = profile.id + ) + } + ).flow.cachedIn(viewModelScope).collectLatest { + _momentsFlow.value = it } - ).flow.cachedIn(viewModelScope).collectLatest { - _momentsFlow.value = it + }catch (e: Exception){ + Log.e("MyProfileViewModel", "loadProfile: ", e) } + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt index 50750de..7b482a3 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt @@ -162,8 +162,6 @@ fun ProfilePage() { ) } } - - Box( modifier = Modifier .align(Alignment.TopEnd) @@ -174,10 +172,14 @@ fun ProfilePage() { ) ) { Box( - modifier = Modifier.padding(16.dp).clip(RoundedCornerShape(8.dp)).shadow( - elevation = 20.dp - ).background(Color.White.copy(alpha = 0.7f)) - ){ + 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 = "", @@ -191,7 +193,7 @@ fun ProfilePage() { com.aiosman.riderpro.ui.composables.DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, - width = 300, + width = 250, menuItems = listOf( MenuItem( stringResource(R.string.logout), @@ -217,7 +219,7 @@ fun ProfilePage() { } }, MenuItem( - "Favourite", + stringResource(R.string.favourites), R.drawable.rider_pro_favourite ) { expanded = false @@ -227,7 +229,7 @@ fun ProfilePage() { } ), - ) + ) } } Spacer(modifier = Modifier.height(32.dp)) diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt index eb8d546..fd5781d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt @@ -49,15 +49,19 @@ class CommentsViewModel( fun reloadComment() { viewModelScope.launch { - Pager(config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { - CommentPagingSource( - CommentRemoteDataSource(commentService), - postId = postId.toInt(), - order = order - ) - }).flow.cachedIn(viewModelScope).collectLatest { - _commentsFlow.value = it + try { + Pager(config = PagingConfig(pageSize = 5, enablePlaceholders = false), + pagingSourceFactory = { + CommentPagingSource( + CommentRemoteDataSource(commentService), + postId = postId.toInt(), + order = order + ) + }).flow.cachedIn(viewModelScope).collectLatest { + _commentsFlow.value = it + } + } catch (e: Exception) { + e.printStackTrace() } } } @@ -144,7 +148,12 @@ class CommentsViewModel( fun deleteComment(commentId: Int) { viewModelScope.launch { commentService.DeleteComment(commentId) - reloadComment() + // 如果是刚刚创建的评论,则从addedCommentList中删除 + if (addedCommentList.any { it.id == commentId }) { + addedCommentList = addedCommentList.filter { it.id != commentId } + } else { + reloadComment() + } } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt index e92b84e..f4edcee 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt @@ -1,7 +1,9 @@ package com.aiosman.riderpro.ui.post -import android.util.Log +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.fadeIn +import androidx.compose.animation.slideInVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -14,7 +16,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -24,17 +25,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState 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.foundation.text.ClickableText -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -46,22 +42,24 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -69,7 +67,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import com.aiosman.riderpro.AppState import com.aiosman.riderpro.LocalAnimatedContentScope @@ -82,7 +79,6 @@ import com.aiosman.riderpro.entity.MomentImageEntity import com.aiosman.riderpro.exp.formatPostTime import com.aiosman.riderpro.exp.timeAgo import com.aiosman.riderpro.ui.NavigationRoute -import com.aiosman.riderpro.ui.comment.CommentModalViewModel import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder @@ -92,6 +88,13 @@ import com.aiosman.riderpro.ui.composables.StatusBarSpacer import com.aiosman.riderpro.ui.imageviewer.ImageViewerViewModel import com.aiosman.riderpro.ui.modifiers.noRippleClickable import kotlinx.coroutines.launch +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.paging.LoadState +import com.aiosman.riderpro.ui.comment.NoticeScreenHeader +import com.aiosman.riderpro.ui.composables.CustomClickableText @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -113,28 +116,67 @@ fun PostScreen( var contextComment by remember { mutableStateOf(null) } var replyComment by remember { mutableStateOf(null) } var showCommentModal by remember { mutableStateOf(false) } + var commentModalState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + var editCommentModalState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) LaunchedEffect(Unit) { viewModel.initData() } + if (showCommentMenu) { ModalBottomSheet( onDismissRequest = { showCommentMenu = false }, containerColor = Color.White, - sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true - ), + sheetState = commentModalState, dragHandle = {}, shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), windowInsets = WindowInsets(0) ) { CommentMenuModal( onDeleteClick = { - showCommentMenu = false + scope.launch { + commentModalState.hide() + showCommentMenu = false + } contextComment?.let { viewModel.deleteComment(it.id) } + + }, + commentEntity = contextComment, + onCloseClick = { + scope.launch { + commentModalState.hide() + showCommentMenu = false + } + }, + isSelf = AppState.UserId?.toLong() == contextComment?.author, + onLikeClick = { + scope.launch { + commentModalState.hide() + showCommentMenu = false + } + contextComment?.let { + viewModel.viewModelScope.launch { + if (it.liked) { + viewModel.unlikeComment(it.id) + } else { + viewModel.likeComment(it.id) + } + } + } + + }, + onReplyClick = { + scope.launch { + commentModalState.hide() + showCommentMenu = false + replyComment = contextComment + showCommentModal = true + } } ) } @@ -177,6 +219,8 @@ fun PostScreen( content = it ) } + + editCommentModalState.hide() showCommentModal = false } @@ -186,31 +230,34 @@ fun PostScreen( Scaffold( modifier = Modifier.fillMaxSize(), bottomBar = { - PostBottomBar( - onLikeClick = { - scope.launch { - if (viewModel.moment?.liked == true) { - viewModel.dislikeMoment() - } else { - viewModel.likeMoment() + if (!viewModel.isError) { + PostBottomBar( + onLikeClick = { + scope.launch { + if (viewModel.moment?.liked == true) { + viewModel.dislikeMoment() + } else { + viewModel.likeMoment() + } } - } - }, - onCreateCommentClick = { - replyComment = null - showCommentModal = true - }, - onFavoriteClick = { - scope.launch { - if (viewModel.moment?.isFavorite == true) { - viewModel.unfavoriteMoment() - } else { - viewModel.favoriteMoment() + }, + onCreateCommentClick = { + replyComment = null + showCommentModal = true + }, + onFavoriteClick = { + scope.launch { + if (viewModel.moment?.isFavorite == true) { + viewModel.unfavoriteMoment() + } else { + viewModel.favoriteMoment() + } } - } - }, - momentEntity = viewModel.moment - ) + }, + momentEntity = viewModel.moment + ) + } + } ) { it @@ -220,96 +267,119 @@ fun PostScreen( .background(Color.White) ) { StatusBarSpacer() - Header( - avatar = viewModel.avatar, - nickname = viewModel.nickname, - userId = viewModel.moment?.authorId, - isFollowing = viewModel.accountProfileEntity?.isFollowing ?: false, - onFollowClick = { - scope.launch { - if (viewModel.accountProfileEntity?.isFollowing == true) { - viewModel.unfollowUser() - } else { - viewModel.followUser() - } - } - }, - onDeleteClick = { - viewModel.deleteMoment { - navController.popBackStack() - } + if (viewModel.isError) { + Box( + modifier = Modifier.fillMaxWidth().padding(16.dp) + ) { + NoticeScreenHeader("Post", moreIcon = false) } - ) - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - item { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(383f / 527f) - ) { - PostImageView( - viewModel.moment?.images ?: emptyList() + + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = "Umm, post are not found.", + style = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 16.sp ) - - } - PostDetails( - viewModel.moment - ) - Spacer(modifier = Modifier.height(16.dp)) - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .height(1.dp) - .background(Color(0xFFF7F7F7)) - ) { - - } - Spacer(modifier = Modifier.height(24.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource( - R.string.comment_count, - (viewModel.moment?.commentCount ?: 0) - ), fontSize = 14.sp - ) - Spacer(modifier = Modifier.weight(1f)) - OrderSelectionComponent() { - commentsViewModel.order = it - viewModel.reloadComment() - } - } - - Spacer(modifier = Modifier.height(16.dp)) - } - item { - CommentContent( - viewModel = commentsViewModel, - onLongClick = { - showCommentMenu = true - contextComment = it - }, - onReply = { parentComment, _, _, _ -> - replyComment = parentComment - showCommentModal = true - } ) } - item { - Spacer(modifier = Modifier.height(120.dp)) - } + }else{ + Header( + avatar = viewModel.avatar, + nickname = viewModel.nickname, + userId = viewModel.moment?.authorId, + isFollowing = viewModel.moment?.followStatus == true, + onFollowClick = { + scope.launch { + if (viewModel.moment?.followStatus == true) { + viewModel.unfollowUser() + } else { + viewModel.followUser() + } + } + }, + onDeleteClick = { + viewModel.deleteMoment { + navController.popBackStack() + } + } + ) + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(383f / 527f) + ) { + PostImageView( + viewModel.moment?.images ?: emptyList() + ) + } + PostDetails( + viewModel.moment + ) + Spacer(modifier = Modifier.height(16.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .height(1.dp) + .background(Color(0xFFF7F7F7)) + ) { + + } + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource( + R.string.comment_count, + (viewModel.moment?.commentCount ?: 0) + ), fontSize = 14.sp + ) + Spacer(modifier = Modifier.weight(1f)) + OrderSelectionComponent() { + commentsViewModel.order = it + viewModel.reloadComment() + } + } + + Spacer(modifier = Modifier.height(16.dp)) + } + item { + CommentContent( + viewModel = commentsViewModel, + onLongClick = { comment -> + showCommentMenu = true + contextComment = comment + }, + onReply = { parentComment, _, _, _ -> + replyComment = parentComment + showCommentModal = true + } + ) + } + item { + Spacer(modifier = Modifier.height(120.dp)) + } + + } } + } } @@ -327,41 +397,43 @@ fun CommentContent( } for (item in addedTopLevelComment) { - Box( - modifier = Modifier.padding(horizontal = 16.dp) + AnimatedVisibility( + visible = true, + enter = fadeIn() + slideInVertically() ) { - CommentItem( - item, - onLike = { comment -> - viewModel.viewModelScope.launch { - if (comment.liked) { - viewModel.unlikeComment(comment.id) - } else { - viewModel.likeComment(comment.id) + Box( + modifier = Modifier.padding(horizontal = 16.dp) + ) { + CommentItem( + item, + onLike = { comment -> + viewModel.viewModelScope.launch { + if (comment.liked) { + viewModel.unlikeComment(comment.id) + } else { + viewModel.likeComment(comment.id) + } } - } - }, - onLongClick = { - if (AppState.UserId != item.id) { - return@CommentItem - } - onLongClick(item) - }, - onReply = { parentComment, _, _, _ -> - onReply( - parentComment, - parentComment.author, - parentComment.name, - parentComment.avatar - ) - }, - onLoadMoreSubComments = { - viewModel.viewModelScope.launch { - viewModel.loadMoreSubComments(it.id) - } - }, - addedCommentList = viewModel.addedCommentList - ) + }, + onLongClick = { comment -> + onLongClick(comment) + }, + onReply = { parentComment, _, _, _ -> + onReply( + parentComment, + parentComment.author, + parentComment.name, + parentComment.avatar + ) + }, + onLoadMoreSubComments = { + viewModel.viewModelScope.launch { + viewModel.loadMoreSubComments(it.id) + } + }, + addedCommentList = viewModel.addedCommentList + ) + } } } @@ -381,11 +453,8 @@ fun CommentContent( } } }, - onLongClick = { - if (AppState.UserId != item.id) { - return@CommentItem - } - onLongClick(item) + onLongClick = { comment -> + onLongClick(comment) }, onReply = { parentComment, _, _, _ -> onReply( @@ -404,6 +473,67 @@ fun CommentContent( ) } } + if (commentsPagging.loadState.refresh is LoadState.Loading) { + Box( + modifier = Modifier.fillMaxSize().height(120.dp), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + LinearProgressIndicator( + modifier = Modifier.width(160.dp), + color = Color(0xFFDA3832) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Loading...", + fontSize = 14.sp + ) + } + + } + return + } + if (commentsPagging.loadState.append is LoadState.Loading) { + Box( + modifier = Modifier.fillMaxSize().height(64.dp), + contentAlignment = Alignment.Center + ) { + LinearProgressIndicator( + modifier = Modifier.width(160.dp), + color = Color(0xFFDA3832) + ) + } + } + if (commentsPagging.loadState.refresh is LoadState.Error) { + Box( + modifier = Modifier.fillMaxSize().height(120.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Failed to load comments,click to retry", + fontSize = 14.sp, + modifier = Modifier.noRippleClickable { + viewModel.reloadComment() + } + ) + } + } + if (commentsPagging.loadState.append is LoadState.Error) { + Box( + modifier = Modifier.fillMaxSize().height(64.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Failed to load more comments,click to retry", + fontSize = 14.sp, + modifier = Modifier.noRippleClickable { + commentsPagging.retry() + } + ) + } + } } @@ -499,9 +629,10 @@ fun Header( contentAlignment = Alignment.Center ) { Image( - modifier = Modifier.height(18.dp), + modifier = Modifier.height(18.dp).width(80.dp), painter = painterResource(id = R.drawable.follow_bg), - contentDescription = "" + contentDescription = "", + contentScale = ContentScale.FillWidth ) Text( text = if (isFollowing) stringResource(R.string.following_upper) else stringResource( @@ -581,7 +712,7 @@ fun PostImageView( .fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { - if(images.size > 1){ + if (images.size > 1) { images.forEachIndexed { index, _ -> Box( modifier = Modifier @@ -626,47 +757,28 @@ fun PostDetails( } } +@OptIn(ExperimentalFoundationApi::class) @Composable -fun CommentsSection( - lazyPagingItems: LazyPagingItems, - scrollState: LazyListState = rememberLazyListState(), - onLike: (CommentEntity) -> Unit, - onLongClick: (CommentEntity) -> Unit, - onWillCollapse: (Boolean) -> Unit, +fun LongPressClickableText( + text: AnnotatedString, + onClick: (Int) -> Unit, + onLongClick: () -> Unit, + style: TextStyle = TextStyle.Default ) { - LazyColumn( - state = scrollState, modifier = Modifier - .fillMaxHeight() - .padding(start = 16.dp, end = 16.dp) - ) { - items(lazyPagingItems.itemCount) { idx -> - val item = lazyPagingItems[idx] ?: return@items - CommentItem( - item, - onLike = { - onLike(item) + ClickableText( + text = text, + onClick = onClick, + style = style, + modifier = Modifier.pointerInput(Unit) { + detectTapGestures( + onLongPress = { + onLongClick() }, - onLongClick = { - onLongClick(item) - } ) } - } - - // Detect scroll direction and update showCollapseContent - val coroutineScope = rememberCoroutineScope() - LaunchedEffect(scrollState) { - coroutineScope.launch { - snapshotFlow { scrollState.firstVisibleItemScrollOffset } - .collect { offset -> - Log.d("scroll", "offset: $offset") - onWillCollapse(offset == 0) - } - } - } + ) } - @OptIn(ExperimentalFoundationApi::class) @Composable fun CommentItem( @@ -680,7 +792,7 @@ fun CommentItem( replyUserAvatar: String? ) -> Unit = { _, _, _, _ -> }, onLoadMoreSubComments: ((CommentEntity) -> Unit)? = {}, - onLongClick: () -> Unit = {}, + onLongClick: (CommentEntity) -> Unit = {}, addedCommentList: List = emptyList() ) { val context = LocalContext.current @@ -688,12 +800,6 @@ fun CommentItem( Column( modifier = Modifier .fillMaxWidth() - .combinedClickable( - indication = null, - interactionSource = remember { MutableInteractionSource() }, - onClick = {}, - onLongClick = onLongClick - ) ) { Row(modifier = Modifier.padding(vertical = 8.dp)) { Box( @@ -719,7 +825,15 @@ fun CommentItem( } Spacer(modifier = Modifier.width(8.dp)) Column( - modifier = Modifier.weight(1f) + modifier = Modifier + .weight(1f) + .combinedClickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onLongClick = { + onLongClick(commentEntity) + } + ) {} ) { Text(text = commentEntity.name, fontWeight = FontWeight.W600, fontSize = 14.sp) Row { @@ -741,33 +855,49 @@ fun CommentItem( pop() } append(" ${commentEntity.comment}") + } - ClickableText( - text = annotatedText, - onClick = { offset -> - annotatedText.getStringAnnotations( - tag = "replyUser", - start = offset, - end = offset - ).firstOrNull()?.let { - navController.navigate( - NavigationRoute.AccountProfile.route.replace( - "{id}", - it.item + Box { + CustomClickableText( + text = annotatedText, + onClick = { offset -> + annotatedText.getStringAnnotations( + tag = "replyUser", + start = offset, + end = offset + ).firstOrNull()?.let { + navController.navigate( + NavigationRoute.AccountProfile.route.replace( + "{id}", + it.item + ) ) - ) - } - }, - style = TextStyle(fontSize = 14.sp), - maxLines = Int.MAX_VALUE, - softWrap = true - ) + } + }, + style = TextStyle(fontSize = 14.sp), + onLongPress = { + onLongClick(commentEntity) + }, + ) + } + } else { Text( text = commentEntity.comment, fontSize = 14.sp, maxLines = Int.MAX_VALUE, - softWrap = true + softWrap = true, + modifier = Modifier.combinedClickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onLongClick = { + onLongClick( + commentEntity + ) + }, + ) { + + } ) } } @@ -832,7 +962,9 @@ fun CommentItem( isChild = true, onLike = onLike, onReply = onReply, - onLongClick = onLongClick + onLongClick = { comment -> + onLongClick(comment) + } ) } if (commentEntity.replyCount > 0 && !isChild && commentEntity.reply.size < commentEntity.replyCount) { @@ -851,7 +983,6 @@ fun CommentItem( } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PostBottomBar( onCreateCommentClick: () -> Unit = {}, @@ -956,12 +1087,16 @@ fun PostMenuModal( onDeleteClick() } ) { - Image(painter = painterResource(id = R.drawable.rider_pro_moment_delete), contentDescription = "",modifier = Modifier.size(24.dp)) + Image( + painter = painterResource(id = R.drawable.rider_pro_moment_delete), + contentDescription = "", + modifier = Modifier.size(24.dp) + ) } Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Delete", + text = stringResource(R.string.delete), fontSize = 11.sp, fontWeight = FontWeight.Bold ) @@ -971,43 +1106,165 @@ fun PostMenuModal( } @Composable -fun CommentMenuModal( - onDeleteClick: () -> Unit = {} +fun MenuActionItem( + icon: Int? = null, + text: String, + content: @Composable() (() -> Unit)? = null, + onClick: () -> Unit ) { Column( - modifier = Modifier - .fillMaxWidth() - .height(160.dp) - .padding(vertical = 47.dp, horizontal = 20.dp) + modifier = Modifier, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally ) { - Row( + Box( modifier = Modifier - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier, - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Box( - modifier = Modifier - .clip(CircleShape) - .noRippleClickable { - onDeleteClick() - } - ) { - Image(painter = painterResource(id = R.drawable.rider_pro_moment_delete), contentDescription = "",modifier = Modifier.size(24.dp)) + .clip(CircleShape) + .noRippleClickable { + onClick() } - - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "Delete", - fontSize = 11.sp, - fontWeight = FontWeight.Bold + ) { + content?.invoke() + if (icon != null) { + Icon( + painter = painterResource(id = icon), + contentDescription = "", + modifier = Modifier.size(24.dp), + tint = Color.Black ) } } + + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = text, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + } +} + +/** + * 评论菜单弹窗 + */ +@Composable +fun CommentMenuModal( + onDeleteClick: () -> Unit = {}, + commentEntity: CommentEntity? = null, + onCloseClick: () -> Unit = {}, + onLikeClick: () -> Unit = {}, + onReplyClick: () -> Unit = {}, + isSelf: Boolean = false +) { + val clipboard = LocalClipboardManager.current + + fun copyToClipboard() { + commentEntity?.let { + clipboard.setText( + AnnotatedString( + text = it.comment, + ) + ) + } + } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp, horizontal = 20.dp) + ) { + Text(stringResource(R.string.comment), fontSize = 18.sp, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(24.dp)) + commentEntity?.let { + Column( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xffeeeeee)) + + .padding(8.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + + ) { + Box( + modifier = Modifier + .size(24.dp) + .clip(CircleShape) + ) { + CustomAsyncImage( + imageUrl = it.avatar, + modifier = Modifier.fillMaxSize(), + contentDescription = "Avatar", + ) + } + Spacer(modifier = Modifier.width(8.dp)) + androidx.compose.material.Text( + it.name, + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) + } + Spacer(modifier = Modifier.height(4.dp)) + androidx.compose.material.Text( + it.comment, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(start = 32.dp), + overflow = TextOverflow.Ellipsis + ) + } + Spacer(modifier = Modifier.height(32.dp)) + } + + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + if (isSelf) { + MenuActionItem( + icon = R.drawable.rider_pro_moment_delete, + text = stringResource(R.string.delete) + ) { + onDeleteClick() + } + + Spacer(modifier = Modifier.width(48.dp)) + } + MenuActionItem( + icon = R.drawable.rider_pro_copy, + text = stringResource(R.string.copy) + ) { + copyToClipboard() + onCloseClick() + } + commentEntity?.let { + Spacer(modifier = Modifier.width(48.dp)) + MenuActionItem( + text = "Like", + content = { + AnimatedLikeIcon( + liked = it.liked, + onClick = onLikeClick, + modifier = Modifier.size(24.dp) + ) + } + ) { + onCloseClick() + } + } + if (!isSelf) { + Spacer(modifier = Modifier.width(48.dp)) + MenuActionItem( + icon = R.drawable.rider_pro_comment, + text = "Reply" + ) { + onReplyClick() + } + } + } + Spacer(modifier = Modifier.height(48.dp)) } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt index 4dd3eb0..1f537de 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt @@ -28,24 +28,20 @@ class PostViewModel( var moment by mutableStateOf(null) var accountService: AccountService = AccountServiceImpl() var commentsViewModel: CommentsViewModel = CommentsViewModel(postId) + var isError by mutableStateOf(false) - /** - * 预加载,在跳转到 PostScreen 之前设置好内容 - */ - fun preTransit(momentEntity: MomentEntity?) { - this.moment = momentEntity - this.nickname = momentEntity?.nickname ?: "" - this.commentsViewModel = CommentsViewModel(postId) - commentsViewModel.preTransit() - } fun reloadComment() { commentsViewModel.reloadComment() } suspend fun initData() { - moment = service.getMomentById(postId.toInt()) -// accountProfileEntity = userService.getUserProfile(moment?.authorId.toString()) + try { + moment = service.getMomentById(postId.toInt()) + } catch (e: Exception) { + isError = true + return + } commentsViewModel.reloadComment() } @@ -106,16 +102,16 @@ class PostViewModel( } suspend fun followUser() { - accountProfileEntity?.let { - userService.followUser(it.id.toString()) - accountProfileEntity = accountProfileEntity?.copy(isFollowing = true) + moment?.let { + userService.followUser(it.authorId.toString()) + moment = moment?.copy(followStatus = true) } } suspend fun unfollowUser() { - accountProfileEntity?.let { - userService.unFollowUser(it.id.toString()) - accountProfileEntity = accountProfileEntity?.copy(isFollowing = false) + moment?.let { + userService.unFollowUser(it.authorId.toString()) + moment = moment?.copy(followStatus = false) } } diff --git a/app/src/main/res/drawable/rider_pro_copy.xml b/app/src/main/res/drawable/rider_pro_copy.xml new file mode 100644 index 0000000..fb84e00 --- /dev/null +++ b/app/src/main/res/drawable/rider_pro_copy.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 2c7e47f..2619e37 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -57,4 +57,8 @@ 默认 最新 最早 + 下载 + 原始图片 + 收藏 + 删除 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e790b6..c666a81 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -56,4 +56,9 @@ Default Latest Earliest + Download + Original + Favourite + Delete + Copy \ No newline at end of file