UI调整,群聊开发

This commit is contained in:
weber
2025-08-20 19:19:14 +08:00
parent 791b24b2fb
commit 8f8c2ff2e9
27 changed files with 709 additions and 513 deletions

View File

@@ -33,6 +33,7 @@ open class AppThemeData(
var tabUnselectedBackground: Color, var tabUnselectedBackground: Color,
var tabSelectedText: Color, var tabSelectedText: Color,
var tabUnselectedText: Color, var tabUnselectedText: Color,
var bubbleBackground: Color,
) )
class LightThemeColors : AppThemeData( class LightThemeColors : AppThemeData(
@@ -60,10 +61,10 @@ class LightThemeColors : AppThemeData(
chatActionColor = Color(0xffe0e0e0), chatActionColor = Color(0xffe0e0e0),
brandColorsColor = Color(0xffD80264), brandColorsColor = Color(0xffD80264),
tabSelectedBackground = Color(0xff110C13), tabSelectedBackground = Color(0xff110C13),
tabUnselectedBackground = Color(0xff7C7480), tabUnselectedBackground = Color(0x147C7480),
tabSelectedText = Color(0xffffffff), tabSelectedText = Color(0xffffffff),
tabUnselectedText = Color(0xff000000), tabUnselectedText = Color(0xff000000),
bubbleBackground = Color(0xfff5f5f5)
) )
class DarkThemeColors : AppThemeData( class DarkThemeColors : AppThemeData(
@@ -94,4 +95,5 @@ class DarkThemeColors : AppThemeData(
tabUnselectedBackground = Color(0xff7C7480), tabUnselectedBackground = Color(0xff7C7480),
tabSelectedText = Color(0xff000000), tabSelectedText = Color(0xff000000),
tabUnselectedText = Color(0xffffffff), tabUnselectedText = Color(0xffffffff),
bubbleBackground = Color(0xfff2d2c2e)
) )

View File

@@ -44,19 +44,27 @@ data class SingleChatRequestBody(
val generateText: String, val generateText: String,
) )
data class GroupChatRequestBody(
@SerializedName("trtcGroupId")
val trtcGroupId: String,
)
data class SendChatAiRequestBody( data class SendChatAiRequestBody(
@SerializedName("trtcGroupId")
val trtcGroupId: String? = null,
@SerializedName("fromTrtcUserId") @SerializedName("fromTrtcUserId")
val fromTrtcUserId: String, val fromTrtcUserId: String? = null,
@SerializedName("toTrtcUserId") @SerializedName("toTrtcUserId")
val toTrtcUserId: String, val toTrtcUserId: String? = null,
@SerializedName("message") @SerializedName("message")
val message: String, val message: String,
@SerializedName("skipTrtc") @SerializedName("skipTrtc")
val skipTrtc: Boolean, val skipTrtc: Boolean? = true,
) )
data class CreateGroupChatRequestBody( data class CreateGroupChatRequestBody(
@SerializedName("name") @SerializedName("name")
val name: String, val name: String,
@@ -420,6 +428,8 @@ interface RaveNowAPI {
@Query("nickname") search: String? = null, @Query("nickname") search: String? = null,
@Query("followerId") followerId: Int? = null, @Query("followerId") followerId: Int? = null,
@Query("followingId") followingId: Int? = null, @Query("followingId") followingId: Int? = null,
@Query("includeAI") includeAI: Boolean? = false,
@Query("chatSessionIdNotNull") chatSessionIdNotNull: Boolean? = true,
): Response<ListContainer<AccountProfile>> ): Response<ListContainer<AccountProfile>>
@POST("register/google") @POST("register/google")
@@ -533,6 +543,9 @@ interface RaveNowAPI {
@POST("generate/postText") @POST("generate/postText")
suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>> suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>>
@GET("outside/rooms/open")
suspend fun createGroupChatAi(@Query("trtcGroupId") trtcGroupId: String): Response<DataContainer<Unit>>
@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>>

View File

@@ -95,7 +95,7 @@ sealed class NavigationRoute(
data object FavouriteList : NavigationRoute("FavouriteList") data object FavouriteList : NavigationRoute("FavouriteList")
data object Chat : NavigationRoute("Chat/{id}") data object Chat : NavigationRoute("Chat/{id}")
data object ChatAi : NavigationRoute("ChatAi/{id}") data object ChatAi : NavigationRoute("ChatAi/{id}")
data object ChatGroup : NavigationRoute("ChatGroup/{id}") data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen") data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
data object ImageCrop : NavigationRoute("ImageCrop") data object ImageCrop : NavigationRoute("ImageCrop")
data object AccountSetting : NavigationRoute("AccountSetting") data object AccountSetting : NavigationRoute("AccountSetting")
@@ -389,15 +389,18 @@ fun NavigationController(
composable( composable(
route = NavigationRoute.ChatGroup.route, route = NavigationRoute.ChatGroup.route,
arguments = listOf(navArgument("id") { type = NavType.StringType }) arguments = listOf(navArgument("id") { type = NavType.StringType },
navArgument("name") { type = NavType.StringType },
navArgument("avatar") { type = NavType.StringType })
) { ) {
val encodedId = it.arguments?.getString("id") val encodedId = it.arguments?.getString("id")
val decodedId = encodedId?.let { java.net.URLDecoder.decode(it, "UTF-8") } val decodedId = encodedId?.let { java.net.URLDecoder.decode(it, "UTF-8") }
val name = it.arguments?.getString("name")
val avatar = it.arguments?.getString("avatar")
CompositionLocalProvider( CompositionLocalProvider(
LocalAnimatedContentScope provides this, LocalAnimatedContentScope provides this,
) { ) {
GroupChatScreen(decodedId?:"") GroupChatScreen(decodedId?:"",name?:"",avatar?:"")
} }
} }
@@ -432,24 +435,12 @@ fun NavigationController(
} }
composable( composable(
route = NavigationRoute.AddAgent.route, route = NavigationRoute.AddAgent.route,
enterTransition = {
fadeIn(animationSpec = tween(durationMillis = 0))
},
exitTransition = {
fadeOut(animationSpec = tween(durationMillis = 0))
}
) { ) {
AddAgentScreen() AddAgentScreen()
} }
composable( composable(
route = NavigationRoute.CreateGroupChat.route, route = NavigationRoute.CreateGroupChat.route,
enterTransition = {
fadeIn(animationSpec = tween(durationMillis = 0))
},
exitTransition = {
fadeOut(animationSpec = tween(durationMillis = 0))
}
) { ) {
CreateGroupChatScreen() CreateGroupChatScreen()
} }
@@ -500,6 +491,7 @@ fun NavHostController.navigateToChat(id: String) {
navigate( navigate(
route = NavigationRoute.Chat.route route = NavigationRoute.Chat.route
.replace("{id}", id) .replace("{id}", id)
) )
} }
@@ -510,11 +502,15 @@ fun NavHostController.navigateToChatAi(id: String) {
) )
} }
fun NavHostController.navigateToGroupChat(id: String) { fun NavHostController.navigateToGroupChat(id: String,name:String,avatar:String) {
val encodedId = java.net.URLEncoder.encode(id, "UTF-8") val encodedId = java.net.URLEncoder.encode(id, "UTF-8")
val encodedName = java.net.URLEncoder.encode(name, "UTF-8")
val encodedAvator = java.net.URLEncoder.encode(avatar, "UTF-8")
navigate( navigate(
route = NavigationRoute.ChatGroup.route route = NavigationRoute.ChatGroup.route
.replace("{id}", encodedId) .replace("{id}", encodedId)
.replace("{name}", encodedName)
.replace("{avatar}", encodedAvator)
) )
} }

