Merge pull request #43 from Kevinlinpr/zhong_1

热门聊天室实现;首页UI调整
This commit is contained in:
2025-10-21 21:39:20 +08:00
committed by GitHub
21 changed files with 1097 additions and 346 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -81,7 +81,9 @@ data class CreateGroupChatRequestBody(
data class JoinGroupChatRequestBody( data class JoinGroupChatRequestBody(
@SerializedName("trtcId") @SerializedName("trtcId")
val trtcId: String, val trtcId: String? = null,
@SerializedName("roomId")
val roomId: Int? = null,
) )
data class LoginUserRequestBody( data class LoginUserRequestBody(
@@ -271,6 +273,127 @@ data class RemoveAccountRequestBody(
val password: String, 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( data class CategoryTranslation(
@SerializedName("name") @SerializedName("name")
val name: String?, val name: String?,
@@ -677,7 +800,10 @@ interface RaveNowAPI {
suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>> suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>>
@GET("outside/rooms/open") @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") @POST("outside/rooms/create-single-chat")
suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>> suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>>
@@ -692,6 +818,7 @@ interface RaveNowAPI {
suspend fun getRooms(@Query("page") page: Int = 1, suspend fun getRooms(@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 20, @Query("pageSize") pageSize: Int = 20,
@Query("isRecommended") isRecommended: Int = 1, @Query("isRecommended") isRecommended: Int = 1,
@Query("random") random: Int? = null,
): Response<ListContainer<Room>> ): Response<ListContainer<Room>>
@GET("outside/rooms/detail") @GET("outside/rooms/detail")

View File

@@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -32,6 +33,7 @@ fun ActionButton(
text: String, text: String,
color: Color? = null, color: Color? = null,
backgroundColor: Color? = null, backgroundColor: Color? = null,
backgroundBrush: Brush? = null,
leading: @Composable (() -> Unit)? = null, leading: @Composable (() -> Unit)? = null,
expandText: Boolean = false, expandText: Boolean = false,
contentPadding: PaddingValues = PaddingValues(vertical = 16.dp), contentPadding: PaddingValues = PaddingValues(vertical = 16.dp),
@@ -65,7 +67,11 @@ fun ActionButton(
Box( Box(
modifier = modifier modifier = modifier
.clip(RoundedCornerShape(roundCorner.dp)) .clip(RoundedCornerShape(roundCorner.dp))
.background(animatedBackgroundColor) .background(
brush = backgroundBrush ?: Brush.linearGradient(
colors = listOf(animatedBackgroundColor, animatedBackgroundColor)
)
)
.noRippleClickable { .noRippleClickable {
if (enabled && !isLoading) { if (enabled && !isLoading) {
click() click()

View File

@@ -1,10 +1,15 @@
package com.aiosman.ravenow.ui.composables package com.aiosman.ravenow.ui.composables
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer 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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -12,11 +17,14 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
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 com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.text.font.FontWeight
/** /**
* 可复用的标签页组件 * 可复用的标签页组件
*/ */
@@ -54,3 +62,43 @@ fun TabItem(
fun TabSpacer() { fun TabSpacer() {
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
} }
@Composable
fun UnderlineTabItem(
text: String,
isSelected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
val AppColors = LocalAppTheme.current
Column(
modifier = modifier
.noRippleClickable { onClick() },
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = text,
fontSize = 15.sp,
fontWeight = FontWeight.ExtraBold,
color = if (isSelected) AppColors.text else AppColors.text.copy(alpha = 0.6f),
modifier = Modifier.padding(horizontal = 16.dp).padding(top = 13.dp)
)
// 选中状态下显示图标
Box(
modifier = Modifier.size(24.dp),
contentAlignment = Alignment.Center
) {
if (isSelected) {
Image(
painter = painterResource(id = R.mipmap.underline),
contentDescription = "selected indicator",
)
}
}
}
}

View File

@@ -80,7 +80,12 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState 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.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.foundation.lazy.grid.items as gridItems
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.ui.draw.alpha
// 检测是否接近列表底部的扩展函数 // 检测是否接近列表底部的扩展函数
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean { fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
@@ -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") { stickyHeader(key = "discover_more") {
@@ -278,15 +330,15 @@ fun Agent() {
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.Bottom
) { ) {
Image( Image(
painter = painterResource(R.mipmap.rider_pro_agent2), painter = painterResource(R.mipmap.bars_x_buttons_home_n_copy_2),
contentDescription = "agent", contentDescription = "agent",
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp)
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = stringResource(R.string.agent_find), text = stringResource(R.string.agent_find),
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600, fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
color = AppColors.text color = AppColors.text
) )
} }
@@ -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") @SuppressLint("SuspiciousIndentation")
@Composable @Composable
fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) { fun AgentCardSquare(
agentItem: AgentItem,
viewModel: AgentViewModel,
navController: NavHostController
) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val cardHeight = 200.dp val cardHeight = 180.dp
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一 val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
// 防抖状态 // 防抖状态
@@ -360,9 +468,8 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = avatarSize / 2)
.height(cardHeight) .height(cardHeight)
.background(AppColors.nonActive, RoundedCornerShape(12.dp)) // 修改背景颜色 .background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
viewModel.goToProfile(agentItem.openId, navController) viewModel.goToProfile(agentItem.openId, navController)
@@ -372,12 +479,11 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
}, },
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
) { ) {
// 头像,位于方块上方居中,部分悬于方块外部
Box( Box(
modifier = Modifier modifier = Modifier
.offset(y = -avatarSize / 2) .offset(y = 4.dp)
.size(avatarSize) .size(avatarSize)
.background(Color.White, RoundedCornerShape(avatarSize / 2)) .background(AppColors.background, RoundedCornerShape(avatarSize / 2))
.clip(RoundedCornerShape(avatarSize / 2)), .clip(RoundedCornerShape(avatarSize / 2)),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
@@ -385,9 +491,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
painter = painterResource(R.mipmap.group_copy), painter = painterResource(R.mipmap.group_copy),
contentDescription = "默认头像", contentDescription = "默认头像",
modifier = Modifier.size(avatarSize), modifier = Modifier.size(avatarSize),
contentScale = androidx.compose.ui.layout.ContentScale.Crop
) )
if (agentItem.avatar.isNotEmpty()) { if (agentItem.avatar.isNotEmpty()) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = agentItem.avatar, imageUrl = agentItem.avatar,
@@ -404,7 +508,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .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 horizontalAlignment = Alignment.CenterHorizontally
) { ) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
@@ -418,56 +522,56 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Box( androidx.compose.material3.Text(
modifier = Modifier text = agentItem.desc,
.height(85.dp) fontSize = 12.sp,
.fillMaxWidth() color = AppColors.secondaryText,
) { maxLines = 2,
androidx.compose.material3.Text( overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
text = agentItem.desc, modifier = Modifier.weight(1f, fill = false)
fontSize = 12.sp, )
color = AppColors.secondaryText, }
maxLines = 5,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.height(8.dp)) // 聊天按钮
Box(
// 聊天按钮,位于底部居中 modifier = Modifier
Box( .align(Alignment.BottomCenter)
modifier = Modifier .padding(bottom = 12.dp)
.width(60.dp) .width(60.dp)
.height(32.dp) .height(32.dp)
.background( .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,
color = AppColors.text, 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
)
} }
} }
} }
@@ -644,7 +748,12 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
.size(width = 60.dp, height = 32.dp) .size(width = 60.dp, height = 32.dp)
.background( .background(
color = Color(0X147c7480), color = Color(0X147c7480),
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(
topStart = 14.dp,
topEnd = 14.dp,
bottomStart = 0.dp,
bottomEnd = 14.dp
)
) )
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { 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
)
}
}
}
}

View File

@@ -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.message.MessageListViewModel.userService
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
import kotlinx.coroutines.launch 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列表 * 缓存数据结构用于存储每个分类的Agent列表
@@ -26,6 +41,13 @@ data class AgentCacheData(
val currentPage: Int, val currentPage: Int,
val hasMoreData: Boolean val hasMoreData: Boolean
) )
data class ChatRoom(
val id: Int,
val name: String,
val avatar: String = "",
val banner: String = "",
val memberCount: Int
)
object AgentViewModel: ViewModel() { object AgentViewModel: ViewModel() {
@@ -40,6 +62,11 @@ object AgentViewModel: ViewModel() {
var errorMessage by mutableStateOf<String?>(null) var errorMessage by mutableStateOf<String?>(null)
private set 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) var isRefreshing by mutableStateOf(false)
private set private set
@@ -57,6 +84,9 @@ object AgentViewModel: ViewModel() {
var hasMoreData by mutableStateOf(true) var hasMoreData by mutableStateOf(true)
private set private set
var isJoiningRoom by mutableStateOf(false)
private set
private val pageSize = 20 private val pageSize = 20
private var currentCategoryId: Int? = null private var currentCategoryId: Int? = null
@@ -66,6 +96,7 @@ object AgentViewModel: ViewModel() {
init { init {
loadAgentData() loadAgentData()
loadCategories() loadCategories()
loadChatRooms()
} }
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false, forceRefresh: Boolean = false) { private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false, forceRefresh: Boolean = false) {
@@ -159,6 +190,40 @@ object AgentViewModel: ViewModel() {
} }
} }
private fun loadChatRooms() {
viewModelScope.launch {
try {
val response = apiClient.getRooms(
page = 1,
pageSize = 20,
isRecommended = 1,
random = 1
)
if (response.isSuccessful) {
val allRooms = response.body()?.list ?: emptyList()
val targetCount = (allRooms.size / 2) * 2
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() { private fun loadCategories() {
viewModelScope.launch { viewModelScope.launch {
// 如果分类已经加载,不重复请求 // 如果分类已经加载,不重复请求
@@ -280,6 +345,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状态用于登出或切换账号时清理数据 * 重置ViewModel状态用于登出或切换账号时清理数据
*/ */

View File

@@ -2,6 +2,7 @@ package com.aiosman.ravenow.ui.index.tabs.message.tab
import android.content.Context import android.content.Context
import android.icu.util.Calendar import android.icu.util.Calendar
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -167,10 +168,12 @@ object GroupChatListViewModel : ViewModel() {
} }
fun createGroupChat( fun createGroupChat(
trtcGroupId: String, trtcGroupId: String? = null,
roomId: Int? = null
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId) val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId,roomId = roomId)
Log.d("debug",response.toString())
} }
} }

View File

@@ -3,6 +3,7 @@ package com.aiosman.ravenow.ui.index.tabs.moment
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -20,6 +21,7 @@ import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon import androidx.compose.material.Icon
@@ -54,7 +56,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import com.aiosman.ravenow.ui.composables.TabItem import com.aiosman.ravenow.ui.composables.TabItem
import com.aiosman.ravenow.ui.composables.TabSpacer import com.aiosman.ravenow.ui.composables.UnderlineTabItem
import com.aiosman.ravenow.ui.composables.rememberDebouncer import com.aiosman.ravenow.ui.composables.rememberDebouncer
/** /**
@@ -68,8 +70,8 @@ fun MomentsList() {
val navigationBarPaddings = val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
// 游客模式下不显示timeline只显示2个tabDynamic、Hot // 游客模式下不显示timeline只显示3个tabExplore、Dynamic、Hot // 现在有6个tab推荐、短视频、新闻、探索、关注、热门
val tabCount = if (AppStore.isGuest) 2 else 3 // val tabCount = if (AppStore.isGuest) 3 else 4 val tabCount = 6
var pagerState = rememberPagerState { tabCount } var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
Column( Column(
@@ -81,60 +83,127 @@ fun MomentsList() {
), ),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
// 顶部区域:可滚动的标签页 + 搜索按钮
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(44.dp) .height(44.dp)
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
// center the tabs horizontalArrangement = Arrangement.SpaceBetween,
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
//原探索// // 可滚动的标签页行
// Column( Row(
// modifier = Modifier modifier = Modifier
// .noRippleClickable { .weight(1f)
// scope.launch { .horizontalScroll(rememberScrollState()),
// pagerState.animateScrollToPage(0) horizontalArrangement = Arrangement.Start,
// } verticalAlignment = Alignment.CenterVertically
// }.padding(start = 16.dp), ) {
// verticalArrangement = Arrangement.Center, val tabDebouncer = rememberDebouncer()
// horizontalAlignment = Alignment.CenterHorizontally
// // 推荐标签
// ) { UnderlineTabItem(
// Text( text = stringResource(R.string.tab_recommend),
// text = stringResource(R.string.index_worldwide), isSelected = pagerState.currentPage == 0,
// fontSize = if (pagerState.currentPage == 0)18.sp else 16.sp, onClick = {
// color = if (pagerState.currentPage == 0) AppColors.text else AppColors.nonActiveText, tabDebouncer {
// fontWeight = FontWeight.W600) scope.launch {
// Spacer(modifier = Modifier.height(4.dp)) pagerState.animateScrollToPage(0)
// }
// Image( }
// painter = painterResource( }
// if (pagerState.currentPage == 0) R.mipmap.tab_indicator_selected )
// else R.drawable.tab_indicator_unselected
// ),
// contentDescription = "tab indicator", // 短视频标签
// modifier = Modifier UnderlineTabItem(
// .width(34.dp) text = stringResource(R.string.tab_short_video),
// .height(4.dp) isSelected = pagerState.currentPage == 1,
// ) onClick = {
// tabDebouncer {
// } scope.launch {
// Spacer(modifier = Modifier.width(16.dp)) pagerState.animateScrollToPage(1)
}
}
}
)
// 动态标签
UnderlineTabItem(
text = stringResource(R.string.moment),
isSelected = pagerState.currentPage == 2,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(2)
}
}
}
)
// 只有非游客用户才显示"关注"tab
if (!AppStore.isGuest) {
UnderlineTabItem(
text = stringResource(R.string.index_following),
isSelected = pagerState.currentPage == 3,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(3)
}
}
}
)
// 热门标签
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 4,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(4)
}
}
}
)
} else {
// 热门标签 (游客模式)
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 4,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(4)
}
}
}
)
}
// 新闻标签
UnderlineTabItem(
text = stringResource(R.string.tab_news),
isSelected = pagerState.currentPage == 5,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(5)
}
}
}
)
}
// 搜索按钮
val lastClickTime = remember { mutableStateOf(0L) } val lastClickTime = remember { mutableStateOf(0L) }
val clickDelay = 500L val clickDelay = 500L
Text(
text = stringResource(R.string.moment),
fontSize = 20.sp,
fontWeight = FontWeight.W900,
color = AppColors.text,
modifier = Modifier
.align(Alignment.CenterVertically)
)
Spacer(modifier = Modifier.weight(1f))
Image( Image(
painter = painterResource(id = R.drawable.rider_pro_nav_search), painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search", contentDescription = "search",
@@ -151,113 +220,38 @@ fun MomentsList() {
) )
} }
Spacer(modifier = Modifier.height(23.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 16.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
) {
val tabDebouncer = rememberDebouncer()
// 新探索标签
Box {
CustomTabItem(
text = stringResource(R.string.index_worldwide),
isSelected = pagerState.currentPage == 0,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(0)
}
}
}
)
}
TabSpacer()
// 只有非游客用户才显示"关注"tab
if (!AppStore.isGuest) {
Box {
CustomTabItem(
text = stringResource(R.string.index_following),
isSelected = pagerState.currentPage == 1,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(1)
}
}
}
)
}
TabSpacer()
// 热门标签
Box {
CustomTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 2,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(2)
}
}
}
)
}
} else {
// 热门标签 (游客模式)
Box {
CustomTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 1,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(1)
}
}
}
)
}
}
}
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
) { ) {
if (AppStore.isGuest) { when (it) {
// 游客模式Dynamic(0), Hot(1) 0 -> {
when (it) { // 推荐页面
0 -> {
Dynamic()
}
1 -> {
HotMomentsList()
}
} }
} else { 1 -> {
// 正常用户Dynamic(0), Timeline(1), Hot(2) // 短视频页面
when (it) { }
0 -> { 2 -> {
Dynamic() // 动态页面 - 暂时显示时间线内容
} Dynamic()
1 -> { }
3 -> {
// 关注页面 (仅非游客用户) 或 热门页面 (游客用户)
if (AppStore.isGuest) {
HotMomentsList()
} else {
TimelineMomentsList() TimelineMomentsList()
} }
2 -> { }
HotMomentsList() 4 -> {
} // 热门页面 (仅非游客用户)
HotMomentsList()
}
5 -> {
// 新闻页面
} }
} }
} }

View File

@@ -369,6 +369,7 @@ fun Explore() {
trtcId = roomItem.trtcId.toString(), trtcId = roomItem.trtcId.toString(),
name = roomItem.title, name = roomItem.title,
avatar = roomItem.avatar, avatar = roomItem.avatar,
context = context,
navController = navController, navController = navController,
onSuccess = { onSuccess = {
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show() Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
@@ -523,7 +524,7 @@ fun Explore() {
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
/* Image( Image(
painter = painterResource(R.drawable.rider_pro_nav_profile), painter = painterResource(R.drawable.rider_pro_nav_profile),
contentDescription = "chat", contentDescription = "chat",
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
@@ -535,7 +536,7 @@ fun Explore() {
fontSize = 12.sp, fontSize = 12.sp,
color = Color.White, color = Color.White,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500 fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)*/ )
} }
// 底部:标题和描述 // 底部:标题和描述
@@ -636,6 +637,7 @@ fun Explore() {
trtcId = bannerItem.trtcId.toString(), trtcId = bannerItem.trtcId.toString(),
name = bannerItem.title, name = bannerItem.title,
avatar = bannerItem.avatar, avatar = bannerItem.avatar,
context = context,
navController = navController, navController = navController,
onSuccess = { onSuccess = {
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show() Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()

View File

@@ -17,6 +17,10 @@ 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
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
import com.aiosman.ravenow.ui.navigateToGroupChat 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 kotlinx.coroutines.launch
class ExploreViewModel : ViewModel() { class ExploreViewModel : ViewModel() {
@@ -130,21 +134,24 @@ class ExploreViewModel : ViewModel() {
} }
} }
} }
fun createSingleChat( fun createSingleChat(
openId: String, openId: String,
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId)) val response =
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
} }
} }
fun goToChatAi( fun goToChatAi(
openId: String, openId: String,
navController: NavHostController navController: NavHostController
) { ) {
viewModelScope.launch { viewModelScope.launch {
val profile = userService.getUserProfileByOpenId(openId) 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, trtcId: String,
name: String, name: String,
avatar: String, avatar: String,
context: Context,
navController: NavHostController, navController: NavHostController,
onSuccess: () -> Unit, onSuccess: () -> Unit,
onError: (String) -> Unit onError: (String) -> Unit
@@ -160,24 +168,45 @@ class ExploreViewModel : ViewModel() {
try { try {
val response = apiClient.joinRoom(JoinGroupChatRequestBody(trtcId = trtcId)) val response = apiClient.joinRoom(JoinGroupChatRequestBody(trtcId = trtcId))
if (response.isSuccessful) { if (response.isSuccessful) {
viewModelScope.launch { viewModelScope.launch {
try { try {
createGroupChat(trtcGroupId = trtcId) createGroupChat(trtcGroupId = trtcId)
// 群聊直接使用群ID进行导航 // 群聊直接使用群ID进行导航
navController.navigateToGroupChat( id = trtcId, navController.navigateToGroupChat(
name = name, id = trtcId,
avatar = avatar) name = name,
} catch (e: Exception) { avatar = avatar
onError("加入房间失败") )
e.printStackTrace() } catch (e: Exception) {
} onError("加入房间失败")
} e.printStackTrace()
onSuccess() }
}
onSuccess()
} else { } 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) { } catch (e: Exception) {
Toast.makeText(context, "网络请求失败:${e.message}", Toast.LENGTH_SHORT).show()
onError("网络请求失败:${e.message}") onError("网络请求失败:${e.message}")
} }
} }

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
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.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -40,12 +41,14 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -70,6 +73,11 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
@Preview @Preview
@Composable @Composable
fun LoginPage() { fun LoginPage() {
@@ -209,14 +217,14 @@ fun LoginPage() {
saveData() saveData()
} }
// 显示成功提示 // // 显示成功提示
coroutineScope.launch(Dispatchers.Main) { // coroutineScope.launch(Dispatchers.Main) {
Toast.makeText( // Toast.makeText(
context, // context,
"游客登录成功", // "游客登录成功",
Toast.LENGTH_SHORT // Toast.LENGTH_SHORT
).show() // ).show()
} // }
// 初始化应用状态游客模式会自动跳过推送和TRTC初始化 // 初始化应用状态游客模式会自动跳过推送和TRTC初始化
try { try {
@@ -260,13 +268,52 @@ fun LoginPage() {
.fillMaxSize() .fillMaxSize()
.background(AppColors.background) .background(AppColors.background)
) { ) {
Box( Row(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxWidth()
.padding(horizontal = 24.dp)
.padding(top = 100.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { ) {
val localContext = LocalContext.current // 获取 Context Text(
MovingImageWall(localContext.resources) // 将 resources 传递给 MovingImageWall text = stringResource(R.string.join_party_carnival),
fontSize = 14.sp,
color = AppColors.text
)
Box(
modifier = Modifier
.size(30.dp)
.background(
color = AppColors.text.copy(alpha = 0.1f),
shape = androidx.compose.foundation.shape.CircleShape
)
.noRippleClickable {
guestLogin()
},
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_close),
contentDescription = "Close",
modifier = Modifier.size(16.dp),
colorFilter = ColorFilter.tint(AppColors.text)
)
}
} }
Box(
modifier = Modifier.fillMaxSize()
) {
val lottieFile = if (AppState.darkMode) "login.lottie" else "login_light.lottie"
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset(lottieFile)).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.fillMaxSize()
.padding(30.dp)
)
}
Spacer(modifier = Modifier.height(20.dp))
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -274,78 +321,82 @@ fun LoginPage() {
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Image( // Image(
painter = painterResource(id = R.mipmap.invalid_name), // painter = painterResource(id = R.mipmap.invalid_name),
contentDescription = "Rave Now", // contentDescription = "Rave Now",
modifier = Modifier // modifier = Modifier
.size(52.dp) // .size(52.dp)
.clip(RoundedCornerShape(10.dp)) // .clip(RoundedCornerShape(10.dp))
) // )
Spacer(modifier = Modifier.height(8.dp)) // Spacer(modifier = Modifier.height(8.dp))
Text( // Text(
"Rave Now", // "Rave Now",
fontSize = 28.sp, // fontSize = 28.sp,
fontWeight = FontWeight.W900, // fontWeight = FontWeight.W900,
color = AppColors.text // color = AppColors.text
) // )
Spacer(modifier = Modifier.height(16.dp)) // Spacer(modifier = Modifier.height(16.dp))
Text( // Text(
"Your Night Starts Here", // "Your Night Starts Here",
fontSize = 20.sp, // fontSize = 20.sp,
fontWeight = FontWeight.W700, // fontWeight = FontWeight.W700,
color = AppColors.text // color = AppColors.text
) // )
//注册tab
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
ActionButton( ActionButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.sign_up_upper), text = stringResource(R.string.sign_up_upper),
color = AppColors.mainText, color = if (AppState.darkMode) Color.Black else Color.White,
backgroundColor = AppColors.main backgroundColor = if (AppState.darkMode) Color.White else Color.Black
) { ) {
navController.navigate( navController.navigate(
NavigationRoute.EmailSignUp.route, NavigationRoute.EmailSignUp.route,
) )
} }
if (showGoogleLogin) { // if (showGoogleLogin) {
Spacer(modifier = Modifier.height(16.dp)) // Spacer(modifier = Modifier.height(16.dp))
ActionButton( // ActionButton(
modifier = Modifier.fillMaxWidth(), // modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.sign_in_with_google), // text = stringResource(R.string.sign_in_with_google),
color = AppColors.text, // color = AppColors.text,
leading = { // leading = {
Image( // Image(
painter = painterResource(id = R.drawable.rider_pro_google), // painter = painterResource(id = R.drawable.rider_pro_google),
contentDescription = "Google", // contentDescription = "Google",
modifier = Modifier.size(36.dp) // modifier = Modifier.size(36.dp)
) // )
}, // },
expandText = true, // expandText = true,
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp) // contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp)
) { // ) {
googleLogin() // googleLogin()
} // }
} // }
//登录tab //登录tab
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
ActionButton( Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.login_upper), text = stringResource(R.string.login_upper),
color = AppColors.text, color = AppColors.text.copy(alpha = 0.5f),
) { fontSize = 16.sp,
navController.navigate( textAlign = TextAlign.Center,
NavigationRoute.UserAuth.route, modifier = Modifier
) .fillMaxWidth()
} .noRippleClickable {
navController.navigate(
// 游客登录按钮 NavigationRoute.UserAuth.route,
Spacer(modifier = Modifier.height(16.dp)) )
ActionButton( }
modifier = Modifier.fillMaxWidth(), )
text = "游客模式", // // 游客登录按钮
color = AppColors.text.copy(alpha = 0.7f), // Spacer(modifier = Modifier.height(16.dp))
) { // ActionButton(
guestLogin() // modifier = Modifier.fillMaxWidth(),
} // text = "游客模式",
// color = AppColors.text.copy(alpha = 0.7f),
// ) {
// guestLogin()
// }
Spacer(modifier = Modifier.height(70.dp)) Spacer(modifier = Modifier.height(70.dp))
} }
} }

