Przeglądaj źródła

feat: 私聊batch接口增加缓存&一些遗留问题处理 (#31)

* feat: 会话列表batch接口增加内存缓存

* fix: 解决融云返回hasMoreLoad标识问题

* fix: 敏感词拦截消息发送失败卸载重装不显示状态问题

* fix: 删除会话列表问题

* fix: 会话界面topbar图标间距调整

* fix: 卸载重装loadMore同步会话逻辑

* fix: 卸载重装被拦截消息显示延时问题

* feat: 更新币商钻石提现消息为红点计数

* feat: 补充币商消息改为红点计数

---------

Co-authored-by: wutiaorong <wutiaorong@gmail.com>
Talon 10 miesięcy temu
rodzic
commit
e68d0347e2
23 zmienionych plików z 352 dodań i 140 usunięć
  1. 8 0
      app/src/main/java/com/adealink/weparty/module/couple/CoupleModule.kt
  2. 1 0
      app/src/main/java/com/adealink/weparty/module/couple/ICoupleService.kt
  3. 4 0
      app/src/main/java/com/adealink/weparty/module/couple/data/CoupleData.kt
  4. 2 0
      app/src/main/java/com/adealink/weparty/module/couple/listener/ICoupleListener.kt
  5. 2 2
      app/src/main/java/com/adealink/weparty/module/message/data/MessageData.kt
  6. 70 1
      frame/imkit/src/main/java/com/adealink/frame/imkit/IMService.kt
  7. 4 0
      module/couple/src/main/java/com/adealink/weparty/couple/CoupleServiceImpl.kt
  8. 14 0
      module/couple/src/main/java/com/adealink/weparty/couple/manager/CoupleManager.kt
  9. 2 0
      module/couple/src/main/java/com/adealink/weparty/couple/manager/ICoupleManager.kt
  10. 3 0
      module/message/src/main/java/com/adealink/weparty/message/conversation/comp/ConversationTargetInfoComp.kt
  11. 1 14
      module/message/src/main/java/com/adealink/weparty/message/conversation/data/ConversationData.kt
  12. 2 1
      module/message/src/main/java/com/adealink/weparty/message/conversation/data/MessageExpansion.kt
  13. 1 1
      module/message/src/main/java/com/adealink/weparty/message/conversation/message/ProxyInviteWithdrawMessage.kt
  14. 1 1
      module/message/src/main/java/com/adealink/weparty/message/conversation/message/ProxyWithdrawCompleteMessage.kt
  15. 1 1
      module/message/src/main/java/com/adealink/weparty/message/conversation/message/TransferDealMessage.kt
  16. 1 3
      module/message/src/main/java/com/adealink/weparty/message/conversation/status/MessageProcessor.kt
  17. 42 47
      module/message/src/main/java/com/adealink/weparty/message/conversation/viewmodel/ConversationViewModel.kt
  18. 16 8
      module/message/src/main/java/com/adealink/weparty/message/conversationlist/ConversationListFragment.kt
  19. 31 54
      module/message/src/main/java/com/adealink/weparty/message/conversationlist/viewmodel/ConversationListViewModel.kt
  20. 5 0
      module/message/src/main/java/com/adealink/weparty/message/datasource/local/MessageLocalService.kt
  21. 3 0
      module/message/src/main/java/com/adealink/weparty/message/manager/IMessageManager.kt
  22. 131 1
      module/message/src/main/java/com/adealink/weparty/message/manager/MessageManager.kt
  23. 7 6
      module/message/src/main/res/layout/fragment_conversation.xml

+ 8 - 0
app/src/main/java/com/adealink/weparty/module/couple/CoupleModule.kt

@@ -159,6 +159,10 @@ object CoupleModule : BaseDynamicModule<ICoupleService>(ICoupleService::class),
             ): Map<IntimacyPrivilegeType, Boolean> {
                 return emptyMap()
             }
+
+            override fun notifyMyIntimacyInfoUpdate(coupleUserInfos: List<CoupleUserInfo>) {
+
+            }
         }
     }
 
@@ -284,4 +288,8 @@ object CoupleModule : BaseDynamicModule<ICoupleService>(ICoupleService::class),
     ): Map<IntimacyPrivilegeType, Boolean> {
         return getService().checkIntimacyPrivilegeOpen(intimacyPrivilegeType, peerUid, intimacyLevelWithTarget)
     }
+
+    override fun notifyMyIntimacyInfoUpdate(coupleUserInfos: List<CoupleUserInfo>) {
+        getService().notifyMyIntimacyInfoUpdate(coupleUserInfos)
+    }
 }

+ 1 - 0
app/src/main/java/com/adealink/weparty/module/couple/ICoupleService.kt

@@ -67,4 +67,5 @@ interface ICoupleService : IService<ICoupleService> {
         peerUid: Long,
         intimacyLevelWithTarget: Int? = null
     ): Map<IntimacyPrivilegeType, Boolean>
+    fun notifyMyIntimacyInfoUpdate(coupleUserInfos: List<CoupleUserInfo>)
 }

+ 4 - 0
app/src/main/java/com/adealink/weparty/module/couple/data/CoupleData.kt

@@ -973,6 +973,10 @@ data class IntimacyValInfo(
         val allowedSet = setOf(AccountModule.uid, targetUid)
         return peerUid in allowedSet && uid in allowedSet
     }
