更新代码
This commit is contained in:
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -155,7 +155,8 @@ class CommentRemoteDataSource(
|
||||
postUser: Int?,
|
||||
selfNotice: Boolean?,
|
||||
order: String?,
|
||||
parentCommentId: Int?
|
||||
parentCommentId: Int?,
|
||||
pageSize: Int? = 20
|
||||
): ListContainer<CommentEntity> {
|
||||
return commentService.getComments(
|
||||
pageNumber,
|
||||
@@ -163,7 +164,8 @@ class CommentRemoteDataSource(
|
||||
postUser = postUser,
|
||||
selfNotice = selfNotice,
|
||||
order = order,
|
||||
parentCommentId = parentCommentId
|
||||
parentCommentId = parentCommentId,
|
||||
pageSize = pageSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<TextLayoutResult?>(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)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CommentEntity?>(null) }
|
||||
var replyComment by remember { mutableStateOf<CommentEntity?>(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<CommentEntity>,
|
||||
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<CommentEntity> = 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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,24 +28,20 @@ class PostViewModel(
|
||||
var moment by mutableStateOf<MomentEntity?>(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
app/src/main/res/drawable/rider_pro_copy.xml
Normal file
5
app/src/main/res/drawable/rider_pro_copy.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.77" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M15,20H5V7c0,-0.55 -0.45,-1 -1,-1h0C3.45,6 3,6.45 3,7v13c0,1.1 0.9,2 2,2h10c0.55,0 1,-0.45 1,-1v0C16,20.45 15.55,20 15,20zM20,16V4c0,-1.1 -0.9,-2 -2,-2H9C7.9,2 7,2.9 7,4v12c0,1.1 0.9,2 2,2h9C19.1,18 20,17.1 20,16zM18,16H9V4h9V16z"/>
|
||||
|
||||
</vector>
|
||||
@@ -57,4 +57,8 @@
|
||||
<string name="order_comment_default">默认</string>
|
||||
<string name="order_comment_latest">最新</string>
|
||||
<string name="order_comment_earliest">最早</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="original">原始图片</string>
|
||||
<string name="favourites">收藏</string>
|
||||
<string name="delete">删除</string>
|
||||
</resources>
|
||||
@@ -56,4 +56,9 @@
|
||||
<string name="order_comment_default">Default</string>
|
||||
<string name="order_comment_latest">Latest</string>
|
||||
<string name="order_comment_earliest">Earliest</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="original">Original</string>
|
||||
<string name="favourites">Favourite</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="copy">Copy</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user