UI调整,群聊开发
This commit is contained in:
@@ -33,6 +33,7 @@ open class AppThemeData(
|
||||
var tabUnselectedBackground: Color,
|
||||
var tabSelectedText: Color,
|
||||
var tabUnselectedText: Color,
|
||||
var bubbleBackground: Color,
|
||||
)
|
||||
|
||||
class LightThemeColors : AppThemeData(
|
||||
@@ -60,10 +61,10 @@ class LightThemeColors : AppThemeData(
|
||||
chatActionColor = Color(0xffe0e0e0),
|
||||
brandColorsColor = Color(0xffD80264),
|
||||
tabSelectedBackground = Color(0xff110C13),
|
||||
tabUnselectedBackground = Color(0xff7C7480),
|
||||
tabUnselectedBackground = Color(0x147C7480),
|
||||
tabSelectedText = Color(0xffffffff),
|
||||
tabUnselectedText = Color(0xff000000),
|
||||
|
||||
bubbleBackground = Color(0xfff5f5f5)
|
||||
)
|
||||
|
||||
class DarkThemeColors : AppThemeData(
|
||||
@@ -94,4 +95,5 @@ class DarkThemeColors : AppThemeData(
|
||||
tabUnselectedBackground = Color(0xff7C7480),
|
||||
tabSelectedText = Color(0xff000000),
|
||||
tabUnselectedText = Color(0xffffffff),
|
||||
bubbleBackground = Color(0xfff2d2c2e)
|
||||
)
|
||||
@@ -44,19 +44,27 @@ data class SingleChatRequestBody(
|
||||
val generateText: String,
|
||||
)
|
||||
|
||||
data class GroupChatRequestBody(
|
||||
@SerializedName("trtcGroupId")
|
||||
val trtcGroupId: String,
|
||||
)
|
||||
|
||||
|
||||
data class SendChatAiRequestBody(
|
||||
@SerializedName("trtcGroupId")
|
||||
val trtcGroupId: String? = null,
|
||||
@SerializedName("fromTrtcUserId")
|
||||
val fromTrtcUserId: String,
|
||||
val fromTrtcUserId: String? = null,
|
||||
@SerializedName("toTrtcUserId")
|
||||
val toTrtcUserId: String,
|
||||
val toTrtcUserId: String? = null,
|
||||
@SerializedName("message")
|
||||
val message: String,
|
||||
@SerializedName("skipTrtc")
|
||||
val skipTrtc: Boolean,
|
||||
val skipTrtc: Boolean? = true,
|
||||
|
||||
)
|
||||
|
||||
|
||||
data class CreateGroupChatRequestBody(
|
||||
@SerializedName("name")
|
||||
val name: String,
|
||||
@@ -420,6 +428,8 @@ interface RaveNowAPI {
|
||||
@Query("nickname") search: String? = null,
|
||||
@Query("followerId") followerId: Int? = null,
|
||||
@Query("followingId") followingId: Int? = null,
|
||||
@Query("includeAI") includeAI: Boolean? = false,
|
||||
@Query("chatSessionIdNotNull") chatSessionIdNotNull: Boolean? = true,
|
||||
): Response<ListContainer<AccountProfile>>
|
||||
|
||||
@POST("register/google")
|
||||
@@ -533,6 +543,9 @@ interface RaveNowAPI {
|
||||
@POST("generate/postText")
|
||||
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")
|
||||
suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ sealed class NavigationRoute(
|
||||
data object FavouriteList : NavigationRoute("FavouriteList")
|
||||
data object Chat : NavigationRoute("Chat/{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 ImageCrop : NavigationRoute("ImageCrop")
|
||||
data object AccountSetting : NavigationRoute("AccountSetting")
|
||||
@@ -389,15 +389,18 @@ fun NavigationController(
|
||||
|
||||
composable(
|
||||
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 decodedId = encodedId?.let { java.net.URLDecoder.decode(it, "UTF-8") }
|
||||
|
||||
val name = it.arguments?.getString("name")
|
||||
val avatar = it.arguments?.getString("avatar")
|
||||
CompositionLocalProvider(
|
||||
LocalAnimatedContentScope provides this,
|
||||
) {
|
||||
GroupChatScreen(decodedId?:"")
|
||||
GroupChatScreen(decodedId?:"",name?:"",avatar?:"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,24 +435,12 @@ fun NavigationController(
|
||||
}
|
||||
composable(
|
||||
route = NavigationRoute.AddAgent.route,
|
||||
enterTransition = {
|
||||
fadeIn(animationSpec = tween(durationMillis = 0))
|
||||
},
|
||||
exitTransition = {
|
||||
fadeOut(animationSpec = tween(durationMillis = 0))
|
||||
}
|
||||
) {
|
||||
AddAgentScreen()
|
||||
}
|
||||
|
||||
composable(
|
||||
route = NavigationRoute.CreateGroupChat.route,
|
||||
enterTransition = {
|
||||
fadeIn(animationSpec = tween(durationMillis = 0))
|
||||
},
|
||||
exitTransition = {
|
||||
fadeOut(animationSpec = tween(durationMillis = 0))
|
||||
}
|
||||
) {
|
||||
CreateGroupChatScreen()
|
||||
}
|
||||
@@ -500,6 +491,7 @@ fun NavHostController.navigateToChat(id: String) {
|
||||
navigate(
|
||||
route = NavigationRoute.Chat.route
|
||||
.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 encodedName = java.net.URLEncoder.encode(name, "UTF-8")
|
||||
val encodedAvator = java.net.URLEncoder.encode(avatar, "UTF-8")
|
||||
navigate(
|
||||
route = NavigationRoute.ChatGroup.route
|
||||
.replace("{id}", encodedId)
|
||||
.replace("{name}", encodedName)
|
||||
.replace("{avatar}", encodedAvator)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ fun AddAgentScreen() {
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
fun onNameChange(value: String) {
|
||||
model.name = value
|
||||
model.name = value.trim()
|
||||
agnetNameError = when {
|
||||
else -> null
|
||||
}
|
||||
@@ -73,7 +73,7 @@ fun AddAgentScreen() {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
fun onDescChange(value: String) {
|
||||
model.desc = value
|
||||
model.desc = value.trim()
|
||||
agnetDescError = when {
|
||||
value.length > 100 -> "简介长度不能大于100"
|
||||
else -> null
|
||||
@@ -232,6 +232,7 @@ fun AddAgentScreen() {
|
||||
// 创建成功,关闭页面
|
||||
model.name = ""
|
||||
model.desc = ""
|
||||
model.isFromAddAgent = false // 重置标志
|
||||
navController.popBackStack()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -58,6 +58,10 @@ object AddAgentViewModel : ViewModel() {
|
||||
)
|
||||
|
||||
println("AddAgentViewModel: Agent created successfully with ID: ${result.id}")
|
||||
|
||||
// 通知相关ViewModel更新列表
|
||||
notifyAgentCreated(result)
|
||||
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
println("AddAgentViewModel: Error creating agent: ${e.message}")
|
||||
@@ -66,6 +70,11 @@ object AddAgentViewModel : ViewModel() {
|
||||
isUpdating = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyAgentCreated(agent: AgentEntity) {
|
||||
// 通知我的智能体列表更新
|
||||
com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.addAgentToList(agent)
|
||||
}
|
||||
|
||||
fun validate(): String? {
|
||||
return when {
|
||||
|
||||
@@ -83,6 +83,7 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@Composable
|
||||
@@ -280,7 +281,7 @@ fun ChatAiScreen(userId: String) {
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
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]
|
||||
if (item.showTimeDivider) {
|
||||
val calendar = java.util.Calendar.getInstance()
|
||||
|
||||
@@ -68,11 +68,22 @@ class ChatAiViewModel(
|
||||
textMessageListener = object : V2TIMAdvancedMsgListener() {
|
||||
override fun onRecvNewMessage(msg: V2TIMMessage?) {
|
||||
super.onRecvNewMessage(msg)
|
||||
msg?.let {
|
||||
val chatItem = ChatItem.convertToChatItem(msg, context, avatar = userProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
msg?.let { message ->
|
||||
// 只处理当前聊天对象的消息
|
||||
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 {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
android.util.Log.i("ChatAiViewModel", "收到来自 ${message.sender} 的消息,更新AI聊天列表")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +271,7 @@ class ChatAiViewModel(
|
||||
message: String,
|
||||
) {
|
||||
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))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@Composable
|
||||
@@ -280,7 +281,7 @@ fun ChatScreen(userId: String) {
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
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]
|
||||
if (item.showTimeDivider) {
|
||||
val calendar = java.util.Calendar.getInstance()
|
||||
|
||||
@@ -65,11 +65,22 @@ class ChatViewModel(
|
||||
textMessageListener = object : V2TIMAdvancedMsgListener() {
|
||||
override fun onRecvNewMessage(msg: V2TIMMessage?) {
|
||||
super.onRecvNewMessage(msg)
|
||||
msg?.let {
|
||||
val chatItem = ChatItem.convertToChatItem(msg, context, avatar = userProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
msg?.let { message ->
|
||||
// 只处理当前聊天对象的消息
|
||||
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 {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
android.util.Log.i("ChatViewModel", "收到来自 ${message.sender} 的消息,更新聊天列表")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
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.sp
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -83,9 +84,10 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
@Composable
|
||||
fun GroupChatScreen(groupId: String) {
|
||||
fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalNavController.current.context
|
||||
@@ -96,7 +98,7 @@ fun GroupChatScreen(groupId: String) {
|
||||
key = "GroupChatViewModel_$groupId",
|
||||
factory = object : ViewModelProvider.Factory {
|
||||
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(
|
||||
painter = painterResource(R.drawable.rider_pro_back_icon),
|
||||
modifier = Modifier
|
||||
.size(28.dp)
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
navController.navigateUp()
|
||||
},
|
||||
@@ -180,7 +182,7 @@ fun GroupChatScreen(groupId: String) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = viewModel.groupAvatar,
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.size(32.dp)
|
||||
.clip(RoundedCornerShape(8.dp)),
|
||||
contentDescription = "群聊头像"
|
||||
)
|
||||
@@ -193,12 +195,16 @@ fun GroupChatScreen(groupId: String) {
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = viewModel.groupName.take(1),
|
||||
text = viewModel.groupName,
|
||||
style = TextStyle(
|
||||
color = AppColors.text,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
|
||||
)
|
||||
fontSize = 18.sp,
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
Text(
|
||||
text = "${viewModel.memberCount}人",
|
||||
style = TextStyle(
|
||||
color = AppColors.secondaryText,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
@@ -304,7 +304,7 @@ fun GroupChatScreen(groupId: String) {
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
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]
|
||||
if (item.showTimeDivider) {
|
||||
val calendar = java.util.Calendar.getInstance()
|
||||
@@ -369,7 +369,7 @@ fun GroupChatSelfItem(item: ChatItem) {
|
||||
Column(
|
||||
horizontalAlignment = androidx.compose.ui.Alignment.End,
|
||||
) {
|
||||
Text(
|
||||
/* Text(
|
||||
text = item.nickname,
|
||||
style = TextStyle(
|
||||
color = Color.Gray,
|
||||
@@ -377,15 +377,15 @@ fun GroupChatSelfItem(item: ChatItem) {
|
||||
),
|
||||
modifier = Modifier.padding(bottom = 2.dp)
|
||||
)
|
||||
|
||||
*/
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
min = 20.dp,
|
||||
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
|
||||
)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color(0xFF000000))
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.background(Color(0xFF6246FF))
|
||||
.padding(
|
||||
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)
|
||||
@@ -398,7 +398,7 @@ fun GroupChatSelfItem(item: ChatItem) {
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
color = Color.White,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 14.sp,
|
||||
),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
@@ -417,25 +417,25 @@ fun GroupChatSelfItem(item: ChatItem) {
|
||||
text = "不支持的消息类型",
|
||||
style = TextStyle(
|
||||
color = Color.White,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
/*Spacer(modifier = Modifier.width(12.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(40.dp))
|
||||
.size(24.dp)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.avatar,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentDescription = "avatar"
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,11 +455,11 @@ fun GroupChatOtherItem(item: ChatItem) {
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(40.dp))
|
||||
.size(24.dp)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.avatar,
|
||||
imageUrl = item.avatar.replace("storage/avatars/", "/avatar/"),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentDescription = "avatar"
|
||||
)
|
||||
@@ -481,8 +481,8 @@ fun GroupChatOtherItem(item: ChatItem) {
|
||||
min = 20.dp,
|
||||
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
|
||||
)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(AppColors.background)
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.background(AppColors.bubbleBackground)
|
||||
.padding(
|
||||
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)
|
||||
@@ -495,7 +495,7 @@ fun GroupChatOtherItem(item: ChatItem) {
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
color = AppColors.text,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 14.sp,
|
||||
),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
@@ -528,10 +528,47 @@ fun GroupChatOtherItem(item: ChatItem) {
|
||||
@Composable
|
||||
fun GroupChatItem(item: ChatItem, currentUserId: String) {
|
||||
val isCurrentUser = item.userId == currentUserId
|
||||
if (isCurrentUser) {
|
||||
GroupChatSelfItem(item)
|
||||
} else {
|
||||
GroupChatOtherItem(item)
|
||||
|
||||
// 管理员消息显示特殊布局
|
||||
if (item.userId == "administrator") {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ import com.aiosman.ravenow.data.AccountService
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
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.ChatItem
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
@@ -31,6 +35,8 @@ import java.io.InputStream
|
||||
|
||||
class GroupChatViewModel(
|
||||
val groupId: String,
|
||||
val name: String,
|
||||
val avatar: String,
|
||||
) : ViewModel() {
|
||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
||||
var groupInfo by mutableStateOf<GroupInfo?>(null)
|
||||
@@ -77,8 +83,8 @@ class GroupChatViewModel(
|
||||
// 简化群组信息获取,使用默认信息
|
||||
groupInfo = GroupInfo(
|
||||
groupId = groupId,
|
||||
groupName = "群聊 $groupId",
|
||||
groupAvatar = "",
|
||||
groupName = name,
|
||||
groupAvatar = avatar,
|
||||
memberCount = 0,
|
||||
ownerId = ""
|
||||
)
|
||||
@@ -156,8 +162,8 @@ class GroupChatViewModel(
|
||||
val v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(message)
|
||||
V2TIMManager.getMessageManager().sendMessage(
|
||||
v2TIMMessage,
|
||||
groupId,
|
||||
null,
|
||||
groupId,
|
||||
V2TIMMessage.V2TIM_PRIORITY_NORMAL,
|
||||
false,
|
||||
null,
|
||||
@@ -167,6 +173,7 @@ class GroupChatViewModel(
|
||||
Log.e("GroupChatViewModel", "发送群聊消息失败: $p1")
|
||||
}
|
||||
override fun onSuccess(p0: V2TIMMessage?) {
|
||||
sendChatAiMessage(message = message, trtcGroupId = groupId)
|
||||
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
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) {
|
||||
val tempFile = createTempFile(context, imageUri)
|
||||
val imagePath = tempFile?.path
|
||||
|
||||
@@ -59,15 +59,20 @@ fun AgentCard(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
agentEntity.avatar,
|
||||
contentDescription = "",
|
||||
// 使用remember基于agentEntity.id来缓存图片,避免滑动时重复加载
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(40.dp)),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
.clip(RoundedCornerShape(40.dp))
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
LocalContext.current,
|
||||
agentEntity.avatar,
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(40.dp),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
|
||||
@@ -40,42 +40,6 @@ object CreateGroupChatViewModel : ViewModel() {
|
||||
var isLoading by mutableStateOf(false)
|
||||
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(
|
||||
groupName: String,
|
||||
|
||||
@@ -269,79 +269,68 @@ fun IndexScreen() {
|
||||
targetValue = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
||||
animationSpec = tween(durationMillis = 250), label = ""
|
||||
)
|
||||
|
||||
NavigationBarItem(
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
if (it.route === NavigationItem.Add.route) {
|
||||
NewPostViewModel.asNewPost()
|
||||
navController.navigate(NavigationRoute.NewPost.route)
|
||||
return@NavigationBarItem
|
||||
}
|
||||
coroutineScope.launch {
|
||||
pagerState.scrollToPage(idx)
|
||||
}
|
||||
model.tabIndex = idx
|
||||
},
|
||||
colors = NavigationBarItemColors(
|
||||
selectedIconColor = Color.Transparent,
|
||||
selectedTextColor = Color.Transparent,
|
||||
selectedIndicatorColor = Color.Transparent,
|
||||
unselectedIconColor = Color.Transparent,
|
||||
unselectedTextColor = Color.Transparent,
|
||||
disabledIconColor = Color.Transparent,
|
||||
disabledTextColor = Color.Transparent
|
||||
),
|
||||
icon = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
if (it.route == NavigationItem.Add.route) {
|
||||
// Add按钮:只显示大图标
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(top = 2.dp)
|
||||
.noRippleClickable {
|
||||
if (it.route === NavigationItem.Add.route) {
|
||||
NewPostViewModel.asNewPost()
|
||||
navController.navigate(NavigationRoute.NewPost.route)
|
||||
return@noRippleClickable
|
||||
}
|
||||
coroutineScope.launch {
|
||||
pagerState.scrollToPage(idx)
|
||||
}
|
||||
model.tabIndex = idx
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
if (it.route == NavigationItem.Add.route) {
|
||||
// Add按钮:只显示大图标
|
||||
Image(
|
||||
painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()),
|
||||
contentDescription = it.label(),
|
||||
modifier = Modifier.size(32.dp),
|
||||
colorFilter = if (!isSelected) ColorFilter.tint(AppColors.text) else null
|
||||
)
|
||||
} else {
|
||||
// 其他按钮:图标+文字
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(48.dp)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = if (isSelected) AppColors.brandColorsColor.copy(alpha = 0.15f) else Color.Transparent,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()),
|
||||
contentDescription = it.label(),
|
||||
modifier = Modifier.size(32.dp),
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = if (!isSelected) ColorFilter.tint(AppColors.text) else null
|
||||
)
|
||||
} else {
|
||||
// 其他按钮:图标+文字
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(48.dp)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = if (isSelected) AppColors.brandColorsColor.copy(alpha = 0.15f) else Color.Transparent,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()),
|
||||
contentDescription = it.label(),
|
||||
modifier = Modifier.size(24.dp),
|
||||
colorFilter = if (!isSelected) ColorFilter.tint(AppColors.text) else null
|
||||
)
|
||||
}
|
||||
|
||||
// 文字标签,可控制间距
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
|
||||
Text(
|
||||
text = it.label(),
|
||||
fontSize = 10.sp,
|
||||
color = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
||||
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
||||
)
|
||||
}
|
||||
|
||||
// 文字标签,可控制间距
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
|
||||
Text(
|
||||
text = it.label(),
|
||||
fontSize = 10.sp,
|
||||
color = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
||||
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
||||
)
|
||||
}
|
||||
},
|
||||
label = {
|
||||
// 不显示默认标签
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,8 @@ fun Agent() {
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.noRippleClickable {
|
||||
//
|
||||
// 设置标志,表示新增智能体后不需要刷新
|
||||
com.aiosman.ravenow.ui.agent.AddAgentViewModel.isFromAddAgent = true
|
||||
navController.navigate(
|
||||
NavigationRoute.AddAgent.route
|
||||
)
|
||||
@@ -168,7 +169,8 @@ fun Agent() {
|
||||
state = pagerState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.weight(1f),
|
||||
beyondBoundsPageCount = 1 // 预加载相邻页面,避免切换时重新加载
|
||||
) {
|
||||
when (it) {
|
||||
0 -> {
|
||||
|
||||
@@ -25,9 +25,11 @@ open class BaseAgentModel :ViewModel(){
|
||||
var refreshing by mutableStateOf(false)
|
||||
var isFirstLoad = true
|
||||
var agentList by mutableStateOf<List<AgentEntity>>(listOf())
|
||||
|
||||
open fun extraArgs(): AgentLoaderExtraArgs {
|
||||
return AgentLoaderExtraArgs()
|
||||
}
|
||||
|
||||
fun refreshPager(pullRefresh: Boolean = false) {
|
||||
if (!isFirstLoad && !pullRefresh) {
|
||||
return
|
||||
@@ -46,6 +48,10 @@ open class BaseAgentModel :ViewModel(){
|
||||
}
|
||||
}
|
||||
|
||||
// 添加智能体到列表顶部,避免重新加载
|
||||
fun addAgentToList(agent: AgentEntity) {
|
||||
agentList = listOf(agent) + agentList
|
||||
}
|
||||
|
||||
fun ResetModel() {
|
||||
agentLoader.clear()
|
||||
|
||||
@@ -23,6 +23,8 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -60,10 +62,23 @@ fun HotAgent() {
|
||||
}
|
||||
}
|
||||
|
||||
// 只在首次加载时刷新,避免从AddAgent返回时重复刷新
|
||||
LaunchedEffect(Unit) {
|
||||
model.refreshPager()
|
||||
if (model.agentList.isEmpty() && !model.isLoading) {
|
||||
model.refreshPager()
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
// 当智能体列表加载完成后,预加载图片
|
||||
LaunchedEffect(agentList) {
|
||||
if (agentList.isNotEmpty()) {
|
||||
model.preloadImages(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -107,7 +122,7 @@ fun HotAgent() {
|
||||
) {
|
||||
items(
|
||||
agentList.size,
|
||||
key = { idx -> idx }
|
||||
key = { idx -> agentList[idx].id } // 使用智能体ID作为key,避免重新创建
|
||||
) { idx ->
|
||||
val agentItem = agentList[idx]
|
||||
AgentCard(
|
||||
|
||||
@@ -22,10 +22,14 @@ object HotAgentViewModel : ViewModel() {
|
||||
var currentPage by mutableStateOf(1)
|
||||
var error by mutableStateOf<String?>(null)
|
||||
|
||||
// 记录已预加载的图片ID,避免重复加载
|
||||
private val preloadedImageIds = mutableSetOf<Int>()
|
||||
|
||||
private val pageSize = 20
|
||||
|
||||
init {
|
||||
refreshPager()
|
||||
// 延迟初始化,避免在页面切换时立即加载
|
||||
// refreshPager()
|
||||
}
|
||||
|
||||
fun refreshPager(pullRefresh: Boolean = false) {
|
||||
@@ -37,6 +41,11 @@ object HotAgentViewModel : ViewModel() {
|
||||
refreshing = pullRefresh
|
||||
error = null
|
||||
|
||||
// 清除预加载记录,强制重新加载图片
|
||||
if (pullRefresh) {
|
||||
clearPreloadedImages()
|
||||
}
|
||||
|
||||
val response = ApiClient.api.getAgent(
|
||||
page = 1,
|
||||
pageSize = pageSize
|
||||
@@ -45,7 +54,17 @@ object HotAgentViewModel : ViewModel() {
|
||||
val body = response.body()
|
||||
if (body != null) {
|
||||
val newAgents = body.data.list.map { it.toAgentEntity() }
|
||||
agentList = newAgents
|
||||
// 只有在列表为空或者是下拉刷新时才替换整个列表
|
||||
if (agentList.isEmpty() || pullRefresh) {
|
||||
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
|
||||
hasNext = newAgents.size == pageSize
|
||||
} else {
|
||||
@@ -108,4 +127,32 @@ object HotAgentViewModel : ViewModel() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -60,10 +61,23 @@ fun MineAgent() {
|
||||
}
|
||||
}
|
||||
|
||||
// 只在首次加载时刷新,避免从AddAgent返回时重复刷新
|
||||
LaunchedEffect(Unit) {
|
||||
model.refreshPager()
|
||||
if (model.agentList.isEmpty() && !model.isLoading) {
|
||||
model.refreshPager()
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
// 当智能体列表加载完成后,预加载图片
|
||||
LaunchedEffect(agentList) {
|
||||
if (agentList.isNotEmpty()) {
|
||||
model.preloadImages(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -107,7 +121,7 @@ fun MineAgent() {
|
||||
) {
|
||||
items(
|
||||
agentList.size,
|
||||
key = { idx -> idx }
|
||||
key = { idx -> agentList[idx].id } // 使用智能体ID作为key,避免重新创建
|
||||
) { idx ->
|
||||
val agentItem = agentList[idx]
|
||||
AgentCard(
|
||||
|
||||
@@ -25,10 +25,14 @@ object MineAgentViewModel : ViewModel() {
|
||||
var currentPage by mutableStateOf(1)
|
||||
var error by mutableStateOf<String?>(null)
|
||||
|
||||
// 记录已预加载的图片ID,避免重复加载
|
||||
private val preloadedImageIds = mutableSetOf<Int>()
|
||||
|
||||
private val pageSize = 20
|
||||
|
||||
init {
|
||||
refreshPager()
|
||||
// 延迟初始化,避免在页面切换时立即加载
|
||||
// refreshPager()
|
||||
}
|
||||
|
||||
fun refreshPager(pullRefresh: Boolean = false) {
|
||||
@@ -40,6 +44,11 @@ object MineAgentViewModel : ViewModel() {
|
||||
refreshing = pullRefresh
|
||||
error = null
|
||||
|
||||
// 清除预加载记录,强制重新加载图片
|
||||
if (pullRefresh) {
|
||||
clearPreloadedImages()
|
||||
}
|
||||
|
||||
val response = ApiClient.api.getMyAgent(
|
||||
page = 1,
|
||||
pageSize = pageSize
|
||||
@@ -48,7 +57,17 @@ object MineAgentViewModel : ViewModel() {
|
||||
val body = response.body()
|
||||
if (body != null) {
|
||||
val newAgents = body.list.map { it.toAgentEntity() }
|
||||
agentList = newAgents
|
||||
// 只有在列表为空或者是下拉刷新时才替换整个列表
|
||||
if (agentList.isEmpty() || pullRefresh) {
|
||||
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
|
||||
hasNext = newAgents.size == pageSize
|
||||
} else {
|
||||
@@ -138,4 +157,37 @@ object MineAgentViewModel : ViewModel() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ fun NotificationsScreen() {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 15.dp, vertical = 8.dp),
|
||||
.padding(horizontal = 15.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
@@ -197,7 +197,7 @@ fun NotificationsScreen() {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(horizontal = 16.dp),
|
||||
.padding(start = 16.dp,bottom = 16.dp),
|
||||
// center the tabs
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
@@ -251,43 +251,9 @@ fun NotificationsScreen() {
|
||||
2 -> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
@@ -27,6 +28,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -113,13 +115,7 @@ fun AgentChatListScreen() {
|
||||
model.goToChatAi(conv.trtcUserId,navController)
|
||||
}
|
||||
)
|
||||
|
||||
if (index < AgentChatListViewModel.agentChatList.size - 1) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
color = AppColors.divider
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 加载更多指示器
|
||||
@@ -174,16 +170,16 @@ fun AgentChatItem(
|
||||
onChatClick: (AgentConversation) -> Unit = {}
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.noRippleClickable {
|
||||
onChatClick(conversation)
|
||||
}
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 头像
|
||||
Box {
|
||||
CustomAsyncImage(
|
||||
context = LocalContext.current,
|
||||
@@ -191,18 +187,18 @@ fun AgentChatItem(
|
||||
contentDescription = conversation.nickname,
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clip(RoundedCornerShape(48.dp))
|
||||
.noRippleClickable {
|
||||
onUserAvatarClick(conversation)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 聊天信息
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 12.dp)
|
||||
.padding(start = 12.dp, top = 2.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -210,53 +206,52 @@ fun AgentChatItem(
|
||||
) {
|
||||
Text(
|
||||
text = conversation.nickname,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = AppColors.text,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
|
||||
Text(
|
||||
text = conversation.lastMessageTime,
|
||||
fontSize = 12.sp,
|
||||
fontSize = 11.sp,
|
||||
color = AppColors.secondaryText
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "${if (conversation.isSelf) stringResource(R.string.agent_chat_me_prefix) else ""}${conversation.displayText}",
|
||||
fontSize = 14.sp,
|
||||
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 未读消息数量
|
||||
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
if (conversation.unreadCount > 0) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
|
||||
.background(
|
||||
color = AppColors.main,
|
||||
color = Color(0xFFFF3B30),
|
||||
shape = CircleShape
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
|
||||
color = AppColors.mainText,
|
||||
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp,
|
||||
color = Color.White,
|
||||
fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
@@ -17,6 +18,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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()) {
|
||||
@@ -158,10 +154,11 @@ fun FriendChatItem(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.noRippleClickable {
|
||||
onChatClick(conversation)
|
||||
}
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box {
|
||||
CustomAsyncImage(
|
||||
@@ -170,17 +167,18 @@ fun FriendChatItem(
|
||||
contentDescription = conversation.nickname,
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clip(RoundedCornerShape(48.dp))
|
||||
.noRippleClickable {
|
||||
onUserAvatarClick(conversation)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 12.dp)
|
||||
.padding(start = 12.dp, top = 2.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -188,52 +186,52 @@ fun FriendChatItem(
|
||||
) {
|
||||
Text(
|
||||
text = conversation.nickname,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = AppColors.text,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
|
||||
Text(
|
||||
text = conversation.lastMessageTime,
|
||||
fontSize = 12.sp,
|
||||
fontSize = 11.sp,
|
||||
color = AppColors.secondaryText
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
if (conversation.unreadCount > 0) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
|
||||
.background(
|
||||
color = AppColors.main,
|
||||
color = Color(0xFFFF3B30),
|
||||
shape = CircleShape
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
|
||||
color = AppColors.mainText,
|
||||
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp,
|
||||
color = Color.White,
|
||||
fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
@@ -13,10 +14,12 @@ import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -45,7 +48,17 @@ fun GroupChatListScreen() {
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
GroupChatListViewModel.refreshPager(context = context)
|
||||
// 初始化消息监听器
|
||||
GroupChatListViewModel.initMessageListener(context)
|
||||
}
|
||||
|
||||
// 在组件销毁时清理监听器
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
GroupChatListViewModel.removeMessageListener()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -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()) {
|
||||
@@ -157,10 +164,11 @@ fun GroupChatItem(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.noRippleClickable {
|
||||
onChatClick(conversation)
|
||||
}
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box {
|
||||
CustomAsyncImage(
|
||||
@@ -169,7 +177,7 @@ fun GroupChatItem(
|
||||
contentDescription = conversation.groupName,
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.noRippleClickable {
|
||||
onGroupAvatarClick(conversation)
|
||||
}
|
||||
@@ -179,7 +187,8 @@ fun GroupChatItem(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 12.dp)
|
||||
.padding(start = 12.dp, top = 2.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -187,22 +196,22 @@ fun GroupChatItem(
|
||||
) {
|
||||
Text(
|
||||
text = conversation.groupName,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = AppColors.text,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
|
||||
Text(
|
||||
text = conversation.lastMessageTime,
|
||||
fontSize = 12.sp,
|
||||
fontSize = 11.sp,
|
||||
color = AppColors.secondaryText
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -210,30 +219,29 @@ fun GroupChatItem(
|
||||
) {
|
||||
Text(
|
||||
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
if (conversation.unreadCount > 0) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
|
||||
.background(
|
||||
color = AppColors.main,
|
||||
color = Color(0xFFFF3B30),
|
||||
shape = CircleShape
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
|
||||
color = AppColors.mainText,
|
||||
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
color = Color.White,
|
||||
fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavHostController
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
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.ui.NavigationRoute
|
||||
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.V2TIMMessage
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener
|
||||
import com.tencent.imsdk.v2.V2TIMConversationListener
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -70,7 +75,16 @@ data class GroupConversation(
|
||||
groupName = msg.showName,
|
||||
lastMessage = msg.lastMessage?.textElem?.text ?: "",
|
||||
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,
|
||||
displayText = displayText,
|
||||
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId,
|
||||
@@ -90,6 +104,10 @@ object GroupChatListViewModel : ViewModel() {
|
||||
var error by mutableStateOf<String?>(null)
|
||||
|
||||
private val pageSize = 20
|
||||
|
||||
// 消息监听器
|
||||
private var messageListener: V2TIMAdvancedMsgListener? = null
|
||||
private var conversationListener: V2TIMConversationListener? = null
|
||||
|
||||
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {
|
||||
if (isLoading && !pullRefresh) return
|
||||
@@ -162,14 +180,24 @@ object GroupChatListViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun createGroupChat(
|
||||
trtcGroupId: String,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun goToChat(
|
||||
conversation: GroupConversation,
|
||||
navController: NavHostController
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
createGroupChat(trtcGroupId = conversation.groupId)
|
||||
// 群聊直接使用群ID进行导航
|
||||
navController.navigateToGroupChat(conversation.groupId)
|
||||
navController.navigateToGroupChat(conversation.groupId, conversation.groupName, conversation.avatar)
|
||||
} catch (e: Exception) {
|
||||
error = ""
|
||||
e.printStackTrace()
|
||||
@@ -184,7 +212,7 @@ object GroupChatListViewModel : ViewModel() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// 可以导航到群详情页面,这里暂时使用群聊页面
|
||||
navController.navigateToChat(conversation.groupId)
|
||||
//
|
||||
} catch (e: Exception) {
|
||||
error = ""
|
||||
e.printStackTrace()
|
||||
@@ -197,4 +225,77 @@ object GroupChatListViewModel : ViewModel() {
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.aiosman.ravenow.AppStore
|
||||
@@ -232,9 +233,9 @@ fun Explore() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AgentPage(agentItems: List<AgentItem>, page: Int) {
|
||||
fun AgentPage(agentItems: List<AgentItem>, page: Int, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 0.dp)
|
||||
) {
|
||||
@@ -268,11 +269,27 @@ fun Explore() {
|
||||
) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp),
|
||||
pageSpacing = 0.dp
|
||||
) { page ->
|
||||
// 计算当前页面的偏移量
|
||||
val pageOffset = (
|
||||
(pagerState.currentPage - page) + pagerState
|
||||
.currentPageOffsetFraction
|
||||
).coerceIn(-1f, 1f)
|
||||
|
||||
// 根据偏移量计算缩放比例
|
||||
val scale = 1f - (0.1f * kotlin.math.abs(pageOffset))
|
||||
|
||||
AgentPage(
|
||||
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
|
||||
fun BannerCard(bannerItem: BannerItem) {
|
||||
fun BannerCard(bannerItem: BannerItem, modifier: Modifier = Modifier) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val context = LocalContext.current
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 0.dp),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
@@ -595,15 +612,15 @@ fun Explore() {
|
||||
) {
|
||||
|
||||
|
||||
// 可以添加更多不同高度的内容项
|
||||
// 第一块区域
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 6.dp),
|
||||
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceEvenly
|
||||
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween
|
||||
) {
|
||||
// 第一个
|
||||
// 第一个 - 靠左显示
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
@@ -633,69 +650,75 @@ fun Explore() {
|
||||
)
|
||||
}
|
||||
|
||||
// 第二个
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
navController.navigate(
|
||||
NavigationRoute.AddAgent.route)
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
// 中间两个 - 平均分布
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceEvenly
|
||||
) {
|
||||
Box(
|
||||
// 第二个
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(Color(0xFF94f9f2), RoundedCornerShape(24.dp)),
|
||||
contentAlignment = Alignment.Center
|
||||
.clickable {
|
||||
navController.navigate(
|
||||
NavigationRoute.AddAgent.route)
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent),
|
||||
contentDescription = "创建智能体",
|
||||
modifier = Modifier.size(24.dp),
|
||||
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Text(
|
||||
text = "创建Agent",
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)
|
||||
}
|
||||
|
||||
// 第三个
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
NewPostViewModel.asNewPost()
|
||||
navController.navigate("NewPost")
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(Color(0xFFfafd5d), RoundedCornerShape(24.dp)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_release),
|
||||
contentDescription = "发布动态",
|
||||
modifier = Modifier.size(24.dp),
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(Color(0xFF94f9f2), RoundedCornerShape(24.dp)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent),
|
||||
contentDescription = "创建智能体",
|
||||
modifier = Modifier.size(24.dp),
|
||||
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Text(
|
||||
text = "创建Agent",
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)
|
||||
}
|
||||
|
||||
// 第三个
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
NewPostViewModel.asNewPost()
|
||||
navController.navigate("NewPost")
|
||||
},
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(Color(0xFFfafd5d), RoundedCornerShape(24.dp)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_release),
|
||||
contentDescription = "发布动态",
|
||||
modifier = Modifier.size(24.dp),
|
||||
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Text(
|
||||
text = "发布动态",
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Text(
|
||||
text = "发布动态",
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)
|
||||
}
|
||||
|
||||
// 第四个
|
||||
// 第四个 - 靠右显示
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
@@ -719,9 +742,9 @@ fun Explore() {
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -767,10 +790,28 @@ fun BannerSection(bannerItems: List<BannerItem>) {
|
||||
) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp),
|
||||
) { 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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user