View File

@@ -22,6 +22,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -299,32 +301,38 @@ fun UserAuthScreen() {
ActionButton( ActionButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.lets_ride_upper), text = stringResource(R.string.lets_ride_upper),
backgroundColor = AppColors.main, backgroundBrush = Brush.linearGradient(
colors = listOf(
Color(0xFF7c45ed),
Color(0x777c68ef),
Color(0x777bd8f8)
)
),
color = AppColors.mainText, color = AppColors.mainText,
) { ) {
onLogin() onLogin()
} }
if (AppState.enableGoogleLogin) { // if (AppState.enableGoogleLogin) {
Spacer(modifier = Modifier.height(16.dp)) // Spacer(modifier = Modifier.height(16.dp))
Text(stringResource(R.string.or_login_with), color = AppColors.secondaryText) // Text(stringResource(R.string.or_login_with), color = AppColors.secondaryText)
Spacer(modifier = Modifier.height(16.dp)) // Spacer(modifier = Modifier.height(16.dp))
ActionButton( // ActionButton(
modifier = Modifier.fillMaxWidth(), // modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.sign_in_with_google), // text = stringResource(R.string.sign_in_with_google),
color = AppColors.text, // color = AppColors.text,
leading = { // leading = {
Image( // Image(
painter = painterResource(id = R.drawable.rider_pro_google), // painter = painterResource(id = R.drawable.rider_pro_google),
contentDescription = "Google", // contentDescription = "Google",
modifier = Modifier.size(36.dp) // modifier = Modifier.size(36.dp)
) // )
}, // },
expandText = true, // expandText = true,
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp) // contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp)
) { // ) {
googleLogin() // googleLogin()
} // }
} // }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -147,6 +147,7 @@
<string name="chat_group">グループ</string> <string name="chat_group">グループ</string>
<string name="chat_friend">友達</string> <string name="chat_friend">友達</string>
<string name="chat_all">すべて</string> <string name="chat_all">すべて</string>
<string name="chatting_now">人はおしゃべりをしている…</string>
<string name="agent_chat_list_title">AIエージェントチャット</string> <string name="agent_chat_list_title">AIエージェントチャット</string>
<string name="agent_chat_empty_title">AIエージェントチャットがありません</string> <string name="agent_chat_empty_title">AIエージェントチャットがありません</string>
<string name="agent_chat_empty_subtitle">AIエージェントと対話してみましょう</string> <string name="agent_chat_empty_subtitle">AIエージェントと対話してみましょう</string>
@@ -218,5 +219,13 @@
<string name="friend_chat_no_network_title">オフラインだ..</string> <string name="friend_chat_no_network_title">オフラインだ..</string>
<string name="friend_chat_no_network_subtitle">ネットワークを確認して、この宇宙に接続してください</string> <string name="friend_chat_no_network_subtitle">ネットワークを確認して、この宇宙に接続してください</string>
<string name="Reload">再ロード</string> <string name="Reload">再ロード</string>
<!-- Login page -->
<string name="join_party_carnival">パーティーに参加して、一緒に盛り上がろう</string>
<!-- Tab labels -->
<string name="tab_recommend">おすすめ</string>
<string name="tab_short_video">ショート動画</string>
<string name="tab_news">ニュース</string>
</resources> </resources>

