优化智能体(Agent)展示轮播

- 将原有的分页网格布局,重构为全屏卡片式轮播。
- 每个智能体以带有背景大图的卡片展示,增强视觉效果。
- 在卡片底部增加了渐变遮罩、标题和描述,并在底部中央添加了“聊天”按钮,以改善用户交互和界面美观度。
- 调整了轮播的尺寸和间距,使其自适应屏幕宽度。
This commit is contained in:
2025-11-10 23:34:35 +08:00
parent 1b70cb5cdb
commit 2f41c61b7e

View File

@@ -88,6 +88,10 @@ import androidx.compose.ui.window.DialogProperties
import androidx.compose.foundation.lazy.grid.items as gridItems import androidx.compose.foundation.lazy.grid.items as gridItems
import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.CircularProgressIndicator
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Brush
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.ui.platform.LocalConfiguration
// 检测是否接近列表底部的扩展函数 // 检测是否接近列表底部的扩展函数
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean { fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
@@ -581,72 +585,146 @@ fun AgentCardSquare(
@Composable @Composable
fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel) { fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
if (agentItems.isEmpty()) return
// 每页显示5个agent val pagerState = rememberPagerState(pageCount = { agentItems.size })
val itemsPerPage = 5 val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val totalPages = (agentItems.size + itemsPerPage - 1) / itemsPerPage val cardAspect = 1133.5f / 846.4f
// 外层 LazyColumn 左右各 8dp + Pager contentPadding 左右各 20dp
if (totalPages > 0) { val horizontalPaddings = 56.dp
val pagerState = rememberPagerState(pageCount = { totalPages }) val pagerHeight = (screenWidth - horizontalPaddings) * cardAspect
Column { Column {
// Agent内容
Box( Box(
modifier = Modifier modifier = Modifier
.height(310.dp) .height(pagerHeight)
) { ) {
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 20.dp),
pageSpacing = 0.dp pageSpacing = 12.dp
) { page -> ) { page ->
// 计算当前页面的偏移量 // 缩放效果
val pageOffset = ( val pageOffset = (
(pagerState.currentPage - page) + pagerState (pagerState.currentPage - page) + pagerState
.currentPageOffsetFraction .currentPageOffsetFraction
).coerceIn(-1f, 1f) ).coerceIn(-1f, 1f)
val scale = 1f - (0.06f * kotlin.math.abs(pageOffset))
// 根据偏移量计算缩放比例 AgentLargeCard(
val scale = 1f - (0.1f * kotlin.math.abs(pageOffset)) agentItem = agentItems[page],
AgentPage(
viewModel = viewModel, viewModel = viewModel,
agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage), navController = LocalNavController.current,
page = page,
modifier = Modifier modifier = Modifier
.height(310.dp)
.graphicsLayer { .graphicsLayer {
scaleX = scale scaleX = scale
scaleY = scale scaleY = scale
}, }
navController = LocalNavController.current, )
}
}
}
}
@SuppressLint("SuspiciousIndentation")
@Composable
fun AgentLargeCard(
agentItem: AgentItem,
viewModel: AgentViewModel,
navController: NavHostController,
modifier: Modifier = Modifier
) {
val AppColors = LocalAppTheme.current
var lastClickTime by remember { mutableStateOf(0L) }
Box(
modifier = modifier
.fillMaxWidth()
.aspectRatio(846.4f / 1133.5f)
.clip(RoundedCornerShape(24.dp))
.noRippleClickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
viewModel.goToProfile(agentItem.openId, navController)
}) {
lastClickTime = System.currentTimeMillis()
}
}
) {
// 背景大图
CustomAsyncImage(
imageUrl = agentItem.avatar,
contentDescription = agentItem.title,
modifier = Modifier.fillMaxSize(),
contentScale = androidx.compose.ui.layout.ContentScale.Crop
)
// 底部渐变与文字
Box(
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.background(
Brush.verticalGradient(
0f to Color.Transparent,
1f to Color(0xB2000000)
)
)
.padding(16.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 56.dp) // 为底部聊天按钮预留空间
) {
androidx.compose.material3.Text(
text = agentItem.title,
color = Color.White,
fontSize = 20.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W700,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(8.dp))
androidx.compose.material3.Text(
text = agentItem.desc,
color = Color.White.copy(alpha = 0.92f),
fontSize = 14.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
) )
} }
} }
// 指示器 // 底部居中 Chat 按钮
Row(
modifier = Modifier
.fillMaxWidth()
.height(30.dp)
.padding(top = 12.dp),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.Center
) {
repeat(totalPages) { index ->
Box( Box(
modifier = Modifier modifier = Modifier
.padding(horizontal = 4.dp) .align(Alignment.BottomCenter)
.size(3.dp) .padding(bottom = 16.dp)
.background( .widthIn(min = 180.dp)
color = if (pagerState.currentPage == index) AppColors.text else AppColors.secondaryText.copy( .fillMaxWidth(0.65f)
alpha = 0.3f .height(44.dp)
), .background(AppColors.text, RoundedCornerShape(22.dp))
shape = androidx.compose.foundation.shape.CircleShape .noRippleClickable {
) if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
) if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
viewModel.createSingleChat(agentItem.openId)
viewModel.goToChatAi(agentItem.openId, navController)
} }
}) {
lastClickTime = System.currentTimeMillis()
} }
},
contentAlignment = Alignment.Center
) {
androidx.compose.material3.Text(
text = stringResource(R.string.chat),
color = AppColors.background,
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600
)
} }
} }
} }