初步替换IM接口

This commit is contained in:
2025-09-08 15:13:17 +08:00
parent 1d632fb757
commit 1a41cb7aef
20 changed files with 846 additions and 773 deletions

View File

@@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-08-11T15:57:44.196893Z"> <DropdownSelection timestamp="2025-09-08T06:52:32.669239Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/kevinlinpr/.android/avd/Pixel_8_Pro_API_34.avd" /> <DeviceId pluginId="LocalEmulator" identifier="path=/Users/liudikang/.android/avd/Pixel_8_API_30.avd" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

176
MIGRATION_GUIDE.md Normal file
View File

@@ -0,0 +1,176 @@
# 腾讯云 IM SDK 到 OpenIM 迁移指南
## 迁移状态
### ✅ 已完成的工作
1. **依赖项迁移**
-`build.gradle.kts` 中移除了腾讯云 IM SDK 依赖 (`libs.imsdk.plus`)
-`gradle/libs.versions.toml` 中移除了相关版本定义
- 保留了 OpenIM SDK 依赖 (`io.openim:android-sdk`, `io.openim:core-sdk`)
2. **核心组件迁移**
-`TrtcHelper.kt` - 完全迁移到 OpenIM API
-`Chat.kt` 实体类 - 更新为 OpenIM 消息模型
-`AgentChatListViewModel.kt` - 部分迁移到 OpenIM API
-`OpenIMManager.kt` - 完整的 OpenIM 管理器
-`OpenIMService.kt` - OpenIM 后台服务
-`AppState.kt` - 已使用 OpenIM 进行初始化和登录
3. **兼容层创建**
- 创建了 `TencentIMCompat.kt` 兼容层,避免编译错误
- 所有使用腾讯云 IM 的文件都已添加兼容层导入
4. **配置清理**
- AndroidManifest.xml 已经是干净的,无需额外清理
### 🔄 需要进一步完成的工作
#### 1. 完整的 ViewModel 迁移
以下文件仍在使用兼容层,需要完全迁移到 OpenIM
- `ChatViewModel.kt` - 聊天功能核心
- `ChatAiViewModel.kt` - AI 聊天功能
- `GroupChatViewModel.kt` - 群聊功能
- `FriendChatListViewModel.kt` - 好友聊天列表
- `GroupChatListViewModel.kt` - 群聊列表
- `MessageListViewModel.kt` - 消息列表
- `MineAgentViewModel.kt` - 我的智能体
- `CreateGroupChatViewModel.kt` - 创建群聊
#### 2. UI 组件迁移
以下 Screen 文件需要更新以使用新的数据模型:
- `ChatScreen.kt`
- `ChatAiScreen.kt`
- `GroupChatScreen.kt`
#### 3. 消息类型映射
需要完善 OpenIM 消息类型到应用内部类型的映射:
```kotlin
// OpenIM 消息类型
101 -> TEXT
102 -> IMAGE
103 -> AUDIO
104 -> VIDEO
105 -> FILE
```
## OpenIM 集成状态
### ✅ 已集成的功能
1. **SDK 初始化** - `OpenIMManager.initSDK()`
2. **用户登录** - `AppState.loginToOpenIM()`
3. **连接监听** - 连接状态、踢下线、token 过期
4. **消息监听** - 新消息、消息撤回、已读回执等
5. **会话管理** - 会话变化、未读数统计
6. **用户信息管理** - 用户资料更新
7. **好友关系管理** - 好友申请、添加、删除等
8. **群组管理** - 群信息变更、成员管理等
### 🔧 需要实现的功能
1. **消息发送**
```kotlin
// 需要实现
OpenIMClient.getInstance().messageManager.sendMessage(...)
```
2. **历史消息获取**
```kotlin
// 需要实现
OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(...)
```
3. **会话列表获取**
```kotlin
// 已在 AgentChatListViewModel 中部分实现
OpenIMClient.getInstance().conversationManager.getAllConversationList(...)
```
4. **图片消息处理**
- 需要适配 OpenIM 的 `PictureElem` 结构
- 更新图片显示逻辑
## 迁移步骤建议
### 第一阶段:核心聊天功能
1. 完成 `ChatViewModel.kt` 的完整迁移
2. 实现消息发送和接收
3. 实现历史消息加载
4. 测试基本聊天功能
### 第二阶段:会话管理
1. 完成各种 ChatListViewModel 的迁移
2. 实现会话列表的正确显示
3. 实现未读消息统计
### 第三阶段:高级功能
1. 群聊功能迁移
2. 文件、图片等多媒体消息
3. 消息状态和已读回执
### 第四阶段:清理和优化
1. 删除兼容层 `TencentIMCompat.kt`
2. 清理所有临时代码
3. 性能优化和测试
## 关键 API 对比
### 消息发送
```kotlin
// 腾讯云 IM
V2TIMManager.getMessageManager().sendMessage(message, receiver, null, ...)
// OpenIM
OpenIMClient.getInstance().messageManager.sendMessage(message, receiver, ...)
```
### 获取会话列表
```kotlin
// 腾讯云 IM
V2TIMManager.getConversationManager().getConversationList(...)
// OpenIM
OpenIMClient.getInstance().conversationManager.getAllConversationList(...)
```
### 消息监听
```kotlin
// 腾讯云 IM
V2TIMManager.getMessageManager().addAdvancedMsgListener(listener)
// OpenIM
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(listener)
```
## 注意事项
1. **数据结构差异**OpenIM 和腾讯云 IM 的数据结构有所不同,需要仔细映射
2. **回调机制**OpenIM 使用不同的回调接口
3. **消息 ID**OpenIM 使用 `clientMsgID`,腾讯云使用 `msgID`
4. **时间戳**:注意时间戳的单位和格式差异
5. **用户 ID**:确保用户 ID 在两个系统中的一致性
## 测试建议
1. **单元测试**:为每个迁移的组件编写测试
2. **集成测试**:测试完整的聊天流程
3. **兼容性测试**:确保与现有数据的兼容性
4. **性能测试**:对比迁移前后的性能表现
## 删除兼容层的时机
当以下条件都满足时,可以安全删除 `TencentIMCompat.kt`
1. 所有 ViewModel 都已完全迁移到 OpenIM
2. 所有功能都已测试通过
3. 没有编译错误
4. 应用运行正常
删除步骤:
1. 删除 `app/src/main/java/com/aiosman/ravenow/compat/TencentIMCompat.kt`
2. 从所有文件中移除 `import com.aiosman.ravenow.compat.*`
3. 清理所有相关的临时注释

View File

@@ -113,7 +113,6 @@ dependencies {
implementation(libs.firebase.messaging.ktx) implementation(libs.firebase.messaging.ktx)
implementation (libs.jpush.google) implementation (libs.jpush.google)
api (libs.imsdk.plus)
implementation (libs.im.sdk) implementation (libs.im.sdk)
implementation (libs.im.core.sdk) implementation (libs.im.core.sdk)
implementation (libs.gson) implementation (libs.gson)

View File

@@ -79,9 +79,6 @@
<action android:name="cn.jiguang.user.service.action" /> <action android:name="cn.jiguang.user.service.action" />
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".TrtcService"
android:exported="false" />
<service <service
android:name=".OpenIMService" android:name=".OpenIMService"
android:exported="false" /> android:exported="false" />

View File

@@ -1,131 +0,0 @@
package com.aiosman.ravenow
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.aiosman.ravenow.entity.ChatItem
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TrtcService : Service() {
private var trtcMessageListener: V2TIMAdvancedMsgListener? = null
private val channelId = "chat_notification"
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("TrtcService", "onStartCommand")
createNotificationChannel()
CoroutineScope(Dispatchers.IO).launch {
registerMessageListener(applicationContext)
}
return START_STICKY;
}
override fun onDestroy() {
super.onDestroy()
Log.d("TrtcService", "onDestroy")
CoroutineScope(Dispatchers.IO).launch {
unRegisterMessageListener()
}
}
private fun registerMessageListener(context: Context) {
val scope = CoroutineScope(Dispatchers.IO)
trtcMessageListener = object : V2TIMAdvancedMsgListener() {
override fun onRecvNewMessage(msg: V2TIMMessage?) {
super.onRecvNewMessage(msg)
msg?.let {
//MessageListViewModel.refreshConversation(context, it.sender)
if (MainActivityLifecycle.isForeground) {
return
}
scope.launch {
// 先获取通知策略
val notiStrategy = ChatState.getStrategyByTargetTrtcId(it.sender)
if (notiStrategy == null) {
// 未设置策略, 默认通知
sendNotification(context, it)
return@launch
}
if (notiStrategy.strategy != "mute") {
sendNotification(context, it)
}
}
}
}
}
V2TIMManager.getMessageManager().addAdvancedMsgListener(trtcMessageListener)
}
private fun unRegisterMessageListener() {
V2TIMManager.getMessageManager().removeAdvancedMsgListener(trtcMessageListener)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Chat Notification"
val descriptionText = "Notification for chat message"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(channelId, name, importance).apply {
description = descriptionText
}
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun sendNotification(context: Context, message: V2TIMMessage) {
val intent = Intent(context, MainActivity::class.java).apply {
putExtra("ACTION", "TRTC_NEW_MESSAGE")
putExtra("SENDER", message.sender)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val chatItem = ChatItem.convertToChatItem(message, context) ?: return
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.mipmap.rider_pro_log_round)
.setContentTitle(chatItem.nickname)
.setContentText(chatItem.textDisplay)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
with(NotificationManagerCompat.from(context)) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return
}
notify(message.msgID.hashCode(), builder.build())
}
}
}

View File

