初步替换IM接口

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,11 +23,8 @@ import com.aiosman.ravenow.exp.formatChatTime
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.navigateToChat
import com.aiosman.ravenow.utils.TrtcHelper
import com.tencent.imsdk.v2.V2TIMConversation
import com.tencent.imsdk.v2.V2TIMConversationResult
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMMessage
import com.tencent.imsdk.v2.V2TIMValueCallback
// 临时兼容层 - TODO: 完成 OpenIM 迁移后删除
import com.aiosman.ravenow.compat.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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