View File

@@ -191,9 +191,10 @@
<string name="group_room_enter">进入</string> <string name="group_room_enter">进入</string>
<string name="group_room_enter_success">成功加入房间</string> <string name="group_room_enter_success">成功加入房间</string>
<string name="group_room_enter_fail">加入房间失败</string> <string name="group_room_enter_fail">加入房间失败</string>
<string name="agent_createing">创建中...</string> <string name="agent_createing">创建中</string>
<string name="agent_find">发现</string> <string name="agent_find">发现</string>
<string name="text_error_password_too_long">密码不能超过 %1$d 个字符</string> <string name="text_error_password_too_long">密码不能超过 %1$d 个字符</string>
<string name="chatting_now">人正在热聊…</string>
<!-- Create Bottom Sheet --> <!-- Create Bottom Sheet -->
<string name="create_title">创建</string> <string name="create_title">创建</string>
@@ -222,4 +223,12 @@
<string name="friend_chat_no_network_subtitle">确认一下网络,连接这个宇宙</string> <string name="friend_chat_no_network_subtitle">确认一下网络,连接这个宇宙</string>
<string name="Reload">重新加载</string> <string name="Reload">重新加载</string>
<!-- Login page -->
<string name="join_party_carnival">加入派派,一起狂欢</string>
<!-- Tab labels -->
<string name="tab_recommend">推荐</string>
<string name="tab_short_video">短视频</string>
<string name="tab_news">新闻</string>
</resources> </resources>