@@ -5,8 +5,8 @@ import android.icu.util.Calendar
import com.aiosman.ravenow.ConstVars import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.exp.formatChatTime import com.aiosman.ravenow.exp.formatChatTime
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import com.tencent.imsdk.v2.V2TIMImageElem import io.openim.android.sdk.models.Message
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.models.PictureElem
data class ChatItem( data class ChatItem(
val message: String, val message: String,
@@ -16,7 +16,7 @@ data class ChatItem(
val nickname: String, val nickname: String,
val timeCategory: String = "", val timeCategory: String = "",
val timestamp: Long = 0, val timestamp: Long = 0,
val imageList: MutableList<V2TIMImageElem.V2TIMImage> = emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(), val imageList: MutableList<PictureInfo> = emptyList<PictureInfo>().toMutableList(),
val messageType: Int = 0, val messageType: Int = 0,
val textDisplay: String = "", val textDisplay: String = "",
val msgId: String, // Add this property val msgId: String, // Add this property
@@ -24,63 +24,81 @@ data class ChatItem(
var showTimeDivider: Boolean = false var showTimeDivider: Boolean = false
) { ) {
companion object { companion object {
fun convertToChatItem(message: V2TIMMessage, context: Context,avatar: String? = null): ChatItem? { // OpenIM 消息类型常量
val timestamp = message.timestamp const val MESSAGE_TYPE_TEXT = 101
const val MESSAGE_TYPE_IMAGE = 102
const val MESSAGE_TYPE_AUDIO = 103
const val MESSAGE_TYPE_VIDEO = 104
const val MESSAGE_TYPE_FILE = 105
fun convertToChatItem(message: Message, context: Context, avatar: String? = null): ChatItem? {
val timestamp = message.createTime
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
calendar.timeInMillis = timestamp * 1000 calendar.timeInMillis = timestamp
val imageElm = message.imageElem?.imageList
var faceAvatar = avatar var faceAvatar = avatar
if (faceAvatar == null) { if (faceAvatar == null) {
faceAvatar = "${ConstVars.BASE_SERVER}${message.faceUrl}" faceAvatar = "${ConstVars.BASE_SERVER}${message.senderFaceUrl}"
} }
when (message.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> { when (message.contentType) {
val imageElm = message.imageElem?.imageList?.all { MESSAGE_TYPE_IMAGE -> {
it.size == 0 val pictureElem = message.pictureElem
} if (pictureElem != null && !pictureElem.sourcePicture.url.isNullOrEmpty()) {
if (imageElm != true) {
return ChatItem( return ChatItem(
message = "Image", message = "Image",
avatar = faceAvatar, avatar = faceAvatar,
time = calendar.time.formatChatTime(context), time = calendar.time.formatChatTime(context),
userId = message.sender, userId = message.sendID,
nickname = message.nickName, nickname = message.senderNickname ?: "",
timestamp = timestamp * 1000, timestamp = timestamp,
imageList = message.imageElem?.imageList imageList = listOfNotNull(
?: emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(), PictureInfo(
messageType = V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE, url = pictureElem.sourcePicture.url ?: "",
width = pictureElem.sourcePicture.width,
height = pictureElem.sourcePicture.height,
size = pictureElem.sourcePicture.size
)
).toMutableList(),
messageType = MESSAGE_TYPE_IMAGE,
textDisplay = "Image", textDisplay = "Image",
msgId = message.msgID // Add this line to include msgId msgId = message.clientMsgID
) )
} }
return null return null
} }
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { MESSAGE_TYPE_TEXT -> {
return ChatItem( return ChatItem(
message = message.textElem?.text ?: "Unsupported message type", message = message.textElem?.content ?: "Unsupported message type",
avatar = faceAvatar, avatar = faceAvatar,
time = calendar.time.formatChatTime(context), time = calendar.time.formatChatTime(context),
userId = message.sender, userId = message.sendID,
nickname = message.nickName, nickname = message.senderNickname ?: "",
timestamp = timestamp * 1000, timestamp = timestamp,
imageList = imageElm?.toMutableList() imageList = emptyList<PictureInfo>().toMutableList(),
?: emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(), messageType = MESSAGE_TYPE_TEXT,
messageType = V2TIMMessage.V2TIM_ELEM_TYPE_TEXT, textDisplay = message.textElem?.content ?: "Unsupported message type",
textDisplay = message.textElem?.text ?: "Unsupported message type", msgId = message.clientMsgID
msgId = message.msgID // Add this line to include msgId
) )
} }
else -> { else -> {
return null return null
} }
} }
} }
} }
} }
// OpenIM 图片信息数据类
data class PictureInfo(
val url: String,
val width: Int,
val height: Int,
val size: Long
)
data class ChatNotification( data class ChatNotification(
@SerializedName("userId") @SerializedName("userId")

View File

@@ -84,7 +84,7 @@ import com.aiosman.ravenow.ui.composables.MenuItem
import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils import com.aiosman.ravenow.utils.NetworkUtils
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.enums.MessageType
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -379,18 +379,18 @@ fun ChatAiSelfItem(item: ChatItem) {
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 == MessageType.TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp))
.background(Color(0xFF6246FF)) .background(Color(0xFF6246FF))
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
) )
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { MessageType.TEXT -> {
Text( Text(
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
@@ -401,7 +401,7 @@ fun ChatAiSelfItem(item: ChatItem) {
) )
} }
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> { MessageType.PICTURE -> {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.imageList[1].url, imageUrl = item.imageList[1].url,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -467,18 +467,18 @@ fun ChatAiOtherItem(item: ChatItem) {
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 == MessageType.TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(AppColors.bubbleBackground) .background(AppColors.bubbleBackground)
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
) )
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp)) .padding(bottom = (if (item.messageType == MessageType.TEXT) 3.dp else 0.dp))
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { MessageType.TEXT -> {
Text( Text(
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
@@ -489,7 +489,7 @@ fun ChatAiOtherItem(item: ChatItem) {
) )
} }
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> { MessageType.PICTURE -> {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.imageList[1].url, imageUrl = item.imageList[1].url,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),

View File

@@ -23,13 +23,13 @@ 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
import com.aiosman.ravenow.ui.navigateToChatAi import com.aiosman.ravenow.ui.navigateToChatAi
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener // OpenIM SDK 导入
import com.tencent.imsdk.v2.V2TIMCallback import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMConversationOperationResult import io.openim.android.sdk.enums.ViewType
import com.tencent.imsdk.v2.V2TIMManager import io.openim.android.sdk.listener.OnAdvanceMsgListener
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.listener.OnBase
import com.tencent.imsdk.v2.V2TIMSendCallback import io.openim.android.sdk.listener.OnMsgSendCallback
import com.tencent.imsdk.v2.V2TIMValueCallback import io.openim.android.sdk.models.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -44,10 +44,10 @@ class ChatAiViewModel(
var myProfile by mutableStateOf<AccountProfileEntity?>(null) var myProfile by mutableStateOf<AccountProfileEntity?>(null)
val userService: UserService = UserServiceImpl() val userService: UserService = UserServiceImpl()
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var textMessageListener: V2TIMAdvancedMsgListener? = null var textMessageListener: OnAdvanceMsgListener? = null
var hasMore by mutableStateOf(true) var hasMore by mutableStateOf(true)
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
var lastMessage: V2TIMMessage? = null var lastMessage: Message? = null
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
var chatNotification by mutableStateOf<ChatNotification?>(null) var chatNotification by mutableStateOf<ChatNotification?>(null)
var goToNew by mutableStateOf(false) var goToNew by mutableStateOf(false)
@@ -68,9 +68,14 @@ class ChatAiViewModel(
fun RegistListener(context: Context) { fun RegistListener(context: Context) {
textMessageListener = object : V2TIMAdvancedMsgListener() { // 检查 OpenIM 是否已登录
override fun onRecvNewMessage(msg: V2TIMMessage?) { if (!com.aiosman.ravenow.AppState.enableChat) {
super.onRecvNewMessage(msg) android.util.Log.w("ChatAiViewModel", "OpenIM 未登录,跳过注册消息监听器")
return
}
textMessageListener = object : OnAdvanceMsgListener {
override fun onRecvNewMessage(msg: Message?) {
msg?.let { message -> msg?.let { message ->
// 只处理当前聊天对象的消息 // 只处理当前聊天对象的消息
val currentChatUserId = userProfile?.trtcUserId val currentChatUserId = userProfile?.trtcUserId
@@ -78,38 +83,41 @@ class ChatAiViewModel(
if (currentChatUserId != null && currentUserId != null) { if (currentChatUserId != null && currentUserId != null) {
// 检查消息是否来自当前聊天对象,且不是自己发送的消息 // 检查消息是否来自当前聊天对象,且不是自己发送的消息
if ((message.userID == currentChatUserId || message.userID == currentUserId) && if ((message.sendID == currentChatUserId || message.sendID == currentUserId) &&
message.sender != currentUserId) { message.sendID != currentUserId) {
val chatItem = ChatItem.convertToChatItem(message, context, avatar = userProfile?.avatar) 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聊天列表") android.util.Log.i("ChatAiViewModel", "收到来自 ${message.sendID} 的消息更新AI聊天列表")
} }
} }
} }
} }
} }
} }
V2TIMManager.getMessageManager().addAdvancedMsgListener(textMessageListener); OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
} }
fun UnRegistListener() { fun UnRegistListener() {
V2TIMManager.getMessageManager().removeAdvancedMsgListener(textMessageListener); // OpenIM SDK 不需要显式移除监听器,只需要设置为 null
textMessageListener = null
} }
fun clearUnRead() { fun clearUnRead() {
val conversationID = "c2c_${userProfile?.trtcUserId}" val conversationID = "single_${userProfile?.trtcUserId}"
V2TIMManager.getConversationManager() OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
.cleanConversationUnreadMessageCount(conversationID, 0, 0, object : V2TIMCallback { conversationID,
override fun onSuccess() { object : OnBase<String> {
Log.i("imsdk", "success") override fun onSuccess(data: String?) {
Log.i("openim", "clear unread success")
} }
override fun onError(code: Int, desc: String) { override fun onError(code: Int, error: String?) {
Log.i("imsdk", "failure, code:$code, desc:$desc") Log.i("openim", "clear unread failure, code:$code, error:$error")
} }
}) }
)
} }
@@ -119,117 +127,124 @@ class ChatAiViewModel(
} }
isLoading = true isLoading = true
viewModelScope.launch { viewModelScope.launch {
V2TIMManager.getMessageManager().getC2CHistoryMessageList( val conversationID = "single_${userProfile?.trtcUserId!!}"
userProfile?.trtcUserId!!, // val options = OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList() .apply {
20, // conversationID = conversationID
lastMessage, // count = 20
object : V2TIMValueCallback<List<V2TIMMessage>> { // lastMinSeq = lastMessage?.seq ?: 0
override fun onSuccess(p0: List<V2TIMMessage>?) { // }
chatData = chatData + (p0 ?: emptyList()).map { OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
object : OnBase<AdvancedMessage> {
override fun onSuccess(data: AdvancedMessage?) {
val messages = data?.messageList ?: emptyList()
chatData = chatData + messages.map {
var avatar = userProfile?.avatar var avatar = userProfile?.avatar
if (it.isSelf) { if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
avatar = myProfile?.avatar avatar = myProfile?.avatar
} }
ChatItem.convertToChatItem(it, context,avatar) ChatItem.convertToChatItem(it, context, avatar)
}.filterNotNull() }.filterNotNull()
if ((p0?.size ?: 0) < 20) {
if (messages.size < 20) {
hasMore = false hasMore = false
} }
lastMessage = p0?.lastOrNull() lastMessage = messages.lastOrNull()
isLoading = false isLoading = false
Log.d("ChatViewModel", "fetch history message success") Log.d("ChatAiViewModel", "fetch history message success")
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("ChatViewModel", "fetch history message error: $p1") Log.e("ChatAiViewModel", "fetch history message error: $error")
isLoading = false isLoading = false
} }
} },
conversationID,
lastMessage,
20,
ViewType.History
) )
} }
} }
fun sendMessage(message: String, context: Context) { fun sendMessage(message: String, context: Context) {
V2TIMManager.getInstance().sendC2CTextMessage( // 检查 OpenIM 是否已登录
message, if (!com.aiosman.ravenow.AppState.enableChat) {
userProfile?.trtcUserId!!, android.util.Log.w("ChatAiViewModel", "OpenIM 未登录,无法发送消息")
object : V2TIMSendCallback<V2TIMMessage> { return
override fun onProgress(p0: Int) { }
val textMessage = OpenIMClient.getInstance().messageManager.createTextMessage(message)
OpenIMClient.getInstance().messageManager.sendMessage(
object : OnMsgSendCallback {
override fun onProgress(progress: Long) {
// 发送进度
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("ChatViewModel", "send message error: $p1") Log.e("ChatAiViewModel", "send message error: $error")
} }
override fun onSuccess(p0: V2TIMMessage?) { override fun onSuccess(data: Message?) {
Log.d("ChatViewModel", "send message success") Log.d("ChatAiViewModel", "send message success")
sendChatAiMessage(myProfile?.trtcUserId!!,userProfile?.trtcUserId!!, message) sendChatAiMessage(myProfile?.trtcUserId!!, userProfile?.trtcUserId!!, message)
createGroup2ChatAi(userProfile?.trtcUserId!!,"ai_group") createGroup2ChatAi(userProfile?.trtcUserId!!, "ai_group")
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar) data?.let { sentMessage ->
chatItem?.let { val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
chatData = listOf(it) + chatData
goToNew = true
}
}
}
)
}
fun createGroup2ChatAi(
trtcUserId: String,
groupName: String,
) {
val conversationIDList = listOf("c2c_${trtcUserId}")
V2TIMManager.getConversationManager().createConversationGroup(
groupName,
conversationIDList,
object : V2TIMValueCallback<List<V2TIMConversationOperationResult>> {
override fun onSuccess(v2TIMConversationOperationResults: List<V2TIMConversationOperationResult>) {
// 创建会话分组成功
}
override fun onError(code: Int, desc: String) {
// 创建会话分组失败
}
}
)
}
fun sendImageMessage(imageUri: Uri, context: Context) {
val tempFile = createTempFile(context, imageUri)
val imagePath = tempFile?.path
if (imagePath != null) {
val v2TIMMessage = V2TIMManager.getMessageManager().createImageMessage(imagePath)
V2TIMManager.getMessageManager().sendMessage(
v2TIMMessage,
userProfile?.trtcUserId!!,
null,
V2TIMMessage.V2TIM_PRIORITY_NORMAL,
false,
null,
object : V2TIMSendCallback<V2TIMMessage> {
override fun onProgress(p0: Int) {
Log.d("ChatViewModel", "send image message progress: $p0")
}
override fun onError(p0: Int, p1: String?) {
Log.e("ChatViewModel", "send image message error: $p1")
}
override fun onSuccess(p0: V2TIMMessage?) {
Log.d("ChatViewModel", "send image message success")
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
chatItem?.let { chatItem?.let {
chatData = listOf(it) + chatData chatData = listOf(it) + chatData
goToNew = true goToNew = true
} }
} }
} }
},
textMessage,
userProfile?.trtcUserId!!, // recvID
null, // groupID
null // offlinePushInfo
)
}
fun createGroup2ChatAi(
trtcUserId: String,
groupName: String,
) {
// OpenIM 不支持会话分组功能,这里可以留空或者使用其他方式实现
Log.d("ChatAiViewModel", "OpenIM 不支持会话分组功能")
}
fun sendImageMessage(imageUri: Uri, context: Context) {
val tempFile = createTempFile(context, imageUri)
val imagePath = tempFile?.path
if (imagePath != null) {
val imageMessage = OpenIMClient.getInstance().messageManager.createImageMessageFromFullPath(imagePath)
OpenIMClient.getInstance().messageManager.sendMessage(
object : OnMsgSendCallback {
override fun onProgress(progress: Long) {
Log.d("ChatAiViewModel", "send image message progress: $progress")
}
override fun onError(code: Int, error: String?) {
Log.e("ChatAiViewModel", "send image message error: $error")
}
override fun onSuccess(data: Message?) {
Log.d("ChatAiViewModel", "send image message success")
data?.let { sentMessage ->
val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
chatItem?.let {
chatData = listOf(it) + chatData
goToNew = true
}
}
}
},
imageMessage,
userProfile?.trtcUserId!!, // recvID
null, // groupID
null // offlinePushInfo
) )
} }
} }
fun createTempFile(context: Context, uri: Uri): File? { fun createTempFile(context: Context, uri: Uri): File? {
@@ -264,30 +279,35 @@ class ChatAiViewModel(
} }
fun fetchHistoryMessage(context: Context) { fun fetchHistoryMessage(context: Context) {
V2TIMManager.getMessageManager().getC2CHistoryMessageList( val conversationID = "single_${userProfile?.trtcUserId!!}"
userProfile?.trtcUserId!!,
20,
null, OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
object : V2TIMValueCallback<List<V2TIMMessage>> { object : OnBase<AdvancedMessage> {
override fun onSuccess(p0: List<V2TIMMessage>?) { override fun onSuccess(data: AdvancedMessage?) {
chatData = (p0 ?: emptyList()).mapNotNull { val messages = data?.messageList ?: emptyList()
chatData = messages.mapNotNull {
var avatar = userProfile?.avatar var avatar = userProfile?.avatar
if (it.isSelf) { if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
avatar = myProfile?.avatar avatar = myProfile?.avatar
} }
ChatItem.convertToChatItem(it, context,avatar) ChatItem.convertToChatItem(it, context, avatar)
} }
if ((p0?.size ?: 0) < 20) { if (messages.size < 20) {
hasMore = false hasMore = false
} }
lastMessage = p0?.lastOrNull() lastMessage = messages.lastOrNull()
Log.d("ChatViewModel", "fetch history message success") Log.d("ChatAiViewModel", "fetch history message success")
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("ChatViewModel", "fetch history message error: $p1") Log.e("ChatAiViewModel", "fetch history message error: $error")
} }
} },
conversationID,
lastMessage,
20,
ViewType.History
) )
} }
fun sendChatAiMessage( fun sendChatAiMessage(

View File

@@ -84,7 +84,7 @@ import com.aiosman.ravenow.ui.composables.MenuItem
import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils import com.aiosman.ravenow.utils.NetworkUtils
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.enums.MessageType
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -379,18 +379,18 @@ fun ChatSelfItem(item: ChatItem) {
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 == MessageType.TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp))
.background(Color(0xFF6246FF)) .background(Color(0xFF6246FF))
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
) )
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { MessageType.TEXT -> {
Text( Text(
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
@@ -401,7 +401,7 @@ fun ChatSelfItem(item: ChatItem) {
) )
} }
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> { MessageType.PICTURE -> {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.imageList[1].url, imageUrl = item.imageList[1].url,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -467,18 +467,18 @@ fun ChatOtherItem(item: ChatItem) {
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 == MessageType.TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(AppColors.background) .background(AppColors.background)
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
) )
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp)) .padding(bottom = (if (item.messageType == MessageType.TEXT) 3.dp else 0.dp))
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { MessageType.TEXT -> {
Text( Text(
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
@@ -489,7 +489,7 @@ fun ChatOtherItem(item: ChatItem) {
) )
} }
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> { MessageType.PICTURE -> {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.imageList[1].url, imageUrl = item.imageList[1].url,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),

View File

@@ -18,12 +18,14 @@ import com.aiosman.ravenow.data.UserServiceImpl
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
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener // OpenIM SDK 导入
import com.tencent.imsdk.v2.V2TIMCallback import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMManager import io.openim.android.sdk.enums.MessageType
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.enums.ViewType
import com.tencent.imsdk.v2.V2TIMSendCallback import io.openim.android.sdk.listener.OnAdvanceMsgListener
import com.tencent.imsdk.v2.V2TIMValueCallback import io.openim.android.sdk.listener.OnBase
import io.openim.android.sdk.listener.OnMsgSendCallback
import io.openim.android.sdk.models.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -38,10 +40,10 @@ class ChatViewModel(
var myProfile by mutableStateOf<AccountProfileEntity?>(null) var myProfile by mutableStateOf<AccountProfileEntity?>(null)
val userService: UserService = UserServiceImpl() val userService: UserService = UserServiceImpl()
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var textMessageListener: V2TIMAdvancedMsgListener? = null var textMessageListener: OnAdvanceMsgListener? = null
var hasMore by mutableStateOf(true) var hasMore by mutableStateOf(true)
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
var lastMessage: V2TIMMessage? = null var lastMessage: Message? = null
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
var chatNotification by mutableStateOf<ChatNotification?>(null) var chatNotification by mutableStateOf<ChatNotification?>(null)
var goToNew by mutableStateOf(false) var goToNew by mutableStateOf(false)
@@ -62,9 +64,14 @@ class ChatViewModel(
fun RegistListener(context: Context) { fun RegistListener(context: Context) {
textMessageListener = object : V2TIMAdvancedMsgListener() { // 检查 OpenIM 是否已登录
override fun onRecvNewMessage(msg: V2TIMMessage?) { if (!com.aiosman.ravenow.AppState.enableChat) {
super.onRecvNewMessage(msg) android.util.Log.w("ChatViewModel", "OpenIM 未登录,跳过注册消息监听器")
return
}
textMessageListener = object : OnAdvanceMsgListener {
override fun onRecvNewMessage(msg: Message?) {
msg?.let { message -> msg?.let { message ->
// 只处理当前聊天对象的消息 // 只处理当前聊天对象的消息
val currentChatUserId = userProfile?.trtcUserId val currentChatUserId = userProfile?.trtcUserId
@@ -72,38 +79,41 @@ class ChatViewModel(
if (currentChatUserId != null && currentUserId != null) { if (currentChatUserId != null && currentUserId != null) {
// 检查消息是否来自当前聊天对象,且不是自己发送的消息 // 检查消息是否来自当前聊天对象,且不是自己发送的消息
if ((message.userID == currentChatUserId || message.userID == currentUserId) && if ((message.sendID == currentChatUserId || message.sendID == currentUserId) &&
message.sender != currentUserId) { message.sendID != currentUserId) {
val chatItem = ChatItem.convertToChatItem(message, context, avatar = userProfile?.avatar) 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} 的消息,更新聊天列表") android.util.Log.i("ChatViewModel", "收到来自 ${message.sendID} 的消息,更新聊天列表")
} }
} }
} }
} }
} }
} }
V2TIMManager.getMessageManager().addAdvancedMsgListener(textMessageListener); OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
} }
fun UnRegistListener() { fun UnRegistListener() {
V2TIMManager.getMessageManager().removeAdvancedMsgListener(textMessageListener); // OpenIM SDK 不需要显式移除监听器,只需要设置为 null
textMessageListener = null
} }
fun clearUnRead() { fun clearUnRead() {
val conversationID = "c2c_${userProfile?.trtcUserId}" val conversationID = "single_${userProfile?.trtcUserId}"
V2TIMManager.getConversationManager() OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
.cleanConversationUnreadMessageCount(conversationID, 0, 0, object : V2TIMCallback { conversationID,
override fun onSuccess() { object : OnBase<String> {
Log.i("imsdk", "success") override fun onSuccess(data: String?) {
Log.i("openim", "clear unread success")
} }
override fun onError(code: Int, desc: String) { override fun onError(code: Int, error: String?) {
Log.i("imsdk", "failure, code:$code, desc:$desc") Log.i("openim", "clear unread failure, code:$code, error:$error")
} }
}) }
)
} }
@@ -113,58 +123,75 @@ class ChatViewModel(
} }
isLoading = true isLoading = true
viewModelScope.launch { viewModelScope.launch {
V2TIMManager.getMessageManager().getC2CHistoryMessageList( val conversationID = "single_${userProfile?.trtcUserId!!}"
userProfile?.trtcUserId!!,
20, OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
lastMessage, object : OnBase<AdvancedMessage> {
object : V2TIMValueCallback<List<V2TIMMessage>> { override fun onSuccess(data: AdvancedMessage?) {
override fun onSuccess(p0: List<V2TIMMessage>?) { val messages = data?.messageList ?: emptyList()
chatData = chatData + (p0 ?: emptyList()).map { chatData = chatData + messages.map {
var avatar = userProfile?.avatar var avatar = userProfile?.avatar
if (it.isSelf) { if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
avatar = myProfile?.avatar avatar = myProfile?.avatar
} }
ChatItem.convertToChatItem(it, context,avatar) ChatItem.convertToChatItem(it, context, avatar)
}.filterNotNull() }.filterNotNull()
if ((p0?.size ?: 0) < 20) {
if (messages.size < 20) {
hasMore = false hasMore = false
} }
lastMessage = p0?.lastOrNull() lastMessage = messages.lastOrNull()
isLoading = false isLoading = false
Log.d("ChatViewModel", "fetch history message success") Log.d("ChatViewModel", "fetch history message success")
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("ChatViewModel", "fetch history message error: $p1") Log.e("ChatViewModel", "fetch history message error: $error")
isLoading = false isLoading = false
} }
} },
conversationID,
lastMessage,
20,
ViewType.History
) )
} }
} }
fun sendMessage(message: String, context: Context) { fun sendMessage(message: String, context: Context) {
V2TIMManager.getInstance().sendC2CTextMessage( // 检查 OpenIM 是否已登录
message, if (!com.aiosman.ravenow.AppState.enableChat) {
userProfile?.trtcUserId!!, android.util.Log.w("ChatViewModel", "OpenIM 未登录,无法发送消息")
object : V2TIMSendCallback<V2TIMMessage> { return
override fun onProgress(p0: Int) { }
val textMessage = OpenIMClient.getInstance().messageManager.createTextMessage(message)
OpenIMClient.getInstance().messageManager.sendMessage(
object : OnMsgSendCallback {
override fun onProgress(progress: Long) {
// 发送进度
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("ChatViewModel", "send message error: $p1") Log.e("ChatViewModel", "send message error: $error")
} }
override fun onSuccess(p0: V2TIMMessage?) { override fun onSuccess(data: Message?) {
Log.d("ChatViewModel", "send message success") Log.d("ChatViewModel", "send message success")
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar) data?.let { sentMessage ->
chatItem?.let { val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
chatData = listOf(it) + chatData chatItem?.let {
goToNew = true chatData = listOf(it) + chatData
goToNew = true
}
} }
} }
} },
textMessage,
userProfile?.trtcUserId!!, // recvID
null, // groupID
null // offlinePushInfo
) )
} }
@@ -172,35 +199,35 @@ class ChatViewModel(
val tempFile = createTempFile(context, imageUri) val tempFile = createTempFile(context, imageUri)
val imagePath = tempFile?.path val imagePath = tempFile?.path
if (imagePath != null) { if (imagePath != null) {
val v2TIMMessage = V2TIMManager.getMessageManager().createImageMessage(imagePath) val imageMessage = OpenIMClient.getInstance().messageManager.createImageMessageFromFullPath(imagePath)
V2TIMManager.getMessageManager().sendMessage(
v2TIMMessage, OpenIMClient.getInstance().messageManager.sendMessage(
userProfile?.trtcUserId!!, object : OnMsgSendCallback {
null, override fun onProgress(progress: Long) {
V2TIMMessage.V2TIM_PRIORITY_NORMAL, Log.d("ChatViewModel", "send image message progress: $progress")
false,
null,
object : V2TIMSendCallback<V2TIMMessage> {
override fun onProgress(p0: Int) {
Log.d("ChatViewModel", "send image message progress: $p0")
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("ChatViewModel", "send image message error: $p1") Log.e("ChatViewModel", "send image message error: $error")
} }
override fun onSuccess(p0: V2TIMMessage?) { override fun onSuccess(data: Message?) {
Log.d("ChatViewModel", "send image message success") Log.d("ChatViewModel", "send image message success")
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar) data?.let { sentMessage ->
chatItem?.let { val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
chatData = listOf(it) + chatData chatItem?.let {
goToNew = true chatData = listOf(it) + chatData
goToNew = true
}
} }
} }
} },
imageMessage,
userProfile?.trtcUserId!!, // recvID
null, // groupID
null // offlinePushInfo
) )
} }
} }
fun createTempFile(context: Context, uri: Uri): File? { fun createTempFile(context: Context, uri: Uri): File? {
@@ -235,30 +262,34 @@ class ChatViewModel(
} }
fun fetchHistoryMessage(context: Context) { fun fetchHistoryMessage(context: Context) {
V2TIMManager.getMessageManager().getC2CHistoryMessageList( val conversationID = "single_${userProfile?.trtcUserId!!}"
userProfile?.trtcUserId!!,
20, OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
null, object : OnBase<AdvancedMessage> {
object : V2TIMValueCallback<List<V2TIMMessage>> { override fun onSuccess(data: AdvancedMessage?) {
override fun onSuccess(p0: List<V2TIMMessage>?) { val messages = data?.messageList ?: emptyList()
chatData = (p0 ?: emptyList()).mapNotNull { chatData = messages.mapNotNull {
var avatar = userProfile?.avatar var avatar = userProfile?.avatar
if (it.isSelf) { if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
avatar = myProfile?.avatar avatar = myProfile?.avatar
} }
ChatItem.convertToChatItem(it, context,avatar) ChatItem.convertToChatItem(it, context, avatar)
} }
if ((p0?.size ?: 0) < 20) { if (messages.size < 20) {
hasMore = false hasMore = false
} }
lastMessage = p0?.lastOrNull() lastMessage = messages.lastOrNull()
Log.d("ChatViewModel", "fetch history message success") Log.d("ChatViewModel", "fetch history message success")
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("ChatViewModel", "fetch history message error: $p1") Log.e("ChatViewModel", "fetch history message error: $error")
} }
} },
conversationID,
null,
20,
ViewType.History
) )
} }

