重构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
|
package com.aiosman.ravenow.ui.chat
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import com.aiosman.ravenow.ChatState
|
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.ApiClient
|
||||||
import com.aiosman.ravenow.data.api.SendChatAiRequestBody
|
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.AccountProfileEntity
|
||||||
import com.aiosman.ravenow.entity.ChatItem
|
|
||||||
import com.aiosman.ravenow.entity.ChatNotification
|
import com.aiosman.ravenow.entity.ChatNotification
|
||||||
import com.aiosman.ravenow.ui.navigateToChatAi
|
import io.openim.android.sdk.enums.ConversationType
|
||||||
// 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 io.openim.android.sdk.models.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
|
|
||||||
class ChatAiViewModel(
|
class ChatAiViewModel(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
) : ViewModel() {
|
) : BaseChatViewModel() {
|
||||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
|
||||||
var userProfile by mutableStateOf<AccountProfileEntity?>(null)
|
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 chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||||
var goToNew by mutableStateOf(false)
|
override fun init(context: Context) {
|
||||||
fun init(context: Context) {
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val resp = userService.getUserProfile(userId)
|
val resp = userService.getUserProfile(userId)
|
||||||
@@ -59,150 +29,55 @@ class ChatAiViewModel(
|
|||||||
myProfile = accountService.getMyAccountProfile()
|
myProfile = accountService.getMyAccountProfile()
|
||||||
|
|
||||||
RegistListener(context)
|
RegistListener(context)
|
||||||
|
|
||||||
|
// 获取会话信息,然后加载历史消息
|
||||||
|
getOneConversation {
|
||||||
fetchHistoryMessage(context)
|
fetchHistoryMessage(context)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取通知信息
|
// 获取通知信息
|
||||||
val notiStrategy = ChatState.getStrategyByTargetTrtcId(resp.trtcUserId)
|
val notiStrategy = ChatState.getStrategyByTargetTrtcId(resp.trtcUserId)
|
||||||
chatNotification = notiStrategy
|
chatNotification = notiStrategy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getConversationParams(): Triple<String, Int, Boolean> {
|
||||||
fun RegistListener(context: Context) {
|
return Triple(userProfile?.trtcUserId ?: userId, ConversationType.SINGLE_CHAT, true)
|
||||||
// 检查 OpenIM 是否已登录
|
|
||||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
|
||||||
android.util.Log.w("ChatAiViewModel", "OpenIM 未登录,跳过注册消息监听器")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textMessageListener = object : OnAdvanceMsgListener {
|
override fun getLogTag(): String {
|
||||||
override fun onRecvNewMessage(msg: Message?) {
|
return "ChatAiViewModel"
|
||||||
msg?.let { message ->
|
}
|
||||||
|
|
||||||
|
override fun handleNewMessage(message: Message, context: Context): Boolean {
|
||||||
// 只处理当前聊天对象的消息
|
// 只处理当前聊天对象的消息
|
||||||
val currentChatUserId = userProfile?.trtcUserId
|
val currentChatUserId = userProfile?.trtcUserId
|
||||||
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
||||||
|
|
||||||
if (currentChatUserId != null && currentUserId != null) {
|
if (currentChatUserId != null && currentUserId != null) {
|
||||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||||
if ((message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
return (message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
||||||
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聊天列表")
|
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun UnRegistListener() {
|
override fun getReceiverInfo(): Pair<String?, String?> {
|
||||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
return Pair(userProfile?.trtcUserId, null) // (recvID, groupID)
|
||||||
textMessageListener = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearUnRead() {
|
override fun getMessageAvatar(message: Message): String? {
|
||||||
val conversationID = "single_${userProfile?.trtcUserId}"
|
return if (message.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
myProfile?.avatar
|
||||||
conversationID,
|
} else {
|
||||||
object : OnBase<String> {
|
userProfile?.avatar
|
||||||
override fun onSuccess(data: String?) {
|
|
||||||
Log.i("openim", "clear unread success")
|
|
||||||
}
|
|
||||||
|
|
||||||
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!!}"
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
if (messages.size < 20) {
|
|
||||||
hasMore = false
|
|
||||||
}
|
|
||||||
lastMessage = messages.lastOrNull()
|
|
||||||
isLoading = false
|
|
||||||
Log.d("ChatAiViewModel", "fetch history message success")
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
override fun onMessageSentSuccess(message: String, sentMessage: Message?) {
|
||||||
// 检查 OpenIM 是否已登录
|
// AI聊天特有的处理逻辑
|
||||||
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)
|
sendChatAiMessage(myProfile?.trtcUserId!!, userProfile?.trtcUserId!!, message)
|
||||||
createGroup2ChatAi(userProfile?.trtcUserId!!, "ai_group")
|
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
fun createGroup2ChatAi(
|
fun createGroup2ChatAi(
|
||||||
trtcUserId: String,
|
trtcUserId: String,
|
||||||
@@ -212,104 +87,6 @@ class ChatAiViewModel(
|
|||||||
Log.d("ChatAiViewModel", "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
|
|
||||||
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(
|
fun sendChatAiMessage(
|
||||||
fromTrtcUserId: String,
|
fromTrtcUserId: String,
|
||||||
toTrtcUserId: String,
|
toTrtcUserId: String,
|
||||||
@@ -318,16 +95,6 @@ class ChatAiViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(fromTrtcUserId = fromTrtcUserId,toTrtcUserId = toTrtcUserId,message = message))
|
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) {
|
suspend fun updateNotificationStrategy(strategy: String) {
|
||||||
|
|||||||
@@ -1,53 +1,24 @@
|
|||||||
package com.aiosman.ravenow.ui.chat
|
package com.aiosman.ravenow.ui.chat
|
||||||
|
|
||||||
import android.content.Context
|
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.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.aiosman.ravenow.ChatState
|
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.AccountProfileEntity
|
||||||
import com.aiosman.ravenow.entity.ChatItem
|
|
||||||
import com.aiosman.ravenow.entity.ChatNotification
|
import com.aiosman.ravenow.entity.ChatNotification
|
||||||
// OpenIM SDK 导入
|
import io.openim.android.sdk.enums.ConversationType
|
||||||
import io.openim.android.sdk.OpenIMClient
|
import io.openim.android.sdk.models.Message
|
||||||
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 kotlinx.coroutines.launch
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
|
|
||||||
class ChatViewModel(
|
class ChatViewModel(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
) : ViewModel() {
|
) : BaseChatViewModel() {
|
||||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
|
||||||
var userProfile by mutableStateOf<AccountProfileEntity?>(null)
|
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 chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||||
var goToNew by mutableStateOf(false)
|
override fun init(context: Context) {
|
||||||
fun init(context: Context) {
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val resp = userService.getUserProfile(userId)
|
val resp = userService.getUserProfile(userId)
|
||||||
@@ -55,252 +26,50 @@ class ChatViewModel(
|
|||||||
myProfile = accountService.getMyAccountProfile()
|
myProfile = accountService.getMyAccountProfile()
|
||||||
|
|
||||||
RegistListener(context)
|
RegistListener(context)
|
||||||
|
|
||||||
|
// 获取会话信息,然后加载历史消息
|
||||||
|
getOneConversation {
|
||||||
fetchHistoryMessage(context)
|
fetchHistoryMessage(context)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取通知信息
|
// 获取通知信息
|
||||||
val notiStrategy = ChatState.getStrategyByTargetTrtcId(resp.trtcUserId)
|
val notiStrategy = ChatState.getStrategyByTargetTrtcId(resp.trtcUserId)
|
||||||
chatNotification = notiStrategy
|
chatNotification = notiStrategy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getConversationParams(): Triple<String, Int, Boolean> {
|
||||||
fun RegistListener(context: Context) {
|
return Triple(userProfile?.trtcUserId ?: userId, ConversationType.SINGLE_CHAT, true)
|
||||||
// 检查 OpenIM 是否已登录
|
|
||||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
|
||||||
android.util.Log.w("ChatViewModel", "OpenIM 未登录,跳过注册消息监听器")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textMessageListener = object : OnAdvanceMsgListener {
|
override fun getLogTag(): String {
|
||||||
override fun onRecvNewMessage(msg: Message?) {
|
return "ChatViewModel"
|
||||||
msg?.let { message ->
|
}
|
||||||
|
|
||||||
|
override fun handleNewMessage(message: Message, context: Context): Boolean {
|
||||||
// 只处理当前聊天对象的消息
|
// 只处理当前聊天对象的消息
|
||||||
val currentChatUserId = userProfile?.trtcUserId
|
val currentChatUserId = userProfile?.trtcUserId
|
||||||
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
val currentUserId = com.aiosman.ravenow.AppState.profile?.trtcUserId
|
||||||
|
|
||||||
if (currentChatUserId != null && currentUserId != null) {
|
if (currentChatUserId != null && currentUserId != null) {
|
||||||
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
// 检查消息是否来自当前聊天对象,且不是自己发送的消息
|
||||||
if ((message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
return (message.sendID == currentChatUserId || message.sendID == currentUserId) &&
|
||||||
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} 的消息,更新聊天列表")
|
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun UnRegistListener() {
|
override fun getReceiverInfo(): Pair<String?, String?> {
|
||||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
return Pair(userProfile?.trtcUserId, null) // (recvID, groupID)
|
||||||
textMessageListener = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearUnRead() {
|
override fun getMessageAvatar(message: Message): String? {
|
||||||
val conversationID = "single_${userProfile?.trtcUserId}"
|
return if (message.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
myProfile?.avatar
|
||||||
conversationID,
|
|
||||||
object : OnBase<String> {
|
|
||||||
override fun onSuccess(data: String?) {
|
|
||||||
Log.i("openim", "clear unread success")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
null
|
userProfile?.avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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) {
|
suspend fun updateNotificationStrategy(strategy: String) {
|
||||||
userProfile?.let {
|
userProfile?.let {
|
||||||
|
|||||||
@@ -1,55 +1,23 @@
|
|||||||
package com.aiosman.ravenow.ui.chat
|
package com.aiosman.ravenow.ui.chat
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
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.ApiClient
|
||||||
import com.aiosman.ravenow.data.api.GroupChatRequestBody
|
|
||||||
import com.aiosman.ravenow.data.api.SendChatAiRequestBody
|
import com.aiosman.ravenow.data.api.SendChatAiRequestBody
|
||||||
import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
import io.openim.android.sdk.enums.ConversationType
|
||||||
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.models.*
|
import io.openim.android.sdk.models.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class GroupChatViewModel(
|
class GroupChatViewModel(
|
||||||
val groupId: String,
|
val groupId: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val avatar: String,
|
val avatar: String,
|
||||||
) : ViewModel() {
|
) : BaseChatViewModel() {
|
||||||
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
|
||||||
var groupInfo by mutableStateOf<GroupInfo?>(null)
|
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)
|
var memberCount by mutableStateOf(0)
|
||||||
@@ -64,13 +32,17 @@ class GroupChatViewModel(
|
|||||||
val ownerId: String
|
val ownerId: String
|
||||||
)
|
)
|
||||||
|
|
||||||
fun init(context: Context) {
|
override fun init(context: Context) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
getGroupInfo()
|
getGroupInfo()
|
||||||
myProfile = accountService.getMyAccountProfile()
|
myProfile = accountService.getMyAccountProfile()
|
||||||
RegistListener(context)
|
RegistListener(context)
|
||||||
|
|
||||||
|
// 获取会话信息,然后加载历史消息
|
||||||
|
getOneConversation {
|
||||||
fetchHistoryMessage(context)
|
fetchHistoryMessage(context)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("GroupChatViewModel", "初始化失败: ${e.message}")
|
Log.e("GroupChatViewModel", "初始化失败: ${e.message}")
|
||||||
}
|
}
|
||||||
@@ -91,120 +63,36 @@ class GroupChatViewModel(
|
|||||||
memberCount = groupInfo?.memberCount ?: 0
|
memberCount = groupInfo?.memberCount ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RegistListener(context: Context) {
|
override fun getConversationParams(): Triple<String, Int, Boolean> {
|
||||||
// 检查 OpenIM 是否已登录
|
// 根据群组类型决定ConversationType,这里假设是普通群聊
|
||||||
if (!com.aiosman.ravenow.AppState.enableChat) {
|
return Triple(groupId, ConversationType.GROUP_CHAT, false)
|
||||||
android.util.Log.w("GroupChatViewModel", "OpenIM 未登录,跳过注册消息监听器")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textMessageListener = object : OnAdvanceMsgListener {
|
override fun getLogTag(): String {
|
||||||
override fun onRecvNewMessage(msg: Message?) {
|
return "GroupChatViewModel"
|
||||||
msg?.let {
|
}
|
||||||
|
|
||||||
|
override fun handleNewMessage(message: Message, context: Context): Boolean {
|
||||||
// 检查是否是当前群聊的消息
|
// 检查是否是当前群聊的消息
|
||||||
if (it.groupID == groupId) {
|
return message.groupID == groupId
|
||||||
val chatItem = ChatItem.convertToChatItem(msg, context, avatar = null)
|
|
||||||
chatItem?.let {
|
|
||||||
chatData = listOf(it) + chatData
|
|
||||||
goToNew = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OpenIMClient.getInstance().messageManager.setAdvancedMsgListener(textMessageListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun UnRegistListener() {
|
override fun getReceiverInfo(): Pair<String?, String?> {
|
||||||
// OpenIM SDK 不需要显式移除监听器,只需要设置为 null
|
return Pair(null, groupId) // (recvID, groupID)
|
||||||
textMessageListener = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearUnRead() {
|
override fun getMessageAvatar(message: Message): String? {
|
||||||
val conversationID = "group_${groupId}"
|
// 群聊中,如果是自己发送的消息显示自己的头像,否则为null(由ChatItem处理)
|
||||||
OpenIMClient.getInstance().messageManager.markConversationMessageAsRead(
|
return if (message.sendID == com.aiosman.ravenow.AppState.profile?.trtcUserId) {
|
||||||
conversationID,
|
myProfile?.avatar
|
||||||
object : OnBase<String> {
|
} else {
|
||||||
override fun onSuccess(data: String?) {
|
null
|
||||||
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
|
|
||||||
isLoading = true
|
|
||||||
viewModelScope.launch {
|
|
||||||
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 (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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendMessage(message: String, context: Context) {
|
override fun onMessageSentSuccess(message: String, sentMessage: Message?) {
|
||||||
// 检查 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)
|
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -216,107 +104,6 @@ class GroupChatViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(trtcGroupId = trtcGroupId,message = message))
|
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