diff --git a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/chat/ChatViewModel.kt b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/chat/ChatViewModel.kt index c13c553..407fb2e 100644 --- a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/chat/ChatViewModel.kt +++ b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/chat/ChatViewModel.kt @@ -71,6 +71,14 @@ class ChatViewModel : ViewModel() { override fun onGroupMessage(message: ImMessage) { handleIncomingMessage(message) } + + override fun onRead(message: ImMessage) { + handleIncomingMessage(message) + } + + override fun onRevoke(message: ImMessage) { + handleIncomingMessage(message) + } } fun init(targetId: String, chatType: String) { @@ -217,6 +225,18 @@ class ChatViewModel : ViewModel() { _replyTargetMessage.value = null } + fun revokeMessage(messageId: String) { + if (!initialized) return + viewModelScope.launch { + runCatching { ImSDK.revokeMessage(messageId) } + .onSuccess { revoked -> handleIncomingMessage(revoked) } + .onFailure { + _events.tryEmit("消息撤回失败,请检查网络后重试") + Log.w(TAG, "revokeMessage failed messageId=$messageId", it) + } + } + } + fun sendImage(uri: Uri) = sendAttachment(uri, AttachmentKind.IMAGE) fun sendImageBytes(fileName: String, bytes: ByteArray, width: Int? = null, height: Int? = null) { diff --git a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/contact/ContactScreen.kt b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/contact/ContactScreen.kt index b1a9bb2..add457d 100644 --- a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/contact/ContactScreen.kt +++ b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/contact/ContactScreen.kt @@ -83,6 +83,8 @@ class ContactViewModel( private val listener = object : ImEventListener { override fun onMessage(message: ImMessage) = handleNotification(message) override fun onGroupMessage(message: ImMessage) = handleNotification(message) + override fun onRead(message: ImMessage) = handleNotification(message) + override fun onRevoke(message: ImMessage) = handleNotification(message) } init { diff --git a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt index 0df402f..09c24ee 100644 --- a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt +++ b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt @@ -49,6 +49,22 @@ class ConversationViewModel : ViewModel() { ) refresh() } + + override fun onRead(message: ImMessage) { + Log.d( + TAG, + "incoming read refresh request id=${message.id} from=${message.fromId} to=${message.toId} msgType=${message.msgType}", + ) + refresh() + } + + override fun onRevoke(message: ImMessage) { + Log.d( + TAG, + "incoming revoke refresh request id=${message.id} from=${message.fromId} to=${message.toId} msgType=${message.msgType}", + ) + refresh() + } } init { diff --git a/sdk-im/src/main/java/com/xuqm/sdk/im/ImClient.kt b/sdk-im/src/main/java/com/xuqm/sdk/im/ImClient.kt index 7e4de82..991a861 100644 --- a/sdk-im/src/main/java/com/xuqm/sdk/im/ImClient.kt +++ b/sdk-im/src/main/java/com/xuqm/sdk/im/ImClient.kt @@ -203,6 +203,13 @@ class ImClient( append(" status=").append(msg.status) }, ) + if (msg.status.uppercase() == "READ") { + listeners.forEach { it.onRead(msg) } + } + if (msg.status.uppercase() == "REVOKED" || msg.msgType.uppercase() == "REVOKED") { + listeners.forEach { it.onRevoke(msg) } + return + } if (msg.chatType.uppercase() == "GROUP") { listeners.forEach { it.onGroupMessage(msg) } } else { diff --git a/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt b/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt index bdd6007..1b2288f 100644 --- a/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt +++ b/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt @@ -6,6 +6,7 @@ import com.xuqm.sdk.core.ServiceEndpointRegistry import com.xuqm.sdk.im.api.AddMemberRequest import com.xuqm.sdk.im.api.CreateGroupRequest import com.xuqm.sdk.im.api.ImApi +import com.xuqm.sdk.im.model.EditMessageRequest import com.xuqm.sdk.im.api.MuteGroupMemberRequest import com.xuqm.sdk.im.api.SetMutedRequest import com.xuqm.sdk.im.api.SetPinnedRequest @@ -142,6 +143,12 @@ object ImSDK { return sendMessage(toId, chatType, "TEXT", content, mentionedUserIds) } + suspend fun editMessage(messageId: String, content: String): ImMessage = + withContext(Dispatchers.IO) { api.editMessage(messageId, XuqmSDK.appId, EditMessageRequest(content)).data ?: throw IllegalStateException("edit message failed") } + + suspend fun revokeMessage(messageId: String): ImMessage = + withContext(Dispatchers.IO) { api.revokeMessage(messageId, XuqmSDK.appId).data ?: throw IllegalStateException("revoke message failed") } + fun sendImageMessage( toId: String, chatType: String, @@ -435,6 +442,48 @@ object ImSDK { ).data ?: emptyList() } + suspend fun locateHistoryPage( + toId: String, + messageId: String, + pageSize: Int = 20, + maxPages: Int = 20, + ): List? = locatePage( + maxPages = maxPages, + loadPage = { page -> fetchHistory(toId, page, pageSize) }, + messageId = messageId, + pageSize = pageSize, + ) + + suspend fun locateGroupHistoryPage( + groupId: String, + messageId: String, + pageSize: Int = 20, + maxPages: Int = 20, + ): List? = locatePage( + maxPages = maxPages, + loadPage = { page -> fetchGroupHistory(groupId, page, pageSize) }, + messageId = messageId, + pageSize = pageSize, + ) + + private suspend fun locatePage( + maxPages: Int, + loadPage: suspend (Int) -> List, + messageId: String, + pageSize: Int, + ): List? { + repeat(maxPages.coerceAtLeast(1)) { page -> + val messages = loadPage(page) + if (messages.any { it.id == messageId }) { + return messages + } + if (messages.size < pageSize) { + return null + } + } + return null + } + suspend fun listGroups(): List = withContext(Dispatchers.IO) { api.listGroups(XuqmSDK.appId).data ?: emptyList() } diff --git a/sdk-im/src/main/java/com/xuqm/sdk/im/api/ImApi.kt b/sdk-im/src/main/java/com/xuqm/sdk/im/api/ImApi.kt index f158b15..f6ea387 100644 --- a/sdk-im/src/main/java/com/xuqm/sdk/im/api/ImApi.kt +++ b/sdk-im/src/main/java/com/xuqm/sdk/im/api/ImApi.kt @@ -2,10 +2,12 @@ package com.xuqm.sdk.im.api import com.xuqm.sdk.im.model.ConversationData import com.xuqm.sdk.im.model.BlacklistEntry +import com.xuqm.sdk.im.model.EditMessageRequest import com.xuqm.sdk.im.model.FriendRequest import com.xuqm.sdk.im.model.ImGroup import com.xuqm.sdk.im.model.GroupJoinRequest import com.xuqm.sdk.im.model.ImMessage +import com.xuqm.sdk.im.model.PageResult import com.xuqm.sdk.im.model.UserProfile import retrofit2.http.Body import retrofit2.http.DELETE @@ -275,6 +277,19 @@ interface ImApi { @Query("chatType") chatType: String, ): ApiResponse + @PUT("api/im/messages/{messageId}") + suspend fun editMessage( + @Path("messageId") messageId: String, + @Query("appId") appId: String, + @Body request: EditMessageRequest, + ): ApiResponse + + @POST("api/im/messages/{messageId}/revoke") + suspend fun revokeMessage( + @Path("messageId") messageId: String, + @Query("appId") appId: String, + ): ApiResponse + @PUT("api/im/conversations/{targetId}/draft") suspend fun setDraft( @Path("targetId") targetId: String, diff --git a/sdk-im/src/main/java/com/xuqm/sdk/im/listener/ImEventListener.kt b/sdk-im/src/main/java/com/xuqm/sdk/im/listener/ImEventListener.kt index 71941e7..1d61d17 100644 --- a/sdk-im/src/main/java/com/xuqm/sdk/im/listener/ImEventListener.kt +++ b/sdk-im/src/main/java/com/xuqm/sdk/im/listener/ImEventListener.kt @@ -7,5 +7,7 @@ interface ImEventListener { fun onDisconnected(reason: String?) {} fun onMessage(message: ImMessage) {} fun onGroupMessage(message: ImMessage) {} + fun onRead(message: ImMessage) {} + fun onRevoke(message: ImMessage) {} fun onError(error: String) {} } diff --git a/sdk-im/src/main/java/com/xuqm/sdk/im/model/ImMessage.kt b/sdk-im/src/main/java/com/xuqm/sdk/im/model/ImMessage.kt index 0a1aea3..8d225bd 100644 --- a/sdk-im/src/main/java/com/xuqm/sdk/im/model/ImMessage.kt +++ b/sdk-im/src/main/java/com/xuqm/sdk/im/model/ImMessage.kt @@ -14,6 +14,10 @@ data class PageResult( val empty: Boolean = true, ) +data class EditMessageRequest( + val content: String, +) + data class ImMessage( val id: String, val appId: String,