首页UI调整

This commit is contained in:
weber
2025-08-14 19:04:20 +08:00
parent d8c091b19b
commit 112efa74e7
16 changed files with 993 additions and 95 deletions

View File

@@ -18,8 +18,8 @@ import com.aiosman.ravenow.ui.favourite.FavouriteNoticeViewModel
import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel
import com.aiosman.ravenow.ui.index.IndexViewModel import com.aiosman.ravenow.ui.index.IndexViewModel
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic.DynamicViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.Explore import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.Explore
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.ExploreViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentViewModel import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
@@ -152,7 +152,7 @@ object AppState {
fun ReloadAppState(context: Context) { fun ReloadAppState(context: Context) {
// 重置动态列表页面 // 重置动态列表页面
TimelineMomentViewModel.ResetModel() TimelineMomentViewModel.ResetModel()
ExploreViewModel.ResetModel() DynamicViewModel.ResetModel()
HotMomentViewModel.ResetModel() HotMomentViewModel.ResetModel()
// 重置我的页面 // 重置我的页面

View File

@@ -110,11 +110,13 @@ fun HotAgent() {
key = { idx -> idx } key = { idx -> idx }
) { idx -> ) { idx ->
val agentItem = agentList[idx] val agentItem = agentList[idx]
AgentCard(agentEntity = agentItem, AgentCard(
agentEntity = agentItem,
onClick = { onClick = {
model.createSingleChat(agentItem.openId) model.createSingleChat(agentItem.openId)
model.goToChatAi(agentItem.openId,navController) model.goToChatAi(agentItem.openId,navController)
}) },
)
} }
// 加载更多指示器 // 加载更多指示器

View File

@@ -27,13 +27,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
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.lifecycle.viewModelScope
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.AgentCard import com.aiosman.ravenow.ui.composables.AgentCard
import com.aiosman.ravenow.ui.navigateToChat
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -113,11 +110,14 @@ fun MineAgent() {
key = { idx -> idx } key = { idx -> idx }
) { idx -> ) { idx ->
val agentItem = agentList[idx] val agentItem = agentList[idx]
AgentCard(agentEntity = agentItem, AgentCard(
agentEntity = agentItem,
onClick = { onClick = {
model.createSingleChat(agentItem.openId) model.createSingleChat(agentItem.openId)
model.goToChatAi(agentItem.openId,navController) model.goToChatAi(agentItem.openId,navController)
}) },
)
} }
// 加载更多指示器 // 加载更多指示器

View File

@@ -228,10 +228,10 @@ fun MomentsList() {
) { ) {
when (it) { when (it) {
0 -> { 0 -> {
Dynamic() Explore()
} }
1 -> { 1 -> {
Explore() Dynamic()
} }
2 -> { 2 -> {

View File

@@ -21,11 +21,86 @@ import com.aiosman.ravenow.ui.composables.MomentCard
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
* 探索 * 动态列表
*/ */
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun Dynamic() { fun Dynamic() {
val model = DynamicViewModel val model = DynamicViewModel
var moments = model.moments
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(
pullRefresh = true
)
})
val listState = rememberLazyListState()
// observe list scrolling
val reachedBottom by remember {
derivedStateOf {
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2
}
}
// load more if scrolled to bottom
LaunchedEffect(reachedBottom) {
if (reachedBottom) {
model.loadMore()
}
}
LaunchedEffect(Unit) {
model.refreshPager()
}
Column(
modifier = Modifier
.fillMaxSize()
) {
Box(Modifier.pullRefresh(state)) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState
) {
items(
moments.size,
key = { idx -> idx }
) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem,
onAddComment = {
scope.launch {
model.onAddComment(momentItem.id)
}
},
onLikeClick = {
scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
}
}
},
onFavoriteClick = {
scope.launch {
if (momentItem.isFavorite) {
model.unfavoriteMoment(momentItem.id)
} else {
model.favoriteMoment(momentItem.id)
}
}
},
onFollowClick = {
model.followAction(momentItem)
},
showFollowButton = true
)
}
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
} }

View File

@@ -1,12 +1,17 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic package com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic
import androidx.lifecycle.ViewModel
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
object DynamicViewModel : ViewModel() { object DynamicViewModel : BaseMomentModel() {
init {
EventBus.getDefault().register(this)
}
override fun extraArgs(): MomentLoaderExtraArgs {
return MomentLoaderExtraArgs(explore = true)
}
} }

View File

@@ -1,106 +1,790 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre package com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Card
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.aiosman.ravenow.ui.composables.MomentCard import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.draw.clip
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.data.Room
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.ui.composables.AgentCard
// Banner数据类
data class BannerItem(
val id: Int,
val title: String,
val subtitle: String,
val imageUrl: String,
val backgroundImageUrl: String,
val userCount: Int,
val agentName: String
) {
companion object {
fun fromRoom(room: Room): BannerItem {
return BannerItem(
id = room.id,
title = room.name ?: "聊天室",
subtitle = room.description ?: "欢迎加入我们的聊天室",
imageUrl = room.avatar ?: "",
backgroundImageUrl = "${ApiClient.BASE_API_URL+"/outside"}${room.recommendBanner}"+"?token="+"${AppStore.token}" ?: "",
userCount = room.userCount,
agentName = room.creator.profile.nickname
)
}
}
}
// Agent数据类
data class AgentItem(
val id: Int,
val title: String,
val desc: String,
val avatar: String,
val useCount: Int,
val author: String
) {
companion object {
fun fromAgent(agent: Agent): AgentItem {
return AgentItem(
id = agent.id,
title = agent.title,
desc = agent.desc,
avatar = "${ApiClient.BASE_API_URL+"/outside"}${agent.avatar}"+"?token="+"${AppStore.token}",
useCount = agent.useCount,
author = agent.author
)
}
}
}
/** /**
* 动态列表 * 探索页面
*/ */
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun Explore() { fun Explore() {
val model = ExploreViewModel val AppColors = LocalAppTheme.current
var moments = model.moments
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = { val viewModel: ExploreViewModel = viewModel()
model.refreshPager(
pullRefresh = true // 模拟刷新状态
var isRefreshing by remember { mutableStateOf(false) }
// 监听ViewModel的刷新状态
LaunchedEffect(viewModel.isRefreshing) {
isRefreshing = viewModel.isRefreshing
}
// 处理刷新
LaunchedEffect(isRefreshing) {
if (isRefreshing) {
viewModel.refreshBannerData()
viewModel.refreshAgentData()
// 模拟网络请求延迟
kotlinx.coroutines.delay(2000)
isRefreshing = false
}
}
val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing,
onRefresh = {
scope.launch {
isRefreshing = true
viewModel.refreshBannerData()
viewModel.refreshAgentData()
// 模拟网络请求延迟
kotlinx.coroutines.delay(2000)
isRefreshing = false
}
}
) )
})
val listState = rememberLazyListState()
// observe list scrolling @Composable
val reachedBottom by remember { fun AgentCard2(agentItem: AgentItem) {
derivedStateOf { val AppColors = LocalAppTheme.current
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2 Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 左侧头像
Box(
modifier = Modifier
.size(48.dp)
.background(Color(0xFFF5F5F5), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
if (agentItem.avatar.isNotEmpty()) {
CustomAsyncImage(
imageUrl = agentItem.avatar,
contentDescription = "Agent头像",
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(24.dp)),
contentScale = androidx.compose.ui.layout.ContentScale.Crop
)
} else {
Image(
painter = painterResource(R.mipmap.rider_pro_agent),
contentDescription = "默认头像",
modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.secondaryText)
)
} }
} }
// load more if scrolled to bottom Spacer(modifier = Modifier.width(12.dp))
LaunchedEffect(reachedBottom) {
if (reachedBottom) { // 中间文字内容
model.loadMore() Column(
modifier = Modifier
.weight(1f)
.padding(end = 8.dp)
) {
// 标题
Text(
text = agentItem.title,
fontSize = 14.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text,
maxLines = 1,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(4.dp))
// 描述
Text(
text = agentItem.desc,
fontSize = 12.sp,
color = AppColors.secondaryText,
maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
)
}
// 右侧聊天按钮
androidx.compose.material3.Button(
onClick = {
// 聊天逻辑
},
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
containerColor = AppColors.main,
contentColor = Color.White
),
shape = RoundedCornerShape(20.dp),
modifier = Modifier.size(width = 60.dp, height = 32.dp)
) {
Text(
text = "聊天",
fontSize = 12.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
} }
} }
LaunchedEffect(Unit) {
model.refreshPager()
} }
@Composable
fun AgentPage(agentItems: List<AgentItem>, page: Int) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 0.dp)
) {
// 显示3个agent
agentItems.forEachIndexed { index, agentItem ->
AgentCard2(agentItem = agentItem)
if (index < agentItems.size - 1) {
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AgentViewPagerSection(agentItems: List<AgentItem>) {
val AppColors = LocalAppTheme.current
// 每页显示3个agent
val itemsPerPage = 3
val totalPages = (agentItems.size + itemsPerPage - 1) / itemsPerPage
if (totalPages > 0) {
val pagerState = rememberPagerState(pageCount = { totalPages })
Column {
// Agent内容
Box(
modifier = Modifier
.fillMaxWidth()
) { ) {
Box(Modifier.pullRefresh(state)) { HorizontalPager(
LazyColumn( state = pagerState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize()
state = listState ) { page ->
) { AgentPage(
items( agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage),
moments.size, page = page
key = { idx -> idx }
) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem,
onAddComment = {
scope.launch {
model.onAddComment(momentItem.id)
}
},
onLikeClick = {
scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
}
}
},
onFavoriteClick = {
scope.launch {
if (momentItem.isFavorite) {
model.unfavoriteMoment(momentItem.id)
} else {
model.favoriteMoment(momentItem.id)
}
}
},
onFollowClick = {
model.followAction(momentItem)
},
showFollowButton = true
) )
} }
} }
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
// 指示器
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.Center
) {
repeat(totalPages) { index ->
Box(
modifier = Modifier
.padding(horizontal = 4.dp)
.size(3.dp)
.background(
color = if (pagerState.currentPage == index) AppColors.main else AppColors.secondaryText.copy(alpha = 0.3f),
shape = androidx.compose.foundation.shape.CircleShape
)
)
} }
} }
} }
}
}
@Composable
fun HotChatRoomCard(roomItem: BannerItem) {
val AppColors = LocalAppTheme.current
Card(
modifier = Modifier
.width(160.dp)
.height(80.dp),
shape = RoundedCornerShape(12.dp),
elevation = androidx.compose.material3.CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Row(
modifier = Modifier
.fillMaxSize()
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 左侧图标
Box(
modifier = Modifier
.size(32.dp)
.background(
color = when (roomItem.id % 5) {
0 -> Color(0xFFE3F2FD) // 浅蓝色
1 -> Color(0xFFF3E5F5) // 浅紫色
2 -> Color(0xFFFFF3E0) // 浅橙色
3 -> Color(0xFFFCE4EC) // 浅粉色
else -> Color(0xFFFFF8E1) // 浅黄色
},
shape = RoundedCornerShape(8.dp)
),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.rider_pro_group_chat),
contentDescription = "chat",
modifier = Modifier.size(16.dp),
colorFilter = ColorFilter.tint(Color.White)
)
}
Spacer(modifier = Modifier.width(8.dp))
// 右侧文字
Text(
text = roomItem.title,
fontSize = 12.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500,
color = AppColors.text,
maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
}
}
}
@Composable
fun HotChatRoomSection(roomItems: List<BannerItem>) {
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(12.dp)
) {
items(roomItems.size) { index ->
HotChatRoomCard(roomItem = roomItems[index])
}
}
}
@Composable
fun BannerCard(bannerItem: BannerItem) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
Card(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 0.dp),
shape = RoundedCornerShape(20.dp),
elevation = androidx.compose.material3.CardDefaults.cardElevation(defaultElevation = 0.dp)
) {
Box(
modifier = Modifier.fillMaxSize()
) {
// 背景图片
if (bannerItem.backgroundImageUrl.isNotEmpty()) {
CustomAsyncImage(
imageUrl = bannerItem.backgroundImageUrl,
contentDescription = "背景图片",
modifier = Modifier.fillMaxSize(),
contentScale = androidx.compose.ui.layout.ContentScale.Crop
)
} else {
// 如果没有背景图片,使用默认渐变背景
Box(
modifier = Modifier
.fillMaxSize()
.background(
brush = androidx.compose.ui.graphics.Brush.linearGradient(
colors = listOf(
Color(0xFF4CAF50),
Color(0xFF4CAF50).copy(alpha = 0.8f)
)
)
)
)
}
// 内容
Column(
modifier = Modifier
.fillMaxSize()
.padding(20.dp),
verticalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween
) {
// 顶部:用户数量
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.rider_pro_nav_profile),
contentDescription = "chat",
modifier = Modifier.size(16.dp),
colorFilter = ColorFilter.tint(Color.White)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "${bannerItem.userCount}人在聊",
fontSize = 12.sp,
color = Color.White,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
// 底部:标题和描述
Column {
Text(
text = bannerItem.title,
fontSize = 24.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = bannerItem.subtitle,
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.9f),
maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Agent信息
Row(
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(32.dp)
.background(Color.White.copy(alpha = 0.2f), RoundedCornerShape(16.dp)),
contentAlignment = Alignment.Center
) {
Text(
text = bannerItem.agentName.firstOrNull().toString(),
fontSize = 14.sp,
color = Color.White,
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
)
}
Spacer(modifier = Modifier.width(8.dp))
Text(
text = bannerItem.agentName,
fontSize = 14.sp,
color = Color.White,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
// 进入按钮
androidx.compose.material3.Button(
onClick = {
// 进入房间逻辑
},
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
containerColor = Color.White.copy(alpha = 0.9f),
contentColor = Color(0xFF4CAF50)
),
shape = RoundedCornerShape(20.dp)
) {
Text(
text = "进入",
fontSize = 14.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600
)
}
}
}
}
}
}
}
Box(
modifier = Modifier
.fillMaxSize()
.pullRefresh(pullRefreshState)
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp)
) {
// 可以添加更多不同高度的内容项
// 四个圆角布局
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 6.dp),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceEvenly
) {
// 第一个
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color(0XFFC686FF), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.mipmap.rider_pro_group),
contentDescription = "创建群聊",
modifier = Modifier.size(24.dp),
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "创建群聊",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
// 第二个
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color(0xFF94f9f2), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.mipmap.rider_pro_agent),
contentDescription = "创建Agent",
modifier = Modifier.size(24.dp),
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "创建Agent",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
// 第三个
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color(0xFFfafd5d), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.mipmap.rider_pro_release),
contentDescription = "发布动态",
modifier = Modifier.size(24.dp),
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "发布动态",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
// 第四个
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color(0xFFfc724b), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.mipmap.rider_pro_fire),
contentDescription = "热门Agents",
modifier = Modifier.size(24.dp),
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "热门Agents",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BannerSection(bannerItems: List<BannerItem>) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(pageCount = { bannerItems.size })
Column {
// Banner内容
Box(
modifier = Modifier
.fillMaxWidth()
.height(362.dp)
) {
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { page ->
val bannerItem = bannerItems[page]
BannerCard(bannerItem = bannerItem)
}
}
// 指示器
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.Center
) {
bannerItems.forEachIndexed { index, _ ->
Box(
modifier = Modifier
.padding(horizontal = 4.dp)
.size(3.dp)
.background(
color = if (pagerState.currentPage == index) AppColors.main else AppColors.secondaryText.copy(alpha = 0.3f),
shape = androidx.compose.foundation.shape.CircleShape
)
)
}
}
}
}
// 第二块区域Banner
item {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
// 标题
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(bottom = 12.dp)
) {
Image(
painter = painterResource(R.mipmap.rider_pro_fire2),
contentDescription = "fire",
modifier = Modifier.size(28.dp),
colorFilter = ColorFilter.tint(Color(0xFFEF4444))
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "正在高能对话中",
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text
)
}
// Banner
BannerSection(bannerItems = viewModel.bannerItems)
}
}
// 第三块区域推荐Agent
item {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.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))
Text(
text = "推荐Agent",
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text
)
}
// Agent ViewPager
AgentViewPagerSection(agentItems = viewModel.agentItems)
}
}
// 第四块区域:热门聊天室
item {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
// 标题
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(bottom = 12.dp)
) {
Image(
painter = painterResource(R.mipmap.rider_pro_group),
contentDescription = "chat room",
modifier = Modifier.size(16.dp),
colorFilter = ColorFilter.tint(Color(0xFFFFA500))
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "热门聊天室",
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text
)
}
// 热门聊天室列表
//HotChatRoomSection(roomItems = viewModel.bannerItems)
}
}
}
PullRefreshIndicator(
refreshing = isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
)
}
}

