热门聊天室实现;首页UI调整
This commit is contained in:
@@ -81,7 +81,9 @@ data class CreateGroupChatRequestBody(
|
||||
|
||||
data class JoinGroupChatRequestBody(
|
||||
@SerializedName("trtcId")
|
||||
val trtcId: String,
|
||||
val trtcId: String? = null,
|
||||
@SerializedName("roomId")
|
||||
val roomId: Int? = null,
|
||||
)
|
||||
|
||||
data class LoginUserRequestBody(
|
||||
@@ -271,6 +273,127 @@ data class RemoveAccountRequestBody(
|
||||
val password: String,
|
||||
)
|
||||
|
||||
// API 错误响应(用于加入房间等接口的错误处理)
|
||||
data class ApiErrorResponse(
|
||||
@SerializedName("err")
|
||||
val error: String,
|
||||
@SerializedName("success")
|
||||
val success: Boolean
|
||||
)
|
||||
|
||||
// 群聊中的用户信息
|
||||
data class GroupChatUser(
|
||||
@SerializedName("ID")
|
||||
val id: Int,
|
||||
@SerializedName("CreatedAt")
|
||||
val createdAt: String,
|
||||
@SerializedName("UpdatedAt")
|
||||
val updatedAt: String,
|
||||
@SerializedName("DeletedAt")
|
||||
val deletedAt: String?,
|
||||
@SerializedName("userSessionId")
|
||||
val userSessionId: String,
|
||||
@SerializedName("sessions")
|
||||
val sessions: Any?, // 根据实际需要可以定义具体类型
|
||||
@SerializedName("prompts")
|
||||
val prompts: Any?, // 根据实际需要可以定义具体类型
|
||||
@SerializedName("isAgent")
|
||||
val isAgent: Boolean
|
||||
)
|
||||
|
||||
// 智能体角色信息
|
||||
data class GroupChatPrompt(
|
||||
@SerializedName("ID")
|
||||
val id: Int,
|
||||
@SerializedName("CreatedAt")
|
||||
val createdAt: String,
|
||||
@SerializedName("UpdatedAt")
|
||||
val updatedAt: String,
|
||||
@SerializedName("DeletedAt")
|
||||
val deletedAt: String?,
|
||||
@SerializedName("Title")
|
||||
val title: String,
|
||||
@SerializedName("Desc")
|
||||
val desc: String,
|
||||
@SerializedName("Value")
|
||||
val value: String,
|
||||
@SerializedName("Enable")
|
||||
val enable: Boolean,
|
||||
@SerializedName("UserSessions")
|
||||
val userSessions: Any?, // 根据实际需要可以定义具体类型
|
||||
@SerializedName("Avatar")
|
||||
val avatar: String,
|
||||
@SerializedName("AuthorId")
|
||||
val authorId: Int?,
|
||||
@SerializedName("Author")
|
||||
val author: Any?, // 根据实际需要可以定义具体类型
|
||||
@SerializedName("TokenCount")
|
||||
val tokenCount: Int,
|
||||
@SerializedName("OpenId")
|
||||
val openId: String,
|
||||
@SerializedName("Public")
|
||||
val public: Boolean,
|
||||
@SerializedName("BreakMode")
|
||||
val breakMode: Boolean,
|
||||
@SerializedName("DocNamespace")
|
||||
val docNamespace: String,
|
||||
@SerializedName("UseRag")
|
||||
val useRag: Boolean,
|
||||
@SerializedName("RagThreshold")
|
||||
val ragThreshold: Double,
|
||||
@SerializedName("WorkflowId")
|
||||
val workflowId: Int?,
|
||||
@SerializedName("Workflow")
|
||||
val workflow: Any?, // 根据实际需要可以定义具体类型
|
||||
@SerializedName("WorkflowInputs")
|
||||
val workflowInputs: Any?, // 根据实际需要可以定义具体类型
|
||||
@SerializedName("Source")
|
||||
val source: String,
|
||||
@SerializedName("categories")
|
||||
val categories: Any? // 根据实际需要可以定义具体类型
|
||||
)
|
||||
|
||||
// 群聊详细信息响应
|
||||
data class GroupChatResponse(
|
||||
@SerializedName("ID")
|
||||
val id: Int,
|
||||
@SerializedName("CreatedAt")
|
||||
val createdAt: String,
|
||||
@SerializedName("UpdatedAt")
|
||||
val updatedAt: String,
|
||||
@SerializedName("DeletedAt")
|
||||
val deletedAt: String?,
|
||||
@SerializedName("name")
|
||||
val name: String,
|
||||
@SerializedName("description")
|
||||
val description: String,
|
||||
@SerializedName("creatorId")
|
||||
val creatorId: Int,
|
||||
@SerializedName("creator")
|
||||
val creator: Any?, // 根据实际需要可以定义具体类型
|
||||
@SerializedName("trtcRoomId")
|
||||
val trtcRoomId: String,
|
||||
@SerializedName("trtcType")
|
||||
val trtcType: String,
|
||||
@SerializedName("cover")
|
||||
val cover: String,
|
||||
@SerializedName("avatar")
|
||||
val avatar: String,
|
||||
@SerializedName("recommendBanner")
|
||||
val recommendBanner: String,
|
||||
@SerializedName("isRecommended")
|
||||
val isRecommended: Boolean,
|
||||
@SerializedName("allowInHot")
|
||||
val allowInHot: Boolean,
|
||||
@SerializedName("users")
|
||||
val users: List<GroupChatUser>,
|
||||
@SerializedName("prompts")
|
||||
val prompts: List<GroupChatPrompt>,
|
||||
@SerializedName("source")
|
||||
val source: String
|
||||
)
|
||||
|
||||
|
||||
data class CategoryTranslation(
|
||||
@SerializedName("name")
|
||||
val name: String?,
|
||||
@@ -677,7 +800,10 @@ interface RaveNowAPI {
|
||||
suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>>
|
||||
|
||||
@GET("outside/rooms/open")
|
||||
suspend fun createGroupChatAi(@Query("trtcGroupId") trtcGroupId: String): Response<DataContainer<Unit>>
|
||||
suspend fun createGroupChatAi(
|
||||
@Query("trtcGroupId") trtcGroupId: String? = null,
|
||||
@Query("roomId") roomId: Int? = null
|
||||
): Response<DataContainer<GroupChatResponse>>
|
||||
|
||||
@POST("outside/rooms/create-single-chat")
|
||||
suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>>
|
||||
@@ -692,6 +818,7 @@ interface RaveNowAPI {
|
||||
suspend fun getRooms(@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("isRecommended") isRecommended: Int = 1,
|
||||
@Query("random") random: Int? = null,
|
||||
): Response<ListContainer<Room>>
|
||||
|
||||
@GET("outside/rooms/detail")
|
||||
|
||||
@@ -80,14 +80,19 @@ 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.ui.platform.LocalContext
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.compose.foundation.lazy.grid.items as gridItems
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.ui.draw.alpha
|
||||
|
||||
// 检测是否接近列表底部的扩展函数
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -124,17 +129,17 @@ fun Agent() {
|
||||
|
||||
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) {
|
||||
@@ -266,6 +271,53 @@ fun Agent() {
|
||||
}
|
||||
}
|
||||
|
||||
// 热门聊天室
|
||||
stickyHeader(key = "hot_rooms_header") {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(AppColors.background)
|
||||
.padding(top = 8.dp, bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_hot_room),
|
||||
contentDescription = "chat room",
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.hot_rooms),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
||||
color = AppColors.text
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 热门聊天室网格
|
||||
items(viewModel.chatRooms.chunked(2)) { rowRooms ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
rowRooms.forEach { chatRoom ->
|
||||
ChatRoomCard(
|
||||
chatRoom = chatRoom,
|
||||
navController = LocalNavController.current,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
if (rowRooms.size == 1) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item { Spacer(modifier = Modifier.height(20.dp)) }
|
||||
|
||||
// "发现更多" 标题 - 吸顶
|
||||
stickyHeader(key = "discover_more") {
|
||||
@@ -278,15 +330,15 @@ fun Agent() {
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent2),
|
||||
painter = painterResource(R.mipmap.bars_x_buttons_home_n_copy_2),
|
||||
contentDescription = "agent",
|
||||
modifier = Modifier.size(28.dp),
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
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,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
||||
color = AppColors.text
|
||||
)
|
||||
}
|
||||
@@ -320,7 +372,7 @@ fun Agent() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 加载更多指示器
|
||||
if (viewModel.isLoadingMore) {
|
||||
item {
|
||||
@@ -347,11 +399,67 @@ fun Agent() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AgentGridLayout(
|
||||
agentItems: List<AgentItem>,
|
||||
viewModel: AgentViewModel,
|
||||
navController: NavHostController
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
// 将agentItems按两列分组
|
||||
agentItems.chunked(2).forEachIndexed { rowIndex, rowItems ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
top = if (rowIndex == 0) 30.dp else 20.dp, // 第一行添加更多顶部间距
|
||||
bottom = 20.dp
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// 第一列
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
AgentCardSquare(
|
||||
agentItem = rowItems[0],
|
||||
viewModel = viewModel,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
|
||||
// 第二列(如果存在)
|
||||
if (rowItems.size > 1) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
AgentCardSquare(
|
||||
agentItem = rowItems[1],
|
||||
viewModel = viewModel,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 如果只有一列,添加空白占位
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SuspiciousIndentation")
|
||||
@Composable
|
||||
fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) {
|
||||
fun AgentCardSquare(
|
||||
agentItem: AgentItem,
|
||||
viewModel: AgentViewModel,
|
||||
navController: NavHostController
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val cardHeight = 200.dp
|
||||
val cardHeight = 180.dp
|
||||
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
|
||||
|
||||
// 防抖状态
|
||||
@@ -360,9 +468,8 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = avatarSize / 2)
|
||||
.height(cardHeight)
|
||||
.background(AppColors.nonActive, RoundedCornerShape(12.dp)) // 修改背景颜色
|
||||
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
viewModel.goToProfile(agentItem.openId, navController)
|
||||
@@ -372,12 +479,11 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
},
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
// 头像,位于方块上方居中,部分悬于方块外部
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.offset(y = -avatarSize / 2)
|
||||
.offset(y = 4.dp)
|
||||
.size(avatarSize)
|
||||
.background(Color.White, RoundedCornerShape(avatarSize / 2))
|
||||
.background(AppColors.background, RoundedCornerShape(avatarSize / 2))
|
||||
.clip(RoundedCornerShape(avatarSize / 2)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
@@ -385,9 +491,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
painter = painterResource(R.mipmap.group_copy),
|
||||
contentDescription = "默认头像",
|
||||
modifier = Modifier.size(avatarSize),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||
)
|
||||
|
||||
if (agentItem.avatar.isNotEmpty()) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = agentItem.avatar,
|
||||
@@ -404,7 +508,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = avatarSize / 2 + 8.dp, start = 8.dp, end = 8.dp, bottom = 8.dp),
|
||||
.padding(top = 4.dp + avatarSize + 8.dp, start = 8.dp, end = 8.dp, bottom = 48.dp), // 为底部聊天按钮留出空间
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
@@ -418,56 +522,56 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(85.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = agentItem.desc,
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 5,
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
androidx.compose.material3.Text(
|
||||
text = agentItem.desc,
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 2,
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, fill = false)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// 聊天按钮,位于底部居中
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(60.dp)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = Color(0X147c7480),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.clickable {
|
||||
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 = navController
|
||||
)
|
||||
}
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.chat),
|
||||
fontSize = 12.sp,
|
||||
// 聊天按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 12.dp)
|
||||
.width(60.dp)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 14.dp,
|
||||
topEnd = 14.dp,
|
||||
bottomStart = 0.dp,
|
||||
bottomEnd = 14.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
.clickable {
|
||||
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 = navController
|
||||
)
|
||||
}
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.chat),
|
||||
fontSize = 15.sp,
|
||||
color = AppColors.background,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,7 +670,7 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
|
||||
@Composable
|
||||
fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: NavHostController) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
|
||||
// 防抖状态
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
|
||||
@@ -644,7 +748,12 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
.size(width = 60.dp, height = 32.dp)
|
||||
.background(
|
||||
color = Color(0X147c7480),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 14.dp,
|
||||
topEnd = 14.dp,
|
||||
bottomStart = 0.dp,
|
||||
bottomEnd = 14.dp
|
||||
)
|
||||
)
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
@@ -673,3 +782,210 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
}
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun ChatRoomsSection(
|
||||
chatRooms: List<ChatRoom>,
|
||||
navController: NavHostController
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
// 标题
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_hot_room),
|
||||
contentDescription = "chat room",
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.hot_rooms),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
||||
color = AppColors.text
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
chatRooms.chunked(2).forEach { rowRooms ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
rowRooms.forEach { chatRoom ->
|
||||
ChatRoomCard(
|
||||
chatRoom = chatRoom,
|
||||
navController = navController,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatRoomCard(
|
||||
chatRoom: ChatRoom,
|
||||
navController: NavHostController,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val cardSize = 180.dp
|
||||
val viewModel: AgentViewModel = viewModel()
|
||||
val context = LocalContext.current
|
||||
|
||||
// 防抖状态
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
|
||||
// Loading 对话框
|
||||
if (viewModel.isJoiningRoom) {
|
||||
Dialog(
|
||||
onDismissRequest = { /* 阻止用户关闭对话框 */ },
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = false,
|
||||
dismissOnClickOutside = false
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.background(
|
||||
color = AppColors.background,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(32.dp),
|
||||
color = AppColors.main
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
androidx.compose.material3.Text(
|
||||
text = "加入中...",
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.text
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 正方形卡片,文字重叠在底部
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(cardSize)
|
||||
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||
.clickable(enabled = !viewModel.isJoiningRoom) {
|
||||
if (!viewModel.isJoiningRoom && DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
// 加入群聊房间
|
||||
viewModel.joinRoom(
|
||||
id = chatRoom.id,
|
||||
name = chatRoom.name,
|
||||
avatar = chatRoom.avatar,
|
||||
context = context,
|
||||
navController = navController,
|
||||
onSuccess = {
|
||||
// 成功加入房间
|
||||
},
|
||||
onError = { errorMsg ->
|
||||
// 处理错误,可以显示Toast或其他提示
|
||||
}
|
||||
)
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
) {
|
||||
// 优先显示banner,如果没有banner则显示头像
|
||||
val imageUrl = if (chatRoom.banner.isNotEmpty()) chatRoom.banner else chatRoom.avatar
|
||||
|
||||
if (imageUrl.isNotEmpty()) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = imageUrl,
|
||||
contentDescription = if (chatRoom.banner.isNotEmpty()) "房间banner" else "房间头像",
|
||||
modifier = Modifier
|
||||
.width(cardSize)
|
||||
.height(120.dp)
|
||||
.clip(RoundedCornerShape(
|
||||
topStart = 12.dp,
|
||||
topEnd = 12.dp,
|
||||
bottomStart = 0.dp,
|
||||
bottomEnd = 0.dp)),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||
)
|
||||
} else {
|
||||
// 默认房间图标
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent),
|
||||
contentDescription = "默认房间图标",
|
||||
modifier = Modifier.size(cardSize * 0.4f),
|
||||
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
||||
)
|
||||
}
|
||||
|
||||
// 房间名称,重叠在底部
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 32.dp, start = 10.dp, end = 10.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = chatRoom.name,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
||||
color = AppColors.text,
|
||||
maxLines = 1,
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Left
|
||||
)
|
||||
}
|
||||
// 显示人数
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp, start = 10.dp, end = 10.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.rider_pro_nav_profile),
|
||||
contentDescription = "chat",
|
||||
modifier = Modifier.size(16.dp),
|
||||
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "${chatRoom.memberCount} ${stringResource(R.string.chatting_now)}",
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.alpha(0.6f),
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,21 @@ import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
||||
import kotlinx.coroutines.launch
|
||||
import android.util.Log
|
||||
import com.aiosman.ravenow.data.Room
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.data.api.CreateGroupChatRequestBody
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
|
||||
import com.aiosman.ravenow.ui.navigateToGroupChat
|
||||
import com.aiosman.ravenow.data.api.ApiErrorResponse
|
||||
import com.google.gson.Gson
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import kotlinx.coroutines.launch
|
||||
import com.aiosman.ravenow.data.api.JoinGroupChatRequestBody
|
||||
|
||||
|
||||
/**
|
||||
* 缓存数据结构,用于存储每个分类的Agent列表
|
||||
@@ -26,6 +41,13 @@ data class AgentCacheData(
|
||||
val currentPage: Int,
|
||||
val hasMoreData: Boolean
|
||||
)
|
||||
data class ChatRoom(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val avatar: String = "",
|
||||
val banner: String = "",
|
||||
val memberCount: Int
|
||||
)
|
||||
|
||||
object AgentViewModel: ViewModel() {
|
||||
|
||||
@@ -40,6 +62,11 @@ object AgentViewModel: ViewModel() {
|
||||
var errorMessage by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
var chatRooms by mutableStateOf<List<ChatRoom>>(emptyList())
|
||||
private set
|
||||
|
||||
var rooms by mutableStateOf<List<Room>>(emptyList())
|
||||
private set
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
@@ -57,6 +84,9 @@ object AgentViewModel: ViewModel() {
|
||||
var hasMoreData by mutableStateOf(true)
|
||||
private set
|
||||
|
||||
var isJoiningRoom by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
private val pageSize = 20
|
||||
private var currentCategoryId: Int? = null
|
||||
|
||||
@@ -66,6 +96,7 @@ object AgentViewModel: ViewModel() {
|
||||
init {
|
||||
loadAgentData()
|
||||
loadCategories()
|
||||
loadChatRooms()
|
||||
}
|
||||
|
||||
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false, forceRefresh: Boolean = false) {
|
||||
@@ -159,6 +190,41 @@ object AgentViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadChatRooms() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val response = apiClient.getRooms(
|
||||
page = 1,
|
||||
pageSize = 21,
|
||||
isRecommended = 1,
|
||||
random = 1
|
||||
) // 请求21个,确保是3的倍数
|
||||
if (response.isSuccessful) {
|
||||
val allRooms = response.body()?.list ?: emptyList()
|
||||
// 确保房间数量是3的倍数,如果不足则截取,如果超出则取前几个
|
||||
val targetCount = (allRooms.size / 3) * 3 // 向下取整到最近的3的倍数
|
||||
rooms = allRooms.take(targetCount)
|
||||
|
||||
// 转换为ChatRoom格式用于兼容现有UI
|
||||
chatRooms = rooms.map { room ->
|
||||
ChatRoom(
|
||||
id = room.id,
|
||||
name = room.name,
|
||||
avatar = room.avatar,
|
||||
banner = ConstVars.BASE_SERVER + "/api/v1/outside/" + room.recommendBanner + "?token=${AppStore.token}",
|
||||
memberCount = room.userCount
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// 如果网络请求失败,使用默认数据
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCategories() {
|
||||
viewModelScope.launch {
|
||||
// 如果分类已经加载,不重复请求
|
||||
@@ -280,6 +346,81 @@ object AgentViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入房间
|
||||
*/
|
||||
fun joinRoom(
|
||||
id: Int,
|
||||
name: String,
|
||||
avatar: String,
|
||||
context: Context,
|
||||
navController: NavHostController,
|
||||
onSuccess: () -> Unit,
|
||||
onError: (String) -> Unit
|
||||
) {
|
||||
// 防止重复点击
|
||||
if (isJoiningRoom) return
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
isJoiningRoom = true
|
||||
val response = apiClient.joinRoom(JoinGroupChatRequestBody(roomId = id))
|
||||
if (response.isSuccessful) {
|
||||
// 打开房间
|
||||
val openRoomResponse = apiClient.createGroupChatAi(
|
||||
roomId = id
|
||||
)
|
||||
|
||||
if (openRoomResponse.isSuccessful){
|
||||
val respData = openRoomResponse.body()
|
||||
respData?.let {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// 群聊直接使用群ID进行导航
|
||||
navController.navigateToGroupChat(
|
||||
id = respData.data.trtcRoomId,
|
||||
name = name,
|
||||
avatar = avatar
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
onError("加入房间失败")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onSuccess()
|
||||
|
||||
} else {
|
||||
// 处理错误响应
|
||||
try {
|
||||
val errorBody = response.errorBody()?.string()
|
||||
if (errorBody != null) {
|
||||
val gson = Gson()
|
||||
val errorResponse = gson.fromJson(errorBody, ApiErrorResponse::class.java)
|
||||
|
||||
// 在主线程显示 Toast
|
||||
Toast.makeText(context, errorResponse.error, Toast.LENGTH_LONG).show()
|
||||
onError(errorResponse.error)
|
||||
} else {
|
||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
||||
onError("加入房间失败")
|
||||
}
|
||||
} catch (parseException: Exception) {
|
||||
// 如果解析错误响应失败,显示默认错误信息
|
||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
||||
onError("加入房间失败")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context, "网络请求失败:${e.message}", Toast.LENGTH_SHORT).show()
|
||||
onError("网络请求失败:${e.message}")
|
||||
} finally {
|
||||
isJoiningRoom = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置ViewModel状态,用于登出或切换账号时清理数据
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.aiosman.ravenow.ui.index.tabs.message.tab
|
||||
|
||||
import android.content.Context
|
||||
import android.icu.util.Calendar
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -48,7 +49,7 @@ data class GroupConversation(
|
||||
val lastMessage = Calendar.getInstance().apply {
|
||||
timeInMillis = conversation.latestMsgSendTime
|
||||
}
|
||||
|
||||
|
||||
// 解析最新消息
|
||||
val (displayText, isSelf) = MessageParser.parseLatestMessage(conversation.latestMsg)
|
||||
|
||||
@@ -139,7 +140,7 @@ object GroupChatListViewModel : ViewModel() {
|
||||
android.util.Log.w("GroupChatListViewModel", "OpenIM 未登录,跳过加载群聊列表")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
val result = suspendCoroutine { continuation ->
|
||||
// OpenIM 获取所有会话列表
|
||||
OpenIMClient.getInstance().conversationManager.getAllConversationList(
|
||||
@@ -167,10 +168,12 @@ object GroupChatListViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun createGroupChat(
|
||||
trtcGroupId: String,
|
||||
trtcGroupId: String? = null,
|
||||
roomId: Int? = null
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId)
|
||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId,roomId = roomId)
|
||||
Log.d("debug",response.toString())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -369,6 +369,7 @@ fun Explore() {
|
||||
trtcId = roomItem.trtcId.toString(),
|
||||
name = roomItem.title,
|
||||
avatar = roomItem.avatar,
|
||||
context = context,
|
||||
navController = navController,
|
||||
onSuccess = {
|
||||
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
|
||||
@@ -523,7 +524,7 @@ fun Explore() {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
/* Image(
|
||||
Image(
|
||||
painter = painterResource(R.drawable.rider_pro_nav_profile),
|
||||
contentDescription = "chat",
|
||||
modifier = Modifier.size(16.dp),
|
||||
@@ -535,7 +536,7 @@ fun Explore() {
|
||||
fontSize = 12.sp,
|
||||
color = Color.White,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)*/
|
||||
)
|
||||
}
|
||||
|
||||
// 底部:标题和描述
|
||||
@@ -636,6 +637,7 @@ fun Explore() {
|
||||
trtcId = bannerItem.trtcId.toString(),
|
||||
name = bannerItem.title,
|
||||
avatar = bannerItem.avatar,
|
||||
context = context,
|
||||
navController = navController,
|
||||
onSuccess = {
|
||||
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
|
||||
|
||||
@@ -17,32 +17,36 @@ import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userServic
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
|
||||
import com.aiosman.ravenow.ui.navigateToGroupChat
|
||||
import com.aiosman.ravenow.data.api.ApiErrorResponse
|
||||
import com.google.gson.Gson
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
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
|
||||
|
||||
|
||||
init {
|
||||
loadBannerData()
|
||||
loadAgentData()
|
||||
}
|
||||
|
||||
|
||||
fun refreshBannerData() {
|
||||
viewModelScope.launch {
|
||||
isRefreshing = true
|
||||
@@ -64,7 +68,7 @@ class ExploreViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun refreshAgentData() {
|
||||
viewModelScope.launch {
|
||||
isRefreshing = true
|
||||
@@ -86,7 +90,7 @@ class ExploreViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun loadBannerData() {
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
@@ -108,7 +112,7 @@ class ExploreViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun loadAgentData() {
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
@@ -130,21 +134,24 @@ class ExploreViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createSingleChat(
|
||||
openId: String,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
||||
val response =
|
||||
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun goToChatAi(
|
||||
openId: String,
|
||||
navController: NavHostController
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val profile = userService.getUserProfileByOpenId(openId)
|
||||
createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id)
|
||||
createGroup2ChatAi(profile.trtcUserId, "ai_group", navController, profile.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +159,7 @@ class ExploreViewModel : ViewModel() {
|
||||
trtcId: String,
|
||||
name: String,
|
||||
avatar: String,
|
||||
context: Context,
|
||||
navController: NavHostController,
|
||||
onSuccess: () -> Unit,
|
||||
onError: (String) -> Unit
|
||||
@@ -160,24 +168,45 @@ class ExploreViewModel : ViewModel() {
|
||||
try {
|
||||
val response = apiClient.joinRoom(JoinGroupChatRequestBody(trtcId = trtcId))
|
||||
if (response.isSuccessful) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
createGroupChat(trtcGroupId = trtcId)
|
||||
// 群聊直接使用群ID进行导航
|
||||
navController.navigateToGroupChat( id = trtcId,
|
||||
name = name,
|
||||
avatar = avatar)
|
||||
} catch (e: Exception) {
|
||||
onError("加入房间失败")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
onSuccess()
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
createGroupChat(trtcGroupId = trtcId)
|
||||
// 群聊直接使用群ID进行导航
|
||||
navController.navigateToGroupChat(
|
||||
id = trtcId,
|
||||
name = name,
|
||||
avatar = avatar
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
onError("加入房间失败")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
onSuccess()
|
||||
|
||||
} else {
|
||||
onError("加入房间失败")
|
||||
// 处理错误响应
|
||||
try {
|
||||
val errorBody = response.errorBody()?.string()
|
||||
if (errorBody != null) {
|
||||
val gson = Gson()
|
||||
val errorResponse = gson.fromJson(errorBody, ApiErrorResponse::class.java)
|
||||
|
||||
// 在主线程显示 Toast
|
||||
Toast.makeText(context, errorResponse.error, Toast.LENGTH_LONG).show()
|
||||
onError(errorResponse.error)
|
||||
} else {
|
||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
||||
onError("加入房间失败")
|
||||
}
|
||||
} catch (parseException: Exception) {
|
||||
// 如果解析错误响应失败,显示默认错误信息
|
||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
||||
onError("加入房间失败")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context, "网络请求失败:${e.message}", Toast.LENGTH_SHORT).show()
|
||||
onError("网络请求失败:${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user