View File

@@ -86,7 +86,8 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.navigateToGroupInfo import com.aiosman.ravenow.ui.navigateToGroupInfo
import com.aiosman.ravenow.utils.NetworkUtils import com.aiosman.ravenow.utils.NetworkUtils
import com.tencent.imsdk.v2.V2TIMMessage // 临时兼容层 - TODO: 完成 OpenIM 迁移后删除
import io.openim.android.sdk.enums.MessageType
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -376,18 +377,18 @@ fun GroupChatSelfItem(item: ChatItem) {
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 == MessageType.TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp))
.background(Color(0xFF6246FF)) .background(Color(0xFF6246FF))
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
) )
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { MessageType.TEXT -> {
Text( Text(
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
@@ -398,7 +399,7 @@ fun GroupChatSelfItem(item: ChatItem) {
) )
} }
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> { MessageType.PICTURE -> {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.imageList[1].url, imageUrl = item.imageList[1].url,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -469,17 +470,17 @@ fun GroupChatOtherItem(item: ChatItem, showAvatarAndNickname: Boolean = true) {
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 == MessageType.TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp))
.background(AppColors.bubbleBackground) .background(AppColors.bubbleBackground)
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
) )
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { MessageType.TEXT -> {
Text( Text(
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
@@ -490,7 +491,7 @@ fun GroupChatOtherItem(item: ChatItem, showAvatarAndNickname: Boolean = true) {
) )
} }
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> { MessageType.PICTURE -> {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.imageList[1].url, imageUrl = item.imageList[1].url,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),

View File

@@ -22,12 +22,13 @@ 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
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener // OpenIM SDK 导入
import com.tencent.imsdk.v2.V2TIMCallback import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMManager import io.openim.android.sdk.enums.ViewType
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.listener.OnAdvanceMsgListener
import com.tencent.imsdk.v2.V2TIMSendCallback import io.openim.android.sdk.listener.OnBase
import com.tencent.imsdk.v2.V2TIMValueCallback import io.openim.android.sdk.listener.OnMsgSendCallback
import io.openim.android.sdk.models.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -43,10 +44,10 @@ class GroupChatViewModel(
var myProfile by mutableStateOf<AccountProfileEntity?>(null) var myProfile by mutableStateOf<AccountProfileEntity?>(null)
val userService: UserService = UserServiceImpl() val userService: UserService = UserServiceImpl()
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var textMessageListener: V2TIMAdvancedMsgListener? = null var textMessageListener: OnAdvanceMsgListener? = null
var hasMore by mutableStateOf(true) var hasMore by mutableStateOf(true)
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
var lastMessage: V2TIMMessage? = null var lastMessage: Message? = null
val showTimestampMap = mutableMapOf<String, Boolean>() val showTimestampMap = mutableMapOf<String, Boolean>()
var goToNew by mutableStateOf(false) var goToNew by mutableStateOf(false)
@@ -91,10 +92,16 @@ class GroupChatViewModel(
} }
fun RegistListener(context: Context) { fun RegistListener(context: Context) {
textMessageListener = object : V2TIMAdvancedMsgListener() { // 检查 OpenIM 是否已登录
override fun onRecvNewMessage(msg: V2TIMMessage?) { if (!com.aiosman.ravenow.AppState.enableChat) {
super.onRecvNewMessage(msg) android.util.Log.w("GroupChatViewModel", "OpenIM 未登录,跳过注册消息监听器")
return
}
textMessageListener = object : OnAdvanceMsgListener {
override fun onRecvNewMessage(msg: Message?) {
msg?.let { msg?.let {
// 检查是否是当前群聊的消息
if (it.groupID == groupId) { if (it.groupID == groupId) {
val chatItem = ChatItem.convertToChatItem(msg, context, avatar = null) val chatItem = ChatItem.convertToChatItem(msg, context, avatar = null)
chatItem?.let { chatItem?.let {
@@ -105,79 +112,98 @@ class GroupChatViewModel(
} }
} }
} }
V2TIMManager.getMessageManager().addAdvancedMsgListener(textMessageListener) OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
} }
fun UnRegistListener() { fun UnRegistListener() {
V2TIMManager.getMessageManager().removeAdvancedMsgListener(textMessageListener) // OpenIM SDK 不需要显式移除监听器,只需要设置为 null
textMessageListener = null
} }
fun clearUnRead() { fun clearUnRead() {
val conversationID = "group_${groupId}" val conversationID = "group_${groupId}"
V2TIMManager.getConversationManager() OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
.cleanConversationUnreadMessageCount(conversationID, 0, 0, object : V2TIMCallback { conversationID,
override fun onSuccess() { object : OnBase<String> {
Log.i("imsdk", "清除群聊未读消息成功") override fun onSuccess(data: String?) {
Log.i("openim", "清除群聊未读消息成功")
} }
override fun onError(code: Int, desc: String) { override fun onError(code: Int, error: String?) {
Log.i("imsdk", "清除群聊未读消息失败, code:$code, desc:$desc") Log.i("openim", "清除群聊未读消息失败, code:$code, error:$error")
} }
}) }
)
} }
fun onLoadMore(context: Context) { fun onLoadMore(context: Context) {
if (!hasMore || isLoading) return if (!hasMore || isLoading) return
isLoading = true isLoading = true
viewModelScope.launch { viewModelScope.launch {
V2TIMManager.getMessageManager().getGroupHistoryMessageList( val conversationID = "group_${groupId}"
groupId,
20, OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
lastMessage, object : OnBase<AdvancedMessage> {
object : V2TIMValueCallback<List<V2TIMMessage>> { override fun onSuccess(data: AdvancedMessage?) {
override fun onSuccess(p0: List<V2TIMMessage>?) { val messages = data?.messageList ?: emptyList()
chatData = chatData + (p0 ?: emptyList()).map { chatData = chatData + messages.map {
ChatItem.convertToChatItem(it, context, avatar = null) ChatItem.convertToChatItem(it, context, avatar = null)
}.filterNotNull() }.filterNotNull()
if ((p0?.size ?: 0) < 20) {
if (messages.size < 20) {
hasMore = false hasMore = false
} }
lastMessage = p0?.lastOrNull() lastMessage = messages.lastOrNull()
isLoading = false isLoading = false
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $p1") Log.e("GroupChatViewModel", "获取群聊历史消息失败: $error")
isLoading = false isLoading = false
} }
} },
conversationID,
lastMessage,
20,
ViewType.History
) )
} }
} }
fun sendMessage(message: String, context: Context) { fun sendMessage(message: String, context: Context) {
val v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(message) // 检查 OpenIM 是否已登录
V2TIMManager.getMessageManager().sendMessage( if (!com.aiosman.ravenow.AppState.enableChat) {
v2TIMMessage, android.util.Log.w("GroupChatViewModel", "OpenIM 未登录,无法发送消息")
null, return
groupId, }
V2TIMMessage.V2TIM_PRIORITY_NORMAL,
false, val textMessage = OpenIMClient.getInstance().messageManager.createTextMessage(message)
null,
object : V2TIMSendCallback<V2TIMMessage> { OpenIMClient.getInstance().messageManager.sendMessage(
override fun onProgress(p0: Int) {} object : OnMsgSendCallback {
override fun onError(p0: Int, p1: String?) { override fun onProgress(progress: Long) {
Log.e("GroupChatViewModel", "发送群聊消息失败: $p1") // 发送进度
} }
override fun onSuccess(p0: V2TIMMessage?) {
override fun onError(code: Int, error: String?) {
Log.e("GroupChatViewModel", "发送群聊消息失败: $error")
}
override fun onSuccess(data: Message?) {
sendChatAiMessage(message = message, trtcGroupId = groupId) sendChatAiMessage(message = message, trtcGroupId = groupId)
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar) data?.let { sentMessage ->
chatItem?.let { val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
chatData = listOf(it) + chatData chatItem?.let {
goToNew = true chatData = listOf(it) + chatData
goToNew = true
}
} }
} }
} },
textMessage,
null, // recvID (群聊为 null)
groupId, // groupID
null // offlinePushInfo
) )
} }
@@ -197,27 +223,32 @@ class GroupChatViewModel(
val tempFile = createTempFile(context, imageUri) val tempFile = createTempFile(context, imageUri)
val imagePath = tempFile?.path val imagePath = tempFile?.path
if (imagePath != null) { if (imagePath != null) {
val v2TIMMessage = V2TIMManager.getMessageManager().createImageMessage(imagePath) val imageMessage = OpenIMClient.getInstance().messageManager.createImageMessageFromFullPath(imagePath)
V2TIMManager.getMessageManager().sendMessage(
v2TIMMessage, OpenIMClient.getInstance().messageManager.sendMessage(
groupId, object : OnMsgSendCallback {
null, override fun onProgress(progress: Long) {
V2TIMMessage.V2TIM_PRIORITY_NORMAL, Log.d("GroupChatViewModel", "发送群聊图片消息进度: $progress")
false,
null,
object : V2TIMSendCallback<V2TIMMessage> {
override fun onProgress(p0: Int) {}
override fun onError(p0: Int, p1: String?) {
Log.e("GroupChatViewModel", "发送群聊图片消息失败: $p1")
} }
override fun onSuccess(p0: V2TIMMessage?) {
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar) override fun onError(code: Int, error: String?) {
chatItem?.let { Log.e("GroupChatViewModel", "发送群聊图片消息失败: $error")
chatData = listOf(it) + chatData }
goToNew = true
override fun onSuccess(data: Message?) {
data?.let { sentMessage ->
val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
chatItem?.let {
chatData = listOf(it) + chatData
goToNew = true
}
} }
} }
} },
imageMessage,
null, // recvID (群聊为 null)
groupId, // groupID
null // offlinePushInfo
) )
} }
} }
@@ -254,25 +285,29 @@ class GroupChatViewModel(
} }
fun fetchHistoryMessage(context: Context) { fun fetchHistoryMessage(context: Context) {
V2TIMManager.getMessageManager().getGroupHistoryMessageList( val conversationID = "group_${groupId}"
groupId,
20, OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
null, object : OnBase<AdvancedMessage> {
object : V2TIMValueCallback<List<V2TIMMessage>> { override fun onSuccess(data: AdvancedMessage?) {
override fun onSuccess(p0: List<V2TIMMessage>?) { val messages = data?.messageList ?: emptyList()
chatData = (p0 ?: emptyList()).mapNotNull { chatData = messages.mapNotNull {
ChatItem.convertToChatItem(it, context, avatar = null) ChatItem.convertToChatItem(it, context, avatar = null)
} }
if ((p0?.size ?: 0) < 20) { if (messages.size < 20) {
hasMore = false hasMore = false
} }
lastMessage = p0?.lastOrNull() lastMessage = messages.lastOrNull()
} }
override fun onError(p0: Int, p1: String?) { override fun onError(code: Int, error: String?) {
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $p1") Log.e("GroupChatViewModel", "获取群聊历史消息失败: $error")
} }
} },
conversationID,
null,
20,
ViewType.History
) )
} }