View File

@@ -1,17 +1,126 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre package com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs import androidx.compose.runtime.getValue
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel import androidx.compose.runtime.mutableStateOf
import org.greenrobot.eventbus.EventBus import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.data.Room
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.RaveNowAPI
import kotlinx.coroutines.launch
class ExploreViewModel : ViewModel() {
private val apiClient: RaveNowAPI = ApiClient.api
var bannerItems by mutableStateOf<List<BannerItem>>(emptyList())
private set
var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
private set
var isLoading by mutableStateOf(false)
private set
var isRefreshing by mutableStateOf(false)
private set
var errorMessage by mutableStateOf<String?>(null)
private set
object ExploreViewModel : BaseMomentModel() {
init { init {
EventBus.getDefault().register(this) loadBannerData()
loadAgentData()
}
fun refreshBannerData() {
viewModelScope.launch {
isRefreshing = true
errorMessage = null
try {
val response = apiClient.getRooms(page = 1, pageSize = 12, isRecommended = 1)
if (response.isSuccessful) {
val rooms = response.body()?.list ?: emptyList()
bannerItems = rooms.map { room ->
BannerItem.fromRoom(room)
}
} else {
errorMessage = "获取数据失败: ${response.code()}"
}
} catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}"
} finally {
isRefreshing = false
}
}
}
fun refreshAgentData() {
viewModelScope.launch {
isRefreshing = true
errorMessage = null
try {
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList()
agentItems = agents.map { agent ->
AgentItem.fromAgent(agent)
}
} else {
errorMessage = "获取Agent数据失败: ${response.code()}"
}
} catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}"
} finally {
isRefreshing = false
}
}
}
private fun loadBannerData() {
viewModelScope.launch {
isLoading = true
errorMessage = null
try {
val response = apiClient.getRooms(page = 1, pageSize = 10, isRecommended = 1)
if (response.isSuccessful) {
val rooms = response.body()?.list ?: emptyList()
bannerItems = rooms.map { room ->
BannerItem.fromRoom(room)
}
} else {
errorMessage = "获取数据失败: ${response.code()}"
}
} catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}"
} finally {
isLoading = false
}
}
}
private fun loadAgentData() {
viewModelScope.launch {
isLoading = true
errorMessage = null
try {
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList()
agentItems = agents.map { agent ->
AgentItem.fromAgent(agent)
}
} else {
errorMessage = "获取Agent数据失败: ${response.code()}"
}
} catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}"
} finally {
isLoading = false
}
} }
override fun extraArgs(): MomentLoaderExtraArgs {
return MomentLoaderExtraArgs(explore = true)
} }
} }

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillType="evenOdd"
android:strokeColor="#110C13"
android:strokeWidth="1.432"
android:strokeLineCap="round"
android:pathData="M8.804 5.458a3.58 3.58 0 0 1 3.074-1.046l5.454 0.797 c2.008 0.293 3.407 2.224 3.125 4.312l-1.022 7.56-1.167-0.17" />
<path
android:fillColor="#110C13"
android:fillType="evenOdd"
android:pathData="M6.494 14.375 7.348-0.04-0.01 1.907-7.349 0.04 z" />
<path
android:fillType="evenOdd"
android:strokeColor="#110C13"
android:strokeWidth="1.432"
android:pathData="M6.77 8.113 5.171-0.756a3.818 3.818 0 0 1 4.335 3.266l0.51 3.775a3.818 3.818 0 0 1-3.23 4.289L4.565 20 3.54 12.402a3.818 3.818 0 0 1 3.23-4.29z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B