View File

@@ -64,7 +64,7 @@ fun AddAgentScreen() {
var errorMessage by remember { mutableStateOf<String?>(null) } var errorMessage by remember { mutableStateOf<String?>(null) }
fun onNameChange(value: String) { fun onNameChange(value: String) {
model.name = value model.name = value.trim()
agnetNameError = when { agnetNameError = when {
else -> null else -> null
} }
@@ -73,7 +73,7 @@ fun AddAgentScreen() {
val appColors = LocalAppTheme.current val appColors = LocalAppTheme.current
fun onDescChange(value: String) { fun onDescChange(value: String) {
model.desc = value model.desc = value.trim()
agnetDescError = when { agnetDescError = when {
value.length > 100 -> "简介长度不能大于100" value.length > 100 -> "简介长度不能大于100"
else -> null else -> null
@@ -232,6 +232,7 @@ fun AddAgentScreen() {
// 创建成功,关闭页面 // 创建成功,关闭页面
model.name = "" model.name = ""
model.desc = "" model.desc = ""
model.isFromAddAgent = false // 重置标志
navController.popBackStack() navController.popBackStack()
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -58,6 +58,10 @@ object AddAgentViewModel : ViewModel() {
) )
println("AddAgentViewModel: Agent created successfully with ID: ${result.id}") println("AddAgentViewModel: Agent created successfully with ID: ${result.id}")
// 通知相关ViewModel更新列表
notifyAgentCreated(result)
return result return result
} catch (e: Exception) { } catch (e: Exception) {
println("AddAgentViewModel: Error creating agent: ${e.message}") println("AddAgentViewModel: Error creating agent: ${e.message}")
@@ -67,6 +71,11 @@ object AddAgentViewModel : ViewModel() {
} }
} }
private fun notifyAgentCreated(agent: AgentEntity) {
// 通知我的智能体列表更新
com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.addAgentToList(agent)
}
fun validate(): String? { fun validate(): String? {
return when { return when {
name.isEmpty() -> "智能体名称不能为空" name.isEmpty() -> "智能体名称不能为空"

View File

@@ -83,6 +83,7 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
@Composable @Composable
@@ -280,7 +281,7 @@ fun ChatAiScreen(userId: String) {
verticalArrangement = Arrangement.Top verticalArrangement = Arrangement.Top
) { ) {
val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel) val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel)
items(chatList.size, key = { index -> chatList[index].msgId }) { index -> items(chatList.size, key = { index -> chatList[index].msgId + UUID.randomUUID().toString()}) { index ->
val item = chatList[index] val item = chatList[index]
if (item.showTimeDivider) { if (item.showTimeDivider) {
val calendar = java.util.Calendar.getInstance() val calendar = java.util.Calendar.getInstance()

View File

@@ -68,11 +68,22 @@ class ChatAiViewModel(
textMessageListener = object : V2TIMAdvancedMsgListener() { textMessageListener = object : V2TIMAdvancedMsgListener() {
override fun onRecvNewMessage(msg: V2TIMMessage?) { override fun onRecvNewMessage(msg: V2TIMMessage?) {
super.onRecvNewMessage(msg) super.onRecvNewMessage(msg)
msg?.let { msg?.let { message ->
val chatItem = ChatItem.convertToChatItem(msg, context, avatar = userProfile?.avatar) // 只处理当前聊天对象的消息
val currentChatUserId = userProfile?.trtcUserId
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
if (currentChatUserId != null && currentUserId != null) {
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
if ((message.userID == currentChatUserId || message.userID == currentUserId) &&
message.sender != currentUserId) {
val chatItem = ChatItem.convertToChatItem(message, context, avatar = userProfile?.avatar)
chatItem?.let { chatItem?.let {
chatData = listOf(it) + chatData chatData = listOf(it) + chatData
goToNew = true goToNew = true
android.util.Log.i("ChatAiViewModel", "收到来自 ${message.sender} 的消息更新AI聊天列表")
}
}
} }
} }
} }
@@ -260,7 +271,7 @@ class ChatAiViewModel(
message: String, message: String,
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(fromTrtcUserId = fromTrtcUserId,toTrtcUserId = toTrtcUserId,message = message,skipTrtc = true)) val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(fromTrtcUserId = fromTrtcUserId,toTrtcUserId = toTrtcUserId,message = message))
} }
} }

View File

@@ -83,6 +83,7 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
@Composable @Composable
@@ -280,7 +281,7 @@ fun ChatScreen(userId: String) {
verticalArrangement = Arrangement.Top verticalArrangement = Arrangement.Top
) { ) {
val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel) val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel)
items(chatList.size, key = { index -> chatList[index].msgId }) { index -> items(chatList.size, key = { index -> chatList[index].msgId + UUID.randomUUID().toString()}) { index ->
val item = chatList[index] val item = chatList[index]
if (item.showTimeDivider) { if (item.showTimeDivider) {
val calendar = java.util.Calendar.getInstance() val calendar = java.util.Calendar.getInstance()

View File

@@ -65,11 +65,22 @@ class ChatViewModel(
textMessageListener = object : V2TIMAdvancedMsgListener() { textMessageListener = object : V2TIMAdvancedMsgListener() {
override fun onRecvNewMessage(msg: V2TIMMessage?) { override fun onRecvNewMessage(msg: V2TIMMessage?) {
super.onRecvNewMessage(msg) super.onRecvNewMessage(msg)
msg?.let { msg?.let { message ->
val chatItem = ChatItem.convertToChatItem(msg, context, avatar = userProfile?.avatar) // 只处理当前聊天对象的消息
val currentChatUserId = userProfile?.trtcUserId
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
if (currentChatUserId != null && currentUserId != null) {
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
if ((message.userID == currentChatUserId || message.userID == currentUserId) &&
message.sender != currentUserId) {
val chatItem = ChatItem.convertToChatItem(message, context, avatar = userProfile?.avatar)
chatItem?.let { chatItem?.let {
chatData = listOf(it) + chatData chatData = listOf(it) + chatData
goToNew = true goToNew = true
android.util.Log.i("ChatViewModel", "收到来自 ${message.sender} 的消息,更新聊天列表")
}
}
} }
} }
} }

View File

@@ -65,6 +65,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -83,9 +84,10 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
@Composable @Composable
fun GroupChatScreen(groupId: String) { fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
var isMenuExpanded by remember { mutableStateOf(false) } var isMenuExpanded by remember { mutableStateOf(false) }
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalNavController.current.context val context = LocalNavController.current.context
@@ -96,7 +98,7 @@ fun GroupChatScreen(groupId: String) {
key = "GroupChatViewModel_$groupId", key = "GroupChatViewModel_$groupId",
factory = object : ViewModelProvider.Factory { factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return GroupChatViewModel(groupId) as T return GroupChatViewModel(groupId,name,avatar) as T
} }
} }
) )
@@ -167,7 +169,7 @@ fun GroupChatScreen(groupId: String) {
Image( Image(
painter = painterResource(R.drawable.rider_pro_back_icon), painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier modifier = Modifier
.size(28.dp) .size(24.dp)
.noRippleClickable { .noRippleClickable {
navController.navigateUp() navController.navigateUp()
}, },
@@ -180,7 +182,7 @@ fun GroupChatScreen(groupId: String) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = viewModel.groupAvatar, imageUrl = viewModel.groupAvatar,
modifier = Modifier modifier = Modifier
.size(40.dp) .size(32.dp)
.clip(RoundedCornerShape(8.dp)), .clip(RoundedCornerShape(8.dp)),
contentDescription = "群聊头像" contentDescription = "群聊头像"
) )
@@ -193,12 +195,16 @@ fun GroupChatScreen(groupId: String) {
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = viewModel.groupName.take(1), text = viewModel.groupName,
style = TextStyle( style = TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 18.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
) fontWeight = androidx.compose.ui.text.font.FontWeight.W700
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
) )
} }
} }
@@ -213,13 +219,7 @@ fun GroupChatScreen(groupId: String) {
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
) )
) )
Text(
text = "${viewModel.memberCount}",
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 14.sp
)
)
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
@@ -304,7 +304,7 @@ fun GroupChatScreen(groupId: String) {
verticalArrangement = Arrangement.Top verticalArrangement = Arrangement.Top
) { ) {
val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel) val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel)
items(chatList.size, key = { index -> chatList[index].msgId }) { index -> items(chatList.size, key = { index -> chatList[index].msgId + UUID.randomUUID().toString()}) { index ->
val item = chatList[index] val item = chatList[index]
if (item.showTimeDivider) { if (item.showTimeDivider) {
val calendar = java.util.Calendar.getInstance() val calendar = java.util.Calendar.getInstance()
@@ -369,7 +369,7 @@ fun GroupChatSelfItem(item: ChatItem) {
Column( Column(
horizontalAlignment = androidx.compose.ui.Alignment.End, horizontalAlignment = androidx.compose.ui.Alignment.End,
) { ) {
Text( /* Text(
text = item.nickname, text = item.nickname,
style = TextStyle( style = TextStyle(
color = Color.Gray, color = Color.Gray,
@@ -377,15 +377,15 @@ fun GroupChatSelfItem(item: ChatItem) {
), ),
modifier = Modifier.padding(bottom = 2.dp) modifier = Modifier.padding(bottom = 2.dp)
) )
*/
Box( Box(
modifier = Modifier modifier = Modifier
.widthIn( .widthIn(
min = 20.dp, min = 20.dp,
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp) max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(20.dp))
.background(Color(0xFF000000)) .background(Color(0xFF6246FF))
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp)
@@ -398,7 +398,7 @@ fun GroupChatSelfItem(item: ChatItem) {
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
color = Color.White, color = Color.White,
fontSize = 16.sp, fontSize = 14.sp,
), ),
textAlign = TextAlign.Start textAlign = TextAlign.Start
) )
@@ -417,25 +417,25 @@ fun GroupChatSelfItem(item: ChatItem) {
text = "不支持的消息类型", text = "不支持的消息类型",
style = TextStyle( style = TextStyle(
color = Color.White, color = Color.White,
fontSize = 16.sp, fontSize = 14.sp,
) )
) )
} }
} }
} }
} }
Spacer(modifier = Modifier.width(12.dp)) /*Spacer(modifier = Modifier.width(12.dp))
Box( Box(
modifier = Modifier modifier = Modifier
.size(40.dp) .size(24.dp)
.clip(RoundedCornerShape(40.dp)) .clip(RoundedCornerShape(24.dp))
) { ) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.avatar, imageUrl = item.avatar,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentDescription = "avatar" contentDescription = "avatar"
) )
} }*/
} }
} }
} }
@@ -455,11 +455,11 @@ fun GroupChatOtherItem(item: ChatItem) {
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(40.dp) .size(24.dp)
.clip(RoundedCornerShape(40.dp)) .clip(RoundedCornerShape(24.dp))
) { ) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.avatar, imageUrl = item.avatar.replace("storage/avatars/", "/avatar/"),
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentDescription = "avatar" contentDescription = "avatar"
) )
@@ -481,8 +481,8 @@ fun GroupChatOtherItem(item: ChatItem) {
min = 20.dp, min = 20.dp,
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp) max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(20.dp))
.background(AppColors.background) .background(AppColors.bubbleBackground)
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp)
@@ -495,7 +495,7 @@ fun GroupChatOtherItem(item: ChatItem) {
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 14.sp,
), ),
textAlign = TextAlign.Start textAlign = TextAlign.Start
) )
@@ -528,10 +528,47 @@ fun GroupChatOtherItem(item: ChatItem) {
@Composable @Composable
fun GroupChatItem(item: ChatItem, currentUserId: String) { fun GroupChatItem(item: ChatItem, currentUserId: String) {
val isCurrentUser = item.userId == currentUserId val isCurrentUser = item.userId == currentUserId
if (isCurrentUser) {
GroupChatSelfItem(item) // 管理员消息显示特殊布局
} else { if (item.userId == "administrator") {
GroupChatOtherItem(item) GroupChatAdminItem(item)
return
}
// 根据是否是当前用户显示不同样式
when (item.userId) {
currentUserId -> GroupChatSelfItem(item)
else -> GroupChatOtherItem(item)
}
}
@Composable
fun GroupChatAdminItem(item: ChatItem) {
val AppColors = LocalAppTheme.current
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 50.dp, vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.padding(vertical = 8.dp, horizontal = 16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = item.message,
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 12.sp,
textAlign = TextAlign.Center
),
maxLines = Int.MAX_VALUE
)
}
} }
} }

