Refactor: Add debounce for navigation and optimize comments loading
- Implemented debounced navigation to prevent multiple rapid navigations. - Replaced Pager-based comment loading with a simpler list-based approach for improved performance and reduced complexity. - Added loading and error states for comment fetching. - Introduced `debouncedClickable` modifier for handling click events with debounce. - Updated image viewer to use simple navigation arrows instead of HorizontalPager for better user experience. - Added a new string resource for password length error.
This commit is contained in:
@@ -0,0 +1,163 @@
|
|||||||
|
package com.aiosman.ravenow.ui.composables
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
|
import androidx.compose.ui.platform.debugInspectorInfo
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防抖点击修饰符
|
||||||
|
* @param enabled 是否启用点击
|
||||||
|
* @param debounceTime 防抖时间(毫秒),默认500ms
|
||||||
|
* @param onClick 点击回调
|
||||||
|
*/
|
||||||
|
fun Modifier.debouncedClickable(
|
||||||
|
enabled: Boolean = true,
|
||||||
|
debounceTime: Long = 500L,
|
||||||
|
onClick: () -> Unit
|
||||||
|
): Modifier = composed(
|
||||||
|
inspectorInfo = debugInspectorInfo {
|
||||||
|
name = "debouncedClickable"
|
||||||
|
properties["enabled"] = enabled
|
||||||
|
properties["debounceTime"] = debounceTime
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
var isClickable by remember { mutableStateOf(true) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
clickable(
|
||||||
|
enabled = enabled && isClickable,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null
|
||||||
|
) {
|
||||||
|
if (isClickable) {
|
||||||
|
isClickable = false
|
||||||
|
onClick()
|
||||||
|
scope.launch {
|
||||||
|
delay(debounceTime)
|
||||||
|
isClickable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防抖点击修饰符(带涟漪效果)
|
||||||
|
* @param enabled 是否启用点击
|
||||||
|
* @param debounceTime 防抖时间(毫秒),默认500ms
|
||||||
|
* @param onClick 点击回调
|
||||||
|
*/
|
||||||
|
fun Modifier.debouncedClickableWithRipple(
|
||||||
|
enabled: Boolean = true,
|
||||||
|
debounceTime: Long = 500L,
|
||||||
|
onClick: () -> Unit
|
||||||
|
): Modifier = composed(
|
||||||
|
inspectorInfo = debugInspectorInfo {
|
||||||
|
name = "debouncedClickableWithRipple"
|
||||||
|
properties["enabled"] = enabled
|
||||||
|
properties["debounceTime"] = debounceTime
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
var isClickable by remember { mutableStateOf(true) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
clickable(
|
||||||
|
enabled = enabled && isClickable,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = androidx.compose.material.ripple.rememberRipple()
|
||||||
|
) {
|
||||||
|
if (isClickable) {
|
||||||
|
isClickable = false
|
||||||
|
onClick()
|
||||||
|
scope.launch {
|
||||||
|
delay(debounceTime)
|
||||||
|
isClickable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用防抖处理器
|
||||||
|
* 可以用于任何需要防抖的场景
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberDebouncer(debounceTime: Long = 500L): ((() -> Unit) -> Unit) {
|
||||||
|
var isExecuting by remember { mutableStateOf(false) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
return remember {
|
||||||
|
{ action ->
|
||||||
|
if (!isExecuting) {
|
||||||
|
isExecuting = true
|
||||||
|
action()
|
||||||
|
scope.launch {
|
||||||
|
delay(debounceTime)
|
||||||
|
isExecuting = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防抖状态管理器
|
||||||
|
* 可以手动控制防抖状态
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberDebouncedState(
|
||||||
|
debounceTime: Long = 500L
|
||||||
|
): Triple<Boolean, () -> Unit, () -> Unit> {
|
||||||
|
var isDebouncing by remember { mutableStateOf(false) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val startDebounce: () -> Unit = remember {
|
||||||
|
{
|
||||||
|
isDebouncing = true
|
||||||
|
scope.launch {
|
||||||
|
delay(debounceTime)
|
||||||
|
isDebouncing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val reset = remember {
|
||||||
|
{
|
||||||
|
isDebouncing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Triple(isDebouncing, startDebounce, reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防抖的导航处理器
|
||||||
|
* 专门用于导航操作的防抖
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberDebouncedNavigation(
|
||||||
|
debounceTime: Long = 1000L
|
||||||
|
): ((() -> Unit) -> Unit) {
|
||||||
|
var isNavigating by remember { mutableStateOf(false) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
return remember {
|
||||||
|
{ navigation ->
|
||||||
|
if (!isNavigating) {
|
||||||
|
isNavigating = true
|
||||||
|
try {
|
||||||
|
navigation()
|
||||||
|
} finally {
|
||||||
|
scope.launch {
|
||||||
|
delay(debounceTime)
|
||||||
|
isNavigating = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,48 +5,43 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.Pager
|
|
||||||
import androidx.paging.PagingConfig
|
|
||||||
import androidx.paging.PagingData
|
|
||||||
import androidx.paging.cachedIn
|
|
||||||
import androidx.paging.filter
|
|
||||||
import androidx.paging.map
|
|
||||||
import com.aiosman.ravenow.data.CommentRemoteDataSource
|
|
||||||
import com.aiosman.ravenow.data.CommentService
|
import com.aiosman.ravenow.data.CommentService
|
||||||
import com.aiosman.ravenow.data.CommentServiceImpl
|
import com.aiosman.ravenow.data.CommentServiceImpl
|
||||||
import com.aiosman.ravenow.entity.CommentEntity
|
import com.aiosman.ravenow.entity.CommentEntity
|
||||||
import com.aiosman.ravenow.entity.CommentPagingSource
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class CommentsViewModel(
|
class CommentsViewModel(
|
||||||
var postId: String = 0.toString(),
|
var postId: String = 0.toString(),
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
var commentService: CommentService = CommentServiceImpl()
|
var commentService: CommentService = CommentServiceImpl()
|
||||||
private var _commentsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
|
var commentsList by mutableStateOf<List<CommentEntity>>(emptyList())
|
||||||
val commentsFlow = _commentsFlow.asStateFlow()
|
|
||||||
var order: String by mutableStateOf("like")
|
var order: String by mutableStateOf("like")
|
||||||
var addedCommentList by mutableStateOf<List<CommentEntity>>(emptyList())
|
var addedCommentList by mutableStateOf<List<CommentEntity>>(emptyList())
|
||||||
var subCommentLoadingMap by mutableStateOf(mutableMapOf<Int, Boolean>())
|
var subCommentLoadingMap by mutableStateOf(mutableMapOf<Int, Boolean>())
|
||||||
var highlightCommentId by mutableStateOf<Int?>(null)
|
var highlightCommentId by mutableStateOf<Int?>(null)
|
||||||
var highlightComment by mutableStateOf<CommentEntity?>(null)
|
var highlightComment by mutableStateOf<CommentEntity?>(null)
|
||||||
|
var isLoading by mutableStateOf(false)
|
||||||
|
var hasError by mutableStateOf(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预加载,在跳转到 PostScreen 之前设置好内容
|
* 预加载,在跳转到 PostScreen 之前设置好内容
|
||||||
*/
|
*/
|
||||||
fun preTransit() {
|
fun preTransit() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
Pager(config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
try {
|
||||||
pagingSourceFactory = {
|
isLoading = true
|
||||||
CommentPagingSource(
|
val response = commentService.getComments(
|
||||||
CommentRemoteDataSource(commentService),
|
pageNumber = 1,
|
||||||
postId = postId.toInt()
|
postId = postId.toInt(),
|
||||||
)
|
pageSize = 10
|
||||||
}).flow.cachedIn(viewModelScope).collectLatest {
|
)
|
||||||
_commentsFlow.value = it
|
commentsList = response.list
|
||||||
|
hasError = false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
hasError = true
|
||||||
|
} finally {
|
||||||
|
isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,18 +52,20 @@ class CommentsViewModel(
|
|||||||
fun reloadComment() {
|
fun reloadComment() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
Pager(config = PagingConfig(pageSize = 20, enablePlaceholders = false),
|
isLoading = true
|
||||||
pagingSourceFactory = {
|
val response = commentService.getComments(
|
||||||
CommentPagingSource(
|
pageNumber = 1,
|
||||||
CommentRemoteDataSource(commentService),
|
postId = postId.toInt(),
|
||||||
postId = postId.toInt(),
|
order = order,
|
||||||
order = order
|
pageSize = 50
|
||||||
)
|
)
|
||||||
}).flow.cachedIn(viewModelScope).collectLatest {
|
commentsList = response.list
|
||||||
_commentsFlow.value = it
|
hasError = false
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
hasError = true
|
||||||
|
} finally {
|
||||||
|
isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,8 +125,7 @@ class CommentsViewModel(
|
|||||||
* 更新评论点赞状态
|
* 更新评论点赞状态
|
||||||
*/
|
*/
|
||||||
private fun updateCommentLike(commentId: Int, isLike: Boolean) {
|
private fun updateCommentLike(commentId: Int, isLike: Boolean) {
|
||||||
val currentPagingData = commentsFlow.value
|
commentsList = commentsList.map { comment ->
|
||||||
val updatedPagingData = currentPagingData.map { comment ->
|
|
||||||
if (comment.id == commentId) {
|
if (comment.id == commentId) {
|
||||||
comment.copy(
|
comment.copy(
|
||||||
liked = isLike,
|
liked = isLike,
|
||||||
@@ -149,7 +145,6 @@ class CommentsViewModel(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_commentsFlow.value = updatedPagingData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用于防止重复点赞的状态集合
|
// 用于防止重复点赞的状态集合
|
||||||
@@ -270,9 +265,7 @@ class CommentsViewModel(
|
|||||||
if (addedCommentList.any { it.id == commentId }) {
|
if (addedCommentList.any { it.id == commentId }) {
|
||||||
addedCommentList = addedCommentList.filter { it.id != commentId }
|
addedCommentList = addedCommentList.filter { it.id != commentId }
|
||||||
} else {
|
} else {
|
||||||
val currentPagingData = commentsFlow.value
|
commentsList = commentsList.filter { it.id != commentId }
|
||||||
val updatedPagingData = currentPagingData.filter { it.id != commentId }
|
|
||||||
_commentsFlow.value = updatedPagingData
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,8 +290,7 @@ class CommentsViewModel(
|
|||||||
} else {
|
} else {
|
||||||
// 普通评论
|
// 普通评论
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val currentPagingData = commentsFlow.value
|
commentsList = commentsList.map { comment ->
|
||||||
val updatedPagingData = currentPagingData.map { comment ->
|
|
||||||
if (comment.id == commentId) {
|
if (comment.id == commentId) {
|
||||||
try {
|
try {
|
||||||
subCommentLoadingMap[commentId] = true
|
subCommentLoadingMap[commentId] = true
|
||||||
@@ -321,7 +313,6 @@ class CommentsViewModel(
|
|||||||
}
|
}
|
||||||
comment
|
comment
|
||||||
}
|
}
|
||||||
_commentsFlow.value = updatedPagingData
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
|
||||||
import androidx.compose.foundation.pager.PagerDefaults
|
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -87,8 +84,6 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.paging.LoadState
|
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.ConstVars
|
import com.aiosman.ravenow.ConstVars
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||||
@@ -119,6 +114,8 @@ import com.aiosman.ravenow.ui.composables.EditCommentBottomModal
|
|||||||
import com.aiosman.ravenow.ui.composables.FollowButton
|
import com.aiosman.ravenow.ui.composables.FollowButton
|
||||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
import com.aiosman.ravenow.ui.composables.debouncedClickable
|
||||||
|
import com.aiosman.ravenow.ui.composables.rememberDebouncedNavigation
|
||||||
import com.aiosman.ravenow.utils.FileUtil.saveImageToGallery
|
import com.aiosman.ravenow.utils.FileUtil.saveImageToGallery
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -147,6 +144,7 @@ fun PostScreen(
|
|||||||
val commentsViewModel = viewModel.commentsViewModel
|
val commentsViewModel = viewModel.commentsViewModel
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
val debouncedNavigation = rememberDebouncedNavigation()
|
||||||
var showCommentMenu by remember { mutableStateOf(false) }
|
var showCommentMenu by remember { mutableStateOf(false) }
|
||||||
var contextComment by remember { mutableStateOf<CommentEntity?>(null) }
|
var contextComment by remember { mutableStateOf<CommentEntity?>(null) }
|
||||||
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
|
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
|
||||||
@@ -202,7 +200,13 @@ fun PostScreen(
|
|||||||
commentModalState.hide()
|
commentModalState.hide()
|
||||||
showCommentMenu = false
|
showCommentMenu = false
|
||||||
}
|
}
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
commentModalState.hide()
|
commentModalState.hide()
|
||||||
@@ -228,7 +232,13 @@ fun PostScreen(
|
|||||||
commentModalState.hide()
|
commentModalState.hide()
|
||||||
showCommentMenu = false
|
showCommentMenu = false
|
||||||
}
|
}
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
commentModalState.hide()
|
commentModalState.hide()
|
||||||
@@ -316,7 +326,13 @@ fun PostScreen(
|
|||||||
onLikeClick = {
|
onLikeClick = {
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (viewModel.moment?.liked == true) {
|
if (viewModel.moment?.liked == true) {
|
||||||
@@ -330,7 +346,13 @@ fun PostScreen(
|
|||||||
onCreateCommentClick = {
|
onCreateCommentClick = {
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
replyComment = null
|
replyComment = null
|
||||||
showCommentModal = true
|
showCommentModal = true
|
||||||
@@ -339,7 +361,13 @@ fun PostScreen(
|
|||||||
onFavoriteClick = {
|
onFavoriteClick = {
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (viewModel.moment?.isFavorite == true) {
|
if (viewModel.moment?.isFavorite == true) {
|
||||||
@@ -394,7 +422,13 @@ fun PostScreen(
|
|||||||
onFollowClick = {
|
onFollowClick = {
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (viewModel.moment?.followStatus == true) {
|
if (viewModel.moment?.followStatus == true) {
|
||||||
@@ -407,13 +441,21 @@ fun PostScreen(
|
|||||||
},
|
},
|
||||||
onDeleteClick = {
|
onDeleteClick = {
|
||||||
viewModel.deleteMoment {
|
viewModel.deleteMoment {
|
||||||
navController.navigateUp()
|
debouncedNavigation {
|
||||||
|
navController.navigateUp()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onReportClick = {
|
onReportClick = {
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.REPORT_CONTENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.REPORT_CONTENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showReportDialog = true
|
showReportDialog = true
|
||||||
}
|
}
|
||||||
@@ -478,7 +520,13 @@ fun PostScreen(
|
|||||||
onReply = { parentComment, _, _, _ ->
|
onReply = { parentComment, _, _, _ ->
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
replyComment = parentComment
|
replyComment = parentComment
|
||||||
showCommentModal = true
|
showCommentModal = true
|
||||||
@@ -506,8 +554,9 @@ fun CommentContent(
|
|||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
val debouncedNavigation = rememberDebouncedNavigation()
|
||||||
|
|
||||||
val commentsPagging = viewModel.commentsFlow.collectAsLazyPagingItems()
|
val commentsList = viewModel.commentsList
|
||||||
val addedTopLevelComment = viewModel.addedCommentList.filter {
|
val addedTopLevelComment = viewModel.addedCommentList.filter {
|
||||||
it.parentCommentId == null
|
it.parentCommentId == null
|
||||||
}
|
}
|
||||||
@@ -522,7 +571,13 @@ fun CommentContent(
|
|||||||
onLike = { comment ->
|
onLike = { comment ->
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 使用防抖机制避免重复点击
|
// 使用防抖机制避免重复点击
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
@@ -540,7 +595,13 @@ fun CommentContent(
|
|||||||
onReply = { parentComment, _, _, _ ->
|
onReply = { parentComment, _, _, _ ->
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onReply(
|
onReply(
|
||||||
parentComment,
|
parentComment,
|
||||||
@@ -577,7 +638,13 @@ fun CommentContent(
|
|||||||
onLike = { comment ->
|
onLike = { comment ->
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 防抖机制已在ViewModel中实现
|
// 防抖机制已在ViewModel中实现
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
@@ -612,8 +679,7 @@ fun CommentContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (idx in 0 until commentsPagging.itemCount) {
|
commentsList.forEach { item ->
|
||||||
val item = commentsPagging[idx] ?: return
|
|
||||||
if (
|
if (
|
||||||
item.id != viewModel.highlightCommentId &&
|
item.id != viewModel.highlightCommentId &&
|
||||||
viewModel.addedCommentList.none { it.id == item.id }
|
viewModel.addedCommentList.none { it.id == item.id }
|
||||||
@@ -631,7 +697,13 @@ fun CommentContent(
|
|||||||
onLike = { comment ->
|
onLike = { comment ->
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 防抖机制已在ViewModel中实现
|
// 防抖机制已在ViewModel中实现
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
@@ -649,7 +721,13 @@ fun CommentContent(
|
|||||||
onReply = { parentComment, _, _, _ ->
|
onReply = { parentComment, _, _, _ ->
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onReply(
|
onReply(
|
||||||
parentComment,
|
parentComment,
|
||||||
@@ -669,47 +747,35 @@ fun CommentContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle loading and error states as before
|
// 加载状态处理
|
||||||
// if (commentsPagging.loadState.refresh is LoadState.Loading) {
|
if (viewModel.isLoading) {
|
||||||
// Box(
|
Box(
|
||||||
// modifier = Modifier
|
modifier = Modifier
|
||||||
// .fillMaxSize()
|
.fillMaxSize()
|
||||||
// .height(120.dp),
|
.height(120.dp),
|
||||||
// contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
// ) {
|
) {
|
||||||
// Column(
|
Column(
|
||||||
// horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
// ) {
|
) {
|
||||||
// LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
// modifier = Modifier.width(160.dp),
|
modifier = Modifier.width(160.dp),
|
||||||
// color = AppColors.main
|
color = AppColors.main
|
||||||
// )
|
)
|
||||||
// Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
// Text(
|
Text(
|
||||||
// text = "Loading...",
|
text = "Loading...",
|
||||||
// fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
// if (commentsPagging.loadState.append is LoadState.Loading) {
|
|
||||||
// Box(
|
// 错误状态处理
|
||||||
// modifier = Modifier
|
if (viewModel.hasError) {
|
||||||
// .fillMaxSize()
|
|
||||||
// .height(64.dp),
|
|
||||||
// contentAlignment = Alignment.Center
|
|
||||||
// ) {
|
|
||||||
// LinearProgressIndicator(
|
|
||||||
// modifier = Modifier.width(160.dp),
|
|
||||||
// color = AppColors.main
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (commentsPagging.loadState.refresh is LoadState.Error) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -724,25 +790,11 @@ fun CommentContent(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
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()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 评论为空
|
// 评论为空
|
||||||
if (commentsPagging.itemCount == 0 && commentsPagging.loadState.refresh is LoadState.NotLoading && addedTopLevelComment.isEmpty()) {
|
if (commentsList.isEmpty() && addedTopLevelComment.isEmpty()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -791,6 +843,7 @@ fun Header(
|
|||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val debouncedNavigation = rememberDebouncedNavigation()
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
@@ -829,8 +882,10 @@ fun Header(
|
|||||||
painter = painterResource(id = R.drawable.rider_pro_back_icon), // Replace with your image resource
|
painter = painterResource(id = R.drawable.rider_pro_back_icon), // Replace with your image resource
|
||||||
contentDescription = "Back",
|
contentDescription = "Back",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.noRippleClickable {
|
.debouncedClickable {
|
||||||
navController.navigateUp()
|
debouncedNavigation {
|
||||||
|
navController.navigateUp()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.size(24.dp),
|
.size(24.dp),
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
@@ -849,14 +904,16 @@ fun Header(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(32.dp)
|
.size(32.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.noRippleClickable {
|
.debouncedClickable(debounceTime = 1000L) {
|
||||||
userId?.let {
|
userId?.let {
|
||||||
navController.navigate(
|
debouncedNavigation {
|
||||||
NavigationRoute.AccountProfile.route.replace(
|
navController.navigate(
|
||||||
"{id}",
|
NavigationRoute.AccountProfile.route.replace(
|
||||||
userId.toString()
|
"{id}",
|
||||||
|
userId.toString()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
@@ -897,7 +954,7 @@ fun Header(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ImageViewerDialog(
|
fun ImageViewerDialog(
|
||||||
images: List<MomentImageEntity>,
|
images: List<MomentImageEntity>,
|
||||||
@@ -906,11 +963,12 @@ fun ImageViewerDialog(
|
|||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val pagerState = rememberPagerState(pageCount = { images.size }, initialPage = initialPage)
|
var currentPage by remember { mutableStateOf(initialPage) }
|
||||||
val navigationBarPaddings =
|
val navigationBarPaddings =
|
||||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 16.dp
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 16.dp
|
||||||
val showRawImageStates = remember { mutableStateListOf(*Array(images.size) { false }) }
|
val showRawImageStates = remember { mutableStateListOf(*Array(images.size) { false }) }
|
||||||
var isDownloading by remember { mutableStateOf(false) }
|
var isDownloading by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
BasicAlertDialog(
|
BasicAlertDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
@@ -921,8 +979,6 @@ fun ImageViewerDialog(
|
|||||||
dismissOnClickOutside = true,
|
dismissOnClickOutside = true,
|
||||||
usePlatformDefaultWidth = false,
|
usePlatformDefaultWidth = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -930,16 +986,15 @@ fun ImageViewerDialog(
|
|||||||
.background(Color.Black),
|
.background(Color.Black),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
HorizontalPager(
|
Box(
|
||||||
state = pagerState,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(0.8f),
|
.weight(0.8f)
|
||||||
) { page ->
|
) {
|
||||||
val zoomState = rememberZoomState()
|
val zoomState = rememberZoomState()
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context,
|
context,
|
||||||
if (showRawImageStates[page]) images[page].url else images[page].thumbnail,
|
if (showRawImageStates[currentPage]) images[currentPage].url else images[currentPage].thumbnail,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -951,7 +1006,49 @@ fun ImageViewerDialog(
|
|||||||
),
|
),
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Navigation arrows
|
||||||
|
if (images.size > 1) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Left arrow
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
if (currentPage > 0) {
|
||||||
|
currentPage--
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (currentPage > 0) {
|
||||||
|
Text("<", color = Color.White, fontSize = 24.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right arrow
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
if (currentPage < images.size - 1) {
|
||||||
|
currentPage++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (currentPage < images.size - 1) {
|
||||||
|
Text(">", color = Color.White, fontSize = 24.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.padding(top = 10.dp, bottom = 10.dp)) {
|
Box(modifier = Modifier.padding(top = 10.dp, bottom = 10.dp)) {
|
||||||
if (images.size > 1) {
|
if (images.size > 1) {
|
||||||
Box(
|
Box(
|
||||||
@@ -961,7 +1058,7 @@ fun ImageViewerDialog(
|
|||||||
.padding(vertical = 4.dp, horizontal = 24.dp)
|
.padding(vertical = 4.dp, horizontal = 24.dp)
|
||||||
) {
|
) {
|
||||||
androidx.compose.material.Text(
|
androidx.compose.material.Text(
|
||||||
text = "${pagerState.currentPage + 1}/${images.size}",
|
text = "${currentPage + 1}/${images.size}",
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -998,7 +1095,7 @@ fun ImageViewerDialog(
|
|||||||
}
|
}
|
||||||
isDownloading = true
|
isDownloading = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
saveImageToGallery(context, images[pagerState.currentPage].url)
|
saveImageToGallery(context, images[currentPage].url)
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1023,15 +1120,15 @@ fun ImageViewerDialog(
|
|||||||
color = Color.White
|
color = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!showRawImageStates[pagerState.currentPage]) {
|
if (!showRawImageStates[currentPage]) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
if (!showRawImageStates[pagerState.currentPage]) {
|
if (!showRawImageStates[currentPage]) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.noRippleClickable {
|
modifier = Modifier.noRippleClickable {
|
||||||
showRawImageStates[pagerState.currentPage] = true
|
showRawImageStates[currentPage] = true
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
androidx.compose.material.Icon(
|
androidx.compose.material.Icon(
|
||||||
@@ -1053,94 +1150,112 @@ fun ImageViewerDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PostImageView(
|
fun PostImageView(
|
||||||
images: List<MomentImageEntity>,
|
images: List<MomentImageEntity>,
|
||||||
initialPage: Int? = 0
|
initialPage: Int? = 0
|
||||||
) {
|
) {
|
||||||
val pagerState = rememberPagerState(pageCount = { images.size }, initialPage = initialPage ?: 0)
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var isImageViewerDialog by remember { mutableStateOf(false) }
|
var isImageViewerDialog by remember { mutableStateOf(false) }
|
||||||
|
var currentImageIndex by remember { mutableStateOf(initialPage ?: 0) }
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
isImageViewerDialog = false
|
isImageViewerDialog = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isImageViewerDialog) {
|
if (isImageViewerDialog) {
|
||||||
ImageViewerDialog(
|
ImageViewerDialog(
|
||||||
images = images,
|
images = images,
|
||||||
initialPage = pagerState.currentPage
|
initialPage = currentImageIndex
|
||||||
) {
|
) {
|
||||||
isImageViewerDialog = false
|
isImageViewerDialog = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
) {
|
) {
|
||||||
HorizontalPager(
|
if (images.isNotEmpty()) {
|
||||||
state = pagerState,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
detectTapGestures(
|
|
||||||
onTap = {
|
|
||||||
isImageViewerDialog = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.background(Color.Gray.copy(alpha = 0.1f)),
|
|
||||||
flingBehavior = PagerDefaults.flingBehavior(
|
|
||||||
state = pagerState,
|
|
||||||
snapAnimationSpec = spring(
|
|
||||||
dampingRatio = Spring.DampingRatioNoBouncy,
|
|
||||||
stiffness = Spring.StiffnessMedium,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
) { page ->
|
|
||||||
val image = images[page]
|
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context,
|
context,
|
||||||
image.thumbnail,
|
images[currentImageIndex].thumbnail,
|
||||||
contentDescription = "Image",
|
contentDescription = "Image",
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.weight(1f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onTap = {
|
||||||
|
isImageViewerDialog = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.background(Color.Gray.copy(alpha = 0.1f))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicator container
|
// Navigation and Indicator container
|
||||||
Row(
|
if (images.size > 1) {
|
||||||
modifier = Modifier
|
Row(
|
||||||
.padding(8.dp)
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.padding(8.dp)
|
||||||
horizontalArrangement = Arrangement.Center
|
.fillMaxWidth(),
|
||||||
) {
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
if (images.size > 1) {
|
verticalAlignment = Alignment.CenterVertically
|
||||||
images.forEachIndexed { index, _ ->
|
) {
|
||||||
Box(
|
// Previous button
|
||||||
modifier = Modifier
|
Text(
|
||||||
.size(4.dp)
|
text = "Previous",
|
||||||
.clip(CircleShape)
|
modifier = Modifier
|
||||||
.background(
|
.padding(8.dp)
|
||||||
if (pagerState.currentPage == index) Color.Red else Color.Gray.copy(
|
.noRippleClickable {
|
||||||
alpha = 0.5f
|
if (currentImageIndex > 0) {
|
||||||
|
currentImageIndex--
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color = if (currentImageIndex > 0) Color.Blue else Color.Gray
|
||||||
|
)
|
||||||
|
|
||||||
|
// Indicators
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
images.forEachIndexed { index, _ ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(4.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(
|
||||||
|
if (currentImageIndex == index) Color.Red else Color.Gray.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
.padding(4.dp)
|
||||||
.padding(4.dp)
|
)
|
||||||
|
if (index < images.size - 1) {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
)
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Next button
|
||||||
|
Text(
|
||||||
|
text = "Next",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
if (currentImageIndex < images.size - 1) {
|
||||||
|
currentImageIndex++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color = if (currentImageIndex < images.size - 1) Color.Blue else Color.Gray
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1189,6 +1304,7 @@ fun CommentItem(
|
|||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
val debouncedNavigation = rememberDebouncedNavigation()
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -1199,21 +1315,24 @@ fun CommentItem(
|
|||||||
.size(if (isChild) 24.dp else 32.dp)
|
.size(if (isChild) 24.dp else 32.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(Color.Gray.copy(alpha = 0.1f))
|
.background(Color.Gray.copy(alpha = 0.1f))
|
||||||
.noRippleClickable {
|
|
||||||
navController.navigate(
|
|
||||||
NavigationRoute.AccountProfile.route.replace(
|
|
||||||
"{id}",
|
|
||||||
commentEntity.author.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context = context,
|
context = context,
|
||||||
imageUrl = commentEntity.avatar,
|
imageUrl = commentEntity.avatar,
|
||||||
contentDescription = "Comment Profile Picture",
|
contentDescription = "Comment Profile Picture ${commentEntity.name}",
|
||||||
modifier = Modifier.size(if (isChild) 24.dp else 32.dp)
|
modifier = Modifier
|
||||||
.clip(CircleShape),
|
.size(if (isChild) 24.dp else 32.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.debouncedClickable(debounceTime = 1000L) {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(
|
||||||
|
NavigationRoute.AccountProfile.route.replace(
|
||||||
|
"{id}",
|
||||||
|
commentEntity.author.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1273,12 +1392,14 @@ fun CommentItem(
|
|||||||
start = offset,
|
start = offset,
|
||||||
end = offset
|
end = offset
|
||||||
).firstOrNull()?.let {
|
).firstOrNull()?.let {
|
||||||
navController.navigate(
|
debouncedNavigation {
|
||||||
NavigationRoute.AccountProfile.route.replace(
|
navController.navigate(
|
||||||
"{id}",
|
NavigationRoute.AccountProfile.route.replace(
|
||||||
it.item
|
"{id}",
|
||||||
|
it.item
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = TextStyle(fontSize = 14.sp, color = AppColors.text),
|
style = TextStyle(fontSize = 14.sp, color = AppColors.text),
|
||||||
|
|||||||
@@ -189,5 +189,6 @@
|
|||||||
<string name="group_room_enter_fail">加入房间失败</string>
|
<string name="group_room_enter_fail">加入房间失败</string>
|
||||||
<string name="agent_createing">创建中...</string>
|
<string name="agent_createing">创建中...</string>
|
||||||
<string name="agent_find">发现</string>
|
<string name="agent_find">发现</string>
|
||||||
|
<string name="text_error_password_too_long">密码不能超过 %1$d 个字符</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user