优化AI界面,添加分页加载功能,支持动态加载更多智能体数据;重构UI布局

This commit is contained in:
2025-09-23 11:57:11 +08:00
parent 742410223c
commit 1a24136c35
2 changed files with 323 additions and 258 deletions

View File

@@ -28,9 +28,14 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -69,13 +74,24 @@ import com.aiosman.ravenow.utils.DebounceUtils
import com.aiosman.ravenow.utils.ResourceCleanupManager
import kotlinx.coroutines.launch
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.items as gridItems
@OptIn( ExperimentalFoundationApi::class)
// 检测是否接近列表底部的扩展函数
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
val layoutInfo = this.layoutInfo
val totalItemsCount = layoutInfo.totalItemsCount
val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
return lastVisibleItemIndex >= (totalItemsCount - buffer)
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun Agent() {
val AppColors = LocalAppTheme.current
@@ -89,7 +105,6 @@ fun Agent() {
var scope = rememberCoroutineScope()
val viewModel: AgentViewModel = viewModel()
val scrollState = rememberScrollState()
// 确保推荐Agent数据已加载
LaunchedEffect(Unit) {
@@ -107,37 +122,38 @@ fun Agent() {
}
}
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
.padding(
top = statusBarPaddingValues.calculateTopPadding(),
bottom = navigationBarPaddings,
start = 16.dp,
end = 16.dp
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier
.wrapContentHeight()
.height(44.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
val agentItems = viewModel.agentItems
var selectedTabIndex by remember { mutableStateOf(0) }
// 无限滚动状态
val listState = rememberLazyListState()
// 创建一个可观察的滚动到底部状态
val isScrolledToEnd by remember {
derivedStateOf {
listState.isScrolledToEnd()
}
}
// 检测滚动到底部并加载更多数据
LaunchedEffect(isScrolledToEnd) {
if (isScrolledToEnd && !viewModel.isLoadingMore && agentItems.isNotEmpty() && viewModel.hasMoreData) {
viewModel.loadMoreAgents()
}
}
Scaffold(
topBar = {
TopAppBar(
title = {
androidx.compose.material3.Text(
text = "Rave AI",
fontSize = 20.sp,
fontWeight = FontWeight.W900,
color = AppColors.text,
modifier = Modifier
.align(Alignment.CenterVertically)
color = AppColors.text
)
Spacer(modifier = Modifier.weight(1f))
},
actions = {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search",
@@ -148,95 +164,40 @@ fun Agent() {
},
colorFilter = ColorFilter.tint(AppColors.text)
)
}
Spacer(modifier = Modifier.height(15.dp))
// // 搜索框
// Row(
// modifier = Modifier
// .height(36.dp)
// .weight(1f)
// .clip(shape = RoundedCornerShape(8.dp))
// .background(AppColors.inputBackground)
// .padding(horizontal = 8.dp, vertical = 0.dp)
// .noRippleClickable {
// // 搜索框点击事件
// },
// verticalAlignment = Alignment.CenterVertically
// ) {
// Icon(
// painter = painterResource(id = R.drawable.rider_pro_nav_search),
// contentDescription = null,
// tint = AppColors.inputHint
// )
// Box {
// Text(
// text = stringResource(R.string.search),
// modifier = Modifier.padding(start = 8.dp),
// color = AppColors.inputHint,
// fontSize = 17.sp
// )
// }
// }
// Spacer(modifier = Modifier.width(16.dp))
// // 创建智能体
// Icon(
// modifier = Modifier
// .size(36.dp)
// .noRippleClickable {
// if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
// // 检查游客模式,如果是游客则跳转登录
// if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_AGENT)) {
// navController.navigate(NavigationRoute.Login.route)
// } else {
// // 导航到添加智能体页面
// navController.navigate(
// NavigationRoute.AddAgent.route
// )
// }
// }) {
// lastClickTime = System.currentTimeMillis()
// }
// },
// painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
// contentDescription = null,
// tint = AppColors.text
// )
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = AppColors.background
)
)
},
containerColor = AppColors.background,
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { paddingValues ->
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(
bottom = navigationBarPaddings,
start = 16.dp,
end = 16.dp
)
) {
// 类别标签页 - 吸顶
stickyHeader(key = "category_tabs") {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.background(AppColors.background)
.padding(top = 4.dp, bottom = 8.dp)
) {
// // 标题
// Row(
// verticalAlignment = Alignment.CenterVertically,
// modifier = Modifier.padding(bottom = 12.dp)
// ) {
// Image(
// painter = painterResource(R.mipmap.rider_pro_agent2),
// contentDescription = "agent",
// modifier = Modifier.size(28.dp),
//
// )
// Spacer(modifier = Modifier.width(4.dp))
// androidx.compose.material3.Text(
// text = stringResource(R.string.agent_recommend_agent),
// fontSize = 16.sp,
// fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
// color = AppColors.text
// )
// }
var selectedTabIndex by remember { mutableStateOf(0) }
// 标签页
LazyRow(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(bottom = 16.dp),
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
@@ -325,7 +286,15 @@ fun Agent() {
)
}
}
}
}
// 推荐内容区域
item {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
when {
selectedTabIndex == 0 -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
@@ -339,12 +308,16 @@ fun Agent() {
}
}
}
}
// "发现更多" 标题 - 吸顶
stickyHeader(key = "discover_more") {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
// center the tabs
.background(AppColors.background)
.padding(top = 8.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
) {
@@ -352,7 +325,6 @@ fun Agent() {
painter = painterResource(R.mipmap.rider_pro_agent2),
contentDescription = "agent",
modifier = Modifier.size(28.dp),
)
Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text(
@@ -361,22 +333,24 @@ fun Agent() {
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text
)
}
Spacer(modifier = Modifier.height(16.dp))
Column(
}
// Agent网格 - 使用行式布局
items(
items = agentItems.chunked(2),
key = { row -> row.firstOrNull()?.openId ?: "" }
) { rowItems ->
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
val agentItems = viewModel.agentItems
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(32.dp)
rowItems.forEach { agentItem ->
Box(
modifier = Modifier.weight(1f)
) {
items(agentItems) { agentItem ->
AgentCardSquare(
agentItem = agentItem,
viewModel = viewModel,
@@ -384,6 +358,36 @@ fun Agent() {
)
}
}
// 如果这一行只有一个item添加一个空的占位符
if (rowItems.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
}
}
// 加载更多指示器
if (viewModel.isLoadingMore) {
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
horizontalArrangement = Arrangement.Center
) {
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.text,
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(12.dp))
androidx.compose.material3.Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
)
}
}
}
}
}
}