View File

@@ -15,6 +15,10 @@ import com.aiosman.ravenow.data.AccountService
import com.aiosman.ravenow.data.AccountServiceImpl import com.aiosman.ravenow.data.AccountServiceImpl
import com.aiosman.ravenow.data.UserService import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.UserServiceImpl import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.GroupChatRequestBody
import com.aiosman.ravenow.data.api.SendChatAiRequestBody
import com.aiosman.ravenow.data.api.SingleChatRequestBody
import com.aiosman.ravenow.entity.AccountProfileEntity import com.aiosman.ravenow.entity.AccountProfileEntity
import com.aiosman.ravenow.entity.ChatItem import com.aiosman.ravenow.entity.ChatItem
import com.aiosman.ravenow.entity.ChatNotification import com.aiosman.ravenow.entity.ChatNotification
@@ -31,6 +35,8 @@ import java.io.InputStream
class GroupChatViewModel( class GroupChatViewModel(
val groupId: String, val groupId: String,
val name: String,
val avatar: String,
) : ViewModel() { ) : ViewModel() {
var chatData by mutableStateOf<List<ChatItem>>(emptyList()) var chatData by mutableStateOf<List<ChatItem>>(emptyList())
var groupInfo by mutableStateOf<GroupInfo?>(null) var groupInfo by mutableStateOf<GroupInfo?>(null)
@@ -77,8 +83,8 @@ class GroupChatViewModel(
// 简化群组信息获取,使用默认信息 // 简化群组信息获取,使用默认信息
groupInfo = GroupInfo( groupInfo = GroupInfo(
groupId = groupId, groupId = groupId,
groupName = "群聊 $groupId", groupName = name,
groupAvatar = "", groupAvatar = avatar,
memberCount = 0, memberCount = 0,
ownerId = "" ownerId = ""
) )
@@ -156,8 +162,8 @@ class GroupChatViewModel(
val v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(message) val v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(message)
V2TIMManager.getMessageManager().sendMessage( V2TIMManager.getMessageManager().sendMessage(
v2TIMMessage, v2TIMMessage,
groupId,
null, null,
groupId,
V2TIMMessage.V2TIM_PRIORITY_NORMAL, V2TIMMessage.V2TIM_PRIORITY_NORMAL,
false, false,
null, null,
@@ -167,6 +173,7 @@ class GroupChatViewModel(
Log.e("GroupChatViewModel", "发送群聊消息失败: $p1") Log.e("GroupChatViewModel", "发送群聊消息失败: $p1")
} }
override fun onSuccess(p0: V2TIMMessage?) { override fun onSuccess(p0: V2TIMMessage?) {
sendChatAiMessage(message = message, trtcGroupId = groupId)
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar) val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
chatItem?.let { chatItem?.let {
chatData = listOf(it) + chatData chatData = listOf(it) + chatData
@@ -177,6 +184,18 @@ class GroupChatViewModel(
) )
} }
fun sendChatAiMessage(
trtcGroupId: String,
message: String,
) {
viewModelScope.launch {
val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(trtcGroupId = trtcGroupId,message = message))
}
}
fun sendImageMessage(imageUri: Uri, context: Context) { fun sendImageMessage(imageUri: Uri, context: Context) {
val tempFile = createTempFile(context, imageUri) val tempFile = createTempFile(context, imageUri)
val imagePath = tempFile?.path val imagePath = tempFile?.path

View File

@@ -59,15 +59,20 @@ fun AgentCard(
Row( Row(
modifier = Modifier modifier = Modifier
) { ) {
CustomAsyncImage( // 使用remember基于agentEntity.id来缓存图片避免滑动时重复加载
context, Box(
agentEntity.avatar,
contentDescription = "",
modifier = Modifier modifier = Modifier
.size(40.dp) .size(40.dp)
.clip(RoundedCornerShape(40.dp)), .clip(RoundedCornerShape(40.dp))
) {
CustomAsyncImage(
LocalContext.current,
agentEntity.avatar,
contentDescription = "",
modifier = Modifier.size(40.dp),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
}
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)

View File

@@ -40,42 +40,6 @@ object CreateGroupChatViewModel : ViewModel() {
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
var errorMessage by mutableStateOf<String?>(null) var errorMessage by mutableStateOf<String?>(null)
// 获取AI智能体列表
suspend fun getAiAgents(): List<GroupMember> {
return try {
// TODO: 从API获取AI智能体列表
listOf(
GroupMember("1", "AI助手", "https://example.com/avatar1.jpg"),
GroupMember("2", "智能客服", "https://example.com/avatar2.jpg"),
GroupMember("3", "翻译助手", "https://example.com/avatar3.jpg"),
GroupMember("4", "写作助手", "https://example.com/avatar4.jpg"),
GroupMember("5", "编程助手", "https://example.com/avatar5.jpg"),
GroupMember("6", "设计助手", "https://example.com/avatar6.jpg")
)
} catch (e: Exception) {
errorMessage = "获取AI智能体列表失败: ${e.message}"
emptyList()
}
}
// 获取朋友列表
suspend fun getFriends(): List<GroupMember> {
return try {
// TODO: 从API获取朋友列表
listOf(
GroupMember("7", "张三", "https://example.com/avatar7.jpg"),
GroupMember("8", "李四", "https://example.com/avatar8.jpg"),
GroupMember("9", "王五", "https://example.com/avatar9.jpg"),
GroupMember("10", "赵六", "https://example.com/avatar10.jpg"),
GroupMember("11", "钱七", "https://example.com/avatar11.jpg"),
GroupMember("12", "孙八", "https://example.com/avatar12.jpg")
)
} catch (e: Exception) {
errorMessage = "获取朋友列表失败: ${e.message}"
emptyList()
}
}
// 创建群聊 // 创建群聊
suspend fun createGroupChat( suspend fun createGroupChat(
groupName: String, groupName: String,

View File

@@ -270,30 +270,23 @@ fun IndexScreen() {
animationSpec = tween(durationMillis = 250), label = "" animationSpec = tween(durationMillis = 250), label = ""
) )
NavigationBarItem( Box(
modifier = Modifier.padding(top = 2.dp), modifier = Modifier
selected = isSelected, .weight(1f)
onClick = { .padding(top = 2.dp)
.noRippleClickable {
if (it.route === NavigationItem.Add.route) { if (it.route === NavigationItem.Add.route) {
NewPostViewModel.asNewPost() NewPostViewModel.asNewPost()
navController.navigate(NavigationRoute.NewPost.route) navController.navigate(NavigationRoute.NewPost.route)
return@NavigationBarItem return@noRippleClickable
} }
coroutineScope.launch { coroutineScope.launch {
pagerState.scrollToPage(idx) pagerState.scrollToPage(idx)
} }
model.tabIndex = idx model.tabIndex = idx
}, },
colors = NavigationBarItemColors( contentAlignment = Alignment.Center
selectedIconColor = Color.Transparent, ) {
selectedTextColor = Color.Transparent,
selectedIndicatorColor = Color.Transparent,
unselectedIconColor = Color.Transparent,
unselectedTextColor = Color.Transparent,
disabledIconColor = Color.Transparent,
disabledTextColor = Color.Transparent
),
icon = {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
@@ -337,11 +330,7 @@ fun IndexScreen() {
) )
} }
} }
},
label = {
// 不显示默认标签
} }
)
} }
} }

View File

@@ -104,7 +104,8 @@ fun Agent() {
modifier = Modifier modifier = Modifier
.size(36.dp) .size(36.dp)
.noRippleClickable { .noRippleClickable {
// // 设置标志,表示新增智能体后不需要刷新
com.aiosman.ravenow.ui.agent.AddAgentViewModel.isFromAddAgent = true
navController.navigate( navController.navigate(
NavigationRoute.AddAgent.route NavigationRoute.AddAgent.route
) )
@@ -168,7 +169,8 @@ fun Agent() {
state = pagerState, state = pagerState,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f),
beyondBoundsPageCount = 1 // 预加载相邻页面,避免切换时重新加载
) { ) {
when (it) { when (it) {
0 -> { 0 -> {

View File

@@ -25,9 +25,11 @@ open class BaseAgentModel :ViewModel(){
var refreshing by mutableStateOf(false) var refreshing by mutableStateOf(false)
var isFirstLoad = true var isFirstLoad = true
var agentList by mutableStateOf<List<AgentEntity>>(listOf()) var agentList by mutableStateOf<List<AgentEntity>>(listOf())
open fun extraArgs(): AgentLoaderExtraArgs { open fun extraArgs(): AgentLoaderExtraArgs {
return AgentLoaderExtraArgs() return AgentLoaderExtraArgs()
} }
fun refreshPager(pullRefresh: Boolean = false) { fun refreshPager(pullRefresh: Boolean = false) {
if (!isFirstLoad && !pullRefresh) { if (!isFirstLoad && !pullRefresh) {
return return
@@ -46,6 +48,10 @@ open class BaseAgentModel :ViewModel(){
} }
} }
// 添加智能体到列表顶部,避免重新加载
fun addAgentToList(agent: AgentEntity) {
agentList = listOf(agent) + agentList
}
fun ResetModel() { fun ResetModel() {
agentLoader.clear() agentLoader.clear()

View File

@@ -23,6 +23,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -60,9 +62,22 @@ fun HotAgent() {
} }
} }
// 只在首次加载时刷新避免从AddAgent返回时重复刷新
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (model.agentList.isEmpty() && !model.isLoading) {
model.refreshPager() model.refreshPager()
} }
}
val context = LocalContext.current
// 当智能体列表加载完成后,预加载图片
LaunchedEffect(agentList) {
if (agentList.isNotEmpty()) {
model.preloadImages(context)
}
}
Column( Column(
modifier = Modifier modifier = Modifier
@@ -107,7 +122,7 @@ fun HotAgent() {
) { ) {
items( items(
agentList.size, agentList.size,
key = { idx -> idx } key = { idx -> agentList[idx].id } // 使用智能体ID作为key避免重新创建
) { idx -> ) { idx ->
val agentItem = agentList[idx] val agentItem = agentList[idx]
AgentCard( AgentCard(

View File

@@ -22,10 +22,14 @@ object HotAgentViewModel : ViewModel() {
var currentPage by mutableStateOf(1) var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null) var error by mutableStateOf<String?>(null)
// 记录已预加载的图片ID避免重复加载
private val preloadedImageIds = mutableSetOf<Int>()
private val pageSize = 20 private val pageSize = 20
init { init {
refreshPager() // 延迟初始化,避免在页面切换时立即加载
// refreshPager()
} }
fun refreshPager(pullRefresh: Boolean = false) { fun refreshPager(pullRefresh: Boolean = false) {
@@ -37,6 +41,11 @@ object HotAgentViewModel : ViewModel() {
refreshing = pullRefresh refreshing = pullRefresh
error = null error = null
// 清除预加载记录,强制重新加载图片
if (pullRefresh) {
clearPreloadedImages()
}
val response = ApiClient.api.getAgent( val response = ApiClient.api.getAgent(
page = 1, page = 1,
pageSize = pageSize pageSize = pageSize
@@ -45,7 +54,17 @@ object HotAgentViewModel : ViewModel() {
val body = response.body() val body = response.body()
if (body != null) { if (body != null) {
val newAgents = body.data.list.map { it.toAgentEntity() } val newAgents = body.data.list.map { it.toAgentEntity() }
// 只有在列表为空或者是下拉刷新时才替换整个列表
if (agentList.isEmpty() || pullRefresh) {
agentList = newAgents agentList = newAgents
} else {
// 否则只添加新的智能体
val existingIds = agentList.map { it.id }.toSet()
val newAgentsToAdd = newAgents.filter { it.id !in existingIds }
if (newAgentsToAdd.isNotEmpty()) {
agentList = agentList + newAgentsToAdd
}
}
currentPage = 1 currentPage = 1
hasNext = newAgents.size == pageSize hasNext = newAgents.size == pageSize
} else { } else {
@@ -108,4 +127,32 @@ object HotAgentViewModel : ViewModel() {
createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id) createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id)
} }
} }
// 预加载图片,避免滑动时重复加载
fun preloadImages(context: android.content.Context) {
viewModelScope.launch {
agentList.forEach { agent ->
if (agent.id !in preloadedImageIds && agent.avatar.isNotEmpty()) {
try {
// 预加载头像图片到缓存
com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue(
coil.request.ImageRequest.Builder(context)
.data(agent.avatar)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED)
.build()
)
preloadedImageIds.add(agent.id)
} catch (e: Exception) {
// 忽略预加载错误
}
}
}
}
}
// 清除预加载记录(在刷新时调用)
fun clearPreloadedImages() {
preloadedImageIds.clear()
}
} }

View File

@@ -23,6 +23,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -60,9 +61,22 @@ fun MineAgent() {
} }
} }
// 只在首次加载时刷新避免从AddAgent返回时重复刷新
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (model.agentList.isEmpty() && !model.isLoading) {
model.refreshPager() model.refreshPager()
} }
}
val context = LocalContext.current
// 当智能体列表加载完成后,预加载图片
LaunchedEffect(agentList) {
if (agentList.isNotEmpty()) {
model.preloadImages(context)
}
}
Column( Column(
modifier = Modifier modifier = Modifier
@@ -107,7 +121,7 @@ fun MineAgent() {
) { ) {
items( items(
agentList.size, agentList.size,
key = { idx -> idx } key = { idx -> agentList[idx].id } // 使用智能体ID作为key避免重新创建
) { idx -> ) { idx ->
val agentItem = agentList[idx] val agentItem = agentList[idx]
AgentCard( AgentCard(

View File

@@ -25,10 +25,14 @@ object MineAgentViewModel : ViewModel() {
var currentPage by mutableStateOf(1) var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null) var error by mutableStateOf<String?>(null)
// 记录已预加载的图片ID避免重复加载
private val preloadedImageIds = mutableSetOf<Int>()
private val pageSize = 20 private val pageSize = 20
init { init {
refreshPager() // 延迟初始化,避免在页面切换时立即加载
// refreshPager()
} }
fun refreshPager(pullRefresh: Boolean = false) { fun refreshPager(pullRefresh: Boolean = false) {
@@ -40,6 +44,11 @@ object MineAgentViewModel : ViewModel() {
refreshing = pullRefresh refreshing = pullRefresh
error = null error = null
// 清除预加载记录,强制重新加载图片
if (pullRefresh) {
clearPreloadedImages()
}
val response = ApiClient.api.getMyAgent( val response = ApiClient.api.getMyAgent(
page = 1, page = 1,
pageSize = pageSize pageSize = pageSize
@@ -48,7 +57,17 @@ object MineAgentViewModel : ViewModel() {
val body = response.body() val body = response.body()
if (body != null) { if (body != null) {
val newAgents = body.list.map { it.toAgentEntity() } val newAgents = body.list.map { it.toAgentEntity() }
// 只有在列表为空或者是下拉刷新时才替换整个列表
if (agentList.isEmpty() || pullRefresh) {
agentList = newAgents agentList = newAgents
} else {
// 否则只添加新的智能体
val existingIds = agentList.map { it.id }.toSet()
val newAgentsToAdd = newAgents.filter { it.id !in existingIds }
if (newAgentsToAdd.isNotEmpty()) {
agentList = agentList + newAgentsToAdd
}
}
currentPage = 1 currentPage = 1
hasNext = newAgents.size == pageSize hasNext = newAgents.size == pageSize
} else { } else {
@@ -138,4 +157,37 @@ object MineAgentViewModel : ViewModel() {
createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id) createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id)
} }
} }
// 添加新创建的智能体到列表顶部
fun addAgentToList(agent: AgentEntity) {
agentList = listOf(agent) + agentList
}
// 预加载图片,避免滑动时重复加载
fun preloadImages(context: android.content.Context) {
viewModelScope.launch {
agentList.forEach { agent ->
if (agent.id !in preloadedImageIds && agent.avatar.isNotEmpty()) {
try {
// 预加载头像图片到缓存
com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue(
coil.request.ImageRequest.Builder(context)
.data(agent.avatar)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED)
.build()
)
preloadedImageIds.add(agent.id)
} catch (e: Exception) {
// 忽略预加载错误
}
}
}
}
}
// 清除预加载记录(在刷新时调用)
fun clearPreloadedImages() {
preloadedImageIds.clear()
}
} }

View File

@@ -117,7 +117,7 @@ fun NotificationsScreen() {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 15.dp, vertical = 8.dp), .padding(horizontal = 15.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Box( Box(
@@ -197,7 +197,7 @@ fun NotificationsScreen() {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(horizontal = 16.dp), .padding(start = 16.dp,bottom = 16.dp),
// center the tabs // center the tabs
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.Bottom
@@ -251,43 +251,9 @@ fun NotificationsScreen() {
2 -> { 2 -> {
FriendChatListScreen() FriendChatListScreen()
} }
} }
} }
/* Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
,
contentAlignment = Alignment.Center
) {
if (AppState.enableChat){
ChatMessageList(
MessageListViewModel.chatList,
onUserAvatarClick = { conv ->
MessageListViewModel.goToUserDetail(conv, navController)
},
) { conv ->
MessageListViewModel.goToChat(conv, navController)
}
}else{
// center text
Text(
text = "Chat service is under maintenance",
color = AppColors.text,
fontSize = 16.sp
)
}
}
}
PullRefreshIndicator(
MessageListViewModel.isLoading,
state,
Modifier.align(Alignment.TopCenter)
)*/
} }
} }
@@ -360,131 +326,3 @@ fun NotificationIndicator(
} }
@Composable
fun NotificationCounterItem(count: Int) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
var clickCount by remember { mutableStateOf(0) }
Row(
modifier = Modifier.padding(vertical = 16.dp, horizontal = 32.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_notification),
contentDescription = "",
modifier = Modifier
.size(24.dp).noRippleClickable {
clickCount++
if (clickCount > 5) {
clickCount = 0
AppStore.saveDarkMode(!AppState.darkMode)
Toast.makeText(context, "Dark mode: ${AppState.darkMode},please restart app", Toast.LENGTH_SHORT).show()
}
},
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(24.dp))
Text(stringResource(R.string.notifications_upper), fontSize = 18.sp, color = AppColors.text)
Spacer(modifier = Modifier.weight(1f))
if (count > 0) {
Box(
modifier = Modifier
.background(AppColors.main, RoundedCornerShape(16.dp))
.padding(horizontal = 8.dp, vertical = 2.dp)
) {
Text(
text = count.toString(),
color = AppColors.mainText,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
)
}
}
}
}
@Composable
fun ChatMessageList(
items: List<Conversation>,
onUserAvatarClick: (Conversation) -> Unit = {},
onChatClick: (Conversation) -> Unit = {}
) {
val AppColors = LocalAppTheme.current
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(items.size) { index ->
val item = items[index]
Row(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)
) {
Box {
CustomAsyncImage(
context = LocalContext.current,
imageUrl = item.avatar,
contentDescription = item.nickname,
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(48.dp))
.noRippleClickable {
onUserAvatarClick(item)
}
)
}
Column(
modifier = Modifier
.weight(1f)
.padding(start = 12.dp)
.noRippleClickable {
onChatClick(item)
}
) {
Row {
Text(
text = item.nickname,
fontSize = 16.sp,
modifier = Modifier,
fontWeight = FontWeight.Bold,
color = AppColors.text
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = item.lastMessageTime,
fontSize = 14.sp,
color = AppColors.secondaryText,
)
}
Spacer(modifier = Modifier.height(6.dp))
Row {
Text(
text = "${if (item.isSelf) "Me: " else ""}${item.displayText}",
fontSize = 14.sp,
maxLines = 1,
color = AppColors.secondaryText,
modifier = Modifier.weight(1f),
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.width(4.dp))
if (item.unreadCount > 0) {
Box(
modifier = Modifier
.background(AppColors.main, CircleShape)
.padding(horizontal = 8.dp, vertical = 2.dp)
) {
Text(
text = item.unreadCount.toString(),
color = AppColors.mainText,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Center)
)
}
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}
}
}
}

View File

@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
@@ -27,6 +28,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -114,12 +116,6 @@ fun AgentChatListScreen() {
} }
) )
if (index < AgentChatListViewModel.agentChatList.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 24.dp),
color = AppColors.divider
)
}
} }
// 加载更多指示器 // 加载更多指示器
@@ -178,12 +174,12 @@ fun AgentChatItem(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable { .noRippleClickable {
onChatClick(conversation) onChatClick(conversation)
} },
verticalAlignment = Alignment.CenterVertically
) { ) {
// 头像
Box { Box {
CustomAsyncImage( CustomAsyncImage(
context = LocalContext.current, context = LocalContext.current,
@@ -191,18 +187,18 @@ fun AgentChatItem(
contentDescription = conversation.nickname, contentDescription = conversation.nickname,
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp)
.clip(CircleShape) .clip(RoundedCornerShape(48.dp))
.noRippleClickable { .noRippleClickable {
onUserAvatarClick(conversation) onUserAvatarClick(conversation)
} }
) )
} }
// 聊天信息
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(start = 12.dp) .padding(start = 12.dp, top = 2.dp),
verticalArrangement = Arrangement.Center
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -210,53 +206,52 @@ fun AgentChatItem(
) { ) {
Text( Text(
text = conversation.nickname, text = conversation.nickname,
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AppColors.text, color = AppColors.text,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(6.dp))
Text( Text(
text = conversation.lastMessageTime, text = conversation.lastMessageTime,
fontSize = 12.sp, fontSize = 11.sp,
color = AppColors.secondaryText color = AppColors.secondaryText
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(6.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = "${if (conversation.isSelf) stringResource(R.string.agent_chat_me_prefix) else ""}${conversation.displayText}", text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
fontSize = 14.sp, fontSize = 12.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(10.dp))
// 未读消息数量
if (conversation.unreadCount > 0) { if (conversation.unreadCount > 0) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp) .size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
.background( .background(
color = AppColors.main, color = Color(0xFFFF3B30),
shape = CircleShape shape = CircleShape
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(), text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
color = AppColors.mainText, color = Color.White,
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp, fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
} }

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
@@ -17,6 +18,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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
@@ -97,12 +99,6 @@ fun FriendChatListScreen() {
} }
) )
if (index < FriendChatListViewModel.friendChatList.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 24.dp),
color = AppColors.divider
)
}
} }
if (FriendChatListViewModel.isLoading && FriendChatListViewModel.friendChatList.isNotEmpty()) { if (FriendChatListViewModel.isLoading && FriendChatListViewModel.friendChatList.isNotEmpty()) {
@@ -158,10 +154,11 @@ fun FriendChatItem(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable { .noRippleClickable {
onChatClick(conversation) onChatClick(conversation)
} },
verticalAlignment = Alignment.CenterVertically
) { ) {
Box { Box {
CustomAsyncImage( CustomAsyncImage(
@@ -170,7 +167,7 @@ fun FriendChatItem(
contentDescription = conversation.nickname, contentDescription = conversation.nickname,
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp)
.clip(CircleShape) .clip(RoundedCornerShape(48.dp))
.noRippleClickable { .noRippleClickable {
onUserAvatarClick(conversation) onUserAvatarClick(conversation)
} }
@@ -180,7 +177,8 @@ fun FriendChatItem(
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(start = 12.dp) .padding(start = 12.dp, top = 2.dp),
verticalArrangement = Arrangement.Center
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -188,22 +186,22 @@ fun FriendChatItem(
) { ) {
Text( Text(
text = conversation.nickname, text = conversation.nickname,
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AppColors.text, color = AppColors.text,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(6.dp))
Text( Text(
text = conversation.lastMessageTime, text = conversation.lastMessageTime,
fontSize = 12.sp, fontSize = 11.sp,
color = AppColors.secondaryText color = AppColors.secondaryText
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(6.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -211,29 +209,29 @@ fun FriendChatItem(
) { ) {
Text( Text(
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}", text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
fontSize = 14.sp, fontSize = 12.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(10.dp))
if (conversation.unreadCount > 0) { if (conversation.unreadCount > 0) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp) .size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
.background( .background(
color = AppColors.main, color = Color(0xFFFF3B30),
shape = CircleShape shape = CircleShape
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(), text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
color = AppColors.mainText, color = Color.White,
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp, fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
} }

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
@@ -13,10 +14,12 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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
@@ -45,8 +48,18 @@ fun GroupChatListScreen() {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
GroupChatListViewModel.refreshPager(context = context) GroupChatListViewModel.refreshPager(context = context)
// 初始化消息监听器
GroupChatListViewModel.initMessageListener(context)
} }
// 在组件销毁时清理监听器
DisposableEffect(Unit) {
onDispose {
GroupChatListViewModel.removeMessageListener()
}
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -96,12 +109,6 @@ fun GroupChatListScreen() {
} }
) )
if (index < GroupChatListViewModel.groupChatList.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 24.dp),
color = AppColors.divider
)
}
} }
if (GroupChatListViewModel.isLoading && GroupChatListViewModel.groupChatList.isNotEmpty()) { if (GroupChatListViewModel.isLoading && GroupChatListViewModel.groupChatList.isNotEmpty()) {
@@ -157,10 +164,11 @@ fun GroupChatItem(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable { .noRippleClickable {
onChatClick(conversation) onChatClick(conversation)
} },
verticalAlignment = Alignment.CenterVertically
) { ) {
Box { Box {
CustomAsyncImage( CustomAsyncImage(
@@ -169,7 +177,7 @@ fun GroupChatItem(
contentDescription = conversation.groupName, contentDescription = conversation.groupName,
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp)
.clip(CircleShape) .clip(RoundedCornerShape(12.dp))
.noRippleClickable { .noRippleClickable {
onGroupAvatarClick(conversation) onGroupAvatarClick(conversation)
} }
@@ -179,7 +187,8 @@ fun GroupChatItem(
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(start = 12.dp) .padding(start = 12.dp, top = 2.dp),
verticalArrangement = Arrangement.Center
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -187,22 +196,22 @@ fun GroupChatItem(
) { ) {
Text( Text(
text = conversation.groupName, text = conversation.groupName,
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AppColors.text, color = AppColors.text,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(6.dp))
Text( Text(
text = conversation.lastMessageTime, text = conversation.lastMessageTime,
fontSize = 12.sp, fontSize = 11.sp,
color = AppColors.secondaryText color = AppColors.secondaryText
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(6.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -210,30 +219,29 @@ fun GroupChatItem(
) { ) {
Text( Text(
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}", text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
fontSize = 14.sp, fontSize = 12.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(10.dp))
if (conversation.unreadCount > 0) { if (conversation.unreadCount > 0) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp) .size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
.background( .background(
color = AppColors.main, color = Color(0xFFFF3B30),
shape = CircleShape shape = CircleShape
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(), text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
color = AppColors.mainText, color = Color.White,
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp, fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp,
fontWeight = FontWeight.Bold
) )
} }
} }

View File

@@ -9,9 +9,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.ConstVars import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.data.UserService import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.UserServiceImpl import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.GroupChatRequestBody
import com.aiosman.ravenow.exp.formatChatTime import com.aiosman.ravenow.exp.formatChatTime
import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.navigateToChat import com.aiosman.ravenow.ui.navigateToChat
@@ -22,6 +25,8 @@ import com.tencent.imsdk.v2.V2TIMConversationResult
import com.tencent.imsdk.v2.V2TIMManager import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import com.tencent.imsdk.v2.V2TIMValueCallback import com.tencent.imsdk.v2.V2TIMValueCallback
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener
import com.tencent.imsdk.v2.V2TIMConversationListener
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@@ -70,7 +75,16 @@ data class GroupConversation(
groupName = msg.showName, groupName = msg.showName,
lastMessage = msg.lastMessage?.textElem?.text ?: "", lastMessage = msg.lastMessage?.textElem?.text ?: "",
lastMessageTime = lastMessage.time.formatChatTime(context), lastMessageTime = lastMessage.time.formatChatTime(context),
avatar = "${ConstVars.BASE_SERVER}${msg.faceUrl}", avatar = if (msg.faceUrl.isNullOrEmpty()) {
// 将 groupId 转换为 Base64
val groupIdBase64 = android.util.Base64.encodeToString(
msg.groupID.toByteArray(),
android.util.Base64.NO_WRAP
)
"${ApiClient.RETROFIT_URL+"group/avatar?groupIdBase64="}${groupIdBase64}"+"&token="+"${AppStore.token}"
} else {
"${ApiClient.BASE_API_URL+"/outside/rooms/avatar/"}${msg.faceUrl}"+"?token="+"${AppStore.token}"
},
unreadCount = msg.unreadCount, unreadCount = msg.unreadCount,
displayText = displayText, displayText = displayText,
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId, isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId,
@@ -91,6 +105,10 @@ object GroupChatListViewModel : ViewModel() {
private val pageSize = 20 private val pageSize = 20
// 消息监听器
private var messageListener: V2TIMAdvancedMsgListener? = null
private var conversationListener: V2TIMConversationListener? = null
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) { fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {
if (isLoading && !pullRefresh) return if (isLoading && !pullRefresh) return
viewModelScope.launch { viewModelScope.launch {
@@ -162,14 +180,24 @@ object GroupChatListViewModel : ViewModel() {
} }
} }
fun createGroupChat(
trtcGroupId: String,
) {
viewModelScope.launch {
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId)
}
}
fun goToChat( fun goToChat(
conversation: GroupConversation, conversation: GroupConversation,
navController: NavHostController navController: NavHostController
) { ) {
viewModelScope.launch { viewModelScope.launch {
try { try {
createGroupChat(trtcGroupId = conversation.groupId)
// 群聊直接使用群ID进行导航 // 群聊直接使用群ID进行导航
navController.navigateToGroupChat(conversation.groupId) navController.navigateToGroupChat(conversation.groupId, conversation.groupName, conversation.avatar)
} catch (e: Exception) { } catch (e: Exception) {
error = "" error = ""
e.printStackTrace() e.printStackTrace()
@@ -184,7 +212,7 @@ object GroupChatListViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
try { try {
// 可以导航到群详情页面,这里暂时使用群聊页面 // 可以导航到群详情页面,这里暂时使用群聊页面
navController.navigateToChat(conversation.groupId) //
} catch (e: Exception) { } catch (e: Exception) {
error = "" error = ""
e.printStackTrace() e.printStackTrace()
@@ -197,4 +225,77 @@ object GroupChatListViewModel : ViewModel() {
loadGroupChatList(context) loadGroupChatList(context)
} }
} }
// 初始化消息监听器
fun initMessageListener(context: Context) {
// 消息监听器 - 监听新消息
messageListener = object : V2TIMAdvancedMsgListener() {
override fun onRecvNewMessage(msg: V2TIMMessage?) {
super.onRecvNewMessage(msg)
msg?.let { message ->
if (message.groupID != null && message.groupID.isNotEmpty()) {
// 收到群聊消息,刷新群聊列表
android.util.Log.i("GroupChatList", "收到群聊消息,刷新列表")
refreshGroupChatList(context)
}
}
}
}
// 会话监听器 - 监听会话变化
conversationListener = object : V2TIMConversationListener() {
override fun onConversationChanged(conversationList: MutableList<V2TIMConversation>?) {
super.onConversationChanged(conversationList)
// 会话发生变化,刷新群聊列表
conversationList?.let { conversations ->
val hasGroupConversation = conversations.any { it.type == V2TIMConversation.V2TIM_GROUP }
if (hasGroupConversation) {
android.util.Log.i("GroupChatList", "群聊会话发生变化,刷新列表")
refreshGroupChatList(context)
}
}
}
override fun onNewConversation(conversationList: MutableList<V2TIMConversation>?) {
super.onNewConversation(conversationList)
// 新增会话,刷新群聊列表
conversationList?.let { conversations ->
val hasGroupConversation = conversations.any { it.type == V2TIMConversation.V2TIM_GROUP }
if (hasGroupConversation) {
android.util.Log.i("GroupChatList", "新增群聊会话,刷新列表")
refreshGroupChatList(context)
}
}
}
}
// 注册监听器
V2TIMManager.getMessageManager().addAdvancedMsgListener(messageListener)
V2TIMManager.getConversationManager().addConversationListener(conversationListener)
}
// 移除消息监听器
fun removeMessageListener() {
messageListener?.let {
V2TIMManager.getMessageManager().removeAdvancedMsgListener(it)
}
conversationListener?.let {
V2TIMManager.getConversationManager().removeConversationListener(it)
}
messageListener = null
conversationListener = null
}
// 刷新群聊列表
private fun refreshGroupChatList(context: Context) {
viewModelScope.launch {
try {
loadGroupChatList(context)
} catch (e: Exception) {
android.util.Log.e("GroupChatList", "刷新群聊列表失败: ${e.message}")
}
}
}
} }

View File

@@ -48,6 +48,7 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.graphicsLayer
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
@@ -232,9 +233,9 @@ fun Explore() {
} }
@Composable @Composable
fun AgentPage(agentItems: List<AgentItem>, page: Int) { fun AgentPage(agentItems: List<AgentItem>, page: Int, modifier: Modifier = Modifier) {
Column( Column(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 0.dp) .padding(horizontal = 0.dp)
) { ) {
@@ -268,11 +269,27 @@ fun Explore() {
) { ) {
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp),
pageSpacing = 0.dp
) { page -> ) { page ->
// 计算当前页面的偏移量
val pageOffset = (
(pagerState.currentPage - page) + pagerState
.currentPageOffsetFraction
).coerceIn(-1f, 1f)
// 根据偏移量计算缩放比例
val scale = 1f - (0.1f * kotlin.math.abs(pageOffset))
AgentPage( AgentPage(
agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage), agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage),
page = page page = page,
modifier = Modifier
.graphicsLayer {
scaleX = scale
scaleY = scale
}
) )
} }
} }
@@ -410,12 +427,12 @@ fun Explore() {
@Composable @Composable
fun BannerCard(bannerItem: BannerItem) { fun BannerCard(bannerItem: BannerItem, modifier: Modifier = Modifier) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val context = LocalContext.current val context = LocalContext.current
Card( Card(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 0.dp), .padding(horizontal = 0.dp),
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(20.dp),
@@ -595,15 +612,15 @@ fun Explore() {
) { ) {
// 可以添加更多不同高度的内容项 // 第一块区域
item { item {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 6.dp), .padding(vertical = 6.dp),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceEvenly horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween
) { ) {
// 第一个 // 第一个 - 靠左显示
Column( Column(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
@@ -633,6 +650,11 @@ fun Explore() {
) )
} }
// 中间两个 - 平均分布
Row(
modifier = Modifier.weight(1f),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceEvenly
) {
// 第二个 // 第二个
Column( Column(
modifier = Modifier modifier = Modifier
@@ -694,8 +716,9 @@ fun Explore() {
fontWeight = androidx.compose.ui.text.font.FontWeight.W500 fontWeight = androidx.compose.ui.text.font.FontWeight.W500
) )
} }
}
// 第四个 // 第四个 - 靠右显示
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
@@ -721,7 +744,7 @@ fun Explore() {
) )
} }
} }
} }
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@@ -767,10 +790,28 @@ fun BannerSection(bannerItems: List<BannerItem>) {
) { ) {
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp),
) { page -> ) { page ->
val bannerItem = bannerItems[page] val bannerItem = bannerItems[page]
BannerCard(bannerItem = bannerItem)
// 计算当前页面的偏移量
val pageOffset = (
(pagerState.currentPage - page) + pagerState
.currentPageOffsetFraction
).coerceIn(-1f, 1f)
// 根据偏移量计算缩放比例
val scale = 1f - (0.1f * kotlin.math.abs(pageOffset))
BannerCard(
bannerItem = bannerItem,
modifier = Modifier
.graphicsLayer {
scaleX = scale
scaleY = scale
}
)
} }
} }

View File

@@ -1,12 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:pathData="M4.5,11L6.5,11C6.776,11 7,11.224 7,11.5L7,13.5C7,13.776 6.776,14 6.5,14L4.5,14C4.224,14 4,13.776 4,13.5L4,11.5C4,11.224 4.224,11 4.5,11ZM11.5,11L13.5,11C13.776,11 14,11.224 14,11.5L14,13.5C14,13.776 13.776,14 13.5,14L11.5,14C11.224,14 11,13.776 11,13.5L11,11.5C11,11.224 11.224,11 11.5,11ZM18.5,11L20.5,11C20.776,11 21,11.224 21,11.5L21,13.5C21,13.776 20.776,14 20.5,14L18.5,14C18.224,14 18,13.776 18,13.5L18,11.5C18,11.224 18.224,11 18.5,11Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="evenOdd" android:fillType="evenOdd"
android:strokeColor="#00000000"/> android:pathData="M0 0h24v24H0z" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M 5 11 C 5.55228474983 11 6 11.4477152502 6 12 C 6 12.5522847498 5.55228474983 13 5 13 C 4.44771525017 13 4 12.5522847498 4 12 C 4 11.4477152502 4.44771525017 11 5 11 Z" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M 12 11 C 12.5522847498 11 13 11.4477152502 13 12 C 13 12.5522847498 12.5522847498 13 12 13 C 11.4477152502 13 11 12.5522847498 11 12 C 11 11.4477152502 11.4477152502 11 12 11 Z" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M 19 11 C 19.5522847498 11 20 11.4477152502 20 12 C 20 12.5522847498 19.5522847498 13 19 13 C 18.4477152502 13 18 12.5522847498 18 12 C 18 11.4477152502 18.4477152502 11 19 11 Z" />
</vector> </vector>