View File

@@ -145,6 +145,7 @@
<string name="chat_ai">Ai</string> <string name="chat_ai">Ai</string>
<string name="chat_group">Group</string> <string name="chat_group">Group</string>
<string name="chat_friend">Friends</string> <string name="chat_friend">Friends</string>
<string name="chatting_now">people chatting now…</string>
<string name="chat_all">All</string> <string name="chat_all">All</string>
<string name="agent_chat_list_title">Agent Chat</string> <string name="agent_chat_list_title">Agent Chat</string>
<string name="agent_chat_empty_title">No Agent Chat</string> <string name="agent_chat_empty_title">No Agent Chat</string>
@@ -217,4 +218,12 @@
<string name="friend_chat_no_network_title">Offline…</string> <string name="friend_chat_no_network_title">Offline…</string>
<string name="friend_chat_no_network_subtitle">Check your network to connect to this universe</string> <string name="friend_chat_no_network_subtitle">Check your network to connect to this universe</string>
<string name="Reload">Reload</string> <string name="Reload">Reload</string>
<!-- Login page -->
<string name="join_party_carnival">Join the party, let\'s celebrate together</string>
<!-- Tab labels -->
<string name="tab_recommend">Recommend</string>
<string name="tab_short_video">Short Video</string>
<string name="tab_news">News</string>
</resources> </resources>