View File

@@ -38,45 +38,85 @@ object AgentViewModel: ViewModel() {
var isLoading by mutableStateOf(false)
private set
// 分页相关状态
var isLoadingMore by mutableStateOf(false)
private set
var currentPage by mutableStateOf(1)
private set
var hasMoreData by mutableStateOf(true)
private set
private val pageSize = 20
private var currentCategoryId: Int? = null
init {
loadAgentData()
loadCategories()
}
private fun loadAgentData(categoryId: Int? = null) {
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false) {
viewModelScope.launch {
if (isLoadMore) {
isLoadingMore = true
} else {
isLoading = true
// 重置分页状态
currentPage = 1
hasMoreData = true
currentCategoryId = categoryId
}
errorMessage = null
try {
val response = if (categoryId != null) {
// 根据分类ID获取智能体
apiClient.getAgent(
page = 1,
pageSize = 20,
page = page,
pageSize = pageSize,
withWorkflow = 1,
categoryIds = listOf(categoryId)
)
} else {
// 获取所有智能体
apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1)
}
if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList<Agent>()
agentItems = agents.map { agent ->
val responseData = response.body()?.data
val agents = responseData?.list ?: emptyList<Agent>()
val newAgentItems = agents.map { agent ->
AgentItem.fromAgent(agent)
}
if (isLoadMore) {
// 加载更多:追加到现有列表
agentItems = agentItems + newAgentItems
currentPage = page
} else {
// 首次加载或刷新:替换整个列表
agentItems = newAgentItems
currentPage = 1
}
// 检查是否还有更多数据
hasMoreData = agents.size >= pageSize
} else {
errorMessage = "获取Agent数据失败: ${response.code()}"
}
} catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}"
} finally {
if (isLoadMore) {
isLoadingMore = false
} else {
isLoading = false
}
}
}
}
private fun loadCategories() {
viewModelScope.launch {
@@ -115,6 +155,23 @@ object AgentViewModel: ViewModel() {
fun loadAllAgents() {
loadAgentData()
}
/**
* 加载更多Agent数据
*/
fun loadMoreAgents() {
// 检查是否正在加载或没有更多数据
if (isLoadingMore || !hasMoreData) {
return
}
val nextPage = currentPage + 1
loadAgentData(
categoryId = currentCategoryId,
page = nextPage,
isLoadMore = true
)
}
fun createSingleChat(
openId: String,
) {
@@ -176,6 +233,10 @@ object AgentViewModel: ViewModel() {
errorMessage = null
isRefreshing = false
isLoading = false
isLoadingMore = false
currentPage = 1
hasMoreData = true
currentCategoryId = null
}
}