修复多个bug、更改评论筛选
-修复搜索/动态/关注界面可以同时点赞/收藏同一个动态,使总点赞/收藏数增加 -修复个人主页上滑时动态/智能体/群聊图标会被遮挡 -评论显示不全,只显示50条内容(现在滑动到第50条评论后会出现加载更多按键) -评论筛选改为全部和最新和热门 -修复评论筛选图标自动发生改变
This commit is contained in:
@@ -410,7 +410,16 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||
fun updateMomentLike(id: Int,isLike:Boolean) {
|
||||
this.list = this.list.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(likeCount = momentItem.likeCount + if (isLike) 1 else -1, liked = isLike)
|
||||
// 只有当状态发生变化时才更新计数,避免重复更新
|
||||
val countDelta = if (momentItem.liked != isLike) {
|
||||
if (isLike) 1 else -1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
momentItem.copy(
|
||||
likeCount = (momentItem.likeCount + countDelta).coerceAtLeast(0),
|
||||
liked = isLike
|
||||
)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
@@ -421,7 +430,16 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||
fun updateFavoriteCount(id: Int,isFavorite:Boolean) {
|
||||
this.list = this.list.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(favoriteCount = momentItem.favoriteCount + if (isFavorite) 1 else -1, isFavorite = isFavorite)
|
||||
// 只有当状态发生变化时才更新计数,避免重复更新
|
||||
val countDelta = if (momentItem.isFavorite != isFavorite) {
|
||||
if (isFavorite) 1 else -1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
momentItem.copy(
|
||||
favoriteCount = (momentItem.favoriteCount + countDelta).coerceAtLeast(0),
|
||||
isFavorite = isFavorite
|
||||
)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
|
||||
@@ -208,7 +208,9 @@ fun CommentModalContent(
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText
|
||||
)
|
||||
OrderSelectionComponent {
|
||||
OrderSelectionComponent(
|
||||
selectedOrder = commentViewModel.order
|
||||
) {
|
||||
commentViewModel.order = it
|
||||
commentViewModel.reloadComment()
|
||||
}
|
||||
|
||||
@@ -64,12 +64,36 @@ open class BaseMomentModel :ViewModel(){
|
||||
momentLoader.updateMomentLike(event.postId, event.isLike)
|
||||
}
|
||||
suspend fun likeMoment(id: Int) {
|
||||
// 获取当前动态信息,用于计算新的点赞数
|
||||
val currentMoment = momentLoader.list.find { it.id == id }
|
||||
val newLikeCount = (currentMoment?.likeCount ?: 0) + 1
|
||||
|
||||
momentService.likeMoment(id)
|
||||
momentLoader.updateMomentLike(id, true)
|
||||
|
||||
// 只发送事件,让事件订阅者统一处理更新,避免重复更新
|
||||
EventBus.getDefault().post(
|
||||
MomentLikeChangeEvent(
|
||||
postId = id,
|
||||
likeCount = newLikeCount,
|
||||
isLike = true
|
||||
)
|
||||
)
|
||||
}
|
||||
suspend fun dislikeMoment(id: Int) {
|
||||
// 获取当前动态信息,用于计算新的点赞数
|
||||
val currentMoment = momentLoader.list.find { it.id == id }
|
||||
val newLikeCount = ((currentMoment?.likeCount ?: 0) - 1).coerceAtLeast(0)
|
||||
|
||||
momentService.dislikeMoment(id)
|
||||
momentLoader.updateMomentLike(id, false)
|
||||
|
||||
// 只发送事件,让事件订阅者统一处理更新,避免重复更新
|
||||
EventBus.getDefault().post(
|
||||
MomentLikeChangeEvent(
|
||||
postId = id,
|
||||
likeCount = newLikeCount,
|
||||
isLike = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,14 +114,27 @@ open class BaseMomentModel :ViewModel(){
|
||||
|
||||
suspend fun favoriteMoment(id: Int) {
|
||||
momentService.favoriteMoment(id)
|
||||
momentLoader.updateFavoriteCount(id, true)
|
||||
|
||||
// 只发送事件,让事件订阅者统一处理更新,避免重复更新
|
||||
EventBus.getDefault().post(
|
||||
MomentFavouriteChangeEvent(
|
||||
postId = id,
|
||||
isFavourite = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
suspend fun unfavoriteMoment(id: Int) {
|
||||
momentService.unfavoriteMoment(id)
|
||||
momentLoader.updateFavoriteCount(id, false)
|
||||
|
||||
// 只发送事件,让事件订阅者统一处理更新,避免重复更新
|
||||
EventBus.getDefault().post(
|
||||
MomentFavouriteChangeEvent(
|
||||
postId = id,
|
||||
isFavourite = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
||||
@@ -226,7 +226,9 @@ fun NewsCommentModal(
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
OrderSelectionComponent {
|
||||
OrderSelectionComponent(
|
||||
selectedOrder = commentViewModel.order
|
||||
) {
|
||||
commentViewModel.order = it
|
||||
commentViewModel.reloadComment()
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -59,6 +60,8 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInRoot
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@@ -85,6 +88,8 @@ import com.aiosman.ravenow.ui.index.IndexViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GalleryGrid
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GroupChatEmptyContent
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.OtherProfileAction
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.SegmentedControl
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.AgentSegmentedControl
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsList
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsRow
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserContentPageIndicator
|
||||
@@ -168,6 +173,33 @@ fun ProfileV3(
|
||||
initialFirstVisibleItemScrollOffset = model.profileGridFirstVisibleItemOffset
|
||||
)
|
||||
val scrollState = rememberScrollState(model.profileScrollOffset)
|
||||
var tabIndicatorContentOffset by remember { mutableStateOf<Float?>(null) }
|
||||
var tabIndicatorHeightPx by remember { mutableStateOf(0) }
|
||||
val topNavigationBarHeightPx = with(density) { (statusBarPaddingValues.calculateTopPadding() + 56.dp).toPx() }
|
||||
val stickyTopPadding = statusBarPaddingValues.calculateTopPadding() + 56.dp
|
||||
var agentSegmentOffset by remember { mutableStateOf<Float?>(null) }
|
||||
var agentSegmentHeightPx by remember { mutableStateOf(0) }
|
||||
var groupSegmentOffset by remember { mutableStateOf<Float?>(null) }
|
||||
var groupSegmentHeightPx by remember { mutableStateOf(0) }
|
||||
var agentSegmentSelected by remember { mutableStateOf(0) }
|
||||
var groupSegmentSelected by remember { mutableStateOf(0) }
|
||||
val tabIndicatorHeightDp = with(density) { tabIndicatorHeightPx.toDp() }
|
||||
val tabBarBottomPx = topNavigationBarHeightPx + tabIndicatorHeightPx
|
||||
val tabBarBottomPadding = stickyTopPadding + tabIndicatorHeightDp
|
||||
val tabStickyThreshold = remember(tabIndicatorContentOffset, topNavigationBarHeightPx) {
|
||||
tabIndicatorContentOffset?.minus(topNavigationBarHeightPx)
|
||||
}
|
||||
val agentSegmentThreshold = remember(agentSegmentOffset, tabBarBottomPx) {
|
||||
agentSegmentOffset?.minus(tabBarBottomPx)
|
||||
}
|
||||
val groupSegmentThreshold = remember(groupSegmentOffset, tabBarBottomPx) {
|
||||
groupSegmentOffset?.minus(tabBarBottomPx)
|
||||
}
|
||||
val agentTabIndex = if (isAiAccount) -1 else 1
|
||||
val groupTabIndex = if (isAiAccount) 1 else 2
|
||||
val shouldStickTabBar = tabStickyThreshold?.let { scrollState.value >= it } ?: false
|
||||
val shouldStickAgentSegments = isSelf && !isAiAccount && agentSegmentThreshold?.let { scrollState.value >= it } == true && pagerState.currentPage == agentTabIndex
|
||||
val shouldStickGroupSegments = isSelf && groupSegmentThreshold?.let { scrollState.value >= it } == true && pagerState.currentPage == groupTabIndex
|
||||
val externalOwnerSessionId = remember(isSelf, profile?.chatAIId, profile?.trtcUserId) {
|
||||
if (isSelf) {
|
||||
null
|
||||
@@ -499,11 +531,20 @@ fun ProfileV3(
|
||||
.fillMaxWidth()
|
||||
.background(AppColors.profileBackground)
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onGloballyPositioned { coordinates ->
|
||||
tabIndicatorHeightPx = coordinates.size.height
|
||||
tabIndicatorContentOffset = coordinates.positionInRoot().y + scrollState.value
|
||||
}
|
||||
.alpha(if (shouldStickTabBar) 0f else 1f)
|
||||
) {
|
||||
UserContentPageIndicator(
|
||||
pagerState = pagerState,
|
||||
showAgentTab = !isAiAccount
|
||||
)
|
||||
}
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.height(650.dp) // 固定滚动高度
|
||||
@@ -542,7 +583,15 @@ fun ProfileV3(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
nestedScrollConnection = nestedScrollConnection,
|
||||
showSegments = isSelf // 只有查看自己的主页时才显示分段控制器
|
||||
showSegments = isSelf, // 只有查看自己的主页时才显示分段控制器
|
||||
segmentSelectedIndex = agentSegmentSelected,
|
||||
onSegmentSelected = { agentSegmentSelected = it },
|
||||
onSegmentMeasured = { offset, height ->
|
||||
agentSegmentOffset = offset
|
||||
agentSegmentHeightPx = height
|
||||
},
|
||||
isSegmentSticky = shouldStickAgentSegments,
|
||||
parentScrollProvider = { scrollState.value }
|
||||
)
|
||||
} else {
|
||||
// 查看其他用户的主页时,传递该用户的会话ID以显示其创建的群聊;查看自己的主页时传递null
|
||||
@@ -551,7 +600,15 @@ fun ProfileV3(
|
||||
listState = groupChatListState,
|
||||
nestedScrollConnection = nestedScrollConnection,
|
||||
ownerSessionId = externalOwnerSessionId,
|
||||
showSegments = isSelf // 只有查看自己的主页时才显示分段控制器
|
||||
showSegments = isSelf, // 只有查看自己的主页时才显示分段控制器
|
||||
selectedSegmentIndex = groupSegmentSelected,
|
||||
onSegmentSelected = { groupSegmentSelected = it },
|
||||
onSegmentMeasured = { offset, height ->
|
||||
groupSegmentOffset = offset
|
||||
groupSegmentHeightPx = height
|
||||
},
|
||||
isSegmentSticky = shouldStickGroupSegments,
|
||||
parentScrollProvider = { scrollState.value }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -563,7 +620,15 @@ fun ProfileV3(
|
||||
listState = groupChatListState,
|
||||
nestedScrollConnection = nestedScrollConnection,
|
||||
ownerSessionId = externalOwnerSessionId,
|
||||
showSegments = isSelf // 只有查看自己的主页时才显示分段控制器
|
||||
showSegments = isSelf, // 只有查看自己的主页时才显示分段控制器
|
||||
selectedSegmentIndex = groupSegmentSelected,
|
||||
onSegmentSelected = { groupSegmentSelected = it },
|
||||
onSegmentMeasured = { offset, height ->
|
||||
groupSegmentOffset = offset
|
||||
groupSegmentHeightPx = height
|
||||
},
|
||||
isSegmentSticky = shouldStickGroupSegments,
|
||||
parentScrollProvider = { scrollState.value }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -575,6 +640,55 @@ fun ProfileV3(
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
if (shouldStickTabBar && tabIndicatorHeightPx > 0) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(top = stickyTopPadding)
|
||||
.background(AppColors.profileBackground)
|
||||
) {
|
||||
UserContentPageIndicator(
|
||||
pagerState = pagerState,
|
||||
showAgentTab = !isAiAccount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldStickAgentSegments && agentSegmentHeightPx > 0) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(top = tabBarBottomPadding)
|
||||
.background(AppColors.profileBackground)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
AgentSegmentedControl(
|
||||
selectedIndex = agentSegmentSelected,
|
||||
onSegmentSelected = { agentSegmentSelected = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldStickGroupSegments && groupSegmentHeightPx > 0) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(top = tabBarBottomPadding)
|
||||
.background(AppColors.profileBackground)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
SegmentedControl(
|
||||
selectedIndex = groupSegmentSelected,
|
||||
onSegmentSelected = { groupSegmentSelected = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 顶部导航栏
|
||||
TopNavigationBar(
|
||||
isMain = isMain,
|
||||
@@ -673,14 +787,24 @@ private fun GroupChatPlaceholder(
|
||||
listState: androidx.compose.foundation.lazy.LazyListState,
|
||||
nestedScrollConnection: NestedScrollConnection? = null,
|
||||
ownerSessionId: String? = null, // 创建者用户ID(ChatAIID),用于过滤特定创建者的房间。如果为null,则显示当前用户创建或加入的房间
|
||||
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
|
||||
showSegments: Boolean = true,
|
||||
selectedSegmentIndex: Int = 0,
|
||||
onSegmentSelected: (Int) -> Unit = {},
|
||||
onSegmentMeasured: ((Float, Int) -> Unit)? = null,
|
||||
isSegmentSticky: Boolean = false,
|
||||
parentScrollProvider: () -> Int = { 0 }
|
||||
) {
|
||||
GroupChatEmptyContent(
|
||||
modifier = modifier,
|
||||
listState = listState,
|
||||
nestedScrollConnection = nestedScrollConnection,
|
||||
ownerSessionId = ownerSessionId,
|
||||
showSegments = showSegments
|
||||
showSegments = showSegments,
|
||||
selectedSegmentIndex = selectedSegmentIndex,
|
||||
onSegmentSelected = onSegmentSelected,
|
||||
onSegmentMeasured = onSegmentMeasured,
|
||||
isSegmentSticky = isSegmentSticky,
|
||||
parentScrollProvider = parentScrollProvider
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInRoot
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -74,9 +76,13 @@ fun GroupChatEmptyContent(
|
||||
listState: LazyListState,
|
||||
nestedScrollConnection: NestedScrollConnection? = null,
|
||||
ownerSessionId: String? = null, // 创建者用户ID(ChatAIID),用于过滤特定创建者的房间。如果为null,则显示当前用户创建或加入的房间
|
||||
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
|
||||
showSegments: Boolean = true, // 是否显示分段控制器(全部、公开、私有)
|
||||
selectedSegmentIndex: Int = 0,
|
||||
onSegmentSelected: (Int) -> Unit = {},
|
||||
onSegmentMeasured: ((Float, Int) -> Unit)? = null,
|
||||
isSegmentSticky: Boolean = false,
|
||||
parentScrollProvider: () -> Int = { 0 }
|
||||
) {
|
||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
||||
val AppColors = LocalAppTheme.current
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
@@ -86,7 +92,7 @@ fun GroupChatEmptyContent(
|
||||
val networkAvailable = isNetworkAvailable(context)
|
||||
|
||||
// 如果查看其他用户的房间,固定使用全部类型(filterType = 0)
|
||||
val filterType = if (showSegments) selectedSegment else 0
|
||||
val filterType = if (showSegments) selectedSegmentIndex else 0
|
||||
|
||||
val state = rememberPullRefreshState(
|
||||
refreshing = if (canLoadRooms) viewModel.roomsRefreshing else false,
|
||||
@@ -98,7 +104,7 @@ fun GroupChatEmptyContent(
|
||||
)
|
||||
|
||||
// 当分段或用户ID改变时,重新加载数据
|
||||
LaunchedEffect(selectedSegment, normalizedOwnerSessionId, showSegments) {
|
||||
LaunchedEffect(selectedSegmentIndex, normalizedOwnerSessionId, showSegments) {
|
||||
if (canLoadRooms) {
|
||||
viewModel.refreshRooms(filterType = filterType, ownerSessionId = normalizedOwnerSessionId)
|
||||
}
|
||||
@@ -120,12 +126,17 @@ fun GroupChatEmptyContent(
|
||||
// 只在查看自己的房间时显示分段控制器
|
||||
if (showSegments) {
|
||||
SegmentedControl(
|
||||
selectedIndex = selectedSegment,
|
||||
onSegmentSelected = {
|
||||
selectedSegment = it
|
||||
// LaunchedEffect 会监听 selectedSegment 的变化并自动刷新
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
selectedIndex = selectedSegmentIndex,
|
||||
onSegmentSelected = onSegmentSelected,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onGloballyPositioned { coordinates ->
|
||||
onSegmentMeasured?.invoke(
|
||||
coordinates.positionInRoot().y + parentScrollProvider(),
|
||||
coordinates.size.height
|
||||
)
|
||||
}
|
||||
.alpha(if (isSegmentSticky) 0f else 1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
@@ -445,7 +456,7 @@ fun RoomItem(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SegmentedControl(
|
||||
fun SegmentedControl(
|
||||
selectedIndex: Int,
|
||||
onSegmentSelected: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -30,9 +29,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInRoot
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -53,6 +55,7 @@ import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.entity.AgentEntity
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.ui.network.ReloadButton
|
||||
import com.aiosman.ravenow.utils.DebounceUtils
|
||||
import com.aiosman.ravenow.utils.NetworkUtils
|
||||
@@ -68,7 +71,12 @@ fun UserAgentsList(
|
||||
modifier: Modifier = Modifier,
|
||||
state: LazyListState,
|
||||
nestedScrollConnection: NestedScrollConnection? = null,
|
||||
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
|
||||
showSegments: Boolean = true, // 是否显示分段控制器(全部、公开、私有)
|
||||
segmentSelectedIndex: Int = 0,
|
||||
onSegmentSelected: (Int) -> Unit = {},
|
||||
onSegmentMeasured: ((Float, Int) -> Unit)? = null,
|
||||
isSegmentSticky: Boolean = false,
|
||||
parentScrollProvider: () -> Int = { 0 }
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val listModifier = if (nestedScrollConnection != null) {
|
||||
@@ -82,7 +90,14 @@ fun UserAgentsList(
|
||||
Box(
|
||||
modifier = listModifier.fillMaxSize()
|
||||
) {
|
||||
AgentEmptyContentWithSegments(showSegments = showSegments)
|
||||
AgentEmptyContentWithSegments(
|
||||
showSegments = showSegments,
|
||||
segmentSelectedIndex = segmentSelectedIndex,
|
||||
onSegmentSelected = onSegmentSelected,
|
||||
onSegmentMeasured = onSegmentMeasured,
|
||||
isSegmentSticky = isSegmentSticky,
|
||||
parentScrollProvider = parentScrollProvider
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
@@ -251,9 +266,13 @@ fun UserAgentCard(
|
||||
|
||||
@Composable
|
||||
fun AgentEmptyContentWithSegments(
|
||||
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
|
||||
showSegments: Boolean = true,
|
||||
segmentSelectedIndex: Int = 0,
|
||||
onSegmentSelected: (Int) -> Unit = {},
|
||||
onSegmentMeasured: ((Float, Int) -> Unit)? = null,
|
||||
isSegmentSticky: Boolean = false,
|
||||
parentScrollProvider: () -> Int = { 0 }
|
||||
) {
|
||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
||||
val AppColors = LocalAppTheme.current
|
||||
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||
|
||||
@@ -267,9 +286,17 @@ fun AgentEmptyContentWithSegments(
|
||||
// 只在查看自己的智能体时显示分段控制器
|
||||
if (showSegments) {
|
||||
AgentSegmentedControl(
|
||||
selectedIndex = selectedSegment,
|
||||
onSegmentSelected = { selectedSegment = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
selectedIndex = segmentSelectedIndex,
|
||||
onSegmentSelected = onSegmentSelected,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onGloballyPositioned { coordinates ->
|
||||
onSegmentMeasured?.invoke(
|
||||
coordinates.positionInRoot().y + parentScrollProvider(),
|
||||
coordinates.size.height
|
||||
)
|
||||
}
|
||||
.alpha(if (isSegmentSticky) 0f else 1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
@@ -321,7 +348,7 @@ fun AgentEmptyContentWithSegments(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AgentSegmentedControl(
|
||||
fun AgentSegmentedControl(
|
||||
selectedIndex: Int,
|
||||
onSegmentSelected: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
|
||||
@@ -13,37 +13,30 @@ import kotlinx.coroutines.launch
|
||||
class CommentsViewModel(
|
||||
var postId: String = 0.toString(),
|
||||
) : ViewModel() {
|
||||
companion object {
|
||||
private const val ORDER_ALL = "all"
|
||||
private const val COMMENTS_PAGE_SIZE = 50
|
||||
}
|
||||
|
||||
var commentService: CommentService = CommentServiceImpl()
|
||||
var commentsList by mutableStateOf<List<CommentEntity>>(emptyList())
|
||||
var order: String by mutableStateOf("like")
|
||||
var order: String by mutableStateOf(ORDER_ALL)
|
||||
var addedCommentList by mutableStateOf<List<CommentEntity>>(emptyList())
|
||||
var subCommentLoadingMap by mutableStateOf(mutableMapOf<Int, Boolean>())
|
||||
var highlightCommentId by mutableStateOf<Int?>(null)
|
||||
var highlightComment by mutableStateOf<CommentEntity?>(null)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var hasError by mutableStateOf(false)
|
||||
var isLoadingMore by mutableStateOf(false)
|
||||
var hasMore by mutableStateOf(false)
|
||||
private var currentPage by mutableStateOf(0)
|
||||
private var totalComments by mutableStateOf(0)
|
||||
|
||||
/**
|
||||
* 预加载,在跳转到 PostScreen 之前设置好内容
|
||||
*/
|
||||
fun preTransit() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
isLoading = true
|
||||
val response = commentService.getComments(
|
||||
pageNumber = 1,
|
||||
postId = postId.toInt(),
|
||||
pageSize = 10
|
||||
)
|
||||
commentsList = response.list
|
||||
hasError = false
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
hasError = true
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
reloadComment()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,25 +44,61 @@ class CommentsViewModel(
|
||||
*/
|
||||
fun reloadComment() {
|
||||
viewModelScope.launch {
|
||||
loadComments(page = 1, reset = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMoreComments() {
|
||||
if (isLoading || isLoadingMore || !hasMore) {
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
loadComments(page = currentPage + 1, reset = false)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadComments(page: Int, reset: Boolean) {
|
||||
try {
|
||||
if (reset) {
|
||||
isLoading = true
|
||||
val response = commentService.getComments(
|
||||
pageNumber = 1,
|
||||
postId = postId.toInt(),
|
||||
order = order,
|
||||
pageSize = 50
|
||||
)
|
||||
commentsList = response.list
|
||||
hasError = false
|
||||
} else {
|
||||
isLoadingMore = true
|
||||
}
|
||||
val response = commentService.getComments(
|
||||
pageNumber = page,
|
||||
postId = postId.toInt(),
|
||||
order = normalizeOrder(order),
|
||||
pageSize = COMMENTS_PAGE_SIZE
|
||||
)
|
||||
val total = response.total.coerceAtMost(Int.MAX_VALUE.toLong()).toInt()
|
||||
totalComments = total
|
||||
currentPage = response.page
|
||||
commentsList = if (reset) {
|
||||
response.list
|
||||
} else {
|
||||
commentsList + response.list
|
||||
}
|
||||
hasMore = commentsList.size < totalComments
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
if (reset) {
|
||||
hasError = true
|
||||
commentsList = emptyList()
|
||||
}
|
||||
} finally {
|
||||
if (reset) {
|
||||
isLoading = false
|
||||
} else {
|
||||
isLoadingMore = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeOrder(currentOrder: String): String? {
|
||||
return currentOrder.takeUnless { it.equals(ORDER_ALL, ignoreCase = true) }
|
||||
}
|
||||
|
||||
|
||||
suspend fun highlightComment(commentId: Int) {
|
||||
highlightCommentId = commentId
|
||||
|
||||
@@ -495,7 +495,9 @@ fun PostScreen(
|
||||
color = AppColors.nonActiveText
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
OrderSelectionComponent() {
|
||||
OrderSelectionComponent(
|
||||
selectedOrder = commentsViewModel.order
|
||||
) {
|
||||
commentsViewModel.order = it
|
||||
viewModel.reloadComment()
|
||||
}
|
||||
@@ -742,6 +744,33 @@ fun CommentContent(
|
||||
}
|
||||
}
|
||||
|
||||
if (viewModel.isLoadingMore || viewModel.hasMore) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 12.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (viewModel.isLoadingMore) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = AppColors.main,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(id = R.string.load_more),
|
||||
color = AppColors.main,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.noRippleClickable {
|
||||
viewModel.loadMoreComments()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载状态处理
|
||||
if (viewModel.isLoading) {
|
||||
Box(
|
||||
@@ -1909,15 +1938,15 @@ fun CommentMenuModal(
|
||||
|
||||
@Composable
|
||||
fun OrderSelectionComponent(
|
||||
selectedOrder: String,
|
||||
onSelected: (String) -> Unit = {}
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
var selectedOrder by remember { mutableStateOf("like") }
|
||||
val orders = listOf(
|
||||
"like" to stringResource(R.string.order_comment_default),
|
||||
"earliest" to stringResource(R.string.order_comment_earliest),
|
||||
"latest" to stringResource(R.string.order_comment_latest)
|
||||
"all" to stringResource(R.string.order_comment_default),
|
||||
"latest" to stringResource(R.string.order_comment_latest),
|
||||
"like" to stringResource(R.string.order_comment_hot)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -1935,9 +1964,10 @@ fun OrderSelectionComponent(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.noRippleClickable {
|
||||
selectedOrder = order.first
|
||||
if (selectedOrder != order.first) {
|
||||
onSelected(order.first)
|
||||
}
|
||||
}
|
||||
.background(
|
||||
if (
|
||||
selectedOrder == order.first
|
||||
|
||||
@@ -75,9 +75,9 @@
|
||||
<string name="bio">署名</string>
|
||||
<string name="nickname">名前</string>
|
||||
<string name="comment">コメント</string>
|
||||
<string name="order_comment_default">デフォルト</string>
|
||||
<string name="order_comment_default">すべて</string>
|
||||
<string name="order_comment_latest">最新</string>
|
||||
<string name="order_comment_earliest">最も古い</string>
|
||||
<string name="order_comment_hot">人気</string>
|
||||
<string name="download">ダウンロード</string>
|
||||
<string name="original">オリジナル</string>
|
||||
<string name="favourites">お気に入り</string>
|
||||
|
||||
@@ -80,9 +80,9 @@
|
||||
<string name="bio">个性签名</string>
|
||||
<string name="nickname">昵称</string>
|
||||
<string name="comment">评论</string>
|
||||
<string name="order_comment_default">默认</string>
|
||||
<string name="order_comment_default">全部</string>
|
||||
<string name="order_comment_latest">最新</string>
|
||||
<string name="order_comment_earliest">最早</string>
|
||||
<string name="order_comment_hot">热门</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="original">原始图片</string>
|
||||
<string name="favourites">收藏</string>
|
||||
|
||||
@@ -74,9 +74,9 @@
|
||||
<string name="bio">Signature</string>
|
||||
<string name="nickname">Name</string>
|
||||
<string name="comment">COMMENTS</string>
|
||||
<string name="order_comment_default">Default</string>
|
||||
<string name="order_comment_default">All</string>
|
||||
<string name="order_comment_latest">Latest</string>
|
||||
<string name="order_comment_earliest">Earliest</string>
|
||||
<string name="order_comment_hot">Hot</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="original">Original</string>
|
||||
<string name="favourites">Favourite</string>
|
||||
|
||||
Reference in New Issue
Block a user