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