初步替换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

@@ -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
}