重构IM viewmodel代码
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
package com.aiosman.ravenow.ui.chat
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.data.AccountService
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.ChatItem
|
||||
import io.openim.android.sdk.OpenIMClient
|
||||
import io.openim.android.sdk.enums.ConversationType
|
||||
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
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* 聊天ViewModel基类,包含所有聊天功能的通用实现
|
||||
* 子类需要实现抽象方法来处理特定的聊天类型(单聊/群聊)
|
||||
*/
|
||||
abstract class BaseChatViewModel : ViewModel() {
|
||||
|
||||
// 通用状态属性
|
||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
||||
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var hasMore by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var lastMessage: Message? = null
|
||||
val showTimestampMap = mutableMapOf<String, Boolean>()
|
||||
var goToNew by mutableStateOf(false)
|
||||
var conversationID: String = "" // 会话ID,通过getOneConversation初始化
|
||||
|
||||
// 通用服务
|
||||
val userService: UserService = UserServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var textMessageListener: OnAdvanceMsgListener? = null
|
||||
|
||||
val fetchHistorySize = 20
|
||||
|
||||
/**
|
||||
* 初始化方法,子类需要实现具体的初始化逻辑
|
||||
*/
|
||||
abstract fun init(context: Context)
|
||||
|
||||
/**
|
||||
* 获取日志标签,子类需要实现
|
||||
*/
|
||||
abstract fun getLogTag(): String
|
||||
|
||||
/**
|
||||
* 获取会话参数,子类需要实现
|
||||
* @return Triple(targetId, conversationType, isSingleChat)
|
||||
*/
|
||||
abstract fun getConversationParams(): Triple<String, Int, Boolean>
|
||||
|
||||
/**
|
||||
* 处理接收到的新消息,子类可以重写以添加特定逻辑
|
||||
*/
|
||||
open fun handleNewMessage(message: Message, context: Context): Boolean {
|
||||
return false // 默认不处理,子类重写
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取发送消息时的接收者ID,子类需要实现
|
||||
*/
|
||||
abstract fun getReceiverInfo(): Pair<String?, String?> // (recvID, groupID)
|
||||
|
||||
/**
|
||||
* 发送消息成功后的额外处理,子类可以重写
|
||||
*/
|
||||
open fun onMessageSentSuccess(message: String, sentMessage: Message?) {
|
||||
// 默认无额外处理,子类可以重写
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话信息并初始化conversationID
|
||||
*/
|
||||
fun getOneConversation(onSuccess: (() -> Unit)? = null) {
|
||||
val (targetId, conversationType, isSingleChat) = getConversationParams()
|
||||
|
||||
OpenIMClient.getInstance().conversationManager.getOneConversation(
|
||||
object : OnBase<ConversationInfo> {
|
||||
override fun onError(code: Int, error: String) {
|
||||
Log.e(getLogTag(), "getOneConversation error: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: ConversationInfo) {
|
||||
conversationID = data.conversationID
|
||||
Log.d(getLogTag(), "获取会话信息成功,conversationID: $conversationID")
|
||||
onSuccess?.invoke()
|
||||
}
|
||||
},
|
||||
targetId,
|
||||
conversationType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册消息监听器
|
||||
*/
|
||||
fun RegistListener(context: Context) {
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
Log.w(getLogTag(), "OpenIM 未登录,跳过注册消息监听器")
|
||||
return
|
||||
}
|
||||
|
||||
textMessageListener = object : OnAdvanceMsgListener {
|
||||
override fun onRecvNewMessage(msg: Message?) {
|
||||
msg?.let { message ->
|
||||
if (handleNewMessage(message, context)) {
|
||||
val chatItem = ChatItem.convertToChatItem(message, context, avatar = getMessageAvatar(message))
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
Log.i(getLogTag(), "收到来自 ${message.sendID} 的消息,更新聊天列表")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息头像,子类可以重写
|
||||
*/
|
||||
open fun getMessageAvatar(message: Message): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册消息监听器
|
||||
*/
|
||||
fun UnRegistListener() {
|
||||
textMessageListener = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除未读消息
|
||||
*/
|
||||
fun clearUnRead() {
|
||||
if (conversationID.isEmpty()) {
|
||||
Log.w(getLogTag(), "conversationID为空,无法清除未读消息")
|
||||
return
|
||||
}
|
||||
|
||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
||||
conversationID,
|
||||
object : OnBase<String> {
|
||||
override fun onSuccess(data: String?) {
|
||||
Log.i("openim", "清除未读消息成功")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.i("openim", "清除未读消息失败, code:$code, error:$error")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载更多历史消息
|
||||
*/
|
||||
fun onLoadMore(context: Context) {
|
||||
if (!hasMore || isLoading) {
|
||||
return
|
||||
}
|
||||
loadHistoryMessages(context, isLoadMore = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文本消息
|
||||
*/
|
||||
fun sendMessage(message: String, context: Context) {
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
Log.w(getLogTag(), "OpenIM 未登录,无法发送消息")
|
||||
return
|
||||
}
|
||||
|
||||
val textMessage = OpenIMClient.getInstance().messageManager.createTextMessage(message)
|
||||
val (recvID, groupID) = getReceiverInfo()
|
||||
|
||||
OpenIMClient.getInstance().messageManager.sendMessage(
|
||||
object : OnMsgSendCallback {
|
||||
override fun onProgress(progress: Long) {
|
||||
// 发送进度
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e(getLogTag(), "发送消息失败: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Message?) {
|
||||
Log.d(getLogTag(), "发送消息成功")
|
||||
onMessageSentSuccess(message, data)
|
||||
data?.let { sentMessage ->
|
||||
val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
textMessage,
|
||||
recvID,
|
||||
groupID,
|
||||
OfflinePushInfo()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送图片消息
|
||||
*/
|
||||
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)
|
||||
val (recvID, groupID) = getReceiverInfo()
|
||||
|
||||
OpenIMClient.getInstance().messageManager.sendMessage(
|
||||
object : OnMsgSendCallback {
|
||||
override fun onProgress(progress: Long) {
|
||||
Log.d(getLogTag(), "发送图片消息进度: $progress")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e(getLogTag(), "发送图片消息失败: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Message?) {
|
||||
Log.d(getLogTag(), "发送图片消息成功")
|
||||
data?.let { sentMessage ->
|
||||
val chatItem = ChatItem.convertToChatItem(sentMessage, context, avatar = myProfile?.avatar)
|
||||
chatItem?.let {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
imageMessage,
|
||||
recvID,
|
||||
groupID,
|
||||
OfflinePushInfo()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时文件
|
||||
*/
|
||||
fun createTempFile(context: Context, uri: Uri): File? {
|
||||
return try {
|
||||
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||
val cursor = context.contentResolver.query(uri, projection, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
val filePath = it.getString(columnIndex)
|
||||
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
|
||||
val tempFile =
|
||||
File.createTempFile("temp_image", ".$extension", context.cacheDir)
|
||||
val outputStream = FileOutputStream(tempFile)
|
||||
|
||||
inputStream?.use { input ->
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
tempFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史消息
|
||||
*/
|
||||
fun fetchHistoryMessage(context: Context) {
|
||||
loadHistoryMessages(context, isLoadMore = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载历史消息的通用方法
|
||||
* @param context 上下文
|
||||
* @param isLoadMore 是否是加载更多(true:追加到现有数据,false:替换现有数据)
|
||||
*/
|
||||
private fun loadHistoryMessages(context: Context, isLoadMore: Boolean) {
|
||||
if (conversationID.isEmpty()) {
|
||||
Log.w(getLogTag(), "conversationID为空,无法${if (isLoadMore) "加载更多" else "获取"}历史消息")
|
||||
return
|
||||
}
|
||||
|
||||
if (isLoadMore) {
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
OpenIMClient.getInstance().messageManager.getAdvancedHistoryMessageList(
|
||||
object : OnBase<AdvancedMessage> {
|
||||
override fun onSuccess(data: AdvancedMessage?) {
|
||||
val messages = data?.messageList ?: emptyList()
|
||||
val newChatItems = messages.mapNotNull {
|
||||
ChatItem.convertToChatItem(it, context, avatar = getMessageAvatar(it))
|
||||
}.reversed() // 反转顺序,使最新消息在前面
|
||||
|
||||
// 根据是否是加载更多来决定数据处理方式
|
||||
chatData = if (isLoadMore) {
|
||||
chatData + newChatItems // 追加到现有数据
|
||||
} else {
|
||||
newChatItems // 替换现有数据
|
||||
}
|
||||
|
||||
if (messages.size < fetchHistorySize) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = messages.firstOrNull()
|
||||
|
||||
if (isLoadMore) {
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
Log.d(getLogTag(), "${if (isLoadMore) "加载更多" else "获取"}历史消息成功")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e(getLogTag(), "${if (isLoadMore) "加载更多" else "获取"}历史消息失败: $error")
|
||||
if (isLoadMore) {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
if (isLoadMore) lastMessage else null, // 首次加载不传lastMessage
|
||||
fetchHistorySize,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取显示的聊天列表
|
||||
*/
|
||||
fun getDisplayChatList(): List<ChatItem> {
|
||||
val list = chatData
|
||||
// 更新每条消息的时间戳显示状态
|
||||
for (item in list) {
|
||||
item.showTimestamp = showTimestampMap.getOrDefault(item.msgId, false)
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,27 @@
|
||||
package com.aiosman.ravenow.ui.chat
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavHostController
|
||||
import com.aiosman.ravenow.ChatState
|
||||
import com.aiosman.ravenow.data.AccountService
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.SendChatAiRequestBody
|
||||
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.aiosman.ravenow.ui.navigateToChatAi
|
||||
// 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.enums.ConversationType
|
||||
import io.openim.android.sdk.models.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
class ChatAiViewModel(
|
||||
val userId: String,
|
||||
) : ViewModel() {
|
||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
||||
) : BaseChatViewModel() {
|
||||
var userProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
val userService: UserService = UserServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var textMessageListener: OnAdvanceMsgListener? = null
|
||||
var hasMore by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var lastMessage: Message? = null
|
||||
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
|
||||
var chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||
var goToNew by mutableStateOf(false)
|
||||
fun init(context: Context) {
|
||||
override fun init(context: Context) {
|
||||
// 获取用户信息
|
||||
viewModelScope.launch {
|
||||
val resp = userService.getUserProfile(userId)
|
||||
@@ -59,150 +29,55 @@ class ChatAiViewModel(
|
||||
myProfile = accountService.getMyAccountProfile()
|
||||
|
||||
RegistListener(context)
|
||||
fetchHistoryMessage(context)
|
||||
|
||||
// 获取会话信息,然后加载历史消息
|
||||
getOneConversation {
|
||||
fetchHistoryMessage(context)
|
||||
}
|
||||
|
||||
// 获取通知信息
|
||||
val notiStrategy = ChatState.getStrategyByTargetTrtcId(resp.trtcUserId)
|
||||
chatNotification = notiStrategy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun RegistListener(context: Context) {
|
||||
// 检查 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
|
||||
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
||||
|
||||
if (currentChatUserId != null && currentUserId != null) {
|
||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||
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.sendID} 的消息,更新AI聊天列表")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
||||
override fun getConversationParams(): Triple<String, Int, Boolean> {
|
||||
return Triple(userProfile?.trtcUserId ?: userId, ConversationType.SINGLE_CHAT, true)
|
||||
}
|
||||
|
||||
fun UnRegistListener() {
|
||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
||||
textMessageListener = null
|
||||
override fun getLogTag(): String {
|
||||
return "ChatAiViewModel"
|
||||
}
|
||||
|
||||
fun clearUnRead() {
|
||||
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 handleNewMessage(message: Message, context: Context): Boolean {
|
||||
// 只处理当前聊天对象的消息
|
||||
val currentChatUserId = userProfile?.trtcUserId
|
||||
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.i("openim", "clear unread failure, code:$code, error:$error")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun onLoadMore(context: Context) {
|
||||
if (!hasMore || isLoading) {
|
||||
return
|
||||
if (currentChatUserId != null && currentUserId != null) {
|
||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||
return (message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
||||
message.sendID != currentUserId
|
||||
}
|
||||
isLoading = true
|
||||
viewModelScope.launch {
|
||||
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.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}.filterNotNull()
|
||||
return false
|
||||
}
|
||||
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = messages.lastOrNull()
|
||||
isLoading = false
|
||||
Log.d("ChatAiViewModel", "fetch history message success")
|
||||
}
|
||||
override fun getReceiverInfo(): Pair<String?, String?> {
|
||||
return Pair(userProfile?.trtcUserId, null) // (recvID, groupID)
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatAiViewModel", "fetch history message error: $error")
|
||||
isLoading = false
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
override fun getMessageAvatar(message: Message): String? {
|
||||
return if (message.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
myProfile?.avatar
|
||||
} else {
|
||||
userProfile?.avatar
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage(message: String, context: Context) {
|
||||
// 检查 OpenIM 是否已登录
|
||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
||||
android.util.Log.w("ChatAiViewModel", "OpenIM 未登录,无法发送消息")
|
||||
return
|
||||
}
|
||||
|
||||
val textMessage = OpenIMClient.getInstance().messageManager.createTextMessage(message)
|
||||
val conversationID = "single_${userProfile?.trtcUserId!!}"
|
||||
OpenIMClient.getInstance().messageManager.sendMessage(
|
||||
object : OnMsgSendCallback {
|
||||
override fun onProgress(progress: Long) {
|
||||
// 发送进度
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatAiViewModel", "send message error: $error")
|
||||
}
|
||||
|
||||
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,
|
||||
null, // groupID
|
||||
OfflinePushInfo() // offlinePushInfo
|
||||
)
|
||||
override fun onMessageSentSuccess(message: String, sentMessage: Message?) {
|
||||
// AI聊天特有的处理逻辑
|
||||
sendChatAiMessage(myProfile?.trtcUserId!!, userProfile?.trtcUserId!!, message)
|
||||
createGroup2ChatAi(userProfile?.trtcUserId!!, "ai_group")
|
||||
}
|
||||
fun createGroup2ChatAi(
|
||||
trtcUserId: String,
|
||||
@@ -212,104 +87,6 @@ class ChatAiViewModel(
|
||||
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
|
||||
OfflinePushInfo() // offlinePushInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createTempFile(context: Context, uri: Uri): File? {
|
||||
return try {
|
||||
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||
val cursor = context.contentResolver.query(uri, projection, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
val filePath = it.getString(columnIndex)
|
||||
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
|
||||
val tempFile =
|
||||
File.createTempFile("temp_image", ".$extension", context.cacheDir)
|
||||
val outputStream = FileOutputStream(tempFile)
|
||||
|
||||
inputStream?.use { input ->
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
tempFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchHistoryMessage(context: Context) {
|
||||
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.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = messages.lastOrNull()
|
||||
Log.d("ChatAiViewModel", "fetch history message success")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatAiViewModel", "fetch history message error: $error")
|
||||
}
|
||||
},
|
||||
userProfile!!.trtcUserId,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
fun sendChatAiMessage(
|
||||
fromTrtcUserId: String,
|
||||
toTrtcUserId: String,
|
||||
@@ -318,16 +95,6 @@ class ChatAiViewModel(
|
||||
viewModelScope.launch {
|
||||
val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(fromTrtcUserId = fromTrtcUserId,toTrtcUserId = toTrtcUserId,message = message))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getDisplayChatList(): List<ChatItem> {
|
||||
val list = chatData
|
||||
// Update showTimestamp for each message
|
||||
for (item in list) {
|
||||
item.showTimestamp = showTimestampMap.getOrDefault(item.msgId, false)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
suspend fun updateNotificationStrategy(strategy: String) {
|
||||
|
||||
@@ -1,53 +1,24 @@
|
||||
package com.aiosman.ravenow.ui.chat
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.ChatState
|
||||
import com.aiosman.ravenow.data.AccountService
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.ChatItem
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
// 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 io.openim.android.sdk.enums.ConversationType
|
||||
import io.openim.android.sdk.models.Message
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
class ChatViewModel(
|
||||
val userId: String,
|
||||
) : ViewModel() {
|
||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
||||
) : BaseChatViewModel() {
|
||||
var userProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
val userService: UserService = UserServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var textMessageListener: OnAdvanceMsgListener? = null
|
||||
var hasMore by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var lastMessage: Message? = null
|
||||
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
|
||||
var chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||
var goToNew by mutableStateOf(false)
|
||||
fun init(context: Context) {
|
||||
override fun init(context: Context) {
|
||||
// 获取用户信息
|
||||
viewModelScope.launch {
|
||||
val resp = userService.getUserProfile(userId)
|
||||
@@ -55,253 +26,51 @@ class ChatViewModel(
|
||||
myProfile = accountService.getMyAccountProfile()
|
||||
|
||||
RegistListener(context)
|
||||
fetchHistoryMessage(context)
|
||||
|
||||
// 获取会话信息,然后加载历史消息
|
||||
getOneConversation {
|
||||
fetchHistoryMessage(context)
|
||||
}
|
||||
|
||||
// 获取通知信息
|
||||
val notiStrategy = ChatState.getStrategyByTargetTrtcId(resp.trtcUserId)
|
||||
chatNotification = notiStrategy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun RegistListener(context: Context) {
|
||||
// 检查 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
|
||||
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
||||
|
||||
if (currentChatUserId != null && currentUserId != null) {
|
||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||
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.sendID} 的消息,更新聊天列表")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
||||
override fun getConversationParams(): Triple<String, Int, Boolean> {
|
||||
return Triple(userProfile?.trtcUserId ?: userId, ConversationType.SINGLE_CHAT, true)
|
||||
}
|
||||
|
||||
fun UnRegistListener() {
|
||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
||||
textMessageListener = null
|
||||
override fun getLogTag(): String {
|
||||
return "ChatViewModel"
|
||||
}
|
||||
|
||||
fun clearUnRead() {
|
||||
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 handleNewMessage(message: Message, context: Context): Boolean {
|
||||
// 只处理当前聊天对象的消息
|
||||
val currentChatUserId = userProfile?.trtcUserId
|
||||
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.i("openim", "clear unread failure, code:$code, error:$error")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun onLoadMore(context: Context) {
|
||||
if (!hasMore || isLoading) {
|
||||
return
|
||||
}
|
||||
isLoading = true
|
||||
viewModelScope.launch {
|
||||
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.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}.filterNotNull()
|
||||
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = messages.lastOrNull()
|
||||
isLoading = false
|
||||
Log.d("ChatViewModel", "fetch history message success")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $error")
|
||||
isLoading = false
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
if (currentChatUserId != null && currentUserId != null) {
|
||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||
return (message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
||||
message.sendID != currentUserId
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun sendMessage(message: String, context: Context) {
|
||||
// 检查 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(code: Int, error: String?) {
|
||||
Log.e("ChatViewModel", "send message error: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Message?) {
|
||||
Log.d("ChatViewModel", "send message success")
|
||||
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
|
||||
OfflinePushInfo() // offlinePushInfo
|
||||
)
|
||||
override fun getReceiverInfo(): Pair<String?, String?> {
|
||||
return Pair(userProfile?.trtcUserId, null) // (recvID, groupID)
|
||||
}
|
||||
|
||||
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("ChatViewModel", "send image message progress: $progress")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatViewModel", "send image message error: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Message?) {
|
||||
Log.d("ChatViewModel", "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
|
||||
OfflinePushInfo() // offlinePushInfo
|
||||
)
|
||||
override fun getMessageAvatar(message: Message): String? {
|
||||
return if (message.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
myProfile?.avatar
|
||||
} else {
|
||||
userProfile?.avatar
|
||||
}
|
||||
}
|
||||
|
||||
fun createTempFile(context: Context, uri: Uri): File? {
|
||||
return try {
|
||||
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||
val cursor = context.contentResolver.query(uri, projection, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
val filePath = it.getString(columnIndex)
|
||||
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
|
||||
val tempFile =
|
||||
File.createTempFile("temp_image", ".$extension", context.cacheDir)
|
||||
val outputStream = FileOutputStream(tempFile)
|
||||
|
||||
inputStream?.use { input ->
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
tempFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchHistoryMessage(context: Context) {
|
||||
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.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
avatar = myProfile?.avatar
|
||||
}
|
||||
ChatItem.convertToChatItem(it, context, avatar)
|
||||
}
|
||||
if (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = messages.lastOrNull()
|
||||
Log.d("ChatViewModel", "fetch history message success")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("ChatViewModel", "fetch history message error: $error")
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
null,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
|
||||
fun getDisplayChatList(): List<ChatItem> {
|
||||
val list = chatData
|
||||
// Update showTimestamp for each message
|
||||
for (item in list) {
|
||||
item.showTimestamp = showTimestampMap.getOrDefault(item.msgId, false)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
suspend fun updateNotificationStrategy(strategy: String) {
|
||||
userProfile?.let {
|
||||
val result = ChatState.updateChatNotification(it.id, strategy)
|
||||
|
||||
@@ -1,55 +1,23 @@
|
||||
package com.aiosman.ravenow.ui.chat
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.ChatState
|
||||
import com.aiosman.ravenow.data.AccountService
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.GroupChatRequestBody
|
||||
import com.aiosman.ravenow.data.api.SendChatAiRequestBody
|
||||
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
|
||||
// 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.enums.ConversationType
|
||||
import io.openim.android.sdk.models.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
class GroupChatViewModel(
|
||||
val groupId: String,
|
||||
val name: String,
|
||||
val avatar: String,
|
||||
) : ViewModel() {
|
||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
||||
) : BaseChatViewModel() {
|
||||
var groupInfo by mutableStateOf<GroupInfo?>(null)
|
||||
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
val userService: UserService = UserServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var textMessageListener: OnAdvanceMsgListener? = null
|
||||
var hasMore by mutableStateOf(true)
|
||||
var isLoading by mutableStateOf(false)
|
||||
var lastMessage: Message? = null
|
||||
val showTimestampMap = mutableMapOf<String, Boolean>()
|
||||
var goToNew by mutableStateOf(false)
|
||||
|
||||
// 群聊特有属性
|
||||
var memberCount by mutableStateOf(0)
|
||||
@@ -64,13 +32,17 @@ class GroupChatViewModel(
|
||||
val ownerId: String
|
||||
)
|
||||
|
||||
fun init(context: Context) {
|
||||
override fun init(context: Context) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
getGroupInfo()
|
||||
myProfile = accountService.getMyAccountProfile()
|
||||
RegistListener(context)
|
||||
fetchHistoryMessage(context)
|
||||
|
||||
// 获取会话信息,然后加载历史消息
|
||||
getOneConversation {
|
||||
fetchHistoryMessage(context)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("GroupChatViewModel", "初始化失败: ${e.message}")
|
||||
}
|
||||
@@ -91,120 +63,36 @@ class GroupChatViewModel(
|
||||
memberCount = groupInfo?.memberCount ?: 0
|
||||
}
|
||||
|
||||
fun RegistListener(context: Context) {
|
||||
// 检查 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 {
|
||||
chatData = listOf(it) + chatData
|
||||
goToNew = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
||||
override fun getConversationParams(): Triple<String, Int, Boolean> {
|
||||
// 根据群组类型决定ConversationType,这里假设是普通群聊
|
||||
return Triple(groupId, ConversationType.GROUP_CHAT, false)
|
||||
}
|
||||
|
||||
fun UnRegistListener() {
|
||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
||||
textMessageListener = null
|
||||
override fun getLogTag(): String {
|
||||
return "GroupChatViewModel"
|
||||
}
|
||||
|
||||
fun clearUnRead() {
|
||||
val conversationID = "group_${groupId}"
|
||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
||||
conversationID,
|
||||
object : OnBase<String> {
|
||||
override fun onSuccess(data: String?) {
|
||||
Log.i("openim", "清除群聊未读消息成功")
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.i("openim", "清除群聊未读消息失败, code:$code, error:$error")
|
||||
}
|
||||
}
|
||||
)
|
||||
override fun handleNewMessage(message: Message, context: Context): Boolean {
|
||||
// 检查是否是当前群聊的消息
|
||||
return message.groupID == groupId
|
||||
}
|
||||
|
||||
fun onLoadMore(context: Context) {
|
||||
if (!hasMore || isLoading) return
|
||||
isLoading = true
|
||||
viewModelScope.launch {
|
||||
val conversationID = "group_${groupId}"
|
||||
override fun getReceiverInfo(): Pair<String?, String?> {
|
||||
return Pair(null, groupId) // (recvID, 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 (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = messages.lastOrNull()
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $error")
|
||||
isLoading = false
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
lastMessage,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
override fun getMessageAvatar(message: Message): String? {
|
||||
// 群聊中,如果是自己发送的消息显示自己的头像,否则为null(由ChatItem处理)
|
||||
return if (message.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||
myProfile?.avatar
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage(message: String, context: Context) {
|
||||
// 检查 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 onError(code: Int, error: String?) {
|
||||
Log.e("GroupChatViewModel", "发送群聊消息失败: $error")
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Message?) {
|
||||
sendChatAiMessage(message = message, trtcGroupId = groupId)
|
||||
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
|
||||
OfflinePushInfo() // offlinePushInfo
|
||||
)
|
||||
override fun onMessageSentSuccess(message: String, sentMessage: Message?) {
|
||||
// 群聊特有的处理逻辑
|
||||
sendChatAiMessage(message = message, trtcGroupId = groupId)
|
||||
}
|
||||
|
||||
|
||||
@@ -216,107 +104,6 @@ class GroupChatViewModel(
|
||||
viewModelScope.launch {
|
||||
val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(trtcGroupId = trtcGroupId,message = message))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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("GroupChatViewModel", "发送群聊图片消息进度: $progress")
|
||||
}
|
||||
|
||||
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
|
||||
OfflinePushInfo() // offlinePushInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createTempFile(context: Context, uri: Uri): File? {
|
||||
return try {
|
||||
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||
val cursor = context.contentResolver.query(uri, projection, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
val filePath = it.getString(columnIndex)
|
||||
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
|
||||
val tempFile =
|
||||
File.createTempFile("temp_image", ".$extension", context.cacheDir)
|
||||
val outputStream = FileOutputStream(tempFile)
|
||||
|
||||
inputStream?.use { input ->
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
tempFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchHistoryMessage(context: Context) {
|
||||
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 (messages.size < 20) {
|
||||
hasMore = false
|
||||
}
|
||||
lastMessage = messages.lastOrNull()
|
||||
}
|
||||
|
||||
override fun onError(code: Int, error: String?) {
|
||||
Log.e("GroupChatViewModel", "获取群聊历史消息失败: $error")
|
||||
}
|
||||
},
|
||||
conversationID,
|
||||
null,
|
||||
20,
|
||||
ViewType.History
|
||||
)
|
||||
}
|
||||
|
||||
fun getDisplayChatList(): List<ChatItem> {
|
||||
val list = chatData
|
||||
for (item in list) {
|
||||
item.showTimestamp = showTimestampMap.getOrDefault(item.msgId, false)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user