View File

@@ -23,11 +23,8 @@ 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
import com.aiosman.ravenow.utils.TrtcHelper import com.aiosman.ravenow.utils.TrtcHelper
import com.tencent.imsdk.v2.V2TIMConversation // 临时兼容层 - TODO: 完成 OpenIM 迁移后删除
import com.tencent.imsdk.v2.V2TIMConversationResult import com.aiosman.ravenow.compat.*
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMMessage
import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@@ -27,9 +27,8 @@ import com.aiosman.ravenow.entity.MomentServiceImpl
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
import com.aiosman.ravenow.ui.navigateToChatAi import com.aiosman.ravenow.ui.navigateToChatAi
import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.NavigationRoute
import com.tencent.imsdk.v2.V2TIMConversationOperationResult // OpenIM SDK 导入 - 会话分组功能在 OpenIM 中不支持,将直接跳转到聊天页面
import com.tencent.imsdk.v2.V2TIMManager import android.util.Log
import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@@ -82,23 +81,10 @@ object MineAgentViewModel : ViewModel() {
navController: NavHostController, navController: NavHostController,
id: Int id: Int
) { ) {
val conversationIDList = listOf("c2c_${trtcUserId}") // TODO: OpenIM 不支持会话分组功能,直接跳转到聊天页面
// OpenIM 不支持会话分组功能,直接跳转到聊天页面
V2TIMManager.getConversationManager().createConversationGroup( Log.d("MineAgentViewModel", "OpenIM 不支持会话分组,直接跳转到 AI 聊天页面")
groupName, navController.navigateToChatAi(id.toString())
conversationIDList,
object : V2TIMValueCallback<List<V2TIMConversationOperationResult>> {
override fun onSuccess(v2TIMConversationOperationResults: List<V2TIMConversationOperationResult>) {
// 创建会话分组成功
navController.navigateToChatAi(id.toString())
}
override fun onError(code: Int, desc: String) {
// 创建会话分组失败
}
}
)
} }

