Просмотр исходного кода

feat: 系统推送&客服 (#18)

* feat: 系统推送和客服

* feat: 客服不展示CP表情包

* feat: 增加融云连接断开提示条

* feat: 消息拦截错误提示问题

* fix: 客服问题修复

* fix: 增加新消息提醒日志

* feat: 先去掉系统消息隐藏逻辑

* feat: 官方标签&会话隐藏逻辑修改

---------

Co-authored-by: wutiaorong <wutiaorong@gmail.com>
Talon 11 месяцев назад
Родитель
Сommit
df9c155f6b
68 измененных файлов с 1026 добавлено и 368 удалено
  1. 5 0
      app/src/main/java/com/adealink/weparty/module/message/IMessageService.kt
  2. 17 0
      app/src/main/java/com/adealink/weparty/module/message/MessageModule.kt
  3. 14 1
      app/src/main/java/com/adealink/weparty/module/message/data/MessageData.kt
  4. 6 1
      app/src/main/java/com/adealink/weparty/module/message/viewmodel/IMessageViewModel.kt
  5. 35 0
      app/src/main/java/com/adealink/weparty/module/profile/data/UserConfigType.kt
  6. 3 1
      app/src/main/java/com/adealink/weparty/module/profile/decorate/data/UserDecorateData.kt
  7. 105 0
      app/src/main/java/com/adealink/weparty/network/ConnectionStatusManager.kt
  8. 7 48
      app/src/main/java/com/adealink/weparty/network/NetworkManager.kt
  9. 37 0
      app/src/main/java/com/adealink/weparty/network/view/NetworkReConnectFloatView.kt
  10. 1 0
      app/src/main/java/com/adealink/weparty/network/view/NetworkReconnectFloatData.kt
  11. BIN
      app/src/main/res/drawable-xhdpi/common_customer_server_ic.webp
  12. BIN
      app/src/main/res/drawable-xhdpi/common_label_bigv_ic.webp
  13. BIN
      app/src/main/res/drawable-xhdpi/common_label_official_inside_ic.webp
  14. 11 0
      app/src/main/res/drawable/common_customer_service_bg.xml
  15. 2 2
      app/src/main/res/drawable/label_official_bg.xml
  16. 4 4
      app/src/main/res/layout/layout_profile_label_official.xml
  17. 1 0
      app/src/main/res/values-ar/strings.xml
  18. 1 0
      app/src/main/res/values-zh/strings.xml
  19. 4 0
      app/src/main/res/values/colors.xml
  20. 2 1
      app/src/main/res/values/strings.xml
  21. 0 1
      frame/imkit/src/main/java/com/adealink/frame/imkit/IMService.kt
  22. 16 5
      module/emotion/src/main/java/com/adealink/weparty/emotion/panel/EmotionPanelFragment.kt
  23. 9 0
      module/message/src/main/java/com/adealink/weparty/message/MessageServiceImpl.kt
  24. 0 1
      module/message/src/main/java/com/adealink/weparty/message/config/ConversationListConfig.kt
  25. 8 0
      module/message/src/main/java/com/adealink/weparty/message/config/DefaultConversationListProcessor.kt
  26. 1 0
      module/message/src/main/java/com/adealink/weparty/message/constant/Tags.kt
  27. 1 4
      module/message/src/main/java/com/adealink/weparty/message/conversation/ConversationFragment.kt
  28. 59 16
      module/message/src/main/java/com/adealink/weparty/message/conversation/comp/ConversationTargetInfoComp.kt
  29. 21 2
      module/message/src/main/java/com/adealink/weparty/message/conversation/data/MessageExpansion.kt
  30. 3 0
      module/message/src/main/java/com/adealink/weparty/message/conversation/provider/BaseMessageItemProvider.kt
  31. 10 1
      module/message/src/main/java/com/adealink/weparty/message/conversation/setting/ConversationSettingActivity.kt
  32. 3 1
      module/message/src/main/java/com/adealink/weparty/message/conversation/viewmodel/ConversationViewModel.kt
  33. 0 60
      module/message/src/main/java/com/adealink/weparty/message/conversationlist/ConversationListFragment.kt
  34. 3 0
      module/message/src/main/java/com/adealink/weparty/message/conversationlist/model/BaseUiConversation.kt
  35. 8 0
      module/message/src/main/java/com/adealink/weparty/message/conversationlist/model/SingleConversation.kt
  36. 16 0
      module/message/src/main/java/com/adealink/weparty/message/conversationlist/provider/BaseConversationProvider.kt
  37. 14 74
      module/message/src/main/java/com/adealink/weparty/message/conversationlist/viewmodel/ConversationListViewModel.kt
  38. 0 7
      module/message/src/main/java/com/adealink/weparty/message/data/NoticeContent.kt
  39. 14 0
      module/message/src/main/java/com/adealink/weparty/message/datasource/remote/MessageHttpService.kt
  40. 32 14
      module/message/src/main/java/com/adealink/weparty/message/manager/IMConnectManager.kt
  41. 14 3
      module/message/src/main/java/com/adealink/weparty/message/manager/IMNotificationFrequencyLimiter.kt
  42. 8 0
      module/message/src/main/java/com/adealink/weparty/message/manager/IMessageManager.kt
  43. 40 3
      module/message/src/main/java/com/adealink/weparty/message/manager/MessageManager.kt
  44. 36 9
      module/message/src/main/java/com/adealink/weparty/message/viewmodel/MessageViewModel.kt
  45. 0 35
      module/message/src/main/res/layout/conversationlist_notice_view.xml
  46. 1 8
      module/message/src/main/res/layout/fragment_conversationlist.xml
  47. 45 2
      module/message/src/main/res/layout/im_conversationlist_item.xml
  48. 0 35
      module/message/src/main/res/layout/im_conversationlist_notice_view.xml
  49. 10 0
      module/message/src/main/res/layout/im_message_item.xml
  50. 62 7
      module/message/src/main/res/layout/layout_conversation_top_bar_center_new.xml
  51. 0 5
      module/message/src/main/res/values-ar/im_strings.xml
  52. 0 5
      module/message/src/main/res/values-zh/im_strings.xml
  53. 0 5
      module/message/src/main/res/values/im_strings.xml
  54. 1 1
      module/moment/src/main/res/layout/item_moment_gift.xml
  55. 1 1
      module/moment/src/main/res/layout/item_moment_like_avatar.xml
  56. 1 1
      module/moment/src/main/res/layout/item_moment_msgs_list.xml
  57. 1 1
      module/moment/src/main/res/layout/layout_moment_member_info_view.xml
  58. 56 0
      module/profile/src/main/java/com/adealink/weparty/profile/me/MeFragment.kt
  59. BIN
      module/profile/src/main/res/drawable-xhdpi/profile_customer_service_ic.webp
  60. 7 0
      module/profile/src/main/res/drawable/profile_customer_online_bg.xml
  61. 11 2
      module/profile/src/main/res/layout/fragment_me.xml
  62. 73 0
      module/profile/src/main/res/layout/layout_custom_service_list_item.xml
  63. 44 0
      module/setting/src/main/java/com/adealink/weparty/setting/chat/ChatSettingActivity.kt
  64. 41 1
      module/setting/src/main/java/com/adealink/weparty/setting/chat/ChatSettingViewModel.kt
  65. 89 0
      module/setting/src/main/res/layout/activity_chat_setting.xml
  66. 4 0
      module/setting/src/main/res/values-ar/strings.xml
  67. 4 0
      module/setting/src/main/res/values-zh/strings.xml
  68. 4 0
      module/setting/src/main/res/values/strings.xml

+ 5 - 0
app/src/main/java/com/adealink/weparty/module/message/IMessageService.kt

@@ -7,6 +7,7 @@ import com.adealink.frame.base.Rlt
 import com.adealink.frame.imkit.manager.UnReadMessageManager
 import com.adealink.weparty.commonui.widget.floatview.data.IBaseFloatData
 import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
+import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.message.viewmodel.IMessageViewModel
 import io.rong.imlib.IRongCoreCallback
 import io.rong.imlib.model.Conversation
@@ -39,4 +40,8 @@ interface IMessageService : IService<IMessageService> {
         callback: IRongCoreCallback.ResultCallback<List<Conversation>?>,
         vararg conversationTypes: Conversation.ConversationType
     )
+    suspend fun getCustomerList(
+        cache: Boolean = true,
+    ): List<CustomerInfo>?
+    suspend fun checkIsCustomerService(targetId: Long, cache: Boolean): Boolean
 }

+ 17 - 0
app/src/main/java/com/adealink/weparty/module/message/MessageModule.kt

@@ -9,6 +9,7 @@ import com.adealink.frame.imkit.manager.UnReadMessageManager
 import com.adealink.weparty.R
 import com.adealink.weparty.commonui.widget.floatview.data.IBaseFloatData
 import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
+import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.message.viewmodel.IMessageViewModel
 import io.rong.imlib.IRongCoreCallback
 import io.rong.imlib.model.Conversation
@@ -65,6 +66,14 @@ object MessageModule : BaseDynamicModule<IMessageService>(IMessageService::class
         getService().getUnreadConversationList(callback, *conversationTypes)
     }
 