+
+    fun isSelfInvolved() : Boolean {
+        return AccountModule.uid in setOf(uid, peerUid)
+    }
 }
 
 data class IntimacyTypeInfo(

+ 2 - 0
app/src/main/java/com/adealink/weparty/module/couple/listener/ICoupleListener.kt

@@ -2,6 +2,7 @@ package com.adealink.weparty.module.couple.listener
 
 import com.adealink.frame.frame.IListener
 import com.adealink.frame.room.data.CpRoomInfo
+import com.adealink.weparty.module.couple.data.CoupleUserInfo
 import com.adealink.weparty.module.couple.data.IntimacyOnMicNotify
 import com.adealink.weparty.module.couple.data.IntimacyTypeInfo
 import com.adealink.weparty.module.couple.data.IntimacyValInfo
@@ -12,4 +13,5 @@ interface ICoupleListener : IListener {
     fun onIntimacyAdded(info: IntimacyTypeInfo) {}
     fun onIntimacyDissolved(info: IntimacyTypeInfo) {}
     fun onAddIntimacyVal(intimacyValInfo: IntimacyValInfo) {}
+    fun onGetMyIntimacyInfos(coupleUserInfos: List<CoupleUserInfo>) {}
 }

+ 2 - 2
app/src/main/java/com/adealink/weparty/module/message/data/MessageData.kt

@@ -128,10 +128,10 @@ data class SessionInfo(
     @SerializedName("currentInRoomId")
     val currentInRoomId: Long? = null,//用户当前所在房间id
     @SerializedName("onlineStatus")
-    val onlineStatus: Int,//用户在线状态,0:离线,1:当前在线
+    var onlineStatus: Int,//用户在线状态,0:离线,1:当前在线
     @GsonNullable
     @SerializedName("userIntimacyInfo")
-    val userIntimacyInfo: CoupleUserInfo?,//亲密关系信息
+    var userIntimacyInfo: CoupleUserInfo?,//亲密关系信息
 ) {
     fun toPrivateConversation(): Conversation {
         val conversation = Conversation.obtain(

+ 70 - 1
frame/imkit/src/main/java/com/adealink/frame/imkit/IMService.kt

@@ -1336,7 +1336,7 @@ class IMService(val config: IIMConfig) {
     }
 
     /**
-     * 获取指定会话历史消息。
+     * 获取指定会话历史消息。(该方法返回的消息会包含命令消息,导致无法通过消息条数判定是否还有更多消息)
      *
      *
      * 此方法先从本地获取历史消息,本地有缺失的情况下会从服务端同步缺失的部分;从服务端同步失败的时候会返回非 0 的 errorCode,同时把本地能取到的消息回调上去。<br></br>
@@ -1382,6 +1382,75 @@ class IMService(val config: IIMConfig) {
             )
     }
 
+    /**
+     * 获取指定会话历史消息。
+     *
+     *
+     * 此方法先从本地获取历史消息,本地有缺失的情况下会从服务端同步缺失的部分;从服务端同步失败的时候会返回非 0 的 errorCode,同时把本地能取到的消息回调上去。<br></br>
+     * 必须开通历史消息云存储功能。
+     *
+     * @param conversationIdentifier 会话标识
+     * @param historyMsgOption [HistoryMessageOption]
+     * @param callback 获取消息的回调。该回调在主线程中执行,请避免在回调中执行耗时操作,防止 SDK 线程阻塞。
+     * @param refreshMessage 是否通知会话页面刷新某条消息,默认为 true
+     */
+    fun getMessagesRecursively(
+        conversationIdentifier: ConversationIdentifier,
+        historyMsgOption: HistoryMessageOption?,
+        callback: IRongCoreCallback.IGetMessageCallbackEx?,
+        refreshMessage: Boolean = true,
+    ) {
+        if (historyMsgOption == null) {
+            callback?.onComplete(emptyList(), 0L, false, CoreErrorCode.PARAMETER_ERROR)
+            return
+        }
+
+        val expectedCount = historyMsgOption.count
+        val allMessages = mutableListOf<Message>()
+
+        fun fetchNext() {
+            ChannelClient.getInstance().getMessages(
+                conversationIdentifier.type,
+                conversationIdentifier.targetId,
+                conversationIdentifier.channelId,
+                historyMsgOption,
+                object : IRongCoreCallback.IGetMessageCallbackEx {
+                    override fun onComplete(
+                        messageList: List<Message>?,
+                        syncTimestamp: Long,
+                        hasMoreMsg: Boolean,
+                        errorCode: CoreErrorCode?
+                    ) {
+                        if (errorCode != CoreErrorCode.SUCCESS) {
+                            callback?.onComplete(allMessages, syncTimestamp, false, errorCode)
+                            return
+                        }
+
+                        val currentList = messageList ?: emptyList()
+                        allMessages.addAll(currentList)
+
+                        val continueFetching = allMessages.size < expectedCount && hasMoreMsg
+                        if (continueFetching) {
+                            historyMsgOption.dataTime = allMessages.lastOrNull()?.sentTime ?: 0
+                            fetchNext()
+                        } else {
+                            if (refreshMessage && allMessages.isNotEmpty()) {
+                                refreshMessage(allMessages.last())
+                            }
+                            callback?.onComplete(allMessages, syncTimestamp, hasMoreMsg, errorCode)
+                        }
+                    }
+
+                    override fun onFail(errorCode: CoreErrorCode?) {
+                        callback?.onFail(errorCode)
+                    }
+                }
+            )
+        }
+
+        fetchNext()
+    }
+
     /**
      * 删除指定时间戳之前的消息,可选择是否同时删除服务器端消息
      *

+ 4 - 0
module/couple/src/main/java/com/adealink/weparty/couple/CoupleServiceImpl.kt

@@ -212,4 +212,8 @@ class CoupleServiceImpl: ICoupleService {
     ): Map<IntimacyPrivilegeType, Boolean> {
         return coupleManager.checkIntimacyPrivilegeOpen(intimacyPrivilegeType, peerUid, intimacyLevelWithTarget)
     }
+
+    override fun notifyMyIntimacyInfoUpdate(coupleUserInfos: List<CoupleUserInfo>) {
+        coupleManager.notifyMyIntimacyInfoUpdate(coupleUserInfos)
+    }
 }

+ 14 - 0
module/couple/src/main/java/com/adealink/weparty/couple/manager/CoupleManager.kt

@@ -852,6 +852,10 @@ class CoupleManager : BaseFrame<ICoupleListener>(), ICoupleManager {
                         friends.forEach {
                             myIntimacyUidSet.add(it.uid)
                         }
+                        onGetMyIntimacyInfos(buildList {
+                            cp?.let { add(it) }
+                            addAll(friends)
+                        })
                     }
                 }
 
@@ -1278,4 +1282,14 @@ class CoupleManager : BaseFrame<ICoupleListener>(), ICoupleManager {
         }
         return res
     }
+
+    override fun notifyMyIntimacyInfoUpdate(coupleUserInfos: List<CoupleUserInfo>) {
+        onGetMyIntimacyInfos(coupleUserInfos)
+    }
+
+    private fun onGetMyIntimacyInfos(coupleUserInfos: List<CoupleUserInfo>) {
+        launch {
+            dispatch { it.onGetMyIntimacyInfos(coupleUserInfos) }
+        }
+    }
 }

+ 2 - 0
module/couple/src/main/java/com/adealink/weparty/couple/manager/ICoupleManager.kt

@@ -99,4 +99,6 @@ interface ICoupleManager : IBaseFrame<ICoupleListener> {
         peerUid: Long,
         intimacyLevelWithTarget: Int? = null
     ): Map<IntimacyPrivilegeType, Boolean>
+
+    fun notifyMyIntimacyInfoUpdate(coupleUserInfos: List<CoupleUserInfo>)
 }

+ 3 - 0
module/message/src/main/java/com/adealink/weparty/message/conversation/comp/ConversationTargetInfoComp.kt

@@ -139,6 +139,9 @@ class ConversationTargetInfoComp(
                     this@ConversationTargetInfoComp.targetUserInfo = data.userInfo
                     updateIntimacyInfoUI()
                     checkOffPageIntimacyUpgradeNotify()
+                    data.userIntimacyInfo?.let { coupleUserInfo ->
+                        CoupleModule.notifyMyIntimacyInfoUpdate(listOf(coupleUserInfo))
+                    }
                 }
             }
         }

+ 1 - 14
module/message/src/main/java/com/adealink/weparty/message/conversation/data/ConversationData.kt

@@ -2,21 +2,8 @@ package com.adealink.weparty.message.conversation.data
 
 import com.adealink.weparty.commonui.recycleview.diffutil.BaseListItemData
 import com.adealink.weparty.module.message.data.QuickMessageInfo
-import com.google.gson.annotations.GsonNullable
-import com.google.gson.annotations.JsonAdapter
-import com.google.gson.annotations.SerializedName
-import com.google.gson.internal.bind.ExtReflectiveTypeAdapterFactory
 
-@JsonAdapter(ExtReflectiveTypeAdapterFactory::class)
-data class MessageBlockRlt(
-    @GsonNullable
-    @SerializedName("result")
-    val result: MessageShowInfo?,
-)
-
-interface IConversationData : BaseListItemData {
-
-}
+interface IConversationData : BaseListItemData
 
 data class QuickMessageItem(var data: QuickMessageInfo) : IConversationData {
 

+ 2 - 1
module/message/src/main/java/com/adealink/weparty/message/conversation/data/MessageExpansion.kt

@@ -18,7 +18,8 @@ enum class MessageExpansionKey(val key: String) {
     ExperienceCard("experience_card_spend"), //道具体验卡
     AcceptStatus("is_accept"),//是否接受 "1": "已接受"
     Scene("scene"),//消息发送场景
-    SessionListChangeType("session_list_change_type");//会话列表变更信息
+    SessionListChangeType("session_list_change_type"),//会话列表变更信息
+    BlockMessageShowInfo("block_message_show_info");//拦截消息显示信息
 
     companion object {
         fun map(key: String): MessageExpansionKey? {

+ 1 - 1
module/message/src/main/java/com/adealink/weparty/message/conversation/message/ProxyInviteWithdrawMessage.kt

@@ -12,7 +12,7 @@ import java.io.UnsupportedEncodingException
 /**
  * Created by PengWuLiang on 2025/4/21
  */
-@MessageTag(value = "app:proxyInviteWithdraw", flag = MessageTag.ISPERSISTED)
+@MessageTag(value = "app:proxyInviteWithdraw", flag = MessageTag.ISCOUNTED)
 class ProxyInviteWithdrawMessage : MessageContent {
     var merchantUid: Long? = null //币商用户ID
     var uid: Long? = null //被邀请用户ID

+ 1 - 1
module/message/src/main/java/com/adealink/weparty/message/conversation/message/ProxyWithdrawCompleteMessage.kt

@@ -12,7 +12,7 @@ import java.io.UnsupportedEncodingException
 /**
  * Created by PengWuLiang on 2025/4/21
  */
-@MessageTag(value = "app:proxyWithdrawComplete", flag = MessageTag.ISPERSISTED)
+@MessageTag(value = "app:proxyWithdrawComplete", flag = MessageTag.ISCOUNTED)
 class ProxyWithdrawCompleteMessage : MessageContent {
     var merchantUid: Long? = null //币商用户ID
     var uid: Long? = null //被邀请用户ID

+ 1 - 1
module/message/src/main/java/com/adealink/weparty/message/conversation/message/TransferDealMessage.kt

@@ -12,7 +12,7 @@ import java.io.UnsupportedEncodingException
 /**
  * Created by PengWuLiang on 2025/4/21
  */
-@MessageTag(value = "app:appTransferDeal", flag = MessageTag.ISPERSISTED)
+@MessageTag(value = "app:appTransferDeal", flag = MessageTag.ISCOUNTED)
 class TransferDealMessage : MessageContent {
     var currencyType: Int? = null //货币类型 0-金币, 1-蓝钻
     var num: Long? = null //到账数量

+ 1 - 3
module/message/src/main/java/com/adealink/weparty/message/conversation/status/MessageProcessor.kt

@@ -7,9 +7,7 @@ import com.adealink.weparty.message.event.uievent.ShowLoadMessageDialogEvent
 import com.adealink.frame.util.AppUtil
 import com.adealink.weparty.message.conversation.viewmodel.IConversationViewModel
 import com.scwang.smart.refresh.layout.constant.RefreshState
-import io.rong.imlib.ChannelClient
 import io.rong.imlib.IRongCoreCallback
-import io.rong.imlib.IRongCoreCallback.IGetMessageCallback
 import io.rong.imlib.IRongCoreEnum.ConversationLoadMessageType
 import io.rong.imlib.IRongCoreEnum.CoreErrorCode
 import io.rong.imlib.RongIMClient
@@ -112,7 +110,7 @@ object MessageProcessor {
         val cId = conversationViewModel.conversationIdentifier ?: return
         IMService
             .innerService
-            .getMessages(
+            .getMessagesRecursively(
                 cId,
                 historyMessageOption,
                 object : IRongCoreCallback.IGetMessageCallbackEx {

+ 42 - 47
module/message/src/main/java/com/adealink/weparty/message/conversation/viewmodel/ConversationViewModel.kt

@@ -350,11 +350,11 @@ open class ConversationViewModel : BaseViewModel(), IConversationViewModel, Mess
             }
             Log.d(TAG_IM_CONV_VM, "onRecallMessage")
             val uiMessage: UiMessage? = findUIMessage(message.messageId)
-            // 当uiMessage = null, 从_newUnReadMessages中遍历
-            if(uiMessage?.getMessage()?.content is ProxyInviteWithdrawMessage) {
+            if (uiMessage?.getMessage()?.content is ProxyInviteWithdrawMessage) {
                 Log.d(TAG_IM_CONV_VM, "ProxyInviteWithdrawMessage recall")
                 dismissDialogIfShow()
             }
+            // 当uiMessage = null, 从_newUnReadMessages中遍历
             val newUnreadMessage: UiMessage? = findNewUnreadMessage(message.messageId)
             if (newUnreadMessage != null) {
                 _newUnReadMessages.remove(newUnreadMessage)
@@ -428,7 +428,7 @@ open class ConversationViewModel : BaseViewModel(), IConversationViewModel, Mess
                 val blockMsgUId = info.blockMsgUId ?: return
                 val blockUiMessage = findUIMessage(blockMsgUId) ?: return
                 if (messageShowInfo.isAsyncAuditing(blockUiMessage.getMessage().content)) {
-                    //因为需要异步审核而被拦截的消息,更新showInfo到消息拓展
+                    //因为需要异步审核而被拦截的消息,设置showInfo到消息拓展
                     blockUiMessage.setExpansion(
                         hashMapOf(MessageExpansionKey.MessageShowInfo.key to info.extra)
                     )
@@ -436,10 +436,9 @@ open class ConversationViewModel : BaseViewModel(), IConversationViewModel, Mess
                     return
                 }
                 if (messageShowInfo.shouldNotShow()) {
-                    //不应该显示消息,展示发送失败
+                    //不应该显示消息,更新消息拓展,后续会被设置为发送失败
                     val blockMessage = blockUiMessage.getMessage()
-                    blockMessage.sentStatus = Message.SentStatus.FAILED
-                    IMService.innerService.setMessageSentStatus(blockMessage, null)
+                    updateMessageExpansion(blockMessage, hashMapOf(MessageExpansionKey.BlockMessageShowInfo.key to info.extra))
                 }
             }
         }
@@ -454,37 +453,35 @@ open class ConversationViewModel : BaseViewModel(), IConversationViewModel, Mess
                 if (!isSameConversationMessage(message)) {
                     return
                 }
-                viewModelScope.launch {
-                    val uiMessage: UiMessage? = findUIMessage(message.messageId)
-                    Log.d(TAG_IM_MSG_EXPANSION, "onMessageExpansionUpdate, messageId: ${message.messageId}, expansion:${expansion}, uiMessageId:${uiMessage?.messageId}")
-                    if (uiMessage != null) {
-                        val messageShowInfoJsonStr = expansion?.get(MessageExpansionKey.MessageShowInfo.key)
-                        if (!messageShowInfoJsonStr.isNullOrEmpty()) {
-                            val messageShowInfo: MessageShowInfo? = froJsonErrorNull(messageShowInfoJsonStr)
-                            val errorMsg = messageShowInfo?.message
-                            val code = messageShowInfo?.code
-                            if (code != 200 && !errorMsg.isNullOrEmpty()) {
-                                messageBlockResLD.send(messageShowInfo)
-                                _pageEventLiveData.send(
-                                    ToastEvent(errorMsg.ifEmpty { "${messageShowInfo.code ?: 0}" })
-                                )
-                                messageBlockResLD.send(messageShowInfo)
-                            }
-                        }
-                        val originExpansion = uiMessage.expansion
-                        val newMap = if (originExpansion != null) {
-                            hashMapOf<String, String>().apply {
-                                putAll(originExpansion)
-                            }
-                        } else {
-                            hashMapOf()
+                val uiMessage: UiMessage? = findUIMessage(message.messageId)
+                Log.d(TAG_IM_MSG_EXPANSION, "onMessageExpansionUpdate, messageId: ${message.messageId}, expansion:${expansion}, uiMessageId:${uiMessage?.messageId}")
+                if (uiMessage != null) {
+                    val messageShowInfoJsonStr = expansion?.get(MessageExpansionKey.MessageShowInfo.key)
+                    if (!messageShowInfoJsonStr.isNullOrEmpty()) {
+                        val messageShowInfo: MessageShowInfo? = froJsonErrorNull(messageShowInfoJsonStr)
+                        val errorMsg = messageShowInfo?.message
+                        val code = messageShowInfo?.code
+                        if (code != 200 && !errorMsg.isNullOrEmpty()) {
+                            messageBlockResLD.send(messageShowInfo)
+                            _pageEventLiveData.send(
+                                ToastEvent(errorMsg.ifEmpty { "${messageShowInfo.code ?: 0}" })
+                            )
+                            messageBlockResLD.send(messageShowInfo)
                         }
-                        for (entry in expansion ?: return@launch) {
-                            newMap[entry.key] = entry.value
+                    }
+                    val originExpansion = uiMessage.expansion
+                    val newMap = if (originExpansion != null) {
+                        hashMapOf<String, String>().apply {
+                            putAll(originExpansion)
                         }
-                        uiMessage.setExpansion(newMap)
-                        refreshSingleMessage(uiMessage)
+                    } else {
+                        hashMapOf()
+                    }
+                    for (entry in expansion ?: return) {
+                        newMap[entry.key] = entry.value
                     }
+                    uiMessage.setExpansion(newMap)
+                    refreshSingleMessage(uiMessage)
                 }
             }
 
@@ -492,20 +489,18 @@ open class ConversationViewModel : BaseViewModel(), IConversationViewModel, Mess
                 keyArray: MutableList<String>?,
                 message: Message?
             ) {
-                viewModelScope.launch {
-                    message ?: return@launch
-                    val uiMessage: UiMessage? = findUIMessage(message.messageId)
-                    if (uiMessage != null) {
-                        val originExpansion = uiMessage.expansion ?: return@launch
-                        val newMap = hashMapOf<String, String>().apply {
-                            putAll(originExpansion)
-                        }
-                        for (key in keyArray ?: return@launch) {
-                            newMap.remove(key)
-                        }
-                        uiMessage.setExpansion(newMap)
-                        refreshSingleMessage(uiMessage)
+                message ?: return
+                val uiMessage: UiMessage? = findUIMessage(message.messageId)
+                if (uiMessage != null) {
+                    val originExpansion = uiMessage.expansion ?: return
+                    val newMap = hashMapOf<String, String>().apply {
+                        putAll(originExpansion)
+                    }
+                    for (key in keyArray ?: return) {
+                        newMap.remove(key)
                     }
+                    uiMessage.setExpansion(newMap)
+                    refreshSingleMessage(uiMessage)
                 }
             }
 

+ 16 - 8
module/message/src/main/java/com/adealink/weparty/message/conversationlist/ConversationListFragment.kt

@@ -66,6 +66,7 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
         )
     }
     private var emptyViewAdded = false
+    private var firstLoad = true
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -135,12 +136,17 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
 
     override fun loadData() {
         super.loadData()
-        conversationListViewModel.getConversationList(
-            loadMore = false,
-            isEventManual = false,
-            delayTime = 0,
-            requireResumed = false
-        )
+        if (firstLoad) {
+            firstLoad = false
+            binding.refreshLayout.autoRefresh()
+        } else {
+            conversationListViewModel.getConversationList(
+                loadMore = false,
+                isEventManual = false,
+                delayTime = 0,
+                requireResumed = false
+            )
+        }
     }
 
     override fun onResume() {
@@ -198,7 +204,8 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
         conversationListViewModel.getConversationList(
             loadMore = false,
             isEventManual = true,
-            delayTime = 0
+            delayTime = 0,
+            requireResumed = false
         )
     }
 
@@ -206,7 +213,8 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
         conversationListViewModel.getConversationList(
             loadMore = true,
             isEventManual = true,
-            delayTime = 0
+            delayTime = 0,
+            requireResumed = false
         )
     }
 

+ 31 - 54
module/message/src/main/java/com/adealink/weparty/message/conversationlist/viewmodel/ConversationListViewModel.kt

@@ -41,20 +41,19 @@ import com.adealink.weparty.message.conversation.provider.ConversationClickType
 import com.adealink.weparty.message.conversationlist.model.BaseUiConversation
 import com.adealink.weparty.message.conversationlist.model.SingleConversation
 import com.adealink.weparty.message.data.ConversationListType
+import com.adealink.weparty.message.datasource.local.MessageLocalService
 import com.adealink.weparty.message.datasource.remote.MessageHttpService
 import com.adealink.weparty.message.event.Event
 import com.adealink.weparty.message.listener.IIMUserInfoListener
 import com.adealink.weparty.message.manager.messageManager
 import com.adealink.weparty.message.userinfo.IMUserInfoManager
 import com.adealink.weparty.message.userinfo.model.IMUserInfo
-import com.adealink.weparty.module.message.data.BatchGetSessionListReq
 import com.adealink.weparty.module.message.data.GetSessionListReq
 import com.adealink.weparty.module.message.data.GetSessionListRes
 import com.adealink.weparty.module.message.data.UpdateSessionListOperate
 import com.adealink.weparty.module.message.data.UpdateSessionListReq
 import com.adealink.weparty.module.moment.MomentModule
 import com.adealink.weparty.module.moment.dot.momentMsgDot
-import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.profile.data.OnlineInRoomStatus
 import com.scwang.smart.refresh.layout.constant.RefreshState
 import io.rong.imlib.IRongCoreCallback
@@ -97,7 +96,6 @@ open class ConversationListViewModel(
     private var delayRefreshTime = 5000
     private var sizePerPage: Int
     private var pageLimit: Int
-    private var firstLoadFlag = 0
 
     /** 是否优先显示置顶会话(查询结果的排序方式,是否置顶优先,true 表示置顶会话优先返回,false 结果只以会话时间排序)  */
     private var isTopPriority: Boolean
@@ -405,8 +403,7 @@ open class ConversationListViewModel(
     /**
      * 获取会话列表。 此处借鉴前端的函数节流思想,在 [.REFRESH_INTERVAL] 时间内,丢弃掉其它触发,只做一次执行。 以便提高接受大量消息时的刷新性能。
      *
-     * @param loadMore 是否拉取更多会话
-     * 条会话。
+     * @param loadMore 是否拉取更多会话条会话。
      * @param isEventManual 是否是用手动触发的刷新获取,手动触发的需要主动关闭下
      * @param delayTime 延迟时间,单位毫秒,默认 0 毫秒,即立即执行。
      * @param onlyLoadLocal 是否只加载本地会话,默认 false,即加载本地会话和服务器会话。目前在收到新消息的时的刷新是true,目的是避免频繁调用后端接口
@@ -619,13 +616,11 @@ open class ConversationListViewModel(
         loadMore: Boolean,
         isEventManual: Boolean,
         delayTime: Long,
-        fetchExtraInfo: Boolean = true,
-        onlyLoadLocal: Boolean = false
     ) {
         serialHandler.post {
             loadMoreEnableEventLiveData.send(
                 Event.LoadMoreEnableEvent(
-                    (conversations?.size ?: 0) >= pageLimit
+                    (conversations?.size ?: 0) >= sizePerPage
                 )
             )
             if (loadMore) {
@@ -633,10 +628,7 @@ open class ConversationListViewModel(
                     sizePerPage = conversations.size
                 }
             } else {
-                if (!onlyLoadLocal) {
-                    //只加载本地场景刷新场景,不清空原有会话列表
-                    uiConversationList.clear()
-                }
+                uiConversationList.clear()
             }
             sendRefreshEvent(isEventManual, loadMore)
             if (conversations == null) {
@@ -681,17 +673,6 @@ open class ConversationListViewModel(
                     }
                 }
             }
-            if (onlyLoadLocal) {
-                //只加载本地场景刷新场景,需要重新排序一下
-                uiConversationList.sortWith(
-                    compareByDescending<BaseUiConversation> { it.conversation.isTop }
-                        .thenByDescending { it.conversation.sentTime }
-                )
-            }
-            if (!fetchExtraInfo) {
-                refreshConversationList()
-                return@post
-            }
             if (targetIds.isNotEmpty()) {
                 batchGetTargetExtraInfo(targetIds)
             } else {
@@ -702,36 +683,14 @@ open class ConversationListViewModel(
 
     private fun batchGetTargetExtraInfo(targetIds: Set<Long>) {
         launch {
-            val sessionInfoListDef = async {
-                messageHttpService.batchGetSessionInfo(
-                    BatchGetSessionListReq(targetIds.toList())
-                )
+            val uid2SessionInfoDef = async {
+                messageManager.batchGetSessionInfo(targetIds, cache = true, fromOnlineList = listType == ConversationListType.Online.type)
             }
             val customerListDef = async {
                 messageManager.getCustomerList(cache = true)
             }
-            val sessionInfoListRes = sessionInfoListDef.await()
+            val uid2SessionInfo = uid2SessionInfoDef.await()
             val customerIdSet = customerListDef.await()?.map { it.customerUid }?.toSet() ?: setOf()
-            if (sessionInfoListRes is Rlt.Failed) {
-                val uid2UserInfo =
-                    (ProfileModule.getUsersInfoByUid(targetIds, true) as? Rlt.Success)?.data
-                for (baseUiConversation in uiConversationList) {
-                    val uiConversation = baseUiConversation.conversation
-                    val targetIdLong = uiConversation.targetId.safeToLong(0)
-                    val userInfo = uid2UserInfo?.get(targetIdLong) ?: continue
-                    baseUiConversation.onUserInfoUpdate(IMUserInfo(userInfo))
-                    baseUiConversation.onCustomerUpdate(customerIdSet.contains(targetIdLong))
-                }
-                refreshConversationList()
-                return@launch
-            }
-            val uid2SessionInfo =
-                (sessionInfoListRes as? Rlt.Success)?.data?.data?.sessionInfos?.associateBy { it.uid }
-                    ?: hashMapOf()
-            if (uid2SessionInfo.isEmpty()) {
-                refreshConversationList()
-                return@launch
-            }
             for (baseUiConversation in uiConversationList) {
                 val uiConversation = baseUiConversation.conversation
                 val targetIdLong = uiConversation.targetId.safeToLong(0)
@@ -1173,7 +1132,7 @@ open class ConversationListViewModel(
                 TAG_IM_CONV_LIST_VM,
                 "ConversationListResultCallback.onSuccess, session:$session ,data:$log"
             )
-            viewModel.doUpdate(conversations, isLoadMore, isEventManual, delayTime, onlyLoadLocal = onlyLoadLocal)
+            viewModel.doUpdate(conversations, isLoadMore, isEventManual, delayTime)
         }
 
         override fun onError(e: IRongCoreEnum.CoreErrorCode) {
@@ -1224,18 +1183,36 @@ open class ConversationListViewModel(
                     isLoadMore,
                     isEventManual,
                     delayTime,
-                    onlyLoadLocal = true
                 )
                 return
             }
+            if (isLoadMore && sizePerPage <= MessageLocalService.firstLoadMoreCnt) {
+                // All列表非首次拉取更多,直接返回本地会话列表
+                viewModel.doUpdate(
+                    localList,
+                    true,
+                    isEventManual,
+                    delayTime,
+                )
+                return
+            }
+            if (!isLoadMore && !MessageLocalService.firstLoadAll) {
+                //All列表非首次加载,直接返回本地会话列表
+                viewModel.doUpdate(
+                    localList,
+                    false,
+                    isEventManual,
+                    delayTime,
+                )
+                return
+            }
+            // All列表首次加载(拉取更多)才拉取业务服务器记录的1v1会话列表,用于同步卸载重装场景的会话列表
             viewModel.viewModelScope.launch {
-                if (viewModel.firstLoadFlag++ == 0) {
-                    //首次加载才先更新,加快首次显示速度
-                    viewModel.doUpdate(conversations, isLoadMore, isEventManual, delayTime, false)
-                }
                 // 拉业务服务端记录的1v1会话列表
                 val bizSessionResRlt = viewModel.getBizSessionList(0, sizePerPage)
                 bizSessionResRlt.onSuccess { res ->
+                    MessageLocalService.firstLoadAll = false
+                    MessageLocalService.firstLoadMoreCnt = sizePerPage
                     val bizSessionList = res.data?.sessionInfos
                     if (bizSessionList.isNullOrEmpty()) {
                         viewModel.doUpdate(conversations, isLoadMore, isEventManual, delayTime)

+ 5 - 0
module/message/src/main/java/com/adealink/weparty/message/datasource/local/MessageLocalService.kt

@@ -37,4 +37,9 @@ object MessageLocalService : TypeDelegationPrefs(
 
     var merchantDealWarnMsgSent: Boolean by PrefUserKey("key_merchant_deal_warn_msg_sent", false)
     var closeOpenNotificationTip: Boolean by PrefUserKey("key_close_open_notification_tip", false)
+
+    // All列表首次加载
+    var firstLoadAll: Boolean by PrefUserKey("key_first_load_all", true)
+    // All列表首次LoadMore的数量
+    var firstLoadMoreCnt: Int by PrefUserKey("key_first_load_more_cnt", 0)
 }

+ 3 - 0
module/message/src/main/java/com/adealink/weparty/message/manager/IMessageManager.kt

@@ -7,6 +7,7 @@ import com.adealink.frame.router.Router
 import com.adealink.weparty.message.listener.IMessageListener
 import com.adealink.weparty.module.account.Account
 import com.adealink.weparty.module.message.data.CustomerInfo
+import com.adealink.weparty.module.message.data.SessionInfo
 import com.adealink.weparty.module.room.Room
 
 interface IMessageManager : IBaseFrame<IMessageListener> {
@@ -27,6 +28,8 @@ interface IMessageManager : IBaseFrame<IMessageListener> {
 
     suspend fun getDiamondAgentOrderStatus(orderId: String): Rlt<Int>
 
+    suspend fun batchGetSessionInfo(targetIdSet: Set<Long>, cache: Boolean = true, fromOnlineList: Boolean = false): Map<Long, SessionInfo>
+
     companion object {
         val NEW_MSG_DISMISS_ACTIVITIES = hashSetOf<String>().apply {
             Router.getClazz(Account.Login.PATH)?.let { this.add(it.name) }

+ 131 - 1
module/message/src/main/java/com/adealink/weparty/message/manager/MessageManager.kt

@@ -9,6 +9,7 @@ import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.base.fastLazy
 import com.adealink.frame.coroutine.dispatcher.Dispatcher
+import com.adealink.frame.data.json.froJsonErrorNull
 import com.adealink.frame.ext.findAndSetSpan
 import com.adealink.frame.frame.BaseFrame
 import com.adealink.frame.image.imageService
@@ -19,6 +20,7 @@ import com.adealink.frame.imkit.model.TAG_IM_UI
 import com.adealink.frame.log.Log
 import com.adealink.frame.network.ISocketNotify
 import com.adealink.frame.network.data.Res
+import com.adealink.frame.storage.cache.TimeoutLruCache
 import com.adealink.frame.util.AppUtil
 import com.adealink.frame.util.ONE_MINUTE
 import com.adealink.weparty.App
@@ -36,6 +38,8 @@ import com.adealink.weparty.message.constant.TAG_IM_MSG_RECEIVE
 import com.adealink.weparty.message.constant.TAG_IM_NEW_MSG_NTF
 import com.adealink.weparty.message.constant.TAG_MESSAGE
 import com.adealink.weparty.message.conversation.ConversationActivity
+import com.adealink.weparty.message.conversation.data.MessageExpansionKey
+import com.adealink.weparty.message.conversation.data.MessageShowInfo
 import com.adealink.weparty.message.conversation.message.APP_INFO_MESSAGE_TYPE
 import com.adealink.weparty.message.conversation.message.CPInviteMessage
 import com.adealink.weparty.message.conversation.message.CP_INVITE_MESSAGE_TYPE
@@ -48,14 +52,20 @@ import com.adealink.weparty.message.userinfo.IMUserInfoManager
 import com.adealink.weparty.message.userinfo.UserDataProvider
 import com.adealink.weparty.message.userinfo.model.IMUserInfo
 import com.adealink.weparty.module.account.AccountModule
+import com.adealink.weparty.module.couple.CoupleModule
 import com.adealink.weparty.module.couple.data.CoupleOnlineFloatData
+import com.adealink.weparty.module.couple.data.CoupleUserInfo
+import com.adealink.weparty.module.couple.data.IntimacyValInfo
+import com.adealink.weparty.module.couple.listener.ICoupleListener
 import com.adealink.weparty.module.message.data.BatchGetFirstChatTsReq
+import com.adealink.weparty.module.message.data.BatchGetSessionListReq
 import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.message.data.EnterConversationFrom
 import com.adealink.weparty.module.message.data.IMMessageScene
 import com.adealink.weparty.module.message.data.Im1v1ContinuousSendMessageDeductNotifyInfo
 import com.adealink.weparty.module.message.data.OFFICIAL_TARGET_ID
 import com.adealink.weparty.module.message.data.SendQuickMessageReq
+import com.adealink.weparty.module.message.data.SessionInfo
 import com.adealink.weparty.module.message.data.isSystemTarget
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.profile.data.UserInfo
@@ -66,10 +76,12 @@ import com.adealink.weparty.push.data.WeNextPushMessage
 import com.adealink.weparty.ui.home.util.HomeUIUtil
 import com.adealink.weparty.widget.floatview.FloatViewFactory
 import com.facebook.common.util.UriUtil
+import io.rong.imlib.IMessageExpansionListener
 import io.rong.imlib.MessageTag
 import io.rong.imlib.RongIMClient
 import io.rong.imlib.model.Message
 import io.rong.imlib.model.MessageContent
+import kotlinx.coroutines.async
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -82,7 +94,7 @@ import com.adealink.weparty.module.message.Message as MESSAGE_ROUTER
 val messageManager: IMessageManager by lazy { MessageManager() }
 
 class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
-    UserDataProvider.UserInfoProvider {
+    UserDataProvider.UserInfoProvider, ICoupleListener {
     private val messageHttpService by lazy {
         App.instance.networkService.getHttpService(MessageHttpService::class.java)
     }
@@ -196,6 +208,33 @@ class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
             }
         }
 
+    private val messageExpansionListener = object : RongIMClient.MessageExpansionListener {
+        override fun onMessageExpansionUpdate(
+            expansion: MutableMap<String, String>?,
+            message: Message?
+        ) {
+            message ?: return
+            val blockMessageShowInfoExpansion = expansion?.get(MessageExpansionKey.BlockMessageShowInfo.key)
+            if (blockMessageShowInfoExpansion != null && message.sentStatus != Message.SentStatus.FAILED) {
+                val blockMessageShowInfo = froJsonErrorNull<MessageShowInfo>(blockMessageShowInfoExpansion)
+                if (blockMessageShowInfo?.shouldNotShow() == true) {
+                    //被拦截消息不应该显示,设置消息发送状态为发送失败
+                    message.sentStatus = Message.SentStatus.FAILED
+                    IMService.innerService.setMessageSentStatus(message, null)
+                }
+            }
+        }
+
+        override fun onMessageExpansionRemove(
+            keyArray: MutableList<String>?,
+            message: Message?
+        ) {
+            //Ntd.
+        }
+
+    }
+
+
     init {
         App.instance.networkService.subscribeNotify(notificationMessageNotify)
         App.instance.networkService.subscribeNotify(im1v1ContinuousSendMessageDeductNotify)
@@ -205,6 +244,8 @@ class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
         IMUserInfoManager.setUserInfoProvider(this)
         IMService.innerService.setMessageInterceptor(messageInterceptor)
         IMService.innerService.addOnReceiveMessageListener(onReceiveMessageListener)
+        IMService.innerService.addMessageExpansionListener(messageExpansionListener)
+        CoupleModule.addListener(this)
     }
 
     override suspend fun sendQuickMessage(
@@ -517,4 +558,93 @@ class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
             }
         }
     }
+
+    private val sessionInfoCache = TimeoutLruCache<Long, SessionInfo>(500, 5 * 60 * 1000)//最多500个,缓存5分钟
+    override suspend fun batchGetSessionInfo(
+        targetIdSet: Set<Long>,
+        cache: Boolean,
+        fromOnlineList: Boolean
+    ): Map<Long, SessionInfo> {
+        val resultMap = mutableMapOf<Long, SessionInfo>()
+        val idsToFetch = mutableSetOf<Long>()
+
+        if (cache) {
+            for (id in targetIdSet) {
+                val cached = sessionInfoCache[id]
+                if (cached != null) {
+                    resultMap[id] = cached
+                    if (fromOnlineList) {
+                        cached.onlineStatus = 1
+                    }
+                } else {
+                    idsToFetch.add(id)
+                }
+            }
+
+            if (idsToFetch.isEmpty()) {
+                return resultMap
+            }
+        } else {
+            idsToFetch.addAll(targetIdSet)
+        }
+
+        val sessionInfoListDef = async {
+            messageHttpService.batchGetSessionInfo(BatchGetSessionListReq(idsToFetch.toList()))
+        }
+
+        val sessionInfoRes = sessionInfoListDef.await()
+
+        if (sessionInfoRes !is Rlt.Success) {
+            return resultMap
+        }
+
+        val fetchedSessionInfos = (sessionInfoRes as? Rlt.Success)?.data?.data?.sessionInfos ?: emptyList()
+        for (info in fetchedSessionInfos) {
+            sessionInfoCache.put(info.uid, info)
+            resultMap[info.uid] = info
+        }
+
+        return resultMap
+    }
+
+    override fun onAddIntimacyVal(intimacyValInfo: IntimacyValInfo) {
+        super.onAddIntimacyVal(intimacyValInfo)
+        if (!intimacyValInfo.isSelfInvolved()) {
+            return
+        }
+        launch {
+            val selfUid = AccountModule.uid
+            val targetUid = arrayListOf(intimacyValInfo.uid, intimacyValInfo.peerUid).apply {
+                remove(selfUid)
+            }.getOrNull(0) ?: return@launch
+            val cache = sessionInfoCache[targetUid]
+            if (cache != null) {
+                val oldIntimacyInfo = cache.userIntimacyInfo ?: return@launch
+                oldIntimacyInfo.exp += intimacyValInfo.value
+            }
+        }
+    }
+
+    override fun onGetMyIntimacyInfos(coupleUserInfos: List<CoupleUserInfo>) {
+        super.onGetMyIntimacyInfos(coupleUserInfos)
+        launch {
+            coupleUserInfos.forEach { newIntimacyInfo ->
+                val cache = sessionInfoCache[newIntimacyInfo.uid]
+                if (cache != null) {
+                    val oldIntimacyInfo = cache.userIntimacyInfo
+                    if (oldIntimacyInfo == null) {
+                        cache.userIntimacyInfo = newIntimacyInfo
+                        return@launch
+                    }
+                    if (oldIntimacyInfo.type != newIntimacyInfo.type
+                        && oldIntimacyInfo.friendType != newIntimacyInfo.friendType
+                        && oldIntimacyInfo.level != newIntimacyInfo.level
+                        && oldIntimacyInfo.exp != newIntimacyInfo.exp
+                    ) {
+                        cache.userIntimacyInfo = newIntimacyInfo
+                    }
+                }
+            }
+        }
+    }
 }

+ 7 - 6
module/message/src/main/res/layout/fragment_conversation.xml

@@ -27,15 +27,15 @@
         android:id="@+id/top_bar_layout"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingStart="16dp"
-        android:paddingEnd="16dp"
+        android:paddingHorizontal="7dp"
         android:background="@color/white"
         app:layout_constraintTop_toTopOf="parent">
 
         <androidx.appcompat.widget.AppCompatImageView
             android:id="@+id/back_iv"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
+            android:layout_width="34dp"
+            android:layout_height="34dp"
+            android:padding="5dp"
             android:rotationY="@integer/locale_mirror_flip"
             android:src="@drawable/commonui_back_black_48_ic"
             app:layout_constraintBottom_toBottomOf="parent"
@@ -44,8 +44,9 @@
 
         <androidx.appcompat.widget.AppCompatImageView
             android:id="@+id/more_iv"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
+            android:layout_width="34dp"
+            android:layout_height="34dp"
+            android:padding="5dp"
             android:src="@drawable/common_top_bar_more_24_ic"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"