View File

@@ -23,11 +23,10 @@ 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
import com.aiosman.ravenow.utils.TrtcHelper import com.aiosman.ravenow.utils.TrtcHelper
import com.tencent.imsdk.v2.V2TIMConversation // OpenIM SDK 导入
import com.tencent.imsdk.v2.V2TIMConversationResult import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMManager import io.openim.android.sdk.listener.OnBase
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.models.ConversationInfo
import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@@ -44,33 +43,10 @@ data class Conversation(
val isSelf: Boolean val isSelf: Boolean
) { ) {
companion object { companion object {
fun convertToConversation(msg: V2TIMConversation, context: Context): Conversation { // TODO: 如果需要使用 Conversation 数据类,可以实现 OpenIM 版本的 convertToConversation 方法
val lastMessage = Calendar.getInstance().apply { // fun convertToConversation(conversation: ConversationInfo, context: Context): Conversation {
timeInMillis = msg.lastMessage?.timestamp ?: 0 // // OpenIM 版本的转换逻辑
timeInMillis *= 1000 // }
}
var displayText = ""
when (msg.lastMessage?.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
displayText = msg.lastMessage?.textElem?.text ?: ""
}
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
displayText = "[图片]"
}
}
return Conversation(
id = msg.conversationID,
nickname = msg.showName,
lastMessage = msg.lastMessage?.textElem?.text ?: "",
lastMessageTime = lastMessage.time.formatChatTime(context),
avatar = "${ConstVars.BASE_SERVER}${msg.faceUrl}",
unreadCount = msg.unreadCount,
trtcUserId = msg.userID,
displayText = displayText,
isSelf = msg.lastMessage.sender == AppState.profile?.trtcUserId
)
}
} }
} }
@@ -151,25 +127,31 @@ object MessageListViewModel : ViewModel() {
} }
suspend fun loadChatList(context: Context) { suspend fun loadChatList(context: Context) {
// 检查 OpenIM 是否已登录
if (!com.aiosman.ravenow.AppState.enableChat) {
android.util.Log.w("MessageListViewModel", "OpenIM 未登录,跳过加载聊天列表")
return
}
val result = suspendCoroutine { continuation -> val result = suspendCoroutine { continuation ->
V2TIMManager.getConversationManager().getConversationList( // OpenIM 获取所有会话列表
0, OpenIMClient.getInstance().conversationManager.getAllConversationList(
Int.MAX_VALUE, object : OnBase<List<ConversationInfo>> {
object : V2TIMValueCallback<V2TIMConversationResult> { override fun onSuccess(data: List<ConversationInfo>?) {
override fun onSuccess(t: V2TIMConversationResult?) { continuation.resumeWith(Result.success(data ?: emptyList()))
continuation.resumeWith(Result.success(t))
} }
override fun onError(code: Int, desc: String?) { override fun onError(code: Int, error: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
} }
} }
) )
} }
/* chatList = result?.conversationList?.map { msg: V2TIMConversation -> // 暂时注释掉,因为 convertToConversation 方法已被注释
Conversation.convertToConversation(msg, context) // chatList = result.map { conversation ->
} ?: emptyList()*/ // Conversation.convertToConversation(conversation, context)
// }
} }
suspend fun loadUnreadCount() { suspend fun loadUnreadCount() {

View File

@@ -21,12 +21,9 @@ import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel
import com.aiosman.ravenow.ui.navigateToChatAi import com.aiosman.ravenow.ui.navigateToChatAi
import com.tencent.imsdk.v2.V2TIMConversation import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMConversationListFilter import io.openim.android.sdk.listener.OnBase
import com.tencent.imsdk.v2.V2TIMConversationResult import io.openim.android.sdk.models.ConversationInfo
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMMessage
import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@@ -42,42 +39,43 @@ data class AgentConversation(
val isSelf: Boolean val isSelf: Boolean
) { ) {
companion object { companion object {
fun convertToAgentConversation(msg: V2TIMConversation, context: Context): AgentConversation { fun convertToAgentConversation(conversation: ConversationInfo, context: Context): AgentConversation {
val lastMessage = Calendar.getInstance().apply { val lastMessage = Calendar.getInstance().apply {
timeInMillis = msg.lastMessage?.timestamp ?: 0 timeInMillis = conversation.latestMsgSendTime
timeInMillis *= 1000
}
var displayText = ""
when (msg.lastMessage?.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
displayText = msg.lastMessage?.textElem?.text ?: ""
}
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
displayText = "[图片]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_SOUND -> {
displayText = "[语音]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO -> {
displayText = "[视频]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_FILE -> {
displayText = "[文件]"
}
else -> {
displayText = "[消息]"
}
} }
var displayText = conversation.latestMsg?: ""
// when (conversation.latestMsg) {
// 101 -> { // TEXT
// displayText = conversation.latestMsg?: ""
// }
// 102 -> { // IMAGE
// displayText = "[图片]"
// }
// 103 -> { // AUDIO
// displayText = "[语音]"
// }
// 104 -> { // VIDEO
// displayText = "[视频]"
// }
// 105 -> { // FILE
// displayText = "[文件]"
// }
// else -> {
// displayText = "[消息]"
// }
// }
return AgentConversation( return AgentConversation(
id = msg.conversationID, id = conversation.conversationID,
nickname = msg.showName, nickname = conversation.showName ?: "",
lastMessage = msg.lastMessage?.textElem?.text ?: "", lastMessage = conversation.latestMsg ?: "",
lastMessageTime = lastMessage.time.formatChatTime(context), lastMessageTime = lastMessage.time.formatChatTime(context),
avatar = "${ApiClient.BASE_API_URL+"/"}${msg.faceUrl}"+"?token="+"${AppStore.token}".replace("storage/avatars/", "/avatar/"), avatar = "${ApiClient.BASE_API_URL+"/"}${conversation.faceURL}"+"?token="+"${AppStore.token}".replace("storage/avatars/", "/avatar/"),
unreadCount = msg.unreadCount, unreadCount = conversation.unreadCount,
trtcUserId = msg.userID, trtcUserId = conversation.userID ?: "",
displayText = displayText, displayText = displayText,
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId // TODO: openim latestMsg
isSelf = false,
// isSelf = conversation.latestMsg?.sendID == AppState.profile?.trtcUserId
) )
} }
} }
@@ -136,29 +134,38 @@ object AgentChatListViewModel : ViewModel() {
} }
private suspend fun loadAgentChatList(context: Context) { private suspend fun loadAgentChatList(context: Context) {
// 检查 OpenIM 是否已登录
if (!com.aiosman.ravenow.AppState.enableChat) {
android.util.Log.w("AgentChatListViewModel", "OpenIM 未登录,跳过加载智能体聊天列表")
return
}
val result = suspendCoroutine { continuation -> val result = suspendCoroutine { continuation ->
val filter = V2TIMConversationListFilter() // OpenIM 获取所有会话列表
filter.conversationGroup = "ai_group" OpenIMClient.getInstance().conversationManager.getAllConversationList(
filter.conversationType = V2TIMConversation.V2TIM_C2C object : OnBase<List<ConversationInfo>> {
V2TIMManager.getConversationManager().getConversationListByFilter( override fun onSuccess(data: List<ConversationInfo>?) {
filter, // 过滤出智能体会话(单聊类型,且可能有特定标识)
0, val agentConversations = data?.filter { conversation ->
Int.MAX_VALUE, // 这里需要根据实际业务逻辑来过滤智能体会话
object : V2TIMValueCallback<V2TIMConversationResult> { // 可能通过会话类型、用户ID前缀、或其他标识来判断
override fun onSuccess(t: V2TIMConversationResult?) { conversation.conversationType == 1 // 1 表示单聊
continuation.resumeWith(Result.success(t)) // 可以添加更多过滤条件,比如:
// && conversation.userID?.startsWith("ai_") == true
} ?: emptyList()
continuation.resumeWith(Result.success(agentConversations))
} }
override fun onError(code: Int, desc: String?) { override fun onError(code: Int, error: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
} }
} }
) )
} }
agentChatList = result?.conversationList?.map { msg: V2TIMConversation -> agentChatList = result.map { conversation ->
AgentConversation.convertToAgentConversation(msg, context) AgentConversation.convertToAgentConversation(conversation, context)
} ?: emptyList() }
} }
fun goToChatAi( fun goToChatAi(

View File

@@ -15,12 +15,12 @@ import com.aiosman.ravenow.data.UserServiceImpl
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
import com.tencent.imsdk.v2.V2TIMConversation // OpenIM SDK 导入
import com.tencent.imsdk.v2.V2TIMConversationListFilter import com.aiosman.ravenow.AppStore
import com.tencent.imsdk.v2.V2TIMConversationResult import com.aiosman.ravenow.data.api.ApiClient
import com.tencent.imsdk.v2.V2TIMManager import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.listener.OnBase
import com.tencent.imsdk.v2.V2TIMValueCallback import io.openim.android.sdk.models.ConversationInfo
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@@ -36,42 +36,21 @@ data class FriendConversation(
val isSelf: Boolean val isSelf: Boolean
) { ) {
companion object { companion object {
fun convertToFriendConversation(msg: V2TIMConversation, context: Context): FriendConversation { fun convertToFriendConversation(conversation: ConversationInfo, context: Context): FriendConversation {
val lastMessage = Calendar.getInstance().apply { val lastMessage = Calendar.getInstance().apply {
timeInMillis = msg.lastMessage?.timestamp ?: 0 timeInMillis = conversation.latestMsgSendTime
timeInMillis *= 1000
}
var displayText = ""
when (msg.lastMessage?.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
displayText = msg.lastMessage?.textElem?.text ?: ""
}
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
displayText = "[图片]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_SOUND -> {
displayText = "[语音]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO -> {
displayText = "[视频]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_FILE -> {
displayText = "[文件]"
}
else -> {
displayText = "[消息]"
}
} }
var displayText = conversation.latestMsg
return FriendConversation( return FriendConversation(
id = msg.conversationID, id = conversation.conversationID,
nickname = msg.showName, nickname = conversation.showName ?: "",
lastMessage = msg.lastMessage?.textElem?.text ?: "", lastMessage = conversation.latestMsg ?: "",
lastMessageTime = lastMessage.time.formatChatTime(context), lastMessageTime = lastMessage.time.formatChatTime(context),
avatar = "${ConstVars.BASE_SERVER}${msg.faceUrl}", avatar = "${ApiClient.BASE_API_URL+"/"}${conversation.faceURL}"+"?token="+"${AppStore.token}".replace("storage/avatars/", "/avatar/"),
unreadCount = msg.unreadCount, unreadCount = conversation.unreadCount,
trtcUserId = msg.userID, trtcUserId = conversation.userID ?: "",
displayText = displayText, displayText = displayText,
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId isSelf = false
) )
} }
} }
@@ -130,34 +109,38 @@ object FriendChatListViewModel : ViewModel() {
} }
private suspend fun loadFriendChatList(context: Context) { private suspend fun loadFriendChatList(context: Context) {
val filter = V2TIMConversationListFilter() // 检查 OpenIM 是否已登录
filter.conversationType = V2TIMConversation.V2TIM_C2C if (!com.aiosman.ravenow.AppState.enableChat) {
android.util.Log.w("FriendChatListViewModel", "OpenIM 未登录,跳过加载朋友聊天列表")
return
}
val result = suspendCoroutine { continuation -> val result = suspendCoroutine { continuation ->
// 获取全部会话列表 // OpenIM 获取所有会话列表
V2TIMManager.getConversationManager().getConversationListByFilter( OpenIMClient.getInstance().conversationManager.getAllConversationList(
filter, object : OnBase<List<ConversationInfo>> {
0, override fun onSuccess(data: List<ConversationInfo>?) {
Int.MAX_VALUE, continuation.resumeWith(Result.success(data ?: emptyList()))
object : V2TIMValueCallback<V2TIMConversationResult> {
override fun onSuccess(t: V2TIMConversationResult?) {
continuation.resumeWith(Result.success(t))
} }
override fun onError(code: Int, desc: String?) { override fun onError(code: Int, error: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
} }
} }
) )
} }
// 过滤掉ai_group的会话只保留朋友聊天 // 过滤出朋友会话(单聊类型,且排除 AI 智能体)
val filteredConversations = result?.conversationList?.filter { conversation -> val filteredConversations = result.filter { conversation ->
// 排除ai_group的会话 // 1 表示单聊,排除 AI 智能体会话
!conversation.conversationGroupList.contains("ai_group")&& conversation.type == V2TIMConversation.V2TIM_C2C conversation.conversationType == 1 &&
} ?: emptyList() // 可以根据实际业务逻辑添加更多过滤条件
// 比如排除 AI 智能体的 userID 前缀或标识
!(conversation.userID?.startsWith("ai_") == true)
}
friendChatList = filteredConversations.map { msg: V2TIMConversation -> friendChatList = filteredConversations.map { conversation ->
FriendConversation.convertToFriendConversation(msg, context) FriendConversation.convertToFriendConversation(conversation, context)
} }
} }