+    override suspend fun getCustomerList(cache: Boolean): List<CustomerInfo>? {
+        return getService().getCustomerList(cache)
+    }
+
+    override suspend fun checkIsCustomerService(targetId: Long, cache: Boolean): Boolean {
+        return getService().checkIsCustomerService(targetId, cache)
+    }
+
     override fun emptyService(): IMessageService {
 
         return object : IMessageService {
@@ -111,6 +120,14 @@ object MessageModule : BaseDynamicModule<IMessageService>(IMessageService::class
 
             }
 
+            override suspend fun getCustomerList(cache: Boolean): List<CustomerInfo>? {
+                return null
+            }
+
+            override suspend fun checkIsCustomerService(targetId: Long, cache: Boolean): Boolean {
+                return false
+            }
+
             override fun getService(): IMessageService? {
                 return null
             }

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

@@ -61,7 +61,8 @@ data class SendQuickMessageReq(
 enum class IMMessageScene(val scene: Int) {
     NORMAL(1),//普通消息
     QUICK_MESSAGE(2),//快捷消息(打招呼
-    PRIVATE_PAGE_QUICK_MESSAGE(3)//私聊页(打招呼)
+    PRIVATE_PAGE_QUICK_MESSAGE(3),//私聊页(打招呼)
+    SYSTEM_MATCH_MESSAGE(4),//系统配对消息
 }
 
 data class BatchGetFirstChatTsReq(
@@ -196,4 +197,16 @@ data class SendRCMessageReq(
     val target: String,//目标对象,可以是单个用户或者群"
     @SerializedName("isIncludeSender")
     val isIncludeSender: Int,//是否需要将消息同步给发送方,0否,1是
+)
+
+@JsonAdapter(ExtReflectiveTypeAdapterFactory::class)
+data class CustomerInfo(
+    @SerializedName("customerUid") val customerUid: Long,
+    @SerializedName("isOnline") val isOnline: Boolean,
+)
+
+@JsonAdapter(ExtReflectiveTypeAdapterFactory::class)
+data class BatchGetCustomerListRes(
+    @SerializedName("customerInfoList")
+    val customerInfoList: List<CustomerInfo>,
 )

+ 6 - 1
app/src/main/java/com/adealink/weparty/module/message/viewmodel/IMessageViewModel.kt

@@ -3,9 +3,9 @@ package com.adealink.weparty.module.message.viewmodel
 import androidx.lifecycle.LiveData
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.mvvm.livedata.ExtLiveData
-import com.adealink.frame.mvvm.livedata.NoSendCacheValueLiveData
 import com.adealink.weparty.module.couple.data.IntimacyPrivilegeType
 import com.adealink.weparty.module.message.data.ChatPageDetailRes
+import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.message.data.GetCustomQuickMessageRes
 import com.adealink.weparty.module.message.data.Im1v1ContinuousSendMessageDeductNotifyInfo
 import com.adealink.weparty.module.message.data.QuickMessageInfo
@@ -30,4 +30,9 @@ interface IMessageViewModel {
     fun updateCustomQuickMessage(index: Int, messageInfo: QuickMessageInfo): LiveData<Rlt<Any>>
 
     fun sendEmotionMessage(emotionId: Int, toUid: Long): LiveData<Rlt<Any>>
+
+    /**
+     * 获取客服信息
+     */
+    fun getCustomerInfo(): LiveData<Rlt<CustomerInfo>>
 }

+ 35 - 0
app/src/main/java/com/adealink/weparty/module/profile/data/UserConfigType.kt

@@ -85,6 +85,8 @@ enum class UserConfigType(val type: Int, val clazz: Class<*>? = null) : Parcelab
     USER_CUSTOM_CHAT_PRICE(149), //用户自定义聊天价格
     USER_CHAT_ACHIEVEMENT(150), //聊天成就
     USER_PRIVACY_ALBUM(151), // 私密相册
+    USER_RECV_SYSTEM_PUSH(152), // 是否接受系统推送
+    USER_SEND_SYSTEM_PUSH(153), // 是否发送系统推送
 
     USER_CONFIG_UNKNOWN(999); //无效配置
 
@@ -188,11 +190,30 @@ data class UserCommonConfig(
         return (userConfigValue?.get(UserConfigType.USER_CHAT_ACHIEVEMENT.type)
             ?.convertToUserConfigContent() as? UserChatAchievementContent)
     }
+
     fun getPrivacyAlbumList(): List<String>? {
         return (userConfigValue?.get(UserConfigType.USER_PRIVACY_ALBUM.type)
             ?.convertToUserConfigContent() as? UserPrivacyAlbumContent)?.getUserPrivacyAlbumList()
     }
 
+    fun getReceiveSystemPushSwitch(): Boolean {
+        return (userConfigValue?.get(UserConfigType.USER_RECV_SYSTEM_PUSH.type)
+            ?.convertToUserConfigContent() as? UserSystemPushConfigContent)?.open == 1
+    }
+
+    fun updateReceiveSystemPushSwitch(open: Int) {
+        userConfigValue?.get(UserConfigType.USER_RECV_SYSTEM_PUSH.type)?.intDataValue1 = open.toLong()
+    }
+
+    fun getSendSystemPushSwitch(): Boolean {
+        return (userConfigValue?.get(UserConfigType.USER_SEND_SYSTEM_PUSH.type)
+            ?.convertToUserConfigContent() as? UserSystemPushConfigContent)?.open == 1
+    }
+
+    fun updateSendSystemPushSwitch(open: Int) {
+        userConfigValue?.get(UserConfigType.USER_SEND_SYSTEM_PUSH.type)?.intDataValue1 = open.toLong()
+    }
+
 }
 
 @Parcelize
@@ -311,6 +332,10 @@ data class UserCommonConfigInfo(
                 UserPrivacyAlbumContent(stringDataValue1)
             }
 
+            UserConfigType.USER_RECV_SYSTEM_PUSH, UserConfigType.USER_SEND_SYSTEM_PUSH -> {
+                UserSystemPushConfigContent(intDataValue1.toInt())
+            }
+
             else -> {
                 EmptyUserConfigContent()
             }
@@ -645,4 +670,14 @@ data class UserPrivacyAlbumContent(
     fun getUserPrivacyAlbumList(): List<String>? {
         return froJsonErrorNull<List<String>>(json)
     }
+}
+
+@JsonAdapter(ExtReflectiveTypeAdapterFactory::class)
+data class UserSystemPushConfigContent(
+    @SerializedName("intDataValue1") val open: Int,
+) : UserConfigContent() {
+    override fun areContentsTheSame(newItem: UserConfigContent): Boolean {
+        val other = newItem as? UserSystemPushConfigContent ?: return false
+        return open == other.open
+    }
 }

+ 3 - 1
app/src/main/java/com/adealink/weparty/module/profile/decorate/data/UserDecorateData.kt

@@ -58,7 +58,9 @@ enum class DecorType(val value: Int) {
     VISITOR_RECORD_ENABLE(144), // 访客记录查看权限--仅对男性用户生效
     STRANGER_ANTI_DISTURB(145), // 陌生消息防打扰
     USER_CUSTOM_CHAT_PRICE(149), //用户自定义聊天价格
-    USER_PRIVACY_ALBUM(151); // 用户私密相册
+    USER_PRIVACY_ALBUM(151), // 用户私密相册
+    USER_RECV_SYSTEM_PUSH(152), // 是否接受系统推送
+    USER_SEND_SYSTEM_PUSH(153); // 是否发送系统推送
 
 
     companion object {

+ 105 - 0
app/src/main/java/com/adealink/weparty/network/ConnectionStatusManager.kt

@@ -0,0 +1,105 @@
+package com.adealink.weparty.network
+
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.coroutine.dispatcher.Dispatcher
+import com.adealink.frame.log.Log
+import com.adealink.frame.util.runOnUiThread
+import com.adealink.weparty.commonui.widget.floatview.WindowManagerProxy
+import com.adealink.weparty.commonui.widget.floatview.data.IBaseFloatData
+import com.adealink.weparty.network.view.NetworkReconnectFloatData
+import com.adealink.weparty.network.view.NetworkReconnectFloatView
+import com.adealink.weparty.widget.floatview.FloatViewFactory
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.launch
+
+enum class ConnectType {
+    BIZ, //业务长链接
+    IM //IM服务连接(融云)
+}
+
+enum class ConnectStatus {
+    Disconnected, //未连接
+    ConnectingOrConnected, //已连接或者连接中
+}
+
+object ConnectionStatusManager: CoroutineScope {
+    private val floatViewFactory by fastLazy { FloatViewFactory() }
+    private var disconnectFloatView: NetworkReconnectFloatView? = null
+    private var connectType2ConnectStatus = hashMapOf<ConnectType, ConnectStatus>()
+    private var connectType2ClickListener = hashMapOf<ConnectType, (()->Unit)?>()
+    val serialHandler = Dispatcher.getSerialHandler()
+    override val coroutineContext = SupervisorJob() + serialHandler.asCoroutineDispatcher()
+
+    fun addReconnectClickListener(type: ConnectType, clickListener: (()->Unit)?) {
+        launch {
+            connectType2ClickListener[type] = clickListener
+        }
+    }
+
+    fun notifyConnectStatus(type: ConnectType, status: ConnectStatus) {
+        launch {
+            connectType2ConnectStatus[type] = status
+            updateFloatView()
+        }
+    }
+
+    /**
+     * 如果业务服务器长链接断开,显示业务服务器长链接断开状态
+     * 如果IM服务断开,显示IM服务断开状态
+     * 如果都没有断开,不显示任何状态
+     */
+    private fun updateFloatView() {
+        val bizConnectStatus = connectType2ConnectStatus[ConnectType.BIZ]
+        if (bizConnectStatus == ConnectStatus.Disconnected) {
+            addDisconnectFloatView(ConnectType.BIZ)
+            return
+        }
+        val imConnectStatus = connectType2ConnectStatus[ConnectType.IM]
+        if (imConnectStatus == ConnectStatus.Disconnected) {
+            addDisconnectFloatView(ConnectType.IM)
+            return
+        }
+        removeDisconnectFloatView()
+    }
+
+    private fun addDisconnectFloatView(type: ConnectType) {
+        runOnUiThread {
+            if (disconnectFloatView != null) {
+                disconnectFloatView?.updateWithConnectType(type)
+                return@runOnUiThread
+            }
+
+            disconnectFloatView =
+                floatViewFactory.createFloatView(NetworkReconnectFloatData(IBaseFloatData.MODE_APPLICATION)) as? NetworkReconnectFloatView
+            disconnectFloatView?.setOnClickListener {
+                removeDisconnectFloatView()
+                connectType2ClickListener[type]?.invoke()
+            }
+            Log.d(
+                TAG_DISCONNECT_TIP,
+                "addDisconnectFloatView, disconnectFloatView:${disconnectFloatView}"
+            )
+            if (disconnectFloatView != null) {
+                WindowManagerProxy.getWindowManager().addView(disconnectFloatView!!)
+                disconnectFloatView!!.updateWithConnectType(type)
+            }
+        }
+    }
+
+    fun removeDisconnectFloatView() {
+        runOnUiThread {
+            Log.d(
+                TAG_DISCONNECT_TIP,
+                "removeDisconnectFloatView, disconnectFloatView:${disconnectFloatView}"
+            )
+            if (disconnectFloatView != null) {
+                WindowManagerProxy.getWindowManager().removeView(disconnectFloatView!!)
+                disconnectFloatView = null
+            }
+        }
+    }
+
+    private const val TAG_DISCONNECT_TIP = "tag_disconnect_tip"
+}

+ 7 - 48
app/src/main/java/com/adealink/weparty/network/NetworkManager.kt

@@ -3,7 +3,6 @@ package com.adealink.weparty.network
 import android.app.Application
 import android.os.SystemClock
 import com.adealink.frame.base.Rlt
-import com.adealink.frame.base.fastLazy
 import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.log.Log
 import com.adealink.frame.network.IConnectStateListener
@@ -17,17 +16,11 @@ import com.adealink.frame.util.ActivityLifecycleCallbacksExt
 import com.adealink.frame.util.AppUtil
 import com.adealink.frame.util.OnNetworkListener
 import com.adealink.frame.util.registerNetworkListener
-import com.adealink.frame.util.runOnUiThread
 import com.adealink.frame.util.unregisterNetworkListener
 import com.adealink.weparty.App
-import com.adealink.weparty.commonui.widget.floatview.WindowManagerProxy
-import com.adealink.weparty.commonui.widget.floatview.data.IBaseFloatData
-import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 import com.adealink.weparty.module.account.AccountModule
 import com.adealink.weparty.module.account.ILoginListener
 import com.adealink.weparty.module.room.RoomModule
-import com.adealink.weparty.network.view.NetworkReconnectFloatData
-import com.adealink.weparty.widget.floatview.FloatViewFactory
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.SupervisorJob
@@ -45,7 +38,6 @@ class NetworkManager : INetworkManager, ILoginListener, IConnectStateListener,
     companion object {
         private const val TAG_NETWORK = "tag_network"
         private const val TAG_PING = "tag_ping"
-        private const val TAG_DISCONNECT_TIP = "tag_disconnect_tip"
         private const val PING_DEFAULT_INTERVAL = 10 * 1000L
         private const val RTC_LEAVE_CHANNEL_DELAY = 20_000L
     }
@@ -54,8 +46,6 @@ class NetworkManager : INetworkManager, ILoginListener, IConnectStateListener,
         App.instance.networkService.getSocketService(PingSocketService::class.java)
     }
 
-    private val floatViewFactory by fastLazy { FloatViewFactory() }
-    private var disconnectFloatView: BaseFloatView? = null
     private var pingInterval = PING_DEFAULT_INTERVAL
     private val pingRunnable by lazy {
         Runnable {
@@ -72,6 +62,10 @@ class NetworkManager : INetworkManager, ILoginListener, IConnectStateListener,
         if (AccountModule.isLogin()) {
             handleLogin()
         }
+        ConnectionStatusManager.addReconnectClickListener(
+            ConnectType.BIZ,
+            this::socketConnect
+        )
     }
 
     private fun handleLogin() {
@@ -97,7 +91,7 @@ class NetworkManager : INetworkManager, ILoginListener, IConnectStateListener,
         App.instance.networkService.unregisterConnectStateListener(this)
         AppUtil.unregisterActivityLifecycleCallbacks(this)
         unregisterNetworkListener(this)
-        removeDisconnectFloatView()
+        ConnectionStatusManager.removeDisconnectFloatView()
         stopPing()
     }
 
@@ -106,7 +100,7 @@ class NetworkManager : INetworkManager, ILoginListener, IConnectStateListener,
         reason: ISocket.ConnectStateReason
     ) {
         if (connectState == ISocket.ConnectState.DISCONNECT) {
-            addDisconnectFloatView()
+            ConnectionStatusManager.notifyConnectStatus(ConnectType.BIZ, ConnectStatus.Disconnected)
             stopPing()
             if (rtcLeaveChannelJob == null) {
                 rtcLeaveChannelJob = launch {
@@ -119,7 +113,7 @@ class NetworkManager : INetworkManager, ILoginListener, IConnectStateListener,
                 }
             }
         } else {
-            removeDisconnectFloatView()
+            ConnectionStatusManager.notifyConnectStatus(ConnectType.BIZ, ConnectStatus.ConnectingOrConnected)
             //正在连接或已连接
             if (connectState == ISocket.ConnectState.CONNECTED) {
                 startPing(0)
@@ -137,41 +131,6 @@ class NetworkManager : INetworkManager, ILoginListener, IConnectStateListener,
         }
     }
 
-    private fun addDisconnectFloatView() {
-        runOnUiThread {
-            if (disconnectFloatView != null) {
-                return@runOnUiThread
-            }
-
-            disconnectFloatView =
-                floatViewFactory.createFloatView(NetworkReconnectFloatData(IBaseFloatData.MODE_APPLICATION))
-            disconnectFloatView?.setOnClickListener {
-                removeDisconnectFloatView()
-                socketConnect()
-            }
-            Log.d(
-                TAG_DISCONNECT_TIP,
-                "addDisconnectFloatView, disconnectFloatView:${disconnectFloatView}"
-            )
-            if (disconnectFloatView != null) {
-                WindowManagerProxy.getWindowManager().addView(disconnectFloatView!!)
-            }
-        }
-    }
-
-    private fun removeDisconnectFloatView() {
-        runOnUiThread {
-            Log.d(
-                TAG_DISCONNECT_TIP,
-                "removeDisconnectFloatView, disconnectFloatView:${disconnectFloatView}"
-            )
-            if (disconnectFloatView != null) {
-                WindowManagerProxy.getWindowManager().removeView(disconnectFloatView!!)
-                disconnectFloatView = null
-            }
-        }
-    }
-
     private fun ping() {
         launch {
             Log.d(TAG_PING, "ping")

+ 37 - 0
app/src/main/java/com/adealink/weparty/network/view/NetworkReConnectFloatView.kt

@@ -3,13 +3,19 @@ package com.adealink.weparty.network.view
 import android.app.Activity
 import android.graphics.PixelFormat
 import android.view.*
+import com.adealink.frame.aab.util.getCompatString
 import com.adealink.weparty.commonui.widget.floatview.data.IBaseFloatData
 import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 import com.adealink.frame.router.Router
 import com.adealink.frame.util.DisplayUtil.dp2px
 import com.adealink.frame.util.DisplayUtil.getScreenWidth
+import com.adealink.weparty.AppModule
+import com.adealink.weparty.R
 import com.adealink.weparty.module.account.Account
 import com.adealink.weparty.databinding.LayoutNetworkReconnectFloatViewBinding
+import com.adealink.weparty.module.message.Message
+import com.adealink.weparty.network.ConnectType
+import com.adealink.weparty.ui.home.util.HomeUIUtil
 
 /**
  * Created by sunxiaodong on 2021/5/25.
@@ -20,8 +26,15 @@ class NetworkReconnectFloatView(baseFloatData: IBaseFloatData) : BaseFloatView(b
         private val DISMISS_ACTIVITIES = hashSetOf<String>().apply {
             Router.getClazz(Account.Login.PATH)?.let { this.add(it.name) }
         }
+        private val SHOW_IM_ACTIVITIES = hashSetOf<String>().apply {
+            Router.getClazz(Message.Conversation.PATH)?.let { this.add(it.name) }
+            Router.getClazz(AppModule.Main.PATH)?.let { this.add(it.name) }
+        }
     }
 
+    private var binding: LayoutNetworkReconnectFloatViewBinding? = null
+    private var connectType: ConnectType = ConnectType.BIZ
+
     private val show: Boolean
         get() {
             return windowManager?.currentActivity?.get()
@@ -29,13 +42,33 @@ class NetworkReconnectFloatView(baseFloatData: IBaseFloatData) : BaseFloatView(b
                 ?: false
         }
 
+    private val showInIMScene: Boolean
+        get() {
+            return (windowManager?.currentActivity?.get()
+                ?.let { SHOW_IM_ACTIVITIES.contains(it.javaClass.name) }
+                ?: false) || HomeUIUtil.isInMessageTab()
+        }
+
     override fun onCreate() {
         super.onCreate()
         val binding = LayoutNetworkReconnectFloatViewBinding.inflate(LayoutInflater.from(context))
+        this.binding = binding
         setContentView(binding.root)
         visibility = if (show) View.VISIBLE else View.GONE
     }
 
+    fun updateWithConnectType(connectType: ConnectType) {
+        this.connectType = connectType
+        when(connectType) {
+            ConnectType.BIZ -> {
+                binding?.text?.text = getCompatString(R.string.network_reconnect_tip)
+            }
+            ConnectType.IM -> {
+                binding?.text?.text = getCompatString(R.string.network_im_reconnect_tip)
+            }
+        }
+    }
+
     override fun getWindowLayoutParams(): WindowManager.LayoutParams {
         return WindowManager.LayoutParams().apply {
             width = getLayoutParamWidth()
@@ -55,6 +88,10 @@ class NetworkReconnectFloatView(baseFloatData: IBaseFloatData) : BaseFloatView(b
 
     override fun onActivityChange(activity: Activity) {
         super.onActivityChange(activity)
+        if (this.connectType == ConnectType.IM) {
+            visibility = if (showInIMScene) View.VISIBLE else View.GONE
+            return
+        }
         visibility = if (show) View.VISIBLE else View.GONE
     }
 

+ 1 - 0
app/src/main/java/com/adealink/weparty/network/view/NetworkReconnectFloatData.kt

@@ -2,6 +2,7 @@ package com.adealink.weparty.network.view
 
 
 import com.adealink.weparty.commonui.widget.floatview.data.IBaseFloatData
+import com.adealink.weparty.network.ConnectType
 import com.adealink.weparty.widget.floatview.FloatViewFactory.Companion.NETWORK_DISCONNECT_TIP
 
 class NetworkReconnectFloatData(private val windowMode: Int) : IBaseFloatData {

BIN
app/src/main/res/drawable-xhdpi/common_customer_server_ic.webp


BIN
app/src/main/res/drawable-xhdpi/common_label_bigv_ic.webp


BIN
app/src/main/res/drawable-xhdpi/common_label_official_inside_ic.webp


+ 11 - 0
app/src/main/res/drawable/common_customer_service_bg.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="16dp" />
+    <gradient
+        android:angle="0"
+        android:endColor="@color/color_FFFF78F0"
+        android:startColor="@color/color_FFFF50AE"
+        android:type="linear"
+        android:useLevel="false" />
+</shape>

+ 2 - 2
app/src/main/res/drawable/label_official_bg.xml

@@ -2,8 +2,8 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
     <gradient
-        android:endColor="@color/color_FFFB7658"
-        android:startColor="@color/color_FFFE983B"
+        android:endColor="@color/color_FFFFBE3F"
+        android:startColor="@color/color_FFFF6929"
         android:type="linear" />
     <corners android:radius="8dp" />
 </shape>

+ 4 - 4
app/src/main/res/layout/layout_profile_label_official.xml

@@ -12,10 +12,10 @@
 
     <androidx.appcompat.widget.AppCompatImageView
         android:id="@+id/icon"
-        android:layout_width="14dp"
-        android:layout_height="14dp"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
         android:layout_marginStart="2dp"
-        android:src="@drawable/common_label_official_ic"
+        android:src="@drawable/common_label_official_inside_ic"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
@@ -28,7 +28,7 @@
         android:includeFontPadding="false"
         android:text="@string/commonui_official"
         android:textColor="@color/white"
-        android:textSize="10sp"
+        android:textSize="9sp"
         app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"

+ 1 - 0
app/src/main/res/values-ar/strings.xml

@@ -19,6 +19,7 @@
     <string name="common_sign_in">تسجيل الدخول</string>
     <string name="common_x_count">%1$s x %2$s</string>
     <string name="network_reconnect_tip">الشبكة غير متصلة ، يرجى النقر لإعادة الاتصال</string>
+    <string name="network_im_reconnect_tip">تم قطع الاتصال بخادم المحادثة، الرجاء النقر لإعادة الاتصال.</string>
     <string name="common_input_empty">لا يمكن أن يكون الإدخال فارغًا</string>
     <string name="common_net_error"> فشل طلب الشبكة ، يرجى التحقق من شبكة الانترنت</string>
     <string name="common_chat">محادثة</string>

+ 1 - 0
app/src/main/res/values-zh/strings.xml

@@ -18,6 +18,7 @@
     <string name="common_save">保存</string>
     <string name="common_x_count">%1$s x %2$s</string>
     <string name="network_reconnect_tip">网络断开,请点击重连</string>
+    <string name="network_im_reconnect_tip">已与IM服务器断开连接,请点击重新连接。</string>
     <string name="common_input_empty">输入不能为空</string>
     <string name="common_net_error">网络异常,请检查网络</string>
     <string name="common_chat">聊天</string>

+ 4 - 0
app/src/main/res/values/colors.xml

@@ -1023,4 +1023,8 @@
     <color name="color_FFFFA4D2">#FFFFA4D2</color>
     <color name="color_FF1C54D5">#FF1C54D5</color>
     <color name="color_FFFFE5EB">#FFFFE5EB</color>
+    <color name="color_FFFF78F0">#FFFF78F0</color>
+
+    <color name="color_FFFF6929">#FFFF6929</color>
+    <color name="color_FFFFBE3F">#FFFFBE3F</color>
 </resources>

+ 2 - 1
app/src/main/res/values/strings.xml

@@ -20,7 +20,8 @@
     <string name="common_save">Save</string>
     <string name="common_sign_in">Sign in</string>
     <string name="common_x_count">%1$s x %2$s</string>
-    <string name="network_reconnect_tip">Disconnect with server,please click for reconnect.</string>
+    <string name="network_reconnect_tip">Disconnect with server, please click for reconnect.</string>
+    <string name="network_im_reconnect_tip">Disconnect with IM server, please click for reconnect.</string>
     <string name="common_input_empty">Input must not empty</string>
     <string name="common_net_error">Net request failed.</string>
     <string name="common_chat">Chat</string>

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

@@ -27,7 +27,6 @@ import io.rong.common.SystemUtils
 import io.rong.imlib.ChannelClient
 import io.rong.imlib.IRongCallback
 import io.rong.imlib.IRongCoreCallback
-import io.rong.imlib.IRongCoreCallback.IGetMessageCallback
 import io.rong.imlib.IRongCoreEnum.CoreErrorCode
 import io.rong.imlib.IRongCoreListener
 import io.rong.imlib.MessageTag

+ 16 - 5
module/emotion/src/main/java/com/adealink/weparty/emotion/panel/EmotionPanelFragment.kt

@@ -5,6 +5,7 @@ import android.os.Bundle
 import android.view.View
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.lifecycleScope
 import androidx.viewpager2.adapter.FragmentStateAdapter
 import com.adealink.frame.aab.util.getCompatColor
 import com.adealink.frame.aab.util.getCompatDrawable
@@ -28,9 +29,11 @@ import com.adealink.weparty.emotion.viewmodel.EmotionViewModelFactory
 import com.adealink.weparty.module.couple.CoupleModule
 import com.adealink.weparty.module.emotion.Emotion
 import com.adealink.weparty.module.emotion.data.EmotionPackage
+import com.adealink.weparty.module.message.MessageModule
 import com.adealink.weparty.module.room.data.SendEmotionScene
 import com.google.android.material.tabs.TabLayout
 import com.google.android.material.tabs.TabLayoutMediator
+import kotlinx.coroutines.launch
 import com.adealink.weparty.R as APP_R
 
 @RouterUri(path = [Emotion.EmotionPanel.PATH], desc = "表情面板")
@@ -140,13 +143,21 @@ class EmotionPanelFragment : BottomDialogFragment(R.layout.fragment_emotion_pane
     override fun observeViewModel() {
         super.observeViewModel()
         emotionViewModel.emotionPackagesLD.observe(viewLifecycleOwner) { packages ->
-            if (SendEmotionScene.map(scene) == SendEmotionScene.SESSION_DETAIL && toUid > 0) {
-                //私聊场景检查亲密等级
+            viewLifecycleOwner.lifecycleScope.launch {
+                if (SendEmotionScene.map(scene) != SendEmotionScene.SESSION_DETAIL || toUid <= 0) {
+                    //非私聊场景过滤掉亲密关系表情包
+                    updateEmotionPackages(packages.filter { it.minIntimacyLevel <= 0 })
+                    return@launch
+                }
+                val isCustomerService = MessageModule.checkIsCustomerService(toUid, true)
+                if (isCustomerService) {
+                    //对方是客服,过滤掉亲密关系表情包
+                    updateEmotionPackages(packages.filter { it.minIntimacyLevel <= 0 })
+                    return@launch
+                }
+                //检查亲密等级
                 checkIntimacyLevel(packages)
-                return@observe
             }
-            //其他场景过滤掉亲密关系表情包
-            updateEmotionPackages(packages.filter { it.minIntimacyLevel <= 0 })
         }
         emotionViewModel.selectKindLD.observeWithoutCache(viewLifecycleOwner) {
             val toEmotionKind =

+ 9 - 0
module/message/src/main/java/com/adealink/weparty/message/MessageServiceImpl.kt

@@ -17,6 +17,7 @@ import com.adealink.weparty.message.manager.messageManager
 import com.adealink.weparty.message.viewmodel.MessageViewModel
 import com.adealink.weparty.message.viewmodel.MessageViewModelFactory
 import com.adealink.weparty.module.message.IMessageService
+import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.message.viewmodel.IMessageViewModel
 import io.rong.imlib.IRongCoreCallback
 import io.rong.imlib.model.Conversation
@@ -69,6 +70,14 @@ class MessageServiceImpl : IMessageService {
         App.instance.imService.getUnreadConversationList(callback, *conversationTypes)
     }
 
+    override suspend fun getCustomerList(cache: Boolean): List<CustomerInfo>? {
+        return messageManager.getCustomerList(cache)
+    }
+
+    override suspend fun checkIsCustomerService(targetId: Long, cache: Boolean): Boolean {
+        return messageManager.checkIsCustomerService(targetId, cache)
+    }
+
     override fun getService(): IMessageService {
         return this
     }

+ 0 - 1
module/message/src/main/java/com/adealink/weparty/message/config/ConversationListConfig.kt

@@ -20,7 +20,6 @@ class ConversationListConfig {
     var topPriority = true
     // 置顶会话的最大数量
     val maxPriorityCount = 10
-    var isEnableConnectStateNotice = true
 
     init {
         val providerList = ArrayList<IViewProvider<BaseUiConversation>>()

+ 8 - 0
module/message/src/main/java/com/adealink/weparty/message/config/DefaultConversationListProcessor.kt

@@ -1,13 +1,21 @@
 package com.adealink.weparty.message.config
 
 import android.text.TextUtils
+import com.adealink.frame.data.json.froJsonErrorNull
+import com.adealink.weparty.message.conversation.data.MessageExpansionKey
+import com.adealink.weparty.message.conversation.data.MessageSessionListChangeInfo
+import com.adealink.weparty.message.conversation.data.SessionListChangeType
 import io.rong.imlib.model.Conversation
+import io.rong.imlib.model.Message
 import java.util.LinkedList
 
 class DefaultConversationListProcessor : BaseDataProcessor<Conversation>() {
     override fun filtered(data: List<Conversation>): List<Conversation> {
         val invalidConversation: MutableList<Conversation> = LinkedList()
         for (item in data) {
+            val lastMessageSessionListChangeInfo = froJsonErrorNull<MessageSessionListChangeInfo>(item.latestExpansion?.get(MessageExpansionKey.SessionListChangeType.key))
+            val shouldHide = item.latestMessageDirection == Message.MessageDirection.SEND
+                        && lastMessageSessionListChangeInfo?.senderSessionListChangeType == SessionListChangeType.HIDE.type
             if (TextUtils.isEmpty(item.targetId) || item.conversationType == null) {
                 invalidConversation.add(item)
             }

+ 1 - 0
module/message/src/main/java/com/adealink/weparty/message/constant/Tags.kt

@@ -14,6 +14,7 @@ const val TAG_IM_CONV_VM = "${TAG_IM}_conv_vm"
 const val TAG_IM_PROCESSOR = "${TAG_IM}_processor"
 const val TAG_IM_MEDIA_MSG_SEND = "${TAG_IM}_media_msg_send"
 const val TAG_IM_MSG_RECEIVE = "${TAG_IM}_msg_receive"
+const val TAG_IM_NEW_MSG_NTF = "${TAG_IM}_new_msg_ntf"
 
 const val TAG_CONV = "tag_conv"
 const val TAG_CONV_FRG = "${TAG_CONV}_frg"

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

@@ -442,10 +442,7 @@ class ConversationFragment : BaseFragment(R.layout.fragment_conversation),
             binding.sendGiftNoticeView,
             binding.topBarCenterLayout.selfAvatar,
             binding.topBarCenterLayout.targetAvatar,
-            this,
-        ) { receiverId ->
-
-        }.attach())
+            this).attach())
         compList.add(SendGiftComboComp(
             this,
             binding.sendGiftComboView

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

@@ -3,6 +3,7 @@ package com.adealink.weparty.message.conversation.comp
 import android.content.Intent
 import android.os.Bundle
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
 import com.adealink.frame.aab.util.getCompatDrawable
 import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.base.fastLazy
@@ -28,14 +29,17 @@ import com.adealink.weparty.message.conversation.util.getIntimacyValueBg
 import com.adealink.weparty.message.databinding.LayoutConversationTopBarCenterNewBinding
 import com.adealink.weparty.message.datasource.local.MessageLocalService
 import com.adealink.weparty.message.effect.IntimacyLevelUpgradeEffectEntity
+import com.adealink.weparty.message.manager.messageManager
 import com.adealink.weparty.message.viewmodel.MessageViewModel
 import com.adealink.weparty.module.couple.Couple
 import com.adealink.weparty.module.couple.CoupleModule
 import com.adealink.weparty.module.couple.data.CoupleType
 import com.adealink.weparty.module.couple.data.CoupleUserInfo
 import com.adealink.weparty.module.message.data.OFFICIAL_TARGET_ID
+import com.adealink.weparty.module.message.data.isSystemTarget
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.profile.data.UserInfo
+import kotlinx.coroutines.launch
 
 class ConversationTargetInfoComp(
     lifecycleOwner: LifecycleOwner,
@@ -62,30 +66,69 @@ class ConversationTargetInfoComp(
 
     private fun observerViewModels() {
         if (callback.getTargetId() == OFFICIAL_TARGET_ID) {
+            //官号,只展示昵称 + 官方号图标
             binding.nameTv.show()
+            binding.officialTargetIv.show()
+            binding.officialLabelTv.gone()
             binding.intimacyCl.gone()
+            binding.customerServiceTv.gone()
             binding.nameTv.text = getCompatString(R.string.message_official_entry)
             return
         }
+        if (isSystemTarget(callback.getTargetId())) {
+            //兼容后续官号,目前只展示空昵称
+            binding.nameTv.show()
+            binding.officialTargetIv.gone()
+            binding.officialLabelTv.gone()
+            binding.intimacyCl.gone()
+            binding.customerServiceTv.gone()
+            binding.nameTv.text = ""
+            return
+        }
         messageViewModel.chatPageDetailResLD.observe(viewLifecycleOwner) {
             it.onSuccess { data ->
-                binding.nameTv.gone()
-                binding.intimacyCl.setOnClickListener {
-                    val activityFragmentManager = activity?.supportFragmentManager ?: return@setOnClickListener
-                    Router.getRouterInstance<BaseDialogFragment>(Couple.IntimateRelationship.PATH)?.apply {
-                        arguments = Bundle().apply {
-                            putLong(Couple.IntimateRelationship.EXTRA_UID, callback.getTargetId())
-                            putParcelable(Couple.IntimateRelationship.EXTRA_COUPLE_USER_INFO, data.userIntimacyInfo)
-                        }
-                    }?.show(activityFragmentManager)
+                viewLifecycleOwner.lifecycleScope.launch {
+                    val isCustomerService = messageManager.checkIsCustomerService(callback.getTargetId(), true)
+                    if (isCustomerService) {
+                        //客服,展示客服标签,不展示亲密关系
+                        binding.nameTv.show()
+                        binding.officialTargetIv.gone()
+                        binding.officialLabelTv.gone()
+                        binding.intimacyCl.gone()
+                        binding.nameTv.text = data.userInfo?.name ?: ""
+                        binding.customerServiceTv.show()
+                        return@launch
+                    }
+                    if (data.userInfo?.isOfficial() == true) {
+                        // 对方身份是官方人员, 展示官方标签,不展示亲密关系
+                        binding.nameTv.show()
+                        binding.officialTargetIv.gone()
+                        binding.officialLabelTv.show()
+                        binding.intimacyCl.gone()
+                        binding.nameTv.text = data.userInfo?.name ?: ""
+                        binding.customerServiceTv.gone()
+                        return@launch
+                    }
+                    binding.nameTv.gone()
+                    binding.officialTargetIv.gone()
+                    binding.customerServiceTv.gone()
+                    binding.intimacyCl.setOnClickListener {
+                        val activityFragmentManager = activity?.supportFragmentManager ?: return@setOnClickListener
+                        Router.getRouterInstance<BaseDialogFragment>(Couple.IntimateRelationship.PATH)?.apply {
+                            arguments = Bundle().apply {
+                                putLong(Couple.IntimateRelationship.EXTRA_UID, callback.getTargetId())
+                                putParcelable(Couple.IntimateRelationship.EXTRA_COUPLE_USER_INFO, data.userIntimacyInfo)
+                            }
+                        }?.show(activityFragmentManager)
+                    }
+                    binding.intimacyCl.show()
+                    binding.targetAvatar.setImageUrl(data.userInfo?.url)
+                    binding.selfAvatar.setImageUrl(ProfileModule.getMyUserInfo()?.url)
+                    this@ConversationTargetInfoComp.coupleUserInfo = data.userIntimacyInfo
+                    this@ConversationTargetInfoComp.targetUserInfo = data.userInfo
+                    updateIntimacyInfoUI()
+                    checkOffPageIntimacyUpgradeNotify()
                 }
-                binding.intimacyCl.show()
-                binding.targetAvatar.setImageUrl(data.userInfo?.url)
-                binding.selfAvatar.setImageUrl(ProfileModule.getMyUserInfo()?.url)
-                this.coupleUserInfo = data.userIntimacyInfo
-                this.targetUserInfo = data.userInfo
-                updateIntimacyInfoUI()
-                checkOffPageIntimacyUpgradeNotify()
             }
         }
         coupleViewModel?.addIntimacyValInfoLd?.observeWithoutCache(viewLifecycleOwner) {

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

@@ -13,7 +13,9 @@ enum class MessageExpansionKey(val key: String) {
     Income("income"),//聊天收入
     MessageShowInfo("message_show_info"),//消息显示信息
     ExperienceCard("experience_card_spend"), //道具体验卡
-    AcceptStatus("is_accept");//是否接受 "1": "已接受"
+    AcceptStatus("is_accept"),//是否接受 "1": "已接受"
+    Scene("scene"),//消息发送场景
+    SessionListChangeType("session_list_change_type");//会话列表变更信息
 
     companion object {
         fun map(key: String): MessageExpansionKey? {
@@ -89,4 +91,21 @@ data class ExperienceCardInfo(
     var experienceCardType: Int, //使用的体验卡类型
     @SerializedName("amount")
     var amount: Long? = null, //使用的数量
-)
+)
+
+@JsonAdapter(ExtReflectiveTypeAdapterFactory::class)
+data class MessageSceneInfo(
+    @SerializedName("scene")
+    val scene: Int,
+)
+
+@JsonAdapter(ExtReflectiveTypeAdapterFactory::class)
+data class MessageSessionListChangeInfo(
+    @SerializedName("senderSessionListChangeType")
+    val senderSessionListChangeType: Int,//发送方会话状态,1:隐藏会话列表,2:不更新会话列表
+)
+
+enum class SessionListChangeType(val type: Int) {
+    HIDE(1),//隐藏
+    NOT_UPDATE(2),//不更新
+}

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

@@ -29,6 +29,7 @@ import com.adealink.weparty.message.conversation.data.MessageShowInfo
 import com.adealink.weparty.message.data.UiMessage
 import com.adealink.weparty.message.util.IMDateUtils
 import com.adealink.weparty.module.backpack.CHAT_EXPERIENCE_CARD
+import com.adealink.weparty.module.message.data.isOfficialTarget
 import io.rong.imlib.model.Message
 import io.rong.imlib.model.MessageContent
 import io.rong.message.HistoryDividerMessage
@@ -159,6 +160,7 @@ abstract class BaseMessageItemProvider<T : MessageContent> : IMessageProvider<T>
         if (config.showPortrait) {
             holder.setVisible(R.id.im_left_portrait, !isSender)
             holder.setVisible(R.id.im_right_portrait, isSender)
+            holder.setVisible(R.id.im_official_iv, isOfficialTarget(uiMessage.targetId))
             val portrait =
                 holder.getView<NetworkImageView>(if (isSender) R.id.im_right_portrait else R.id.im_left_portrait)
             val userInfo = uiMessage.getUserInfo()
@@ -198,6 +200,7 @@ abstract class BaseMessageItemProvider<T : MessageContent> : IMessageProvider<T>
             holder.setVisible(R.id.im_left_portrait, false)
             holder.setVisible(R.id.im_right_portrait, false)
             holder.setVisible(R.id.im_title, false)
+            holder.setVisible(R.id.im_official_iv, false)
         }
     }
 

+ 10 - 1
module/message/src/main/java/com/adealink/weparty/message/conversation/setting/ConversationSettingActivity.kt

@@ -3,9 +3,11 @@ package com.adealink.weparty.message.conversation.setting
 import android.os.Bundle
 import android.util.TypedValue
 import android.view.LayoutInflater
+import android.view.View
 import androidx.activity.viewModels
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.lifecycleScope
 import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.base.fastLazy
 import com.adealink.frame.mvvm.view.viewBinding
@@ -17,6 +19,7 @@ import com.adealink.weparty.commonui.BaseActivity
 import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
 import com.adealink.weparty.commonui.ext.onFailure
 import com.adealink.weparty.commonui.ext.onSuccess
+import com.adealink.weparty.commonui.ext.setVisible
 import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.toast.util.showFailedToast
 import com.adealink.weparty.commonui.widget.CommonDialog
@@ -29,10 +32,10 @@ import com.adealink.weparty.message.databinding.LayoutMessageSettingSwitchBindin
 import com.adealink.weparty.module.follow.FollowModule
 import com.adealink.weparty.module.follow.data.FollowOpFrom
 import com.adealink.weparty.module.message.Message
+import com.adealink.weparty.module.message.MessageModule
 import com.adealink.weparty.module.profile.Profile
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.userprotect.UserProtect
-import com.adealink.weparty.module.userprotect.UserProtectModule
 import com.adealink.weparty.module.userprotect.data.ReportFrom
 import com.adealink.weparty.module.userprotect.data.ReportType
 import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView
@@ -40,6 +43,7 @@ import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView
 import com.qmuiteam.qmui.widget.util.QMUIStatusBarHelper
 import io.rong.imlib.model.Conversation.ConversationType
 import io.rong.imlib.model.ConversationIdentifier
+import kotlinx.coroutines.launch
 import com.adealink.weparty.R as APP_R
 
 @RouterUri(path = [Message.Conversation.SETTING], desc = "会话设置页")
@@ -183,6 +187,7 @@ class ConversationSettingActivity : BaseActivity() {
             QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON,
             itemHeight
         ).apply {
+            visibility = View.GONE
             setBackgroundResource(APP_R.drawable.common_white_top_radius_12_bg)
             textView.setTextSize(
                 TypedValue.COMPLEX_UNIT_PX,
@@ -283,6 +288,10 @@ class ConversationSettingActivity : BaseActivity() {
                 }
             }
         }
+        lifecycleScope.launch {
+            val isCustomerService = MessageModule.checkIsCustomerService(toUid, true)
+            chatBackgroundSettingItem.setVisible(!isCustomerService)
+        }
     }
 
     override fun observeViewModel() {

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

@@ -73,6 +73,7 @@ import io.rong.message.RecallNotificationMessage
 import io.rong.message.ReferenceMessage
 import kotlinx.coroutines.launch
 import java.lang.ref.WeakReference
+import com.adealink.weparty.R as APP_R
 
 open class ConversationViewModel : BaseViewModel(), IConversationViewModel, MessageEventListener,
     IIMUserInfoListener {
@@ -406,8 +407,9 @@ open class ConversationViewModel : BaseViewModel(), IConversationViewModel, Mess
                 val errorMsg = messageShowInfo.message
                 val code = messageShowInfo.code
                 if (!errorMsg.isNullOrEmpty()) {
+                    val errorToast = getCompatString(APP_R.string.commonui_error_msg, errorMsg.toString(), "${code ?: 0}")
                     _pageEventLiveData.send(
-                        ToastEvent(errorMsg.ifEmpty { "${code ?: 0}" })
+                        ToastEvent(errorToast)
                     )
                     messageBlockResLD.send(messageShowInfo)
                 }

+ 0 - 60
module/message/src/main/java/com/adealink/weparty/message/conversationlist/ConversationListFragment.kt

@@ -1,13 +1,10 @@
 package com.adealink.weparty.message.conversationlist
 
 import android.os.Bundle
-import android.view.View
 import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.adealink.frame.aab.util.getCompatString
-import com.adealink.frame.base.AppBaseInfo
 import com.adealink.frame.base.fastLazy
 import com.adealink.frame.ext.isViewBindingValid
 import com.adealink.frame.imkit.widget.FixedLinearLayoutManager
@@ -18,8 +15,6 @@ import com.adealink.frame.router.Router
 import com.adealink.frame.router.annotation.BindExtra
 import com.adealink.frame.router.annotation.RouterUri
 import com.adealink.weparty.commonui.BaseFragment
-import com.adealink.weparty.commonui.ext.gone
-import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.widget.CommonDialog
 import com.adealink.weparty.message.R
 import com.adealink.weparty.message.constant.TAG_CONV_LIST_FRG
@@ -27,22 +22,18 @@ import com.adealink.weparty.message.conversation.provider.ConversationClickType
 import com.adealink.weparty.message.conversationlist.model.BaseUiConversation
 import com.adealink.weparty.message.conversationlist.viewmodel.WeConversationListViewModel
 import com.adealink.weparty.message.data.ConversationListType
-import com.adealink.weparty.message.data.NoticeContent
 import com.adealink.weparty.message.databinding.FragmentConversationlistBinding
 import com.adealink.weparty.message.databinding.ImConversationlistEmptyViewBinding
 import com.adealink.weparty.message.viewmodel.ConversationListViewModelFactory
 import com.adealink.weparty.module.anchor.data.FromScene
 import com.adealink.weparty.module.message.Message
 import com.adealink.weparty.module.profile.Profile
-import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.room.Room
 import com.adealink.weparty.room.data.EnterRoomInfo
 import com.adealink.weparty.room.data.JoinRoomFrom
 import com.scwang.smart.refresh.layout.api.RefreshLayout
 import com.scwang.smart.refresh.layout.constant.RefreshState
 import com.scwang.smart.refresh.layout.simple.SimpleMultiListener
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
 
 @RouterUri(path = [Message.Conversation.LIST], desc = "会话列表")
 class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist),
@@ -64,7 +55,6 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
             conversationListType
         )
     }
-    private val profileViewModel by fastLazy { ProfileModule.getProfileViewModel(requireActivity()) }
     private var newState: Int = RecyclerView.SCROLL_STATE_IDLE
     private var delayRefresh: Boolean = false
 
@@ -140,29 +130,6 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
             .observe(viewLifecycleOwner) { refreshEvent ->
                 binding.refreshLayout.setEnableLoadMore(refreshEvent.enable)
             }
-
-        // Debug版本开启融云连接状态监听
-        if (!AppBaseInfo.isRelease) {
-            conversationListViewModel
-                .noticeContentLiveData
-                .observe(
-                    viewLifecycleOwner
-                ) {
-                    // 当连接通知没有显示时,延迟进行显示,防止连接闪断造成画面闪跳。
-                    if (binding.noticeContainer.root.visibility == View.GONE) {
-                        viewLifecycleOwner.lifecycleScope.launch {
-                            delay(NOTICE_SHOW_DELAY_MILLIS)
-                            updateNoticeContent(
-                                conversationListViewModel
-                                    .noticeContentLiveData
-                                    .getValue()
-                            )
-                        }
-                    } else {
-                        updateNoticeContent(it)
-                    }
-                }
-        }
     }
 
     override fun loadData() {
@@ -222,25 +189,6 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
             })
     }
 
-    /**
-     * 更新连接状态通知栏
-     *
-     * @param content
-     */
-    fun updateNoticeContent(content: NoticeContent?) {
-        if (content == null) return
-
-        if (content.isShowNotice) {
-            binding.noticeContainer.root.show()
-            binding.noticeContainer.noticeTv.text = content.content ?: ""
-            if (content.iconResId != 0) {
-                binding.noticeContainer.noticeIconIv.setImageResource(content.iconResId)
-            }
-        } else {
-            binding.noticeContainer.root.gone()
-        }
-    }
-
     private fun onResolveAdapter(): ConversationListAdapter {
         return ConversationListAdapter(this)
     }
@@ -331,12 +279,4 @@ class ConversationListFragment : BaseFragment(R.layout.fragment_conversationlist
     override fun onViewLongClick(clickType: Int, data: BaseUiConversation): Boolean {
         return false
     }
-
-    companion object {
-        /*
-        * 连接通知状态延迟显示时间。
-        * 为了防止连接闪断,不会在断开连接时立即显示连接通知状态,而是在延迟一定时间后显示。
-        */
-        const val NOTICE_SHOW_DELAY_MILLIS: Long = 4000L
-    }
 }

+ 3 - 0
module/message/src/main/java/com/adealink/weparty/message/conversationlist/model/BaseUiConversation.kt

@@ -18,6 +18,7 @@ abstract class BaseUiConversation(var conversation: Conversation, var listType:
     var targetUserInfo: IMUserInfo? = null
     var onlineInRoomStatus: OnlineInRoomStatus? = null
     var coupleUserInfo: CoupleUserInfo? = null
+    var isCustomer: Boolean = false
 
     val conversationIdentifier: ConversationIdentifier?
         get() {
@@ -58,6 +59,8 @@ abstract class BaseUiConversation(var conversation: Conversation, var listType:
 
     abstract fun onCoupleUserInfoUpdate(info: CoupleUserInfo?)
 
+    abstract fun onCustomerUpdate(isCustomer: Boolean)
+
     /**
      * 会话更新
      *

+ 8 - 0
module/message/src/main/java/com/adealink/weparty/message/conversationlist/model/SingleConversation.kt

@@ -72,6 +72,14 @@ class SingleConversation(conversation: Conversation, listType: Int) :
         change()
     }
 
+    override fun onCustomerUpdate(isCustomer: Boolean) {
+        if (isCustomer == this.isCustomer) {
+            return
+        }
+        this.isCustomer = isCustomer
+        change()
+    }
+
     override fun onConversationUpdate(conversation: Conversation) {
         processResending(conversation)
         val oldConversation = this.conversation

+ 16 - 0
module/message/src/main/java/com/adealink/weparty/message/conversationlist/provider/BaseConversationProvider.kt

@@ -138,6 +138,22 @@ open class BaseConversationProvider : IViewProvider<BaseUiConversation> {
                 )
         }
 
+        //客服和官方号标签
+        when {
+            uiConversation.targetUserInfo?.userInfo?.isOfficial() == true -> {
+                holder.setVisible(R.id.im_conversation_customer_service_tv, false)
+                holder.setVisible(R.id.im_conversation_official_label_tv, true)
+            }
+            uiConversation.isCustomer -> {
+                holder.setVisible(R.id.im_conversation_customer_service_tv, true)
+                holder.setVisible(R.id.im_conversation_official_label_tv, false)
+            }
+            else -> {
+                holder.setVisible(R.id.im_conversation_customer_service_tv, false)
+                holder.setVisible(R.id.im_conversation_official_label_tv, false)
+            }
+        }
+
         val swipeMenuLayout = holder.getView<SwipeMenuLayout>(R.id.im_conversation_item)
         if (DisplayUtil.isRtlLayout()) {
             //阿语状态下左滑(→)

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

@@ -3,7 +3,6 @@ package com.adealink.weparty.message.conversationlist.viewmodel
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MediatorLiveData
 import androidx.lifecycle.MutableLiveData
-import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.dot.NumDot
@@ -36,10 +35,10 @@ 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.data.NoticeContent
 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
@@ -65,6 +64,7 @@ import io.rong.message.ReadReceiptMessage
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.async
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import java.lang.ref.WeakReference
@@ -107,12 +107,6 @@ open class ConversationListViewModel(
     val conversationListLiveData: MediatorLiveData<List<BaseUiConversation>>
         get() = _conversationListLiveData
 
-    /**
-     * 获取连接状态通知内容 LiveData
-     */
-    val noticeContentLiveData: LiveData<NoticeContent>
-        get() = _noticeContentLiveData
-
     /**
      * 获取刷新事件 LiveData
      */
@@ -124,8 +118,6 @@ open class ConversationListViewModel(
 
     private val connectionStatusLiveData =
         MutableLiveData<RongIMClient.ConnectionStatusListener.ConnectionStatus>()
-    private val _noticeContentLiveData: MutableLiveData<NoticeContent> =
-        MutableLiveData<NoticeContent>()
     private val _refreshEventLiveData = MutableLiveData<Event.RefreshEvent>()
     private val _loadMoreEnableEventLiveData = MutableLiveData<Event.LoadMoreEnableEvent>()
 
@@ -341,8 +333,6 @@ open class ConversationListViewModel(
             if (status == RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTED) {
                 getConversationList(loadMore = false, isEventManual = false, delayTime = 0)
             }
-            // 更新连接状态通知信息
-            updateNoticeContent(status)
             preConnectionStatus = status
         }
 
@@ -389,7 +379,6 @@ open class ConversationListViewModel(
             IMService.innerService.addConversationEventListener(conversationEventListener)
             IMService.innerService.addMessageEventListener(messageEventListener)
             IMService.innerService.addCancelSendMediaMessageListener(cancelSendMediaMessageListener)
-            updateNoticeContent(IMService.innerService.getCurrentConnectionStatus())
         }
     }
 
@@ -650,9 +639,16 @@ open class ConversationListViewModel(
 
     private fun batchGetTargetExtraInfo(targetIds: Set<Long>) {
         viewModelScope.launch {
-            val sessionInfoListRes = messageHttpService.batchGetSessionInfo(
-                BatchGetSessionListReq(targetIds.toList())
-            )
+            val sessionInfoListDef = async {
+                messageHttpService.batchGetSessionInfo(
+                    BatchGetSessionListReq(targetIds.toList())
+                )
+            }
+            val customerListDef = async {
+                messageManager.getCustomerList(cache = true)
+            }
+            val sessionInfoListRes = sessionInfoListDef.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
@@ -661,6 +657,7 @@ open class ConversationListViewModel(
                     val targetIdLong = uiConversation.targetId.safeToLong(0)
                     val userInfo = uid2UserInfo?.get(targetIdLong) ?: continue
                     baseUiConversation?.onUserInfoUpdate(IMUserInfo(userInfo))
+                    baseUiConversation?.onCustomerUpdate(customerIdSet.contains(targetIdLong))
                 }
                 refreshConversationList()
                 return@launch
@@ -684,6 +681,7 @@ open class ConversationListViewModel(
                 baseUiConversation?.onUserInfoUpdate(IMUserInfo(userInfo))
                 baseUiConversation?.onOnlineInRoomStatusUpdate(onlineInRoomStatus)
                 baseUiConversation?.onCoupleUserInfoUpdate(sessionInfo.userIntimacyInfo)
+                baseUiConversation?.onCustomerUpdate(customerIdSet.contains(targetIdLong))
             }
             refreshConversationList()
         }
@@ -1024,64 +1022,6 @@ open class ConversationListViewModel(
         IMService.innerService.removeCancelSendMediaMessageListener(cancelSendMediaMessageListener)
     }
 
-    /**
-     * 更新连接状态通知
-     *
-     * @param status
-     */
-    private fun updateNoticeContent(status: RongIMClient.ConnectionStatusListener.ConnectionStatus) {
-        if (status == preConnectionStatus) {
-            return
-        }
-        val noticeContent = NoticeContent()
-        var content: String? = null
-        var isShowContent = true
-        var resId = 0
-
-        if (!IMConfigCenter.conversationListConfig.isEnableConnectStateNotice) {
-            Log.e(TAG_IM_CONV_LIST_VM, "isEnableConnectStateNotice is disabled.")
-            return
-        }
-        when (status) {
-            RongIMClient.ConnectionStatusListener.ConnectionStatus.NETWORK_UNAVAILABLE -> {
-                content = getCompatString(R.string.rc_conversation_list_notice_network_unavailable)
-                resId = R.drawable.im_ic_error_notice
-            }
-            RongIMClient.ConnectionStatusListener.ConnectionStatus.KICKED_OFFLINE_BY_OTHER_CLIENT -> {
-                content = getCompatString(R.string.rc_conversation_list_notice_kicked)
-                resId = R.drawable.im_ic_error_notice
-            }
-            RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTED -> {
-                isShowContent = false
-            }
-            RongIMClient.ConnectionStatusListener.ConnectionStatus.UNCONNECTED -> {
-                content = getCompatString(R.string.rc_conversation_list_notice_disconnect)
-                resId = R.drawable.im_ic_error_notice
-            }
-            RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTING, RongIMClient.ConnectionStatusListener.ConnectionStatus.SUSPEND -> {
-                content = getCompatString(R.string.rc_conversation_list_notice_connecting)
-                resId = R.drawable.im_conversationlist_notice_connecting_animated
-            }
-            RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTION_STATUS_PROXY_UNAVAILABLE -> {
-                content = getCompatString(R.string.rc_conversation_list_notice_proxy_unavailable)
-                resId = R.drawable.im_ic_error_notice
-            }
-            else -> {
-                if (preConnectionStatus == RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTION_STATUS_PROXY_UNAVAILABLE) {
-                    return
-                }
-                content = getCompatString(R.string.rc_conversation_list_notice_network_unavailable)
-                resId = R.drawable.im_ic_error_notice
-            }
-        }
-
-        noticeContent.content = content
-        noticeContent.isShowNotice = isShowContent
-        noticeContent.iconResId = resId
-
-        _noticeContentLiveData.postValue(noticeContent)
-    }
-
     protected fun refreshConversationList() {
         removeDupData()
         _conversationListLiveData.send(uiConversationList)

+ 0 - 7
module/message/src/main/java/com/adealink/weparty/message/data/NoticeContent.kt

@@ -1,7 +0,0 @@
-package com.adealink.weparty.message.data
-
-class NoticeContent {
-    var content: String? = null
-    var iconResId: Int = 0
-    var isShowNotice: Boolean = false
-}

+ 14 - 0
module/message/src/main/java/com/adealink/weparty/message/datasource/remote/MessageHttpService.kt

@@ -3,10 +3,12 @@ package com.adealink.weparty.message.datasource.remote
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.network.data.Res
 import com.adealink.weparty.message.data.IMTokenResData
+import com.adealink.weparty.module.message.data.BatchGetCustomerListRes
 import com.adealink.weparty.module.message.data.BatchGetFirstChatTsReq
 import com.adealink.weparty.module.message.data.BatchGetFirstChatTsRes
 import com.adealink.weparty.module.message.data.BatchGetSessionListReq
 import com.adealink.weparty.module.message.data.ChatPageDetailRes
+import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.message.data.GetCustomQuickMessageRes
 import com.adealink.weparty.module.message.data.GetSessionListReq
 import com.adealink.weparty.module.message.data.GetSessionListRes
@@ -103,4 +105,16 @@ interface MessageHttpService {
         @Body req: SendRCMessageReq,
     ): Rlt<Res<Any>>
 
+    /**
+     * 获取客服信息
+     */
+    @GET("im/getCustomerInfo")
+    suspend fun getCustomerInfo(): Rlt<Res<CustomerInfo>>
+
+    /**
+     * 批量拉取客服列表
+     */
+    @GET("im/batchGetCustomerInfo")
+    suspend fun batchGetCustomerInfo(): Rlt<Res<BatchGetCustomerListRes>>
+
 }

+ 32 - 14
module/message/src/main/java/com/adealink/weparty/message/manager/IMConnectManager.kt

@@ -10,6 +10,9 @@ import com.adealink.weparty.App
 import com.adealink.weparty.message.datasource.remote.MessageHttpService
 import com.adealink.weparty.module.account.AccountModule
 import com.adealink.weparty.module.account.ILoginListener
+import com.adealink.weparty.network.ConnectStatus
+import com.adealink.weparty.network.ConnectType
+import com.adealink.weparty.network.ConnectionStatusManager
 import com.adealink.weparty.storage.AppPref
 import io.rong.imlib.RongIMClient
 import kotlinx.coroutines.delay
@@ -22,10 +25,13 @@ class IMConnectManager: BaseFrame<IListener>(), IIMConnectManager, ILoginListene
 
     override fun init() {
         AccountModule.registerListener(this)
+        IMService.innerService.addConnectionStatusListener(this)
         if (AccountModule.isLogin()) {
             connectIM()
         }
-        IMService.innerService.addConnectionStatusListener(this)
+        ConnectionStatusManager.addReconnectClickListener(ConnectType.IM) {
+            connectIM()
+        }
     }
 
     override fun onLogin() {
@@ -38,6 +44,9 @@ class IMConnectManager: BaseFrame<IListener>(), IIMConnectManager, ILoginListene
         IMService.innerService.logout()
     }
 
+    /**
+     * 连接IM,先拿本地token,如果本地token为空,重新获取token,并连接
+     */
     private fun connectIM() {
         launch {
             if (AppPref.imToken.isNotEmpty()) {
@@ -64,6 +73,9 @@ class IMConnectManager: BaseFrame<IListener>(), IIMConnectManager, ILoginListene
         }
     }
 
+    /**
+     * 延迟一段时间重新连接
+     */
     private fun delayReconnect() {
         launch {
             delay(IM_RECONNECT_TIME_SECONDS * 1000L)
@@ -71,6 +83,9 @@ class IMConnectManager: BaseFrame<IListener>(), IIMConnectManager, ILoginListene
         }
     }
 
+    /**
+     * 使用token连接IM
+     */
     private fun realConnectIM(token: String?) {
         token ?: return
         IMService.innerService.connect(token, IM_CONNECT_TIMEOUT_SECONDS, object :
@@ -88,10 +103,7 @@ class IMConnectManager: BaseFrame<IListener>(), IIMConnectManager, ILoginListene
                 if (e == RongIMClient.ConnectionErrorCode.RC_CONN_TOKEN_INCORRECT || e == RongIMClient.ConnectionErrorCode.RC_CONN_TOKEN_EXPIRE) {
                     //token不正确或者过期,延迟一段时间重新获取token,并连接
                     AppPref.imToken = ""
-                    launch {
-                        delay(IM_RECONNECT_TIME_SECONDS * 1000L)
-                        connectIM()
-                    }
+                    delayReconnect()
                     return
                 }
                 //连接失败,延迟重新连接
@@ -105,18 +117,24 @@ class IMConnectManager: BaseFrame<IListener>(), IIMConnectManager, ILoginListene
         })
     }
 
-    companion object {
-        private const val IM_CONNECT_TIMEOUT_SECONDS = 60
-        private const val IM_RECONNECT_TIME_SECONDS = 30
-    }
-
     override fun onChanged(status: RongIMClient.ConnectionStatusListener.ConnectionStatus?) {
-        if (status == RongIMClient.ConnectionStatusListener.ConnectionStatus.TOKEN_INCORRECT) {
-            launch {
-                AppPref.imToken = ""
-                connectIM()
+        Log.i(TAG_IM_SERVICE, "IM connectStatusChanged status:(${status?.value}, ${status?.message})")
+        when(status) {
+            RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTED,
+            RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTING -> {
+                ConnectionStatusManager.notifyConnectStatus(ConnectType.IM, ConnectStatus.ConnectingOrConnected)
+            }
+            RongIMClient.ConnectionStatusListener.ConnectionStatus.SIGN_OUT -> {
+                ConnectionStatusManager.removeDisconnectFloatView()
+            }
+            else -> {
+                ConnectionStatusManager.notifyConnectStatus(ConnectType.IM, ConnectStatus.Disconnected)
             }
         }
     }
 
+    companion object {
+        private const val IM_CONNECT_TIMEOUT_SECONDS = 60
+        private const val IM_RECONNECT_TIME_SECONDS = 30
+    }
 }

+ 14 - 3
module/message/src/main/java/com/adealink/weparty/message/manager/IMNotificationFrequencyLimiter.kt

@@ -1,7 +1,9 @@
 package com.adealink.weparty.message.manager
 
+import android.util.Log
 import com.adealink.frame.data.json.froJsonErrorNull
 import com.adealink.frame.data.json.toJsonErrorNull
+import com.adealink.weparty.message.constant.TAG_IM_NEW_MSG_NTF
 import com.adealink.weparty.message.datasource.local.MessageLocalService
 import java.text.SimpleDateFormat
 import java.util.Date
@@ -46,9 +48,18 @@ object IMNotificationFrequencyLimiter {
             MessageLocalService.newMsgNoticeTodayCount = 0
         }
 
-        if (todayCount >= DAILY_MAX_COUNT) return false
-        if (now - lastGlobalShownTime < GLOBAL_LIMIT_INTERVAL_MS) return false
-        if (now - (perUserShownMap[targetId] ?: 0L) < USER_LIMIT_INTERVAL_MS) return false
+        if (todayCount >= DAILY_MAX_COUNT) {
+            Log.e(TAG_IM_NEW_MSG_NTF, "超过今天最大展示数量:$DAILY_MAX_COUNT")
+            return false
+        }
+        if (now - lastGlobalShownTime < GLOBAL_LIMIT_INTERVAL_MS) {
+            Log.e(TAG_IM_NEW_MSG_NTF, "${GLOBAL_LIMIT_INTERVAL_MS}毫秒内最多展示1条")
+            return false
+        }
+        if (now - (perUserShownMap[targetId] ?: 0L) < USER_LIMIT_INTERVAL_MS) {
+            Log.e(TAG_IM_NEW_MSG_NTF, "同一个用户${USER_LIMIT_INTERVAL_MS}毫秒内最多展示一条")
+            return false
+        }
 
         // 满足条件,更新内存 + SP
         lastGlobalShownTime = now

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

@@ -6,12 +6,20 @@ import com.adealink.frame.network.data.Res
 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.room.Room
 
 interface IMessageManager : IBaseFrame<IMessageListener> {
     fun init()
     suspend fun batchQuerySayHiState(uidSet: Set<Long>): Rlt<Map<Long, Boolean>>
     fun getSayHiState(uid: Long): Boolean
+    /**
+     * 获取客服列表
+     */
+    suspend fun getCustomerList(
+        cache: Boolean = true,
+    ): List<CustomerInfo>?
+    suspend fun checkIsCustomerService(targetId: Long, cache: Boolean): Boolean
 
     suspend fun sendQuickMessage(toUid: Long, message1: String? = null, message2: String? = null, scene: Int? = null):Rlt<Res<Any>>
 

+ 40 - 3
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
@@ -20,6 +21,7 @@ import com.adealink.frame.log.Log
 import com.adealink.frame.network.ISocketNotify
 import com.adealink.frame.network.data.Res
 import com.adealink.frame.util.AppUtil
+import com.adealink.frame.util.ONE_MINUTE
 import com.adealink.weparty.App
 import com.adealink.weparty.commonui.ext.dp
 import com.adealink.weparty.commonui.ext.onSuccess
@@ -32,8 +34,12 @@ import com.adealink.weparty.message.config.IMConfigCenter
 import com.adealink.weparty.message.constant.IM_NOTIFICATION_CHANNEL_ID
 import com.adealink.weparty.message.constant.OFFICIAL_NAME
 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.MessageSessionListChangeInfo
+import com.adealink.weparty.message.conversation.data.SessionListChangeType
 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,6 +54,7 @@ import com.adealink.weparty.message.userinfo.model.IMUserInfo
 import com.adealink.weparty.module.account.AccountModule
 import com.adealink.weparty.module.couple.data.CoupleOnlineFloatData
 import com.adealink.weparty.module.message.data.BatchGetFirstChatTsReq
+import com.adealink.weparty.module.message.data.CustomerInfo
 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
@@ -85,6 +92,8 @@ class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
     private var isNewMessageNoticeShowing = false
     private var notificationMessageNoticeQueue = LinkedList<NotificationMessage>()
     private val floatViewFactory by fastLazy { FloatViewFactory() }
+    private var customerList: List<CustomerInfo>? = null
+    private var lastFetchCustomerListTime: Long = 0
 
     private val notificationMessageNotify =
         object : ISocketNotify<NotificationMessage> {
@@ -138,6 +147,13 @@ class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
                 //处理新消息提醒条
                 if (message == null) return false
                 if (offline) return false
+                val lastMessageSessionListChangeInfo = froJsonErrorNull<MessageSessionListChangeInfo>(message.expansion?.get(
+                    MessageExpansionKey.SessionListChangeType.key))
+                val shouldIgnoreMessage = message.messageDirection == Message.MessageDirection.SEND
+                        && lastMessageSessionListChangeInfo?.senderSessionListChangeType == SessionListChangeType.HIDE.type
+                if (shouldIgnoreMessage) {
+                    return true
+                }
                 launch {
                     val targetIdLong = message.targetId.toLongOrNull() ?: return@launch
                     if (isSystemTarget(targetIdLong)) {
@@ -266,6 +282,23 @@ class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
         return sayHiCache.containsKey(uid) && sayHiCache[uid] == true
     }
 
+    override suspend fun getCustomerList(cache: Boolean): List<CustomerInfo>? {
+        val useCache = customerList != null && cache && (System.currentTimeMillis() - lastFetchCustomerListTime < 30 * ONE_MINUTE)
+        if (useCache) {
+            return customerList
+        }
+        messageHttpService.batchGetCustomerInfo().onSuccess {
+            lastFetchCustomerListTime = System.currentTimeMillis()
+            customerList = it.data?.customerInfoList ?: listOf()
+        }
+        return customerList
+    }
+
+    override suspend fun checkIsCustomerService(targetId: Long, cache: Boolean): Boolean {
+        val customerIdSet = getCustomerList(cache)?.map { it.customerUid }?.toSet()?: setOf()
+        return customerIdSet.contains(targetId)
+    }
+
     private var systemUserCache = hashMapOf(
         OFFICIAL_TARGET_ID to UserInfo(
             OFFICIAL_TARGET_ID,
@@ -326,20 +359,24 @@ class MessageManager : BaseFrame<IMessageListener>(), IMessageManager,
         }
 
         if (newMessageData.messageType == PushMessageType.IM.type && !shouldShowNewMessageNotice(newMessageData.targetId)) {
+            Log.e(TAG_IM_NEW_MSG_NTF, "IMNewMessage shouldNotShow, messageData:$newMessageData")
             isNewMessageNoticeShowing = false
             return
         }
 
         isNewMessageNoticeShowing = true
+        Log.i(TAG_IM_NEW_MSG_NTF, "showNotificationMessageNotice, messageData:$newMessageData")
         showNotificationMessageNotice(newMessageData)
     }
 
     private fun shouldAllowNewMessageInUIScene(targetId: String?): Boolean {
-        //1. 不在会话列表tab页
+        //1. 应用在后台,都展示
+        if (AppUtil.background) return true
+        //2. 不在会话列表tab页
         if (HomeUIUtil.isInMessageTab()) return false
-        //2. 不在当前会话页
+        //3. 不在当前会话页
         if (ConversationActivity.activeTargetId.toString() == targetId) return false
-        //3. 不在登录页、语聊房
+        //4. 不在登录页、语聊房
         if (AppUtil.currentActivity?.let { NEW_MSG_DISMISS_ACTIVITIES.contains(it.javaClass.name) } == true) return false
         return true
     }

+ 36 - 9
module/message/src/main/java/com/adealink/weparty/message/viewmodel/MessageViewModel.kt

@@ -6,7 +6,6 @@ import com.adealink.frame.base.CommonDataNullError
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.mvvm.livedata.ExtLiveData
 import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
-import com.adealink.frame.mvvm.livedata.NoSendCacheValueLiveData
 import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
 import com.adealink.frame.oss.data.UploadFile
@@ -20,12 +19,14 @@ import com.adealink.weparty.module.account.AccountModule
 import com.adealink.weparty.module.couple.CoupleModule
 import com.adealink.weparty.module.couple.data.IntimacyPrivilegeType
 import com.adealink.weparty.module.message.data.ChatPageDetailRes
+import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.message.data.GetCustomQuickMessageRes
 import com.adealink.weparty.module.message.data.Im1v1ContinuousSendMessageDeductNotifyInfo
 import com.adealink.weparty.module.message.data.QuickMessageInfo
 import com.adealink.weparty.module.message.data.SendRCMessageReq
 import com.adealink.weparty.module.message.data.UpdateQuickMessageReq
 import com.adealink.weparty.module.message.viewmodel.IMessageViewModel
+import com.adealink.weparty.module.wallet.data.getErrorByServerError
 import kotlinx.coroutines.launch
 
 class MessageViewModel : BaseViewModel(), IMessageViewModel, IMessageListener {
@@ -35,7 +36,8 @@ class MessageViewModel : BaseViewModel(), IMessageViewModel, IMessageListener {
     override val editedCustomQuickMessageLD: ExtLiveData<Pair<Int, QuickMessageInfo>> =
         ExtMutableLiveData()
     override val sayHiLD: ExtLiveData<Long> = ExtMutableLiveData()
-    override val im1v1ContinuousSendMessageDeductNotifyLD: ExtLiveData<Im1v1ContinuousSendMessageDeductNotifyInfo> = ExtMutableLiveData()
+    override val im1v1ContinuousSendMessageDeductNotifyLD: ExtLiveData<Im1v1ContinuousSendMessageDeductNotifyInfo> =
+        ExtMutableLiveData()
 
     private val messageHttpService =
         App.instance.networkService.getHttpService(MessageHttpService::class.java)
@@ -170,13 +172,38 @@ class MessageViewModel : BaseViewModel(), IMessageViewModel, IMessageListener {
     override fun sendEmotionMessage(emotionId: Int, toUid: Long): LiveData<Rlt<Any>> {
         val liveData = OnceMutableLiveData<Rlt<Any>>()
         viewModelScope.launch {
-            liveData.send(messageHttpService.sendMessage(SendRCMessageReq(
-                messageType = EMOTION_MESSAGE_TYPE,
-                content = EmotionMessage.obtainSendContent(emotionId),
-                fromUid = AccountModule.uid,
-                target = toUid.toString(),
-                isIncludeSender = 1
-            )))
+            liveData.send(
+                messageHttpService.sendMessage(
+                    SendRCMessageReq(
+                        messageType = EMOTION_MESSAGE_TYPE,
+                        content = EmotionMessage.obtainSendContent(emotionId),
+                        fromUid = AccountModule.uid,
+                        target = toUid.toString(),
+                        isIncludeSender = 1
+                    )
+                )
+            )
+        }
+        return liveData
+    }
+
+    override fun getCustomerInfo(): LiveData<Rlt<CustomerInfo>> {
+        val liveData = OnceMutableLiveData<Rlt<CustomerInfo>>()
+        viewModelScope.launch {
+            liveData.send(
+                when (val result = messageHttpService.getCustomerInfo()) {
+                    is Rlt.Success -> {
+                        val customerInfo = result.data.data
+                        if (customerInfo == null) {
+                            Rlt.Failed(CommonDataNullError())
+                        } else {
+                            Rlt.Success(customerInfo)
+                        }
+                    }
+
+                    is Rlt.Failed -> Rlt.Failed(getErrorByServerError(result.error))
+                }
+            )
         }
         return liveData
     }

+ 0 - 35
module/message/src/main/res/layout/conversationlist_notice_view.xml

@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="48dp"
-    android:background="@color/color_FFFFDFDF">
-
-    <ImageView
-        android:id="@+id/notice_icon_iv"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_marginStart="20dp"
-        android:src="@drawable/im_ic_error_notice"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <TextView
-        android:id="@+id/notice_tv"
-        style="@style/IMTextStyle.Alignment"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:textSize="14sp"
-        android:textColor="@color/color_AAAAAA"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0.0"
-        app:layout_constraintStart_toEndOf="@+id/notice_icon_iv"
-        app:layout_constraintTop_toTopOf="parent" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 8
module/message/src/main/res/layout/fragment_conversationlist.xml

@@ -1,8 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    xmlns:tools="http://schemas.android.com/tools">
+    android:layout_height="match_parent">
 
     <com.scwang.smart.refresh.layout.SmartRefreshLayout
         android:id="@+id/refresh_layout"
@@ -16,10 +15,4 @@
             android:overScrollMode="never" />
     </com.scwang.smart.refresh.layout.SmartRefreshLayout>
 
-    <include
-        android:id="@+id/notice_container"
-        layout="@layout/conversationlist_notice_view"
-        android:visibility="gone"
-        tools:visibility="visible" />
-
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 45 - 2
module/message/src/main/res/layout/im_conversationlist_item.xml

@@ -100,15 +100,58 @@
             app:layout_constraintBottom_toTopOf="@id/im_conversation_content"/>
 
         <!-- 昵称右侧标签容器 -->
-        <FrameLayout
+        <androidx.appcompat.widget.LinearLayoutCompat
             android:id="@+id/im_conversation_identity_label_container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginEnd="@dimen/rc_margin_size_10"
+            android:orientation="horizontal"
             app:layout_constraintTop_toTopOf="@id/im_conversation_title"
             app:layout_constraintBottom_toBottomOf="@id/im_conversation_title"
             app:layout_constraintStart_toEndOf="@id/im_conversation_title"
-            app:layout_constraintEnd_toStartOf="@id/im_conversation_date"/>
+            app:layout_constraintEnd_toStartOf="@id/im_conversation_date">
+
+            <!--客服标签-->
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/im_conversation_customer_service_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="4dp"
+                android:paddingVertical="2dp"
+                android:drawablePadding="2dp"
+                android:layout_marginStart="4dp"
+                android:visibility="gone"
+                android:textSize="9sp"
+                android:textColor="@color/white"
+                android:drawableStart="@drawable/common_customer_server_ic"
+                android:background="@drawable/common_customer_service_bg"
+                android:text="@string/common_customer_service"/>
+
+            <!--官方人员标签-->
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/im_conversation_official_label_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="4dp"
+                android:includeFontPadding="false"
+                android:text="@string/commonui_official"
+                android:textColor="@color/white"
+                android:textSize="9sp"
+                android:paddingVertical="1dp"
+                android:paddingStart="2dp"
+                android:paddingEnd="6dp"
+                android:visibility="gone"
+                android:drawablePadding="1dp"
+                android:drawableStart="@drawable/common_label_official_inside_ic"
+                android:background="@drawable/label_official_bg"
+                app:layout_constrainedWidth="true"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/icon"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:ignore="SmallSp" />
+
+        </androidx.appcompat.widget.LinearLayoutCompat>
 
         <TextView
             android:id="@+id/im_conversation_content"

+ 0 - 35
module/message/src/main/res/layout/im_conversationlist_notice_view.xml

@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/im_notice_item_height"
-    android:background="@color/im_notice_error_bg">
-
-    <ImageView
-        android:id="@+id/rc_conversationlist_notice_icon_iv"
-        android:layout_width="@dimen/im_notice_item_icon_size"
-        android:layout_height="@dimen/im_notice_item_icon_size"
-        android:layout_marginStart="@dimen/im_notice_item_padding"
-        android:src="@drawable/im_ic_error_notice"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <TextView
-        android:id="@+id/rc_conversationlist_notice_tv"
-        style="@style/IMTextStyle.Alignment"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:textSize="@dimen/im_font_text_third_size"
-        android:textColor="@color/im_text_main_color"
-        android:layout_marginStart="@dimen/im_notice_icon_text_spacing"
-        android:layout_marginEnd="@dimen/im_notice_icon_text_spacing"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0.0"
-        app:layout_constraintStart_toEndOf="@+id/rc_conversationlist_notice_icon_iv"
-        app:layout_constraintTop_toTopOf="parent" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 10 - 0
module/message/src/main/res/layout/im_message_item.xml

@@ -43,6 +43,16 @@
         app:layout_constraintTop_toTopOf="@id/ll_content"
         app:layout_goneMarginStart="@dimen/rc_margin_size_16" />
 
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/im_official_iv"
+        android:layout_width="12dp"
+        android:layout_height="12dp"
+        android:layout_marginBottom="2dp"
+        android:visibility="visible"
+        app:layout_constraintBottom_toBottomOf="@id/im_left_portrait"
+        android:src="@drawable/common_label_official_ic"
+        app:layout_constraintEnd_toEndOf="@id/im_left_portrait" />
+
     <com.adealink.frame.image.view.NetworkImageView
         android:id="@+id/im_right_portrait"
         android:layout_width="@dimen/im_message_portrait_size"

+ 62 - 7
module/message/src/main/res/layout/layout_conversation_top_bar_center_new.xml

@@ -3,31 +3,86 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
-    android:layout_height="44dp">
+    android:layout_height="wrap_content"
+    android:paddingHorizontal="30dp">
 
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/name_tv"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="30dp"
-        android:layout_marginEnd="30dp"
         android:textSize="15sp"
         android:gravity="center"
         android:lines="1"
         android:visibility="gone"
         android:ellipsize="end"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintVertical_chainStyle="packed"
+        app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintTop_toTopOf="parent"
         android:textColor="@color/color_222222"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/other_label_cl"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/official_target_iv"/>
+
+    <!--官方号图标-->
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/official_target_iv"
+        android:layout_width="16dp"
+        android:layout_height="16dp"
+        android:layout_marginStart="4dp"
+        android:visibility="gone"
+        app:layout_constraintTop_toTopOf="@id/name_tv"
+        app:layout_constraintBottom_toBottomOf="@id/name_tv"
+        android:src="@drawable/common_label_official_ic"
+        app:layout_constraintStart_toEndOf="@id/name_tv"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/other_label_cl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"/>
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/name_tv"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+        <!--客服标签-->
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/customer_service_tv"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingHorizontal="4dp"
+            android:paddingVertical="2dp"
+            android:drawablePadding="2dp"
+            android:visibility="gone"
+            android:textSize="9sp"
+            android:includeFontPadding="false"
+            android:textColor="@color/white"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            android:drawableStart="@drawable/common_customer_server_ic"
+            android:background="@drawable/common_customer_service_bg"
+            android:text="@string/common_customer_service"/>
+
+        <!--官方人员标签-->
+        <com.adealink.weparty.module.profile.view.OfficialLabelView
+            android:id="@+id/official_label_tv"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/customer_service_tv" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/intimacy_cl"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:visibility="gone"
-        tools:visibility="visible"
+        tools:visibility="gone"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"

+ 0 - 5
module/message/src/main/res/values-ar/im_strings.xml

@@ -1,11 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <!-- ConversationList -->
-    <string name="rc_conversation_list_notice_network_unavailable">الشبكة غير متاحه, يرجى التأكد من الشبكة</string>
-    <string name="rc_conversation_list_notice_proxy_unavailable">الوكيل غير متوفر ، يرجى التحقق من الوكيل الخاص بك</string>
-    <string name="rc_conversation_list_notice_disconnect">تعذر الوصول للخادم</string>
-    <string name="rc_conversation_list_notice_kicked">تم تسجيل الدخول على هذا الحساب من أجهزة اخرى</string>
-    <string name="rc_conversation_list_notice_connecting">جاري الإتصال…</string>
     <string name="rc_conversation_swipe_pin">تثبيت</string>
     <string name="rc_conversation_swipe_unpin">فك التثبيت</string>
     <string name="rc_conversation_swipe_read">مقروء</string>

+ 0 - 5
module/message/src/main/res/values-zh/im_strings.xml

@@ -3,11 +3,6 @@
     //会话列表
     <string name="rc_conversation_list_title">会话列表</string>
     <string name="rc_conversation_list_empty_prompt">"暂无聊天信息"</string>
-    <string name="rc_conversation_list_notice_network_unavailable">当前网络不可用,请检查你的网络设置</string>
-    <string name="rc_conversation_list_notice_proxy_unavailable">当前设置的代理不可用</string>
-    <string name="rc_conversation_list_notice_disconnect">无法连接到服务器</string>
-    <string name="rc_conversation_list_notice_kicked">该帐号已经在其他设备登录</string>
-    <string name="rc_conversation_list_notice_connecting">连接中…</string>
     <string name="rc_conversation_swipe_pin">置顶</string>
     <string name="rc_conversation_swipe_unpin">取消置顶</string>
     <string name="rc_conversation_swipe_read">已读</string>

+ 0 - 5
module/message/src/main/res/values/im_strings.xml

@@ -3,11 +3,6 @@
     <!-- ConversationList -->
     <string name="rc_conversation_list_title">Conversation List</string>
     <string name="rc_conversation_list_empty_prompt">No chats info</string>
-    <string name="rc_conversation_list_notice_network_unavailable">Network unavailable, please check your network</string>
-    <string name="rc_conversation_list_notice_proxy_unavailable">Proxy unavailable,please check your proxy</string>
-    <string name="rc_conversation_list_notice_disconnect">Unable to connect to server</string>
-    <string name="rc_conversation_list_notice_kicked">This account has been logged-in on other devices</string>
-    <string name="rc_conversation_list_notice_connecting">Connecting…</string>
     <string name="rc_conversation_swipe_pin">Pin</string>
     <string name="rc_conversation_swipe_unpin">Unpin</string>
     <string name="rc_conversation_swipe_read">Read</string>

+ 1 - 1
module/moment/src/main/res/layout/item_moment_gift.xml

@@ -21,7 +21,7 @@
         android:layout_width="11dp"
         android:layout_height="11dp"
         android:elevation="10dp"
-        android:src="@drawable/common_label_official_ic"
+        android:src="@drawable/common_label_bigv_ic"
         android:visibility="gone"
         app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
         app:layout_constraintEnd_toEndOf="@id/iv_avatar"

+ 1 - 1
module/moment/src/main/res/layout/item_moment_like_avatar.xml

@@ -18,6 +18,6 @@
         android:visibility="gone"
         app:layout_constraintBottom_toBottomOf="@+id/iv_avatar"
         app:layout_constraintEnd_toEndOf="@+id/iv_avatar"
-        app:srcCompat="@drawable/common_label_official_ic" />
+        app:srcCompat="@drawable/common_label_bigv_ic" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 1
module/moment/src/main/res/layout/item_moment_msgs_list.xml

@@ -21,7 +21,7 @@
         android:id="@+id/iv_single_official"
         android:layout_width="16dp"
         android:layout_height="16dp"
-        android:src="@drawable/common_label_official_ic"
+        android:src="@drawable/common_label_bigv_ic"
         android:visibility="gone"
         app:layout_constraintBottom_toBottomOf="@+id/iv_single"
         app:layout_constraintEnd_toEndOf="@+id/iv_single"

+ 1 - 1
module/moment/src/main/res/layout/layout_moment_member_info_view.xml

@@ -59,7 +59,7 @@
         android:layout_width="12dp"
         android:layout_height="12dp"
         android:layout_marginStart="4dp"
-        android:src="@drawable/common_label_official_ic"
+        android:src="@drawable/common_label_bigv_ic"
         app:layout_constraintBottom_toBottomOf="@id/tv_name"
         app:layout_constraintEnd_toStartOf="@id/user_certification_view"
         app:layout_constraintStart_toEndOf="@id/tv_name"

+ 56 - 0
module/profile/src/main/java/com/adealink/weparty/profile/me/MeFragment.kt

@@ -7,6 +7,7 @@ import androidx.annotation.StringRes
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.updateLayoutParams
 import androidx.fragment.app.viewModels
+import com.adealink.frame.aab.util.getCompatColor
 import com.adealink.frame.aab.util.getCompatDrawable
 import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.base.Rlt
@@ -30,6 +31,9 @@ import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.debug.Debug
 import com.adealink.weparty.module.account.Account
 import com.adealink.weparty.module.backpack.Backpack
+import com.adealink.weparty.module.message.Message
+import com.adealink.weparty.module.message.MessageModule
+import com.adealink.weparty.module.message.data.CustomerInfo
 import com.adealink.weparty.module.profile.Profile
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.profile.data.Gender
@@ -63,6 +67,7 @@ import com.adealink.weparty.url.H5Page
 import com.adealink.weparty.url.urlConfigService
 import com.adealink.weparty.widget.skin.appOtherBgSkin
 import com.adealink.weparty.widget.skin.skinStyleObserveData
+import io.rong.push.RongPushClient.ConversationType
 import com.adealink.weparty.R as APP_R
 
 @RouterUri(path = [Profile.Me.PATH], desc = "首页我的")
@@ -71,6 +76,8 @@ class MeFragment : BaseFragment(R.layout.fragment_me) {
     private val binding by viewBinding(FragmentMeBinding::bind)
     private val profileViewModel by viewModels<ProfileViewModel> { ProfileViewModelFactory() }
     private val walletViewModel by fastLazy { WalletModule.getWalletViewModel(this) }
+    private val messageViewModel by fastLazy { MessageModule.getMessageViewModel(this.requireActivity()) }
+    private var customerInfo: CustomerInfo? = null
 
     override fun initViews() {
         val activity = activity ?: return
@@ -258,6 +265,38 @@ class MeFragment : BaseFragment(R.layout.fragment_me) {
         initChatAchievement()
     }
 
+    private fun updateCustomerServiceItemView() {
+        if (customerInfo == null) {
+            binding.itemCustomerService.root.gone()
+            return
+        }
+        binding.itemCustomerService.root.show()
+        binding.itemCustomerService.root.onClick {
+            val act = activity ?: return@onClick
+            val customerInfo = customerInfo?: return@onClick
+            Router.build(act, Message.Conversation.PATH)
+                .putExtra(Message.Common.EXTRA_TO_UID, customerInfo.customerUid)
+                .putExtra(Message.Common.EXTRA_CONVERSATION_TYPE, ConversationType.PRIVATE.value)
+                .start()
+        }
+        binding.itemCustomerService.groupListItemImageView.setDefaultDrawable(getCompatDrawable(R.drawable.profile_customer_service_ic))
+        binding.itemCustomerService.groupListItemTextView.text = getCompatString(APP_R.string.common_customer_service)
+        when(customerInfo?.isOnline) {
+            true -> {
+                binding.itemCustomerService.onlineTv.text = getCompatString(APP_R.string.profile_user_online)
+                binding.itemCustomerService.onlineTv.setTextColor(getCompatColor(APP_R.color.color_FF00D956))
+                binding.itemCustomerService.onlineTv.setCompoundDrawablesWithIntrinsicBounds(
+                    getCompatDrawable(APP_R.drawable.profile_user_online_ic), null, null, null)
+            }
+            else -> {
+                binding.itemCustomerService.onlineTv.text = getCompatString(APP_R.string.profile_user_offline)
+                binding.itemCustomerService.onlineTv.setTextColor(getCompatColor(APP_R.color.color_AAAAAA))
+                binding.itemCustomerService.onlineTv.setCompoundDrawablesWithIntrinsicBounds(
+                    getCompatDrawable(APP_R.drawable.profile_user_offline_ic), null, null, null)
+            }
+        }
+    }
+
     override fun loadData() {
         super.loadData()
         updateWithUserInfo(ProfileModule.getMyUserInfo())
@@ -330,6 +369,8 @@ class MeFragment : BaseFragment(R.layout.fragment_me) {
         profileViewModel.getChatAchieveActivityInfo()
 
         checkChatAchievementGuide()
+
+        getCustomerStatus()
     }
 
     private fun checkChatAchievementGuide() {
@@ -401,5 +442,20 @@ class MeFragment : BaseFragment(R.layout.fragment_me) {
         }
     }
 
+    private fun getCustomerStatus() {
+        messageViewModel?.getCustomerInfo()?.observe(viewLifecycleOwner) {
+            when (it) {
+                is Rlt.Success -> {
+                    customerInfo = it.data
+                    updateCustomerServiceItemView()
+                }
+
+                is Rlt.Failed -> {
+                    updateCustomerServiceItemView()
+                }
+            }
+        }
+    }
+
 
 }

BIN
module/profile/src/main/res/drawable-xhdpi/profile_customer_service_ic.webp


+ 7 - 0
module/profile/src/main/res/drawable/profile_customer_online_bg.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/color_FFF5F7FA" />
+    <corners android:radius="25dp" />
+    <stroke android:color="@color/color_FFE1E3E6" android:width="0.5dp"/>
+</shape>

+ 11 - 2
module/profile/src/main/res/layout/fragment_me.xml

@@ -485,18 +485,27 @@
                 android:layout_width="match_parent"
                 android:layout_height="52dp"
                 android:visibility="gone"
-                app:layout_constraintBottom_toTopOf="@id/item_account_and_security"
+                app:layout_constraintBottom_toTopOf="@id/item_customer_service"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/item_backpack" />
 
+            <include
+                android:id="@+id/item_customer_service"
+                layout="@layout/layout_custom_service_list_item"
+                android:layout_width="match_parent"
+                android:layout_height="52dp"
+                android:visibility="gone"
+                app:layout_constraintBottom_toTopOf="@id/item_account_and_security"
+                app:layout_constraintTop_toBottomOf="@id/item_sellcoin" />
+
             <include
                 android:id="@+id/item_account_and_security"
                 layout="@layout/layout_profile_me_list_item"
                 android:layout_width="match_parent"
                 android:layout_height="52dp"
                 app:layout_constraintBottom_toTopOf="@id/divider_3"
-                app:layout_constraintTop_toBottomOf="@id/item_sellcoin" />
+                app:layout_constraintTop_toBottomOf="@id/item_customer_service" />
 
             <View
                 android:id="@+id/divider_3"

+ 73 - 0
module/profile/src/main/res/layout/layout_custom_service_list_item.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="52dp"
+    android:background="@drawable/common_item_click_selector"
+    tools:layout_height="52dp"
+    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
+
+    <com.adealink.weparty.widget.skin.SkinView
+        android:id="@+id/group_list_item_imageView"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="12dp"
+        android:contentDescription="@null"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:skinDynamicImgScaleType="fitCenter"
+        app:skinStaticImgScaleType="fitCenter" />
+
+    <!--名称-->
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/group_list_item_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="12dp"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textColor="@color/color_222222"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/online_tv"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toEndOf="@+id/group_list_item_imageView"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_goneMarginStart="0dp"
+        tools:text="标题"
+        tools:textColor="#000"
+        tools:textSize="12sp"
+        tools:visibility="visible" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/online_tv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="6dp"
+        android:paddingHorizontal="6dp"
+        android:paddingVertical="2dp"
+        android:drawablePadding="2dp"
+        tools:drawableStart="@drawable/profile_user_online_ic"
+        tools:textColor="@color/color_FF00D956"
+        android:text="@string/profile_user_online"
+        android:textSize="12sp"
+        android:background="@drawable/profile_customer_online_bg"
+        app:layout_constraintEnd_toStartOf="@+id/group_list_item_accessoryView"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/group_list_item_accessoryView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="20dp"
+        android:src="@drawable/profile_me_arrow_icon"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 44 - 0
module/setting/src/main/java/com/adealink/weparty/setting/chat/ChatSettingActivity.kt

@@ -11,9 +11,13 @@ import com.adealink.frame.util.DisplayUtil
 import com.adealink.weparty.commonui.BaseActivity
 import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
 import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.onFailure
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.commonui.widget.switchbutton.SwitchButton
 import com.adealink.weparty.module.message.Message
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.profile.data.UserInfo
+import com.adealink.weparty.module.profile.decorate.data.DecorType
 import com.adealink.weparty.setting.R
 import com.adealink.weparty.setting.SettingViewModelFactory
 import com.adealink.weparty.setting.data.CUSTOM_MESSAGE_MIN_PRICE
@@ -34,6 +38,32 @@ class ChatSettingActivity : BaseActivity() {
 
         val myUserInfo = ProfileModule.getMyUserInfo() ?: return
         updateCustomMessagePriceEntry(myUserInfo)
+
+        binding.sendMatchSwitch.checkChangedListener = object : SwitchButton.OnCheckChangedListener {
+            override fun onChecked(isChecked: Boolean, fromUser: Boolean) {
+                if (fromUser) {
+                    viewModel.updateSystemPushSwitch(DecorType.USER_SEND_SYSTEM_PUSH, isChecked).observe(this@ChatSettingActivity) {
+                        showFailedToast(it)
+                        it.onFailure {
+                            binding.sendMatchSwitch.setChecked(!isChecked)
+                        }
+                    }
+                }
+            }
+        }
+
+        binding.receiveMatchSwitch.checkChangedListener = object : SwitchButton.OnCheckChangedListener {
+            override fun onChecked(isChecked: Boolean, fromUser: Boolean) {
+                if (fromUser) {
+                    viewModel.updateSystemPushSwitch(DecorType.USER_RECV_SYSTEM_PUSH, isChecked).observe(this@ChatSettingActivity) {
+                        showFailedToast(it)
+                        it.onFailure {
+                            binding.receiveMatchSwitch.setChecked(!isChecked)
+                        }
+                    }
+                }
+            }
+        }
     }
 
     private fun updateCustomMessagePriceEntry(userInfo: UserInfo) {
@@ -72,6 +102,18 @@ class ChatSettingActivity : BaseActivity() {
             getCompatString(R.string.setting_custom_message_price, myCustomPrice)
     }
 
+    private fun updateSendSystemPushSwitch(userInfo: UserInfo) {
+        //开关默认是true
+        val open = userInfo.commonConfigInfo?.getSendSystemPushSwitch() ?: true
+        binding.sendMatchSwitch.setChecked(open)
+    }
+
+    private fun updateReceiveSystemPushSwitch(userInfo: UserInfo) {
+        //开关默认是true
+        val open = userInfo.commonConfigInfo?.getReceiveSystemPushSwitch() ?: true
+        binding.receiveMatchSwitch.setChecked(open)
+    }
+
     override fun loadData() {
         viewModel.getUserInfo()
     }
@@ -83,6 +125,8 @@ class ChatSettingActivity : BaseActivity() {
 
         viewModel.userInfoLD.observe(this) {
             updateCustomPrice(it)
+            updateSendSystemPushSwitch(it)
+            updateReceiveSystemPushSwitch(it)
         }
     }
 }

+ 41 - 1
module/setting/src/main/java/com/adealink/weparty/setting/chat/ChatSettingViewModel.kt

@@ -4,6 +4,8 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.coroutine.dispatcher.Dispatcher
+import com.adealink.frame.ext.toInt
+import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
 import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.module.profile.ProfileModule
@@ -36,9 +38,47 @@ class ChatSettingViewModel : BaseViewModel() {
         }
     }
 
+    fun updateSystemPushSwitch(decorType: DecorType, open: Boolean): LiveData<Rlt<Any>> {
+        val liveData: LiveData<Rlt<Any>> = OnceMutableLiveData()
+        viewModelScope.launch {
+            when (val rlt = ProfileModule.updateUserConfig(
+                decorType,
+                open.toInt().toString()
+            )) {
+                is Rlt.Success -> {
+                    liveData.send(rlt)
+                    val userInfo = ProfileModule.getMyUserInfo()
+                    when (decorType) {
+                        DecorType.USER_RECV_SYSTEM_PUSH -> {
+                            userInfo?.commonConfigInfo?.updateReceiveSystemPushSwitch(open.toInt())
+                        }
+                        DecorType.USER_SEND_SYSTEM_PUSH -> {
+                            userInfo?.commonConfigInfo?.updateSendSystemPushSwitch(open.toInt())
+                        }
+                        else -> {
+                            //ntd.
+                        }
+                    }
+                }
+                is Rlt.Failed -> {
+                    liveData.send(rlt)
+                }
+            }
+
+        }
+        return liveData
+    }
+
     fun getUserInfo() {
         viewModelScope.launch(Dispatcher.WENEXT_THREAD_POOL) {
-            val rlt = ProfileModule.getUserInfoByUid(ProfileModule.getMyUid(), attrSet = setOf(UserConfigType.USER_CUSTOM_CHAT_PRICE))
+            val rlt = ProfileModule.getUserInfoByUid(
+                ProfileModule.getMyUid(),
+                attrSet = setOf(
+                    UserConfigType.USER_CUSTOM_CHAT_PRICE,
+                    UserConfigType.USER_RECV_SYSTEM_PUSH,
+                    UserConfigType.USER_SEND_SYSTEM_PUSH
+                )
+            )
             when (rlt) {
                 is Rlt.Success -> {
                     userInfoLD.send(rlt.data)

+ 89 - 0
module/setting/src/main/res/layout/activity_chat_setting.xml

@@ -116,4 +116,93 @@
         app:layout_constraintStart_toStartOf="@id/cl_greeting_setting"
         app:layout_constraintTop_toBottomOf="@id/cl_greeting_setting" />
 
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_receive_match_setting"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:layout_marginHorizontal="12dp"
+        android:layout_marginTop="20dp"
+        android:background="@drawable/common_white_radius_12_bg"
+        app:layout_constraintTop_toBottomOf="@id/tv_tips_greeting_setting">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_receive_match_setting"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:text="@string/setting_accept_match_msg"
+            android:textColor="@color/color_222222"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <com.adealink.weparty.commonui.widget.switchbutton.SwitchButton
+            android:id="@+id/receive_match_switch"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="12dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_tips_receive_match_setting"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="12dp"
+        android:layout_marginTop="4dp"
+        android:text="@string/setting_accept_match_msg_tip"
+        android:textColor="@color/color_777777"
+        android:textSize="12sp"
+        app:layout_constraintEnd_toEndOf="@id/cl_receive_match_setting"
+        app:layout_constraintStart_toStartOf="@id/cl_receive_match_setting"
+        app:layout_constraintTop_toBottomOf="@id/cl_receive_match_setting" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_send_match_setting"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:layout_marginHorizontal="12dp"
+        android:layout_marginTop="20dp"
+        android:background="@drawable/common_white_radius_12_bg"
+        app:layout_constraintTop_toBottomOf="@id/tv_tips_receive_match_setting">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_send_match_setting"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:text="@string/setting_send_match_msg"
+            android:textColor="@color/color_222222"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <com.adealink.weparty.commonui.widget.switchbutton.SwitchButton
+            android:id="@+id/send_match_switch"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="12dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_tips_send_match_setting"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="12dp"
+        android:layout_marginTop="4dp"
+        android:text="@string/setting_send_match_msg_tip"
+        android:textColor="@color/color_777777"
+        android:textSize="12sp"
+        app:layout_constraintEnd_toEndOf="@id/cl_send_match_setting"
+        app:layout_constraintStart_toStartOf="@id/cl_send_match_setting"
+        app:layout_constraintTop_toBottomOf="@id/cl_send_match_setting" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 4 - 0
module/setting/src/main/res/values-ar/strings.xml

@@ -65,4 +65,8 @@
     <string name="setting_custom_message_price_tips">عدد العملات المنفقة لكل رسالة من الجنس الآخر أثناء الدردشة معك</string>
     <string name="setting_greeting_setting">إعداد التحية</string>
     <string name="setting_greeting_setting_tips">قم بتعيين رسالة التحية السريعة للدردشة، سيقوم النظام تلقائيًا بإرسال أول تحية تم تعيينها إلى الجنس الآخر عند المطابقة</string>
+    <string name="setting_accept_match_msg">استقبال رسائل المطابقة</string>
+    <string name="setting_accept_match_msg_tip">عند الإيقاف، لن يقوم النظام بمطابقتك تلقائيًا مع الجنس الآخر</string>
+    <string name="setting_send_match_msg">إرسال رسائل المطابقة</string>
+    <string name="setting_send_match_msg_tip">عند الإيقاف، لن يقوم النظام بمطابقتك تلقائيًا مع الجنس الآخر</string>
 </resources>

+ 4 - 0
module/setting/src/main/res/values-zh/strings.xml

@@ -65,4 +65,8 @@
     <string name="setting_custom_message_price_tips">与异性聊天时,每条消息消耗的金币数量</string>
     <string name="setting_greeting_setting">打招呼设置</string>
     <string name="setting_greeting_setting_tips">设置聊天快捷招呼语,系统在匹配时会自动向异性发送第一条设置的招呼语</string>
+    <string name="setting_accept_match_msg">接收匹配消息</string>
+    <string name="setting_accept_match_msg_tip">关闭后系统将不再为你自动匹配异性</string>
+    <string name="setting_send_match_msg">发送匹配消息</string>
+    <string name="setting_send_match_msg_tip">关闭后系统将不会把你自动匹配给异性</string>
 </resources>

+ 4 - 0
module/setting/src/main/res/values/strings.xml

@@ -65,4 +65,8 @@
     <string name="setting_custom_message_price_tips">The number of coins spent per message of the opposite sex chatting with you</string>
     <string name="setting_greeting_setting">Greeting Setting</string>
     <string name="setting_greeting_setting_tips">Set the chat quick greeting, the system will automatically send the first set of greeting to the opposite sex when pairing</string>
+    <string name="setting_accept_match_msg">Receive Match Messages</string>
+    <string name="setting_accept_match_msg_tip">When turned off, the system will no longer automatically match you with the opposite sex</string>
+    <string name="setting_send_match_msg">Send Match Messages</string>
+    <string name="setting_send_match_msg_tip">When turned off, the system will no longer automatically match you to the opposite sex</string>
 </resources>