优化个人主页并实现列表位置记忆

- **实现列表滚动位置记忆**: 在`MyProfileViewModel`中添加状态变量,用于记录动态、智能体和群聊列表的滚动位置,以及当前所在的标签页。当用户离开并返回个人主页时,能够恢复到上次浏览的位置,提升用户体验。
- **添加智能体列表分页加载**: 新增`loadMoreAgent`方法,并结合`derivedStateOf`在列表滚动到底部时自动加载更多智能体数据。
- **统一列表滚动处理**: 引入`NestedScrollConnection`,以协调处理个人主页头部区域与下方标签页中各列表(动态、智能体、群聊)之间的嵌套滚动,解决了滚动冲突问题。
- **列表加载与空状态优化**: 为智能体列表添加了加载中(loading)和“没有更多”的提示,并优化了空状态下的布局显示。
- **导航栏样式优化**: 调整了个人主页顶部导航栏的背景和图标颜色过渡效果,使其在滚动时更加平滑和美观。
This commit is contained in:
2025-11-13 14:07:57 +08:00
parent 1953553277
commit 689a4761ce
6 changed files with 276 additions and 102 deletions

View File

@@ -422,13 +422,6 @@ fun Profile() {
systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode) systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode)
} }
// 页面退出时清理个人资料相关资源
DisposableEffect(Unit) {
onDispose {
ResourceCleanupManager.cleanupPageResources("profile")
}
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize(), .fillMaxSize(),

View File

@@ -52,15 +52,24 @@ object MyProfileViewModel : ViewModel() {
private val roomsPageSize = 20 private val roomsPageSize = 20
private val apiClient: RaveNowAPI = ApiClient.api private val apiClient: RaveNowAPI = ApiClient.api
var profileScrollOffset by mutableStateOf(0)
var profileGridFirstVisibleItemIndex by mutableStateOf(0)
var profileGridFirstVisibleItemOffset by mutableStateOf(0)
var profileAgentListFirstVisibleItemIndex by mutableStateOf(0)
var profileAgentListFirstVisibleItemOffset by mutableStateOf(0)
var profileGroupChatFirstVisibleItemIndex by mutableStateOf(0)
var profileGroupChatFirstVisibleItemOffset by mutableStateOf(0)
var profileCurrentPage by mutableStateOf(0)
val momentLoader: MomentLoader = MomentLoader().apply { val momentLoader: MomentLoader = MomentLoader().apply {
pageSize = 20 // 设置与后端一致的页面大小 pageSize = 20 // 设置与后端一致的页面大小
onListChanged = { onListChanged = { updated ->
moments = it moments = updated.toList()
} }
} }
val agentLoader: AgentLoader = AgentLoader().apply { val agentLoader: AgentLoader = AgentLoader().apply {
onListChanged = { onListChanged = { updated ->
agents = it agents = updated.toList()
} }
} }
var refreshing by mutableStateOf(false) var refreshing by mutableStateOf(false)
@@ -137,6 +146,26 @@ object MyProfileViewModel : ViewModel() {
} }
} }
fun loadMoreAgent() {
if (AppStore.isGuest) {
Log.d("MyProfileViewModel", "loadMoreAgent: 游客模式下跳过加载更多智能体")
return
}
viewModelScope.launch {
try {
Log.d("MyProfileViewModel", "loadMoreAgent: hasNext = ${agentLoader.hasNext}")
if (profile == null) {
agentLoader.loadMore(extra = AgentLoaderExtraArgs(authorId = null))
} else {
agentLoader.loadMore(extra = AgentLoaderExtraArgs(authorId = profile?.id))
}
} catch (e: Exception) {
Log.e("MyProfileViewModel", "loadMoreAgent: ", e)
}
}
}
fun logout(context: Context) { fun logout(context: Context) {
viewModelScope.launch { viewModelScope.launch {
// 只有非游客用户才需要取消注册推送设备 // 只有非游客用户才需要取消注册推送设备
@@ -230,6 +259,14 @@ object MyProfileViewModel : ViewModel() {
momentLoader.clear() momentLoader.clear()
agentLoader.clear() agentLoader.clear()
firstLoad = true firstLoad = true
profileScrollOffset = 0
profileGridFirstVisibleItemIndex = 0
profileGridFirstVisibleItemOffset = 0
profileAgentListFirstVisibleItemIndex = 0
profileAgentListFirstVisibleItemOffset = 0
profileGroupChatFirstVisibleItemIndex = 0
profileGroupChatFirstVisibleItemOffset = 0
profileCurrentPage = 0
} }
override fun onCleared() { override fun onCleared() {

View File

@@ -41,6 +41,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -106,11 +107,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import java.text.NumberFormat import java.text.NumberFormat
import java.util.Locale import java.util.Locale
import com.aiosman.ravenow.ui.points.PointsBottomSheet import com.aiosman.ravenow.ui.points.PointsBottomSheet
import kotlin.math.max
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -133,7 +137,10 @@ fun ProfileV3(
) { ) {
val model = MyProfileViewModel val model = MyProfileViewModel
// Tabs: 动态、(可选)智能体、群聊 // Tabs: 动态、(可选)智能体、群聊
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 2 else 3 }) val pagerState = rememberPagerState(
initialPage = model.profileCurrentPage,
pageCount = { if (isAiAccount) 2 else 3 }
)
val enabled by remember { mutableStateOf(true) } val enabled by remember { mutableStateOf(true) }
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
@@ -165,9 +172,65 @@ fun ProfileV3(
val appTheme = LocalAppTheme.current val appTheme = LocalAppTheme.current
val AppColors = appTheme val AppColors = appTheme
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val listState = rememberLazyListState() val listState = rememberLazyListState(
val gridState = rememberLazyGridState() initialFirstVisibleItemIndex = model.profileAgentListFirstVisibleItemIndex,
val scrollState = rememberScrollState() initialFirstVisibleItemScrollOffset = model.profileAgentListFirstVisibleItemOffset
)
val groupChatListState = rememberLazyListState(
initialFirstVisibleItemIndex = model.profileGroupChatFirstVisibleItemIndex,
initialFirstVisibleItemScrollOffset = model.profileGroupChatFirstVisibleItemOffset
)
val gridState = rememberLazyGridState(
initialFirstVisibleItemIndex = model.profileGridFirstVisibleItemIndex,
initialFirstVisibleItemScrollOffset = model.profileGridFirstVisibleItemOffset
)
val scrollState = rememberScrollState(model.profileScrollOffset)
val nestedScrollConnection = remember(scrollState, pagerState, gridState, listState, groupChatListState, isAiAccount) {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (available.y >= 0f) return Offset.Zero
val consumed = scrollState.dispatchRawDelta(-available.y)
return if (consumed != 0f) Offset(0f, -consumed) else Offset.Zero
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
if (available.y <= 0f) return Offset.Zero
val childCanScrollUp = when (pagerState.currentPage) {
0 -> gridState.firstVisibleItemIndex > 0 || gridState.firstVisibleItemScrollOffset > 0
1 -> if (!isAiAccount) {
listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0
} else {
groupChatListState.firstVisibleItemIndex > 0 || groupChatListState.firstVisibleItemScrollOffset > 0
}
2 -> if (!isAiAccount) {
groupChatListState.firstVisibleItemIndex > 0 || groupChatListState.firstVisibleItemScrollOffset > 0
} else {
false
}
else -> false
}
if (childCanScrollUp) return Offset.Zero
val consumedByParent = scrollState.dispatchRawDelta(-available.y)
return if (consumedByParent != 0f) Offset(0f, -consumedByParent) else Offset.Zero
}
}
}
LaunchedEffect(pagerState.currentPage) {
model.profileCurrentPage = pagerState.currentPage
}
DisposableEffect(Unit) {
onDispose {
model.profileScrollOffset = scrollState.value
model.profileGridFirstVisibleItemIndex = gridState.firstVisibleItemIndex
model.profileGridFirstVisibleItemOffset = gridState.firstVisibleItemScrollOffset
model.profileAgentListFirstVisibleItemIndex = listState.firstVisibleItemIndex
model.profileAgentListFirstVisibleItemOffset = listState.firstVisibleItemScrollOffset
model.profileGroupChatFirstVisibleItemIndex = groupChatListState.firstVisibleItemIndex
model.profileGroupChatFirstVisibleItemOffset = groupChatListState.firstVisibleItemScrollOffset
}
}
val pointsBalanceState = PointService.pointsBalance.collectAsState(initial = null) val pointsBalanceState = PointService.pointsBalance.collectAsState(initial = null)
// 计算导航栏背景透明度根据滚动位置从0到1 // 计算导航栏背景透明度根据滚动位置从0到1
@@ -184,10 +247,21 @@ fun ProfileV3(
} }
// observe list scrolling // observe list scrolling
val reachedListBottom by remember { val reachedAgentsBottom by remember {
derivedStateOf { derivedStateOf {
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull() if (isAiAccount || pagerState.currentPage != 1) {
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2 false
} else {
val layoutInfo = listState.layoutInfo
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
val totalItems = layoutInfo.totalItemsCount
if (lastVisibleItem == null || totalItems == 0) {
false
} else {
val triggerIndex = max(0, totalItems - 2)
lastVisibleItem.index >= triggerIndex && model.agentLoader.hasNext && !model.agentLoader.isLoading
}
}
} }
} }
@@ -217,9 +291,9 @@ fun ProfileV3(
} }
// load more if scrolled to bottom of list // load more if scrolled to bottom of list
LaunchedEffect(reachedListBottom) { LaunchedEffect(reachedAgentsBottom) {
if (reachedListBottom) { if (reachedAgentsBottom) {
onLoadMore() model.loadMoreAgent()
} }
} }
@@ -362,7 +436,6 @@ fun ProfileV3(
} }
// 壁纸下方间距 // 壁纸下方间距
Spacer(modifier = Modifier.height(16.dp))
// 用户信息 // 用户信息
Box( Box(
@@ -460,7 +533,12 @@ fun ProfileV3(
modifier = Modifier.height(500.dp) // 固定滚动高度 modifier = Modifier.height(500.dp) // 固定滚动高度
) { idx -> ) { idx ->
when (idx) { when (idx) {
0 -> GalleryGrid(moments = moments) 0 -> GalleryGrid(
moments = moments,
modifier = Modifier.fillMaxSize(),
state = gridState,
nestedScrollConnection = nestedScrollConnection
)
1 -> { 1 -> {
if (!isAiAccount) { if (!isAiAccount) {
UserAgentsList( UserAgentsList(
@@ -482,15 +560,28 @@ fun ProfileV3(
} }
} }
}, },
modifier = Modifier.fillMaxSize() hasMore = model.agentLoader.hasNext,
isLoading = model.agentLoader.isLoading,
showNoMoreText = isSelf,
modifier = Modifier.fillMaxSize(),
state = listState,
nestedScrollConnection = nestedScrollConnection
) )
} else { } else {
GroupChatPlaceholder() GroupChatPlaceholder(
modifier = Modifier.fillMaxSize(),
listState = groupChatListState,
nestedScrollConnection = nestedScrollConnection
)
} }
} }
2 -> { 2 -> {
if (!isAiAccount) { if (!isAiAccount) {
GroupChatPlaceholder() GroupChatPlaceholder(
modifier = Modifier.fillMaxSize(),
listState = groupChatListState,
nestedScrollConnection = nestedScrollConnection
)
} }
} }
} }
@@ -498,7 +589,7 @@ fun ProfileV3(
} }
// 底部间距,增加滚动距离 // 底部间距,增加滚动距离
Spacer(modifier = Modifier.height(100.dp)) Spacer(modifier = Modifier.height(16.dp))
} }
// 顶部导航栏 // 顶部导航栏
@@ -594,8 +685,16 @@ fun ProfileV3(
} }
@Composable @Composable
private fun GroupChatPlaceholder() { private fun GroupChatPlaceholder(
GroupChatEmptyContent() modifier: Modifier = Modifier,
listState: androidx.compose.foundation.lazy.LazyListState,
nestedScrollConnection: NestedScrollConnection? = null
) {
GroupChatEmptyContent(
modifier = modifier,
listState = listState,
nestedScrollConnection = nestedScrollConnection
)
} }
//顶部导航栏组件 //顶部导航栏组件
@@ -616,18 +715,24 @@ fun TopNavigationBar(
// 仅本人主页显示积分:收集全局积分 // 仅本人主页显示积分:收集全局积分
val pointsBalanceState = if (isSelf) PointService.pointsBalance.collectAsState(initial = null) else null val pointsBalanceState = if (isSelf) PointService.pointsBalance.collectAsState(initial = null) else null
// 根据背景透明度和暗色模式决定图标颜色 // 根据背景透明度和主题决定图标与边框颜色
// 暗色模式下:图标始终为白色 val iconColor = if (backgroundAlpha >= 0.7f) appColors.text else Color.White
// 亮色模式下根据背景透明度决定透明度为1时变黑否则为白色 val cardBorderColor = if (backgroundAlpha >= 0.7f) appColors.divider else Color.White
val iconColor = if (AppState.darkMode) { val toolbarSolidColor = remember(backgroundAlpha, appColors) {
Color.White // 暗色模式下图标始终为白色 appColors.background.copy(alpha = backgroundAlpha.coerceIn(0f, 1f))
} else {
if (backgroundAlpha >= 1f) Color.Black else Color.White
} }
val cardBorderColor = if (AppState.darkMode) { val toolbarOverlayBrush = remember(backgroundAlpha) {
Color.White // 暗色模式下边框应为白色 val overlayAlpha = (1f - backgroundAlpha).coerceIn(0f, 1f) * 0.25f
} else { if (overlayAlpha > 0f) {
if (backgroundAlpha >= 1f) Color.Black else Color.White Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = overlayAlpha),
Color.Transparent
)
)
} else {
null
}
} }
Box( Box(
@@ -640,58 +745,23 @@ fun TopNavigationBar(
// 导航栏背景层,包括状态栏区域,根据滚动位置逐渐变白 // 导航栏背景层,包括状态栏区域,根据滚动位置逐渐变白
val totalHeight = statusBarHeight + navigationBarHeight val totalHeight = statusBarHeight + navigationBarHeight
val density = LocalDensity.current
val totalHeightPx = with(density) { totalHeight.toPx() }
// 根据滚动位置计算基础颜色从深色平滑过渡到白色透明度从初始值逐渐减到0
val baseColor = remember(backgroundAlpha) {
val smoothProgress = backgroundAlpha.coerceIn(0f, 1f)
if (AppState.darkMode) {
// 暗色模式下从黑色透明度0逐渐变为黑色透明度1
val alpha = smoothProgress.coerceIn(0f, 1f)
Color.Black.copy(alpha = alpha)
} else {
// 亮色模式:初始状态:半透明深色,让白色图标清晰可见
val initialDarkAlpha = 0.12f
// 使用平滑的插值函数,让整个过渡更自然
val easedProgress = smoothProgress * smoothProgress * (3f - 2f * smoothProgress) // smoothstep
// 颜色值:从黑色(0)平滑过渡到白色(1)
val colorValue = easedProgress
// 透明度:从初始值(0.12f)逐渐减少到0
// 当smoothProgress从0到1时alpha从initialDarkAlpha减少到0
val alpha = initialDarkAlpha * (1f - easedProgress)
Color(
red = colorValue,
green = colorValue,
blue = colorValue,
alpha = alpha.coerceIn(0f, initialDarkAlpha)
)
}
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(totalHeight) // 状态栏高度 + 导航栏高度 .height(totalHeight) // 状态栏高度 + 导航栏高度
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.background( .background(toolbarSolidColor)
brush = Brush.verticalGradient(
colors = listOf(
baseColor, // 顶部保持基础颜色
baseColor, // 中间保持基础颜色
baseColor.copy(alpha = baseColor.alpha * 0.5f), // 底部过渡,逐渐变透明
Color.Transparent // 最底部完全透明
),
startY = 0f,
endY = totalHeightPx
)
)
) )
toolbarOverlayBrush?.let { brush ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(totalHeight)
.align(Alignment.TopCenter)
.background(brush)
)
}
// 功能按钮区域,图标和文字根据背景透明度改变颜色 // 功能按钮区域,图标和文字根据背景透明度改变颜色
Row( Row(

View File

@@ -32,9 +32,9 @@ import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -49,6 +49,8 @@ import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.ravenow.ui.network.ReloadButton import com.aiosman.ravenow.ui.network.ReloadButton
import com.aiosman.ravenow.utils.NetworkUtils import com.aiosman.ravenow.utils.NetworkUtils
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
@Composable @Composable
fun GalleryItem( fun GalleryItem(
moment: MomentEntity, moment: MomentEntity,
@@ -132,18 +134,25 @@ fun GalleryItem(
@Composable @Composable
fun GalleryGrid( fun GalleryGrid(
moments: List<MomentEntity> moments: List<MomentEntity>,
modifier: Modifier = Modifier,
state: LazyGridState,
nestedScrollConnection: NestedScrollConnection? = null
) { ) {
val navController = LocalNavController.current val navController = LocalNavController.current
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val gridState = rememberLazyGridState()
val debouncer = rememberDebouncer() val debouncer = rememberDebouncer()
var refreshKey by remember { mutableStateOf(0) } var refreshKey by remember { mutableStateOf(0) }
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
val baseModifier = if (nestedScrollConnection != null) {
modifier.nestedScroll(nestedScrollConnection)
} else {
modifier
}
if (!isNetworkAvailable) { if (!isNetworkAvailable) {
Column( Column(
modifier = Modifier modifier = baseModifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(vertical = 60.dp), .padding(vertical = 60.dp),
@@ -183,7 +192,7 @@ fun GalleryGrid(
} }
} else if (moments.isEmpty()) { } else if (moments.isEmpty()) {
Column( Column(
modifier = Modifier modifier = baseModifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(vertical = 60.dp), .padding(vertical = 60.dp),
@@ -227,8 +236,10 @@ fun GalleryGrid(
} else { } else {
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
state = gridState, state = state,
modifier = Modifier.fillMaxSize().padding(bottom = 8.dp), modifier = baseModifier
.fillMaxSize()
.padding(bottom = 8.dp),
) { ) {
itemsIndexed(moments) { idx, moment -> itemsIndexed(moments) { idx, moment ->
if (moment != null && moment.images.isNotEmpty()) { if (moment != null && moment.images.isNotEmpty()) {

View File

@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
@@ -43,6 +44,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import com.aiosman.ravenow.ConstVars import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
@@ -59,7 +62,11 @@ import android.util.Base64
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun GroupChatEmptyContent() { fun GroupChatEmptyContent(
modifier: Modifier = Modifier,
listState: LazyListState,
nestedScrollConnection: NestedScrollConnection? = null
) {
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有 var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val context = LocalContext.current val context = LocalContext.current
@@ -86,8 +93,14 @@ fun GroupChatEmptyContent() {
} }
} }
val nestedScrollModifier = if (nestedScrollConnection != null) {
Modifier.nestedScroll(nestedScrollConnection)
} else {
Modifier
}
Column( Column(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
@@ -106,14 +119,14 @@ fun GroupChatEmptyContent() {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Box( Box(
modifier = Modifier modifier = nestedScrollModifier
.fillMaxSize() .fillMaxSize()
.pullRefresh(state) .pullRefresh(state)
) { ) {
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) { if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
// 空状态内容(居中) // 空状态内容(居中)
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = nestedScrollModifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// 空状态插图 // 空状态插图
@@ -135,7 +148,8 @@ fun GroupChatEmptyContent() {
} }
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize() state = listState,
modifier = nestedScrollModifier.fillMaxSize()
) { ) {
itemsIndexed( itemsIndexed(
items = viewModel.rooms, items = viewModel.rooms,

View File

@@ -15,8 +15,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -38,6 +40,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.GuestLoginCheckOut import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene import com.aiosman.ravenow.GuestLoginCheckOutScene
@@ -57,16 +61,31 @@ fun UserAgentsList(
agents: List<AgentEntity>, agents: List<AgentEntity>,
onAgentClick: (AgentEntity) -> Unit = {}, onAgentClick: (AgentEntity) -> Unit = {},
onAvatarClick: (AgentEntity) -> Unit = {}, onAvatarClick: (AgentEntity) -> Unit = {},
modifier: Modifier = Modifier hasMore: Boolean,
isLoading: Boolean,
showNoMoreText: Boolean = false,
modifier: Modifier = Modifier,
state: LazyListState,
nestedScrollConnection: NestedScrollConnection? = null
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val listModifier = if (nestedScrollConnection != null) {
modifier.nestedScroll(nestedScrollConnection)
} else {
modifier
}
if (agents.isEmpty()) { if (agents.isEmpty()) {
// 使用带分段控制器的空状态布局 // 使用带分段控制器的空状态布局
AgentEmptyContentWithSegments() Box(
modifier = listModifier.fillMaxSize()
) {
AgentEmptyContentWithSegments()
}
} else { } else {
LazyColumn( LazyColumn(
modifier = modifier.fillMaxSize(), state = state,
modifier = listModifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
items(agents) { agent -> items(agents) { agent ->
@@ -77,9 +96,39 @@ fun UserAgentsList(
) )
} }
// 底部间距 if (isLoading) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
)
}
}
} else if (!hasMore && showNoMoreText) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "没有更多的滋养",
fontSize = 14.sp,
color = AppColors.secondaryText
)
}
}
}
item { item {
Spacer(modifier = Modifier.height(120.dp)) Spacer(modifier = Modifier.height(80.dp))
} }
} }
} }