View File

@@ -20,14 +20,12 @@ import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.navigateToChat import com.aiosman.ravenow.ui.navigateToChat
import com.aiosman.ravenow.ui.navigateToGroupChat import com.aiosman.ravenow.ui.navigateToGroupChat
import com.aiosman.ravenow.ui.navigateToGroupInfo import com.aiosman.ravenow.ui.navigateToGroupInfo
import com.tencent.imsdk.v2.V2TIMConversation // OpenIM SDK 导入
import com.tencent.imsdk.v2.V2TIMConversationListFilter import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMConversationResult import io.openim.android.sdk.listener.OnAdvanceMsgListener
import com.tencent.imsdk.v2.V2TIMManager import io.openim.android.sdk.listener.OnBase
import com.tencent.imsdk.v2.V2TIMMessage import io.openim.android.sdk.listener.OnConversationListener
import com.tencent.imsdk.v2.V2TIMValueCallback import io.openim.android.sdk.models.ConversationInfo
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
@@ -44,52 +42,32 @@ data class GroupConversation(
val memberCount: Int = 0 val memberCount: Int = 0
) { ) {
companion object { companion object {
fun convertToGroupConversation(msg: V2TIMConversation, context: Context): GroupConversation { fun convertToGroupConversation(conversation: ConversationInfo, context: Context): GroupConversation {
val lastMessage = Calendar.getInstance().apply { val lastMessage = Calendar.getInstance().apply {
timeInMillis = msg.lastMessage?.timestamp ?: 0 timeInMillis = conversation.latestMsgSendTime
timeInMillis *= 1000
}
var displayText = ""
when (msg.lastMessage?.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
displayText = msg.lastMessage?.textElem?.text ?: ""
}
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
displayText = "[图片]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_SOUND -> {
displayText = "[语音]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO -> {
displayText = "[视频]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_FILE -> {
displayText = "[文件]"
}
else -> {
displayText = "[消息]"
}
} }
return GroupConversation( return GroupConversation(
id = msg.conversationID, id = conversation.conversationID,
groupId = msg.groupID, groupId = conversation.groupID ?: "",
groupName = msg.showName, groupName = conversation.showName ?: "",
lastMessage = msg.lastMessage?.textElem?.text ?: "", lastMessage = conversation.latestMsg?: "",
lastMessageTime = lastMessage.time.formatChatTime(context), lastMessageTime = lastMessage.time.formatChatTime(context),
avatar = if (msg.faceUrl.isNullOrEmpty()) { avatar = if (conversation.faceURL.isNullOrEmpty()) {
// 将 groupId 转换为 Base64 // 将 groupId 转换为 Base64
val groupIdBase64 = android.util.Base64.encodeToString( val groupIdBase64 = android.util.Base64.encodeToString(
msg.groupID.toByteArray(), (conversation.groupID ?: "").toByteArray(),
android.util.Base64.NO_WRAP android.util.Base64.NO_WRAP
) )
"${ApiClient.RETROFIT_URL+"group/avatar?groupIdBase64="}${groupIdBase64}"+"&token="+"${AppStore.token}" "${ApiClient.RETROFIT_URL+"group/avatar?groupIdBase64="}${groupIdBase64}"+"&token="+"${AppStore.token}"
} else { } else {
"${ApiClient.BASE_API_URL+"/outside/rooms/avatar/"}${msg.faceUrl}"+"?token="+"${AppStore.token}" "${ApiClient.BASE_API_URL+"/outside/rooms/avatar/"}${conversation.faceURL}"+"?token="+"${AppStore.token}"
}, },
unreadCount = msg.unreadCount, unreadCount = conversation.unreadCount,
displayText = displayText, displayText = conversation.latestMsg?: "",
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId, isSelf = false,
memberCount = msg.groupAtInfoList?.size ?: 0 // TODO openim get grouplist
memberCount = 0
) )
} }
} }
@@ -111,8 +89,8 @@ object GroupChatListViewModel : ViewModel() {
private val pageSize = 20 private val pageSize = 20
// 消息监听器 // 消息监听器
private var messageListener: V2TIMAdvancedMsgListener? = null private var messageListener: OnAdvanceMsgListener? = null
private var conversationListener: V2TIMConversationListener? = null private var conversationListener: OnConversationListener? = 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
@@ -152,34 +130,35 @@ object GroupChatListViewModel : ViewModel() {
} }
private suspend fun loadGroupChatList(context: Context) { private suspend fun loadGroupChatList(context: Context) {
val filter = V2TIMConversationListFilter() // 检查 OpenIM 是否已登录
filter.conversationType = V2TIMConversation.V2TIM_GROUP if (!com.aiosman.ravenow.AppState.enableChat) {
android.util.Log.w("GroupChatListViewModel", "OpenIM 未登录,跳过加载群聊列表")
return
}
val result = suspendCoroutine { continuation -> val result = suspendCoroutine { continuation ->
// 获取全部会话列表 // OpenIM 获取所有会话列表
V2TIMManager.getConversationManager().getConversationListByFilter( OpenIMClient.getInstance().conversationManager.getAllConversationList(
filter, object : OnBase<List<ConversationInfo>> {
0, override fun onSuccess(data: List<ConversationInfo>?) {
Int.MAX_VALUE, continuation.resumeWith(Result.success(data ?: emptyList()))
object : V2TIMValueCallback<V2TIMConversationResult> {
override fun onSuccess(t: V2TIMConversationResult?) {
continuation.resumeWith(Result.success(t))
} }
override fun onError(code: Int, desc: String?) { override fun onError(code: Int, error: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
} }
} }
) )
} }
// 只保留群聊会话,过滤掉单聊会话 // 过滤出群聊会话(群聊类型)
val filteredConversations = result?.conversationList?.filter { conversation -> val filteredConversations = result.filter { conversation ->
// 只保留群聊会话conversationType为2表示群聊 // 2 表示群聊类型
conversation.type == V2TIMConversation.V2TIM_GROUP conversation.conversationType == 2
} ?: emptyList() }
groupChatList = filteredConversations.map { msg: V2TIMConversation -> groupChatList = filteredConversations.map { conversation ->
GroupConversation.convertToGroupConversation(msg, context) GroupConversation.convertToGroupConversation(conversation, context)
} }
} }
@@ -232,11 +211,10 @@ object GroupChatListViewModel : ViewModel() {
// 初始化消息监听器 // 初始化消息监听器
fun initMessageListener(context: Context) { fun initMessageListener(context: Context) {
// 消息监听器 - 监听新消息 // 消息监听器 - 监听新消息
messageListener = object : V2TIMAdvancedMsgListener() { messageListener = object : OnAdvanceMsgListener {
override fun onRecvNewMessage(msg: V2TIMMessage?) { override fun onRecvNewMessage(msg: io.openim.android.sdk.models.Message?) {
super.onRecvNewMessage(msg)
msg?.let { message -> msg?.let { message ->
if (message.groupID != null && message.groupID.isNotEmpty()) { if (!message.groupID.isNullOrEmpty()) {
// 收到群聊消息,刷新群聊列表 // 收到群聊消息,刷新群聊列表
android.util.Log.i("GroupChatList", "收到群聊消息,刷新列表") android.util.Log.i("GroupChatList", "收到群聊消息,刷新列表")
refreshGroupChatList(context) refreshGroupChatList(context)
@@ -246,12 +224,11 @@ object GroupChatListViewModel : ViewModel() {
} }
// 会话监听器 - 监听会话变化 // 会话监听器 - 监听会话变化
conversationListener = object : V2TIMConversationListener() { conversationListener = object : OnConversationListener {
override fun onConversationChanged(conversationList: MutableList<V2TIMConversation>?) { override fun onConversationChanged(conversationList: List<ConversationInfo>?) {
super.onConversationChanged(conversationList)
// 会话发生变化,刷新群聊列表 // 会话发生变化,刷新群聊列表
conversationList?.let { conversations -> conversationList?.let { conversations ->
val hasGroupConversation = conversations.any { it.type == V2TIMConversation.V2TIM_GROUP } val hasGroupConversation = conversations.any { it.conversationType == 2 }
if (hasGroupConversation) { if (hasGroupConversation) {
android.util.Log.i("GroupChatList", "群聊会话发生变化,刷新列表") android.util.Log.i("GroupChatList", "群聊会话发生变化,刷新列表")
refreshGroupChatList(context) refreshGroupChatList(context)
@@ -259,11 +236,10 @@ object GroupChatListViewModel : ViewModel() {
} }
} }
override fun onNewConversation(conversationList: MutableList<V2TIMConversation>?) { override fun onNewConversation(conversationList: List<ConversationInfo>?) {
super.onNewConversation(conversationList)
// 新增会话,刷新群聊列表 // 新增会话,刷新群聊列表
conversationList?.let { conversations -> conversationList?.let { conversations ->
val hasGroupConversation = conversations.any { it.type == V2TIMConversation.V2TIM_GROUP } val hasGroupConversation = conversations.any { it.conversationType == 2 }
if (hasGroupConversation) { if (hasGroupConversation) {
android.util.Log.i("GroupChatList", "新增群聊会话,刷新列表") android.util.Log.i("GroupChatList", "新增群聊会话,刷新列表")
refreshGroupChatList(context) refreshGroupChatList(context)
@@ -273,18 +249,13 @@ object GroupChatListViewModel : ViewModel() {
} }
// 注册监听器 // 注册监听器
V2TIMManager.getMessageManager().addAdvancedMsgListener(messageListener) OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(messageListener)
V2TIMManager.getConversationManager().addConversationListener(conversationListener) OpenIMClient.getInstance().conversationManager.setOnConversationListener(conversationListener)
} }
// 移除消息监听器 // 移除消息监听器
fun removeMessageListener() { fun removeMessageListener() {
messageListener?.let { // OpenIM SDK 不需要显式移除监听器,只需要设置为 null
V2TIMManager.getMessageManager().removeAdvancedMsgListener(it)
}
conversationListener?.let {
V2TIMManager.getConversationManager().removeConversationListener(it)
}
messageListener = null messageListener = null
conversationListener = null conversationListener = null
} }

View File

@@ -1,24 +1,26 @@
package com.aiosman.ravenow.utils package com.aiosman.ravenow.utils
import com.tencent.imsdk.v2.V2TIMCallback import io.openim.android.sdk.OpenIMClient
import com.tencent.imsdk.v2.V2TIMManager import io.openim.android.sdk.listener.OnBase
import com.tencent.imsdk.v2.V2TIMUserFullInfo import io.openim.android.sdk.models.UserInfo
import com.tencent.imsdk.v2.V2TIMValueCallback import io.openim.android.sdk.models.UserInfoReq
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
object TrtcHelper { object TrtcHelper {
suspend fun loadUnreadCount(): Long { suspend fun loadUnreadCount(): Long {
return suspendCoroutine { continuation -> return suspendCoroutine { continuation ->
V2TIMManager.getConversationManager() OpenIMClient.getInstance().conversationManager
.getTotalUnreadMessageCount(object : V2TIMValueCallback<Long> { .getTotalUnreadMsgCount(object : OnBase<String> {
override fun onSuccess(t: Long?) { override fun onSuccess(data: String?) {
continuation.resumeWith(Result.success(t ?: 0)) // OpenIM 返回的是字符串格式的数字
val count = data?.toLongOrNull() ?: 0L
continuation.resumeWith(Result.success(count))
} }
override fun onError(code: Int, desc: String?) { override fun onError(code: Int, error: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
} }
}); })
} }
} }
@@ -26,19 +28,20 @@ object TrtcHelper {
avatar: String?, avatar: String?,
nickName: String? nickName: String?
) { ) {
val info = V2TIMUserFullInfo() val infoReq = UserInfoReq()
nickName?.let { info.setNickname(it) } nickName?.let { infoReq.nickname = it }
avatar?.let { info.faceUrl = it } avatar?.let { infoReq.faceURL = it }
return suspendCoroutine { continuation -> return suspendCoroutine { continuation ->
V2TIMManager.getInstance().setSelfInfo(info, object : V2TIMCallback { //(OnBase<String> base, UserInfoReq userInfoReq
override fun onError(code: Int, desc: String?) { OpenIMClient.getInstance().userInfoManager.setSelfInfo(object : OnBase<String> {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc"))) override fun onError(code: Int, error: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
} }
override fun onSuccess() { override fun onSuccess(data: String?) {
continuation.resumeWith(Result.success(Unit)) continuation.resumeWith(Result.success(Unit))
} }
}) },infoReq)
} }
} }
} }

View File

@@ -12,7 +12,6 @@ eventbus = "3.3.1"
firebaseBom = "33.2.0" firebaseBom = "33.2.0"
gson = "2.12.1" gson = "2.12.1"
imagecropview = "3.0.1" imagecropview = "3.0.1"
imsdkPlus = "8.1.6116"
jpushGoogle = "5.4.0" jpushGoogle = "5.4.0"
jwtdecode = "2.0.2" jwtdecode = "2.0.2"
kotlin = "1.9.10" kotlin = "1.9.10"
@@ -70,7 +69,6 @@ firebase-messaging-ktx = { module = "com.google.firebase:firebase-messaging-ktx"
firebase-perf = { module = "com.google.firebase:firebase-perf" } firebase-perf = { module = "com.google.firebase:firebase-perf" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
imagecropview = { module = "io.github.rroohit:ImageCropView", version.ref = "imagecropview" } imagecropview = { module = "io.github.rroohit:ImageCropView", version.ref = "imagecropview" }
imsdk-plus = { module = "com.tencent.imsdk:imsdk-plus", version.ref = "imsdkPlus" }
jpush-google = { module = "cn.jiguang.sdk:jpush-google", version.ref = "jpushGoogle" } jpush-google = { module = "cn.jiguang.sdk:jpush-google", version.ref = "jpushGoogle" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }