初步替换IM接口
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -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
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.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)
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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.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")
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
|
||||||
// 创建会话分组失败
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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" }
|
||||||
|
|||||||
Reference in New Issue
Block a user