初步替换IM接口
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<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">
|
||||
<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>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
176
MIGRATION_GUIDE.md
Normal file
176
MIGRATION_GUIDE.md
Normal 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. 清理所有相关的临时注释
|
||||
@@ -113,7 +113,6 @@ dependencies {
|
||||
implementation(libs.firebase.messaging.ktx)
|
||||
|
||||
implementation (libs.jpush.google)
|
||||
api (libs.imsdk.plus)
|
||||
implementation (libs.im.sdk)
|
||||
implementation (libs.im.core.sdk)
|
||||
implementation (libs.gson)
|
||||
|
||||
@@ -79,9 +79,6 @@
|
||||
<action android:name="cn.jiguang.user.service.action" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".TrtcService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".OpenIMService"
|
||||
android:exported="false" />
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import android.icu.util.Calendar
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.exp.formatChatTime
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.tencent.imsdk.v2.V2TIMImageElem
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import io.openim.android.sdk.models.Message
|
||||
import io.openim.android.sdk.models.PictureElem
|
||||
|
||||
data class ChatItem(
|
||||
val message: String,
|
||||
@@ -16,7 +16,7 @@ data class ChatItem(
|
||||
val nickname: String,
|
||||
val timeCategory: String = "",
|
||||
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 textDisplay: String = "",
|
||||
val msgId: String, // Add this property
|
||||
@@ -24,63 +24,81 @@ data class ChatItem(
|
||||
var showTimeDivider: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
fun convertToChatItem(message: V2TIMMessage, context: Context,avatar: String? = null): ChatItem? {
|
||||
val timestamp = message.timestamp
|
||||
// OpenIM 消息类型常量
|
||||
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()
|
||||
calendar.timeInMillis = timestamp * 1000
|
||||
val imageElm = message.imageElem?.imageList
|
||||
calendar.timeInMillis = timestamp
|
||||
|
||||
var faceAvatar = avatar
|
||||
if (faceAvatar == null) {
|
||||
faceAvatar = "${ConstVars.BASE_SERVER}${message.faceUrl}"
|
||||
faceAvatar = "${ConstVars.BASE_SERVER}${message.senderFaceUrl}"
|
||||
}
|
||||
when (message.elemType) {
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
|
||||
val imageElm = message.imageElem?.imageList?.all {
|
||||
it.size == 0
|
||||
}
|
||||
if (imageElm != true) {
|
||||
|
||||
when (message.contentType) {
|
||||
MESSAGE_TYPE_IMAGE -> {
|
||||
val pictureElem = message.pictureElem
|
||||
if (pictureElem != null && !pictureElem.sourcePicture.url.isNullOrEmpty()) {
|
||||
return ChatItem(
|
||||
message = "Image",
|
||||
avatar = faceAvatar,
|
||||
time = calendar.time.formatChatTime(context),
|
||||
userId = message.sender,
|
||||
nickname = message.nickName,
|
||||
timestamp = timestamp * 1000,
|
||||
imageList = message.imageElem?.imageList
|
||||
?: emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(),
|
||||
messageType = V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE,
|
||||
userId = message.sendID,
|
||||
nickname = message.senderNickname ?: "",
|
||||
timestamp = timestamp,
|
||||
imageList = listOfNotNull(
|
||||
PictureInfo(
|
||||
url = pictureElem.sourcePicture.url ?: "",
|
||||
width = pictureElem.sourcePicture.width,
|
||||
height = pictureElem.sourcePicture.height,
|
||||
size = pictureElem.sourcePicture.size
|
||||
)
|
||||
).toMutableList(),
|
||||
messageType = MESSAGE_TYPE_IMAGE,
|
||||
textDisplay = "Image",
|
||||
msgId = message.msgID // Add this line to include msgId
|
||||
msgId = message.clientMsgID
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
|
||||
MESSAGE_TYPE_TEXT -> {
|
||||
return ChatItem(
|
||||
message = message.textElem?.text ?: "Unsupported message type",
|
||||
message = message.textElem?.content ?: "Unsupported message type",
|
||||
avatar = faceAvatar,
|
||||
time = calendar.time.formatChatTime(context),
|
||||
userId = message.sender,
|
||||
nickname = message.nickName,
|
||||
timestamp = timestamp * 1000,
|
||||
imageList = imageElm?.toMutableList()
|
||||
?: emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(),
|
||||
messageType = V2TIMMessage.V2TIM_ELEM_TYPE_TEXT,
|
||||
textDisplay = message.textElem?.text ?: "Unsupported message type",
|
||||
msgId = message.msgID // Add this line to include msgId
|
||||
userId = message.sendID,
|
||||
nickname = message.senderNickname ?: "",
|
||||
timestamp = timestamp,
|
||||
imageList = emptyList<PictureInfo>().toMutableList(),
|
||||
messageType = MESSAGE_TYPE_TEXT,
|
||||
textDisplay = message.textElem?.content ?: "Unsupported message type",
|
||||
msgId = message.clientMsgID
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OpenIM 图片信息数据类
|
||||
data class PictureInfo(
|
||||
val url: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val size: Long
|
||||
)
|
||||
|
||||
|
||||
data class ChatNotification(
|
||||
@SerializedName("userId")
|
||||
|
||||
@@ -84,7 +84,7 @@ import com.aiosman.ravenow.ui.composables.MenuItem
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.utils.NetworkUtils
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import io.openim.android.sdk.enums.MessageType
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
@@ -379,18 +379,18 @@ fun ChatAiSelfItem(item: ChatItem) {
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
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))
|
||||
.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)
|
||||
vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
|
||||
horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
|
||||
)
|
||||
|
||||
) {
|
||||
when (item.messageType) {
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
|
||||
MessageType.TEXT -> {
|
||||
Text(
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
@@ -401,7 +401,7 @@ fun ChatAiSelfItem(item: ChatItem) {
|
||||
)
|
||||
}
|
||||
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
|
||||
MessageType.PICTURE -> {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.imageList[1].url,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -467,18 +467,18 @@ fun ChatAiOtherItem(item: ChatItem) {
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
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))
|
||||
.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)
|
||||
vertical = (if (item.messageType == MessageType.TEXT) 8.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) {
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
|
||||
MessageType.TEXT -> {
|
||||
Text(
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
@@ -489,7 +489,7 @@ fun ChatAiOtherItem(item: ChatItem) {
|
||||
)
|
||||
}
|
||||
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
|
||||
MessageType.PICTURE -> {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.imageList[1].url,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
@@ -23,13 +23,13 @@ import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.ChatItem
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
import com.aiosman.ravenow.ui.navigateToChatAi
|
||||
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener
|
||||
import com.tencent.imsdk.v2.V2TIMCallback
|
||||
import com.tencent.imsdk.v2.V2TIMConversationOperationResult
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import com.tencent.imsdk.v2.V2TIMSendCallback
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
// OpenIM SDK 导入
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.enums.ViewType
|
||||
import io.openim.android.sdk.listener.OnAdvanceMsgListener
|
||||
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 java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -44,10 +44,10 @@ class ChatAiViewModel(
|
||||
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
val userService: UserService = UserServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var textMessageListener: V2TIMAdvancedMsgListener? = null
|
||||
var textMessageListener: OnAdvanceMsgListener? = null
|
||||
var hasMore by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var lastMessage: V2TIMMessage? = null
|
||||
var lastMessage: Message? = null
|
||||
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
|
||||
var chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||
var goToNew by mutableStateOf(false)
|
||||
@@ -68,9 +68,14 @@ class ChatAiViewModel(
|
||||
|
||||
|
||||
fun RegistListener(context: Context) {
|
||||
textMessageListener = object : V2TIMAdvancedMsgListener() {
|
||||
override fun onRecvNewMessage(msg: V2TIMMessage?) {
|
||||
super.onRecvNewMessage(msg)
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("ChatAiViewModel", "OpenIM 未登录,跳过注册消息监听器")
|
||||
return
|
||||
}
|
||||
|
||||
textMessageListener = object : OnAdvanceMsgListener {
|
||||
override fun onRecvNewMessage(msg: Message?) {
|
||||
msg?.let { message ->
|
||||
// 只处理当前聊天对象的消息
|
||||
val currentChatUserId = userProfile?.trtcUserId
|
||||
@@ -78,38 +83,41 @@ class ChatAiViewModel(
|
||||
|
||||
if (currentChatUserId != null && currentUserId != null) {
|
||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||
if ((message.userID == currentChatUserId || message.userID == currentUserId) &&
|
||||
message.sender != currentUserId) {
|
||||
if ((message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
||||
message.sendID != 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聊天列表")
|
||||
android.util.Log.i("ChatAiViewModel", "收到来自 ${message.sendID} 的消息,更新AI聊天列表")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
V2TIMManager.getMessageManager().addAdvancedMsgListener(textMessageListener);
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
||||
}
|
||||
|
||||
fun UnRegistListener() {
|
||||
V2TIMManager.getMessageManager().removeAdvancedMsgListener(textMessageListener);
|
||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
||||
textMessageListener = null
|
||||
}
|
||||
|
||||
fun clearUnRead() {
|
||||
val conversationID = "c2c_${userProfile?.trtcUserId}"
|
||||
V2TIMManager.getConversationManager()
|
||||
.cleanConversationUnreadMessageCount(conversationID, 0, 0, object : V2TIMCallback {
|
||||
override fun onSuccess() {
|
||||
Log.i("imsdk", "success")
|
||||
val conversationID = "single_${userProfile?.trtcUserId}"
|
||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
||||
conversationID,
|
||||
object : OnBase<String> {
|
||||
override fun onSuccess(data: String?) {
|
||||
Log.i("openim", "clear unread success")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String) {
|
||||
Log.i("imsdk", "failure, code:$code, desc:$desc")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.i("openim", "clear unread failure, code:$code, error:$error")
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -119,117 +127,124 @@ class ChatAiViewModel(
|
||||
}
|
||||
isLoading = true
|
||||
viewModelScope.launch {
|
||||
V2TIMManager.getMessageManager().getC2CHistoryMessageList(
|
||||
userProfile?.trtcUserId!!,
|
||||
20,
|
||||
lastMessage,
|
||||
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||
chatData = chatData + (p0 ?: emptyList()).map {
|
||||
val conversationID = "single_${userProfile?.trtcUserId!!}"
|
||||
// val options = OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList() .apply {
|
||||
// conversationID = conversationID
|
||||
// count = 20
|
||||
// lastMinSeq = lastMessage?.seq ?: 0
|
||||
// }
|
||||
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
|
||||
if (it.isSelf) {
|
||||
if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context,avatar)
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}.filterNotNull()
|
||||
if ((p0?.size ?: 0) < 20) {
|
||||
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = p0?.lastOrNull()
|
||||
lastMessage = messages.lastOrNull()
|
||||
isLoading = false
|
||||
Log.d("ChatViewModel", "fetch history message success")
|
||||
Log.d("ChatAiViewModel", "fetch history message success")
|
||||
}
|
||||
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatAiViewModel", "fetch history message error: $error")
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage(message: String, context: Context) {
|
||||
V2TIMManager.getInstance().sendC2CTextMessage(
|
||||
message,
|
||||
userProfile?.trtcUserId!!,
|
||||
object : V2TIMSendCallback<V2TIMMessage> {
|
||||
override fun onProgress(p0: Int) {
|
||||
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("ChatAiViewModel", "OpenIM 未登录,无法发送消息")
|
||||
return
|
||||
}
|
||||
|
||||
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?) {
|
||||
Log.e("ChatViewModel", "send message error: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatAiViewModel", "send message error: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(p0: V2TIMMessage?) {
|
||||
Log.d("ChatViewModel", "send message success")
|
||||
sendChatAiMessage(myProfile?.trtcUserId!!,userProfile?.trtcUserId!!, message)
|
||||
createGroup2ChatAi(userProfile?.trtcUserId!!,"ai_group")
|
||||
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
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)
|
||||
override fun onSuccess(data: Message?) {
|
||||
Log.d("ChatAiViewModel", "send message success")
|
||||
sendChatAiMessage(myProfile?.trtcUserId!!, userProfile?.trtcUserId!!, message)
|
||||
createGroup2ChatAi(userProfile?.trtcUserId!!, "ai_group")
|
||||
data?.let { sentMessage ->
|
||||
val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
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? {
|
||||
@@ -264,30 +279,35 @@ class ChatAiViewModel(
|
||||
}
|
||||
|
||||
fun fetchHistoryMessage(context: Context) {
|
||||
V2TIMManager.getMessageManager().getC2CHistoryMessageList(
|
||||
userProfile?.trtcUserId!!,
|
||||
20,
|
||||
null,
|
||||
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||
chatData = (p0 ?: emptyList()).mapNotNull {
|
||||
val conversationID = "single_${userProfile?.trtcUserId!!}"
|
||||
|
||||
|
||||
OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
|
||||
object : OnBase<AdvancedMessage> {
|
||||
override fun onSuccess(data: AdvancedMessage?) {
|
||||
val messages = data?.messageList ?: emptyList()
|
||||
chatData = messages.mapNotNull {
|
||||
var avatar = userProfile?.avatar
|
||||
if (it.isSelf) {
|
||||
if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context,avatar)
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}
|
||||
if ((p0?.size ?: 0) < 20) {
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = p0?.lastOrNull()
|
||||
Log.d("ChatViewModel", "fetch history message success")
|
||||
lastMessage = messages.lastOrNull()
|
||||
Log.d("ChatAiViewModel", "fetch history message success")
|
||||
}
|
||||
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatAiViewModel", "fetch history message error: $error")
|
||||
}
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
fun sendChatAiMessage(
|
||||
|
||||
@@ -84,7 +84,7 @@ import com.aiosman.ravenow.ui.composables.MenuItem
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.utils.NetworkUtils
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import io.openim.android.sdk.enums.MessageType
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
@@ -379,18 +379,18 @@ fun ChatSelfItem(item: ChatItem) {
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
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))
|
||||
.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)
|
||||
vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
|
||||
horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
|
||||
)
|
||||
|
||||
) {
|
||||
when (item.messageType) {
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
|
||||
MessageType.TEXT -> {
|
||||
Text(
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
@@ -401,7 +401,7 @@ fun ChatSelfItem(item: ChatItem) {
|
||||
)
|
||||
}
|
||||
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
|
||||
MessageType.PICTURE -> {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.imageList[1].url,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -467,18 +467,18 @@ fun ChatOtherItem(item: ChatItem) {
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
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))
|
||||
.background(AppColors.background)
|
||||
.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)
|
||||
vertical = (if (item.messageType == MessageType.TEXT) 8.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) {
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
|
||||
MessageType.TEXT -> {
|
||||
Text(
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
@@ -489,7 +489,7 @@ fun ChatOtherItem(item: ChatItem) {
|
||||
)
|
||||
}
|
||||
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
|
||||
MessageType.PICTURE -> {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.imageList[1].url,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
@@ -18,12 +18,14 @@ import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.ChatItem
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener
|
||||
import com.tencent.imsdk.v2.V2TIMCallback
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import com.tencent.imsdk.v2.V2TIMSendCallback
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
// OpenIM SDK 导入
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.enums.MessageType
|
||||
import io.openim.android.sdk.enums.ViewType
|
||||
import io.openim.android.sdk.listener.OnAdvanceMsgListener
|
||||
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 java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -38,10 +40,10 @@ class ChatViewModel(
|
||||
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
val userService: UserService = UserServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var textMessageListener: V2TIMAdvancedMsgListener? = null
|
||||
var textMessageListener: OnAdvanceMsgListener? = null
|
||||
var hasMore by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var lastMessage: V2TIMMessage? = null
|
||||
var lastMessage: Message? = null
|
||||
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
|
||||
var chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||
var goToNew by mutableStateOf(false)
|
||||
@@ -62,9 +64,14 @@ class ChatViewModel(
|
||||
|
||||
|
||||
fun RegistListener(context: Context) {
|
||||
textMessageListener = object : V2TIMAdvancedMsgListener() {
|
||||
override fun onRecvNewMessage(msg: V2TIMMessage?) {
|
||||
super.onRecvNewMessage(msg)
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("ChatViewModel", "OpenIM 未登录,跳过注册消息监听器")
|
||||
return
|
||||
}
|
||||
|
||||
textMessageListener = object : OnAdvanceMsgListener {
|
||||
override fun onRecvNewMessage(msg: Message?) {
|
||||
msg?.let { message ->
|
||||
// 只处理当前聊天对象的消息
|
||||
val currentChatUserId = userProfile?.trtcUserId
|
||||
@@ -72,38 +79,41 @@ class ChatViewModel(
|
||||
|
||||
if (currentChatUserId != null && currentUserId != null) {
|
||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||
if ((message.userID == currentChatUserId || message.userID == currentUserId) &&
|
||||
message.sender != currentUserId) {
|
||||
if ((message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
||||
message.sendID != 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} 的消息,更新聊天列表")
|
||||
android.util.Log.i("ChatViewModel", "收到来自 ${message.sendID} 的消息,更新聊天列表")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
V2TIMManager.getMessageManager().addAdvancedMsgListener(textMessageListener);
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
||||
}
|
||||
|
||||
fun UnRegistListener() {
|
||||
V2TIMManager.getMessageManager().removeAdvancedMsgListener(textMessageListener);
|
||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
||||
textMessageListener = null
|
||||
}
|
||||
|
||||
fun clearUnRead() {
|
||||
val conversationID = "c2c_${userProfile?.trtcUserId}"
|
||||
V2TIMManager.getConversationManager()
|
||||
.cleanConversationUnreadMessageCount(conversationID, 0, 0, object : V2TIMCallback {
|
||||
override fun onSuccess() {
|
||||
Log.i("imsdk", "success")
|
||||
val conversationID = "single_${userProfile?.trtcUserId}"
|
||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
||||
conversationID,
|
||||
object : OnBase<String> {
|
||||
override fun onSuccess(data: String?) {
|
||||
Log.i("openim", "clear unread success")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String) {
|
||||
Log.i("imsdk", "failure, code:$code, desc:$desc")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.i("openim", "clear unread failure, code:$code, error:$error")
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -113,58 +123,75 @@ class ChatViewModel(
|
||||
}
|
||||
isLoading = true
|
||||
viewModelScope.launch {
|
||||
V2TIMManager.getMessageManager().getC2CHistoryMessageList(
|
||||
userProfile?.trtcUserId!!,
|
||||
20,
|
||||
lastMessage,
|
||||
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||
chatData = chatData + (p0 ?: emptyList()).map {
|
||||
val conversationID = "single_${userProfile?.trtcUserId!!}"
|
||||
|
||||
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
|
||||
if (it.isSelf) {
|
||||
if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context,avatar)
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}.filterNotNull()
|
||||
if ((p0?.size ?: 0) < 20) {
|
||||
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = p0?.lastOrNull()
|
||||
lastMessage = messages.lastOrNull()
|
||||
isLoading = false
|
||||
Log.d("ChatViewModel", "fetch history message success")
|
||||
}
|
||||
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $error")
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage(message: String, context: Context) {
|
||||
V2TIMManager.getInstance().sendC2CTextMessage(
|
||||
message,
|
||||
userProfile?.trtcUserId!!,
|
||||
object : V2TIMSendCallback<V2TIMMessage> {
|
||||
override fun onProgress(p0: Int) {
|
||||
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("ChatViewModel", "OpenIM 未登录,无法发送消息")
|
||||
return
|
||||
}
|
||||
|
||||
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?) {
|
||||
Log.e("ChatViewModel", "send message error: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatViewModel", "send message error: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(p0: V2TIMMessage?) {
|
||||
override fun onSuccess(data: Message?) {
|
||||
Log.d("ChatViewModel", "send message success")
|
||||
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
data?.let { sentMessage ->
|
||||
val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
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 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")
|
||||
val imageMessage = OpenIMClient.getInstance().messageManager.createImageMessageFromFullPath(imagePath)
|
||||
|
||||
OpenIMClient.getInstance().messageManager.sendMessage(
|
||||
object : OnMsgSendCallback {
|
||||
override fun onProgress(progress: Long) {
|
||||
Log.d("ChatViewModel", "send image message progress: $progress")
|
||||
}
|
||||
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("ChatViewModel", "send image message error: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
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")
|
||||
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
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? {
|
||||
@@ -235,30 +262,34 @@ class ChatViewModel(
|
||||
}
|
||||
|
||||
fun fetchHistoryMessage(context: Context) {
|
||||
V2TIMManager.getMessageManager().getC2CHistoryMessageList(
|
||||
userProfile?.trtcUserId!!,
|
||||
20,
|
||||
null,
|
||||
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||
chatData = (p0 ?: emptyList()).mapNotNull {
|
||||
val conversationID = "single_${userProfile?.trtcUserId!!}"
|
||||
|
||||
OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
|
||||
object : OnBase<AdvancedMessage> {
|
||||
override fun onSuccess(data: AdvancedMessage?) {
|
||||
val messages = data?.messageList ?: emptyList()
|
||||
chatData = messages.mapNotNull {
|
||||
var avatar = userProfile?.avatar
|
||||
if (it.isSelf) {
|
||||
if (it.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context,avatar)
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}
|
||||
if ((p0?.size ?: 0) < 20) {
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = p0?.lastOrNull()
|
||||
lastMessage = messages.lastOrNull()
|
||||
Log.d("ChatViewModel", "fetch history message success")
|
||||
}
|
||||
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $error")
|
||||
}
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
null,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,8 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.ui.navigateToGroupInfo
|
||||
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 java.util.UUID
|
||||
|
||||
@@ -376,18 +377,18 @@ fun GroupChatSelfItem(item: ChatItem) {
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
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))
|
||||
.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)
|
||||
vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
|
||||
horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
|
||||
)
|
||||
|
||||
) {
|
||||
when (item.messageType) {
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
|
||||
MessageType.TEXT -> {
|
||||
Text(
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
@@ -398,7 +399,7 @@ fun GroupChatSelfItem(item: ChatItem) {
|
||||
)
|
||||
}
|
||||
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
|
||||
MessageType.PICTURE -> {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.imageList[1].url,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -469,17 +470,17 @@ fun GroupChatOtherItem(item: ChatItem, showAvatarAndNickname: Boolean = true) {
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
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))
|
||||
.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)
|
||||
vertical = (if (item.messageType == MessageType.TEXT) 8.dp else 0.dp),
|
||||
horizontal = (if (item.messageType == MessageType.TEXT) 16.dp else 0.dp)
|
||||
)
|
||||
) {
|
||||
when (item.messageType) {
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
|
||||
MessageType.TEXT -> {
|
||||
Text(
|
||||
text = item.message,
|
||||
style = TextStyle(
|
||||
@@ -490,7 +491,7 @@ fun GroupChatOtherItem(item: ChatItem, showAvatarAndNickname: Boolean = true) {
|
||||
)
|
||||
}
|
||||
|
||||
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
|
||||
MessageType.PICTURE -> {
|
||||
CustomAsyncImage(
|
||||
imageUrl = item.imageList[1].url,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
@@ -22,12 +22,13 @@ 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
|
||||
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener
|
||||
import com.tencent.imsdk.v2.V2TIMCallback
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import com.tencent.imsdk.v2.V2TIMSendCallback
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
// OpenIM SDK 导入
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.enums.ViewType
|
||||
import io.openim.android.sdk.listener.OnAdvanceMsgListener
|
||||
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 java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -43,10 +44,10 @@ class GroupChatViewModel(
|
||||
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
val userService: UserService = UserServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var textMessageListener: V2TIMAdvancedMsgListener? = null
|
||||
var textMessageListener: OnAdvanceMsgListener? = null
|
||||
var hasMore by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var lastMessage: V2TIMMessage? = null
|
||||
var lastMessage: Message? = null
|
||||
val showTimestampMap = mutableMapOf<String, Boolean>()
|
||||
var goToNew by mutableStateOf(false)
|
||||
|
||||
@@ -91,10 +92,16 @@ class GroupChatViewModel(
|
||||
}
|
||||
|
||||
fun RegistListener(context: Context) {
|
||||
textMessageListener = object : V2TIMAdvancedMsgListener() {
|
||||
override fun onRecvNewMessage(msg: V2TIMMessage?) {
|
||||
super.onRecvNewMessage(msg)
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("GroupChatViewModel", "OpenIM 未登录,跳过注册消息监听器")
|
||||
return
|
||||
}
|
||||
|
||||
textMessageListener = object : OnAdvanceMsgListener {
|
||||
override fun onRecvNewMessage(msg: Message?) {
|
||||
msg?.let {
|
||||
// 检查是否是当前群聊的消息
|
||||
if (it.groupID == groupId) {
|
||||
val chatItem = ChatItem.convertToChatItem(msg, context, avatar = null)
|
||||
chatItem?.let {
|
||||
@@ -105,79 +112,98 @@ class GroupChatViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
V2TIMManager.getMessageManager().addAdvancedMsgListener(textMessageListener)
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
||||
}
|
||||
|
||||
fun UnRegistListener() {
|
||||
V2TIMManager.getMessageManager().removeAdvancedMsgListener(textMessageListener)
|
||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
||||
textMessageListener = null
|
||||
}
|
||||
|
||||
fun clearUnRead() {
|
||||
val conversationID = "group_${groupId}"
|
||||
V2TIMManager.getConversationManager()
|
||||
.cleanConversationUnreadMessageCount(conversationID, 0, 0, object : V2TIMCallback {
|
||||
override fun onSuccess() {
|
||||
Log.i("imsdk", "清除群聊未读消息成功")
|
||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
||||
conversationID,
|
||||
object : OnBase<String> {
|
||||
override fun onSuccess(data: String?) {
|
||||
Log.i("openim", "清除群聊未读消息成功")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String) {
|
||||
Log.i("imsdk", "清除群聊未读消息失败, code:$code, desc:$desc")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.i("openim", "清除群聊未读消息失败, code:$code, error:$error")
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadMore(context: Context) {
|
||||
if (!hasMore || isLoading) return
|
||||
isLoading = true
|
||||
viewModelScope.launch {
|
||||
V2TIMManager.getMessageManager().getGroupHistoryMessageList(
|
||||
groupId,
|
||||
20,
|
||||
lastMessage,
|
||||
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||
chatData = chatData + (p0 ?: emptyList()).map {
|
||||
val conversationID = "group_${groupId}"
|
||||
|
||||
OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
|
||||
object : OnBase<AdvancedMessage> {
|
||||
override fun onSuccess(data: AdvancedMessage?) {
|
||||
val messages = data?.messageList ?: emptyList()
|
||||
chatData = chatData + messages.map {
|
||||
ChatItem.convertToChatItem(it, context, avatar = null)
|
||||
}.filterNotNull()
|
||||
if ((p0?.size ?: 0) < 20) {
|
||||
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = p0?.lastOrNull()
|
||||
lastMessage = messages.lastOrNull()
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $error")
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage(message: String, context: Context) {
|
||||
val v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(message)
|
||||
V2TIMManager.getMessageManager().sendMessage(
|
||||
v2TIMMessage,
|
||||
null,
|
||||
groupId,
|
||||
V2TIMMessage.V2TIM_PRIORITY_NORMAL,
|
||||
false,
|
||||
null,
|
||||
object : V2TIMSendCallback<V2TIMMessage> {
|
||||
override fun onProgress(p0: Int) {}
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("GroupChatViewModel", "发送群聊消息失败: $p1")
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("GroupChatViewModel", "OpenIM 未登录,无法发送消息")
|
||||
return
|
||||
}
|
||||
|
||||
val textMessage = OpenIMClient.getInstance().messageManager.createTextMessage(message)
|
||||
|
||||
OpenIMClient.getInstance().messageManager.sendMessage(
|
||||
object : OnMsgSendCallback {
|
||||
override fun onProgress(progress: Long) {
|
||||
// 发送进度
|
||||
}
|
||||
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)
|
||||
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
data?.let { sentMessage ->
|
||||
val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
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 imagePath = tempFile?.path
|
||||
if (imagePath != null) {
|
||||
val v2TIMMessage = V2TIMManager.getMessageManager().createImageMessage(imagePath)
|
||||
V2TIMManager.getMessageManager().sendMessage(
|
||||
v2TIMMessage,
|
||||
groupId,
|
||||
null,
|
||||
V2TIMMessage.V2TIM_PRIORITY_NORMAL,
|
||||
false,
|
||||
null,
|
||||
object : V2TIMSendCallback<V2TIMMessage> {
|
||||
override fun onProgress(p0: Int) {}
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("GroupChatViewModel", "发送群聊图片消息失败: $p1")
|
||||
val imageMessage = OpenIMClient.getInstance().messageManager.createImageMessageFromFullPath(imagePath)
|
||||
|
||||
OpenIMClient.getInstance().messageManager.sendMessage(
|
||||
object : OnMsgSendCallback {
|
||||
override fun onProgress(progress: Long) {
|
||||
Log.d("GroupChatViewModel", "发送群聊图片消息进度: $progress")
|
||||
}
|
||||
override fun onSuccess(p0: V2TIMMessage?) {
|
||||
val chatItem = ChatItem.convertToChatItem(p0!!, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("GroupChatViewModel", "发送群聊图片消息失败: $error")
|
||||
}
|
||||
|
||||
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) {
|
||||
V2TIMManager.getMessageManager().getGroupHistoryMessageList(
|
||||
groupId,
|
||||
20,
|
||||
null,
|
||||
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||
chatData = (p0 ?: emptyList()).mapNotNull {
|
||||
val conversationID = "group_${groupId}"
|
||||
|
||||
OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
|
||||
object : OnBase<AdvancedMessage> {
|
||||
override fun onSuccess(data: AdvancedMessage?) {
|
||||
val messages = data?.messageList ?: emptyList()
|
||||
chatData = messages.mapNotNull {
|
||||
ChatItem.convertToChatItem(it, context, avatar = null)
|
||||
}
|
||||
if ((p0?.size ?: 0) < 20) {
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = p0?.lastOrNull()
|
||||
lastMessage = messages.lastOrNull()
|
||||
}
|
||||
|
||||
override fun onError(p0: Int, p1: String?) {
|
||||
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $p1")
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $error")
|
||||
}
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
null,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,8 @@ import com.aiosman.ravenow.exp.formatChatTime
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.navigateToChat
|
||||
import com.aiosman.ravenow.utils.TrtcHelper
|
||||
import com.tencent.imsdk.v2.V2TIMConversation
|
||||
import com.tencent.imsdk.v2.V2TIMConversationResult
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
// 临时兼容层 - TODO: 完成 OpenIM 迁移后删除
|
||||
import com.aiosman.ravenow.compat.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -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.navigateToChatAi
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.tencent.imsdk.v2.V2TIMConversationOperationResult
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
// OpenIM SDK 导入 - 会话分组功能在 OpenIM 中不支持,将直接跳转到聊天页面
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -82,23 +81,10 @@ object MineAgentViewModel : ViewModel() {
|
||||
navController: NavHostController,
|
||||
id: Int
|
||||
) {
|
||||
val conversationIDList = listOf("c2c_${trtcUserId}")
|
||||
|
||||
V2TIMManager.getConversationManager().createConversationGroup(
|
||||
groupName,
|
||||
conversationIDList,
|
||||
object : V2TIMValueCallback<List<V2TIMConversationOperationResult>> {
|
||||
override fun onSuccess(v2TIMConversationOperationResults: List<V2TIMConversationOperationResult>) {
|
||||
// 创建会话分组成功
|
||||
navController.navigateToChatAi(id.toString())
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String) {
|
||||
// 创建会话分组失败
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// TODO: OpenIM 不支持会话分组功能,直接跳转到聊天页面
|
||||
// OpenIM 不支持会话分组功能,直接跳转到聊天页面
|
||||
Log.d("MineAgentViewModel", "OpenIM 不支持会话分组,直接跳转到 AI 聊天页面")
|
||||
navController.navigateToChatAi(id.toString())
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,11 +23,10 @@ import com.aiosman.ravenow.exp.formatChatTime
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.navigateToChat
|
||||
import com.aiosman.ravenow.utils.TrtcHelper
|
||||
import com.tencent.imsdk.v2.V2TIMConversation
|
||||
import com.tencent.imsdk.v2.V2TIMConversationResult
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
// OpenIM SDK 导入
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.listener.OnBase
|
||||
import io.openim.android.sdk.models.ConversationInfo
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
@@ -44,33 +43,10 @@ data class Conversation(
|
||||
val isSelf: Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun convertToConversation(msg: V2TIMConversation, context: Context): Conversation {
|
||||
val lastMessage = Calendar.getInstance().apply {
|
||||
timeInMillis = msg.lastMessage?.timestamp ?: 0
|
||||
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
|
||||
)
|
||||
}
|
||||
// TODO: 如果需要使用 Conversation 数据类,可以实现 OpenIM 版本的 convertToConversation 方法
|
||||
// fun convertToConversation(conversation: ConversationInfo, context: Context): Conversation {
|
||||
// // OpenIM 版本的转换逻辑
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,25 +127,31 @@ object MessageListViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
suspend fun loadChatList(context: Context) {
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("MessageListViewModel", "OpenIM 未登录,跳过加载聊天列表")
|
||||
return
|
||||
}
|
||||
|
||||
val result = suspendCoroutine { continuation ->
|
||||
V2TIMManager.getConversationManager().getConversationList(
|
||||
0,
|
||||
Int.MAX_VALUE,
|
||||
object : V2TIMValueCallback<V2TIMConversationResult> {
|
||||
override fun onSuccess(t: V2TIMConversationResult?) {
|
||||
continuation.resumeWith(Result.success(t))
|
||||
// OpenIM 获取所有会话列表
|
||||
OpenIMClient.getInstance().conversationManager.getAllConversationList(
|
||||
object : OnBase<List<ConversationInfo>> {
|
||||
override fun onSuccess(data: List<ConversationInfo>?) {
|
||||
continuation.resumeWith(Result.success(data ?: emptyList()))
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
|
||||
override fun onError(code: Int, error: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/* chatList = result?.conversationList?.map { msg: V2TIMConversation ->
|
||||
Conversation.convertToConversation(msg, context)
|
||||
} ?: emptyList()*/
|
||||
// 暂时注释掉,因为 convertToConversation 方法已被注释
|
||||
// chatList = result.map { conversation ->
|
||||
// Conversation.convertToConversation(conversation, context)
|
||||
// }
|
||||
}
|
||||
|
||||
suspend fun loadUnreadCount() {
|
||||
|
||||
@@ -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.message.MessageListViewModel
|
||||
import com.aiosman.ravenow.ui.navigateToChatAi
|
||||
import com.tencent.imsdk.v2.V2TIMConversation
|
||||
import com.tencent.imsdk.v2.V2TIMConversationListFilter
|
||||
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 io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.listener.OnBase
|
||||
import io.openim.android.sdk.models.ConversationInfo
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -42,42 +39,43 @@ data class AgentConversation(
|
||||
val isSelf: Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun convertToAgentConversation(msg: V2TIMConversation, context: Context): AgentConversation {
|
||||
fun convertToAgentConversation(conversation: ConversationInfo, context: Context): AgentConversation {
|
||||
val lastMessage = Calendar.getInstance().apply {
|
||||
timeInMillis = msg.lastMessage?.timestamp ?: 0
|
||||
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 = "[消息]"
|
||||
}
|
||||
timeInMillis = conversation.latestMsgSendTime
|
||||
}
|
||||
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(
|
||||
id = msg.conversationID,
|
||||
nickname = msg.showName,
|
||||
lastMessage = msg.lastMessage?.textElem?.text ?: "",
|
||||
id = conversation.conversationID,
|
||||
nickname = conversation.showName ?: "",
|
||||
lastMessage = conversation.latestMsg ?: "",
|
||||
lastMessageTime = lastMessage.time.formatChatTime(context),
|
||||
avatar = "${ApiClient.BASE_API_URL+"/"}${msg.faceUrl}"+"?token="+"${AppStore.token}".replace("storage/avatars/", "/avatar/"),
|
||||
unreadCount = msg.unreadCount,
|
||||
trtcUserId = msg.userID,
|
||||
avatar = "${ApiClient.BASE_API_URL+"/"}${conversation.faceURL}"+"?token="+"${AppStore.token}".replace("storage/avatars/", "/avatar/"),
|
||||
unreadCount = conversation.unreadCount,
|
||||
trtcUserId = conversation.userID ?: "",
|
||||
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) {
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("AgentChatListViewModel", "OpenIM 未登录,跳过加载智能体聊天列表")
|
||||
return
|
||||
}
|
||||
|
||||
val result = suspendCoroutine { continuation ->
|
||||
val filter = V2TIMConversationListFilter()
|
||||
filter.conversationGroup = "ai_group"
|
||||
filter.conversationType = V2TIMConversation.V2TIM_C2C
|
||||
V2TIMManager.getConversationManager().getConversationListByFilter(
|
||||
filter,
|
||||
0,
|
||||
Int.MAX_VALUE,
|
||||
object : V2TIMValueCallback<V2TIMConversationResult> {
|
||||
override fun onSuccess(t: V2TIMConversationResult?) {
|
||||
continuation.resumeWith(Result.success(t))
|
||||
// OpenIM 获取所有会话列表
|
||||
OpenIMClient.getInstance().conversationManager.getAllConversationList(
|
||||
object : OnBase<List<ConversationInfo>> {
|
||||
override fun onSuccess(data: List<ConversationInfo>?) {
|
||||
// 过滤出智能体会话(单聊类型,且可能有特定标识)
|
||||
val agentConversations = data?.filter { conversation ->
|
||||
// 这里需要根据实际业务逻辑来过滤智能体会话
|
||||
// 可能通过会话类型、用户ID前缀、或其他标识来判断
|
||||
conversation.conversationType == 1 // 1 表示单聊
|
||||
// 可以添加更多过滤条件,比如:
|
||||
// && conversation.userID?.startsWith("ai_") == true
|
||||
} ?: emptyList()
|
||||
continuation.resumeWith(Result.success(agentConversations))
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
|
||||
override fun onError(code: Int, error: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
agentChatList = result?.conversationList?.map { msg: V2TIMConversation ->
|
||||
AgentConversation.convertToAgentConversation(msg, context)
|
||||
} ?: emptyList()
|
||||
agentChatList = result.map { conversation ->
|
||||
AgentConversation.convertToAgentConversation(conversation, context)
|
||||
}
|
||||
}
|
||||
|
||||
fun goToChatAi(
|
||||
|
||||
@@ -15,12 +15,12 @@ import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.exp.formatChatTime
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.navigateToChat
|
||||
import com.tencent.imsdk.v2.V2TIMConversation
|
||||
import com.tencent.imsdk.v2.V2TIMConversationListFilter
|
||||
import com.tencent.imsdk.v2.V2TIMConversationResult
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMMessage
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
// OpenIM SDK 导入
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.listener.OnBase
|
||||
import io.openim.android.sdk.models.ConversationInfo
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -36,42 +36,21 @@ data class FriendConversation(
|
||||
val isSelf: Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun convertToFriendConversation(msg: V2TIMConversation, context: Context): FriendConversation {
|
||||
fun convertToFriendConversation(conversation: ConversationInfo, context: Context): FriendConversation {
|
||||
val lastMessage = Calendar.getInstance().apply {
|
||||
timeInMillis = msg.lastMessage?.timestamp ?: 0
|
||||
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 = "[消息]"
|
||||
}
|
||||
timeInMillis = conversation.latestMsgSendTime
|
||||
}
|
||||
var displayText = conversation.latestMsg
|
||||
return FriendConversation(
|
||||
id = msg.conversationID,
|
||||
nickname = msg.showName,
|
||||
lastMessage = msg.lastMessage?.textElem?.text ?: "",
|
||||
id = conversation.conversationID,
|
||||
nickname = conversation.showName ?: "",
|
||||
lastMessage = conversation.latestMsg ?: "",
|
||||
lastMessageTime = lastMessage.time.formatChatTime(context),
|
||||
avatar = "${ConstVars.BASE_SERVER}${msg.faceUrl}",
|
||||
unreadCount = msg.unreadCount,
|
||||
trtcUserId = msg.userID,
|
||||
avatar = "${ApiClient.BASE_API_URL+"/"}${conversation.faceURL}"+"?token="+"${AppStore.token}".replace("storage/avatars/", "/avatar/"),
|
||||
unreadCount = conversation.unreadCount,
|
||||
trtcUserId = conversation.userID ?: "",
|
||||
displayText = displayText,
|
||||
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId
|
||||
isSelf = false
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -130,34 +109,38 @@ object FriendChatListViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private suspend fun loadFriendChatList(context: Context) {
|
||||
val filter = V2TIMConversationListFilter()
|
||||
filter.conversationType = V2TIMConversation.V2TIM_C2C
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("FriendChatListViewModel", "OpenIM 未登录,跳过加载朋友聊天列表")
|
||||
return
|
||||
}
|
||||
|
||||
val result = suspendCoroutine { continuation ->
|
||||
// 获取全部会话列表
|
||||
V2TIMManager.getConversationManager().getConversationListByFilter(
|
||||
filter,
|
||||
0,
|
||||
Int.MAX_VALUE,
|
||||
object : V2TIMValueCallback<V2TIMConversationResult> {
|
||||
override fun onSuccess(t: V2TIMConversationResult?) {
|
||||
continuation.resumeWith(Result.success(t))
|
||||
// OpenIM 获取所有会话列表
|
||||
OpenIMClient.getInstance().conversationManager.getAllConversationList(
|
||||
object : OnBase<List<ConversationInfo>> {
|
||||
override fun onSuccess(data: List<ConversationInfo>?) {
|
||||
continuation.resumeWith(Result.success(data ?: emptyList()))
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
|
||||
override fun onError(code: Int, error: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 过滤掉ai_group的会话,只保留朋友聊天
|
||||
val filteredConversations = result?.conversationList?.filter { conversation ->
|
||||
// 排除ai_group的会话
|
||||
!conversation.conversationGroupList.contains("ai_group")&& conversation.type == V2TIMConversation.V2TIM_C2C
|
||||
} ?: emptyList()
|
||||
// 过滤出朋友会话(单聊类型,且排除 AI 智能体)
|
||||
val filteredConversations = result.filter { conversation ->
|
||||
// 1 表示单聊,排除 AI 智能体会话
|
||||
conversation.conversationType == 1 &&
|
||||
// 可以根据实际业务逻辑添加更多过滤条件
|
||||
// 比如排除 AI 智能体的 userID 前缀或标识
|
||||
!(conversation.userID?.startsWith("ai_") == true)
|
||||
}
|
||||
|
||||
friendChatList = filteredConversations.map { msg: V2TIMConversation ->
|
||||
FriendConversation.convertToFriendConversation(msg, context)
|
||||
friendChatList = filteredConversations.map { conversation ->
|
||||
FriendConversation.convertToFriendConversation(conversation, context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,12 @@ import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.navigateToChat
|
||||
import com.aiosman.ravenow.ui.navigateToGroupChat
|
||||
import com.aiosman.ravenow.ui.navigateToGroupInfo
|
||||
import com.tencent.imsdk.v2.V2TIMConversation
|
||||
import com.tencent.imsdk.v2.V2TIMConversationListFilter
|
||||
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
|
||||
// OpenIM SDK 导入
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.listener.OnAdvanceMsgListener
|
||||
import io.openim.android.sdk.listener.OnBase
|
||||
import io.openim.android.sdk.listener.OnConversationListener
|
||||
import io.openim.android.sdk.models.ConversationInfo
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -44,52 +42,32 @@ data class GroupConversation(
|
||||
val memberCount: Int = 0
|
||||
) {
|
||||
companion object {
|
||||
fun convertToGroupConversation(msg: V2TIMConversation, context: Context): GroupConversation {
|
||||
fun convertToGroupConversation(conversation: ConversationInfo, context: Context): GroupConversation {
|
||||
val lastMessage = Calendar.getInstance().apply {
|
||||
timeInMillis = msg.lastMessage?.timestamp ?: 0
|
||||
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 = "[消息]"
|
||||
}
|
||||
timeInMillis = conversation.latestMsgSendTime
|
||||
}
|
||||
|
||||
return GroupConversation(
|
||||
id = msg.conversationID,
|
||||
groupId = msg.groupID,
|
||||
groupName = msg.showName,
|
||||
lastMessage = msg.lastMessage?.textElem?.text ?: "",
|
||||
id = conversation.conversationID,
|
||||
groupId = conversation.groupID ?: "",
|
||||
groupName = conversation.showName ?: "",
|
||||
lastMessage = conversation.latestMsg?: "",
|
||||
lastMessageTime = lastMessage.time.formatChatTime(context),
|
||||
avatar = if (msg.faceUrl.isNullOrEmpty()) {
|
||||
avatar = if (conversation.faceURL.isNullOrEmpty()) {
|
||||
// 将 groupId 转换为 Base64
|
||||
val groupIdBase64 = android.util.Base64.encodeToString(
|
||||
msg.groupID.toByteArray(),
|
||||
(conversation.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}"
|
||||
"${ApiClient.BASE_API_URL+"/outside/rooms/avatar/"}${conversation.faceURL}"+"?token="+"${AppStore.token}"
|
||||
},
|
||||
unreadCount = msg.unreadCount,
|
||||
displayText = displayText,
|
||||
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId,
|
||||
memberCount = msg.groupAtInfoList?.size ?: 0
|
||||
unreadCount = conversation.unreadCount,
|
||||
displayText = conversation.latestMsg?: "",
|
||||
isSelf = false,
|
||||
// TODO openim get grouplist
|
||||
memberCount = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -111,8 +89,8 @@ object GroupChatListViewModel : ViewModel() {
|
||||
private val pageSize = 20
|
||||
|
||||
// 消息监听器
|
||||
private var messageListener: V2TIMAdvancedMsgListener? = null
|
||||
private var conversationListener: V2TIMConversationListener? = null
|
||||
private var messageListener: OnAdvanceMsgListener? = null
|
||||
private var conversationListener: OnConversationListener? = null
|
||||
|
||||
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {
|
||||
if (isLoading && !pullRefresh) return
|
||||
@@ -152,34 +130,35 @@ object GroupChatListViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private suspend fun loadGroupChatList(context: Context) {
|
||||
val filter = V2TIMConversationListFilter()
|
||||
filter.conversationType = V2TIMConversation.V2TIM_GROUP
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("GroupChatListViewModel", "OpenIM 未登录,跳过加载群聊列表")
|
||||
return
|
||||
}
|
||||
|
||||
val result = suspendCoroutine { continuation ->
|
||||
// 获取全部会话列表
|
||||
V2TIMManager.getConversationManager().getConversationListByFilter(
|
||||
filter,
|
||||
0,
|
||||
Int.MAX_VALUE,
|
||||
object : V2TIMValueCallback<V2TIMConversationResult> {
|
||||
override fun onSuccess(t: V2TIMConversationResult?) {
|
||||
continuation.resumeWith(Result.success(t))
|
||||
// OpenIM 获取所有会话列表
|
||||
OpenIMClient.getInstance().conversationManager.getAllConversationList(
|
||||
object : OnBase<List<ConversationInfo>> {
|
||||
override fun onSuccess(data: List<ConversationInfo>?) {
|
||||
continuation.resumeWith(Result.success(data ?: emptyList()))
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
|
||||
override fun onError(code: Int, error: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 只保留群聊会话,过滤掉单聊会话
|
||||
val filteredConversations = result?.conversationList?.filter { conversation ->
|
||||
// 只保留群聊会话(conversationType为2表示群聊)
|
||||
conversation.type == V2TIMConversation.V2TIM_GROUP
|
||||
} ?: emptyList()
|
||||
// 过滤出群聊会话(群聊类型)
|
||||
val filteredConversations = result.filter { conversation ->
|
||||
// 2 表示群聊类型
|
||||
conversation.conversationType == 2
|
||||
}
|
||||
|
||||
groupChatList = filteredConversations.map { msg: V2TIMConversation ->
|
||||
GroupConversation.convertToGroupConversation(msg, context)
|
||||
groupChatList = filteredConversations.map { conversation ->
|
||||
GroupConversation.convertToGroupConversation(conversation, context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,11 +211,10 @@ object GroupChatListViewModel : ViewModel() {
|
||||
// 初始化消息监听器
|
||||
fun initMessageListener(context: Context) {
|
||||
// 消息监听器 - 监听新消息
|
||||
messageListener = object : V2TIMAdvancedMsgListener() {
|
||||
override fun onRecvNewMessage(msg: V2TIMMessage?) {
|
||||
super.onRecvNewMessage(msg)
|
||||
messageListener = object : OnAdvanceMsgListener {
|
||||
override fun onRecvNewMessage(msg: io.openim.android.sdk.models.Message?) {
|
||||
msg?.let { message ->
|
||||
if (message.groupID != null && message.groupID.isNotEmpty()) {
|
||||
if (!message.groupID.isNullOrEmpty()) {
|
||||
// 收到群聊消息,刷新群聊列表
|
||||
android.util.Log.i("GroupChatList", "收到群聊消息,刷新列表")
|
||||
refreshGroupChatList(context)
|
||||
@@ -246,12 +224,11 @@ object GroupChatListViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
// 会话监听器 - 监听会话变化
|
||||
conversationListener = object : V2TIMConversationListener() {
|
||||
override fun onConversationChanged(conversationList: MutableList<V2TIMConversation>?) {
|
||||
super.onConversationChanged(conversationList)
|
||||
conversationListener = object : OnConversationListener {
|
||||
override fun onConversationChanged(conversationList: List<ConversationInfo>?) {
|
||||
// 会话发生变化,刷新群聊列表
|
||||
conversationList?.let { conversations ->
|
||||
val hasGroupConversation = conversations.any { it.type == V2TIMConversation.V2TIM_GROUP }
|
||||
val hasGroupConversation = conversations.any { it.conversationType == 2 }
|
||||
if (hasGroupConversation) {
|
||||
android.util.Log.i("GroupChatList", "群聊会话发生变化,刷新列表")
|
||||
refreshGroupChatList(context)
|
||||
@@ -259,11 +236,10 @@ object GroupChatListViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewConversation(conversationList: MutableList<V2TIMConversation>?) {
|
||||
super.onNewConversation(conversationList)
|
||||
override fun onNewConversation(conversationList: List<ConversationInfo>?) {
|
||||
// 新增会话,刷新群聊列表
|
||||
conversationList?.let { conversations ->
|
||||
val hasGroupConversation = conversations.any { it.type == V2TIMConversation.V2TIM_GROUP }
|
||||
val hasGroupConversation = conversations.any { it.conversationType == 2 }
|
||||
if (hasGroupConversation) {
|
||||
android.util.Log.i("GroupChatList", "新增群聊会话,刷新列表")
|
||||
refreshGroupChatList(context)
|
||||
@@ -273,18 +249,13 @@ object GroupChatListViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
// 注册监听器
|
||||
V2TIMManager.getMessageManager().addAdvancedMsgListener(messageListener)
|
||||
V2TIMManager.getConversationManager().addConversationListener(conversationListener)
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(messageListener)
|
||||
OpenIMClient.getInstance().conversationManager.setOnConversationListener(conversationListener)
|
||||
}
|
||||
|
||||
// 移除消息监听器
|
||||
fun removeMessageListener() {
|
||||
messageListener?.let {
|
||||
V2TIMManager.getMessageManager().removeAdvancedMsgListener(it)
|
||||
}
|
||||
conversationListener?.let {
|
||||
V2TIMManager.getConversationManager().removeConversationListener(it)
|
||||
}
|
||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
||||
messageListener = null
|
||||
conversationListener = null
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package com.aiosman.ravenow.utils
|
||||
|
||||
import com.tencent.imsdk.v2.V2TIMCallback
|
||||
import com.tencent.imsdk.v2.V2TIMManager
|
||||
import com.tencent.imsdk.v2.V2TIMUserFullInfo
|
||||
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.listener.OnBase
|
||||
import io.openim.android.sdk.models.UserInfo
|
||||
import io.openim.android.sdk.models.UserInfoReq
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object TrtcHelper {
|
||||
suspend fun loadUnreadCount(): Long {
|
||||
return suspendCoroutine { continuation ->
|
||||
V2TIMManager.getConversationManager()
|
||||
.getTotalUnreadMessageCount(object : V2TIMValueCallback<Long> {
|
||||
override fun onSuccess(t: Long?) {
|
||||
continuation.resumeWith(Result.success(t ?: 0))
|
||||
OpenIMClient.getInstance().conversationManager
|
||||
.getTotalUnreadMsgCount(object : OnBase<String> {
|
||||
override fun onSuccess(data: String?) {
|
||||
// OpenIM 返回的是字符串格式的数字
|
||||
val count = data?.toLongOrNull() ?: 0L
|
||||
continuation.resumeWith(Result.success(count))
|
||||
}
|
||||
|
||||
override fun onError(code: Int, desc: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
|
||||
override fun onError(code: Int, error: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $error")))
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,19 +28,20 @@ object TrtcHelper {
|
||||
avatar: String?,
|
||||
nickName: String?
|
||||
) {
|
||||
val info = V2TIMUserFullInfo()
|
||||
nickName?.let { info.setNickname(it) }
|
||||
avatar?.let { info.faceUrl = it }
|
||||
val infoReq = UserInfoReq()
|
||||
nickName?.let { infoReq.nickname = it }
|
||||
avatar?.let { infoReq.faceURL = it }
|
||||
return suspendCoroutine { continuation ->
|
||||
V2TIMManager.getInstance().setSelfInfo(info, object : V2TIMCallback {
|
||||
override fun onError(code: Int, desc: String?) {
|
||||
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
|
||||
//(OnBase<String> base, UserInfoReq userInfoReq
|
||||
OpenIMClient.getInstance().userInfoManager.setSelfInfo(object : OnBase<String> {
|
||||
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))
|
||||
}
|
||||
})
|
||||
},infoReq)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ eventbus = "3.3.1"
|
||||
firebaseBom = "33.2.0"
|
||||
gson = "2.12.1"
|
||||
imagecropview = "3.0.1"
|
||||
imsdkPlus = "8.1.6116"
|
||||
jpushGoogle = "5.4.0"
|
||||
jwtdecode = "2.0.2"
|
||||
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" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
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" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
|
||||
Reference in New Issue
Block a user