优化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.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text 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.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -69,13 +74,24 @@ import com.aiosman.ravenow.utils.DebounceUtils
import com.aiosman.ravenow.utils.ResourceCleanupManager import com.aiosman.ravenow.utils.ResourceCleanupManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.foundation.lazy.LazyRow 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.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.lazy.grid.items as gridItems
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.grid.items
@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 @Composable
fun Agent() { fun Agent() {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -89,7 +105,6 @@ fun Agent() {
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
val viewModel: AgentViewModel = viewModel() val viewModel: AgentViewModel = viewModel()
val scrollState = rememberScrollState()
// 确保推荐Agent数据已加载 // 确保推荐Agent数据已加载
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -107,163 +122,92 @@ fun Agent() {
} }
} }
Column( val agentItems = viewModel.agentItems
modifier = Modifier var selectedTabIndex by remember { mutableStateOf(0) }
.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
) {
androidx.compose.material3.Text(
text = "Rave AI",
fontSize = 20.sp,
fontWeight = FontWeight.W900,
color = AppColors.text,
modifier = Modifier
.align(Alignment.CenterVertically)
)
Spacer(modifier = Modifier.weight(1f)) // 无限滚动状态
val listState = rememberLazyListState()
Image( // 创建一个可观察的滚动到底部状态
painter = painterResource(id = R.drawable.rider_pro_nav_search), val isScrolledToEnd by remember {
contentDescription = "search", derivedStateOf {
modifier = Modifier listState.isScrolledToEnd()
.size(24.dp)
.noRippleClickable {
navController.navigate(NavigationRoute.Search.route)
},
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
// )
Column( // 检测滚动到底部并加载更多数据
modifier = Modifier LaunchedEffect(isScrolledToEnd) {
.fillMaxWidth() if (isScrolledToEnd && !viewModel.isLoadingMore && agentItems.isNotEmpty() && viewModel.hasMoreData) {
.padding(vertical = 8.dp) viewModel.loadMoreAgents()
}
}
) { Scaffold(
topBar = {
TopAppBar(
title = {
androidx.compose.material3.Text(
text = "Rave AI",
fontSize = 20.sp,
fontWeight = FontWeight.W900,
color = AppColors.text
)
},
actions = {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search",
modifier = Modifier
.size(24.dp)
.noRippleClickable {
navController.navigate(NavigationRoute.Search.route)
},
colorFilter = ColorFilter.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
)
) {
// // 标题 // 类别标签页 - 吸顶
// Row( stickyHeader(key = "category_tabs") {
// verticalAlignment = Alignment.CenterVertically, Column(
// 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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .background(AppColors.background)
.padding(bottom = 16.dp), .padding(top = 4.dp, bottom = 8.dp)
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) { ) {
item { LazyRow(
CustomTabItem( modifier = Modifier
text = stringResource(R.string.agent_recommend), .fillMaxWidth()
isSelected = selectedTabIndex == 0, .wrapContentHeight()
onClick = { .padding(bottom = 8.dp),
selectedTabIndex = 0 horizontalArrangement = Arrangement.Start,
viewModel.loadAllAgents() verticalAlignment = Alignment.CenterVertically
} ) {
)
}
item {
TabSpacer()
}
// 动态添加分类标签
viewModel.categories.take(4).forEachIndexed { index, category ->
item { item {
CustomTabItem( CustomTabItem(
text = category.name, text = stringResource(R.string.agent_recommend),
isSelected = selectedTabIndex == index + 1, isSelected = selectedTabIndex == 0,
onClick = { onClick = {
selectedTabIndex = index + 1 selectedTabIndex = 0
viewModel.loadAgentsByCategory(category.id) viewModel.loadAllAgents()
} }
) )
} }
@@ -271,117 +215,177 @@ fun Agent() {
item { item {
TabSpacer() TabSpacer()
} }
}
item { // 动态添加分类标签
CustomTabItem( viewModel.categories.take(4).forEachIndexed { index, category ->
text = "scenes", item {
isSelected = selectedTabIndex == 1, CustomTabItem(
onClick = { text = category.name,
selectedTabIndex = 1 isSelected = selectedTabIndex == index + 1,
onClick = {
selectedTabIndex = index + 1
viewModel.loadAgentsByCategory(category.id)
}
)
} }
)
}
item { item {
TabSpacer() TabSpacer()
}
item {
CustomTabItem(
text = "voices",
isSelected = selectedTabIndex == 6,
onClick = {
selectedTabIndex = 6
} }
) }
}
item { item {
TabSpacer() CustomTabItem(
} text = "scenes",
isSelected = selectedTabIndex == 1,
onClick = {
selectedTabIndex = 1
}
)
}
item { item {
CustomTabItem( TabSpacer()
text = "anime", }
isSelected = selectedTabIndex == 7,
onClick = {
selectedTabIndex = 7
}
)
}
item { item {
TabSpacer() CustomTabItem(
} text = "voices",
isSelected = selectedTabIndex == 6,
onClick = {
selectedTabIndex = 6
}
)
}
item { item {
CustomTabItem( TabSpacer()
text = "assist", }
isSelected = selectedTabIndex == 8,
onClick = { item {
selectedTabIndex = 8 CustomTabItem(
} text = "anime",
) isSelected = selectedTabIndex == 7,
onClick = {
selectedTabIndex = 7
}
)
}
item {
TabSpacer()
}
item {
CustomTabItem(
text = "assist",
isSelected = selectedTabIndex == 8,
onClick = {
selectedTabIndex = 8
}
)
}
} }
} }
}
when { // 推荐内容区域
selectedTabIndex == 0 -> { item {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel) Column(
} modifier = Modifier
selectedTabIndex in 1..viewModel.categories.size -> { .fillMaxWidth()
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel) .padding(vertical = 8.dp)
} ) {
else -> { when {
val shuffledAgents = viewModel.agentItems.shuffled().take(15) selectedTabIndex == 0 -> {
AgentViewPagerSection(agentItems = shuffledAgents, viewModel) AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
}
selectedTabIndex in 1..viewModel.categories.size -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
}
else -> {
val shuffledAgents = viewModel.agentItems.shuffled().take(15)
AgentViewPagerSection(agentItems = shuffledAgents, viewModel)
}
} }
} }
} }
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
// center the tabs
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
) {
Image(
painter = painterResource(R.mipmap.rider_pro_agent2),
contentDescription = "agent",
modifier = Modifier.size(28.dp),
) // "发现更多" 标题 - 吸顶
Spacer(modifier = Modifier.width(4.dp)) stickyHeader(key = "discover_more") {
androidx.compose.material3.Text( Row(
text = stringResource(R.string.agent_find), modifier = Modifier
fontSize = 16.sp, .fillMaxWidth()
fontWeight = androidx.compose.ui.text.font.FontWeight.W600, .background(AppColors.background)
color = AppColors.text .padding(top = 8.dp, bottom = 12.dp),
) horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
} ) {
Spacer(modifier = Modifier.height(16.dp)) Image(
Column( painter = painterResource(R.mipmap.rider_pro_agent2),
modifier = Modifier contentDescription = "agent",
.fillMaxWidth() modifier = Modifier.size(28.dp),
.weight(1f)
) {
val agentItems = viewModel.agentItems
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(32.dp)
) {
items(agentItems) { agentItem ->
AgentCardSquare(
agentItem = agentItem,
viewModel = viewModel,
navController = LocalNavController.current
) )
Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text(
text = stringResource(R.string.agent_find),
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text
)
}
}
// Agent网格 - 使用行式布局
items(
items = agentItems.chunked(2),
key = { row -> row.firstOrNull()?.openId ?: "" }
) { rowItems ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
rowItems.forEach { agentItem ->
Box(
modifier = Modifier.weight(1f)
) {
AgentCardSquare(
agentItem = agentItem,
viewModel = viewModel,
navController = LocalNavController.current
)
}
}
// 如果这一行只有一个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,42 +38,82 @@ object AgentViewModel: ViewModel() {
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
private set 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 { init {
loadAgentData() loadAgentData()
loadCategories() loadCategories()
} }
private fun loadAgentData(categoryId: Int? = null) { private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false) {
viewModelScope.launch { viewModelScope.launch {
isLoading = true if (isLoadMore) {
isLoadingMore = true
} else {
isLoading = true
// 重置分页状态
currentPage = 1
hasMoreData = true
currentCategoryId = categoryId
}
errorMessage = null errorMessage = null
try { try {
val response = if (categoryId != null) { val response = if (categoryId != null) {
// 根据分类ID获取智能体 // 根据分类ID获取智能体
apiClient.getAgent( apiClient.getAgent(
page = 1, page = page,
pageSize = 20, pageSize = pageSize,
withWorkflow = 1, withWorkflow = 1,
categoryIds = listOf(categoryId) categoryIds = listOf(categoryId)
) )
} else { } else {
// 获取所有智能体 // 获取所有智能体
apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1) apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1)
} }
if (response.isSuccessful) { if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList<Agent>() val responseData = response.body()?.data
val agents = responseData?.list ?: emptyList<Agent>()
agentItems = agents.map { agent -> val newAgentItems = agents.map { agent ->
AgentItem.fromAgent(agent) AgentItem.fromAgent(agent)
} }
if (isLoadMore) {
// 加载更多:追加到现有列表
agentItems = agentItems + newAgentItems
currentPage = page
} else {
// 首次加载或刷新:替换整个列表
agentItems = newAgentItems
currentPage = 1
}
// 检查是否还有更多数据
hasMoreData = agents.size >= pageSize
} else { } else {
errorMessage = "获取Agent数据失败: ${response.code()}" errorMessage = "获取Agent数据失败: ${response.code()}"
} }
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}" errorMessage = "网络请求失败: ${e.message}"
} finally { } finally {
isLoading = false if (isLoadMore) {
isLoadingMore = false
} else {
isLoading = false
}
} }
} }
} }
@@ -115,6 +155,23 @@ object AgentViewModel: ViewModel() {
fun loadAllAgents() { fun loadAllAgents() {
loadAgentData() loadAgentData()
} }
/**
* 加载更多Agent数据
*/
fun loadMoreAgents() {
// 检查是否正在加载或没有更多数据
if (isLoadingMore || !hasMoreData) {
return
}
val nextPage = currentPage + 1
loadAgentData(
categoryId = currentCategoryId,
page = nextPage,
isLoadMore = true
)
}
fun createSingleChat( fun createSingleChat(
openId: String, openId: String,
) { ) {
@@ -176,6 +233,10 @@ object AgentViewModel: ViewModel() {
errorMessage = null errorMessage = null
isRefreshing = false isRefreshing = false
isLoading = false isLoading = false
isLoadingMore = false
currentPage = 1
hasMoreData = true
currentCategoryId = null
} }
} }