Ver código fonte

【Android】update version to 2.8.1

rulongzhang 1 ano atrás
pai
commit
daeeeb3c1d
100 arquivos alterados com 4868 adições e 3986 exclusões
  1. 2 2
      Android/app/build.gradle
  2. 3 3
      Android/build.gradle
  3. 3 1
      Android/timcommon/build.gradle
  4. 29 25
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIMessageBean.java
  5. 39 26
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageBaseHolder.java
  6. 208 178
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageContentHolder.java
  7. 0 812
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/SelectTextHelper.java
  8. 512 0
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/SelectionHelper.java
  9. 9 10
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/IndicatorView.java
  10. 0 221
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MessageProperties.java
  11. 51 0
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/CenterImageSpan.java
  12. 1 27
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceManager.java
  13. 0 33
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/fragments/BaseFragment.java
  14. 1 0
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/ShadeImageView.java
  15. 21 6
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/highlight/HighlightPresenter.java
  16. 0 135
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/CornerTransform.java
  17. 2 14
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/GlideEngine.java
  18. 336 0
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/config/classicui/TUIConfigClassic.java
  19. 273 0
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/config/minimalistui/TUIConfigMinimalist.java
  20. 41 30
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageBaseHolder.java
  21. 275 212
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageContentHolder.java
  22. 4 2
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MinimalistMessageLayout.java
  23. 10 3
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ReplyPreviewView.java
  24. 1 1
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileUtil.java
  25. 0 7
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/PopWindowUtil.java
  26. 0 2
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ScreenUtil.java
  27. 12 0
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TUIUtil.java
  28. 132 0
      Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TextUtil.java
  29. 0 0
      Android/timcommon/src/main/res/drawable/indicator_point_nomal.png
  30. 0 0
      Android/timcommon/src/main/res/drawable/indicator_point_select.png
  31. 6 9
      Android/timcommon/src/main/res/layout/message_adapter_item_content.xml
  32. 40 35
      Android/timcommon/src/main/res/layout/minimalist_message_adapter_item_content.xml
  33. 31 0
      Android/timcommon/src/main/res/values-zh-rHK/strings.xml
  34. 0 0
      Android/timcommon/src/main/res/values-zh/strings.xml
  35. 6 0
      Android/tuichat/build.gradle
  36. 1 1
      Android/tuichat/src/main/AndroidManifest.xml
  37. 10 19
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/TUIChatService.java
  38. 0 1
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/ChatInfo.java
  39. 0 33
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/GroupApplyInfo.java
  40. 0 48
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/GroupMemberInfo.java
  41. 1 1
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/InputMoreItem.java
  42. 0 29
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/OfflineMessageBean.java
  43. 0 2
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/OfflinePushInfo.java
  44. 0 54
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/noticelayout/INoticeLayout.java
  45. 1 6
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/noticelayout/NoticeLayout.java
  46. 6 6
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/noticelayout/NoticeLayoutConfig.java
  47. 0 12
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/popmenu/ChatPopMenu.java
  48. 0 2
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/interfaces/IChatLayout.java
  49. 0 27
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/interfaces/IMessageLayout.java
  50. 3 3
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/page/TUIBaseChatActivity.java
  51. 24 48
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/page/TUIBaseChatFragment.java
  52. 0 245
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/setting/ChatLayoutSetting.java
  53. 91 29
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/ChatView.java
  54. 3 2
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/BaseInputFragment.java
  55. 129 294
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/InputView.java
  56. 93 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/ShortcutView.java
  57. 4 4
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/ActionsGridViewAdapter.java
  58. 5 5
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/ActionsPagerAdapter.java
  59. 3 3
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/InputMoreFragment.java
  60. 2 2
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/InputMoreLayout.java
  61. 5 5
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/MessageAdapter.java
  62. 103 267
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/MessageRecyclerView.java
  63. 3 1
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/reply/ReplyDetailsView.java
  64. 5 7
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/CallingMessageHolder.java
  65. 12 4
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/FileMessageHolder.java
  66. 20 8
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/ImageMessageHolder.java
  67. 11 4
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/MergeMessageHolder.java
  68. 22 9
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/QuoteMessageHolder.java
  69. 20 15
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/ReplyMessageHolder.java
  70. 62 23
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/TextMessageHolder.java
  71. 12 8
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/TipsMessageHolder.java
  72. 20 14
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/VideoMessageHolder.java
  73. 27 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/AlbumPicker.java
  74. 54 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/ChatMultimediaRecorderImpl.java
  75. 32 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/SystemAlbumPickerImpl.java
  76. 115 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/SystemMultimediaRecorderImpl.java
  77. 108 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/VideoRecorder.java
  78. 0 6
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/audio/AudioPlayer.java
  79. 1 1
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/progress/ProgressPresenter.java
  80. 7 259
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/GeneralConfig.java
  81. 46 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/ShortcutMenuConfig.java
  82. 776 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/classicui/TUIChatConfigClassic.java
  83. 758 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/minimalistui/TUIChatConfigMinimalist.java
  84. 15 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/AlbumPickerListener.java
  85. 10 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/IAlbumPicker.java
  86. 10 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/IMultimediaRecorder.java
  87. 10 0
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/MultimediaRecorderListener.java
  88. 0 165
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/component/EmojiIndicatorView.java
  89. 0 54
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/component/noticelayout/INoticeLayout.java
  90. 5 10
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/component/noticelayout/NoticeLayout.java
  91. 1 1
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/interfaces/IMessageLayout.java
  92. 7 6
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/page/MessageDetailMinimalistActivity.java
  93. 3 3
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/page/TUIBaseChatMinimalistActivity.java
  94. 24 78
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/page/TUIBaseChatMinimalistFragment.java
  95. 0 242
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/setting/ChatLayoutSetting.java
  96. 66 16
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/ChatView.java
  97. 0 17
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/input/BaseInputFragment.java
  98. 55 78
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/input/InputView.java
  99. 4 4
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/input/inputmore/InputMoreDialogFragment.java
  100. 15 11
      Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/message/MessageAdapter.java

+ 2 - 2
Android/app/build.gradle

@@ -8,8 +8,8 @@ android {
 
     defaultConfig {
         applicationId 'com.tencent.liteav.tuiroom'
-        versionName "2.7.0"
-        versionCode 270
+        versionName "2.8.1"
+        versionCode 281
         minSdkVersion 19
         targetSdkVersion 30
         multiDexEnabled true

+ 3 - 3
Android/build.gradle

@@ -34,7 +34,7 @@ task clean(type: Delete) {
 }
 
 ext {
-    liteavSdk = "com.tencent.liteav:LiteAVSDK_TRTC:12.1.0.14886"
-    roomEngineSdk = "io.trtc.uikit:rtc_room_engine:2.7.0.112"
-    imSdk = "com.tencent.imsdk:imsdk-plus:8.2.6325"
+    liteavSdk = "com.tencent.liteav:LiteAVSDK_TRTC:12.2.0.15072"
+    roomEngineSdk = "io.trtc.uikit:rtc_room_engine:2.8.0.51"
+    imSdk = "com.tencent.imsdk:imsdk-plus:8.3.6498"
 }

+ 3 - 1
Android/timcommon/build.gradle

@@ -42,7 +42,9 @@ dependencies {
     plugin-build-End*/
 
     def projects = this.rootProject.getAllprojects().stream().map { project -> project.name }.collect()
-    api projects.contains("tuicore") ? project(':tuicore') : "com.tencent.liteav.tuikit:tuicore:8.1.6103"
+    api projects.contains("tuicore") ? project(':tuicore') : "com.tencent.liteav.tuikit:tuicore:8.3.6498"
+
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
     implementation 'com.google.code.gson:gson:2.9.1'
     implementation 'androidx.appcompat:appcompat:1.3.1'
     implementation 'com.github.bumptech.glide:glide:4.12.0'

+ 29 - 25
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIMessageBean.java

@@ -17,35 +17,12 @@ import java.util.Map;
 import java.util.Set;
 
 public abstract class TUIMessageBean implements Serializable {
-    /**
-     *
-     * message normal
-     */
-    public static final int MSG_STATUS_NORMAL = 0;
-    /**
-     *
-     * message sending
-     */
+
     public static final int MSG_STATUS_SENDING = V2TIMMessage.V2TIM_MSG_STATUS_SENDING;
-    /**
-     *
-     * message send success
-     */
     public static final int MSG_STATUS_SEND_SUCCESS = V2TIMMessage.V2TIM_MSG_STATUS_SEND_SUCC;
-    /**
-     *
-     * message send failed
-     */
     public static final int MSG_STATUS_SEND_FAIL = V2TIMMessage.V2TIM_MSG_STATUS_SEND_FAIL;
-
-    /**
-     *
-     * messaage revoked
-     */
     public static final int MSG_STATUS_REVOKE = V2TIMMessage.V2TIM_MSG_STATUS_LOCAL_REVOKED;
 
-    /**
-     */
     public static final int MSG_SOURCE_UNKNOWN = 0;
 
     public static final int MSG_SOURCE_ONLINE_PUSH = 1;
@@ -69,6 +46,9 @@ public abstract class TUIMessageBean implements Serializable {
     private MessageRepliesBean messageRepliesBean;
     private boolean hasReaction = false;
     private Map<String, UserBean> userBeanMap = new LinkedHashMap<>();
+    private boolean isSending = false;
+    private boolean isProcessing = false;
+    private Object processingThumbnail;
 
     public void setExcludeFromHistory(boolean excludeFromHistory) {
         this.excludeFromHistory = excludeFromHistory;
@@ -420,7 +400,7 @@ public abstract class TUIMessageBean implements Serializable {
         List<MessageRepliesBean.ReplyBean> replyBeanList = messageRepliesBean.getReplies();
         if (replyBeanList != null && !replyBeanList.isEmpty()) {
             for (MessageRepliesBean.ReplyBean replyBean : replyBeanList) {
-                if (userBean != null) {
+                if (userBean != null && TextUtils.equals(replyBean.getMessageSender(), userID)) {
                     replyBean.setSenderFaceUrl(userBean.getFaceUrl());
                     replyBean.setSenderShowName(userBean.getDisplayString());
                 }
@@ -432,6 +412,14 @@ public abstract class TUIMessageBean implements Serializable {
         return userBeanMap.get(userID);
     }
 
+    public boolean isSending() {
+        return isSending;
+    }
+
+    public void setSending(boolean sending) {
+        this.isSending = sending;
+    }
+
     public Set<String> getAdditionalUserIDList() {
         Set<String> userIdSet = new HashSet<>();
         MessageRepliesBean messageRepliesBean = getMessageRepliesBean();
@@ -444,6 +432,22 @@ public abstract class TUIMessageBean implements Serializable {
         return userIdSet;
     }
 
+    public void setProcessing(boolean processing) {
+        isProcessing = processing;
+    }
+
+    public boolean isProcessing() {
+        return isProcessing;
+    }
+
+    public Object getProcessingThumbnail() {
+        return processingThumbnail;
+    }
+
+    public void setProcessingThumbnail(Object processingThumbnail) {
+        this.processingThumbnail = processingThumbnail;
+    }
+
     public boolean needAsyncGetDisplayString() {
         return false;
     }

+ 39 - 26
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageBaseHolder.java

@@ -2,7 +2,6 @@ package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
 
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
 import android.view.View;
 import android.widget.CheckBox;
 import android.widget.FrameLayout;
@@ -12,8 +11,8 @@ import android.widget.TextView;
 import androidx.recyclerview.widget.RecyclerView;
 import com.tencent.qcloud.tuikit.timcommon.R;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.timcommon.component.MessageProperties;
 import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
+import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
@@ -22,7 +21,6 @@ import java.util.Date;
 
 public abstract class MessageBaseHolder<T extends TUIMessageBean> extends RecyclerView.ViewHolder {
     public ICommonMessageAdapter mAdapter;
-    public MessageProperties properties = MessageProperties.getInstance();
     protected OnItemClickListener onItemClickListener;
 
     public TextView chatTimeText;
@@ -35,6 +33,7 @@ public abstract class MessageBaseHolder<T extends TUIMessageBean> extends Recycl
     public RelativeLayout rightGroupLayout;
     public RelativeLayout mContentLayout;
     private HighlightListener highlightListener;
+    protected T currentMessageBean;
 
     public MessageBaseHolder(View itemView) {
         super(itemView);
@@ -77,16 +76,9 @@ public abstract class MessageBaseHolder<T extends TUIMessageBean> extends Recycl
     }
 
     public void layoutViews(final T msg, final int position) {
+        currentMessageBean = msg;
         registerHighlightListener(msg.getId());
-        if (properties.getChatTimeBubble() != null) {
-            chatTimeText.setBackground(properties.getChatTimeBubble());
-        }
-        if (properties.getChatTimeFontColor() != 0) {
-            chatTimeText.setTextColor(properties.getChatTimeFontColor());
-        }
-        if (properties.getChatTimeFontSize() != 0) {
-            chatTimeText.setTextSize(properties.getChatTimeFontSize());
-        }
+        setChatTimeStyle();
 
         if (position > 1) {
             TUIMessageBean last = mAdapter.getItem(position - 1);
@@ -104,25 +96,46 @@ public abstract class MessageBaseHolder<T extends TUIMessageBean> extends Recycl
         }
     }
 
-    private void registerHighlightListener(String msgID) {
-        highlightListener = new HighlightListener() {
-            @Override
-            public void onHighlightStart() {}
+    private void setChatTimeStyle() {
+        Drawable chatTimeBubble = TUIConfigClassic.getChatTimeBubble();
+        if (chatTimeBubble != null) {
+            chatTimeText.setBackground(chatTimeBubble);
+        }
+        int chatTimeFontColor = TUIConfigClassic.getChatTimeFontColor();
+        if (chatTimeFontColor != TUIConfigClassic.UNDEFINED) {
+            chatTimeText.setTextColor(chatTimeFontColor);
+        }
+        int chatTimeFontSize = TUIConfigClassic.getChatTimeFontSize();
+        if (chatTimeFontSize != TUIConfigClassic.UNDEFINED) {
+            chatTimeText.setTextSize(chatTimeFontSize);
+        }
+    }
 
-            @Override
-            public void onHighlightEnd() {
-                clearHighLightBackground();
-            }
+    private void registerHighlightListener(String msgID) {
+        if (highlightListener == null) {
+            highlightListener = new HighlightListener() {
+                @Override
+                public void onHighlightStart() {}
+
+                @Override
+                public void onHighlightEnd() {
+                    clearHighLightBackground();
+                }
 
-            @Override
-            public void onHighlightUpdate(int color) {
-                setHighLightBackground(color);
-            }
-        };
+                @Override
+                public void onHighlightUpdate(int color) {
+                    setHighLightBackground(color);
+                }
+            };
+        }
         HighlightPresenter.registerHighlightListener(msgID, highlightListener);
     }
 
-    public void onRecycled() {}
+    public void onRecycled() {
+        if (currentMessageBean != null) {
+            HighlightPresenter.unregisterHighlightListener(currentMessageBean.getId());
+        }
+    }
 
     public void setMessageBubbleZeroPadding() {
         if (msgArea == null) {

+ 208 - 178
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageContentHolder.java

@@ -1,5 +1,10 @@
 package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
 
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.view.Gravity;
 import android.view.View;
@@ -9,7 +14,12 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.RecyclerView;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestBuilder;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
 import com.tencent.imsdk.v2.V2TIMManager;
 import com.tencent.imsdk.v2.V2TIMMessage;
 import com.tencent.imsdk.v2.V2TIMUserFullInfo;
@@ -18,15 +28,11 @@ import com.tencent.qcloud.tuicore.TUIConstants;
 import com.tencent.qcloud.tuicore.TUICore;
 import com.tencent.qcloud.tuicore.TUIThemeManager;
 import com.tencent.qcloud.tuikit.timcommon.R;
-import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
 import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
-import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView;
-import com.tencent.qcloud.tuikit.timcommon.interfaces.UserFaceUrlCache;
+import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
 import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
-import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonLog;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
@@ -35,9 +41,10 @@ import java.util.Locale;
 import java.util.Map;
 
 public abstract class MessageContentHolder<T extends TUIMessageBean> extends MessageBaseHolder<T> {
-    public UserIconView leftUserIcon;
-    public UserIconView rightUserIcon;
-    public TextView usernameText;
+
+    public ImageView leftUserIcon;
+    public ImageView rightUserIcon;
+    public TextView leftUserNameText;
     public LinearLayout msgContentLinear;
     public View riskContentLine;
     public TextView riskContentText;
@@ -46,7 +53,6 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
     public TextView isReadText;
     public TextView unreadAudioText;
     public TextView messageDetailsTimeTv;
-    private LinearLayout bottomContentArea;
     private FrameLayout bottomContentFrameLayout;
     private View bottomFailedIv;
 
@@ -55,34 +61,35 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
     public boolean isMultiSelectMode = false;
 
     private List<TUIMessageBean> mForwardDataSource = new ArrayList<>();
-    protected SelectTextHelper selectableTextHelper;
-    
+    protected SelectionHelper selectionHelper;
+
     // Whether to display the bottom content. The merged-forwarded message details activity does not display the bottom content.
     protected boolean isNeedShowBottomLayout = true;
     protected boolean isShowRead = false;
-    private BaseFragment fragment;
+    private Fragment fragment;
     private RecyclerView recyclerView;
     protected boolean hasRiskContent = false;
+    protected boolean isLayoutOnStart = true;
 
     public MessageContentHolder(View itemView) {
         super(itemView);
         leftUserIcon = itemView.findViewById(R.id.left_user_icon_view);
         rightUserIcon = itemView.findViewById(R.id.right_user_icon_view);
-        usernameText = itemView.findViewById(R.id.user_name_tv);
+        leftUserNameText = itemView.findViewById(R.id.left_user_name_tv);
         msgContentLinear = itemView.findViewById(R.id.msg_content_ll);
         riskContentLine = itemView.findViewById(R.id.risk_content_line);
         riskContentText = itemView.findViewById(R.id.risk_content_text);
         statusImage = itemView.findViewById(R.id.message_status_iv);
         sendingProgress = itemView.findViewById(R.id.message_sending_pb);
+        sendingProgress.getIndeterminateDrawable().mutate();
         isReadText = itemView.findViewById(R.id.is_read_tv);
         unreadAudioText = itemView.findViewById(R.id.audio_unread);
         messageDetailsTimeTv = itemView.findViewById(R.id.msg_detail_time_tv);
-        bottomContentArea = itemView.findViewById(R.id.bottom_content_area);
         bottomContentFrameLayout = itemView.findViewById(R.id.bottom_content_fl);
         bottomFailedIv = itemView.findViewById(R.id.bottom_failed_iv);
     }
 
-    public void setFragment(BaseFragment fragment) {
+    public void setFragment(Fragment fragment) {
         this.fragment = fragment;
     }
 
@@ -115,37 +122,57 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
 
     @Override
     public void layoutViews(final T msg, final int position) {
+        Context context = itemView.getContext();
+        if (context instanceof Activity) {
+            if (((Activity) context).isDestroyed()) {
+                return;
+            }
+        }
+
         hasRiskContent = msg.hasRiskContent();
         super.layoutViews(msg, position);
+        setLayoutAlignment(msg);
         setUserIcon(msg);
         setUserName(msg);
         loadAvatar(msg);
         setSendingProgress(msg);
         setStatusImage(msg);
-        setMessageBubbleBackground(msg, position);
+        setMessageBubbleBackground();
         setOnClickListener(msg, position);
 
-        if (isForwardMode || isReplyDetailMode) {
-            setGravity(true);
-            msgContentLinear.removeView(msgAreaAndReply);
-            msgContentLinear.addView(msgAreaAndReply);
-        } else {
-            if (msg.isSelf()) {
-                setGravity(false);
-                msgContentLinear.removeView(msgAreaAndReply);
-                msgContentLinear.addView(msgAreaAndReply);
-            } else {
-                setGravity(true);
-                msgContentLinear.removeView(msgAreaAndReply);
-                msgContentLinear.addView(msgAreaAndReply, 0);
-            }
-        }
-
         if (rightGroupLayout != null) {
             rightGroupLayout.setVisibility(View.VISIBLE);
         }
         msgContentLinear.setVisibility(View.VISIBLE);
 
+        setReadStatus(msg);
+
+        if (isReplyDetailMode) {
+            chatTimeText.setVisibility(View.GONE);
+        }
+
+        setReplyContent(msg);
+        setReactContent(msg);
+        if (isNeedShowBottomLayout) {
+            setBottomContent(msg);
+        }
+        bottomFailedIv.setVisibility(View.GONE);
+        if (hasRiskContent) {
+            bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_risk_bg);
+            if (bottomContentFrameLayout.getVisibility() == View.VISIBLE) {
+                bottomFailedIv.setVisibility(View.VISIBLE);
+            }
+            riskContentLine.setVisibility(View.VISIBLE);
+        } else {
+            riskContentLine.setVisibility(View.GONE);
+            bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_bg);
+        }
+
+        setMessageBubbleDefaultPadding();
+        layoutVariableViews(msg, position);
+    }
+
+    private void setReadStatus(T msg) {
         // clear isReadText status
         isReadText.setTextColor(isReadText.getResources().getColor(R.color.text_gray1));
         isReadText.setOnClickListener(null);
@@ -167,54 +194,71 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
             }
             unreadAudioText.setVisibility(View.GONE);
         }
+    }
 
-        if (isReplyDetailMode) {
-            chatTimeText.setVisibility(View.GONE);
-        }
-
-        setReplyContent(msg);
-        setReactContent(msg);
-        if (isNeedShowBottomLayout) {
-            setBottomContent(msg);
-        }
-        bottomFailedIv.setVisibility(View.GONE);
-        if (hasRiskContent) {
-            bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_risk_bg);
-            if (bottomContentFrameLayout.getVisibility() == View.VISIBLE) {
-                bottomFailedIv.setVisibility(View.VISIBLE);
+    private void setLayoutAlignment(TUIMessageBean msg) {
+        if (isForwardMode || isReplyDetailMode) {
+            isLayoutOnStart = true;
+        } else {
+            if (msg.isSelf()) {
+                isLayoutOnStart = false;
+            } else {
+                isLayoutOnStart = true;
             }
+        }
+        if (isForwardMode || isReplyDetailMode) {
+            msgContentLinear.removeView(msgAreaAndReply);
+            msgContentLinear.addView(msgAreaAndReply);
         } else {
-            bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_bg);
+            if (msg.isSelf()) {
+                msgContentLinear.removeView(msgAreaAndReply);
+                msgContentLinear.addView(msgAreaAndReply);
+            } else {
+                msgContentLinear.removeView(msgAreaAndReply);
+                msgContentLinear.addView(msgAreaAndReply, 0);
+            }
         }
-
-        setMessageBubbleDefaultPadding();
-        layoutVariableViews(msg, position);
+        setGravity(isLayoutOnStart);
     }
 
-    private void setMessageBubbleBackground(T msg, int position) {
+    private void setMessageBubbleBackground() {
+        if (!TUIConfigClassic.isEnableMessageBubbleStyle()) {
+            setMessageBubbleBackground(null);
+            return;
+        }
+
+        Drawable sendBubble = TUIConfigClassic.getSendBubbleBackground();
+        Drawable receiveBubble = TUIConfigClassic.getReceiveBubbleBackground();
+        Drawable sendErrorBubble = TUIConfigClassic.getSendErrorBubbleBackground();
+        Drawable receiveErrorBubble = TUIConfigClassic.getReceiveErrorBubbleBackground();
+
         if (hasRiskContent) {
-            if (msg.isSelf()) {
-                setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_right);
+            if (!isLayoutOnStart) {
+                if (sendErrorBubble != null) {
+                    setMessageBubbleBackground(sendErrorBubble);
+                } else {
+                    setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_right);
+                }
             } else {
-                setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_left);
+                if (receiveErrorBubble != null) {
+                    setMessageBubbleBackground(receiveErrorBubble);
+                } else {
+                    setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_left);
+                }
             }
         } else {
             setRiskContent(null);
-            if (isForwardMode || isReplyDetailMode) {
-                setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_other_bg));
+            if (isLayoutOnStart) {
+                if (receiveBubble != null) {
+                    setMessageBubbleBackground(receiveBubble);
+                } else {
+                    setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_other_bg));
+                }
             } else {
-                if (msg.isSelf()) {
-                    if (properties.getRightBubble() != null && properties.getRightBubble().getConstantState() != null) {
-                        setMessageBubbleBackground(properties.getRightBubble().getConstantState().newDrawable());
-                    } else {
-                        setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_self_bg));
-                    }
+                if (sendBubble != null) {
+                    setMessageBubbleBackground(sendBubble);
                 } else {
-                    if (properties.getLeftBubble() != null && properties.getLeftBubble().getConstantState() != null) {
-                        setMessageBubbleBackground(properties.getLeftBubble().getConstantState().newDrawable());
-                    } else {
-                        setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_other_bg));
-                    }
+                    setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_self_bg));
                 }
             }
         }
@@ -312,58 +356,60 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
 
     private void setSendingProgress(T msg) {
         if (isForwardMode || isReplyDetailMode) {
-            sendingProgress.setVisibility(View.GONE);
+            hideSendingProgress();
         } else {
             if (msg.isSelf()) {
-                if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL || msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_SUCCESS || msg.isPeerRead()) {
-                    sendingProgress.setVisibility(View.GONE);
+                if (msg.isSending()) {
+                    showSendingProgress();
                 } else {
-                    sendingProgress.setVisibility(View.VISIBLE);
+                    hideSendingProgress();
                 }
             } else {
-                sendingProgress.setVisibility(View.GONE);
+                hideSendingProgress();
             }
         }
     }
 
+    protected void showSendingProgress() {
+        sendingProgress.setVisibility(View.VISIBLE);
+        Drawable drawable = sendingProgress.getIndeterminateDrawable();
+        if (drawable instanceof Animatable) {
+            ((Animatable) drawable).start();
+        }
+    }
+
+    protected void hideSendingProgress() {
+        sendingProgress.setVisibility(View.GONE);
+    }
+
+    @SuppressLint("WrongConstant")
     private void setUserName(T msg) {
         if (isForwardMode || isReplyDetailMode) {
-            usernameText.setVisibility(View.VISIBLE);
+            leftUserNameText.setVisibility(View.VISIBLE);
         } else {
-            if (msg.isSelf()) {
-                if (properties.getRightNameVisibility() == 0) {
-                    usernameText.setVisibility(View.GONE);
+            if (isLayoutOnStart) {
+                if (TUIConfigClassic.getReceiveNickNameVisibility() != TUIConfigClassic.UNDEFINED) {
+                    leftUserNameText.setVisibility(TUIConfigClassic.getReceiveNickNameVisibility());
                 } else {
-                    usernameText.setVisibility(properties.getRightNameVisibility());
-                }
-            } else {
-                if (properties.getLeftNameVisibility() == 0) {
                     if (msg.isGroup()) {
-                        usernameText.setVisibility(View.VISIBLE);
+                        leftUserNameText.setVisibility(View.VISIBLE);
                     } else {
-                        usernameText.setVisibility(View.GONE);
+                        leftUserNameText.setVisibility(View.GONE);
                     }
-                } else {
-                    usernameText.setVisibility(properties.getLeftNameVisibility());
                 }
+            } else {
+                leftUserNameText.setVisibility(View.GONE);
             }
         }
-        if (properties.getNameFontColor() != 0) {
-            usernameText.setTextColor(properties.getNameFontColor());
-        }
-        if (properties.getNameFontSize() != 0) {
-            usernameText.setTextSize(properties.getNameFontSize());
+        if (TUIConfigClassic.getReceiveNickNameColor() != TUIConfigClassic.UNDEFINED) {
+            leftUserNameText.setTextColor(TUIConfigClassic.getReceiveNickNameColor());
         }
 
-        if (!TextUtils.isEmpty(msg.getNameCard())) {
-            usernameText.setText(msg.getNameCard());
-        } else if (!TextUtils.isEmpty(msg.getFriendRemark())) {
-            usernameText.setText(msg.getFriendRemark());
-        } else if (!TextUtils.isEmpty(msg.getNickName())) {
-            usernameText.setText(msg.getNickName());
-        } else {
-            usernameText.setText(msg.getSender());
+        if (TUIConfigClassic.getReceiveNickNameFontSize() != TUIConfigClassic.UNDEFINED) {
+            leftUserNameText.setTextSize(TUIConfigClassic.getReceiveNickNameFontSize());
         }
+
+        leftUserNameText.setText(msg.getUserDisplayName());
     }
 
     private void setUserIcon(T msg) {
@@ -379,32 +425,6 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
                 rightUserIcon.setVisibility(View.GONE);
             }
         }
-        if (properties.getAvatar() != 0) {
-            leftUserIcon.setDefaultImageResId(properties.getAvatar());
-            rightUserIcon.setDefaultImageResId(properties.getAvatar());
-        } else {
-            leftUserIcon.setDefaultImageResId(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), R.attr.core_default_user_icon));
-            rightUserIcon.setDefaultImageResId(TUIThemeManager.getAttrResId(rightUserIcon.getContext(), R.attr.core_default_user_icon));
-        }
-        if (properties.getAvatarRadius() != 0) {
-            leftUserIcon.setRadius(properties.getAvatarRadius());
-            rightUserIcon.setRadius(properties.getAvatarRadius());
-        } else {
-            int radius = ScreenUtil.dip2px(4);
-            leftUserIcon.setRadius(radius);
-            rightUserIcon.setRadius(radius);
-        }
-        if (properties.getAvatarSize() != null && properties.getAvatarSize().length == 2) {
-            ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
-            params.width = properties.getAvatarSize()[0];
-            params.height = properties.getAvatarSize()[1];
-            leftUserIcon.setLayoutParams(params);
-
-            params = rightUserIcon.getLayoutParams();
-            params.width = properties.getAvatarSize()[0];
-            params.height = properties.getAvatarSize()[1];
-            rightUserIcon.setLayoutParams(params);
-        }
     }
 
     private void setBottomContent(TUIMessageBean msg) {
@@ -417,6 +437,12 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
     }
 
     private void loadAvatar(TUIMessageBean msg) {
+        Drawable drawable = TUIConfigClassic.getDefaultAvatarImage();
+        if (drawable != null) {
+            setupAvatar(drawable);
+            return;
+        }
+
         if (msg.isUseMsgReceiverAvatar() && mAdapter != null) {
             String cachedFaceUrl = mAdapter.getUserFaceUrlCache().getCachedFaceUrl(msg.getSender());
             if (cachedFaceUrl == null) {
@@ -439,34 +465,59 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
 
                     @Override
                     public void onError(int code, String desc) {
-                        setupAvatar("", msg.isSelf());
+                        setupAvatar("");
                     }
                 });
             } else {
-                setupAvatar(cachedFaceUrl, msg.isSelf());
+                setupAvatar(cachedFaceUrl);
             }
         } else {
-            setupAvatar(msg.getFaceUrl(), msg.isSelf());
+            setupAvatar(msg.getFaceUrl());
         }
     }
 
-    private void setupAvatar(String faceUrl, boolean right) {
-        if (!TextUtils.isEmpty(faceUrl)) {
-            List<Object> urllist = new ArrayList<>();
-            urllist.add(faceUrl);
-            if (isForwardMode || isReplyDetailMode) {
-                leftUserIcon.setIconUrls(urllist);
-            } else {
-                if (right) {
-                    rightUserIcon.setIconUrls(urllist);
-                } else {
-                    leftUserIcon.setIconUrls(urllist);
-                }
-            }
+    private void setupAvatar(Object faceUrl) {
+        int avatarSize = TUIConfigClassic.getMessageListAvatarSize();
+        if (avatarSize == TUIConfigClassic.UNDEFINED) {
+            avatarSize = ScreenUtil.dip2px(41);
+        }
+        ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
+        params.width = avatarSize;
+        if (leftUserIcon.getVisibility() == View.INVISIBLE) {
+            params.height = 1;
         } else {
-            rightUserIcon.setIconUrls(null);
-            leftUserIcon.setIconUrls(null);
+            params.height = avatarSize;
         }
+        leftUserIcon.setLayoutParams(params);
+
+        params = rightUserIcon.getLayoutParams();
+        params.width = avatarSize;
+        if (rightUserIcon.getVisibility() == View.INVISIBLE) {
+            params.height = 1;
+        } else {
+            params.height = avatarSize;
+        }
+        rightUserIcon.setLayoutParams(params);
+
+        int radius = ScreenUtil.dip2px(4);
+        if (TUIConfigClassic.getMessageListAvatarRadius() != TUIConfigClassic.UNDEFINED) {
+            radius = TUIConfigClassic.getMessageListAvatarRadius();
+        }
+
+        ImageView renderedView;
+        if (isLayoutOnStart) {
+            renderedView = leftUserIcon;
+        } else {
+            renderedView = rightUserIcon;
+        }
+
+        RequestBuilder<Drawable> errorRequestBuilder =
+            Glide.with(itemView.getContext())
+                .load(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
+                .placeholder(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
+                .transform(new RoundedCorners(radius));
+
+        Glide.with(itemView.getContext()).load(faceUrl).transform(new RoundedCorners(radius)).error(errorRequestBuilder).into(renderedView);
     }
 
     protected void setMessageBubbleDefaultPadding() {
@@ -520,7 +571,7 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
         Map<String, Object> param = new HashMap<>();
         param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.MESSAGE, messageBean);
         param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE,
-                TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_CLASSIC);
+            TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_CLASSIC);
         TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.EXTENSION_ID, reactionArea, param);
     }
 
@@ -579,8 +630,8 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
 
     public void onRecycled() {
         super.onRecycled();
-        if (selectableTextHelper != null) {
-            selectableTextHelper.destroy();
+        if (selectionHelper != null) {
+            selectionHelper.destroy();
         }
     }
 
@@ -590,37 +641,24 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
         }
     }
 
-    protected void setSelectableTextHelper(TUIMessageBean msg, TextView textView, int position) {
-        if (selectableTextHelper != null) {
-            selectableTextHelper.destroy();
-            selectableTextHelper.setTextView(textView);
+    protected void setSelectionHelper(TUIMessageBean msg, TextView textView, int position) {
+        if (selectionHelper == null) {
+            selectionHelper = new SelectionHelper();
+        }
+        selectionHelper.setTextView(textView);
+        if (isMultiSelectMode || isForwardMode) {
+            selectionHelper.setFrozen(true);
         } else {
-            selectableTextHelper = new SelectTextHelper.Builder(textView)
-                                       .setCursorHandleColor(TIMCommonService.getAppContext().getResources().getColor(R.color.font_blue))
-                                       .setCursorHandleSizeInDp(18)
-                                       .setSelectedColor(TIMCommonService.getAppContext().getResources().getColor(R.color.test_blue))
-                                       .setSelectAll(true)
-                                       .setScrollShow(false)
-                                       .setSelectedAllNoPop(true)
-                                       .setMagnifierShow(false)
-                                       .build();
-        }
-
-        selectableTextHelper.setSelectListener(new SelectTextHelper.OnSelectListener() {
-            @Override
-            public void onClick(View v) {}
-
-            @Override
-            public void onLongClick(View v) {}
-
+            selectionHelper.setFrozen(false);
+        }
+        selectionHelper.setSelectListener(new SelectionHelper.OnSelectListener() {
             @Override
             public void onTextSelected(CharSequence content) {
                 String selectedText = "";
                 if (!TextUtils.isEmpty(content)) {
                     selectedText = content.toString();
                     msg.setSelectText(selectedText);
-                    TIMCommonLog.d("TextMessageHolder", "onTextSelected selectedText = " + selectedText);
-                    SelectTextHelper.setSelected(selectableTextHelper);
+                    SelectionHelper.setSelected(selectionHelper);
                     if (onItemClickListener != null) {
                         onItemClickListener.onTextSelected(msgArea, position, msg);
                     }
@@ -636,19 +674,11 @@ public abstract class MessageContentHolder<T extends TUIMessageBean> extends Mes
             public void onClickUrl(String url) {}
 
             @Override
-            public void onSelectAllShowCustomPop() {}
-
-            @Override
-            public void onReset() {
-                msg.setSelectText(null);
-                msg.setSelectText(msg.getExtra());
-            }
+            public void onShowPop() {}
 
             @Override
-            public void onDismissCustomPop() {}
+            public void onDismissPop() {}
 
-            @Override
-            public void onScrolling() {}
         });
     }
 

+ 0 - 812
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/SelectTextHelper.java

@@ -1,812 +0,0 @@
-package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.os.Build;
-import android.text.Layout;
-import android.text.Selection;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
-import android.text.style.URLSpan;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.Magnifier;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-import androidx.annotation.ColorInt;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.StringRes;
-
-import java.lang.ref.WeakReference;
-import java.util.LinkedList;
-import java.util.List;
-
-public class SelectTextHelper {
-    private static final String TAG = SelectTextHelper.class.getSimpleName();
-
-    private static int DEFAULT_SELECTION_LENGTH = 2;
-    private static int DEFAULT_SHOW_DURATION = 100;
-
-    private CursorHandle mStartHandle;
-    private CursorHandle mEndHandle;
-    private Magnifier mMagnifier;
-    private SelectionInfo mSelectionInfo = new SelectionInfo();
-    private OnSelectListener mSelectListener;
-
-    private Context mContext;
-    private TextView mTextView;
-    private Spannable mSpannable;
-
-    private int mTouchX;
-    private int mTouchY;
-    private int mTextViewMarginStart = 0; 
-
-    private int mSelectedColor;
-    private int mCursorHandleColor;
-    private int mCursorHandleSize;
-    private boolean mSelectAll;
-    private boolean mSelectedAllNoPop;
-    private boolean mScrollShow;
-    private boolean mMagnifierShow;
-    private int mPopSpanCount;
-    private int mPopBgResource;
-    private int mPopArrowImg;
-    private List<Pair<Integer, String>> itemTextList;
-    private List<Builder.OnSeparateItemClickListener> itemListenerList = new LinkedList<>();
-
-    private boolean isHideWhenScroll;
-    private boolean isHide = true;
-    private boolean usedClickListener = false;
-
-    private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener;
-    private ViewTreeObserver.OnScrollChangedListener mOnScrollChangedListener;
-    private View.OnTouchListener mRootTouchListener;
-
-    private static WeakReference<SelectTextHelper> selectedReference;
-
-    public static void setSelected(SelectTextHelper selected) {
-        SelectTextHelper oldSelected = getSelected();
-        if (oldSelected != null && selected != oldSelected) {
-            oldSelected.reset();
-        }
-        selectedReference = new WeakReference<>(selected);
-    }
-
-    public static void resetSelected() {
-        SelectTextHelper selectTextHelper = getSelected();
-        if (selectTextHelper != null) {
-            selectTextHelper.reset();
-        }
-    }
-
-    private static SelectTextHelper getSelected() {
-        if (selectedReference != null) {
-            return selectedReference.get();
-        }
-        return null;
-    }
-
-    public interface OnSelectListener {
-        void onClick(View v); 
-
-        void onLongClick(View v); 
-
-        void onTextSelected(CharSequence content); 
-
-        void onDismiss(); 
-
-        void onClickUrl(String url); 
-
-        void onSelectAllShowCustomPop(); 
-
-        void onReset(); 
-
-        void onDismissCustomPop(); 
-
-        void onScrolling(); 
-    }
-
-    public static class Builder {
-        private TextView mTextView;
-        private int mCursorHandleColor = 0xFF1379D6;
-        private int mSelectedColor = 0xFFAFE1F4;
-        private float mCursorHandleSizeInDp = 24;
-        private boolean mSelectAll = true;
-        private boolean mSelectedAllNoPop = false;
-        private boolean mScrollShow = true;
-        private boolean mMagnifierShow = true;
-        private int mPopSpanCount = 5;
-        private int mPopBgResource = 0;
-        private int mPopArrowImg = 0;
-        private List<Pair<Integer, String>> itemTextList = new LinkedList<>();
-        private List<OnSeparateItemClickListener> itemListenerList = new LinkedList<>();
-
-        public Builder(TextView textView) {
-            mTextView = textView;
-        }
-
-        public Builder setCursorHandleColor(@ColorInt int cursorHandleColor) {
-            mCursorHandleColor = cursorHandleColor;
-            return this;
-        }
-
-        public Builder setCursorHandleSizeInDp(float cursorHandleSizeInDp) {
-            mCursorHandleSizeInDp = cursorHandleSizeInDp;
-            return this;
-        }
-
-        public Builder setSelectedColor(@ColorInt int selectedBgColor) {
-            mSelectedColor = selectedBgColor;
-            return this;
-        }
-
-        public Builder setSelectAll(boolean selectAll) {
-            mSelectAll = selectAll;
-            return this;
-        }
-
-        public Builder setSelectedAllNoPop(boolean selectedAllNoPop) {
-            mSelectedAllNoPop = selectedAllNoPop;
-            return this;
-        }
-
-        public Builder setScrollShow(boolean scrollShow) {
-            mScrollShow = scrollShow;
-            return this;
-        }
-
-        public Builder setMagnifierShow(boolean magnifierShow) {
-            mMagnifierShow = magnifierShow;
-            return this;
-        }
-
-        public Builder setPopSpanCount(int popSpanCount) {
-            mPopSpanCount = popSpanCount;
-            return this;
-        }
-
-        public Builder setPopStyle(int popBgResource, int popArrowImg) {
-            mPopBgResource = popBgResource;
-            mPopArrowImg = popArrowImg;
-            return this;
-        }
-
-        public Builder addItem(@DrawableRes int drawableId, @StringRes int textResId, OnSeparateItemClickListener listener) {
-            itemTextList.add(new Pair<>(drawableId, mTextView.getContext().getResources().getString(textResId)));
-            itemListenerList.add(listener);
-            return this;
-        }
-
-        public Builder addItem(@DrawableRes int drawableId, String itemText, OnSeparateItemClickListener listener) {
-            itemTextList.add(new Pair<>(drawableId, itemText));
-            itemListenerList.add(listener);
-            return this;
-        }
-
-        public SelectTextHelper build() {
-            return new SelectTextHelper(this);
-        }
-
-        public interface OnSeparateItemClickListener {
-            void onClick();
-        }
-    }
-
-    public SelectTextHelper(Builder builder) {
-        mTextView = builder.mTextView;
-        mContext = mTextView.getContext();
-        mSelectedColor = builder.mSelectedColor;
-        mCursorHandleColor = builder.mCursorHandleColor;
-        mSelectAll = builder.mSelectAll;
-        mScrollShow = builder.mScrollShow;
-        mMagnifierShow = builder.mMagnifierShow;
-        mPopSpanCount = builder.mPopSpanCount;
-        mPopBgResource = builder.mPopBgResource;
-        mPopArrowImg = builder.mPopArrowImg;
-        mSelectedAllNoPop = builder.mSelectedAllNoPop;
-        itemTextList = builder.itemTextList;
-        itemListenerList = builder.itemListenerList;
-        mCursorHandleSize = dp2px(builder.mCursorHandleSizeInDp);
-        init();
-    }
-
-    public void reset() {
-        hideSelectView();
-        resetSelectionInfo();
-        
-        if (mSelectListener != null) {
-            mSelectListener.onReset();
-        }
-    }
-
-    public void setSelectListener(OnSelectListener selectListener) {
-        mSelectListener = selectListener;
-    }
-
-    public void destroy() {
-        mTextView.getViewTreeObserver().removeOnScrollChangedListener(mOnScrollChangedListener);
-        mTextView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
-        mTextView.getRootView().setOnTouchListener(null);
-        reset();
-        mStartHandle = null;
-        mEndHandle = null;
-    }
-
-    public void selectAll() {
-        showAllView();
-    }
-
-    public void setTextView(TextView textView) {
-        this.mTextView = textView;
-        init();
-    }
-
-    private void init() {
-        mTextView.setText(mTextView.getText(), TextView.BufferType.SPANNABLE);
-
-        mTextView.setOnTouchListener((v, event) -> {
-            mTouchX = (int) event.getX();
-            mTouchY = (int) event.getY();
-            return false;
-        });
-
-        mTextView.setOnClickListener(v -> {
-            if (usedClickListener) {
-                usedClickListener = false;
-                return;
-            }
-            if (null != mSelectListener) {
-                mSelectListener.onDismiss();
-            }
-            reset();
-            if (null != mSelectListener) {
-                mSelectListener.onClick(mTextView);
-            }
-        });
-
-        mTextView.setOnLongClickListener(new View.OnLongClickListener() {
-            @Override
-            public boolean onLongClick(View v) {
-                onLongTextViewClick();
-                return true;
-            }
-
-            private void onLongTextViewClick() {
-                mTextView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-                    @Override
-                    public void onViewAttachedToWindow(View v) {}
-
-                    @Override
-                    public void onViewDetachedFromWindow(View v) {
-                        destroy();
-                    }
-                });
-
-                mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        if (isHideWhenScroll) {
-                            isHideWhenScroll = false;
-                            postShowSelectView(DEFAULT_SHOW_DURATION);
-                        }
-                        
-                        if (0 == mTextViewMarginStart) {
-                            int[] location = new int[2];
-                            mTextView.getLocationInWindow(location);
-                            mTextViewMarginStart = location[0];
-                        }
-                        return true;
-                    }
-                };
-                mTextView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
-
-                
-                mRootTouchListener = new View.OnTouchListener() {
-                    @Override
-                    public boolean onTouch(View v, MotionEvent event) {
-                        reset();
-                        mTextView.getRootView().setOnTouchListener(null);
-                        return false;
-                    }
-                };
-                mTextView.getRootView().setOnTouchListener(mRootTouchListener);
-
-                mOnScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() {
-                    @Override
-                    public void onScrollChanged() {
-                        if (mScrollShow) {
-                            if (!isHideWhenScroll && !isHide) {
-                                isHideWhenScroll = true;
-                                if (mStartHandle != null) {
-                                    mStartHandle.dismiss();
-                                }
-                                if (mEndHandle != null) {
-                                    mEndHandle.dismiss();
-                                }
-                            }
-                            if (null != mSelectListener) {
-                                mSelectListener.onScrolling();
-                            }
-                        } else {
-                            reset();
-                        }
-                    }
-                };
-                mTextView.getViewTreeObserver().addOnScrollChangedListener(mOnScrollChangedListener);
-
-                if (mSelectAll) {
-                    showAllView();
-                } else {
-                    showSelectView(mTouchX, mTouchY);
-                }
-                if (null != mSelectListener) {
-                    mSelectListener.onLongClick(mTextView);
-                }
-            }
-        });
-        
-        mTextView.setMovementMethod(new LinkMovementMethodInterceptor());
-    }
-
-    private void postShowSelectView(int duration) {
-        mTextView.removeCallbacks(mShowSelectViewRunnable);
-        if (duration <= 0) {
-            mShowSelectViewRunnable.run();
-        } else {
-            mTextView.postDelayed(mShowSelectViewRunnable, duration);
-        }
-    }
-
-    private final Runnable mShowSelectViewRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (isHide) {
-                return;
-            }
-            if (mStartHandle != null) {
-                showCursorHandle(mStartHandle);
-            }
-            if (mEndHandle != null) {
-                showCursorHandle(mEndHandle);
-            }
-        }
-    };
-
-    private void hideSelectView() {
-        isHide = true;
-        usedClickListener = false;
-        if (mStartHandle != null) {
-            mStartHandle.dismiss();
-        }
-        if (mEndHandle != null) {
-            mEndHandle.dismiss();
-        }
-    }
-
-    private void resetSelectionInfo() {
-        mSelectionInfo.mSelectionContent = null;
-        if (mSpannable != null) {
-            Selection.removeSelection(mSpannable);
-        }
-    }
-
-    private void showSelectView(int x, int y) {
-        reset();
-        isHide = false;
-        if (mStartHandle == null) {
-            mStartHandle = new CursorHandle(true);
-        }
-        if (mEndHandle == null) {
-            mEndHandle = new CursorHandle(false);
-        }
-
-        int startOffset = getPreciseOffset(mTextView, x, y);
-        int endOffset = startOffset + DEFAULT_SELECTION_LENGTH;
-        if (mTextView.getText() instanceof Spannable) {
-            mSpannable = (Spannable) mTextView.getText();
-        }
-        if (mSpannable == null || endOffset - 1 >= mTextView.getText().length()) {
-            return;
-        }
-        selectText(startOffset, endOffset);
-        showCursorHandle(mStartHandle);
-        showCursorHandle(mEndHandle);
-    }
-
-    /**
-     * Select all
-     */
-    private void showAllView() {
-        reset();
-        isHide = false;
-        if (mStartHandle == null) {
-            mStartHandle = new CursorHandle(true);
-        }
-        if (mEndHandle == null) {
-            mEndHandle = new CursorHandle(false);
-        }
-
-        if (mTextView.getText() instanceof Spannable) {
-            mSpannable = (Spannable) mTextView.getText();
-        }
-        if (mSpannable == null) {
-            return;
-        }
-        selectText(0, mTextView.getText().length());
-        showCursorHandle(mStartHandle);
-        showCursorHandle(mEndHandle);
-    }
-
-    private void showCursorHandle(CursorHandle cursorHandle) {
-        Layout layout = mTextView.getLayout();
-        if (layout == null) {
-            return;
-        }
-        int offset = cursorHandle.isLeft ? mSelectionInfo.mStart : mSelectionInfo.mEnd;
-        cursorHandle.show((int) layout.getPrimaryHorizontal(offset), layout.getLineBottom(layout.getLineForOffset(offset)));
-    }
-
-    private void selectText(int startPos, int endPos) {
-        if (startPos != -1) {
-            mSelectionInfo.mStart = startPos;
-        }
-        if (endPos != -1) {
-            mSelectionInfo.mEnd = endPos;
-        }
-        if (mSelectionInfo.mStart > mSelectionInfo.mEnd) {
-            int temp = mSelectionInfo.mStart;
-            mSelectionInfo.mStart = mSelectionInfo.mEnd;
-            mSelectionInfo.mEnd = temp;
-        }
-
-        mTextView.requestFocus();
-        mSelectionInfo.mSelectionContent = mSpannable.subSequence(mSelectionInfo.mStart, mSelectionInfo.mEnd).toString();
-        Selection.setSelection(mSpannable, mSelectionInfo.mStart, mSelectionInfo.mEnd);
-        if (mSelectListener != null) {
-            mSelectListener.onTextSelected(mSelectionInfo.mSelectionContent);
-        }
-    }
-
-    private class CursorHandle extends View {
-        private PopupWindow mPopupWindow;
-        private Paint mPaint;
-
-        private int mCircleRadius = mCursorHandleSize / 2;
-        private int mWidth = mCursorHandleSize;
-        private int mHeight = mCursorHandleSize;
-        private int mPadding = 32; 
-        private boolean isLeft;
-
-        public CursorHandle(boolean isLeft) {
-            super(mContext);
-            this.isLeft = isLeft;
-            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-            mPaint.setColor(mCursorHandleColor);
-
-            mPopupWindow = new PopupWindow(this);
-            mPopupWindow.setClippingEnabled(false);
-            mPopupWindow.setWidth(mWidth + mPadding * 2);
-            mPopupWindow.setHeight(mHeight + mPadding / 2);
-            invalidate();
-        }
-
-        @Override
-        protected void onDraw(Canvas canvas) {
-            canvas.drawCircle(mCircleRadius + mPadding, mCircleRadius, mCircleRadius, mPaint);
-            if (isLeft) {
-                canvas.drawRect(mCircleRadius + mPadding, 0, mCircleRadius * 2 + mPadding, mCircleRadius, mPaint);
-            } else {
-                canvas.drawRect(mPadding, 0, mCircleRadius + mPadding, mCircleRadius, mPaint);
-            }
-        }
-
-        private int mAdjustX;
-        private int mAdjustY;
-
-        private int mBeforeDragStart;
-        private int mBeforeDragEnd;
-
-        @Override
-        public boolean onTouchEvent(MotionEvent event) {
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_DOWN:
-                    mBeforeDragStart = mSelectionInfo.mStart;
-                    mBeforeDragEnd = mSelectionInfo.mEnd;
-                    mAdjustX = (int) event.getX();
-                    mAdjustY = (int) event.getY();
-                    break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL:
-                    if (mMagnifierShow) {
-                        
-                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && null != mMagnifier) {
-                            mMagnifier.dismiss();
-                        }
-                    }
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    if (null != mSelectListener) {
-                        mSelectListener.onDismissCustomPop();
-                    }
-                    int rawX = (int) event.getRawX();
-                    int rawY = (int) event.getRawY();
-                    
-                    update(rawX + mAdjustX - mWidth - mTextViewMarginStart, rawY + mAdjustY - mHeight - (int) mTextView.getTextSize());
-                    if (mMagnifierShow) {
-                        
-                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                            if (null == mMagnifier) {
-                                mMagnifier = new Magnifier(mTextView);
-                                mMagnifier.getWidth();
-                            }
-                            final int[] viewPosition = new int[2];
-                            mTextView.getLocationOnScreen(viewPosition);
-                            int magnifierX = rawX - viewPosition[0];
-                            int magnifierY = rawY - viewPosition[1] - dp2px(32);
-                            mMagnifier.show(magnifierX, Math.max(magnifierY, 0));
-                        }
-                    }
-                    break;
-                default:
-                    break;
-            }
-            return true;
-        }
-
-        private void changeDirection() {
-            isLeft = !isLeft;
-            invalidate();
-        }
-
-        public void dismiss() {
-            mPopupWindow.dismiss();
-        }
-
-        private int[] mTempCoors = new int[2];
-
-        public void update(int x, int y) {
-            mTextView.getLocationInWindow(mTempCoors);
-            int oldOffset;
-            if (isLeft) {
-                oldOffset = mSelectionInfo.mStart;
-            } else {
-                oldOffset = mSelectionInfo.mEnd;
-            }
-
-            y -= mTempCoors[1];
-
-            int offset = getHysteresisOffset(mTextView, x, y, oldOffset);
-
-            if (offset != oldOffset) {
-                resetSelectionInfo();
-                if (isLeft) {
-                    if (offset > mBeforeDragEnd) {
-                        CursorHandle handle = getCursorHandle(false);
-                        changeDirection();
-                        handle.changeDirection();
-                        mBeforeDragStart = mBeforeDragEnd;
-                        selectText(mBeforeDragEnd, offset);
-                        handle.updateCursorHandle();
-                    } else {
-                        selectText(offset, -1);
-                    }
-                    updateCursorHandle();
-                } else {
-                    if (offset < mBeforeDragStart) {
-                        CursorHandle handle = getCursorHandle(true);
-                        handle.changeDirection();
-                        changeDirection();
-                        mBeforeDragEnd = mBeforeDragStart;
-                        selectText(offset, mBeforeDragStart);
-                        handle.updateCursorHandle();
-                    } else {
-                        selectText(mBeforeDragStart, offset);
-                    }
-                    updateCursorHandle();
-                }
-            }
-        }
-
-        private void updateCursorHandle() {
-            mTextView.getLocationInWindow(mTempCoors);
-            Layout layout = mTextView.getLayout();
-            if (isLeft) {
-                mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.mStart) - mWidth + getExtraX(),
-                    layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.mStart)) + getExtraY(), -1, -1);
-            } else {
-                mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.mEnd) + getExtraX(),
-                    layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.mEnd)) + getExtraY(), -1, -1);
-            }
-        }
-
-        public void show(int x, int y) {
-            mTextView.getLocationInWindow(mTempCoors);
-            int offset = isLeft ? mWidth : 0;
-            mPopupWindow.showAtLocation(mTextView, Gravity.NO_GRAVITY, x - offset + getExtraX(), y + getExtraY());
-        }
-
-        public int getExtraX() {
-            return mTempCoors[0] - mPadding + mTextView.getPaddingLeft();
-        }
-
-        public int getExtraY() {
-            return mTempCoors[1] + mTextView.getPaddingTop();
-        }
-    }
-
-    private CursorHandle getCursorHandle(boolean isLeft) {
-        if (mStartHandle.isLeft == isLeft) {
-            return mStartHandle;
-        } else {
-            return mEndHandle;
-        }
-    }
-
-    private class SelectionInfo {
-        public int mStart;
-        public int mEnd;
-        public String mSelectionContent;
-    }
-
-    private class LinkMovementMethodInterceptor extends LinkMovementMethod {
-        private long downLinkTime;
-
-        @Override
-        public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
-            int action = event.getAction();
-
-            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-
-                x -= widget.getTotalPaddingLeft();
-                y -= widget.getTotalPaddingTop();
-
-                x += widget.getScrollX();
-                y += widget.getScrollY();
-
-                Layout layout = widget.getLayout();
-                int line = layout.getLineForVertical(y);
-                int off = layout.getOffsetForHorizontal(line, x);
-
-                ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
-
-                if (links.length != 0) {
-                    if (action == MotionEvent.ACTION_UP) {
-                        
-                        if (downLinkTime + ViewConfiguration.getLongPressTimeout() < System.currentTimeMillis()) {
-                            return false;
-                        }
-                        
-                        if (links[0] instanceof URLSpan) {
-                            URLSpan url = (URLSpan) links[0];
-                            if (!TextUtils.isEmpty(url.getURL())) {
-                                if (null != mSelectListener) {
-                                    usedClickListener = true;
-                                    mSelectListener.onClickUrl(url.getURL());
-                                }
-                                return true;
-                            } else {
-                                links[0].onClick(widget);
-                            }
-                        }
-                    } else if (action == MotionEvent.ACTION_DOWN) {
-                        downLinkTime = System.currentTimeMillis();
-                        Selection.setSelection(buffer, buffer.getSpanStart(links[0]), buffer.getSpanEnd(links[0]));
-                    }
-                    return true;
-                } else {
-                    Selection.removeSelection(buffer);
-                }
-            }
-
-            return super.onTouchEvent(widget, buffer, event);
-        }
-    }
-
-    // util
-
-    public static int getPreciseOffset(TextView textView, int x, int y) {
-        Layout layout = textView.getLayout();
-        if (layout != null) {
-            int topVisibleLine = layout.getLineForVertical(y);
-            int offset = layout.getOffsetForHorizontal(topVisibleLine, x);
-
-            int offsetX = (int) layout.getPrimaryHorizontal(offset);
-
-            if (offsetX > x) {
-                return layout.getOffsetToLeftOf(offset);
-            } else {
-                return offset;
-            }
-        } else {
-            return -1;
-        }
-    }
-
-    public static int getHysteresisOffset(TextView textView, int x, int y, int previousOffset) {
-        final Layout layout = textView.getLayout();
-        if (layout == null) {
-            return -1;
-        }
-
-        int line = layout.getLineForVertical(y);
-
-        // The "HACK BLOCK"S in this function is required because of how Android Layout for
-        // TextView works - if 'offset' equals to the last character of a line, then
-        //
-        // * getLineForOffset(offset) will result the NEXT line
-        // * getPrimaryHorizontal(offset) will return 0 because the next insertion point is on the next line
-        // * getOffsetForHorizontal(line, x) will not return the last offset of a line no matter where x is
-        // These are highly undesired and is worked around with the HACK BLOCK
-        //
-        // @see Moon+ Reader/Color Note - see how it can't select the last character of a line unless you move
-        // the cursor to the beginning of the next line.
-        //
-        ////////////////////HACK BLOCK////////////////////////////////////////////////////
-
-        if (isEndOfLineOffset(layout, previousOffset)) {
-            // we have to minus one from the offset so that the code below to find
-            // the previous line can work correctly.
-            int left = (int) layout.getPrimaryHorizontal(previousOffset - 1);
-            int right = (int) layout.getLineRight(line);
-            int threshold = (right - left) / 2; // half the width of the last character
-            if (x > right - threshold) {
-                previousOffset -= 1;
-            }
-        }
-        ///////////////////////////////////////////////////////////////////////////////////
-
-        final int previousLine = layout.getLineForOffset(previousOffset);
-        final int previousLineTop = layout.getLineTop(previousLine);
-        final int previousLineBottom = layout.getLineBottom(previousLine);
-        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
-
-        // If new line is just before or after previous line and y position is less than
-        // hysteresisThreshold away from previous line, keep cursor on previous line.
-        if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold))
-            || ((line == previousLine - 1) && ((previousLineTop - y) < hysteresisThreshold))) {
-            line = previousLine;
-        }
-
-        int offset = layout.getOffsetForHorizontal(line, x);
-
-        // This allow the user to select the last character of a line without moving the
-        // cursor to the next line. (As Layout.getOffsetForHorizontal does not return the
-        // offset of the last character of the specified line)
-        //
-        // But this function will probably get called again immediately, must decrement the offset
-        // by 1 to compensate for the change made below. (see previous HACK BLOCK)
-        /////////////////////HACK BLOCK///////////////////////////////////////////////////
-        if (offset < textView.getText().length() - 1) {
-            if (isEndOfLineOffset(layout, offset + 1)) {
-                int left = (int) layout.getPrimaryHorizontal(offset);
-                int right = (int) layout.getLineRight(line);
-                int threshold = (right - left) / 2; // half the width of the last character
-                if (x > right - threshold) {
-                    offset += 1;
-                }
-            }
-        }
-        //////////////////////////////////////////////////////////////////////////////////
-
-        return offset;
-    }
-
-    private static boolean isEndOfLineOffset(Layout layout, int offset) {
-        return offset > 0 && layout.getLineForOffset(offset) == layout.getLineForOffset(offset - 1) + 1;
-    }
-
-    public static int dp2px(float dpValue) {
-        return (int) (dpValue * Resources.getSystem().getDisplayMetrics().density + 0.5f);
-    }
-}

+ 512 - 0
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/SelectionHelper.java

@@ -0,0 +1,512 @@
+package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+import com.tencent.qcloud.tuikit.timcommon.component.face.CenterImageSpan;
+import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
+import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
+import com.tencent.qcloud.tuikit.timcommon.util.TextUtil;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SelectionHelper {
+    private static final String TAG = "SelectionHelper";
+
+    private SelectionHandle startHandle;
+    private SelectionHandle endHandle;
+    private final SelectionInfo mSelectionInfo = new SelectionInfo();
+    private OnSelectListener mSelectListener;
+
+    private TextView textView;
+    private Spannable spannable;
+
+    private int mTextViewMarginStart = 0;
+    private GestureDetector gestureDetector;
+    private GestureDetector.SimpleOnGestureListener gestureListener;
+    private int selectionColor;
+    private int handleColor;
+    private int handleSize;
+    private BackgroundColorSpan bgSpan;
+    private boolean frozen = false;
+
+    private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener;
+    private View.OnAttachStateChangeListener onAttachStateChangeListener;
+    private static WeakReference<SelectionHelper> selectedReference;
+
+    public static void setSelected(SelectionHelper selected) {
+        SelectionHelper oldSelected = getSelected();
+        if (oldSelected != null && selected != oldSelected) {
+            oldSelected.clearSelection();
+        }
+        selectedReference = new WeakReference<>(selected);
+    }
+
+    public static void resetSelected() {
+        SelectionHelper selectionHelper = getSelected();
+        if (selectionHelper != null) {
+            selectionHelper.clearSelection();
+        }
+    }
+
+    private static SelectionHelper getSelected() {
+        if (selectedReference != null) {
+            return selectedReference.get();
+        }
+        return null;
+    }
+
+    public void setFrozen(boolean frozen) {
+        this.frozen = frozen;
+    }
+
+    public interface OnSelectListener {
+        void onTextSelected(CharSequence content);
+
+        void onDismiss();
+
+        void onClickUrl(String url);
+
+        void onShowPop();
+
+        void onDismissPop();
+    }
+
+    public SelectionHelper() {
+        selectionColor = 0x3f1470ff;
+        handleColor = 0xff1470ff;
+        handleSize = ScreenUtil.dip2px(16);
+        gestureListener = new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public void onShowPress(MotionEvent e) {
+                if (frozen) {
+                    return;
+                }
+                initHandler();
+                ClickableSpan[] spans = TextUtil.findSpansByLocation(textView, Math.round(e.getX()), Math.round(e.getY()));
+                if (spans != null && spans.length > 0) {
+                    ClickableSpan span = spans[0];
+                    int spanStart = spannable.getSpanStart(span);
+                    int spanEnd = spannable.getSpanEnd(span);
+                    setSelection(spanStart, spanEnd);
+                } else {
+                    selectAll();
+                }
+            }
+
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                if (frozen) {
+                    return super.onSingleTapUp(e);
+                }
+                ClickableSpan[] spans = TextUtil.findSpansByLocation(textView, Math.round(e.getX()), Math.round(e.getY()));
+                if (spans != null && spans.length > 0) {
+                    ClickableSpan span = spans[0];
+                    span.onClick(textView);
+                }
+                return false;
+            }
+        };
+        gestureDetector = new GestureDetector(gestureListener);
+    }
+
+    public void setSelectListener(OnSelectListener selectListener) {
+        mSelectListener = selectListener;
+    }
+
+    public void destroy() {
+        if (textView == null) {
+            return;
+        }
+        textView.removeOnAttachStateChangeListener(onAttachStateChangeListener);
+        textView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
+        clearSelection();
+    }
+
+    public void selectAll() {
+        initHandler();
+        if (textView.getText() instanceof Spannable) {
+            spannable = (Spannable) textView.getText();
+        }
+        if (spannable == null) {
+            return;
+        }
+        setSelection(0, textView.getText().length());
+    }
+
+    public void setTextView(TextView textView) {
+        this.textView = textView;
+        if (textView.getText() instanceof Spannable) {
+            this.spannable = (Spannable) textView.getText();
+        }
+        textView.removeOnAttachStateChangeListener(onAttachStateChangeListener);
+        onAttachStateChangeListener = new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {}
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                destroy();
+            }
+        };
+        textView.addOnAttachStateChangeListener(onAttachStateChangeListener);
+        textView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
+        mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                int[] location = new int[2];
+                textView.getLocationInWindow(location);
+                mTextViewMarginStart = location[0];
+                return true;
+            }
+        };
+        textView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
+        textView.setMovementMethod(new LinkMovementMethodInterceptor());
+    }
+
+    private void initHandler() {
+        if (startHandle == null) {
+            startHandle = new SelectionHandle(true);
+        }
+        if (endHandle == null) {
+            endHandle = new SelectionHandle(false);
+        }
+    }
+
+    private void showSelectionHandle(SelectionHandle selectionHandle) {
+        Layout layout = textView.getLayout();
+        if (layout == null) {
+            return;
+        }
+        int offset = selectionHandle.isLeft ? mSelectionInfo.start : mSelectionInfo.end;
+        selectionHandle.show((int) layout.getPrimaryHorizontal(offset), layout.getLineBottom(layout.getLineForOffset(offset)));
+    }
+
+    private void setSelection(int startPos, int endPos) {
+        initHandler();
+
+        if (startPos != -1) {
+            mSelectionInfo.start = startPos;
+        }
+        if (endPos != -1) {
+            mSelectionInfo.end = endPos;
+        }
+        if (mSelectionInfo.start > mSelectionInfo.end) {
+            int temp = mSelectionInfo.start;
+            mSelectionInfo.start = mSelectionInfo.end;
+            mSelectionInfo.end = temp;
+        }
+
+        mSelectionInfo.selectionContent = spannable.subSequence(mSelectionInfo.start, mSelectionInfo.end).toString();
+        setSelectionBg(spannable, mSelectionInfo.start, mSelectionInfo.end);
+        showSelectionHandle(startHandle);
+        showSelectionHandle(endHandle);
+        if (mSelectListener != null) {
+            mSelectListener.onTextSelected(mSelectionInfo.selectionContent);
+        }
+    }
+
+    private void setSelectionBg(Spannable text, int start, int end) {
+        if (bgSpan == null) {
+            bgSpan = new BackgroundColorSpan(selectionColor);
+        }
+        text.setSpan(bgSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        CenterImageSpan[] allImageSpans = text.getSpans(0, text.length(), CenterImageSpan.class);
+        CenterImageSpan[] imageSpans = text.getSpans(start, end, CenterImageSpan.class);
+        if (allImageSpans != null) {
+            for (CenterImageSpan imageSpan : allImageSpans) {
+                imageSpan.setBgColor(-1);
+            }
+        }
+        if (imageSpans != null) {
+            for (CenterImageSpan imageSpan : imageSpans) {
+                imageSpan.setBgColor(selectionColor);
+            }
+        }
+    }
+
+    private void clearSelection() {
+        mSelectionInfo.selectionContent = null;
+        clearSelectionBg();
+    }
+
+    private void clearSelectionBg() {
+        if (spannable == null) {
+            return;
+        }
+        if (bgSpan != null) {
+            spannable.removeSpan(bgSpan);
+        }
+        CenterImageSpan[] imageSpans = spannable.getSpans(0, spannable.length(), CenterImageSpan.class);
+        if (imageSpans != null) {
+            for (CenterImageSpan imageSpan : imageSpans) {
+                imageSpan.setBgColor(-1);
+            }
+        }
+        if (startHandle != null) {
+            startHandle.dismiss();
+        }
+        if (endHandle != null) {
+            endHandle.dismiss();
+        }
+    }
+
+    private class SelectionHandle extends View {
+        private PopupWindow mPopupWindow;
+        private Paint mPaint;
+
+        private int mCircleRadius = handleSize / 2;
+        private int mWidth = handleSize;
+        private int mHeight = handleSize;
+        private int mPadding = 32;
+        private boolean isLeft;
+
+        public SelectionHandle(boolean isLeft) {
+            super(textView.getContext());
+            this.isLeft = isLeft;
+            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mPaint.setColor(handleColor);
+
+            mPopupWindow = new PopupWindow(this);
+            mPopupWindow.setClippingEnabled(false);
+            mPopupWindow.setWidth(mWidth + mPadding * 2);
+            mPopupWindow.setHeight(mHeight + mPadding / 2);
+            invalidate();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawCircle(mCircleRadius + mPadding, mCircleRadius, mCircleRadius, mPaint);
+            if (isLeft) {
+                canvas.drawRect(mCircleRadius + mPadding, 0, mCircleRadius * 2 + mPadding, mCircleRadius, mPaint);
+            } else {
+                canvas.drawRect(mPadding, 0, mCircleRadius + mPadding, mCircleRadius, mPaint);
+            }
+        }
+
+        private int mAdjustX;
+        private int mAdjustY;
+
+        private int mBeforeDragStart;
+        private int mBeforeDragEnd;
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mBeforeDragStart = mSelectionInfo.start;
+                    mBeforeDragEnd = mSelectionInfo.end;
+                    mAdjustX = (int) event.getX();
+                    mAdjustY = (int) event.getY();
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    if (null != mSelectListener) {
+                        mSelectListener.onDismissPop();
+                    }
+                    int rawX = (int) event.getRawX();
+                    int rawY = (int) event.getRawY();
+
+                    update(rawX + mAdjustX - mWidth - mTextViewMarginStart, rawY + mAdjustY - mHeight - (int) textView.getTextSize());
+                    break;
+                default:
+                    break;
+            }
+            return true;
+        }
+
+        private void changeDirection() {
+            isLeft = !isLeft;
+            invalidate();
+        }
+
+        public void dismiss() {
+            Log.e(TAG, "handler dismiss");
+            mPopupWindow.dismiss();
+        }
+
+        private int[] mTempCoors = new int[2];
+
+        public void update(int x, int y) {
+            textView.getLocationInWindow(mTempCoors);
+            int oldOffset;
+            if (isLeft) {
+                oldOffset = mSelectionInfo.start;
+            } else {
+                oldOffset = mSelectionInfo.end;
+            }
+
+            y -= mTempCoors[1];
+
+            int offset = getHysteresisOffset(textView, x, y, oldOffset);
+
+            if (offset != oldOffset) {
+                mSelectionInfo.selectionContent = null;
+                if (isLeft) {
+                    if (offset > mBeforeDragEnd) {
+                        SelectionHandle handle = getSelectionHandle(false);
+                        changeDirection();
+                        handle.changeDirection();
+                        mBeforeDragStart = mBeforeDragEnd;
+                        setSelection(mBeforeDragEnd, offset);
+                        handle.updateSelectionHandle();
+                    } else {
+                        setSelection(offset, -1);
+                    }
+                    updateSelectionHandle();
+                } else {
+                    if (offset < mBeforeDragStart) {
+                        SelectionHandle handle = getSelectionHandle(true);
+                        handle.changeDirection();
+                        changeDirection();
+                        mBeforeDragEnd = mBeforeDragStart;
+                        setSelection(offset, mBeforeDragStart);
+                        handle.updateSelectionHandle();
+                    } else {
+                        setSelection(mBeforeDragStart, offset);
+                    }
+                    updateSelectionHandle();
+                }
+            }
+        }
+
+        private void updateSelectionHandle() {
+            textView.getLocationInWindow(mTempCoors);
+            Layout layout = textView.getLayout();
+            if (isLeft) {
+                mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.start) - mWidth + getExtraX(),
+                    layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.start)) + getExtraY(), -1, -1);
+            } else {
+                mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.end) + getExtraX(),
+                    layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.end)) + getExtraY(), -1, -1);
+            }
+        }
+
+        public void show(int x, int y) {
+            textView.getLocationInWindow(mTempCoors);
+            int offset = isLeft ? mWidth : 0;
+            mPopupWindow.showAtLocation(textView, Gravity.NO_GRAVITY, x - offset + getExtraX(), y + getExtraY());
+        }
+
+        public int getExtraX() {
+            return mTempCoors[0] - mPadding + textView.getPaddingLeft();
+        }
+
+        public int getExtraY() {
+            return mTempCoors[1] + textView.getPaddingTop();
+        }
+    }
+
+    private SelectionHandle getSelectionHandle(boolean isLeft) {
+        if (startHandle.isLeft == isLeft) {
+            return startHandle;
+        } else {
+            return endHandle;
+        }
+    }
+
+    private static class SelectionInfo {
+        public int start;
+        public int end;
+        public String selectionContent;
+    }
+
+    private class LinkMovementMethodInterceptor extends LinkMovementMethod {
+        @Override
+        public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+            return gestureDetector.onTouchEvent(event);
+        }
+    }
+
+    public static int getHysteresisOffset(TextView textView, int x, int y, int previousOffset) {
+        final Layout layout = textView.getLayout();
+        if (layout == null) {
+            return -1;
+        }
+
+        int line = layout.getLineForVertical(y);
+
+        // The "HACK BLOCK"S in this function is required because of how Android Layout for
+        // TextView works - if 'offset' equals to the last character of a line, then
+        //
+        // * getLineForOffset(offset) will result the NEXT line
+        // * getPrimaryHorizontal(offset) will return 0 because the next insertion point is on the next line
+        // * getOffsetForHorizontal(line, x) will not return the last offset of a line no matter where x is
+        // These are highly undesired and is worked around with the HACK BLOCK
+        //
+        // @see Moon+ Reader/Color Note - see how it can't select the last character of a line unless you move
+        // the cursor to the beginning of the next line.
+        //
+        ////////////////////HACK BLOCK////////////////////////////////////////////////////
+
+        if (isEndOfLineOffset(layout, previousOffset)) {
+            // we have to minus one from the offset so that the code below to find
+            // the previous line can work correctly.
+            int left = (int) layout.getPrimaryHorizontal(previousOffset - 1);
+            int right = (int) layout.getLineRight(line);
+            int threshold = (right - left) / 2; // half the width of the last character
+            if (x > right - threshold) {
+                previousOffset -= 1;
+            }
+        }
+        ///////////////////////////////////////////////////////////////////////////////////
+
+        final int previousLine = layout.getLineForOffset(previousOffset);
+        final int previousLineTop = layout.getLineTop(previousLine);
+        final int previousLineBottom = layout.getLineBottom(previousLine);
+        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
+
+        // If new line is just before or after previous line and y position is less than
+        // hysteresisThreshold away from previous line, keep cursor on previous line.
+        if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold))
+            || ((line == previousLine - 1) && ((previousLineTop - y) < hysteresisThreshold))) {
+            line = previousLine;
+        }
+
+        int offset = layout.getOffsetForHorizontal(line, x);
+
+        // This allow the user to select the last character of a line without moving the
+        // cursor to the next line. (As Layout.getOffsetForHorizontal does not return the
+        // offset of the last character of the specified line)
+        //
+        // But this function will probably get called again immediately, must decrement the offset
+        // by 1 to compensate for the change made below. (see previous HACK BLOCK)
+        /////////////////////HACK BLOCK///////////////////////////////////////////////////
+        if (offset < textView.getText().length() - 1) {
+            if (isEndOfLineOffset(layout, offset + 1)) {
+                int left = (int) layout.getPrimaryHorizontal(offset);
+                int right = (int) layout.getLineRight(line);
+                int threshold = (right - left) / 2; // half the width of the last character
+                if (x > right - threshold) {
+                    offset += 1;
+                }
+            }
+        }
+        //////////////////////////////////////////////////////////////////////////////////
+
+        return offset;
+    }
+
+    private static boolean isEndOfLineOffset(Layout layout, int offset) {
+        return offset > 0 && layout.getLineForOffset(offset) == layout.getLineForOffset(offset - 1) + 1;
+    }
+}

+ 9 - 10
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/EmojiIndicatorView.java → Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/IndicatorView.java

@@ -1,4 +1,4 @@
-package com.tencent.qcloud.tuikit.tuichat.classicui.component;
+package com.tencent.qcloud.tuikit.timcommon.component;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -11,32 +11,31 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 
+import com.tencent.qcloud.tuikit.timcommon.R;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
-import com.tencent.qcloud.tuikit.tuichat.R;
 
 import java.util.ArrayList;
 
-public class EmojiIndicatorView extends LinearLayout {
+public class IndicatorView extends LinearLayout {
     private Context mContext;
     private ArrayList<ImageView> mImageViews;
     private Bitmap bmpSelect;
-    private Bitmap bmpNomal;
+    private Bitmap bmpNormal;
     private int mHeight = 16;
     private int mMaxHeight;
-    private AnimatorSet mPlayToAnimatorSet;
     private AnimatorSet mPlayByInAnimatorSet;
     private AnimatorSet mPlayByOutAnimatorSet;
 
-    public EmojiIndicatorView(Context context, AttributeSet attrs) {
+    public IndicatorView(Context context, AttributeSet attrs) {
         super(context, attrs);
         this.mContext = context;
         this.setOrientation(HORIZONTAL);
         mMaxHeight = ScreenUtil.dip2px(mHeight);
         bmpSelect = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_select);
-        bmpNomal = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_nomal);
+        bmpNormal = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_nomal);
     }
 
-    public EmojiIndicatorView(Context context) {
+    public IndicatorView(Context context) {
         this(context, null);
     }
 
@@ -54,7 +53,7 @@ public class EmojiIndicatorView extends LinearLayout {
                 imageView.setImageBitmap(bmpSelect);
                 rl.addView(imageView, layoutParams);
             } else {
-                imageView.setImageBitmap(bmpNomal);
+                imageView.setImageBitmap(bmpNormal);
                 rl.addView(imageView, layoutParams);
             }
             this.addView(rl, params);
@@ -110,7 +109,7 @@ public class EmojiIndicatorView extends LinearLayout {
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                imageViewStrat.setImageBitmap(bmpNomal);
+                imageViewStrat.setImageBitmap(bmpNormal);
                 ObjectAnimator animFil1l = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 1.0f);
                 ObjectAnimator animFill2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 1.0f);
                 AnimatorSet mFillAnimatorSet = new AnimatorSet();

+ 0 - 221
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MessageProperties.java

@@ -1,221 +0,0 @@
-package com.tencent.qcloud.tuikit.timcommon.component;
-
-import android.graphics.drawable.Drawable;
-
-import com.tencent.qcloud.tuikit.timcommon.interfaces.IMessageProperties;
-import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
-
-public class MessageProperties implements IMessageProperties {
-    private static MessageProperties sP = new MessageProperties();
-    private int mAvatarId;
-    private int mAvatarRadius;
-    private int[] avatarSize = null;
-    private int mNameFontSize;
-    private int mNameFontColor;
-    private int mLeftNameVisibility;
-    private int mRightNameVisibility;
-    private int mChatContextFontSize;
-    private int mMyChatContentFontColor;
-    private Drawable mMyBubble;
-    private int mFriendChatContentFontColor;
-    private Drawable mFriendBubble;
-    private int mTipsMessageFontSize;
-    private int mTipsMessageFontColor;
-    private Drawable mTipsMessageBubble;
-    private int mChatTimeFontSize;
-    private int mChatTimeFontColor;
-    private Drawable mChatTimeBubble;
-
-    private MessageProperties() {}
-
-    public static MessageProperties getInstance() {
-        if (sP == null) {
-            sP = new MessageProperties();
-        }
-        return sP;
-    }
-
-    @Override
-    public int getAvatarRadius() {
-        return mAvatarRadius;
-    }
-
-    @Override
-    public void setAvatarRadius(int radius) {
-        mAvatarRadius = ScreenUtil.getPxByDp(radius);
-    }
-
-    @Override
-    public int[] getAvatarSize() {
-        return avatarSize;
-    }
-
-    @Override
-    public void setAvatarSize(int[] size) {
-        if (size != null && size.length == 2) {
-            avatarSize = new int[2];
-            avatarSize[0] = ScreenUtil.getPxByDp(size[0]);
-            avatarSize[1] = ScreenUtil.getPxByDp(size[1]);
-        }
-    }
-
-    @Override
-    public int getAvatar() {
-        return mAvatarId;
-    }
-
-    @Override
-    public void setAvatar(int resId) {
-        this.mAvatarId = resId;
-    }
-
-    @Override
-    public Drawable getRightBubble() {
-        return mMyBubble;
-    }
-
-    @Override
-    public void setRightBubble(Drawable bubble) {
-        this.mMyBubble = bubble;
-    }
-
-    @Override
-    public Drawable getLeftBubble() {
-        return mFriendBubble;
-    }
-
-    @Override
-    public void setLeftBubble(Drawable bubble) {
-        this.mFriendBubble = bubble;
-    }
-
-    @Override
-    public int getNameFontSize() {
-        return mNameFontSize;
-    }
-
-    @Override
-    public void setNameFontSize(int size) {
-        this.mNameFontSize = size;
-    }
-
-    @Override
-    public int getNameFontColor() {
-        return mNameFontColor;
-    }
-
-    @Override
-    public void setNameFontColor(int color) {
-        this.mNameFontColor = color;
-    }
-
-    @Override
-    public int getLeftNameVisibility() {
-        return mLeftNameVisibility;
-    }
-
-    @Override
-    public void setLeftNameVisibility(int visibility) {
-        mLeftNameVisibility = visibility;
-    }
-
-    @Override
-    public int getRightNameVisibility() {
-        return mRightNameVisibility;
-    }
-
-    @Override
-    public void setRightNameVisibility(int visibility) {
-        mRightNameVisibility = visibility;
-    }
-
-    @Override
-    public int getChatContextFontSize() {
-        return mChatContextFontSize;
-    }
-
-    @Override
-    public void setChatContextFontSize(int size) {
-        this.mChatContextFontSize = size;
-    }
-
-    @Override
-    public int getRightChatContentFontColor() {
-        return mMyChatContentFontColor;
-    }
-
-    @Override
-    public void setRightChatContentFontColor(int color) {
-        this.mMyChatContentFontColor = color;
-    }
-
-    @Override
-    public int getLeftChatContentFontColor() {
-        return mFriendChatContentFontColor;
-    }
-
-    @Override
-    public void setLeftChatContentFontColor(int color) {
-        this.mFriendChatContentFontColor = color;
-    }
-
-    @Override
-    public Drawable getTipsMessageBubble() {
-        return mTipsMessageBubble;
-    }
-
-    @Override
-    public void setTipsMessageBubble(Drawable bubble) {
-        this.mTipsMessageBubble = bubble;
-    }
-
-    @Override
-    public int getTipsMessageFontSize() {
-        return mTipsMessageFontSize;
-    }
-
-    @Override
-    public void setTipsMessageFontSize(int size) {
-        this.mTipsMessageFontSize = size;
-    }
-
-    @Override
-    public int getTipsMessageFontColor() {
-        return mTipsMessageFontColor;
-    }
-
-    @Override
-    public void setTipsMessageFontColor(int color) {
-        this.mTipsMessageFontColor = color;
-    }
-
-    @Override
-    public Drawable getChatTimeBubble() {
-        return mChatTimeBubble;
-    }
-
-    @Override
-    public void setChatTimeBubble(Drawable bubble) {
-        this.mChatTimeBubble = bubble;
-    }
-
-    @Override
-    public int getChatTimeFontSize() {
-        return mChatTimeFontSize;
-    }
-
-    @Override
-    public void setChatTimeFontSize(int size) {
-        this.mChatTimeFontSize = size;
-    }
-
-    @Override
-    public int getChatTimeFontColor() {
-        return mChatTimeFontColor;
-    }
-
-    @Override
-    public void setChatTimeFontColor(int color) {
-        this.mChatTimeFontColor = color;
-    }
-}

+ 51 - 0
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/CenterImageSpan.java

@@ -0,0 +1,51 @@
+package com.tencent.qcloud.tuikit.timcommon.component.face;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.style.ImageSpan;
+
+import androidx.annotation.NonNull;
+
+public class CenterImageSpan extends ImageSpan {
+
+    private int bgColor = -1;
+
+    public CenterImageSpan(@NonNull Drawable drawable) {
+        super(drawable);
+    }
+
+    public void setBgColor(int bgColor) {
+        this.bgColor = bgColor;
+    }
+
+    @Override
+    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
+        Drawable drawable = getDrawable();
+        Rect rect = drawable.getBounds();
+        Paint.FontMetricsInt paintFm = paint.getFontMetricsInt();
+        int center = (paintFm.top + paintFm.bottom) / 2;
+        if (fm != null) {
+            fm.ascent = center - (rect.height() / 2);
+            fm.descent = center + (rect.height() / 2);
+            fm.top = fm.ascent;
+            fm.bottom = fm.descent;
+        }
+
+        return rect.right;
+    }
+
+    @Override
+    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
+        if (bgColor == -1) {
+            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
+        } else {
+            canvas.save();
+            paint.setColor(bgColor);
+            canvas.drawRect(x, top, getDrawable().getBounds().right + x, bottom, paint);
+            canvas.restore();
+            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
+        }
+    }
+}

+ 1 - 27
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceManager.java

@@ -3,8 +3,6 @@ package com.tencent.qcloud.tuikit.timcommon.component.face;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Paint;
-import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.text.Editable;
@@ -15,7 +13,6 @@ import android.text.style.ImageSpan;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.TextView;
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.DataSource;
@@ -533,7 +530,7 @@ public class FaceManager {
             + "|[\\U0001F9E0-\\U0001F9EF]|[\\U0001F9F0-\\U0001F9FF]|[\\U0001FA70-\\U0001FA74]|[\\U0001FA78-\\U0001FA7C]"
             + "|[\\U0001FA80-\\U0001FA86]|[\\U0001FA90-\\U0001FA9F]|[\\U0001FAA0-\\U0001FAAC]|[\\U0001FAB0-\\U0001FABA]"
             + "|[\\U0001FAC0-\\U0001FAC5]|[\\U0001FAD0-\\U0001FAD9]|[\\U0001FAE0-\\U0001FAE7]|[\\U0001FAF0-\\U0001FAF6]";
-        String unsupport = "0x0023|0x002A|[0x0030-0x0039]|";
+        String unsupport = "\\u0023|\\u002A|[\\u0030-\\u0039]|";
         String emoji = unsupport + support;
 
         // Construct regex of emoji by the rules above.
@@ -554,27 +551,4 @@ public class FaceManager {
 
         return regexEmoji;
     }
-
-    static class CenterImageSpan extends ImageSpan {
-
-        public CenterImageSpan(@NonNull Drawable drawable) {
-            super(drawable);
-        }
-
-        @Override
-        public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
-            Drawable drawable = getDrawable();
-            Rect rect = drawable.getBounds();
-            Paint.FontMetricsInt paintFm = paint.getFontMetricsInt();
-            int center = (paintFm.top + paintFm.bottom) / 2;
-            if (fm != null) {
-                fm.ascent = center - (rect.height() / 2);
-                fm.descent = center + (rect.height() / 2);
-                fm.top = fm.ascent;
-                fm.bottom = fm.descent;
-            }
-
-            return rect.right;
-        }
-    }
 }

+ 0 - 33
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/fragments/BaseFragment.java

@@ -1,33 +0,0 @@
-package com.tencent.qcloud.tuikit.timcommon.component.fragments;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-
-public class BaseFragment extends Fragment {
-    public void forward(Fragment fragment, boolean hide) {
-        forward(getId(), fragment, null, hide);
-    }
-
-    public void forward(int viewId, Fragment fragment, String name, boolean hide) {
-        if (getFragmentManager() == null) {
-            return;
-        }
-        FragmentTransaction trans = getFragmentManager().beginTransaction();
-        if (hide) {
-            trans.hide(this);
-            trans.add(viewId, fragment);
-        } else {
-            trans.replace(viewId, fragment);
-        }
-
-        trans.addToBackStack(name);
-        trans.commitAllowingStateLoss();
-    }
-
-    public void backward() {
-        if (getFragmentManager() == null) {
-            return;
-        }
-        getFragmentManager().popBackStack();
-    }
-}

+ 1 - 0
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/ShadeImageView.java

@@ -87,5 +87,6 @@ public class ShadeImageView extends ImageView {
 
     public void setRadius(int radius) {
         this.radius = radius;
+        invalidate();
     }
 }

+ 21 - 6
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/highlight/HighlightPresenter.java

@@ -8,9 +8,7 @@ import com.tencent.qcloud.tuikit.timcommon.R;
 import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 public class HighlightPresenter {
@@ -29,9 +27,11 @@ public class HighlightPresenter {
 
     private final Map<String, ValueAnimator> highlightMap = new HashMap<>();
 
+    private int highLightDarkColor = -1;
+    private int highLightLightColor = -1;
+
     private HighlightPresenter() {}
 
-    // Use WeakReference, no need to unregister.
     public static void registerHighlightListener(String highlightID, HighlightListener listener) {
         if (listener == null) {
             return;
@@ -39,6 +39,10 @@ public class HighlightPresenter {
         getInstance().highlightListenerMap.put(highlightID, new WeakReference<>(listener));
     }
 
+    public static void unregisterHighlightListener(String highlightID) {
+        getInstance().highlightListenerMap.remove(highlightID);
+    }
+
     public static void startHighlight(String highlightID) {
         getInstance().internalStartHighlight(highlightID);
     }
@@ -47,11 +51,22 @@ public class HighlightPresenter {
         getInstance().internalStopHighlight(highlightID);
     }
 
+    public static void setHighlightDarkColor(int color) {
+        getInstance().highLightDarkColor = color;
+    }
+
+    public static void setHighlightLightColor(int color) {
+        getInstance().highLightLightColor = color;
+    }
+
     private void internalStartHighlight(String highlightID) {
         ValueAnimator highlightAnimator = new ValueAnimator();
-        int highLightColorDark = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_dark_color);
-        int highLightColorLight = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_light_color);
-        highlightAnimator.setIntValues(highLightColorDark, highLightColorLight);
+        if (highLightDarkColor == highLightLightColor && highLightLightColor == -1) {
+            highLightDarkColor = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_dark_color);
+            highLightLightColor = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_light_color);
+        }
+
+        highlightAnimator.setIntValues(highLightDarkColor, highLightLightColor);
         highlightAnimator.setEvaluator(new ArgbEvaluator());
         highlightAnimator.setRepeatCount(DEFAULT_REPEAT_COUNT);
         highlightAnimator.setDuration(DEFAULT_DURATION);

+ 0 - 135
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/CornerTransform.java

@@ -1,135 +0,0 @@
-package com.tencent.qcloud.tuikit.timcommon.component.impl;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import androidx.annotation.NonNull;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.resource.bitmap.BitmapResource;
-import java.security.MessageDigest;
-
-public class CornerTransform implements Transformation<Bitmap> {
-    private BitmapPool mBitmapPool;
-    private float radius;
-    private boolean exceptLeftTop;
-    private boolean exceptRightTop;
-    private boolean exceptLeftBottom;
-    private boolean exceptRightBottom;
-
-    public CornerTransform(Context context, float radius) {
-        this.mBitmapPool = Glide.get(context).getBitmapPool();
-        this.radius = radius;
-    }
-
-    public void setExceptCorner(boolean leftTop, boolean rightTop, boolean leftBottom, boolean rightBottom) {
-        this.exceptLeftTop = leftTop;
-        this.exceptRightTop = rightTop;
-        this.exceptLeftBottom = leftBottom;
-        this.exceptRightBottom = rightBottom;
-    }
-
-    @NonNull
-    @Override
-    public Resource<Bitmap> transform(@NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) {
-        Bitmap source = resource.get();
-        int finalWidth;
-        int finalHeight;
-        
-        // The width-height or height-width ratio of the output target
-        float ratio;
-        if (outWidth > outHeight) {
-            
-            // output width > output height, find the aspect ratio
-            ratio = (float) outHeight / (float) outWidth;
-            finalWidth = source.getWidth();
-            
-            // Fix the width of the original image and find the final height
-            finalHeight = (int) ((float) source.getWidth() * ratio);
-            if (finalHeight > source.getHeight()) {
-                
-                // Find the final height > the original image height, find the aspect ratio
-                ratio = (float) outWidth / (float) outHeight;
-                finalHeight = source.getHeight();
-                
-                // Fix the width of the original image and find the final width
-                finalWidth = (int) ((float) source.getHeight() * ratio);
-            }
-        } else if (outWidth < outHeight) {
-            
-            // output width < output height, find the aspect ratio
-            ratio = (float) outWidth / (float) outHeight;
-            finalHeight = source.getHeight();
-            
-            // Fix the width of the original image and find the final width
-            finalWidth = (int) ((float) source.getHeight() * ratio);
-            if (finalWidth > source.getWidth()) {
-                
-                // Find the final width > the original image width, find the aspect ratio
-                ratio = (float) outHeight / (float) outWidth;
-                finalWidth = source.getWidth();
-                finalHeight = (int) ((float) source.getWidth() * ratio);
-            }
-        } else {
-            
-            // output width = output height
-            finalHeight = source.getHeight();
-            finalWidth = finalHeight;
-        }
-
-        
-        // Correct rounded corners
-        this.radius *= (float) finalHeight / (float) outHeight;
-        Bitmap outBitmap = this.mBitmapPool.get(finalWidth, finalHeight, Bitmap.Config.ARGB_8888);
-        if (outBitmap == null) {
-            outBitmap = Bitmap.createBitmap(finalWidth, finalHeight, Bitmap.Config.ARGB_8888);
-        }
-
-        Paint paint = new Paint();
-        BitmapShader shader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-        
-        // Calculate the center position and offset it
-        int width = (source.getWidth() - finalWidth) / 2;
-        int height = (source.getHeight() - finalHeight) / 2;
-        if (width != 0 || height != 0) {
-            Matrix matrix = new Matrix();
-            matrix.setTranslate((float) (-width), (float) (-height));
-            shader.setLocalMatrix(matrix);
-        }
-
-        paint.setShader(shader);
-        paint.setAntiAlias(true);
-        Canvas canvas = new Canvas(outBitmap);
-        RectF rectF = new RectF(0.0F, 0.0F, (float) canvas.getWidth(), (float) canvas.getHeight());
-        canvas.drawRoundRect(rectF, this.radius, this.radius, paint);
-
-        if (exceptLeftTop) {
-            canvas.drawRect(0, 0, radius, radius, paint);
-        }
-        if (exceptRightTop) {
-            canvas.drawRect(canvas.getWidth() - radius, 0, radius, radius, paint);
-        }
-
-        if (exceptLeftBottom) {
-            canvas.drawRect(0, canvas.getHeight() - radius, radius, canvas.getHeight(), paint);
-        }
-
-        if (exceptRightBottom) {
-            canvas.drawRect(canvas.getWidth() - radius, canvas.getHeight() - radius, canvas.getWidth(), canvas.getHeight(), paint);
-        }
-
-        return BitmapResource.obtain(outBitmap, this.mBitmapPool);
-    }
-
-    @Override
-    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
-
-    }
-}

+ 2 - 14
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/GlideEngine.java

@@ -17,7 +17,7 @@ import java.io.File;
 import java.util.concurrent.ExecutionException;
 
 public class GlideEngine {
-    public static void loadCornerImageWithoutPlaceHolder(ImageView imageView, String filePath, RequestListener listener, float radius) {
+    public static void loadCornerImageWithoutPlaceHolder(ImageView imageView, Object uri, RequestListener listener, float radius) {
         RoundedCorners transform = null;
         if ((int) radius > 0) {
             transform = new RoundedCorners((int) radius);
@@ -27,7 +27,7 @@ public class GlideEngine {
         if (transform != null) {
             options = options.transform(transform);
         }
-        Glide.with(TUILogin.getAppContext()).load(filePath).apply(options).listener(listener).into(imageView);
+        Glide.with(TUILogin.getAppContext()).load(uri).apply(options).listener(listener).into(imageView);
     }
 
     public static void clear(ImageView imageView) {
@@ -114,18 +114,6 @@ public class GlideEngine {
             .get();
     }
 
-    public static Bitmap loadBitmap(Object imageUrl, int width, int height) throws InterruptedException, ExecutionException {
-        if (imageUrl == null) {
-            return null;
-        }
-        return Glide.with(TUILogin.getAppContext())
-            .asBitmap()
-            .load(imageUrl)
-            .apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
-            .into(width, height)
-            .get();
-    }
-
     public static void loadImageSetDefault(ImageView imageView, Object uri, int defaultResId) {
         Glide.with(TUILogin.getAppContext()).load(uri).placeholder(defaultResId).apply(new RequestOptions().centerCrop().error(defaultResId)).into(imageView);
     }

+ 336 - 0
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/config/classicui/TUIConfigClassic.java

@@ -0,0 +1,336 @@
+package com.tencent.qcloud.tuikit.timcommon.config.classicui;
+
+import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.tencent.qcloud.tuicore.TUIConfig;
+import com.tencent.qcloud.tuicore.TUIThemeManager;
+import com.tencent.qcloud.tuicore.util.ToastUtil;
+import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
+
+public class TUIConfigClassic {
+    private TUIConfigClassic() {}
+
+    private static final class TUIConfigClassicHolder {
+        private static final TUIConfigClassic INSTANCE = new TUIConfigClassic();
+    }
+
+    private static TUIConfigClassic getInstance() {
+        return TUIConfigClassicHolder.INSTANCE;
+    }
+
+    public static final int UNDEFINED = -1;
+    // message bubble
+    private boolean enableMessageBubbleStyle = true;
+    private Drawable sendBubbleBackground;
+    private Drawable receiveBubbleBackground;
+    private Drawable sendErrorBubbleBackground;
+    private Drawable receiveErrorBubbleBackground;
+
+    // message style
+    private Drawable chatTimeBubble;
+    private int chatTimeFontSize = UNDEFINED;
+    private int chatTimeFontColor = UNDEFINED;
+    private Drawable defaultAvatarImage;
+    private int avatarRadius = UNDEFINED;
+    private int avatarSize = UNDEFINED;
+    private int receiveNickNameVisibility = UNDEFINED;
+    private int receiveNickNameFontSize = UNDEFINED;
+    private int receiveNickNameColor = UNDEFINED;
+
+    /**
+     *  Set the default app directory.The default dir is "file".
+     * @param appDir
+     */
+    public static void setDefaultAppDir(String appDir) {
+        TUIConfig.setDefaultAppDir(appDir);
+        TUIConfig.initPath();
+    }
+
+    /**
+     *  Set whether show the toast prompt built into TUIKit.The default value is true.
+     *  @param enableToast
+     */
+    public static void enableToast(boolean enableToast) {
+        ToastUtil.setEnableToast(enableToast);
+    }
+
+    /**
+     * Set whether to enable language switching.The default value is false.
+     * @param enableLanguageSwitch
+     */
+    public static void enableLanguageSwitch(boolean enableLanguageSwitch) {
+        TUIThemeManager.setEnableLanguageSwitch(enableLanguageSwitch);
+    }
+
+    /**
+     * Switch the language of TUIKit.
+     * The currently supported languages are "en", "zh", and "ar".
+     * @param context
+     * @param targetLanguage
+     */
+    public static void switchLanguageToTarget(Context context, String targetLanguage) {
+        TUIThemeManager.getInstance().changeLanguage(context, targetLanguage);
+    }
+
+    /**
+     * Switch theme to target.
+     * The currently supported themes are THEME_LIGHT, THEME_LIVELY, and THEME_SERIOUS.
+     * @param context
+     * @param themeID
+     */
+    public static void switchThemeToTarget(Context context, int themeID) {
+        TUIThemeManager.getInstance().changeTheme(context, themeID);
+    }
+
+    /**
+     * Set whether to enable message bubble style.The default value is true.
+     * @param enable
+     */
+    public static void setEnableMessageBubbleStyle(boolean enable) {
+        getInstance().enableMessageBubbleStyle = enable;
+    }
+
+    /**
+     * Get whether to enable message bubble style.
+     * @return true is enable, false is not
+     */
+    public static boolean isEnableMessageBubbleStyle() {
+        return getInstance().enableMessageBubbleStyle;
+    }
+
+    /**
+     * Set the background of the send message bubble.
+     * @param drawable
+     */
+    public static void setSendBubbleBackground(Drawable drawable) {
+        getInstance().sendBubbleBackground = drawable;
+    }
+
+    /**
+     * Get the background of the send message bubble.
+     * @return the background
+     */
+    public static Drawable getSendBubbleBackground() {
+        return newDrawable(getInstance().sendBubbleBackground);
+    }
+
+    /**
+     * Set the background of the receive message bubble.
+     * @param drawable
+     */
+    public static void setReceiveBubbleBackground(Drawable drawable) {
+        getInstance().receiveBubbleBackground = drawable;
+    }
+
+    /**
+     * Get the background of the receive message bubble.
+     * @return the background
+     */
+    public static Drawable getReceiveBubbleBackground() {
+        return newDrawable(getInstance().receiveBubbleBackground);
+    }
+
+    /**
+     * Set the background of the receive error message bubble.
+     * @param receiveErrorBubbleBackground
+     */
+    public static void setReceiveErrorBubbleBackground(Drawable receiveErrorBubbleBackground) {
+        getInstance().receiveErrorBubbleBackground = receiveErrorBubbleBackground;
+    }
+
+    /**
+     * Get the background of the receive error message bubble.
+     * @return the background
+     */
+    public static Drawable getReceiveErrorBubbleBackground() {
+        return newDrawable(getInstance().receiveErrorBubbleBackground);
+    }
+
+    /**
+     * Set the background of the send error message bubble.
+     * @param sendErrorBubbleBackground
+     */
+    public static void setSendErrorBubbleBackground(Drawable sendErrorBubbleBackground) {
+        getInstance().sendErrorBubbleBackground = sendErrorBubbleBackground;
+    }
+
+    /**
+     * Get the background of the send error message bubble.
+     * @return the background
+     */
+    public static Drawable getSendErrorBubbleBackground() {
+        return newDrawable(getInstance().sendErrorBubbleBackground);
+    }
+
+    /**
+     * Set the light color of the message bubble in highlight status..
+     * @param color
+     */
+    public static void setBubbleHighlightLightColor(int color) {
+        HighlightPresenter.setHighlightLightColor(color);
+    }
+
+    /**
+     * Set the dark color of the message bubble in highlight status..
+     * @param color
+     */
+    public static void setBubbleHighlightDarkColor(int color) {
+        HighlightPresenter.setHighlightDarkColor(color);
+    }
+
+    /**
+     * Set the chat time bubble.
+     * @param drawable
+     */
+    public static void setChatTimeBubble(Drawable drawable) {
+        getInstance().chatTimeBubble = drawable;
+    }
+
+    /**
+     * Get the chat time bubble.
+     * @return
+     */
+    public static Drawable getChatTimeBubble() {
+        return newDrawable(getInstance().chatTimeBubble);
+    }
+
+    /**
+     * Set the font size of the chat time text.
+     * @param size
+     */
+    public static void setChatTimeFontSize(int size) {
+        getInstance().chatTimeFontSize = size;
+    }
+
+    /**
+     * Get the font size of the chat time text.
+     * @return
+     */
+    public static int getChatTimeFontSize() {
+        return getInstance().chatTimeFontSize;
+    }
+
+    /**
+     * Set the font color of the chat time text.
+     * @param color
+     */
+    public static void setChatTimeFontColor(int color) {
+        getInstance().chatTimeFontColor = color;
+    }
+
+    /**
+     * Get the font color of the chat time text.
+     * @return
+     */
+    public static int getChatTimeFontColor() {
+        return getInstance().chatTimeFontColor;
+    }
+
+    /**
+     * Set the default avatar image.
+     * @param drawable
+     */
+    public static void setDefaultAvatarImage(Drawable drawable) {
+        getInstance().defaultAvatarImage = drawable;
+    }
+
+    /**
+     * Get the default avatar image.
+     * @return the default avatar image
+     */
+    public static Drawable getDefaultAvatarImage() {
+        return newDrawable(getInstance().defaultAvatarImage);
+    }
+
+    /**
+     * Set the radius of the avatar in the message list.
+     * @param radius
+     */
+    public static void setMessageListAvatarRadius(int radius) {
+        getInstance().avatarRadius = radius;
+    }
+
+    /**
+     * Get the radius of the avatar in the message list.
+     * @return
+     */
+    public static int getMessageListAvatarRadius() {
+        return getInstance().avatarRadius;
+    }
+
+    /**
+     * Set whether to enable the grid avatar with the group chat.The default value is true.
+     * @param enableGroupGridAvatar
+     */
+    public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
+        TUIConfig.setEnableGroupGridAvatar(enableGroupGridAvatar);
+    }
+
+    /**
+     * Set the avatar size in the message list.
+     * @param size
+     */
+    public static void setMessageListAvatarSize(int size) {
+        getInstance().avatarSize = size;
+    }
+
+    /**
+     * Get the avatar size in the message list.
+     * @return
+     */
+    public static int getMessageListAvatarSize() {
+        return getInstance().avatarSize;
+    }
+
+    /**
+     * Set whether to hide the nickname of the received message.
+     * @param hideReceiveNickName
+     */
+    public static void setHideReceiveNickName(boolean hideReceiveNickName) {
+        getInstance().receiveNickNameVisibility = hideReceiveNickName ? View.GONE : View.VISIBLE;
+    }
+
+    /**
+     * Get the visibility of the nickname of the received message.
+     * @return
+     */
+    public static int getReceiveNickNameVisibility() {
+        return getInstance().receiveNickNameVisibility;
+    }
+
+    /**
+     * Set the font size of the nickname of the received message.
+     * @param size
+     */
+    public static void setReceiveNickNameFontSize(int size) {
+        getInstance().receiveNickNameFontSize = size;
+    }
+
+    /**
+     * Get the font size of the nickname of the received message.
+     * @return
+     */
+    public static int getReceiveNickNameFontSize() {
+        return getInstance().receiveNickNameFontSize;
+    }
+
+    /**
+     * Set the font color of the nickname of the received message.
+     * @param color
+     */
+    public static void setReceiveNickNameColor(int color) {
+        getInstance().receiveNickNameColor = color;
+    }
+
+    /**
+     * Get the font color of the nickname of the received message.
+     * @return
+     */
+    public static int getReceiveNickNameColor() {
+        return getInstance().receiveNickNameColor;
+    }
+}

+ 273 - 0
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/config/minimalistui/TUIConfigMinimalist.java

@@ -0,0 +1,273 @@
+package com.tencent.qcloud.tuikit.timcommon.config.minimalistui;
+
+import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.tencent.qcloud.tuicore.TUIConfig;
+import com.tencent.qcloud.tuicore.TUIThemeManager;
+import com.tencent.qcloud.tuicore.util.ToastUtil;
+import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
+
+public class TUIConfigMinimalist {
+    private TUIConfigMinimalist() {}
+
+    private static final class TUIConfigMinimalistHolder {
+        private static final TUIConfigMinimalist INSTANCE = new TUIConfigMinimalist();
+    }
+
+    private static TUIConfigMinimalist getInstance() {
+        return TUIConfigMinimalistHolder.INSTANCE;
+    }
+
+    public static final int UNDEFINED = -1;
+    // message bubble
+    private boolean enableMessageBubbleStyle = true;
+    private Drawable sendBubbleBackground;
+    private Drawable sendLastBubbleBackground;
+    private Drawable receiveBubbleBackground;
+    private Drawable receiveLastBubbleBackground;
+
+    // message style
+    private Drawable chatTimeBubble;
+    private int chatTimeFontSize = UNDEFINED;
+    private int chatTimeFontColor = UNDEFINED;
+    private Drawable defaultAvatarImage;
+    private int avatarRadius = UNDEFINED;
+    private int avatarSize = UNDEFINED;
+
+    /**
+     *  Set the default app directory.The default dir is "file".
+     * @param appDir
+     */
+    public static void setDefaultAppDir(String appDir) {
+        TUIConfig.setDefaultAppDir(appDir);
+        TUIConfig.initPath();
+    }
+
+    /**
+     *  Set whether show the toast prompt built into TUIKit.The default value is true.
+     *  @param enableToast
+     */
+    public static void enableToast(boolean enableToast) {
+        ToastUtil.setEnableToast(enableToast);
+    }
+
+    /**
+     * Set whether to enable language switching.The default value is false.
+     * @param enableLanguageSwitch
+     */
+    public static void enableLanguageSwitch(boolean enableLanguageSwitch) {
+        TUIThemeManager.setEnableLanguageSwitch(enableLanguageSwitch);
+    }
+
+    /**
+     * Switch the language of TUIKit.
+     * The currently supported languages are "en", "zh", and "ar".
+     * @param context
+     * @param targetLanguage
+     */
+    public static void switchLanguageToTarget(Context context, String targetLanguage) {
+        TUIThemeManager.getInstance().changeLanguage(context, targetLanguage);
+    }
+
+    /**
+     * Set whether to enable message bubble style.The default value is true.
+     * @param enable
+     */
+    public static void setEnableMessageBubbleStyle(boolean enable) {
+        getInstance().enableMessageBubbleStyle = enable;
+    }
+
+    /**
+     * Get whether to enable message bubble style.
+     * @return true is enable, false is not
+     */
+    public static boolean isEnableMessageBubbleStyle() {
+        return getInstance().enableMessageBubbleStyle;
+    }
+
+    /**
+     * Set the background of the send message's bubble in consecutive messages.
+     * @param drawable
+     */
+    public static void setSendBubbleBackground(Drawable drawable) {
+        getInstance().sendBubbleBackground = drawable;
+    }
+
+    /**
+     * Get the background of the send message's bubble in consecutive messages.
+     * @return the background
+     */
+    public static Drawable getSendBubbleBackground() {
+        return newDrawable(getInstance().sendBubbleBackground);
+    }
+
+    /**
+     * Set the background of the receive message's bubble in consecutive messages.
+     * @param drawable
+     */
+    public static void setReceiveBubbleBackground(Drawable drawable) {
+        getInstance().receiveBubbleBackground = drawable;
+    }
+
+    /**
+     * Get the background of the receive message's bubble in consecutive messages.
+     * @return the background
+     */
+    public static Drawable getReceiveBubbleBackground() {
+        return newDrawable(getInstance().receiveBubbleBackground);
+    }
+
+    /**
+     * Set the background image of the last sent message's bubble in consecutive messages.
+     * @param drawable
+     */
+    public static void setSendLastBubbleBackground(Drawable drawable) {
+        getInstance().sendLastBubbleBackground = drawable;
+    }
+
+    /**
+     * Get the background image of the last sent message's bubble in consecutive messages.
+     * @return  drawable
+     */
+    public static Drawable getSendLastBubbleBackground() {
+        return newDrawable(getInstance().sendLastBubbleBackground);
+    }
+
+    /**
+     * Set the background image of the last received message's bubble in consecutive messages.
+     * @param  drawable
+     */
+    public static void setReceiveLastBubbleBackground(Drawable drawable) {
+        getInstance().receiveLastBubbleBackground = drawable;
+    }
+
+    /**
+     * Get the background image of the last received message's bubble in consecutive messages.
+     * @return  drawable
+     */
+    public static Drawable getReceiveLastBubbleBackground() {
+        return newDrawable(getInstance().receiveLastBubbleBackground);
+    }
+
+    /**
+     * Set the light color of the message bubble in highlight status..
+     * @param color
+     */
+    public static void setBubbleHighlightLightColor(int color) {
+        HighlightPresenter.setHighlightLightColor(color);
+    }
+
+    /**
+     * Set the dark color of the message bubble in highlight status..
+     * @param color
+     */
+    public static void setBubbleHighlightDarkColor(int color) {
+        HighlightPresenter.setHighlightDarkColor(color);
+    }
+
+    /**
+     * Set the chat time bubble.
+     * @param drawable
+     */
+    public static void setChatTimeBubble(Drawable drawable) {
+        getInstance().chatTimeBubble = drawable;
+    }
+
+    /**
+     * Get the chat time bubble.
+     * @return
+     */
+    public static Drawable getChatTimeBubble() {
+        return newDrawable(getInstance().chatTimeBubble);
+    }
+
+    /**
+     * Set the font size of the chat time text.
+     * @param size
+     */
+    public static void setChatTimeFontSize(int size) {
+        getInstance().chatTimeFontSize = size;
+    }
+
+    /**
+     * Get the font size of the chat time text.
+     * @return
+     */
+    public static int getChatTimeFontSize() {
+        return getInstance().chatTimeFontSize;
+    }
+
+    /**
+     * Set the font color of the chat time text.
+     * @param color
+     */
+    public static void setChatTimeFontColor(int color) {
+        getInstance().chatTimeFontColor = color;
+    }
+
+    /**
+     * Get the font color of the chat time text.
+     * @return
+     */
+    public static int getChatTimeFontColor() {
+        return getInstance().chatTimeFontColor;
+    }
+
+    /**
+     * Set the default avatar image.
+     * @param drawable
+     */
+    public static void setDefaultAvatarImage(Drawable drawable) {
+        getInstance().defaultAvatarImage = drawable;
+    }
+
+    /**
+     * Get the default avatar image.
+     * @return the default avatar image
+     */
+    public static Drawable getDefaultAvatarImage() {
+        return newDrawable(getInstance().defaultAvatarImage);
+    }
+
+    /**
+     * Set the radius of the avatar in the message list.
+     * @param radius
+     */
+    public static void setMessageListAvatarRadius(int radius) {
+        getInstance().avatarRadius = radius;
+    }
+
+    /**
+     * Get the radius of the avatar in the message list.
+     * @return
+     */
+    public static int getMessageListAvatarRadius() {
+        return getInstance().avatarRadius;
+    }
+
+    /**
+     * Set whether to enable the grid avatar with the group chat.The default value is true.
+     * @param enableGroupGridAvatar
+     */
+    public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
+        TUIConfig.setEnableGroupGridAvatar(enableGroupGridAvatar);
+    }
+
+    /**
+     * Set the avatar size in the message list.
+     * @param size
+     */
+    public static void setMessageListAvatarSize(int size) {
+        getInstance().avatarSize = size;
+    }
+
+    /**
+     * Get the avatar size in the message list.
+     * @return
+     */
+    public static int getMessageListAvatarSize() {
+        return getInstance().avatarSize;
+    }
+}

+ 41 - 30
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageBaseHolder.java

@@ -1,8 +1,5 @@
 package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
 
-import android.animation.Animator;
-import android.animation.ArgbEvaluator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
@@ -16,8 +13,8 @@ import com.tencent.qcloud.tuicore.TUIConfig;
 import com.tencent.qcloud.tuicore.TUIThemeManager;
 import com.tencent.qcloud.tuikit.timcommon.R;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.timcommon.component.MessageProperties;
 import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
+import com.tencent.qcloud.tuikit.timcommon.config.minimalistui.TUIConfigMinimalist;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
@@ -27,7 +24,6 @@ import java.util.Locale;
 
 public abstract class MessageBaseHolder extends RecyclerView.ViewHolder {
     public ICommonMessageAdapter mAdapter;
-    public MessageProperties properties = MessageProperties.getInstance();
     protected OnItemClickListener onItemClickListener;
 
     public FrameLayout msgContentFrame;
@@ -37,13 +33,13 @@ public abstract class MessageBaseHolder extends RecyclerView.ViewHolder {
     public CheckBox mMutiSelectCheckBox;
     public View mContentLayout;
 
-    private ValueAnimator highLightAnimator;
     public TextView chatTimeText;
 
     protected boolean floatMode = false;
 
-    protected boolean isShowStart = true;
+    protected boolean isLayoutOnStart = true;
     private HighlightListener highlightListener;
+    protected TUIMessageBean currentMessageBean;
 
     public MessageBaseHolder(View itemView) {
         super(itemView);
@@ -84,16 +80,10 @@ public abstract class MessageBaseHolder extends RecyclerView.ViewHolder {
     }
 
     public void layoutViews(final TUIMessageBean msg, final int position) {
+        currentMessageBean = msg;
         registerHighlightListener(msg.getId());
-        if (properties.getChatTimeBubble() != null) {
-            chatTimeText.setBackground(properties.getChatTimeBubble());
-        }
-        if (properties.getChatTimeFontColor() != 0) {
-            chatTimeText.setTextColor(properties.getChatTimeFontColor());
-        }
-        if (properties.getChatTimeFontSize() != 0) {
-            chatTimeText.setTextSize(properties.getChatTimeFontSize());
-        }
+
+        setChatTimeStyle();
 
         if (position > 1) {
             TUIMessageBean last = mAdapter.getItem(position - 1);
@@ -111,25 +101,46 @@ public abstract class MessageBaseHolder extends RecyclerView.ViewHolder {
         }
     }
 
-    private void registerHighlightListener(String msgID) {
-        highlightListener = new HighlightListener() {
-            @Override
-            public void onHighlightStart() {}
+    private void setChatTimeStyle() {
+        Drawable chatTimeBubble = TUIConfigMinimalist.getChatTimeBubble();
+        if (chatTimeBubble != null) {
+            chatTimeText.setBackground(chatTimeBubble);
+        }
+        int chatTimeFontColor = TUIConfigMinimalist.getChatTimeFontColor();
+        if (chatTimeFontColor != TUIConfigMinimalist.UNDEFINED) {
+            chatTimeText.setTextColor(chatTimeFontColor);
+        }
+        int chatTimeFontSize = TUIConfigMinimalist.getChatTimeFontSize();
+        if (chatTimeFontSize != TUIConfigMinimalist.UNDEFINED) {
+            chatTimeText.setTextSize(chatTimeFontSize);
+        }
+    }
 
-            @Override
-            public void onHighlightEnd() {
-                clearHighLightBackground();
-            }
+    private void registerHighlightListener(String msgID) {
+        if (highlightListener == null) {
+            highlightListener = new HighlightListener() {
+                @Override
+                public void onHighlightStart() {}
+
+                @Override
+                public void onHighlightEnd() {
+                    clearHighLightBackground();
+                }
 
-            @Override
-            public void onHighlightUpdate(int color) {
-                setHighLightBackground(color);
-            }
-        };
+                @Override
+                public void onHighlightUpdate(int color) {
+                    setHighLightBackground(color);
+                }
+            };
+        }
         HighlightPresenter.registerHighlightListener(msgID, highlightListener);
     }
 
-    public void onRecycled() {}
+    public void onRecycled() {
+        if (currentMessageBean != null) {
+            HighlightPresenter.unregisterHighlightListener(currentMessageBean.getId());
+        }
+    }
 
     public static String getTimeFormatText(Date date) {
         if (date == null) {

+ 275 - 212
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageContentHolder.java

@@ -1,5 +1,8 @@
 package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
 
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.view.Gravity;
 import android.view.View;
@@ -8,7 +11,11 @@ import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.RecyclerView;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestBuilder;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
 import com.tencent.imsdk.v2.V2TIMManager;
 import com.tencent.imsdk.v2.V2TIMMessage;
 import com.tencent.imsdk.v2.V2TIMUserFullInfo;
@@ -20,9 +27,7 @@ import com.tencent.qcloud.tuikit.timcommon.R;
 import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.component.UnreadCountTextView;
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
-import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView;
-import com.tencent.qcloud.tuikit.timcommon.interfaces.UserFaceUrlCache;
+import com.tencent.qcloud.tuikit.timcommon.config.minimalistui.TUIConfigMinimalist;
 import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
 import java.util.ArrayList;
@@ -38,8 +43,8 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
     protected static final int READ_STATUS_HIDE = 4;
     protected static final int READ_STATUS_SENDING = 5;
 
-    public UserIconView leftUserIcon;
-    public UserIconView rightUserIcon;
+    public ImageView leftUserIcon;
+    public ImageView rightUserIcon;
     public TextView usernameText;
     public LinearLayout msgContentLinear;
     public ImageView messageStatusImage;
@@ -53,17 +58,18 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
     public boolean isMultiSelectMode = false;
     public boolean isOptimize = true;
     public boolean isShowSelfAvatar = false;
+    protected boolean isShowAvatar = true;
 
     protected TimeInLineTextLayout timeInLineTextLayout;
     protected MinimalistMessageLayout rootLayout;
     protected ReplyPreviewView replyPreviewView;
 
     private List<TUIMessageBean> mDataSource = new ArrayList<>();
-    
+
     // Whether to display the bottom content. The merged-forwarded message details activity does not display the bottom content.
     protected boolean isNeedShowBottom = true;
     protected boolean isShowRead = false;
-    private BaseFragment fragment;
+    private Fragment fragment;
     private RecyclerView recyclerView;
 
     public MessageContentHolder(View itemView) {
@@ -81,7 +87,7 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
         bottomContentFrameLayout = itemView.findViewById(R.id.bottom_content_fl);
     }
 
-    public void setFragment(BaseFragment fragment) {
+    public void setFragment(Fragment fragment) {
         this.fragment = fragment;
     }
 
@@ -110,80 +116,68 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
 
     @Override
     public void layoutViews(final TUIMessageBean msg, final int position) {
-        super.layoutViews(msg, position);
-
-        if (isForwardMode || isMessageDetailMode) {
-            isShowStart = true;
-        } else {
-            if (msg.isSelf()) {
-                isShowStart = false;
-            } else {
-                isShowStart = true;
+        Context context = itemView.getContext();
+        if (context instanceof Activity) {
+            if (((Activity) context).isDestroyed()) {
+                return;
             }
         }
 
-        setMessageGravity(msg);
+        super.layoutViews(msg, position);
+        setLayoutAlignment(msg);
+        setIsShowAvatar(msg, position);
+        setMessageGravity();
+        setUserNameText(msg);
+        setMessageBubbleBackground();
+        setMessageStatusImage(msg);
+        setMessageTimeVisibility();
+        setAvatarVisibility();
+        setEventListener(msg);
 
-        if (isForwardMode || isMessageDetailMode) {
-            usernameText.setVisibility(View.GONE);
-        } else {
-            if (msg.isSelf()) {
-                if (properties.getRightNameVisibility() == 0) {
-                    usernameText.setVisibility(View.GONE);
-                } else {
-                    usernameText.setVisibility(properties.getRightNameVisibility());
-                }
+        msgContentLinear.setVisibility(View.VISIBLE);
+
+        if (!isForwardMode && !isMessageDetailMode) {
+            setTimeInLineStatus(msg);
+            setShowReadStatusClickListener(msg);
+        }
+        if (timeInLineTextLayout != null && timeInLineTextLayout.getTextView() != null) {
+            if (isMultiSelectMode) {
+                timeInLineTextLayout.getTextView().setActivated(false);
             } else {
-                if (properties.getLeftNameVisibility() == 0) {
-                    if (msg.isGroup()) {
-                        usernameText.setVisibility(View.GONE);
-                    } else {
-                        usernameText.setVisibility(View.GONE);
-                    }
-                } else {
-                    usernameText.setVisibility(properties.getLeftNameVisibility());
-                }
+                timeInLineTextLayout.getTextView().setActivated(true);
             }
         }
-        if (properties.getNameFontColor() != 0) {
-            usernameText.setTextColor(properties.getNameFontColor());
-        }
-        if (properties.getNameFontSize() != 0) {
-            usernameText.setTextSize(properties.getNameFontSize());
-        }
 
-        if (!TextUtils.isEmpty(msg.getNameCard())) {
-            usernameText.setText(msg.getNameCard());
-        } else if (!TextUtils.isEmpty(msg.getFriendRemark())) {
-            usernameText.setText(msg.getFriendRemark());
-        } else if (!TextUtils.isEmpty(msg.getNickName())) {
-            usernameText.setText(msg.getNickName());
-        } else {
-            usernameText.setText(msg.getSender());
+        if (timeInLineTextLayout != null) {
+            timeInLineTextLayout.setTimeText(DateTimeUtil.getHMTimeString(new Date(msg.getMessageTime() * 1000)));
         }
 
-        if (isForwardMode) {
-            chatTimeText.setVisibility(View.GONE);
+        extraInfoArea.setVisibility(View.GONE);
+        setReplyContent(msg);
+        setReactContent(msg);
+        if (isNeedShowBottom) {
+            setBottomContent(msg);
+        }
+        setMessageBubbleDefaultPadding();
+        if (floatMode) {
+            itemView.setPaddingRelative(0, 0, 0, 0);
+            leftUserIcon.setVisibility(View.GONE);
+            rightUserIcon.setVisibility(View.GONE);
+            usernameText.setVisibility(View.GONE);
+            replyPreviewView.setVisibility(View.GONE);
+            reactionArea.setVisibility(View.GONE);
+        }
+        if (isMessageDetailMode) {
+            replyPreviewView.setVisibility(View.GONE);
         }
 
-        if (isForwardMode || isMessageDetailMode) {
-            setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border_left);
-            messageStatusImage.setVisibility(View.GONE);
-        } else {
-            if (msg.isSelf()) {
-                if (properties.getRightBubble() != null && properties.getRightBubble().getConstantState() != null) {
-                    setMessageBubbleBackground(properties.getRightBubble().getConstantState().newDrawable());
-                } else {
-                    setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border_right);
-                }
-            } else {
-                if (properties.getLeftBubble() != null && properties.getLeftBubble().getConstantState() != null) {
-                    setMessageBubbleBackground(properties.getLeftBubble().getConstantState().newDrawable());
-                } else {
-                    setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border_left);
-                }
-            }
+        optimizePadding(position, msg);
+        loadAvatar(msg);
+        layoutVariableViews(msg, position);
+    }
 
+    private void setEventListener(TUIMessageBean msg) {
+        if (!isForwardMode && !isMessageDetailMode) {
             if (onItemClickListener != null) {
                 msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() {
                     @Override
@@ -222,18 +216,7 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
                 });
             }
 
-            if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
-                messageStatusImage.setVisibility(View.VISIBLE);
-
-                messageStatusImage.setOnClickListener(new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        if (onItemClickListener != null) {
-                            onItemClickListener.onSendFailBtnClick(messageStatusImage, msg);
-                        }
-                    }
-                });
-            } else {
+            if (msg.getStatus() != TUIMessageBean.MSG_STATUS_SEND_FAIL) {
                 msgContentFrame.setOnClickListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
@@ -242,7 +225,24 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
                         }
                     }
                 });
-                messageStatusImage.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    private void setMessageTimeVisibility() {
+        if (isForwardMode || floatMode) {
+            chatTimeText.setVisibility(View.GONE);
+        }
+    }
+
+    private void setLayoutAlignment(TUIMessageBean msg) {
+        if (isForwardMode || isMessageDetailMode) {
+            isLayoutOnStart = true;
+        } else {
+            if (msg.isSelf()) {
+                isLayoutOnStart = false;
+            } else {
+                isLayoutOnStart = true;
             }
         }
 
@@ -259,44 +259,107 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
                 msgContentLinear.addView(msgAreaAndReply, 0);
             }
         }
-        setGravity(isShowStart);
-        rootLayout.setIsStart(isShowStart);
-
-        msgContentLinear.setVisibility(View.VISIBLE);
+        setGravity(isLayoutOnStart);
+        rootLayout.setIsStart(isLayoutOnStart);
+    }
 
-        if (!isForwardMode && !isMessageDetailMode) {
-            setTimeInLineStatus(msg);
-            setShowReadStatusClickListener(msg);
+    private void setUserNameText(TUIMessageBean msg) {
+        if (isForwardMode || isMessageDetailMode) {
+            usernameText.setVisibility(View.GONE);
+        } else {
+            if (msg.isSelf()) {
+                usernameText.setVisibility(View.GONE);
+            } else {
+                if (msg.isGroup()) {
+                    usernameText.setVisibility(View.GONE);
+                } else {
+                    usernameText.setVisibility(View.GONE);
+                }
+            }
         }
 
-        if (timeInLineTextLayout != null) {
-            timeInLineTextLayout.setTimeText(DateTimeUtil.getHMTimeString(new Date(msg.getMessageTime() * 1000)));
-        }
+        usernameText.setText(msg.getUserDisplayName());
+    }
 
-        extraInfoArea.setVisibility(View.GONE);
-        setReplyContent(msg);
-        setReactContent(msg);
-        if (isNeedShowBottom) {
-            setBottomContent(msg);
+    public void setIsShowAvatar(TUIMessageBean msg, int position) {
+        isShowAvatar = true;
+        if (mAdapter == null) {
+            return;
         }
-        setMessageBubbleDefaultPadding();
-        if (floatMode) {
-            itemView.setPaddingRelative(0, 0, 0, 0);
-            leftUserIcon.setVisibility(View.GONE);
-            rightUserIcon.setVisibility(View.GONE);
-            usernameText.setVisibility(View.GONE);
-            messageStatusImage.setVisibility(View.GONE);
-            replyPreviewView.setVisibility(View.GONE);
-            reactionArea.setVisibility(View.GONE);
-            chatTimeText.setVisibility(View.GONE);
+        if (isMessageDetailMode || !isOptimize) {
+            return;
         }
-        if (isMessageDetailMode) {
-            replyPreviewView.setVisibility(View.GONE);
+        TUIMessageBean nextMessage = mAdapter.getItem(position + 1);
+        if (nextMessage != null) {
+            if (TextUtils.equals(msg.getSender(), nextMessage.getSender())) {
+                boolean longPeriod = nextMessage.getMessageTime() - msg.getMessageTime() >= 5 * 60;
+                if (!isShowAvatar(nextMessage) && nextMessage.getStatus() != TUIMessageBean.MSG_STATUS_REVOKE && !longPeriod) {
+                    isShowAvatar = false;
+                }
+            }
         }
+    }
 
-        optimizeAvatarAndPadding(position, msg);
-        loadAvatar(msg);
-        layoutVariableViews(msg, position);
+    public void setMessageBubbleBackground() {
+        if (!TUIConfigMinimalist.isEnableMessageBubbleStyle()) {
+            setMessageBubbleBackground(null);
+            return;
+        }
+        Drawable sendBubble = TUIConfigMinimalist.getSendBubbleBackground();
+        Drawable receiveBubble = TUIConfigMinimalist.getReceiveBubbleBackground();
+        Drawable sendLastBubble = TUIConfigMinimalist.getSendLastBubbleBackground();
+        Drawable receiveLastBubble = TUIConfigMinimalist.getReceiveLastBubbleBackground();
+        if (isShowAvatar) {
+            if (isLayoutOnStart) {
+                if (receiveLastBubble != null) {
+                    setMessageBubbleBackground(receiveLastBubble);
+                } else {
+                    setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border_left);
+                }
+            } else {
+                if (sendLastBubble != null) {
+                    setMessageBubbleBackground(sendLastBubble);
+                } else {
+                    setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border_right);
+                }
+            }
+        } else {
+            if (isLayoutOnStart) {
+                if (receiveBubble != null) {
+                    setMessageBubbleBackground(receiveBubble);
+                } else {
+                    setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border);
+                }
+            } else {
+                if (sendBubble != null) {
+                    setMessageBubbleBackground(sendBubble);
+                } else {
+                    setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border);
+                }
+            }
+        }
+    }
+
+    public void setMessageStatusImage(TUIMessageBean msg) {
+        if (isForwardMode || isMessageDetailMode || floatMode) {
+            messageStatusImage.setVisibility(View.GONE);
+        } else {
+            if (msg.hasRiskContent()) {
+                messageStatusImage.setVisibility(View.VISIBLE);
+            } else if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
+                messageStatusImage.setVisibility(View.VISIBLE);
+                messageStatusImage.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        if (onItemClickListener != null) {
+                            onItemClickListener.onSendFailBtnClick(messageStatusImage, msg);
+                        }
+                    }
+                });
+            } else {
+                messageStatusImage.setVisibility(View.GONE);
+            }
+        }
     }
 
     public void setBottomContent(TUIMessageBean msg) {
@@ -309,6 +372,12 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
     }
 
     private void loadAvatar(TUIMessageBean msg) {
+        Drawable drawable = TUIConfigMinimalist.getDefaultAvatarImage();
+        if (drawable != null) {
+            setupAvatar(drawable);
+            return;
+        }
+
         if (msg.isUseMsgReceiverAvatar() && mAdapter != null) {
             String cachedFaceUrl = mAdapter.getUserFaceUrlCache().getCachedFaceUrl(msg.getSender());
             if (cachedFaceUrl == null) {
@@ -331,68 +400,68 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
 
                     @Override
                     public void onError(int code, String desc) {
-                        setupAvatar("", msg.isSelf());
+                        setupAvatar("");
                     }
                 });
             } else {
-                setupAvatar(cachedFaceUrl, msg.isSelf());
+                setupAvatar(cachedFaceUrl);
             }
         } else {
-            setupAvatar(msg.getFaceUrl(), msg.isSelf());
+            setupAvatar(msg.getFaceUrl());
         }
     }
 
-    private void setupAvatar(String faceUrl, boolean right) {
-        if (!TextUtils.isEmpty(faceUrl)) {
-            List<Object> urllist = new ArrayList<>();
-            urllist.add(faceUrl);
-            if (isForwardMode || isMessageDetailMode) {
-                leftUserIcon.setIconUrls(urllist);
-            } else {
-                if (right) {
-                    rightUserIcon.setIconUrls(urllist);
-                } else {
-                    leftUserIcon.setIconUrls(urllist);
-                }
-            }
+    private void setupAvatar(Object faceUrl) {
+        int avatarSize = TUIConfigMinimalist.getMessageListAvatarSize();
+        if (avatarSize == TUIConfigMinimalist.UNDEFINED) {
+            avatarSize = ScreenUtil.dip2px(32);
+        }
+        ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
+        params.width = avatarSize;
+        if (leftUserIcon.getVisibility() == View.INVISIBLE) {
+            params.height = 1;
         } else {
-            rightUserIcon.setIconUrls(null);
-            leftUserIcon.setIconUrls(null);
+            params.height = avatarSize;
         }
-    }
-
-    protected boolean isNeedChangedBackground() {
-        return true;
-    }
+        leftUserIcon.setLayoutParams(params);
 
-    private void optimizeAvatarAndPadding(int position, TUIMessageBean messageBean) {
-        if (mAdapter == null) {
-            return;
+        params = rightUserIcon.getLayoutParams();
+        params.width = avatarSize;
+        if (rightUserIcon.getVisibility() == View.INVISIBLE) {
+            params.height = 1;
+        } else {
+            params.height = avatarSize;
         }
-        if (isMessageDetailMode || !isOptimize) {
-            return;
+        rightUserIcon.setLayoutParams(params);
+
+        int radius = ScreenUtil.dip2px(100);
+        if (TUIConfigMinimalist.getMessageListAvatarRadius() != TUIConfigMinimalist.UNDEFINED) {
+            radius = TUIConfigMinimalist.getMessageListAvatarRadius();
         }
-        TUIMessageBean nextMessage = mAdapter.getItem(position + 1);
-        boolean isShowAvatar = true;
-        boolean needChangedBackground = isNeedChangedBackground();
-        if (nextMessage != null) {
-            if (TextUtils.equals(messageBean.getSender(), nextMessage.getSender())) {
-                boolean longPeriod = nextMessage.getMessageTime() - messageBean.getMessageTime() >= 5 * 60;
-                if (!isShowAvatar(nextMessage) && nextMessage.getStatus() != TUIMessageBean.MSG_STATUS_REVOKE && !longPeriod) {
-                    isShowAvatar = false;
-                }
-            }
+        ImageView renderedView;
+        if (isLayoutOnStart) {
+            renderedView = leftUserIcon;
+        } else {
+            renderedView = rightUserIcon;
         }
 
-        int horizontalPadding = ScreenUtil.dip2px(16);
-        int verticalPadding = ScreenUtil.dip2px(25f);
+        RequestBuilder<Drawable> errorRequestBuilder = Glide.with(itemView.getContext())
+                .load(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
+                .placeholder(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
+                .transform(new RoundedCorners(radius));
+
+        Glide.with(itemView.getContext())
+                .load(faceUrl)
+                .transform(new RoundedCorners(radius))
+                .error(errorRequestBuilder)
+                .into(renderedView);
+    }
+
+    private void setAvatarVisibility() {
         if (isShowAvatar) {
-            if (isShowStart) {
+            if (isLayoutOnStart) {
                 leftUserIcon.setVisibility(View.VISIBLE);
                 rightUserIcon.setVisibility(View.GONE);
-                if (needChangedBackground) {
-                    setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border_left);
-                }
             } else {
                 leftUserIcon.setVisibility(View.GONE);
                 if (isShowSelfAvatar) {
@@ -400,20 +469,8 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
                 } else {
                     rightUserIcon.setVisibility(View.GONE);
                 }
-                if (needChangedBackground) {
-                    setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border_right);
-                }
             }
         } else {
-            if (needChangedBackground) {
-                if (isShowStart) {
-                    setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border);
-                } else {
-                    setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border);
-                }
-            }
-            horizontalPadding = ScreenUtil.dip2px(16);
-            verticalPadding = ScreenUtil.dip2px(2);
             leftUserIcon.setVisibility(View.INVISIBLE);
             if (isShowSelfAvatar) {
                 rightUserIcon.setVisibility(View.INVISIBLE);
@@ -421,6 +478,23 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
                 rightUserIcon.setVisibility(View.GONE);
             }
         }
+    }
+
+    private void optimizePadding(int position, TUIMessageBean messageBean) {
+        if (mAdapter == null) {
+            return;
+        }
+        if (isMessageDetailMode || !isOptimize) {
+            return;
+        }
+
+        TUIMessageBean nextMessage = mAdapter.getItem(position + 1);
+        int horizontalPadding = ScreenUtil.dip2px(16);
+        int verticalPadding = ScreenUtil.dip2px(25f);
+        if (!isShowAvatar) {
+            horizontalPadding = ScreenUtil.dip2px(16);
+            verticalPadding = ScreenUtil.dip2px(2);
+        }
         if (nextMessage != null) {
             rootLayout.setPaddingRelative(horizontalPadding, 0, horizontalPadding, verticalPadding);
         } else {
@@ -431,50 +505,13 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
 
     protected void optimizeMessageContent(boolean isShowAvatar) {}
 
-    private void setMessageGravity(TUIMessageBean messageBean) {
-        if (isForwardMode || isMessageDetailMode) {
-            leftUserIcon.setVisibility(View.VISIBLE);
-            rightUserIcon.setVisibility(View.GONE);
-        } else {
-            if (messageBean.isSelf()) {
-                msgContentLinear.setGravity(Gravity.END);
-                leftUserIcon.setVisibility(View.GONE);
-                rightUserIcon.setVisibility(View.VISIBLE);
-                extraInfoArea.setGravity(Gravity.END);
-            } else {
-                msgContentLinear.setGravity(Gravity.START);
-                leftUserIcon.setVisibility(View.VISIBLE);
-                rightUserIcon.setVisibility(View.GONE);
-                extraInfoArea.setGravity(Gravity.START);
-            }
-        }
-        if (properties.getAvatar() != 0) {
-            leftUserIcon.setDefaultImageResId(properties.getAvatar());
-            rightUserIcon.setDefaultImageResId(properties.getAvatar());
-        } else {
-            leftUserIcon.setDefaultImageResId(
-                TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon));
-            rightUserIcon.setDefaultImageResId(
-                TUIThemeManager.getAttrResId(rightUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon));
-        }
-        if (properties.getAvatarRadius() != 0) {
-            leftUserIcon.setRadius(properties.getAvatarRadius());
-            rightUserIcon.setRadius(properties.getAvatarRadius());
+    private void setMessageGravity() {
+        if (isLayoutOnStart) {
+            msgContentLinear.setGravity(Gravity.START | Gravity.BOTTOM);
+            extraInfoArea.setGravity(Gravity.START);
         } else {
-            int radius = ScreenUtil.dip2px(4);
-            leftUserIcon.setRadius(radius);
-            rightUserIcon.setRadius(radius);
-        }
-        if (properties.getAvatarSize() != null && properties.getAvatarSize().length == 2) {
-            ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
-            params.width = properties.getAvatarSize()[0];
-            params.height = properties.getAvatarSize()[1];
-            leftUserIcon.setLayoutParams(params);
-
-            params = rightUserIcon.getLayoutParams();
-            params.width = properties.getAvatarSize()[0];
-            params.height = properties.getAvatarSize()[1];
-            rightUserIcon.setLayoutParams(params);
+            msgContentLinear.setGravity(Gravity.END | Gravity.BOTTOM);
+            extraInfoArea.setGravity(Gravity.END);
         }
     }
 
@@ -503,8 +540,34 @@ public abstract class MessageContentHolder extends MessageBaseHolder {
             @Override
             public void onClick(View v) {
                 if (onItemClickListener != null) {
-                    onItemClickListener.onMessageClick(v, messageBean);
+                    onItemClickListener.onMessageClick(msgArea, messageBean);
+                }
+            }
+        });
+        timeInLineTextLayout.getTextView().setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (onItemClickListener != null) {
+                    onItemClickListener.onMessageClick(msgArea, messageBean);
+                }
+            }
+        });
+        timeInLineTextLayout.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (onItemClickListener != null) {
+                    onItemClickListener.onMessageLongClick(msgArea, messageBean);
+                }
+                return true;
+            }
+        });
+        timeInLineTextLayout.getTextView().setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (onItemClickListener != null) {
+                    onItemClickListener.onMessageLongClick(msgArea, messageBean);
                 }
+                return true;
             }
         });
     }

+ 4 - 2
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MinimalistMessageLayout.java

@@ -8,13 +8,15 @@ import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.RelativeLayout;
+
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
 import com.tencent.qcloud.tuikit.timcommon.R;
 import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
 
-public class MinimalistMessageLayout extends RelativeLayout {
+public class MinimalistMessageLayout extends ConstraintLayout {
     private View msgArea;
     private View quoteArea;
     private View bottomArea;

+ 10 - 3
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ReplyPreviewView.java

@@ -9,6 +9,7 @@ import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -19,9 +20,9 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestBuilder;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.resource.bitmap.CircleCrop;
-import com.bumptech.glide.request.RequestOptions;
 import com.tencent.qcloud.tuikit.timcommon.R;
 import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
@@ -82,7 +83,7 @@ public class ReplyPreviewView extends FrameLayout {
             replyText.setText(String.format(Locale.US, getResources().getString(R.string.chat_reply_num), messageRepliesBean.getRepliesSize()));
             List<MessageRepliesBean.ReplyBean> repliesBeanList = messageRepliesBean.getReplies();
             List<String> iconList = getReplyUserIconLt(repliesBeanList);
-            if (iconList == null || iconList.isEmpty()) {
+            if (iconList.isEmpty()) {
                 return;
             }
             String secondIconUrl = null;
@@ -131,10 +132,16 @@ public class ReplyPreviewView extends FrameLayout {
                 return;
             }
         }
+
+        RequestBuilder<Drawable> errorRequestBuilder = Glide.with(getContext())
+            .load(com.tencent.qcloud.tuikit.timcommon.R.drawable.core_default_user_icon_light)
+            .transform(new ReplyRingCircleCrop());
+
         Glide.with(getContext())
             .load(url)
             .centerCrop()
-            .apply(new RequestOptions().transform(new ReplyRingCircleCrop()).error(com.tencent.qcloud.tuikit.timcommon.R.drawable.core_default_user_icon_light))
+            .transform(new ReplyRingCircleCrop())
+            .error(errorRequestBuilder)
             .into(imageView);
     }
 

+ 1 - 1
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileUtil.java

@@ -262,7 +262,7 @@ public class FileUtil {
         String mimeType = context.getContentResolver().getType(uri);
         String filename = null;
 
-        if (mimeType == null && context != null) {
+        if (mimeType == null) {
             filename = getName(uri.toString());
         } else {
             Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);

+ 0 - 7
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/PopWindowUtil.java

@@ -1,22 +1,15 @@
 package com.tencent.qcloud.tuikit.timcommon.util;
 
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
 import android.view.Gravity;
 import android.view.View;
 import android.view.WindowManager;
-import android.widget.LinearLayout;
 import android.widget.PopupWindow;
-import com.tencent.qcloud.tuikit.timcommon.R;
 
 public class PopWindowUtil {
 
     public static PopupWindow popupWindow(View windowView, View parent, int x, int y) {
         PopupWindow popup = new PopupWindow(windowView, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
-        //        int[] position = calculatePopWindowPos(windowView, parent, x, y);
         popup.setOutsideTouchable(true);
         popup.setFocusable(true);
         popup.setBackgroundDrawable(new ColorDrawable(0xAEEEEE00));

+ 0 - 2
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ScreenUtil.java

@@ -2,9 +2,7 @@ package com.tencent.qcloud.tuikit.timcommon.util;
 
 import android.content.Context;
 import android.util.DisplayMetrics;
-import android.util.Log;
 import android.util.TypedValue;
-import android.view.Display;
 import android.view.WindowManager;
 import com.tencent.qcloud.tuicore.TUIConfig;
 

+ 12 - 0
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TUIUtil.java

@@ -2,6 +2,7 @@ package com.tencent.qcloud.tuikit.timcommon.util;
 
 import android.app.Application;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.text.TextUtils;
 import com.tencent.imsdk.v2.V2TIMManager;
@@ -52,4 +53,15 @@ public class TUIUtil {
         }
         return R.drawable.core_default_group_icon_community;
     }
+
+    public static Drawable newDrawable(Drawable drawable) {
+        if (drawable == null) {
+            return null;
+        }
+        Drawable.ConstantState state = drawable.getConstantState();
+        if (state != null) {
+            return state.newDrawable().mutate();
+        }
+        return drawable.mutate();
+    }
 }

+ 132 - 0
Android/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TextUtil.java

@@ -0,0 +1,132 @@
+package com.tencent.qcloud.tuikit.timcommon.util;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
+import com.tencent.qcloud.tuicore.TUIThemeManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class TextUtil {
+
+    public static final Pattern PHONE_NUMBER_PATTERN =
+            Pattern.compile("(\\+?(\\d{1,4}[-\\s]?)?)?(\\(?\\d+\\)?[-\\s]?)?[\\d\\s-]{5,14}");
+
+    public static void linkifyUrls(TextView textView) {
+        Linkify.addLinks(textView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
+        Linkify.addLinks(textView, PHONE_NUMBER_PATTERN, "tel:");
+        SpannableString spannableString = new SpannableString(textView.getText());
+
+        URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length(), URLSpan.class);
+        int urlColor = 0xFF6495ED;
+        if (TUIThemeManager.getInstance().getCurrentTheme() != TUIThemeManager.THEME_LIGHT) {
+            urlColor = 0xFF87CEFA;
+        }
+        if (urlSpans != null) {
+            for (URLSpan span : urlSpans) {
+                int start = spannableString.getSpanStart(span);
+                int end = spannableString.getSpanEnd(span);
+                spannableString.removeSpan(span);
+                spannableString.setSpan(new TextLinkSpan(span.getURL(), urlColor), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+        GestureDetector gestureDetector = new GestureDetector(textView.getContext(), new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                if (!textView.isActivated()) {
+                    return false;
+                }
+                ClickableSpan[] spans = findSpansByLocation(textView, Math.round(e.getX()), Math.round(e.getY()));
+                if (spans != null && spans.length > 0) {
+                    ClickableSpan span = spans[0];
+                    span.onClick(textView);
+                }
+                return false;
+            }
+        });
+        textView.setText(spannableString);
+        textView.setMovementMethod(new LinkMovementMethod() {
+            @Override
+            public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+                gestureDetector.onTouchEvent(event);
+                return false;
+            }
+        });
+    }
+
+    public static ClickableSpan[] findSpansByLocation(TextView textView, int x, int y) {
+        if (textView == null || !(textView.getText() instanceof Spannable)) {
+            return null;
+        }
+        Spannable spannable = (Spannable) textView.getText();
+        Layout layout = textView.getLayout();
+        int offset = getPreciseOffset(textView, x, y);
+        ClickableSpan[] spans = spannable.getSpans(offset, offset, ClickableSpan.class);
+        List<ClickableSpan> result = new ArrayList<>();
+        for (ClickableSpan span : spans) {
+            int spanStart = spannable.getSpanStart(span);
+            int spanEnd = spannable.getSpanEnd(span);
+            Path path = new Path();
+            layout.getSelectionPath(spanStart, spanEnd, path);
+            RectF rect = new RectF();
+            path.computeBounds(rect, true);
+            Region region = new Region();
+            Region pathClipRegion = new Region((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom);
+            region.setPath(path, pathClipRegion);
+            if (region.contains(x, y)) {
+                result.add(span);
+            }
+        }
+        return result.toArray(new ClickableSpan[] {});
+    }
+
+    private static int getPreciseOffset(TextView textView, int x, int y) {
+        Layout layout = textView.getLayout();
+        if (layout != null) {
+            int topVisibleLine = layout.getLineForVertical(y);
+            int offset = layout.getOffsetForHorizontal(topVisibleLine, x);
+
+            int offsetX = (int) layout.getPrimaryHorizontal(offset);
+
+            if (offsetX > x) {
+                return layout.getOffsetToLeftOf(offset);
+            } else {
+                return offset;
+            }
+        } else {
+            return -1;
+        }
+    }
+
+    public static class TextLinkSpan extends URLSpan {
+        private final int color;
+
+        public TextLinkSpan(String url, int color) {
+            super(url);
+            this.color = color;
+        }
+
+        @Override
+        public void updateDrawState(@NonNull TextPaint ds) {
+            ds.setColor(color);
+        }
+    }
+}

+ 0 - 0
Android/tuichat/src/main/res/drawable-xxxhdpi/indicator_point_nomal.png → Android/timcommon/src/main/res/drawable/indicator_point_nomal.png


+ 0 - 0
Android/tuichat/src/main/res/drawable-xxxhdpi/indicator_point_select.png → Android/timcommon/src/main/res/drawable/indicator_point_select.png


+ 6 - 9
Android/timcommon/src/main/res/layout/message_adapter_item_content.xml

@@ -44,7 +44,7 @@
         android:layout_below="@id/message_top_time_tv"
         android:layout_toEndOf="@id/select_checkbox">
 
-        <com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView
+        <ImageView
             android:id="@+id/left_user_icon_view"
             android:layout_width="41dp"
             android:layout_height="41dp"
@@ -52,11 +52,9 @@
             android:layout_alignParentTop="true"
             android:layout_marginEnd="11.52dp"
             android:visibility="gone"
-            app:default_image="?attr/core_default_user_icon"
-            app:image_radius="20dp"
             tools:visibility="visible" />
 
-        <com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView
+        <ImageView
             android:id="@+id/right_user_icon_view"
             android:layout_width="41dp"
             android:layout_height="41dp"
@@ -64,11 +62,10 @@
             android:layout_alignParentEnd="true"
             android:layout_marginStart="11.52dp"
             android:visibility="gone"
-            app:default_image="?attr/core_default_user_icon"
-            app:image_radius="20dp" />
+            tools:visibility="visible"/>
 
         <TextView
-            android:id="@+id/user_name_tv"
+            android:id="@+id/left_user_name_tv"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentTop="true"
@@ -84,7 +81,7 @@
             android:id="@+id/msg_detail_time_tv"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_alignTop="@id/user_name_tv"
+            android:layout_alignTop="@id/left_user_name_tv"
             android:layout_alignParentEnd="true"
             android:layout_marginEnd="3.36dp"
             android:ellipsize="end"
@@ -99,7 +96,7 @@
             android:id="@+id/msg_content_ll"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_below="@id/user_name_tv"
+            android:layout_below="@id/left_user_name_tv"
             android:layout_toStartOf="@id/right_user_icon_view"
             android:layout_toEndOf="@id/left_user_icon_view"
 

+ 40 - 35
Android/timcommon/src/main/res/layout/minimalist_message_adapter_item_content.xml

@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message.MinimalistMessageLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message.MinimalistMessageLayout 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:id="@+id/message_content_layout"
@@ -16,61 +15,68 @@
         android:id="@+id/message_top_time_tv"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true"
         android:layout_marginTop="10dp"
         android:layout_marginBottom="10dp"
         android:includeFontPadding="false"
         android:textColor="@color/text_tips_color"
         android:textSize="12.6sp"
+        app:layout_constraintBottom_toTopOf="@id/top_barrier"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
         tools:text="12:00" />
 
     <CheckBox
         android:id="@+id/select_checkbox"
         android:layout_width="21.12dp"
         android:layout_height="21.12dp"
-        android:layout_alignParentStart="true"
-        android:layout_alignTop="@id/msg_content_ll"
         android:layout_marginTop="5dp"
         android:layout_marginEnd="11.52dp"
         android:background="@drawable/chat_checkbox_selector"
         android:button="@null"
         android:visibility="gone"
+        app:layout_constraintEnd_toStartOf="@id/left_user_icon_view"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@id/msg_content_ll"
         tools:visibility="visible" />
 
-    <com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/top_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="left_user_icon_view,right_user_icon_view, msg_content_ll" />
+
+    <ImageView
         android:id="@+id/left_user_icon_view"
         android:layout_width="32dp"
         android:layout_height="32dp"
-        android:layout_alignBottom="@id/msg_content_ll"
         android:layout_marginEnd="8dp"
-        android:layout_toEndOf="@id/select_checkbox"
         android:visibility="gone"
-        app:default_image="?attr/core_default_user_icon"
-        app:image_radius="20dp"
+        app:layout_constraintBottom_toBottomOf="@id/msg_content_ll"
+        app:layout_constraintEnd_toStartOf="@id/msg_content_ll"
+        app:layout_constraintStart_toEndOf="@id/select_checkbox"
         tools:visibility="visible" />
 
-    <com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView
+    <ImageView
         android:id="@+id/right_user_icon_view"
         android:layout_width="32dp"
         android:layout_height="32dp"
-        android:layout_alignBottom="@id/msg_content_ll"
-        android:layout_alignParentEnd="true"
         android:layout_marginStart="8dp"
         android:visibility="gone"
-        app:default_image="?attr/core_default_user_icon"
-        app:image_radius="20dp"
+        app:layout_constraintBottom_toBottomOf="@id/msg_content_ll"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/msg_content_ll"
         tools:visibility="visible" />
 
     <LinearLayout
         android:id="@+id/msg_content_ll"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_below="@id/message_top_time_tv"
-        android:layout_toEndOf="@id/left_user_icon_view"
-        android:layout_toStartOf="@id/right_user_icon_view"
         android:gravity="start"
-        android:orientation="horizontal">
+        android:orientation="horizontal"
+        app:layout_constraintEnd_toStartOf="@id/right_user_icon_view"
+        app:layout_constraintStart_toEndOf="@id/left_user_icon_view"
+        app:layout_constraintTop_toBottomOf="@id/message_top_time_tv">
 
         <ImageView
             android:id="@+id/message_status_iv"
@@ -105,7 +111,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:visibility="gone"
-            tools:visibility="visible"/>
+            tools:visibility="visible" />
 
         <LinearLayout
             android:id="@+id/msg_area_and_reply"
@@ -151,37 +157,37 @@
                 android:layout_height="wrap_content"
                 android:layout_marginStart="16dp"
                 android:layout_marginTop="-3dp"
-                android:layout_marginEnd="16dp"/>
+                android:layout_marginEnd="16dp" />
 
         </LinearLayout>
     </LinearLayout>
 
     <LinearLayout
         android:id="@+id/extra_info_area"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:gravity="start"
         android:layout_marginTop="4dp"
-        android:layout_below="@id/msg_content_ll"
-        android:layout_toStartOf="@id/right_user_icon_view"
-        android:layout_toEndOf="@id/left_user_icon_view">
+        android:gravity="start"
+        android:orientation="vertical"
+        app:layout_constraintEnd_toStartOf="@id/right_user_icon_view"
+        app:layout_constraintStart_toEndOf="@id/left_user_icon_view"
+        app:layout_constraintTop_toBottomOf="@id/msg_content_ll">
         <!-- translation content area -->
         <com.tencent.qcloud.tuikit.timcommon.component.MaxWidthFrameLayout
             android:id="@+id/bottom_content_fl"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:background="@drawable/minimalist_translation_area_bg"
-            app:maxWidth="@dimen/chat_message_content_max_width"
             android:visibility="gone"
-            tools:visibility="visible"/>
+            app:maxWidth="@dimen/chat_message_content_max_width"
+            tools:visibility="visible" />
         <!-- message quote area -->
         <com.tencent.qcloud.tuikit.timcommon.component.MaxWidthFrameLayout
             android:id="@+id/quote_content_fl"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginTop="4dp"
             android:layout_marginStart="16dp"
+            android:layout_marginTop="4dp"
             android:layout_marginEnd="16dp"
             android:background="@drawable/chat_gray_round_rect_bg"
             android:visibility="gone"
@@ -193,9 +199,8 @@
             android:id="@+id/msg_reply_preview"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginTop="4dp"
             android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:layout_below="@id/quote_content_fl" />
+            android:layout_marginTop="4dp"
+            android:layout_marginEnd="16dp" />
     </LinearLayout>
 </com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message.MinimalistMessageLayout>

+ 31 - 0
Android/timcommon/src/main/res/values-zh-rHK/strings.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="open_file_tips">選擇要打開此文件的應用</string>
+
+    <string name="date_year_short">年</string>
+    <string name="date_month_short">月</string>
+    <string name="date_day_short">天</string>
+    <string name="date_hour_short">時</string>
+    <string name="date_minute_short">分</string>
+    <string name="date_second_short">秒</string>
+    <string name="date_yesterday">昨天</string>
+    <string name="chat_time_today">今天</string>
+
+    <string name="setting">設置中</string>
+    <string name="setting_fail">設置失敗!</string>
+    <string name="setting_success">設置成功!</string>
+    <string name="default_text">默認背景</string>
+    <string name="core_next_step">下一步</string>
+    <string name="revoke_tips_you">您撤回了一條消息</string>
+    <string name="revoke_tips">撤回了一條消息</string>
+    <string name="revoke_tips_other">對方撤回了一條消息</string>
+    <string name="common_risk_msg_revoked_tips">違規消息已被撤回</string>
+    <string name="chat_reply_num">%d條回覆</string>
+    <string name="has_read">已讀</string>
+    <string name="unread">未讀</string>
+    <string name="has_all_read">全部已讀</string>
+    <string name="someone_has_read">%d人已讀</string>
+    <string name="translation_support">由騰訊雲 IM 提供翻譯支持</string>
+    <string name="timcommon_no_support_msg">不支持的自定義消息</string>
+
+</resources>

+ 0 - 0
Android/timcommon/src/main/res/values-zh-rCN/strings.xml → Android/timcommon/src/main/res/values-zh/strings.xml


+ 6 - 0
Android/tuichat/build.gradle

@@ -49,6 +49,12 @@ android {
 }
 
 dependencies {
+    /*plugin-build-Begin
+
+    compileOnly fileTree(include: ['*.jar','*.aar'], dir: '../../../../tuikit/android/libs')
+
+    plugin-build-End*/
+
     implementation 'androidx.appcompat:appcompat:1.3.0'
     implementation 'com.google.android.material:material:1.3.0'
     implementation 'com.github.bumptech.glide:glide:4.12.0'

+ 1 - 1
Android/tuichat/src/main/AndroidManifest.xml

@@ -27,7 +27,7 @@
             android:name="com.tencent.qcloud.tuikit.tuichat.classicui.page.TUIC2CChatActivity"
             android:launchMode="singleTask"
             android:screenOrientation="portrait"
-            android:windowSoftInputMode="adjustPan" />
+            android:windowSoftInputMode="adjustNothing|stateHidden" />
 
 
         <activity

+ 10 - 19
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/TUIChatService.java

@@ -2,9 +2,6 @@ package com.tencent.qcloud.tuikit.tuichat;
 
 import android.content.Context;
 import android.text.TextUtils;
-import androidx.datastore.preferences.core.Preferences;
-import androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder;
-import androidx.datastore.rxjava3.RxDataStore;
 import com.google.auto.service.AutoService;
 import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener;
 import com.tencent.imsdk.v2.V2TIMFriendInfo;
@@ -89,7 +86,6 @@ public class TUIChatService implements TUIInitializer, ITUIService, ITUINotifica
 
     private final Map<String, Class<? extends TUIMessageBean>> customMessageMap = new HashMap<>();
     private final Set<Class<? extends TUIMessageBean>> extensionMessageClass = new HashSet<>();
-    private RxDataStore<Preferences> mChatDataStore = null;
 
     @Override
     public void init(Context context) {
@@ -99,24 +95,12 @@ public class TUIChatService implements TUIInitializer, ITUIService, ITUINotifica
         initService();
         initEvent();
         initIMListener();
-        initDataStore();
     }
 
     private void initService() {
         TUICore.registerService(TUIConstants.TUIChat.SERVICE_NAME, this);
     }
 
-    private void initDataStore() {
-        if (mChatDataStore == null) {
-            mChatDataStore = new RxPreferenceDataStoreBuilder(getAppContext(), TUIChatConstants.DataStore.DATA_STORE_NAME).build();
-        }
-        DataStoreUtil.getInstance().setDataStore(mChatDataStore);
-    }
-
-    public RxDataStore<Preferences> getChatDataStore() {
-        return mChatDataStore;
-    }
-
     private void initEvent() {
         TUICore.registerEvent(TUIConstants.TUIGroup.EVENT_GROUP, TUIConstants.TUIGroup.EVENT_SUB_KEY_GROUP_INFO_CHANGED, this);
         TUICore.registerEvent(TUIConstants.TUIGroup.EVENT_GROUP, TUIConstants.TUIGroup.EVENT_SUB_KEY_EXIT_GROUP, this);
@@ -323,20 +307,27 @@ public class TUIChatService implements TUIInitializer, ITUIService, ITUINotifica
             // Set whether to open the floating window for voice and video calls
             Map<String, Object> enableFloatWindowParam = new HashMap<>();
             enableFloatWindowParam.put(
-                TUIConstants.TUICalling.PARAM_NAME_ENABLE_FLOAT_WINDOW, TUIChatConfigs.getConfigs().getGeneralConfig().isEnableFloatWindowForCall());
+                TUIConstants.TUICalling.PARAM_NAME_ENABLE_FLOAT_WINDOW, TUIChatConfigs.getGeneralConfig().isEnableFloatWindowForCall());
             TUICore.callService(TUIConstants.TUICalling.SERVICE_NAME, TUIConstants.TUICalling.METHOD_NAME_ENABLE_FLOAT_WINDOW, enableFloatWindowParam);
 
             // Set Whether to enable multi-terminal login function for audio and video calls
             Map<String, Object> enableMultiDeviceParam = new HashMap<>();
             enableMultiDeviceParam.put(
-                TUIConstants.TUICalling.PARAM_NAME_ENABLE_MULTI_DEVICE, TUIChatConfigs.getConfigs().getGeneralConfig().isEnableMultiDeviceForCall());
+                TUIConstants.TUICalling.PARAM_NAME_ENABLE_MULTI_DEVICE, TUIChatConfigs.getGeneralConfig().isEnableMultiDeviceForCall());
             TUICore.callService(TUIConstants.TUICalling.SERVICE_NAME, TUIConstants.TUICalling.METHOD_NAME_ENABLE_MULTI_DEVICE, enableMultiDeviceParam);
 
             // Set whether to enable incoming banner when user received audio and video calls
             Map<String, Object> incomingBannerParam = new HashMap<>();
             incomingBannerParam.put(
-                TUIConstants.TUICalling.PARAM_NAME_ENABLE_INCOMING_BANNER, TUIChatConfigs.getConfigs().getGeneralConfig().isEnableIncomingBanner());
+                TUIConstants.TUICalling.PARAM_NAME_ENABLE_INCOMING_BANNER, TUIChatConfigs.getGeneralConfig().isEnableIncomingBanner());
             TUICore.callService(TUIConstants.TUICalling.SERVICE_NAME, TUIConstants.TUICalling.METHOD_NAME_ENABLE_INCOMING_BANNER, incomingBannerParam);
+
+            // Set whether to enable the virtual background function for video calls.
+            Map<String, Object> virtualBackgroundForCallParams = new HashMap<>();
+            virtualBackgroundForCallParams.put(
+                    TUIConstants.TUICalling.PARAM_NAME_ENABLE_VIRTUAL_BACKGROUND, TUIChatConfigs.getGeneralConfig().isEnableVirtualBackgroundForCall());
+            TUICore.callService(
+                    TUIConstants.TUICalling.SERVICE_NAME, TUIConstants.TUICalling.METHOD_NAME_ENABLE_VIRTUAL_BACKGROUND, virtualBackgroundForCallParams);
         }
     }
 

+ 0 - 1
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/ChatInfo.java

@@ -16,7 +16,6 @@ public class ChatInfo implements Serializable {
     private static List<V2TIMGroupAtInfo> atInfoList;
 
     protected String chatName;
-    // other's face url
     protected String faceUrl;
     private List<Object> iconUrlList = new ArrayList<>();
     private int type;

+ 0 - 33
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/GroupApplyInfo.java

@@ -5,50 +5,17 @@ import com.tencent.imsdk.v2.V2TIMGroupApplication;
 import java.io.Serializable;
 
 public class GroupApplyInfo implements Serializable {
-    public static final int APPLIED = 1;
-    public static final int REFUSED = -1;
-    public static final int UNHANDLED = 0;
 
-    private int status;
     private V2TIMGroupApplication timGroupApplication;
 
     public GroupApplyInfo(V2TIMGroupApplication timGroupApplication) {
         this.timGroupApplication = timGroupApplication;
     }
 
-    public int getStatus() {
-        return status;
-    }
-
-    public void setStatus(int status) {
-        this.status = status;
-    }
-
     public V2TIMGroupApplication getGroupApplication() {
         return timGroupApplication;
     }
 
-    public String getFromUser() {
-        if (timGroupApplication != null) {
-            return timGroupApplication.getFromUser();
-        }
-        return "";
-    }
-
-    public String getFromUserNickName() {
-        if (timGroupApplication != null) {
-            return timGroupApplication.getFromUserNickName();
-        }
-        return "";
-    }
-
-    public String getRequestMsg() {
-        if (timGroupApplication != null) {
-            return timGroupApplication.getRequestMsg();
-        }
-        return "";
-    }
-
     public boolean isStatusHandled() {
         if (timGroupApplication != null) {
             return timGroupApplication.getHandleStatus() != V2TIMGroupApplication.V2TIM_GROUP_APPLICATION_HANDLE_STATUS_UNHANDLED;

+ 0 - 48
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/GroupMemberInfo.java

@@ -43,30 +43,6 @@ public class GroupMemberInfo implements Serializable {
         this.account = account;
     }
 
-    public String getSignature() {
-        return signature;
-    }
-
-    public void setSignature(String signature) {
-        this.signature = signature;
-    }
-
-    public String getLocation() {
-        return location;
-    }
-
-    public void setLocation(String location) {
-        this.location = location;
-    }
-
-    public String getBirthday() {
-        return birthday;
-    }
-
-    public void setBirthday(String birthday) {
-        this.birthday = birthday;
-    }
-
     public void setNameCard(String nameCard) {
         this.nameCard = nameCard;
     }
@@ -83,22 +59,6 @@ public class GroupMemberInfo implements Serializable {
         return this.nickName;
     }
 
-    public boolean isTopChat() {
-        return isTopChat;
-    }
-
-    public void setTopChat(boolean topChat) {
-        isTopChat = topChat;
-    }
-
-    public boolean isFriend() {
-        return isFriend;
-    }
-
-    public void setFriend(boolean friend) {
-        isFriend = friend;
-    }
-
     public long getJoinTime() {
         return joinTime;
     }
@@ -107,14 +67,6 @@ public class GroupMemberInfo implements Serializable {
         this.joinTime = joinTime;
     }
 
-    public long getTinyId() {
-        return tinyId;
-    }
-
-    public void setTinyId(long tinyId) {
-        this.tinyId = tinyId;
-    }
-
     public int getMemberType() {
         return memberType;
     }

+ 1 - 1
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/InputMoreActionUnit.java → Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/InputMoreItem.java

@@ -2,7 +2,7 @@ package com.tencent.qcloud.tuikit.tuichat.bean;
 
 import android.view.View;
 
-public class InputMoreActionUnit {
+public class InputMoreItem {
     private ChatInfo chatInfo;
 
     private int iconResId;

+ 0 - 29
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/OfflineMessageBean.java

@@ -1,29 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.bean;
-
-import androidx.annotation.NonNull;
-
-import com.tencent.imsdk.v2.V2TIMConversation;
-
-public class OfflineMessageBean {
-    public static final int REDIRECT_ACTION_CHAT = 1;
-    public static final int REDIRECT_ACTION_CALL = 2;
-
-    public int version = 1;
-    public int chatType = V2TIMConversation.V2TIM_C2C;
-    public int action = REDIRECT_ACTION_CHAT;
-    public String sender = "";
-    public String nickname = "";
-    public String faceUrl = "";
-    public String content = "";
-    
-    // seconds
-    public long sendTime = 0;
-
-    @NonNull
-    @Override
-    public String toString() {
-        return "OfflineMessageBean{"
-            + "version=" + version + ", chatType='" + chatType + '\'' + ", action=" + action + ", sender=" + sender + ", nickname=" + nickname
-            + ", faceUrl=" + faceUrl + ", content=" + content + ", sendTime=" + sendTime + '}';
-    }
-}

+ 0 - 2
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/OfflinePushInfo.java

@@ -5,8 +5,6 @@ import com.tencent.imsdk.message.MessageOfflinePushInfo;
 import java.io.Serializable;
 
 public class OfflinePushInfo implements Serializable {
-    public static final String IOS_OFFLINE_PUSH_NO_SOUND = "push.no_sound";
-    public static final String IOS_OFFLINE_PUSH_DEFAULT_SOUND = "default";
 
     private String title;
     private String description;

+ 0 - 54
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/noticelayout/INoticeLayout.java

@@ -1,54 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.classicui.component.noticelayout;
-
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-/**
- *
- * The notification area {@link NoticeLayout} has a fixed position and can only be displayed or hidden.
- * The position will not change with the scrolling of the chat content. It can be used to display pending
- * group messages, or some broadcasts. This area is divided into two parts, which can be used to display
- * content topics and auxiliary topics. Click events can be set up in response to user actions.
- */
-public interface INoticeLayout {
-    /**
-     *
-     * Get parent view
-     *
-     * @return
-     */
-    RelativeLayout getParentLayout();
-
-    /**
-     *
-     * Get the subject information of the notification View
-     *
-     * @return
-     */
-    TextView getContent();
-
-    /**
-     *
-     * Get notifications for further actions View
-     *
-     * @return
-     */
-    TextView getContentExtra();
-
-    /**
-     *
-     * Set the click event for the notification
-     *
-     * @param l
-     */
-    void setOnNoticeClickListener(View.OnClickListener l);
-
-    /**
-     *
-     * Set whether the notification area is always displayed
-     *
-     * @param show  true
-     */
-    void alwaysShow(boolean show);
-}

+ 1 - 6
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/noticelayout/NoticeLayout.java

@@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
 
 import com.tencent.qcloud.tuikit.tuichat.R;
 
-public class NoticeLayout extends RelativeLayout implements INoticeLayout {
+public class NoticeLayout extends RelativeLayout {
     private RelativeLayout mNoticeLayout;
     private TextView mContentText;
     private TextView mContentExtraText;
@@ -37,22 +37,18 @@ public class NoticeLayout extends RelativeLayout implements INoticeLayout {
         mContentExtraText = findViewById(R.id.notice_content_extra);
     }
 
-    @Override
     public RelativeLayout getParentLayout() {
         return mNoticeLayout;
     }
 
-    @Override
     public TextView getContent() {
         return mContentText;
     }
 
-    @Override
     public TextView getContentExtra() {
         return mContentExtraText;
     }
 
-    @Override
     public void setOnNoticeClickListener(OnClickListener l) {
         setOnClickListener(l);
     }
@@ -66,7 +62,6 @@ public class NoticeLayout extends RelativeLayout implements INoticeLayout {
         }
     }
 
-    @Override
     public void alwaysShow(boolean show) {
         mAwaysShow = show;
         if (mAwaysShow) {

+ 6 - 6
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/noticelayout/NoticeLayoutConfig.java

@@ -1,23 +1,23 @@
 package com.tencent.qcloud.tuikit.tuichat.classicui.component.noticelayout;
 
-import android.view.ViewGroup;
+import android.view.View;
 
 public class NoticeLayoutConfig {
-    private ViewGroup mCustomNoticeLayout = null;
+    private View mCustomView = null;
 
     /**
      *
      * Get a custom view of the chat interface
      */
-    public ViewGroup getCustomNoticeLayout() {
-        return mCustomNoticeLayout;
+    public View getCustomNoticeLayout() {
+        return mCustomView;
     }
 
     /**
      *
      * Set a custom view of the chat interface
      */
-    public void setCustomNoticeLayout(ViewGroup mCustomNoticeLayout) {
-        this.mCustomNoticeLayout = mCustomNoticeLayout;
+    public void setCustomNoticeLayout(View mCustomNoticeLayout) {
+        this.mCustomView = mCustomNoticeLayout;
     }
 }

+ 0 - 12
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/component/popmenu/ChatPopMenu.java

@@ -1,8 +1,6 @@
 package com.tencent.qcloud.tuikit.tuichat.classicui.component.popmenu;
 
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
@@ -11,7 +9,6 @@ import android.graphics.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
@@ -21,29 +18,20 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.widget.TextView;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.PagerSnapHelper;
 import androidx.recyclerview.widget.RecyclerView;
 import com.tencent.qcloud.tuicore.TUIConstants;
 import com.tencent.qcloud.tuicore.TUICore;
-import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
-import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
-import com.tencent.qcloud.tuikit.timcommon.component.face.RecentEmojiManager;
-import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
-import com.tencent.qcloud.tuikit.tuichat.classicui.component.EmojiIndicatorView;
-import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageRecyclerView;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.C2CChatEventListener;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.OnEmptySpaceClickListener;
 

+ 0 - 2
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/interfaces/IChatLayout.java

@@ -6,7 +6,6 @@ import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ILayout;
 import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
 import com.tencent.qcloud.tuikit.tuichat.classicui.component.noticelayout.NoticeLayout;
 import com.tencent.qcloud.tuikit.tuichat.classicui.page.TUIBaseChatFragment;
-import com.tencent.qcloud.tuikit.tuichat.classicui.setting.ChatLayoutSetting;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.ChatView;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.InputView;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageRecyclerView;
@@ -41,5 +40,4 @@ public interface IChatLayout extends ILayout {
 
     void sendMessage(TUIMessageBean msg, boolean retry);
 
-    void customizeInputMoreLayout();
 }

+ 0 - 27
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/interfaces/IMessageLayout.java

@@ -1,27 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.classicui.interfaces;
-
-import com.tencent.qcloud.tuikit.timcommon.interfaces.IMessageProperties;
-import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
-import com.tencent.qcloud.tuikit.tuichat.classicui.component.popmenu.ChatPopMenu;
-import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageAdapter;
-import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageRecyclerView;
-
-import java.util.List;
-
-/**
- * The message area {@link MessageRecyclerView} inherits from {@link RecyclerView} and provides the display function of the message.
- * This class provides a large number of methods for customization requirements, including appearance settings, event clicks,
- * and display of custom messages.
- */
-public interface IMessageLayout extends IMessageProperties {
-
-    void setAdapter(MessageAdapter adapter);
-
-    OnItemClickListener getOnItemClickListener();
-
-    void setOnItemClickListener(OnItemClickListener listener);
-
-    List<ChatPopMenu.ChatPopMenuAction> getPopActions();
-
-    void addPopAction(ChatPopMenu.ChatPopMenuAction action);
-}

+ 3 - 3
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/page/TUIBaseChatActivity.java

@@ -28,14 +28,14 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.chat_activity);
-        chat(getIntent());
+        initChat(getIntent());
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         TUIChatLog.i(TAG, "onNewIntent");
         super.onNewIntent(intent);
-        chat(intent);
+        initChat(intent);
     }
 
     @Override
@@ -44,7 +44,7 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
         super.onResume();
     }
 
-    private void chat(Intent intent) {
+    private void initChat(Intent intent) {
         Bundle bundle = intent.getExtras();
         TUIChatLog.i(TAG, "bundle: " + bundle + " intent: " + intent);
 

+ 24 - 48
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/page/TUIBaseChatFragment.java

@@ -2,9 +2,6 @@ package com.tencent.qcloud.tuikit.tuichat.classicui.page;
 
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -17,9 +14,9 @@ import androidx.activity.result.ActivityResultCallback;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.util.Supplier;
+import androidx.fragment.app.Fragment;
+
 import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.target.CustomTarget;
-import com.bumptech.glide.request.transition.Transition;
 import com.tencent.imsdk.v2.V2TIMMessage;
 import com.tencent.qcloud.tuicore.TUIConfig;
 import com.tencent.qcloud.tuicore.TUIConstants;
@@ -30,10 +27,9 @@ import com.tencent.qcloud.tuicore.interfaces.TUIExtensionInfo;
 import com.tencent.qcloud.tuicore.util.ToastUtil;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
 import com.tencent.qcloud.tuikit.timcommon.component.interfaces.IUIKitCallback;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
-import com.tencent.qcloud.tuikit.timcommon.util.ImageUtil;
+import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
 import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
@@ -43,18 +39,21 @@ import com.tencent.qcloud.tuikit.tuichat.classicui.widget.ChatView;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.InputView;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageRecyclerView;
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioPlayer;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
 import com.tencent.qcloud.tuikit.tuichat.presenter.ChatPresenter;
 import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder;
 import com.tencent.qcloud.tuikit.tuichat.util.DataStoreUtil;
 import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
 import com.tencent.qcloud.tuikit.tuichat.util.TUIChatUtils;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
 
-public abstract class TUIBaseChatFragment extends BaseFragment {
+public abstract class TUIBaseChatFragment extends Fragment {
     private static final String TAG = TUIBaseChatFragment.class.getSimpleName();
 
     protected View baseView;
@@ -199,7 +198,7 @@ public abstract class TUIBaseChatFragment extends BaseFragment {
     }
 
     protected void onRecallClicked(TUIMessageBean messageInfo) {
-        if (messageInfo == null) {
+        if (messageInfo == null || TextUtils.isEmpty(messageInfo.getUserId())) {
             return;
         }
         CallingMessageBean callingMessageBean = (CallingMessageBean) messageInfo;
@@ -323,6 +322,13 @@ public abstract class TUIBaseChatFragment extends BaseFragment {
             TUIChatLog.e(TAG, "initChatViewBackground getChatInfo is null");
             return;
         }
+
+        Drawable chatBackground = TUIChatConfigClassic.getBackground();
+        if (chatBackground != null) {
+            setChatBackground(chatBackground);
+            return;
+        }
+
         DataStoreUtil.getInstance().getValueAsync(getChatInfo().getId(), new DataStoreUtil.GetResult<String>() {
             @Override
             public void onSuccess(String result) {
@@ -337,21 +343,10 @@ public abstract class TUIBaseChatFragment extends BaseFragment {
     }
 
     protected void setChatViewBackground(String uri) {
-        TUIChatLog.d(TAG, "setChatViewBackground uri = " + uri);
         if (TextUtils.isEmpty(uri)) {
             return;
         }
 
-        if (chatView == null) {
-            TUIChatLog.e(TAG, "setChatViewBackground chatview is null");
-            return;
-        }
-
-        if (messageRecyclerView == null) {
-            TUIChatLog.e(TAG, "setChatViewBackground messageRecyclerView is null");
-            return;
-        }
-
         String[] list = uri.split(",");
         if (list.length > 0) {
             mChatBackgroundThumbnailUrl = list[0];
@@ -367,35 +362,16 @@ public abstract class TUIBaseChatFragment extends BaseFragment {
             return;
         }
 
-        messageRecyclerView.post(new Runnable() {
-            @Override
-            public void run() {
-                int imageWidth = messageRecyclerView.getWidth();
-                int imageHeight = messageRecyclerView.getHeight();
-                if (imageHeight > messageViewBackgroundHeight) {
-                    messageViewBackgroundHeight = imageHeight;
-                }
-                TUIChatLog.d(TAG, "messageRecyclerView  width = " + imageWidth + ", height = " + messageViewBackgroundHeight);
-                if (imageWidth == 0 || messageViewBackgroundHeight == 0) {
-                    return;
-                }
-                Glide.with(getContext()).asBitmap().load(mChatBackgroundUrl).into(new CustomTarget<Bitmap>(imageWidth, messageViewBackgroundHeight) {
-                    @Override
-                    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
-                        TUIChatLog.d(TAG, "messageRecyclerView onGlobalLayout url = " + mChatBackgroundUrl);
-                        Bitmap srcBitmap = ImageUtil.zoomImg(resource, imageWidth, messageViewBackgroundHeight);
-                        messageRecyclerView.setBackground(new BitmapDrawable(getResources(), resource) {
-                            @Override
-                            public void draw(@NonNull Canvas canvas) {
-                                // TUIChatLog.d(TAG, "draw canvas =" + canvas.getClipBounds());
-                                canvas.drawBitmap(srcBitmap, canvas.getClipBounds(), canvas.getClipBounds(), null);
-                            }
-                        });
-                    }
+        setChatBackground(mChatBackgroundUrl);
+    }
 
-                    @Override
-                    public void onLoadCleared(@Nullable Drawable placeholder) {}
-                });
+    private void setChatBackground(Object backgroundRes) {
+        ThreadUtils.execute(() -> {
+            try {
+                Drawable drawable = Glide.with(baseView.getContext()).asDrawable().load(backgroundRes).submit().get();
+                chatView.setChatBackground(drawable);
+            } catch (ExecutionException | InterruptedException e) {
+                TUIChatLog.e(TAG, "load background failed");
             }
         });
     }

+ 0 - 245
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/setting/ChatLayoutSetting.java

@@ -1,245 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.classicui.setting;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.FrameLayout;
-import com.google.gson.Gson;
-import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
-import com.tencent.qcloud.tuikit.tuichat.bean.CustomHelloMessage;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
-import com.tencent.qcloud.tuikit.tuichat.classicui.widget.ChatView;
-import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.InputView;
-import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageRecyclerView;
-import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
-import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder;
-
-public class ChatLayoutSetting {
-    private static final String TAG = ChatLayoutSetting.class.getSimpleName();
-
-    private Context mContext;
-    private String groupId;
-
-    public ChatLayoutSetting(Context context) {
-        mContext = context;
-    }
-
-    public void setGroupId(String groupId) {
-        this.groupId = groupId;
-    }
-
-    public void customizeMessageLayout(final MessageRecyclerView messageRecyclerView) {
-        if (messageRecyclerView == null) {
-            return;
-        }
-    }
-
-    public void customizeChatLayout(final ChatView layout) {
-        
-        // Set custom view of chat interface as security prompt
-        ViewGroup customNoticeLayout = TUIChatConfigs.getConfigs().getNoticeLayoutConfig().getCustomNoticeLayout();
-        FrameLayout customView = layout.getCustomView();
-        if (customNoticeLayout != null && customView.getVisibility() == View.GONE) {
-            ViewParent viewParent = customNoticeLayout.getParent();
-            if (viewParent instanceof ViewGroup) {
-                ViewGroup parentView = (ViewGroup) viewParent;
-                parentView.removeAllViews();
-            }
-            customView.addView(customNoticeLayout);
-            customView.setVisibility(View.VISIBLE);
-        }
-
-        
-        //====== MessageLayout example ======//
-        MessageRecyclerView messageRecyclerView = layout.getMessageLayout();
-        
-        //        messageRecyclerView.setBackground(new ColorDrawable(0xFFEFE5D4));
-        
-        
-        // Set the default avatar, the default is the same as your friend's and your own avatar
-        //        messageRecyclerView.setAvatar(R.drawable.ic_more_file);
-        
-        messageRecyclerView.setAvatarRadius(4);
-        
-        //        messageRecyclerView.setAvatarSize(new int[]{48, 48});
-        //
-        
-        //        ////// Set the nickname style (the other party is consistent with their own style) //////
-        //        messageRecyclerView.setNameFontSize(12);
-        //        messageRecyclerView.setNameFontColor(0xFF8B5A2B);
-        //
-        
-        
-        //        // Set the background of your own chat bubble
-        //        messageRecyclerView.setRightBubble(new ColorDrawable(0xFFCCE4FC));
-        
-        //        // Set the background of friends chat bubbles
-        //        messageRecyclerView.setLeftBubble(new ColorDrawable(0xFFE4E7EB));
-        //
-        
-        
-        //        // Set the font size of chat content, friends and yourself use a font size
-        //        messageRecyclerView.setChatContextFontSize(15);
-        
-        //        // Set your own chat content font color
-        //        messageRecyclerView.setRightChatContentFontColor(0xFFA9A9A9);
-        
-        //        // Set friend chat content font color
-        //        messageRecyclerView.setLeftChatContentFontColor(0xFFA020F0);
-        //
-        
-        
-        //        // Set the background of the chat timeline
-        //        messageRecyclerView.setChatTimeBubble(new ColorDrawable(0xFFE4E7EB));
-        
-        //        // Set the font size of chat time
-        //        messageRecyclerView.setChatTimeFontSize(12);
-        
-        //        // Set the font color of chat time
-        //        messageRecyclerView.setChatTimeFontColor(0xFF7E848C);
-        //
-        
-        
-        //        // Set the background of the prompt
-        //        messageRecyclerView.setTipsMessageBubble(new ColorDrawable(0xFFE4E7EB));
-        
-        //        // Set the font size of the prompt
-        //        messageRecyclerView.setTipsMessageFontSize(12);
-        
-        //        // Set the font color of the prompt
-        //        messageRecyclerView.setTipsMessageFontColor(0xFF7E848C);
-        //
-
-        //
-        
-        //        // Add a PopMenuAction
-        //        PopMenuAction action = new PopMenuAction();
-        //        action.setActionName("test");
-        //        action.setActionClickListener(new PopActionClickListener() {
-        //            @Override
-        //            public void onActionClick(int position, Object data) {
-        
-        //            }
-        //        });
-        //        messageRecyclerView.addPopAction(action);
-        //
-        //        final MessageRecyclerView.OnItemClickListener l = messageRecyclerView.getOnItemClickListener();
-        //        messageRecyclerView.setOnItemClickListener(new MessageRecyclerView.OnItemClickListener() {
-        //            @Override
-        //            public void onMessageLongClick(View view, int position, MessageInfo messageInfo) {
-        //                l.onMessageLongClick(view, position, messageInfo);
-        
-        //            }
-        //
-        //            @Override
-        //            public void onUserIconClick(View view, int position, MessageInfo messageInfo) {
-        //                l.onUserIconClick(view, position, messageInfo);
-        
-        //            }
-        //        });
-
-        
-        //====== InputLayout example ======//
-        // final InputView inputView = layout.getInputLayout();
-
-        
-        //        // To hide the entrance of audio input, you can open the following code to test
-        //        inputView.disableAudioInput(true);
-        
-        //        // To hide the entry of expression input, you can open the following code test
-        //        inputView.disableEmojiInput(true);
-        
-        //        // To hide the entrance of more functions, you can open the following code test
-        //        inputView.disableMoreInput(true);
-        
-        //        // You can replace the entry of more functions with custom events, you can open the following code test
-        //        inputView.replaceMoreInput(new View.OnClickListener() {
-        //            @Override
-        //            public void onClick(View v) {
-        
-        
-        //                layout.sendMessage(info, false);
-        //            }
-        //        });
-        
-        //        // You can replace more functions with custom fragments, you can open the following code to test
-        //        inputView.replaceMoreInput(new CustomInputFragment().setChatLayout(layout));
-        //
-        
-        //        // You can disable various functions on more panels, you can open the following code test
-        //        inputView.disableCaptureAction(true);
-        //        inputView.disableSendFileAction(true);
-        //        inputView.disableSendPhotoAction(true);
-        //        inputView.disableVideoRecordAction(true);
-    }
-
-    public void customizeInputMoreExtension(ChatView layout) {
-        InputView inputView = layout.getInputLayout();
-        
-        // You can add some functions yourself, you can open the following code to test
-
-        
-        // Add a welcome prompt with rich text
-        if (TUIChatConfigs.getGeneralConfig().isEnableWelcomeCustomMessage() && layout.getChatInfo().isEnableCustomHelloMessage()) {
-            InputMoreActionUnit unit = new InputMoreActionUnit() {};
-            unit.setIconResId(R.drawable.custom);
-            unit.setName(mContext.getString(R.string.test_custom_action));
-            unit.setActionId(CustomHelloMessage.CUSTOM_HELLO_ACTION_ID);
-            unit.setPriority(10);
-            unit.setOnClickListener(unit.new OnActionClickListener() {
-                @Override
-                public void onClick() {
-                    Gson gson = new Gson();
-                    CustomHelloMessage customHelloMessage = new CustomHelloMessage();
-                    customHelloMessage.version = TUIChatConstants.version;
-
-                    String data = gson.toJson(customHelloMessage);
-                    TUIMessageBean info = ChatMessageBuilder.buildCustomMessage(data, customHelloMessage.text, customHelloMessage.text.getBytes());
-                    layout.sendMessage(info, false);
-                }
-            });
-            inputView.addAction(unit);
-        }
-    }
-
-    //    public static class CustomInputFragment extends BaseInputFragment {
-    //        @Nullable
-    //        @Override
-    //        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
-    //            View baseView = inflater.inflate(R.layout.test_chat_input_custom_fragment, container, false);
-    //            Button btn1 = baseView.findViewById(R.id.test_send_message_btn1);
-    //            btn1.setOnClickListener(new View.OnClickListener() {
-    //                @Override
-    //                public void onClick(View v) {
-    
-    //                    if (getChatLayout() != null) {
-    //                        Gson gson = new Gson();
-    //                        CustomHelloMessage customHelloMessage = new CustomHelloMessage();
-    //                        String data = gson.toJson(customHelloMessage);
-    //                        MessageInfo info = ChatMessageInfoUtil.buildCustomMessage(data);
-    //                        getChatLayout().sendMessage(info, false);
-    //                    }
-    //                }
-    //            });
-    //            Button btn2 = baseView.findViewById(R.id.test_send_message_btn2);
-    //            btn2.setOnClickListener(new View.OnClickListener() {
-    //                @Override
-    //                public void onClick(View v) {
-    
-    //                    if (getChatLayout() != null) {
-    //                        Gson gson = new Gson();
-    //                        CustomHelloMessage customHelloMessage = new CustomHelloMessage();
-    //                        String data = gson.toJson(customHelloMessage);
-    //                        MessageInfo info = ChatMessageInfoUtil.buildCustomMessage(data);
-    //                        getChatLayout().sendMessage(info, false);
-    //                    }
-    //                }
-    //            });
-    //            return baseView;
-    //        }
-
-    //    }
-}

+ 91 - 29
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/ChatView.java

@@ -6,7 +6,9 @@ import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.text.Spannable;
@@ -19,6 +21,8 @@ import android.text.style.ForegroundColorSpan;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -35,11 +39,10 @@ import com.tencent.qcloud.tuicore.TUIThemeManager;
 import com.tencent.qcloud.tuicore.interfaces.TUIExtensionInfo;
 import com.tencent.qcloud.tuicore.util.ToastUtil;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.SelectTextHelper;
+import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.SelectionHelper;
 import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
 import com.tencent.qcloud.tuikit.timcommon.component.UnreadCountTextView;
 import com.tencent.qcloud.tuikit.timcommon.component.dialog.TUIKitDialog;
-import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
 import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
 import com.tencent.qcloud.tuikit.timcommon.component.interfaces.IUIKitCallback;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.ChatInputMoreListener;
@@ -57,15 +60,17 @@ import com.tencent.qcloud.tuikit.tuichat.bean.message.ReplyMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.classicui.component.noticelayout.NoticeLayout;
 import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.IChatLayout;
 import com.tencent.qcloud.tuikit.tuichat.classicui.page.TUIBaseChatFragment;
-import com.tencent.qcloud.tuikit.tuichat.classicui.setting.ChatLayoutSetting;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.InputView;
+import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.ShortcutView;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageAdapter;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageRecyclerView;
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioPlayer;
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder;
 import com.tencent.qcloud.tuikit.tuichat.component.pinned.GroupPinnedView;
 import com.tencent.qcloud.tuikit.tuichat.component.progress.ProgressPresenter;
+import com.tencent.qcloud.tuikit.tuichat.config.ShortcutMenuConfig;
 import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.OnEmptySpaceClickListener;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.OnGestureScrollListener;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.TotalUnreadCountListener;
@@ -85,7 +90,7 @@ import java.util.TimerTask;
 
 public class ChatView extends LinearLayout implements IChatLayout {
     private static final String TAG = ChatView.class.getSimpleName();
-    
+
     // Limit the number of messages forwarded one by one
     private static final int FORWARD_MSG_NUM_LIMIT = 30;
 
@@ -99,7 +104,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
     public ChatPresenter.TypingListener mTypingListener = new ChatPresenter.TypingListener() {
         @Override
         public void onTyping(int status) {
-            if (!TUIChatConfigs.getGeneralConfig().isEnableTypingStatus()) {
+            if (!TUIChatConfigClassic.isEnableTypingIndicator()) {
                 return;
             }
 
@@ -140,9 +145,11 @@ public class ChatView extends LinearLayout implements IChatLayout {
     protected TextView mRecordingTips;
     protected TextView recordCountDownTv;
     private TitleBarLayout mTitleBar;
+    private ImageView chatBackgroundView;
     private MessageRecyclerView mMessageRecyclerView;
     private InputView mInputView;
     private View flInputFloatLayout;
+    private ViewGroup shortcutContainer;
     private NoticeLayout mNoticeLayout;
     private LinearLayout mJumpMessageLayout;
     private ImageView mArrowImageView;
@@ -158,7 +165,6 @@ public class ChatView extends LinearLayout implements IChatLayout {
     private View mDeleteButton;
     private long lastTypingTime = 0;
     private boolean isSupportTyping = false;
-    private ChatLayoutSetting mChatLayoutSetting;
 
     private ChatPresenter presenter;
     private int scrollDirection = 0;
@@ -182,10 +188,11 @@ public class ChatView extends LinearLayout implements IChatLayout {
         inflate(getContext(), R.layout.tuichat_chat_layout, this);
 
         mTitleBar = findViewById(R.id.chat_title_bar);
+        chatBackgroundView = findViewById(R.id.chat_background_view);
         mMessageRecyclerView = findViewById(R.id.chat_message_layout);
         mInputView = findViewById(R.id.chat_input_layout);
         mInputView.setChatLayout(this);
-        boolean enableMainPageInputBar = TUIChatConfigs.getConfigs().getGeneralConfig().isEnableMainPageInputBar();
+        boolean enableMainPageInputBar = TUIChatConfigClassic.isShowInputBar();
         mInputView.setVisibility(enableMainPageInputBar ? VISIBLE : GONE);
         mRecordingGroup = findViewById(R.id.voice_recording_view);
         mRecordingIcon = findViewById(R.id.recording_icon);
@@ -201,6 +208,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
         groupPinnedView = findViewById(R.id.group_pinned_message_view);
 
         flInputFloatLayout = findViewById(R.id.fl_input_float);
+        shortcutContainer = findViewById(R.id.shortcut_container);
 
         mForwardLayout = findViewById(R.id.forward_layout);
         mForwardOneButton = findViewById(R.id.forward_one_by_one_button);
@@ -359,7 +367,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            SelectTextHelper.resetSelected();
+            SelectionHelper.resetSelected();
         }
         return super.dispatchTouchEvent(ev);
     }
@@ -399,7 +407,6 @@ public class ChatView extends LinearLayout implements IChatLayout {
         });
 
         mMessageRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
-
             @Override
             public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                 LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getMessageLayout().getLayoutManager();
@@ -411,7 +418,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
 
             @Override
             public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
-                SelectTextHelper.resetSelected();
+                SelectionHelper.resetSelected();
                 if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                     if (scrollDirection == -1) {
                         if (!mMessageRecyclerView.canScrollVertically(-1)) {
@@ -444,6 +451,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
             chatInfo.getLocateMessage(), chatInfo.getLocateMessage() == null ? TUIChatConstants.GET_MESSAGE_FORWARD : TUIChatConstants.GET_MESSAGE_TWO_WAY);
         setTotalUnread();
         initExtension();
+        setShortcutView();
     }
 
     private void initExtension() {
@@ -490,6 +498,26 @@ public class ChatView extends LinearLayout implements IChatLayout {
         TUICore.raiseExtension(TUIConstants.TUIChat.Extension.ChatViewTopAreaExtension.EXTENSION_ID, topExtensionLayout, topExtensionParam);
     }
 
+    private void setShortcutView() {
+        ShortcutMenuConfig.ChatShortcutViewDataSource dataSource = ShortcutMenuConfig.getShortcutViewDataSource();
+        if (dataSource != null) {
+            List<ShortcutMenuConfig.TUIChatShortcutMenuData> dataList = dataSource.itemsInShortcutViewOfInfo(mChatInfo);
+            if (dataList != null && !dataList.isEmpty()) {
+                shortcutContainer.setVisibility(VISIBLE);
+            } else {
+                shortcutContainer.setVisibility(GONE);
+                return;
+            }
+            Drawable drawable = dataSource.shortcutViewBackgroundOfInfo(mChatInfo);
+            if (drawable != null) {
+                shortcutContainer.setBackground(drawable);
+            }
+            ShortcutView shortcutView = new ShortcutView(getContext());
+            shortcutView.setDataList(dataList);
+            shortcutContainer.addView(shortcutView);
+        }
+    }
+
     private void setChatName() {
         if (!TextUtils.isEmpty(mChatInfo.getChatName())) {
             getTitleBar().setTitle(mChatInfo.getChatName(), ITitleBarLayout.Position.MIDDLE);
@@ -527,7 +555,6 @@ public class ChatView extends LinearLayout implements IChatLayout {
         });
     }
 
-    
     private void markCallingMsgRead(int firstPosition, int lastPosition) {
         if (mAdapter == null || presenter == null) {
             return;
@@ -544,7 +571,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
 
     private void notifyMessageDisplayed(int firstPosition, int lastPosition) {
         // *******************************
-        
+
         // *******************************
         markCallingMsgRead(firstPosition, lastPosition);
         // *******************************
@@ -725,13 +752,6 @@ public class ChatView extends LinearLayout implements IChatLayout {
         return mNoticeLayout;
     }
 
-    @Override
-    public void customizeInputMoreLayout() {
-        if (mChatLayoutSetting != null) {
-            mChatLayoutSetting.customizeInputMoreExtension(this);
-        }
-    }
-
     public FrameLayout getCustomView() {
         return mCustomView;
     }
@@ -819,8 +839,8 @@ public class ChatView extends LinearLayout implements IChatLayout {
 
             @Override
             public void onSpeakerModeSwitchClick(TUIMessageBean msg) {
-                boolean enableSpeakerMode = TUIChatConfigs.getConfigs().getGeneralConfig().isEnableSoundMessageSpeakerMode();
-                TUIChatConfigs.getConfigs().getGeneralConfig().setEnableSoundMessageSpeakerMode(!enableSpeakerMode);
+                boolean enableSpeakerMode = TUIChatConfigs.getGeneralConfig().isEnableSoundMessageSpeakerMode();
+                TUIChatConfigClassic.setPlayingSoundMessageViaSpeakerByDefault(!enableSpeakerMode);
                 AudioPlayer.getInstance().setSpeakerMode();
                 if (enableSpeakerMode) {
                     ToastUtil.toastShortMessage(getResources().getString(R.string.chat_speaker_mode_off_tip));
@@ -829,7 +849,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
                 }
             }
         });
-        getMessageLayout().setLoadMoreMessageHandler(new MessageRecyclerView.OnLoadMoreHandler() {
+        getMessageLayout().setChatDelegate(new MessageRecyclerView.ChatDelegate() {
             @Override
             public void displayBackToNewMessage(boolean display, String messageId, int count) {
                 ChatView.this.displayBackToNewMessage(display, messageId, count);
@@ -846,6 +866,11 @@ public class ChatView extends LinearLayout implements IChatLayout {
                     mClickLastMessageShow = false;
                 }
             }
+
+            @Override
+            public void hideSoftInput() {
+                getInputLayout().hideSoftInput();
+            }
         });
         getMessageLayout().setEmptySpaceClickListener(new OnEmptySpaceClickListener() {
             @Override
@@ -895,7 +920,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
 
             @Override
             public void onUserTyping(boolean status, long curTime) {
-                if (!TUIChatConfigs.getConfigs().getGeneralConfig().isEnableTypingStatus()) {
+                if (!TUIChatConfigClassic.isEnableTypingIndicator()) {
                     return;
                 }
 
@@ -1081,18 +1106,31 @@ public class ChatView extends LinearLayout implements IChatLayout {
                 ChatView.this.scrollToEnd();
             }
         });
-        getInputLayout().clearCustomActionList();
         if (getMessageLayout().getAdapter() == null) {
             mAdapter = new MessageAdapter();
             mAdapter.setFragment(fragment);
             mMessageRecyclerView.setAdapter(mAdapter);
         }
-        mChatLayoutSetting = new ChatLayoutSetting(getContext());
-        mChatLayoutSetting.customizeChatLayout(this);
+        setCustomTopView();
         initListener();
         resetForwardState("");
     }
 
+    private void setCustomTopView() {
+        // Set custom view of chat interface as security prompt
+        View customNoticeLayout = TUIChatConfigs.getNoticeLayoutConfig().getCustomNoticeLayout();
+        FrameLayout customView = getCustomView();
+        if (customNoticeLayout != null && customView.getVisibility() == View.GONE) {
+            ViewParent viewParent = customNoticeLayout.getParent();
+            if (viewParent instanceof ViewGroup) {
+                ViewGroup parentView = (ViewGroup) viewParent;
+                parentView.removeAllViews();
+            }
+            customView.addView(customNoticeLayout);
+            customView.setVisibility(View.VISIBLE);
+        }
+    }
+
     @Override
     public void setParentLayout(Object parentContainer) {}
 
@@ -1177,7 +1215,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
         });
 
         getForwardLayout().setVisibility(GONE);
-        boolean enableMainPageInputBar = TUIChatConfigs.getConfigs().getGeneralConfig().isEnableMainPageInputBar();
+        boolean enableMainPageInputBar = TUIChatConfigClassic.isShowInputBar();
         getInputLayout().setVisibility(enableMainPageInputBar ? VISIBLE : GONE);
     }
 
@@ -1386,7 +1424,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
                 presenter.modifyRootMessageToAddReplyInfo((ReplyMessageBean) data, new IUIKitCallback<Void>() {
                     @Override
                     public void onError(String module, int errCode, String errMsg) {
-                        ToastUtil.toastShortMessage("modify message failed code = " + errCode + " message = " + errMsg);
+                        TUIChatLog.e(TAG, "modify message failed code = " + errCode + " message = " + errMsg);
                     }
                 });
             }
@@ -1406,6 +1444,31 @@ public class ChatView extends LinearLayout implements IChatLayout {
         });
     }
 
+    public void setChatBackground(Drawable drawable) {
+        chatBackgroundView.post(() -> {
+            int vw = chatBackgroundView.getWidth();
+            int vh = chatBackgroundView.getHeight();
+            int dw = drawable.getIntrinsicWidth();
+            int dh = drawable.getIntrinsicHeight();
+            float scale;
+            float dx = 0;
+            float dy = 0;
+
+            if (dw * vh > vw * dh) {
+                scale = (float) vh / (float) dh;
+                dx = (vw - dw * scale) * 0.5f;
+            } else {
+                scale = (float) vw / (float) dw;
+                dy = (vh - dh * scale) * 0.5f;
+            }
+            Matrix matrix = new Matrix();
+            matrix.setScale(scale, scale);
+            matrix.postTranslate(Math.round(dx), Math.round(dy));
+            chatBackgroundView.setImageMatrix(matrix);
+            chatBackgroundView.setImageDrawable(drawable);
+        });
+    }
+
     public void sendTypingStatusMessage(boolean status) {
         if (mChatInfo == null || TextUtils.isEmpty(getChatInfo().getId())) {
             TUIChatLog.e(TAG, "sendTypingStatusMessage receiver is invalid");
@@ -1454,7 +1517,6 @@ public class ChatView extends LinearLayout implements IChatLayout {
 
     @Override
     protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
-        
         // You will go to Chat from other interfaces, and you must also report a read receipt
         if (visibility == VISIBLE) {
             if (getMessageLayout() == null) {

+ 3 - 2
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/BaseInputFragment.java

@@ -1,9 +1,10 @@
 package com.tencent.qcloud.tuikit.tuichat.classicui.widget.input;
 
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
+import androidx.fragment.app.Fragment;
+
 import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.IChatLayout;
 
-public class BaseInputFragment extends BaseFragment {
+public class BaseInputFragment extends Fragment {
     private IChatLayout mChatLayout;
 
     public IChatLayout getChatLayout() {

+ 129 - 294
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/InputView.java

@@ -2,17 +2,11 @@ package com.tencent.qcloud.tuikit.tuichat.classicui.widget.input;
 
 import android.app.Activity;
 import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -20,14 +14,14 @@ import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
-import android.webkit.MimeTypeMap;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+
 import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import com.google.gson.Gson;
 import com.google.gson.JsonSyntaxException;
@@ -37,7 +31,6 @@ import com.tencent.qcloud.tuicore.TUICore;
 import com.tencent.qcloud.tuicore.interfaces.TUIExtensionEventListener;
 import com.tencent.qcloud.tuicore.interfaces.TUIExtensionInfo;
 import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
-import com.tencent.qcloud.tuicore.util.TUIBuild;
 import com.tencent.qcloud.tuicore.util.ToastUtil;
 import com.tencent.qcloud.tuikit.timcommon.bean.ChatFace;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
@@ -51,25 +44,27 @@ import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
 import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
+import com.tencent.qcloud.tuikit.tuichat.bean.CustomHelloMessage;
 import com.tencent.qcloud.tuikit.tuichat.bean.DraftInfo;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
 import com.tencent.qcloud.tuikit.tuichat.bean.ReplyPreviewBean;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.FileMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.IChatLayout;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.inputmore.InputMoreFragment;
+import com.tencent.qcloud.tuikit.tuichat.component.album.AlbumPicker;
+import com.tencent.qcloud.tuikit.tuichat.component.album.VideoRecorder;
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder;
-import com.tencent.qcloud.tuikit.tuichat.component.camera.CameraActivity;
 import com.tencent.qcloud.tuikit.tuichat.component.face.FaceFragment;
 import com.tencent.qcloud.tuikit.tuichat.component.inputedittext.TIMMentionEditText;
 import com.tencent.qcloud.tuikit.tuichat.config.GeneralConfig;
-import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.AlbumPickerListener;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.IMultimediaRecorder;
 import com.tencent.qcloud.tuikit.tuichat.presenter.ChatPresenter;
 import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder;
 import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageParser;
-import com.tencent.qcloud.tuikit.tuichat.util.PermissionHelper;
 import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
 import com.tencent.qcloud.tuikit.tuichat.util.TUIChatUtils;
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -132,11 +127,10 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
     protected TIMMentionEditText mTextInput;
     private boolean mIsSending = false;
 
-    protected AppCompatActivity mActivity;
+    protected FragmentActivity mActivity;
     protected View mInputMoreView;
     protected ChatInfo mChatInfo;
-    protected List<InputMoreActionUnit> mInputMoreActionList = new ArrayList<>();
-    protected List<InputMoreActionUnit> mInputMoreCustomActionList = new ArrayList<>();
+    protected List<InputMoreItem> mInputMoreActionList = new ArrayList<>();
 
     private FaceFragment mFaceFragment;
     private ChatInputHandler mChatInputHandler;
@@ -167,6 +161,9 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
     private ImageView quoteCloseBtn;
     private boolean isShowCustomFace = true;
     private ChatInputMoreListener chatInputMoreListener;
+    private IMultimediaRecorder defaultVideoRecorder;
+    private IMultimediaRecorder extVideoRecorder;
+    private AudioRecorder defaultAudioRecorder;
 
     public InputView(Context context) {
         super(context);
@@ -188,7 +185,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
     }
 
     private void initViews() {
-        mActivity = (AppCompatActivity) getContext();
+        mActivity = (FragmentActivity) getContext();
         inflate(mActivity, R.layout.chat_input_layout, this);
         mInputMoreView = findViewById(R.id.more_groups);
         mSendAudioButton = findViewById(R.id.chat_voice_input);
@@ -280,15 +277,15 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
                 switch (motionEvent.getAction()) {
                     case MotionEvent.ACTION_DOWN:
                         mStartRecordY = motionEvent.getY();
-                        startRecord();
+                        startRecordAudio();
                         break;
                     case MotionEvent.ACTION_MOVE:
                         if (motionEvent.getY() - mStartRecordY < -100) {
                             readyToCancel = true;
-                            readyToCancelRecord();
+                            readyToCancelRecordAudio();
                         } else {
                             if (readyToCancel) {
-                                continueRecord();
+                                continueRecordAudio();
                             }
                             readyToCancel = false;
                         }
@@ -296,9 +293,9 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
                     case MotionEvent.ACTION_CANCEL:
                     case MotionEvent.ACTION_UP:
                         if (readyToCancel) {
-                            cancelRecord();
+                            cancelRecordAudio();
                         } else {
-                            stopRecord();
+                            stopRecordAudio();
                         }
                         break;
                     default:
@@ -364,7 +361,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         }
     }
 
-    private void startRecord() {
+    private void startRecordAudio() {
         AudioRecorder.startRecord(new AudioRecorder.AudioRecorderCallback() {
             @Override
             public void onStarted() {
@@ -418,23 +415,23 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         }
     }
 
-    private void readyToCancelRecord() {
+    private void readyToCancelRecordAudio() {
         if (mChatInputHandler != null) {
             mChatInputHandler.onRecordStatusChanged(ChatInputHandler.RECORD_READY_TO_CANCEL);
         }
     }
 
-    private void continueRecord() {
+    private void continueRecordAudio() {
         if (mChatInputHandler != null) {
             mChatInputHandler.onRecordStatusChanged(ChatInputHandler.RECORD_CONTINUE);
         }
     }
 
-    private void cancelRecord() {
+    private void cancelRecordAudio() {
         AudioRecorder.cancelRecord();
     }
 
-    private void stopRecord() {
+    private void stopRecordAudio() {
         AudioRecorder.stopRecord();
     }
 
@@ -454,7 +451,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
                 FaceUtil.handlerEmojiText(mTextInput, text, true);
                 mTextInput.setSelection(selectedIndex + displayInputString.length());
             }
-            
+
             // Afterwards @, the soft keyboard is to be displayed. Activity does not have onResume, so the soft keyboard cannot be displayed
             ThreadUtils.postOnUiThreadDelayed(new Runnable() {
                 @Override
@@ -536,266 +533,69 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
 
     protected void startSendPhoto() {
         TUIChatLog.i(TAG, "startSendPhoto");
-        ActivityResultResolver.getMultipleContent(mInputMoreFragment.getActivity(),
-            new String[] {ActivityResultResolver.CONTENT_TYPE_IMAGE, ActivityResultResolver.CONTENT_TYPE_VIDEO}, new TUIValueCallback<List<Uri>>() {
-                @Override
-                public void onSuccess(List<Uri> uris) {
-                    ThreadUtils.runOnUiThread(() -> sendPhotoVideoMessage(uris));
-                }
-
-                @Override
-                public void onError(int errorCode, String errorMessage) {}
-            });
-    }
-
-    private void sendPhotoVideoMessage(List<Uri> uris) {
-        List<TUIMessageBean> messageBeans = new ArrayList<>();
-        for (Uri data : uris) {
-            TUIChatLog.i(TAG, "onSuccess: " + data);
-            if (data == null) {
-                TUIChatLog.e(TAG, "data is null");
-                continue;
-            }
-
-            String uri = data.toString();
-            if (TextUtils.isEmpty(uri)) {
-                TUIChatLog.e(TAG, "uri is empty");
-                continue;
-            }
-            String filePath = FileUtil.getPathFromUri(data);
-            String fileName = FileUtil.getName(filePath);
-            String fileExtension = FileUtil.getFileExtensionFromUrl(fileName);
-            String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
-            if (TextUtils.isEmpty(mimeType)) {
-                TUIChatLog.e(TAG, "mimeType is empty.");
-                continue;
-            }
-            if (mimeType.contains("video")) {
-                if (FileUtil.isFileSizeExceedsLimit(data, GeneralConfig.VIDEO_MAX_SIZE)) {
-                    ToastUtil.toastShortMessage(getResources().getString(com.tencent.qcloud.tuicore.R.string.TUIKitErrorFileTooLarge));
-                    continue;
-                }
-                TUIMessageBean msg = buildVideoMessage(filePath);
-                if (msg == null) {
-                    ToastUtil.toastShortMessage(getResources().getString(R.string.send_failed_file_not_exists));
-                    TUIChatLog.e(TAG, "start send video error data: " + data);
-                } else {
-                    messageBeans.add(msg);
-                }
-            } else if (mimeType.contains("image")) {
-                if (TextUtils.equals(mimeType, "image/gif")) {
-                    if (FileUtil.isFileSizeExceedsLimit(data, GeneralConfig.GIF_IMAGE_MAX_SIZE)) {
-                        ToastUtil.toastShortMessage(getResources().getString(com.tencent.qcloud.tuicore.R.string.TUIKitErrorFileTooLarge));
-                        continue;
-                    }
-                } else {
-                    if (FileUtil.isFileSizeExceedsLimit(data, GeneralConfig.IMAGE_MAX_SIZE)) {
-                        ToastUtil.toastShortMessage(getResources().getString(com.tencent.qcloud.tuicore.R.string.TUIKitErrorFileTooLarge));
-                        continue;
-                    }
-                }
-                TUIMessageBean msg = ChatMessageBuilder.buildImageMessage(filePath);
-                if (msg == null) {
-                    TUIChatLog.e(TAG, "start send image error data: " + data);
-                    ToastUtil.toastShortMessage(getResources().getString(R.string.send_failed_file_not_exists));
-                } else {
-                    messageBeans.add(msg);
-                }
-            } else {
-                TUIChatLog.e(TAG, "Send photo or video failed , invalid mimeType : " + mimeType);
-            }
-        }
-        if (mMessageHandler != null) {
-            mMessageHandler.sendMessages(messageBeans);
-            hideSoftInput();
-        }
-    }
-
-    private TUIMessageBean buildVideoMessage(String videoPath) {
-        android.media.MediaMetadataRetriever mmr = new android.media.MediaMetadataRetriever();
-        try {
-            mmr.setDataSource(videoPath);
-            String sDuration = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_DURATION);
-            Bitmap bitmap = mmr.getFrameAtTime(0, android.media.MediaMetadataRetriever.OPTION_NEXT_SYNC);
-
-            if (bitmap == null) {
-                TUIChatLog.e(TAG, "buildVideoMessage() bitmap is null");
-                return null;
+        AlbumPicker.pickMedia(mInputMoreFragment.getActivity(), new AlbumPickerListener() {
+            @Override
+            public void onFinished(Uri originalUri, Uri transcodeUri) {
+                sendPhotoVideoMessage(originalUri, transcodeUri);
             }
 
-            String bitmapPath = FileUtil.generateImageFilePath();
-            boolean result = FileUtil.saveBitmap(bitmapPath, bitmap);
-            if (!result) {
-                TUIChatLog.e(TAG, "build video message, save bitmap failed.");
-                return null;
+            @Override
+            public void onProgress(Uri originalUri, int progress)  {
+                presenter.updateMessageProgress(originalUri, progress);
             }
-            int imgWidth = bitmap.getWidth();
-            int imgHeight = bitmap.getHeight();
-            long duration = Long.parseLong(sDuration);
-
-            return ChatMessageBuilder.buildVideoMessage(bitmapPath, videoPath, imgWidth, imgHeight, duration);
-        } catch (Exception ex) {
-            TUIChatLog.e(TAG, "MediaMetadataRetriever exception " + ex);
-        } finally {
-            mmr.release();
-        }
-
-        return null;
-    }
 
-    protected void startCaptureCheckPermission() {
-        TUIChatLog.i(TAG, "startCaptureCheckPermission");
-
-        PermissionHelper.requestPermission(PermissionHelper.PERMISSION_CAMERA, new PermissionHelper.PermissionCallback() {
             @Override
-            public void onGranted() {
-                startCapture();
+            public void onOriginalMediaPicked(Uri originalUri) {
+                presenter.addPlaceholderMessage(originalUri);
+                hideSoftInput();
             }
 
             @Override
-            public void onDenied() {
-                TUIChatLog.i(TAG, "startCapture checkPermission failed");
+            public void onCancel() {
+
             }
         });
     }
 
-    private void startCapture() {
-        if (TUIChatConfigs.getGeneralConfig().isUseSystemCamera()) {
-            if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
-                PermissionHelper.requestPermission(PermissionHelper.PERMISSION_STORAGE, new PermissionHelper.PermissionCallback() {
-                    @Override
-                    public void onGranted() {
-                        String path = FileUtil.generateExternalStorageImageFilePath();
-                        systemCaptureAndSend(path);
-                    }
-
-                    @Override
-                    public void onDenied() {
-                        TUIChatLog.i(TAG, "startCapture checkPermission failed");
-                    }
-                });
-            } else {
-                String path = FileUtil.generateImageFilePath();
-                systemCaptureAndSend(path);
-            }
-        } else {
-            chatCaptureAndSend();
-        }
+    private void sendPhotoVideoMessage(Uri uri) {
+        presenter.sendPhotoVideoMessages(uri, null);
+        hideSoftInput();
     }
 
-    private void chatCaptureAndSend() {
-        Bundle bundle = new Bundle();
-        bundle.putInt(TUIChatConstants.CAMERA_TYPE, CameraActivity.BUTTON_STATE_ONLY_CAPTURE);
-        TUICore.startActivityForResult(mInputMoreFragment, CameraActivity.class, bundle, result -> {
-            if (result.getData() != null) {
-                Uri uri = result.getData().getData();
-                if (uri != null) {
-                    sendPhotoVideoMessage(Collections.singletonList(uri));
-                }
-            }
-        });
+    private void sendPhotoVideoMessage(Uri original, Uri transcodeUri) {
+        presenter.sendPhotoVideoMessages(original, transcodeUri);
+        ThreadUtils.runOnUiThread(this::hideSoftInput);
     }
 
-    private void systemCaptureAndSend(String path) {
-        Uri uri = FileUtil.getUriFromPath(path);
-        if (uri == null) {
-            return;
-        }
-        ActivityResultResolver.takePicture(mInputMoreFragment, uri, new TUIValueCallback<Boolean>() {
-            @Override
-            public void onSuccess(Boolean object) {
-                File imageFile = new File(path);
-                if (imageFile.exists()) {
-                    sendPhotoVideoMessage(Collections.singletonList(uri));
-                }
-            }
-
-            @Override
-            public void onError(int errorCode, String errorMessage) {}
-        });
-    }
+    protected void takePhoto() {
+        TUIChatLog.i(TAG, "takePhoto");
 
-    protected void startVideoRecordCheckPermission() {
-        TUIChatLog.i(TAG, "startVideoRecordCheckPermission");
+        VideoRecorder.openCamera(mInputMoreFragment, new TUIValueCallback<Uri>() {
 
-        PermissionHelper.requestPermission(PermissionHelper.PERMISSION_CAMERA, new PermissionHelper.PermissionCallback() {
             @Override
-            public void onGranted() {
-                PermissionHelper.requestPermission(PermissionHelper.PERMISSION_MICROPHONE, new PermissionHelper.PermissionCallback() {
-                    @Override
-                    public void onGranted() {
-                        startVideoRecord();
-                    }
-
-                    @Override
-                    public void onDenied() {
-                        TUIChatLog.i(TAG, "startVideoRecord checkPermission failed");
-                    }
-                });
+            public void onSuccess(Uri uri) {
+                sendPhotoVideoMessage(uri);
             }
 
             @Override
-            public void onDenied() {
-                TUIChatLog.i(TAG, "startVideoRecord checkPermission failed");
+            public void onError(int errorCode, String errorMessage) {
+                TUIChatLog.e(TAG, "takePhoto errorCode: " + errorCode + " errorMessage: " + errorMessage);
             }
         });
     }
 
-    private void startVideoRecord() {
-        if (TUIChatConfigs.getGeneralConfig().isUseSystemCamera()) {
-            if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
-                PermissionHelper.requestPermission(PermissionHelper.PERMISSION_STORAGE, new PermissionHelper.PermissionCallback() {
-                    @Override
-                    public void onGranted() {
-                        String path = FileUtil.generateExternalStorageVideoFilePath();
-                        systemRecordAndSend(path);
-                    }
-
-                    @Override
-                    public void onDenied() {
-                        TUIChatLog.i(TAG, "startVideoRecord checkPermission failed");
-                    }
-                });
-            } else {
-                String path = FileUtil.generateVideoFilePath();
-                systemRecordAndSend(path);
-            }
-        } else {
-            chatRecordAndSend();
-        }
-    }
+    protected void recordVideo() {
+        TUIChatLog.i(TAG, "openVideoRecord");
 
-    private void systemRecordAndSend(String path) {
-        Uri uri = FileUtil.getUriFromPath(path);
-        if (uri == null) {
-            return;
-        }
-        ActivityResultResolver.takeVideo(mInputMoreFragment, uri, new TUIValueCallback<Boolean>() {
+        VideoRecorder.openVideoRecorder(mInputMoreFragment, new TUIValueCallback<Uri>() {
             @Override
-            public void onSuccess(Boolean object) {
-                File videoFile = new File(path);
-                if (videoFile.exists()) {
-                    sendPhotoVideoMessage(Collections.singletonList(uri));
-                }
+            public void onSuccess(Uri uri) {
+                sendPhotoVideoMessage(uri);
             }
 
             @Override
-            public void onError(int errorCode, String errorMessage) {}
-        });
-    }
-
-    private void chatRecordAndSend() {
-        Bundle bundle = new Bundle();
-        bundle.putInt(TUIChatConstants.CAMERA_TYPE, CameraActivity.BUTTON_STATE_ONLY_RECORDER);
-
-        TUICore.startActivityForResult(mInputMoreFragment, CameraActivity.class, bundle, result -> {
-            Intent data = result.getData();
-            if (data == null) {
-                return;
-            }
-            Uri uri = data.getData();
-            if (uri != null) {
-                sendPhotoVideoMessage(Collections.singletonList(uri));
+            public void onError(int errorCode, String errorMessage) {
+                TUIChatLog.i(TAG, "openVideoRecord errorCode: " + errorCode + " errorMessage: " + errorMessage);
             }
         });
     }
@@ -1218,9 +1018,19 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
 
     protected void assembleActions() {
         mInputMoreActionList.clear();
-        InputMoreActionUnit actionUnit;
-        if (TUIChatConfigs.getGeneralConfig().isEnableAlbum() && getChatInfo().isEnableAlbum()) {
-            actionUnit = new InputMoreActionUnit() {
+
+        List<Integer> excludeItems = new ArrayList<>();
+        TUIChatConfigClassic.ChatInputMoreDataSource dataSource = TUIChatConfigClassic.getChatInputMoreDataSource();
+        if (dataSource != null) {
+            excludeItems.addAll(dataSource.inputBarShouldHideItemsInMoreMenuOfInfo(mChatInfo));
+            mInputMoreActionList.addAll(dataSource.inputBarShouldAddNewItemToMoreMenuOfInfo(mChatInfo));
+        }
+
+        InputMoreItem actionUnit;
+        if (TUIChatConfigClassic.isShowInputBarAlbum()
+                && getChatInfo().isEnableAlbum()
+                && !excludeItems.contains(TUIChatConfigClassic.ALBUM)) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
                     startSendPhoto();
@@ -1232,11 +1042,13 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        if (TUIChatConfigs.getGeneralConfig().isEnableTakePhoto() && getChatInfo().isEnableTakePhoto()) {
-            actionUnit = new InputMoreActionUnit() {
+        if (TUIChatConfigClassic.isShowInputBarTakePhoto()
+                && getChatInfo().isEnableTakePhoto()
+                && !excludeItems.contains(TUIChatConfigClassic.TAKE_PHOTO)) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
-                    startCaptureCheckPermission();
+                    takePhoto();
                 }
             };
             actionUnit.setIconResId(R.drawable.ic_more_camera);
@@ -1245,11 +1057,13 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        if (TUIChatConfigs.getGeneralConfig().isEnableRecordVideo() && getChatInfo().isEnableRecordVideo()) {
-            actionUnit = new InputMoreActionUnit() {
+        if (TUIChatConfigClassic.isShowInputBarRecordVideo()
+                && getChatInfo().isEnableRecordVideo()
+                && !excludeItems.contains(TUIChatConfigClassic.RECORD_VIDEO)) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
-                    startVideoRecordCheckPermission();
+                    recordVideo();
                 }
             };
             actionUnit.setIconResId(R.drawable.ic_more_video);
@@ -1258,8 +1072,10 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        if (TUIChatConfigs.getGeneralConfig().isEnableFile() && getChatInfo().isEnableFile()) {
-            actionUnit = new InputMoreActionUnit() {
+        if (TUIChatConfigClassic.isShowInputBarFile()
+                && getChatInfo().isEnableFile()
+                && !excludeItems.contains(TUIChatConfigClassic.FILE)) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
                     startSendFile();
@@ -1271,16 +1087,36 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        List<InputMoreActionUnit> extensionList = getExtensionInputMoreList();
-        mInputMoreActionList.addAll(extensionList);
-        clearCustomActionList();
-        if (mChatLayout != null) {
-            mChatLayout.customizeInputMoreLayout();
+        // Add a welcome prompt with rich text
+        if (TUIChatConfigClassic.isShowInputBarCustom()
+                && getChatInfo().isEnableCustomHelloMessage()
+                && !excludeItems.contains(TUIChatConfigClassic.CUSTOM)) {
+            actionUnit = new InputMoreItem() {};
+            actionUnit.setIconResId(R.drawable.custom);
+            actionUnit.setName(getResources().getString(R.string.test_custom_action));
+            actionUnit.setActionId(CustomHelloMessage.CUSTOM_HELLO_ACTION_ID);
+            actionUnit.setPriority(10);
+            actionUnit.setOnClickListener(actionUnit.new OnActionClickListener() {
+                @Override
+                public void onClick() {
+                    Gson gson = new Gson();
+                    CustomHelloMessage customHelloMessage = new CustomHelloMessage();
+                    customHelloMessage.version = TUIChatConstants.version;
+
+                    String data = gson.toJson(customHelloMessage);
+                    TUIMessageBean info = ChatMessageBuilder.buildCustomMessage(data, customHelloMessage.text, customHelloMessage.text.getBytes());
+                    mChatLayout.sendMessage(info, false);
+                }
+            });
+            mInputMoreActionList.add(actionUnit);
         }
-        mInputMoreActionList.addAll(mInputMoreCustomActionList);
-        Collections.sort(mInputMoreActionList, new Comparator<InputMoreActionUnit>() {
+
+        List<InputMoreItem> extensionList = getExtensionInputMoreList();
+        mInputMoreActionList.addAll(extensionList);
+
+        Collections.sort(mInputMoreActionList, new Comparator<InputMoreItem>() {
             @Override
-            public int compare(InputMoreActionUnit o1, InputMoreActionUnit o2) {
+            public int compare(InputMoreItem o1, InputMoreItem o2) {
                 return o2.getPriority() - o1.getPriority();
             }
         });
@@ -1290,9 +1126,13 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         return getResources().getString(stringID);
     }
 
-    private List<InputMoreActionUnit> getExtensionInputMoreList() {
-        List<InputMoreActionUnit> list = new ArrayList<>();
-
+    private List<InputMoreItem> getExtensionInputMoreList() {
+        List<InputMoreItem> list = new ArrayList<>();
+        List<Integer> excludeItems = new ArrayList<>();
+        TUIChatConfigClassic.ChatInputMoreDataSource dataSource = TUIChatConfigClassic.getChatInputMoreDataSource();
+        if (dataSource != null) {
+            excludeItems.addAll(dataSource.inputBarShouldHideItemsInMoreMenuOfInfo(mChatInfo));
+        }
         Map<String, Object> param = new HashMap<>();
         param.put(TUIConstants.TUIChat.Extension.InputMore.CONTEXT, getContext());
         if (ChatInfo.TYPE_C2C == mChatInfo.getType()) {
@@ -1306,15 +1146,18 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_ROOM, true);
         } else {
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_VIDEO_CALL,
-                !TUIChatConfigs.getGeneralConfig().isEnableVideoCall() || !getChatInfo().isEnableVideoCall());
+                !TUIChatConfigClassic.isShowInputBarVideoCall() || !getChatInfo().isEnableVideoCall()
+                    || excludeItems.contains(TUIChatConfigClassic.VIDEO_CALL));
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_VOICE_CALL,
-                !TUIChatConfigs.getGeneralConfig().isEnableAudioCall() || !getChatInfo().isEnableAudioCall());
+                !TUIChatConfigClassic.isShowInputBarAudioCall() || !getChatInfo().isEnableAudioCall()
+                    || excludeItems.contains(TUIChatConfigClassic.AUDIO_CALL));
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_ROOM,
-                !TUIChatConfigs.getGeneralConfig().isEnableRoomKit() || !getChatInfo().isEnableRoom());
+                !TUIChatConfigClassic.isShowInputBarRoomKit() || !getChatInfo().isEnableRoom() || excludeItems.contains(TUIChatConfigClassic.ROOM_KIT));
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_GROUP_NOTE,
-                !TUIChatConfigs.getGeneralConfig().isEnableGroupNote() || !getChatInfo().isEnableGroupNote());
+                !TUIChatConfigClassic.isShowInputBarGroupNote() || !getChatInfo().isEnableGroupNote()
+                    || excludeItems.contains(TUIChatConfigClassic.GROUP_NOTE));
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_POLL,
-                !TUIChatConfigs.getGeneralConfig().isEnablePoll() || !getChatInfo().isEnablePoll());
+                !TUIChatConfigClassic.isShowInputBarPoll() || !getChatInfo().isEnablePoll() || excludeItems.contains(TUIChatConfigClassic.POLL));
         }
         param.put(TUIConstants.TUIChat.Extension.InputMore.INPUT_MORE_LISTENER, chatInputMoreListener);
         List<TUIExtensionInfo> extensionList = TUICore.getExtensionList(TUIConstants.TUIChat.Extension.InputMore.CLASSIC_EXTENSION_ID, param);
@@ -1323,7 +1166,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
                 String name = extensionInfo.getText();
                 int icon = (int) extensionInfo.getIcon();
                 int priority = extensionInfo.getWeight();
-                InputMoreActionUnit unit = new InputMoreActionUnit() {
+                InputMoreItem unit = new InputMoreItem() {
                     @Override
                     public void onAction(String chatInfoId, int chatType) {
                         TUIExtensionEventListener extensionListener = extensionInfo.getExtensionListener();
@@ -1378,10 +1221,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         mMoreInputEvent = listener;
     }
 
-    public void addAction(InputMoreActionUnit action) {
-        mInputMoreCustomActionList.add(action);
-    }
-
     public EditText getInputText() {
         return mTextInput;
     }
@@ -1456,10 +1295,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         mEmojiInputButton.setVisibility(visibility);
     }
 
-    public void clearCustomActionList() {
-        mInputMoreCustomActionList.clear();
-    }
-
     public ChatInfo getChatInfo() {
         return mChatInfo;
     }

+ 93 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/ShortcutView.java

@@ -0,0 +1,93 @@
+package com.tencent.qcloud.tuikit.tuichat.classicui.widget.input;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import com.tencent.qcloud.tuikit.tuichat.R;
+import com.tencent.qcloud.tuikit.tuichat.config.ShortcutMenuConfig;
+import java.util.List;
+
+public class ShortcutView extends RecyclerView {
+    private LayoutManager layoutManager;
+    private ShortcutAdapter adapter;
+    private List<ShortcutMenuConfig.TUIChatShortcutMenuData> dataList;
+
+    public ShortcutView(@NonNull Context context) {
+        super(context);
+        init(context);
+    }
+
+    public ShortcutView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public ShortcutView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context);
+    }
+
+    private void init(Context context) {
+        layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
+        setLayoutManager(layoutManager);
+        adapter = new ShortcutAdapter();
+        setAdapter(adapter);
+    }
+
+    public void setDataList(List<ShortcutMenuConfig.TUIChatShortcutMenuData> dataList) {
+        this.dataList = dataList;
+        if (adapter != null) {
+            adapter.notifyDataSetChanged();
+        }
+    }
+
+    class ShortcutAdapter extends Adapter<ShortcutViewHolder> {
+        @NonNull
+        @Override
+        public ShortcutViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_input_shortcut_item, parent, false);
+            return new ShortcutViewHolder(view);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull ShortcutViewHolder holder, int position) {
+            ShortcutMenuConfig.TUIChatShortcutMenuData data = dataList.get(position);
+            holder.shortcutView.setText(data.text);
+            if (data.textColor != ShortcutMenuConfig.TUIChatShortcutMenuData.UNDEFINED) {
+                holder.shortcutView.setTextColor(data.textColor);
+            }
+            if (data.textFontSize != ShortcutMenuConfig.TUIChatShortcutMenuData.UNDEFINED) {
+                holder.shortcutView.setTextSize(data.textFontSize);
+            }
+            if (data.background != null) {
+                holder.shortcutView.setBackground(data.background);
+            }
+            holder.shortcutView.setOnClickListener(data.onClickListener);
+        }
+
+        @Override
+        public int getItemCount() {
+            if (dataList != null) {
+                return dataList.size();
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    static class ShortcutViewHolder extends ViewHolder {
+        TextView shortcutView;
+
+        public ShortcutViewHolder(@NonNull View itemView) {
+            super(itemView);
+            shortcutView = (TextView) itemView;
+        }
+    }
+}

+ 4 - 4
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/ActionsGridViewAdapter.java

@@ -9,15 +9,15 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
 
 import java.util.List;
 
 public class ActionsGridViewAdapter extends BaseAdapter {
 
-    private List<InputMoreActionUnit> baseActions;
+    private List<InputMoreItem> baseActions;
 
-    public void setBaseActions(List<InputMoreActionUnit> baseActions) {
+    public void setBaseActions(List<InputMoreItem> baseActions) {
         this.baseActions = baseActions;
     }
 
@@ -41,7 +41,7 @@ public class ActionsGridViewAdapter extends BaseAdapter {
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
-        InputMoreActionUnit action = baseActions.get(position);
+        InputMoreItem action = baseActions.get(position);
         View unitView = action.getUnitView();
         if (unitView != null) {
             return unitView;

+ 5 - 5
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/ActionsPagerAdapter.java

@@ -10,7 +10,7 @@ import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -18,10 +18,10 @@ import java.util.List;
 public class ActionsPagerAdapter extends RecyclerView.Adapter<ActionsPagerAdapter.ActionsViewHolder> {
     private static final int ITEM_COUNT_PER_GRID_VIEW = 8;
     private static final int COLUMN_COUNT = 4;
-    private final List<InputMoreActionUnit> mInputMoreList;
+    private final List<InputMoreItem> mInputMoreList;
     private final int mGridViewCount;
 
-    public ActionsPagerAdapter(List<InputMoreActionUnit> mInputMoreList) {
+    public ActionsPagerAdapter(List<InputMoreItem> mInputMoreList) {
         this.mInputMoreList = new ArrayList<>(mInputMoreList);
         this.mGridViewCount = (mInputMoreList.size() + ITEM_COUNT_PER_GRID_VIEW - 1) / ITEM_COUNT_PER_GRID_VIEW;
     }
@@ -37,7 +37,7 @@ public class ActionsPagerAdapter extends RecyclerView.Adapter<ActionsPagerAdapte
     @Override
     public void onBindViewHolder(@NonNull ActionsViewHolder holder, int position) {
         int end = (position + 1) * ITEM_COUNT_PER_GRID_VIEW > mInputMoreList.size() ? mInputMoreList.size() : (position + 1) * ITEM_COUNT_PER_GRID_VIEW;
-        List<InputMoreActionUnit> subBaseActions = mInputMoreList.subList(position * ITEM_COUNT_PER_GRID_VIEW, end);
+        List<InputMoreItem> subBaseActions = mInputMoreList.subList(position * ITEM_COUNT_PER_GRID_VIEW, end);
 
         GridView gridView = holder.gridView;
         holder.setActions(subBaseActions);
@@ -72,7 +72,7 @@ public class ActionsPagerAdapter extends RecyclerView.Adapter<ActionsPagerAdapte
             gridView.setAdapter(adapter);
         }
 
-        public void setActions(List<InputMoreActionUnit> actions) {
+        public void setActions(List<InputMoreItem> actions) {
             adapter.setBaseActions(actions);
             adapter.notifyDataSetChanged();
         }

+ 3 - 3
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/InputMoreFragment.java

@@ -8,14 +8,14 @@ import android.view.ViewGroup;
 import androidx.annotation.Nullable;
 
 import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.BaseInputFragment;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class InputMoreFragment extends BaseInputFragment {
-    private List<InputMoreActionUnit> mInputMoreList = new ArrayList<>();
+    private List<InputMoreItem> mInputMoreList = new ArrayList<>();
 
     @Nullable
     @Override
@@ -26,7 +26,7 @@ public class InputMoreFragment extends BaseInputFragment {
         return baseView;
     }
 
-    public void setActions(List<InputMoreActionUnit> actions) {
+    public void setActions(List<InputMoreItem> actions) {
         this.mInputMoreList = actions;
     }
 }

+ 2 - 2
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/input/inputmore/InputMoreLayout.java

@@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
 import androidx.viewpager2.widget.ViewPager2;
 
 import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
 
 import java.util.List;
 
@@ -33,7 +33,7 @@ public class InputMoreLayout extends LinearLayout {
         inflate(getContext(), R.layout.chat_inputmore_layout, this);
     }
 
-    public void init(List<InputMoreActionUnit> actions) {
+    public void init(List<InputMoreItem> actions) {
         final ViewPager2 viewPager = findViewById(R.id.viewPager);
         ActionsPagerAdapter adapter = new ActionsPagerAdapter(actions);
         viewPager.setAdapter(adapter);

+ 5 - 5
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/MessageAdapter.java

@@ -5,17 +5,16 @@ import android.view.ViewGroup;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.RecyclerView;
 import com.tencent.qcloud.tuicore.TUILogin;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageBaseHolder;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageContentHolder;
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
 import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.UserFaceUrlCache;
-import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.TipsMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.classicui.ClassicUIService;
 import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.viewholder.MessageHeadHolder;
@@ -24,7 +23,6 @@ import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.IMessageAdapter;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.IMessageRecyclerView;
 import com.tencent.qcloud.tuikit.tuichat.presenter.ChatPresenter;
-import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -46,14 +44,14 @@ public class MessageAdapter extends RecyclerView.Adapter implements IMessageAdap
     private boolean isReplyDetailMode = false;
 
     private ChatPresenter presenter;
-    private BaseFragment fragment;
+    private Fragment fragment;
     private UserFaceUrlCache faceUrlCache;
 
     public void setPresenter(ChatPresenter chatPresenter) {
         this.presenter = chatPresenter;
     }
 
-    public void setFragment(BaseFragment fragment) {
+    public void setFragment(Fragment fragment) {
         this.fragment = fragment;
     }
 
@@ -236,11 +234,13 @@ public class MessageAdapter extends RecyclerView.Adapter implements IMessageAdap
             setItemChecked(messageBean, false);
             return;
         }
+
         if (isItemChecked(messageBean)) {
             setItemChecked(messageBean, false);
         } else {
             setItemChecked(messageBean, true);
         }
+
         onViewNeedRefresh(IMessageRecyclerView.DATA_CHANGE_TYPE_UPDATE, messageBean);
     }
 

+ 103 - 267
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/MessageRecyclerView.java

@@ -2,7 +2,6 @@ package com.tencent.qcloud.tuikit.tuichat.classicui.widget.message;
 
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
 import android.text.TextUtils;
@@ -23,9 +22,8 @@ import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
 import com.tencent.qcloud.tuicore.util.ErrorMessageConverter;
 import com.tencent.qcloud.tuicore.util.ToastUtil;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.SelectTextHelper;
+import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.SelectionHelper;
 import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager;
-import com.tencent.qcloud.tuikit.timcommon.component.MessageProperties;
 import com.tencent.qcloud.tuikit.timcommon.component.dialog.TUIKitDialog;
 import com.tencent.qcloud.tuikit.timcommon.component.interfaces.IUIKitCallback;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.OnChatPopActionClickListener;
@@ -40,10 +38,10 @@ import com.tencent.qcloud.tuikit.tuichat.bean.message.ReplyMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.SoundMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.TextMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.classicui.component.popmenu.ChatPopMenu;
-import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.IMessageLayout;
 import com.tencent.qcloud.tuikit.tuichat.classicui.page.MessageReplyDetailActivity;
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioPlayer;
 import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.IMessageRecyclerView;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.OnEmptySpaceClickListener;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.OnGestureScrollListener;
@@ -60,7 +58,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-public class MessageRecyclerView extends RecyclerView implements IMessageRecyclerView, IMessageLayout {
+public class MessageRecyclerView extends RecyclerView implements IMessageRecyclerView {
     private static final String TAG = MessageRecyclerView.class.getSimpleName();
 
     // Take a large enough offset to scroll to the bottom at one time
@@ -68,7 +66,7 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
     private static final int SOUND_PLAY_DELAYED = 500;
 
     protected OnItemClickListener mOnItemClickListener;
-    protected MessageRecyclerView.OnLoadMoreHandler mHandler;
+    protected MessageRecyclerView.ChatDelegate chatDelegate;
     protected OnEmptySpaceClickListener mEmptySpaceClickListener;
     protected OnGestureScrollListener onGestureScrollListener;
     protected MessageAdapter mAdapter;
@@ -76,7 +74,6 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
     protected List<ChatPopMenu.ChatPopMenuAction> mPopActions = new ArrayList<>();
     protected List<ChatPopMenu.ChatPopMenuAction> mMorePopActions = new ArrayList<>();
     protected OnChatPopActionClickListener mOnPopActionClickListener;
-    private final MessageProperties properties = MessageProperties.getInstance();
 
     private TUIValueCallback downloadSoundCallback;
     private ChatPresenter presenter;
@@ -192,7 +189,7 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
         }
         mChatPopMenu = new ChatPopMenu(getContext());
         mChatPopMenu.setMessageBean(messageInfo);
-        mChatPopMenu.setShowFaces(TUIChatConfigs.getGeneralConfig().isEnablePopMenuEmojiReactAction());
+        mChatPopMenu.setShowFaces(TUIChatConfigClassic.isEnableEmojiReaction());
         mChatPopMenu.setChatPopMenuActionList(mPopActions);
 
         int[] location = new int[2];
@@ -200,7 +197,7 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
         mChatPopMenu.setEmptySpaceClickListener(new OnEmptySpaceClickListener() {
             @Override
             public void onClick() {
-                SelectTextHelper.resetSelected();
+                SelectionHelper.resetSelected();
             }
         });
         mChatPopMenu.show(view, location[1]);
@@ -260,22 +257,14 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
         ChatPopMenu.ChatPopMenuAction replyAction = null;
         ChatPopMenu.ChatPopMenuAction revokeAction = null;
         ChatPopMenu.ChatPopMenuAction deleteAction = null;
-        ChatPopMenu.ChatPopMenuAction groupPinAction = null;
-        boolean textIsAllSelected = true;
-        if (msg instanceof TextMessageBean || msg instanceof QuoteMessageBean) {
-            String selectText = msg.getSelectText();
-            if (!TextUtils.isEmpty(selectText)) {
-                String text = msg.getExtra();
-                if (!text.equals(selectText)) {
-                    textIsAllSelected = false;
-                }
-            }
-        }
+
+        boolean textIsAllSelected = isTextIsAllSelected(msg);
+
         if (msg instanceof SoundMessageBean) {
             speakerModeSwitchAction = new ChatPopMenu.ChatPopMenuAction();
             int actionIcon = R.drawable.pop_menu_speaker;
             String actionName = getContext().getString(R.string.chat_speaker_mode_on_action);
-            boolean isSpeakerMode = TUIChatConfigs.getConfigs().getGeneralConfig().isEnableSoundMessageSpeakerMode();
+            boolean isSpeakerMode = TUIChatConfigs.getGeneralConfig().isEnableSoundMessageSpeakerMode();
             if (isSpeakerMode) {
                 actionIcon = R.drawable.pop_menu_ear;
                 actionName = getContext().getString(R.string.chat_speaker_mode_off_action);
@@ -299,7 +288,7 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
             if (msg.isSelf()) {
                 if (msg.getStatus() != TUIMessageBean.MSG_STATUS_SEND_FAIL) {
                     long timeInterval = TUIChatUtils.getServerTime() - msg.getMessageTime();
-                    if (timeInterval < TUIChatConfigs.getConfigs().getGeneralConfig().getTimeIntervalForMessageRecall()) {
+                    if (timeInterval < TUIChatConfigClassic.getTimeIntervalForAllowedMessageRecall()) {
                         revokeAction = new ChatPopMenu.ChatPopMenuAction();
                         revokeAction.setActionName(getContext().getString(R.string.revoke_action));
                         revokeAction.setActionIcon(R.drawable.pop_menu_revoke);
@@ -335,9 +324,79 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
             }
         }
 
+        ChatPopMenu.ChatPopMenuAction groupPinAction = getChatPopMenuAction(msg);
+        if (groupPinAction != null && TUIChatConfigClassic.isEnablePin()) {
+            groupPinAction.setPriority(3000);
+            mPopActions.add(groupPinAction);
+        }
+
+        if (speakerModeSwitchAction != null && TUIChatConfigClassic.isEnableSpeakerModeSwitch()) {
+            speakerModeSwitchAction.setPriority(11000);
+            mPopActions.add(speakerModeSwitchAction);
+        }
+        if (multiSelectAction != null && TUIChatConfigClassic.isEnableSelect() && !msg.hasRiskContent()) {
+            multiSelectAction.setPriority(8000);
+            mPopActions.add(multiSelectAction);
+        }
+        if (quoteAction != null && TUIChatConfigClassic.isEnableQuote() && !msg.hasRiskContent()) {
+            quoteAction.setPriority(7000);
+            mPopActions.add(quoteAction);
+        }
+        if (replyAction != null && TUIChatConfigClassic.isEnableReply() && !msg.hasRiskContent()) {
+            replyAction.setPriority(6000);
+            mPopActions.add(replyAction);
+        }
+        if (revokeAction != null && TUIChatConfigClassic.isEnableRecall()) {
+            revokeAction.setPriority(5000);
+            mPopActions.add(revokeAction);
+        }
+        if (deleteAction != null && TUIChatConfigClassic.isEnableDelete()) {
+            deleteAction.setPriority(4000);
+            mPopActions.add(deleteAction);
+        }
+
+
+        if (isDefaultMessage(msg)) {
+            if (copyAction != null && TUIChatConfigClassic.isEnableCopy() && !msg.hasRiskContent()) {
+                copyAction.setPriority(10000);
+                mPopActions.add(copyAction);
+            }
+            if (forwardAction != null && TUIChatConfigClassic.isEnableForward()) {
+                forwardAction.setPriority(9000);
+                mPopActions.add(forwardAction);
+            }
+        }
+
+        mPopActions.addAll(mMorePopActions);
+        mPopActions.addAll(getExtensionActions(msg));
+        Collections.sort(mPopActions, new Comparator<ChatPopMenu.ChatPopMenuAction>() {
+            @Override
+            public int compare(ChatPopMenu.ChatPopMenuAction o1, ChatPopMenu.ChatPopMenuAction o2) {
+                return o2.getPriority() - o1.getPriority();
+            }
+        });
+    }
+
+    private static boolean isTextIsAllSelected(TUIMessageBean msg) {
+        boolean textIsAllSelected = true;
+        if (msg instanceof TextMessageBean || msg instanceof QuoteMessageBean) {
+            String selectText = msg.getSelectText();
+            if (!TextUtils.isEmpty(selectText)) {
+                String text = msg.getExtra();
+                if (!text.equals(selectText)) {
+                    textIsAllSelected = false;
+                }
+            }
+        }
+        return textIsAllSelected;
+    }
+
+    private ChatPopMenu.ChatPopMenuAction getChatPopMenuAction(TUIMessageBean msg) {
+        ChatPopMenu.ChatPopMenuAction groupPinAction = null;
+
         if (presenter instanceof GroupChatPresenter && TUIChatConfigs.getGeneralConfig().isEnableGroupChatPinMessage()
-            && ((GroupChatPresenter) presenter).canPinnedMessage() && msg.getStatus() != TUIMessageBean.MSG_STATUS_SEND_FAIL
-            && msg.getStatus() != TUIMessageBean.MSG_STATUS_SENDING) {
+                && ((GroupChatPresenter) presenter).canPinnedMessage() && msg.getStatus() != TUIMessageBean.MSG_STATUS_SEND_FAIL
+                && msg.getStatus() != TUIMessageBean.MSG_STATUS_SENDING && !msg.hasRiskContent()) {
             groupPinAction = new ChatPopMenu.ChatPopMenuAction();
             if (((GroupChatPresenter) presenter).isMessagePinned(msg.getId())) {
                 groupPinAction.setActionName(getContext().getResources().getString(R.string.chat_group_unpin_message));
@@ -379,55 +438,7 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
                 });
             }
         }
-
-        if (speakerModeSwitchAction != null) {
-            speakerModeSwitchAction.setPriority(11000);
-            mPopActions.add(speakerModeSwitchAction);
-        }
-        if (multiSelectAction != null) {
-            multiSelectAction.setPriority(8000);
-            mPopActions.add(multiSelectAction);
-        }
-        if (quoteAction != null && TUIChatConfigs.getConfigs().getGeneralConfig().isEnablePopMenuReferenceAction()) {
-            quoteAction.setPriority(7000);
-            mPopActions.add(quoteAction);
-        }
-        if (replyAction != null && TUIChatConfigs.getConfigs().getGeneralConfig().isEnablePopMenuReplyAction()) {
-            replyAction.setPriority(6000);
-            mPopActions.add(replyAction);
-        }
-        if (revokeAction != null) {
-            revokeAction.setPriority(5000);
-            mPopActions.add(revokeAction);
-        }
-        if (deleteAction != null) {
-            deleteAction.setPriority(4000);
-            mPopActions.add(deleteAction);
-        }
-        if (groupPinAction != null) {
-            groupPinAction.setPriority(3000);
-            mPopActions.add(groupPinAction);
-        }
-
-        if (isDefaultMessage(msg)) {
-            if (copyAction != null) {
-                copyAction.setPriority(10000);
-                mPopActions.add(copyAction);
-            }
-            if (forwardAction != null) {
-                forwardAction.setPriority(9000);
-                mPopActions.add(forwardAction);
-            }
-        }
-
-        mPopActions.addAll(mMorePopActions);
-        mPopActions.addAll(getExtensionActions(msg));
-        Collections.sort(mPopActions, new Comparator<ChatPopMenu.ChatPopMenuAction>() {
-            @Override
-            public int compare(ChatPopMenu.ChatPopMenuAction o1, ChatPopMenu.ChatPopMenuAction o2) {
-                return o2.getPriority() - o1.getPriority();
-            }
-        });
+        return groupPinAction;
     }
 
     private List<ChatPopMenu.ChatPopMenuAction> getExtensionActions(TUIMessageBean messageBean) {
@@ -457,8 +468,8 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
     }
 
     public void displayBackToNewMessage(boolean display, String messageId, int count) {
-        if (mHandler != null) {
-            mHandler.displayBackToNewMessage(display, messageId, count);
+        if (chatDelegate != null) {
+            chatDelegate.displayBackToNewMessage(display, messageId, count);
         }
     }
 
@@ -496,8 +507,8 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
         }
     }
 
-    public void setLoadMoreMessageHandler(OnLoadMoreHandler mHandler) {
-        this.mHandler = mHandler;
+    public void setChatDelegate(ChatDelegate chatDelegate) {
+        this.chatDelegate = chatDelegate;
     }
 
     public void setEmptySpaceClickListener(OnEmptySpaceClickListener mEmptySpaceClickListener) {
@@ -529,6 +540,10 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
                 if (TUIChatUtils.chatEventOnMessageClicked(view, messageBean)) {
                     return;
                 }
+
+                if (chatDelegate != null) {
+                    chatDelegate.hideSoftInput();
+                }
                 if (messageBean instanceof SoundMessageBean) {
                     onSoundMessageClicked((SoundMessageBean) messageBean);
                     return;
@@ -577,6 +592,9 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
                 if (TUIChatUtils.chatEventOnMessageClicked(view, messageInfo)) {
                     return;
                 }
+                if (chatDelegate != null) {
+                    chatDelegate.hideSoftInput();
+                }
                 if (mOnItemClickListener != null) {
                     mOnItemClickListener.onRecallClick(view, messageInfo);
                 }
@@ -769,230 +787,48 @@ public class MessageRecyclerView extends RecyclerView implements IMessageRecycle
         }
     }
 
-    @Override
-    public int getAvatarRadius() {
-        return properties.getAvatarRadius();
-    }
-
-    @Override
-    public void setAvatarRadius(int radius) {
-        properties.setAvatarRadius(radius);
-    }
-
-    @Override
-    public int[] getAvatarSize() {
-        return properties.getAvatarSize();
-    }
-
-    @Override
-    public void setAvatarSize(int[] size) {
-        properties.setAvatarSize(size);
-    }
-
-    @Override
-    public int getAvatar() {
-        return properties.getAvatar();
-    }
-
-    @Override
-    public void setAvatar(int resId) {
-        properties.setAvatar(resId);
-    }
-
-    @Override
-    public Drawable getRightBubble() {
-        return properties.getRightBubble();
-    }
-
-    @Override
-    public void setRightBubble(Drawable bubble) {
-        properties.setRightBubble(bubble);
-    }
-
-    @Override
-    public Drawable getLeftBubble() {
-        return properties.getLeftBubble();
-    }
-
-    @Override
-    public void setLeftBubble(Drawable bubble) {
-        properties.setLeftBubble(bubble);
-    }
-
-    @Override
-    public int getNameFontSize() {
-        return properties.getNameFontSize();
-    }
-
-    @Override
-    public void setNameFontSize(int size) {
-        properties.setNameFontSize(size);
-    }
-
-    @Override
-    public int getNameFontColor() {
-        return properties.getNameFontColor();
-    }
-
-    @Override
-    public void setNameFontColor(int color) {
-        properties.setNameFontColor(color);
-    }
-
-    @Override
-    public int getLeftNameVisibility() {
-        return properties.getLeftNameVisibility();
-    }
-
-    @Override
-    public void setLeftNameVisibility(int visibility) {
-        properties.setLeftNameVisibility(visibility);
-    }
-
-    @Override
-    public int getRightNameVisibility() {
-        return properties.getRightNameVisibility();
-    }
-
-    @Override
-    public void setRightNameVisibility(int visibility) {
-        properties.setRightNameVisibility(visibility);
-    }
-
-    @Override
-    public int getChatContextFontSize() {
-        return properties.getChatContextFontSize();
-    }
-
-    @Override
-    public void setChatContextFontSize(int size) {
-        properties.setChatContextFontSize(size);
-    }
-
-    @Override
-    public int getRightChatContentFontColor() {
-        return properties.getRightChatContentFontColor();
-    }
-
-    @Override
-    public void setRightChatContentFontColor(int color) {
-        properties.setRightChatContentFontColor(color);
-    }
-
-    @Override
-    public int getLeftChatContentFontColor() {
-        return properties.getLeftChatContentFontColor();
-    }
-
-    @Override
-    public void setLeftChatContentFontColor(int color) {
-        properties.setLeftChatContentFontColor(color);
-    }
-
-    @Override
-    public Drawable getTipsMessageBubble() {
-        return properties.getTipsMessageBubble();
-    }
-
-    @Override
-    public void setTipsMessageBubble(Drawable bubble) {
-        properties.setTipsMessageBubble(bubble);
-    }
-
-    @Override
-    public int getTipsMessageFontSize() {
-        return properties.getTipsMessageFontSize();
-    }
-
-    @Override
-    public void setTipsMessageFontSize(int size) {
-        properties.setTipsMessageFontSize(size);
-    }
-
-    @Override
-    public int getTipsMessageFontColor() {
-        return properties.getTipsMessageFontColor();
-    }
-
-    @Override
-    public void setTipsMessageFontColor(int color) {
-        properties.setTipsMessageFontColor(color);
-    }
-
-    @Override
-    public Drawable getChatTimeBubble() {
-        return properties.getChatTimeBubble();
-    }
-
-    @Override
-    public void setChatTimeBubble(Drawable bubble) {
-        properties.setChatTimeBubble(bubble);
-    }
-
-    @Override
-    public int getChatTimeFontSize() {
-        return properties.getChatTimeFontSize();
-    }
-
-    @Override
-    public void setChatTimeFontSize(int size) {
-        properties.setChatTimeFontSize(size);
-    }
-
-    @Override
-    public int getChatTimeFontColor() {
-        return properties.getChatTimeFontColor();
-    }
-
-    @Override
-    public void setChatTimeFontColor(int color) {
-        properties.setChatTimeFontColor(color);
-    }
-
-    @Override
     public OnItemClickListener getOnItemClickListener() {
         return mAdapter.getOnItemClickListener();
     }
 
-    @Override
     public void setOnItemClickListener(OnItemClickListener listener) {
         mOnItemClickListener = listener;
         setAdapterListener();
     }
 
-    @Override
     public void setAdapter(MessageAdapter adapter) {
         super.setAdapter(adapter);
         mAdapter = adapter;
     }
 
-    @Override
     public List<ChatPopMenu.ChatPopMenuAction> getPopActions() {
         return mPopActions;
     }
 
-    @Override
     public void addPopAction(ChatPopMenu.ChatPopMenuAction action) {
         mMorePopActions.add(action);
     }
 
     public void loadMessageFinish() {
-        if (mHandler != null) {
-            mHandler.loadMessageFinish();
+        if (chatDelegate != null) {
+            chatDelegate.loadMessageFinish();
         }
     }
 
     public void scrollMessageFinish() {
-        if (mHandler != null) {
-            mHandler.scrollMessageFinish();
+        if (chatDelegate != null) {
+            chatDelegate.scrollMessageFinish();
         }
     }
 
-    public interface OnLoadMoreHandler {
+    public interface ChatDelegate {
+
         void displayBackToNewMessage(boolean display, String messageId, int count);
 
         void loadMessageFinish();
 
         void scrollMessageFinish();
+
+        void hideSoftInput();
     }
 }

+ 3 - 1
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/reply/ReplyDetailsView.java

@@ -17,6 +17,7 @@ import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
 import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView;
 import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
+import com.tencent.qcloud.tuikit.timcommon.util.TextUtil;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import java.util.ArrayList;
 import java.util.Date;
@@ -89,7 +90,8 @@ public class ReplyDetailsView extends RecyclerView {
             holder.userFaceView.setIconUrls(iconList);
             holder.userNameTv.setText(userName);
             FaceManager.handlerEmojiText(holder.messageText, messageText, false);
-
+            TextUtil.linkifyUrls(holder.messageText);
+            holder.messageText.setActivated(true);
             setBottomContent(messageBean, holder);
         }
 

+ 5 - 7
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/CallingMessageHolder.java

@@ -63,8 +63,8 @@ public class CallingMessageHolder extends TextMessageHolder {
             msgArea.setOnLongClickListener(new View.OnLongClickListener() {
                 @Override
                 public boolean onLongClick(View v) {
-                    if (selectableTextHelper != null) {
-                        selectableTextHelper.selectAll();
+                    if (selectionHelper != null) {
+                        selectionHelper.selectAll();
                     }
                     return true;
                 }
@@ -79,14 +79,12 @@ public class CallingMessageHolder extends TextMessageHolder {
                 }
             };
 
-            msgArea.setOnClickListener(onRecallClickListener);
-            mCallingLayout.setOnClickListener(onRecallClickListener);
-
             if (isForwardMode || isReplyDetailMode) {
                 return;
             }
-            setSelectableTextHelper(msg, msgBodyText, position);
-            msgBodyText.setOnClickListener(onRecallClickListener);
+            msgArea.setOnClickListener(onRecallClickListener);
+            mCallingLayout.setOnClickListener(onRecallClickListener);
+            setTextClickListener(onRecallClickListener);
         }
     }
 }

+ 12 - 4
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/FileMessageHolder.java

@@ -24,6 +24,7 @@ import com.tencent.qcloud.tuicore.util.ErrorMessageConverter;
 import com.tencent.qcloud.tuicore.util.ToastUtil;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageContentHolder;
+import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
 import com.tencent.qcloud.tuikit.timcommon.util.FileUtil;
 import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
@@ -112,19 +113,24 @@ public class FileMessageHolder extends MessageContentHolder {
             setMessageBubbleBackground(R.drawable.chat_bubble_other_cavity_bg);
         } else {
             if (msg.isSelf()) {
-                if (properties.getRightBubble() != null && properties.getRightBubble().getConstantState() != null) {
-                    setMessageBubbleBackground(properties.getRightBubble().getConstantState().newDrawable());
+                Drawable sendBubble = TUIConfigClassic.getSendBubbleBackground();
+                if (sendBubble != null) {
+                    msgArea.setBackground(sendBubble);
                 } else {
                     setMessageBubbleBackground(R.drawable.chat_bubble_self_cavity_bg);
                 }
             } else {
-                if (properties.getLeftBubble() != null && properties.getLeftBubble().getConstantState() != null) {
-                    setMessageBubbleBackground(properties.getLeftBubble().getConstantState().newDrawable());
+                Drawable receiveBubble = TUIConfigClassic.getReceiveBubbleBackground();
+                if (receiveBubble != null) {
+                    msgArea.setBackground(receiveBubble);
                 } else {
                     setMessageBubbleBackground(R.drawable.chat_bubble_other_cavity_bg);
                 }
             }
         }
+        if (!TUIConfigClassic.isEnableMessageBubbleStyle()) {
+            setMessageBubbleBackground(null);
+        }
         normalBackground = getMessageBubbleBackground();
 
         progressListener = new ProgressPresenter.ProgressListener() {
@@ -204,6 +210,7 @@ public class FileMessageHolder extends MessageContentHolder {
                 if (mAdapter != null) {
                     mAdapter.onItemRefresh(message);
                 }
+                TUIChatService.getInstance().refreshMessage(message);
             }
 
             @Override
@@ -213,6 +220,7 @@ public class FileMessageHolder extends MessageContentHolder {
                 if (mAdapter != null) {
                     mAdapter.onItemRefresh(message);
                 }
+                TUIChatService.getInstance().refreshMessage(message);
             }
         };
         ChatFileDownloadPresenter.downloadFile(message, downloadCallback);

+ 20 - 8
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/ImageMessageHolder.java

@@ -11,7 +11,6 @@ import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
-
 import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageContentHolder;
@@ -26,7 +25,6 @@ import com.tencent.qcloud.tuikit.tuichat.component.progress.ChatRingProgressBar;
 import com.tencent.qcloud.tuikit.tuichat.component.progress.ProgressPresenter;
 import com.tencent.qcloud.tuikit.tuichat.presenter.ChatFileDownloadPresenter;
 import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
-
 import java.io.Serializable;
 
 public class ImageMessageHolder extends MessageContentHolder {
@@ -113,9 +111,24 @@ public class ImageMessageHolder extends MessageContentHolder {
         videoDurationText.setVisibility(View.GONE);
         sendingProgress.setVisibility(View.GONE);
 
-        progressListener = this::updateProgress;
+        progressListener = new ProgressPresenter.ProgressListener() {
+            @Override
+            public void onProgress(int progress) {
+                updateProgress(progress, msg);
+            }
+        };
         ProgressPresenter.registerProgressListener(msg.getId(), progressListener);
 
+        if (!msg.isHasReaction()) {
+            setMessageBubbleBackground(null);
+            setMessageBubbleZeroPadding();
+        }
+
+        if (msg.isProcessing()) {
+            GlideEngine.loadCornerImageWithoutPlaceHolder(contentImage, msg.getProcessingThumbnail(), null, DEFAULT_RADIUS);
+            return;
+        }
+
         String imagePath = ChatFileDownloadPresenter.getImagePath(msg);
         if (FileUtil.isFileExists(imagePath)) {
             loadImage(msg, imagePath);
@@ -185,10 +198,6 @@ public class ImageMessageHolder extends MessageContentHolder {
                 return true;
             }
         });
-        if (!msg.isHasReaction()) {
-            setMessageBubbleBackground(null);
-            setMessageBubbleZeroPadding();
-        }
     }
 
     private void loadImage(TUIMessageBean messageBean, String finalImagePath) {
@@ -197,7 +206,10 @@ public class ImageMessageHolder extends MessageContentHolder {
         }
     }
 
-    private void updateProgress(int progress) {
+    private void updateProgress(int progress, TUIMessageBean messageBean) {
+        if (!TextUtils.equals(msgID, messageBean.getId())) {
+            return;
+        }
         progressContainer.setVisibility(View.VISIBLE);
         progressText.setVisibility(View.VISIBLE);
         fileProgressBar.setVisibility(View.VISIBLE);

+ 11 - 4
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/MergeMessageHolder.java

@@ -1,5 +1,6 @@
 package com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.viewholder;
 
+import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -8,6 +9,7 @@ import com.tencent.qcloud.tuicore.TUIThemeManager;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageContentHolder;
 import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
+import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.MergeMessageBean;
 
@@ -45,19 +47,24 @@ public class MergeMessageHolder extends MessageContentHolder {
             statusImage.setVisibility(View.GONE);
         } else {
             if (msg.isSelf()) {
-                if (properties.getRightBubble() != null && properties.getRightBubble().getConstantState() != null) {
-                    setMessageBubbleBackground(properties.getRightBubble().getConstantState().newDrawable());
+                Drawable sendBubble = TUIConfigClassic.getSendBubbleBackground();
+                if (sendBubble != null) {
+                    msgArea.setBackground(sendBubble);
                 } else {
                     setMessageBubbleBackground(R.drawable.chat_bubble_self_cavity_bg);
                 }
             } else {
-                if (properties.getLeftBubble() != null && properties.getLeftBubble().getConstantState() != null) {
-                    setMessageBubbleBackground(properties.getLeftBubble().getConstantState().newDrawable());
+                Drawable receiveBubble = TUIConfigClassic.getSendBubbleBackground();
+                if (receiveBubble != null) {
+                    msgArea.setBackground(receiveBubble);
                 } else {
                     setMessageBubbleBackground(R.drawable.chat_bubble_other_cavity_bg);
                 }
             }
         }
+        if (!TUIConfigClassic.isEnableMessageBubbleStyle()) {
+            setMessageBubbleBackground(null);
+        }
 
         MergeMessageBean messageBean = (MergeMessageBean) msg;
         String title = messageBean.getTitle();

+ 22 - 9
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/QuoteMessageHolder.java

@@ -1,6 +1,7 @@
 package com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.viewholder;
 
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -15,6 +16,7 @@ import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
 import com.tencent.qcloud.tuikit.timcommon.component.impl.GlideEngine;
 import com.tencent.qcloud.tuikit.timcommon.util.FileUtil;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
+import com.tencent.qcloud.tuikit.timcommon.util.TextUtil;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.ImageMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.QuoteMessageBean;
@@ -106,14 +108,6 @@ public class QuoteMessageHolder extends TextMessageHolder {
             quoteContentFrameLayout.setVisibility(View.GONE);
         }
 
-        msgArea.setOnLongClickListener(new View.OnLongClickListener() {
-            @Override
-            public boolean onLongClick(View v) {
-                selectableTextHelper.selectAll();
-                return true;
-            }
-        });
-
         quoteContentFrameLayout.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
@@ -122,11 +116,30 @@ public class QuoteMessageHolder extends TextMessageHolder {
                 }
             }
         });
+
         setThemeColor(msg);
+        TextUtil.linkifyUrls(msgBodyText);
+        msgBodyText.setActivated(true);
+
         if (isForwardMode || isReplyDetailMode) {
             return;
         }
-        setSelectableTextHelper(msg, msgBodyText, position);
+        msgArea.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                selectionHelper.selectAll();
+                return true;
+            }
+        });
+        msgBodyText.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (onItemClickListener != null) {
+                    onItemClickListener.onMessageClick(v, msg);
+                }
+            }
+        });
+        setSelectionHelper(msg, msgBodyText, position);
     }
 
     private void setThemeColor(TUIMessageBean messageBean) {

+ 20 - 15
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/ReplyMessageHolder.java

@@ -2,7 +2,6 @@ package com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.viewholder;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.text.TextUtils;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
@@ -13,6 +12,8 @@ import com.tencent.qcloud.tuikit.timcommon.bean.TUIReplyQuoteBean;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageContentHolder;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.TUIReplyQuoteView;
 import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
+import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
+import com.tencent.qcloud.tuikit.timcommon.util.TextUtil;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.ReplyMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.reply.ImageReplyQuoteBean;
@@ -43,9 +44,6 @@ public class ReplyMessageHolder extends MessageContentHolder {
         originMsgLayout = itemView.findViewById(R.id.origin_msg_abs_layout);
         quoteFrameLayout = itemView.findViewById(R.id.quote_frame_layout);
         line = itemView.findViewById(R.id.reply_line);
-
-        replyContentTv.setTextIsSelectable(true);
-        replyContentTv.setHighlightColor(itemView.getResources().getColor(com.tencent.qcloud.tuikit.timcommon.R.color.timcommon_text_highlight_color));
     }
 
     @Override
@@ -80,11 +78,17 @@ public class ReplyMessageHolder extends MessageContentHolder {
         } else {
             quoteFrameLayout.setVisibility(View.GONE);
         }
+        setThemeColor(msg);
+        replyContentTv.setActivated(true);
+        TextUtil.linkifyUrls(replyContentTv);
+        if (isForwardMode || isReplyDetailMode) {
+            return;
+        }
 
         msgArea.setOnLongClickListener(new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
-                selectableTextHelper.selectAll();
+                selectionHelper.selectAll();
                 return true;
             }
         });
@@ -92,25 +96,26 @@ public class ReplyMessageHolder extends MessageContentHolder {
         msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
-                selectableTextHelper.selectAll();
+                selectionHelper.selectAll();
                 return true;
             }
         });
-
+        replyContentTv.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (onItemClickListener != null) {
+                    onItemClickListener.onMessageClick(v, msg);
+                }
+            }
+        });
         originMsgLayout.setOnLongClickListener(new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
-                selectableTextHelper.selectAll();
+                selectionHelper.selectAll();
                 return true;
             }
         });
-
-        setThemeColor(msg);
-        if (isForwardMode || isReplyDetailMode) {
-            return;
-        }
-
-        setSelectableTextHelper(msg, replyContentTv, position);
+        setSelectionHelper(msg, replyContentTv, position);
     }
 
     @Override

+ 62 - 23
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/TextMessageHolder.java

@@ -1,24 +1,40 @@
 package com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.viewholder;
 
+import android.annotation.SuppressLint;
 import android.text.TextUtils;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.TextView;
+
 import com.tencent.qcloud.tuicore.TUIThemeManager;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageContentHolder;
 import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
+import com.tencent.qcloud.tuikit.timcommon.util.TextUtil;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.TextMessageBean;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
 
 public class TextMessageHolder extends MessageContentHolder {
+
     protected TextView msgBodyText;
+    private View.OnClickListener onTextClickListener;
+    private final GestureDetector gestureDetector;
 
     public TextMessageHolder(View itemView) {
         super(itemView);
         msgBodyText = itemView.findViewById(R.id.msg_body_tv);
-        msgBodyText.setTextIsSelectable(true);
-        msgBodyText.setHighlightColor(itemView.getResources().getColor(com.tencent.qcloud.tuikit.timcommon.R.color.timcommon_text_highlight_color));
+        gestureDetector = new GestureDetector(itemView.getContext(), new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                if (onTextClickListener != null) {
+                    onTextClickListener.onClick(msgBodyText);
+                }
+                return super.onSingleTapUp(e);
+            }
+        });
     }
 
     @Override
@@ -26,6 +42,7 @@ public class TextMessageHolder extends MessageContentHolder {
         return R.layout.message_adapter_content_text;
     }
 
+    @SuppressLint("ClickableViewAccessibility")
     @Override
     public void layoutVariableViews(TUIMessageBean msg, int position) {
         if (!(msg instanceof TextMessageBean)) {
@@ -48,28 +65,17 @@ public class TextMessageHolder extends MessageContentHolder {
 
         msgBodyText.setVisibility(View.VISIBLE);
 
-        if (properties.getChatContextFontSize() != 0) {
-            msgBodyText.setTextSize(properties.getChatContextFontSize());
-        }
-        if (textMessageBean.isSelf()) {
-            if (properties.getRightChatContentFontColor() != 0) {
-                msgBodyText.setTextColor(properties.getRightChatContentFontColor());
-            }
-        } else {
-            if (properties.getLeftChatContentFontColor() != 0) {
-                msgBodyText.setTextColor(properties.getLeftChatContentFontColor());
-            }
-        }
-
         msgArea.setOnLongClickListener(new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
-                if (selectableTextHelper != null) {
-                    selectableTextHelper.selectAll();
+                if (selectionHelper != null) {
+                    selectionHelper.selectAll();
                 }
                 return true;
             }
         });
+        applyCustomConfig();
+
         if (textMessageBean.getText() != null) {
             FaceManager.handlerEmojiText(msgBodyText, textMessageBean.getText(), false);
         } else if (!TextUtils.isEmpty(textMessageBean.getExtra())) {
@@ -77,18 +83,51 @@ public class TextMessageHolder extends MessageContentHolder {
         } else {
             FaceManager.handlerEmojiText(msgBodyText, TUIChatService.getAppContext().getString(R.string.no_support_msg), false);
         }
+        TextUtil.linkifyUrls(msgBodyText);
+        msgBodyText.setActivated(true);
         if (isForwardMode || isReplyDetailMode) {
             return;
         }
-        setSelectableTextHelper(msg, msgBodyText, position);
+        setSelectionHelper(msg, msgBodyText, position);
 
-        msgBodyText.setOnClickListener(new View.OnClickListener() {
+        msgBodyText.setOnTouchListener(new View.OnTouchListener() {
             @Override
-            public void onClick(View v) {
-                if (onItemClickListener != null) {
-                    onItemClickListener.onMessageClick(v, textMessageBean);
-                }
+            public boolean onTouch(View v, MotionEvent event) {
+                return gestureDetector.onTouchEvent(event);
             }
         });
+
+        setTextClickListener((v) -> {
+            if (onItemClickListener != null) {
+                onItemClickListener.onMessageClick(v, msg);
+            }
+        });
+    }
+
+    protected void setTextClickListener(View.OnClickListener listener) {
+        this.onTextClickListener = listener;
+    }
+
+    protected void applyCustomConfig() {
+        if (isLayoutOnStart) {
+            int receiveTextMessageColor = TUIChatConfigClassic.getReceiveTextMessageColor();
+            if (receiveTextMessageColor != TUIChatConfigClassic.UNDEFINED) {
+                msgBodyText.setTextColor(receiveTextMessageColor);
+            }
+            int receiveTextMessageFontSize = TUIChatConfigClassic.getReceiveTextMessageFontSize();
+            if (receiveTextMessageFontSize != TUIChatConfigClassic.UNDEFINED) {
+                msgBodyText.setTextSize(receiveTextMessageFontSize);
+            }
+        } else {
+            int sendTextMessageColor = TUIChatConfigClassic.getSendTextMessageColor();
+            if (sendTextMessageColor != TUIChatConfigClassic.UNDEFINED) {
+                msgBodyText.setTextColor(sendTextMessageColor);
+            }
+            int sendTextMessageFontSize = TUIChatConfigClassic.getSendTextMessageFontSize();
+            if (sendTextMessageFontSize != TUIChatConfigClassic.UNDEFINED) {
+                msgBodyText.setTextSize(sendTextMessageFontSize);
+            }
+        }
     }
+
 }

+ 12 - 8
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/TipsMessageHolder.java

@@ -1,5 +1,6 @@
 package com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.viewholder;
 
+import android.graphics.drawable.Drawable;
 import android.text.Html;
 import android.text.TextUtils;
 import android.view.View;
@@ -11,8 +12,8 @@ import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
 import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
 import com.tencent.qcloud.tuikit.timcommon.classicui.widget.message.MessageBaseHolder;
 import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.TipsMessageBean;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
 import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageParser;
 
 public class TipsMessageHolder extends MessageBaseHolder {
@@ -33,15 +34,18 @@ public class TipsMessageHolder extends MessageBaseHolder {
     @Override
     public void layoutViews(TUIMessageBean msg, int position) {
         super.layoutViews(msg, position);
-
-        if (properties.getTipsMessageBubble() != null) {
-            mChatTipsTv.setBackground(properties.getTipsMessageBubble());
+        Drawable systemMessageBackground = TUIChatConfigClassic.getSystemMessageBackground();
+        if (systemMessageBackground != null) {
+            mChatTipsTv.setBackground(systemMessageBackground);
         }
-        if (properties.getTipsMessageFontColor() != 0) {
-            mChatTipsTv.setTextColor(properties.getTipsMessageFontColor());
+        int systemMessageTextFontColor = TUIChatConfigClassic.getSystemMessageTextColor();
+        if (systemMessageTextFontColor != TUIChatConfigClassic.UNDEFINED) {
+            mChatTipsTv.setTextColor(systemMessageTextFontColor);
         }
-        if (properties.getTipsMessageFontSize() != 0) {
-            mChatTipsTv.setTextSize(properties.getTipsMessageFontSize());
+        int systemMessageTextFontSize = TUIChatConfigClassic.getSystemMessageFontSize();
+        if (systemMessageTextFontSize != TUIChatConfigClassic.UNDEFINED) {
+            mChatTipsTv.setTextSize(systemMessageTextFontSize);
+            mReEditText.setTextSize(systemMessageTextFontSize);
         }
 
         mReEditText.setVisibility(View.GONE);

+ 20 - 14
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/classicui/widget/message/viewholder/VideoMessageHolder.java

@@ -11,7 +11,6 @@ import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
-
 import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
 import com.tencent.qcloud.tuicore.util.ErrorMessageConverter;
 import com.tencent.qcloud.tuicore.util.ToastUtil;
@@ -29,7 +28,6 @@ import com.tencent.qcloud.tuikit.tuichat.component.progress.ChatRingProgressBar;
 import com.tencent.qcloud.tuikit.tuichat.component.progress.ProgressPresenter;
 import com.tencent.qcloud.tuikit.tuichat.presenter.ChatFileDownloadPresenter;
 import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
-
 import java.io.Serializable;
 
 public class VideoMessageHolder extends MessageContentHolder {
@@ -118,6 +116,23 @@ public class VideoMessageHolder extends MessageContentHolder {
         videoPlayBtn.setVisibility(View.VISIBLE);
         videoDurationText.setVisibility(View.VISIBLE);
 
+        progressListener = new ProgressPresenter.ProgressListener() {
+            @Override
+            public void onProgress(int progress) {
+                updateProgress(progress, msg);
+            }
+        };
+        ProgressPresenter.registerProgressListener(msg.getId(), progressListener);
+        if (!msg.isHasReaction()) {
+            setMessageBubbleBackground(null);
+            setMessageBubbleZeroPadding();
+        }
+        if (msg.isProcessing()) {
+            showProgressBar();
+            GlideEngine.loadCornerImageWithoutPlaceHolder(contentImage, msg.getProcessingThumbnail(), null, DEFAULT_RADIUS);
+            return;
+        }
+
         String snapshotPath = ChatFileDownloadPresenter.getVideoSnapshotPath(msg);
         if (FileUtil.isFileExists(snapshotPath)) {
             loadSnapshotImage(msg, snapshotPath);
@@ -155,14 +170,6 @@ public class VideoMessageHolder extends MessageContentHolder {
             showProgressBar();
         }
 
-        progressListener = new ProgressPresenter.ProgressListener() {
-            @Override
-            public void onProgress(int progress) {
-                updateProgress(progress, msg);
-            }
-        };
-        ProgressPresenter.registerProgressListener(msg.getId(), progressListener);
-
         if (isMultiSelectMode) {
             msgContentFrame.setOnClickListener(new View.OnClickListener() {
                 @Override
@@ -222,10 +229,6 @@ public class VideoMessageHolder extends MessageContentHolder {
                 }
             }
         });
-        if (!msg.isHasReaction()) {
-            setMessageBubbleBackground(null);
-            setMessageBubbleZeroPadding();
-        }
     }
 
     private void loadSnapshotImage(TUIMessageBean messageBean, String snapshotPath) {
@@ -243,6 +246,9 @@ public class VideoMessageHolder extends MessageContentHolder {
     }
 
     private void updateProgress(int progress, VideoMessageBean messageBean) {
+        if (!TextUtils.equals(messageBean.getId(), msgID)) {
+            return;
+        }
         progressContainer.setVisibility(View.VISIBLE);
         fileProgressBar.setVisibility(View.VISIBLE);
         fileProgressBar.setProgress(progress);

+ 27 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/AlbumPicker.java

@@ -0,0 +1,27 @@
+package com.tencent.qcloud.tuikit.tuichat.component.album;
+
+import androidx.activity.result.ActivityResultCaller;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.AlbumPickerListener;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.IAlbumPicker;
+
+public class AlbumPicker {
+    private static final String TAG = AlbumPicker.class.getSimpleName();
+    private static final AlbumPicker INSTANCE = new AlbumPicker();
+    private final IAlbumPicker defaultAlbumPicker = new SystemAlbumPickerImpl();
+    private IAlbumPicker advancedAlbumPicker;
+
+    private AlbumPicker() {}
+
+    public static void registerAdvancedAlbumPicker(IAlbumPicker albumPicker) {
+        INSTANCE.advancedAlbumPicker = albumPicker;
+    }
+
+    public static void pickMedia(ActivityResultCaller activityResultCaller, AlbumPickerListener listener) {
+        IAlbumPicker albumPicker = INSTANCE.defaultAlbumPicker;
+        if (INSTANCE.advancedAlbumPicker != null) {
+            albumPicker = INSTANCE.advancedAlbumPicker;
+            //albumPicker.setConfig("{\"theme_color\": \"#FFFF0000\"}");
+        }
+        albumPicker.pickMedia(activityResultCaller, listener);
+    }
+}

+ 54 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/ChatMultimediaRecorderImpl.java

@@ -0,0 +1,54 @@
+package com.tencent.qcloud.tuikit.tuichat.component.album;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import androidx.activity.result.ActivityResultCaller;
+import com.tencent.qcloud.tuicore.TUICore;
+import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
+import com.tencent.qcloud.tuikit.tuichat.component.camera.CameraActivity;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.IMultimediaRecorder;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.MultimediaRecorderListener;
+
+class ChatMultimediaRecorderImpl extends IMultimediaRecorder {
+    @Override
+    public void openRecorder(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
+        if (listener == null) {
+            return;
+        }
+        Bundle bundle = new Bundle();
+        bundle.putInt(TUIChatConstants.CAMERA_TYPE, CameraActivity.BUTTON_STATE_ONLY_RECORDER);
+
+        TUICore.startActivityForResult(activityResultCaller, CameraActivity.class, bundle, result -> {
+            Intent data = result.getData();
+            if (data == null) {
+                listener.onFailed(-1, "record failed");
+                return;
+            }
+            Uri uri = data.getData();
+            if (uri != null) {
+                listener.onSuccess(uri);
+                return;
+            }
+            listener.onFailed(-1, "record failed");
+        });
+    }
+
+    public void openCamera(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
+        if (listener == null) {
+            return;
+        }
+        Bundle bundle = new Bundle();
+        bundle.putInt(TUIChatConstants.CAMERA_TYPE, CameraActivity.BUTTON_STATE_ONLY_CAPTURE);
+        TUICore.startActivityForResult(activityResultCaller, CameraActivity.class, bundle, result -> {
+            if (result.getData() != null) {
+                Uri uri = result.getData().getData();
+                if (uri != null) {
+                    listener.onSuccess(uri);
+                    return;
+                }
+            }
+            listener.onFailed(-1, "take photo failed");
+        });
+    }
+}

+ 32 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/SystemAlbumPickerImpl.java

@@ -0,0 +1,32 @@
+package com.tencent.qcloud.tuikit.tuichat.component.album;
+
+import android.net.Uri;
+import androidx.activity.result.ActivityResultCaller;
+import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
+import com.tencent.qcloud.tuikit.timcommon.util.ActivityResultResolver;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.AlbumPickerListener;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.IAlbumPicker;
+import java.util.List;
+
+class SystemAlbumPickerImpl extends IAlbumPicker {
+    @Override
+    public void pickMedia(ActivityResultCaller activityResultCaller, AlbumPickerListener listener) {
+        if (activityResultCaller == null || listener == null) {
+            return;
+        }
+        ActivityResultResolver.getMultipleContent(activityResultCaller,
+            new String[] {ActivityResultResolver.CONTENT_TYPE_IMAGE, ActivityResultResolver.CONTENT_TYPE_VIDEO}, new TUIValueCallback<List<Uri>>() {
+                @Override
+                public void onSuccess(List<Uri> uris) {
+                    for (Uri uri : uris) {
+                        listener.onFinished(uri, null);
+                    }
+                }
+
+                @Override
+                public void onError(int errorCode, String errorMessage) {
+                    listener.onCancel();
+                }
+            });
+    }
+}

+ 115 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/SystemMultimediaRecorderImpl.java

@@ -0,0 +1,115 @@
+package com.tencent.qcloud.tuikit.tuichat.component.album;
+
+import android.net.Uri;
+import android.os.Build;
+import androidx.activity.result.ActivityResultCaller;
+import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
+import com.tencent.qcloud.tuicore.util.TUIBuild;
+import com.tencent.qcloud.tuikit.timcommon.util.ActivityResultResolver;
+import com.tencent.qcloud.tuikit.timcommon.util.FileUtil;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.IMultimediaRecorder;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.MultimediaRecorderListener;
+import com.tencent.qcloud.tuikit.tuichat.util.PermissionHelper;
+import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
+import java.io.File;
+
+class SystemMultimediaRecorderImpl extends IMultimediaRecorder {
+    private static final String TAG = "SystemVideoRecorder";
+
+    @Override
+    public void openRecorder(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
+        if (listener == null) {
+            return;
+        }
+        if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
+            PermissionHelper.requestPermission(PermissionHelper.PERMISSION_STORAGE, new PermissionHelper.PermissionCallback() {
+                @Override
+                public void onGranted() {
+                    String path = FileUtil.generateExternalStorageVideoFilePath();
+                    recordVideo(activityResultCaller, path, listener);
+                }
+
+                @Override
+                public void onDenied() {
+                    listener.onFailed(-1, "STORAGE permission denied");
+                    TUIChatLog.i(TAG, "startVideoRecord checkPermission failed");
+                }
+            });
+        } else {
+            String path = FileUtil.generateVideoFilePath();
+            recordVideo(activityResultCaller, path, listener);
+        }
+    }
+
+    private void recordVideo(ActivityResultCaller activityResultCaller, String path, MultimediaRecorderListener listener) {
+        Uri uri = FileUtil.getUriFromPath(path);
+        if (uri == null) {
+            listener.onFailed(-1, "record failed, uri is null");
+            return;
+        }
+        ActivityResultResolver.takeVideo(activityResultCaller, uri, new TUIValueCallback<Boolean>() {
+            @Override
+            public void onSuccess(Boolean object) {
+                File videoFile = new File(path);
+                if (videoFile.exists()) {
+                    listener.onSuccess(uri);
+                    return;
+                }
+                listener.onFailed(-1, "record failed, file not exists");
+            }
+
+            @Override
+            public void onError(int errorCode, String errorMessage) {
+                listener.onFailed(errorCode, errorMessage);
+            }
+        });
+    }
+
+
+    public void openCamera(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
+        if (listener == null) {
+            return;
+        }
+        if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
+            PermissionHelper.requestPermission(PermissionHelper.PERMISSION_STORAGE, new PermissionHelper.PermissionCallback() {
+                @Override
+                public void onGranted() {
+                    String path = FileUtil.generateExternalStorageImageFilePath();
+                    systemCapture(activityResultCaller, path, listener);
+                }
+
+                @Override
+                public void onDenied() {
+                    listener.onFailed(-1, "take photo failed, checkPermission failed");
+                    TUIChatLog.i(TAG, "startCapture checkPermission failed");
+                }
+            });
+        } else {
+            String path = FileUtil.generateImageFilePath();
+            systemCapture(activityResultCaller, path, listener);
+        }
+    }
+
+    private void systemCapture(ActivityResultCaller activityResultCaller, String path, MultimediaRecorderListener listener) {
+        Uri uri = FileUtil.getUriFromPath(path);
+        if (uri == null) {
+            return;
+        }
+        ActivityResultResolver.takePicture(activityResultCaller, uri, new TUIValueCallback<Boolean>() {
+            @Override
+            public void onSuccess(Boolean object) {
+                File imageFile = new File(path);
+                if (imageFile.exists()) {
+                    listener.onSuccess(uri);
+                } else {
+                    listener.onFailed(-1, "take photo failed, file not exist");
+                }
+            }
+
+            @Override
+            public void onError(int errorCode, String errorMessage) {
+                listener.onFailed(errorCode, errorMessage);
+            }
+        });
+    }
+}

+ 108 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/album/VideoRecorder.java

@@ -0,0 +1,108 @@
+package com.tencent.qcloud.tuikit.tuichat.component.album;
+
+import android.net.Uri;
+import androidx.activity.result.ActivityResultCaller;
+import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.IMultimediaRecorder;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.MultimediaRecorderListener;
+import com.tencent.qcloud.tuikit.tuichat.util.PermissionHelper;
+import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
+
+public class VideoRecorder {
+    private static final String TAG = "VideoRecorder";
+
+    private static final VideoRecorder INSTANCE = new VideoRecorder();
+
+    private final IMultimediaRecorder defaultVideoRecorder = new ChatMultimediaRecorderImpl();
+    private final IMultimediaRecorder systemVideoRecorder = new SystemMultimediaRecorderImpl();
+    private IMultimediaRecorder advancedVideoRecorder;
+
+    private VideoRecorder() {}
+
+    public static void registerAdvancedVideoRecorder(IMultimediaRecorder videoRecorder) {
+        INSTANCE.advancedVideoRecorder = videoRecorder;
+    }
+
+    public static void openVideoRecorder(ActivityResultCaller activityResultCaller, TUIValueCallback<Uri> callback) {
+        IMultimediaRecorder videoRecorder = INSTANCE.defaultVideoRecorder;
+        if (INSTANCE.advancedVideoRecorder != null) {
+            videoRecorder = INSTANCE.advancedVideoRecorder;
+            //TUIMultimediaPlugin.setConfig("{\"theme_color\": \"#FFFFFF00\",\"max_record_duration_ms\": \"15000\"}");
+        } else {
+            if (TUIChatConfigClassic.isUseSystemCamera()) {
+                videoRecorder = INSTANCE.systemVideoRecorder;
+            }
+        }
+
+        IMultimediaRecorder finalVideoRecorder = videoRecorder;
+        PermissionHelper.requestPermission(PermissionHelper.PERMISSION_CAMERA, new PermissionHelper.PermissionCallback() {
+            @Override
+            public void onGranted() {
+                PermissionHelper.requestPermission(PermissionHelper.PERMISSION_MICROPHONE, new PermissionHelper.PermissionCallback() {
+                    @Override
+                    public void onGranted() {
+                        finalVideoRecorder.openRecorder(activityResultCaller, new MultimediaRecorderListener() {
+                            @Override
+                            public void onSuccess(Uri uri) {
+                                TUIValueCallback.onSuccess(callback, uri);
+                            }
+
+                            @Override
+                            public void onFailed(int errorCode, String errorMessage) {
+                                TUIValueCallback.onError(callback, errorCode, errorMessage);
+                            }
+                        });
+                    }
+
+                    @Override
+                    public void onDenied() {
+                        TUIValueCallback.onError(callback, -1, "Microphone permission denied");
+                        TUIChatLog.e(TAG, "openVideoRecorder checkPermission failed, Microphone permission denied");
+                    }
+                });
+            }
+
+            @Override
+            public void onDenied() {
+                TUIValueCallback.onError(callback, -1, "camera permission denied");
+                TUIChatLog.e(TAG, "openVideoRecorder checkPermission failed, camera permission denied");
+            }
+        });
+    }
+
+    public static void openCamera(ActivityResultCaller activityResultCaller, TUIValueCallback<Uri> callback) {
+        IMultimediaRecorder videoRecorder = INSTANCE.defaultVideoRecorder;
+        if (INSTANCE.advancedVideoRecorder != null) {
+            videoRecorder = INSTANCE.advancedVideoRecorder;
+        } else {
+            if (TUIChatConfigClassic.isUseSystemCamera()) {
+                videoRecorder = INSTANCE.systemVideoRecorder;
+            }
+        }
+
+        IMultimediaRecorder finalVideoRecorder = videoRecorder;
+        PermissionHelper.requestPermission(PermissionHelper.PERMISSION_CAMERA, new PermissionHelper.PermissionCallback() {
+            @Override
+            public void onGranted() {
+                finalVideoRecorder.openCamera(activityResultCaller, new MultimediaRecorderListener() {
+                    @Override
+                    public void onSuccess(Uri uri) {
+                        TUIValueCallback.onSuccess(callback, uri);
+                    }
+
+                    @Override
+                    public void onFailed(int errorCode, String errorMessage) {
+                        TUIValueCallback.onError(callback, errorCode, errorMessage);
+                    }
+                });
+            }
+
+            @Override
+            public void onDenied() {
+                TUIChatLog.e(TAG, "camera permission denied");
+                TUIValueCallback.onError(callback, -1, "camera permission denied");
+            }
+        });
+    }
+}

+ 0 - 6
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/audio/AudioPlayer.java

@@ -66,11 +66,6 @@ public class AudioPlayer {
         }
     }
 
-    public void resetSpeakerMode() {
-        AudioManager audioManager = (AudioManager) TUIChatService.getAppContext().getSystemService(Context.AUDIO_SERVICE);
-        audioManager.setMode(AudioManager.MODE_NORMAL);
-    }
-
     public void stopPlay() {
         stopInternalPlay();
         onPlayCompleted(false);
@@ -96,7 +91,6 @@ public class AudioPlayer {
         if (mPlayCallback != null) {
             mPlayCallback.onCompletion(success);
         }
-        resetSpeakerMode();
         mPlayer = null;
     }
 

+ 1 - 1
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/component/progress/ProgressPresenter.java

@@ -64,7 +64,7 @@ public class ProgressPresenter {
         }
 
         List<WeakReference<ProgressListener>> list = ProgressPresenter.getInstance().progressListenerMap.get(progressId);
-        if (list != null) {
+        if (list == null) {
             return;
         }
         WeakReference<ProgressListener> remove = null;

+ 7 - 259
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/GeneralConfig.java

@@ -6,7 +6,6 @@ import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
 public class GeneralConfig {
     public static final int DEFAULT_AUDIO_RECORD_MAX_TIME = 60;
     public static final int DEFAULT_VIDEO_RECORD_MAX_TIME = 15;
-    public static final int DEFAULT_MESSAGE_RECALL_TIME_INTERVAL = 120;
     public static final int FILE_MAX_SIZE = 100 * 1024 * 1024;
     public static final int VIDEO_MAX_SIZE = 100 * 1024 * 1024;
     public static final int IMAGE_MAX_SIZE = 28 * 1024 * 1024;
@@ -15,39 +14,15 @@ public class GeneralConfig {
     private int audioRecordMaxTime = DEFAULT_AUDIO_RECORD_MAX_TIME;
     private int videoRecordMaxTime = DEFAULT_VIDEO_RECORD_MAX_TIME;
 
-    private boolean useSystemCamera = false;
-
     private boolean excludedFromUnreadCount;
     private boolean excludedFromLastMessage;
-
     private boolean enableAndroidPrivateRing;
-    private boolean enableTypingStatus = true;
-
     private boolean msgNeedReadReceipt = false;
-    private boolean enablePopMenuEmojiReactAction = true;
-    private boolean enablePopMenuReplyAction = true;
-    private boolean enablePopMenuReferenceAction = true;
-
-    private boolean enableWelcomeCustomMessage = true;
-
-    private boolean enableAudioCall = true;
-    private boolean enableVideoCall = true;
-    private boolean enableRoomKit = true;
-    private boolean enablePoll = true;
-    private boolean enableGroupNote = true;
-    private boolean enableTakePhoto = true;
-    private boolean enableRecordVideo = true;
-    private boolean enableFile = true;
-    private boolean enableAlbum = true;
     private boolean enableGroupChatPinMessage = true;
-
     private boolean enableFloatWindowForCall = true;
     private boolean enableMultiDeviceForCall = false;
     private boolean enableIncomingBanner = true;
-
-    private int timeIntervalForMessageRecall = DEFAULT_MESSAGE_RECALL_TIME_INTERVAL;
-
-    private boolean enableMainPageInputBar = true;
+    private boolean enableVirtualBackgroundForCall = false;
 
     private boolean enableSoundMessageSpeakerMode = true;
 
@@ -94,171 +69,17 @@ public class GeneralConfig {
     }
 
     /**
-     *  Set in the chat interface, long press the pop-up box to display the emoji interactive message function or not, the default is true
-     */
-    public void setEnablePopMenuEmojiReactAction(boolean enablePopMenuEmojiReactAction) {
-        this.enablePopMenuEmojiReactAction = enablePopMenuEmojiReactAction;
-    }
-
-    /**
-     *  Get chat long press whether to display emoji interactive message function
+     * Set whether to enable the virtual background for audio and video calls, default value is false
      */
-    public boolean isEnablePopMenuEmojiReactAction() {
-        return enablePopMenuEmojiReactAction;
+    public void setEnableVirtualBackgroundForCall(boolean enableVirtualBackgroundForCall) {
+        this.enableVirtualBackgroundForCall = enableVirtualBackgroundForCall;
     }
 
     /**
-     *  Set chat long press the pop-up box to display the message reply function entry or not, the default is true
+     * Obtain whether to enable the virtual background for audio and video calls
      */
-    public void setEnablePopMenuReplyAction(boolean enablePopMenuReplyAction) {
-        this.enablePopMenuReplyAction = enablePopMenuReplyAction;
-    }
-
-    /**
-     *  Obtain whether to display the message reply function entry in the chat long press pop-up box
-     */
-    public boolean isEnablePopMenuReplyAction() {
-        return enablePopMenuReplyAction;
-    }
-
-    /**
-     *  Set chat long press the pop-up box to display the entry of the message reference function or not, the default is true
-     */
-    public void setEnablePopMenuReferenceAction(boolean enablePopMenuReferenceAction) {
-        this.enablePopMenuReferenceAction = enablePopMenuReferenceAction;
-    }
-
-    /**
-     *  Obtain whether the chat long press pop-up box displays the message reference function entry
-     */
-    public boolean isEnablePopMenuReferenceAction() {
-        return enablePopMenuReferenceAction;
-    }
-
-    /**
-     *  Set display the video call button or not, if the TUICallKit component is integrated, the default is true
-     */
-    public void setEnableVideoCall(boolean enableVideoCall) {
-        this.enableVideoCall = enableVideoCall;
-    }
-
-    /**
-     *  Get whether to display the video call button
-     */
-    public boolean isEnableVideoCall() {
-        return enableVideoCall;
-    }
-
-    /**
-     *  Whether to display the audio call button, if the TUICallKit component is integrated, the default is true
-     */
-    public void setEnableAudioCall(boolean enableAudioCall) {
-        this.enableAudioCall = enableAudioCall;
-    }
-
-    /**
-     *  Get whether to display the audio call button
-     */
-    public boolean isEnableAudioCall() {
-        return enableAudioCall;
-    }
-
-    /**
-     *  Set whether to display the quick meeting button, if the TUIRoomKit component is integrated, it will be displayed by default
-     */
-    public void setEnableRoomKit(boolean enableRoomKit) {
-        this.enableRoomKit = enableRoomKit;
-    }
-
-    /**
-     * Get whether to display the quick meeting button
-     */
-    public boolean isEnableRoomKit() {
-        return enableRoomKit;
-    }
-
-    /**
-     *  Set whether to display the group note button. If the TUIGroupNotePlugin is integrated, it is displayed by default.
-     */
-    public void setEnableGroupNote(boolean enableGroupNote) {
-        this.enableGroupNote = enableGroupNote;
-    }
-
-    /**
-     * Get whether to display the group note button
-     */
-    public boolean isEnableGroupNote() {
-        return enableGroupNote;
-    }
-
-    /**
-     *  Set whether to display the group voting button. If the TUIPollPlugin component is integrated, it is displayed by default.
-     */
-    public void setEnablePoll(boolean enablePoll) {
-        this.enablePoll = enablePoll;
-    }
-
-    /**
-     * Get whether to display the group voting button
-     */
-    public boolean isEnablePoll() {
-        return enablePoll;
-    }
-
-    /**
-     *  Set whether to display the album button. it is displayed by default.
-     */
-    public void setEnableAlbum(boolean enableAlbum) {
-        this.enableAlbum = enableAlbum;
-    }
-
-    /**
-     * Get whether to display the album button
-     */
-    public boolean isEnableAlbum() {
-        return enableAlbum;
-    }
-
-    /**
-     *  Set whether to display the file button. it is displayed by default.
-     */
-    public void setEnableFile(boolean enableFile) {
-        this.enableFile = enableFile;
-    }
-
-    /**
-     * Get whether to display the file button
-     */
-    public boolean isEnableFile() {
-        return enableFile;
-    }
-
-    /**
-     *  Set whether to display the video recording button. it is displayed by default.
-     */
-    public void setEnableRecordVideo(boolean enableRecordVideo) {
-        this.enableRecordVideo = enableRecordVideo;
-    }
-
-    /**
-     * Get whether to display the video recording button
-     */
-    public boolean isEnableRecordVideo() {
-        return enableRecordVideo;
-    }
-
-    /**
-     *  Set whether to display the photo button. it is displayed by default.
-     */
-    public void setEnableTakePhoto(boolean enableTakePhoto) {
-        this.enableTakePhoto = enableTakePhoto;
-    }
-
-    /**
-     * Get whether to display the photo button
-     */
-    public boolean isEnableTakePhoto() {
-        return enableTakePhoto;
+    public boolean isEnableVirtualBackgroundForCall() {
+        return enableVirtualBackgroundForCall;
     }
 
     /**
@@ -348,79 +169,6 @@ public class GeneralConfig {
         this.enableAndroidPrivateRing = ring;
     }
 
-    /**
-     * Set whether the "Typing..." function is enabled
-     */
-    public void setEnableTypingStatus(boolean enableTypingStatus) {
-        this.enableTypingStatus = enableTypingStatus;
-    }
-
-    /**
-     * Get whether the "Typing..." function is enabled
-     */
-    public boolean isEnableTypingStatus() {
-        return enableTypingStatus;
-    }
-
-    /**
-     *  Whether to display a custom welcome message button
-     */
-    public boolean isEnableWelcomeCustomMessage() {
-        return enableWelcomeCustomMessage;
-    }
-
-    /**
-     *  Display custom welcome message button, default true
-     */
-    public void setEnableWelcomeCustomMessage(boolean enableWelcomeCustomMessage) {
-        this.enableWelcomeCustomMessage = enableWelcomeCustomMessage;
-    }
-
-    /**
-     * The time interval for message recall, in seconds, default is 120 seconds. If you want to adjust this configuration, please modify the IM console settings
-     * synchronously.
-     *
-     * https://cloud.tencent.com/document/product/269/38656#.E6.B6.88.E6.81.AF.E6.92.A4.E5.9B.9E.E8.AE.BE.E7.BD.AE
-     */
-    public void setTimeIntervalForMessageRecall(int timeIntervalForMessageRecall) {
-        this.timeIntervalForMessageRecall = timeIntervalForMessageRecall;
-    }
-
-    /**
-     * Get the time interval for message recall, in seconds
-     */
-    public int getTimeIntervalForMessageRecall() {
-        return timeIntervalForMessageRecall;
-    }
-
-    /**
-     * Whether to use the built-in camera of the system to take photos and video
-     */
-    public void setUseSystemCamera(boolean useSystemCamera) {
-        this.useSystemCamera = useSystemCamera;
-    }
-
-    /**
-     * Obtain whether to use the system's built-in camera for the camera and video functions
-     */
-    public boolean isUseSystemCamera() {
-        return useSystemCamera;
-    }
-
-    /**
-     *  Whether the chat page displays "InputBar", the default is true
-     */
-    public void setEnableMainPageInputBar(boolean enableMainPageInputBar) {
-        this.enableMainPageInputBar = enableMainPageInputBar;
-    }
-
-    /**
-     *  Get whether to display the input box in the chat page
-     */
-    public boolean isEnableMainPageInputBar() {
-        return enableMainPageInputBar;
-    }
-
     /**
      * Set whether to use the speaker to play the voice message or use the earpiece to play, set it to true to use the speaker to play,
      * false to use the earpiece to play. By default, the speaker is used for playback.

+ 46 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/ShortcutMenuConfig.java

@@ -0,0 +1,46 @@
+package com.tencent.qcloud.tuikit.tuichat.config;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
+
+import java.util.List;
+
+public class ShortcutMenuConfig {
+
+    private ShortcutMenuConfig() {}
+
+    private static final class TUIChatShortcutMenuConfigHolder {
+        private static final ShortcutMenuConfig INSTANCE = new ShortcutMenuConfig();
+    }
+
+    static ShortcutMenuConfig getInstance() {
+        return TUIChatShortcutMenuConfigHolder.INSTANCE;
+    }
+
+    private ChatShortcutViewDataSource shortcutViewDataSource;
+
+    public static void setShortcutViewDataSource(ChatShortcutViewDataSource shortcutViewDataSource) {
+        getInstance().shortcutViewDataSource = shortcutViewDataSource;
+    }
+
+    public static ChatShortcutViewDataSource getShortcutViewDataSource() {
+        return getInstance().shortcutViewDataSource;
+    }
+
+    public interface ChatShortcutViewDataSource {
+        List<TUIChatShortcutMenuData> itemsInShortcutViewOfInfo(ChatInfo chatInfo);
+
+        Drawable shortcutViewBackgroundOfInfo(ChatInfo chatInfo);
+    }
+
+    public static class  TUIChatShortcutMenuData {
+        public static final int UNDEFINED = -1;
+        public String text;
+        public int textColor = UNDEFINED;
+        public Drawable background;
+        public int textFontSize = UNDEFINED;
+        public View.OnClickListener onClickListener;
+    }
+}

+ 776 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/classicui/TUIChatConfigClassic.java

@@ -0,0 +1,776 @@
+package com.tencent.qcloud.tuikit.tuichat.config.classicui;
+
+import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.annotation.IntDef;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.tencent.qcloud.tuicore.TUIConstants;
+import com.tencent.qcloud.tuicore.TUICore;
+import com.tencent.qcloud.tuikit.timcommon.bean.ChatFace;
+import com.tencent.qcloud.tuikit.timcommon.bean.FaceGroup;
+import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
+import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
+import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
+import com.tencent.qcloud.tuikit.tuichat.config.ShortcutMenuConfig;
+import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.ChatEventListener;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TUIChatConfigClassic {
+    private TUIChatConfigClassic() {}
+
+    private static final class TUIChatConfigClassicHolder {
+        private static final TUIChatConfigClassic INSTANCE = new TUIChatConfigClassic();
+    }
+
+    private static TUIChatConfigClassic getInstance() {
+        return TUIChatConfigClassicHolder.INSTANCE;
+    }
+
+    public static final int UNDEFINED = -1;
+
+    public static final int REPLY = 1;
+    public static final int QUOTE = 2;
+    public static final int EMOJI_REACTION = 3;
+    public static final int PIN = 4;
+    public static final int RECALL = 5;
+    public static final int TRANSLATE = 6;
+    public static final int CONVERT = 7;
+    public static final int FORWARD = 8;
+    public static final int SELECT = 9;
+    public static final int COPY = 10;
+    public static final int DELETE = 11;
+    public static final int SPEAKER_MODE_SWITCH = 12;
+
+    @IntDef({REPLY, QUOTE, EMOJI_REACTION, PIN, RECALL, TRANSLATE, CONVERT, FORWARD, SELECT, COPY, DELETE, SPEAKER_MODE_SWITCH})
+    public @interface LongPressPopMenuItem {}
+
+    public static final int CUSTOM = 1;
+    public static final int RECORD_VIDEO = 2;
+    public static final int TAKE_PHOTO = 3;
+    public static final int ALBUM = 4;
+    public static final int FILE = 5;
+    public static final int AUDIO_CALL = 6;
+    public static final int VIDEO_CALL = 7;
+    public static final int ROOM_KIT = 8;
+    public static final int POLL = 9;
+    public static final int GROUP_NOTE = 10;
+
+    @IntDef({CUSTOM, RECORD_VIDEO, TAKE_PHOTO, ALBUM, FILE, AUDIO_CALL, VIDEO_CALL, ROOM_KIT, POLL, GROUP_NOTE})
+    public @interface InputMoreMenuItem {}
+
+    private boolean enableTypingIndicator = true;
+    private Drawable background;
+    private boolean useSystemCamera;
+    private int timeIntervalForAllowedMessageRecall = 120;
+    private int sendTextMessageColor = UNDEFINED;
+    private int sendTextMessageFontSize = UNDEFINED;
+    private int receiveTextMessageColor = UNDEFINED;
+    private int receiveTextMessageFontSize = UNDEFINED;
+    // message style
+    private Drawable systemMessageBackground;
+    private int systemMessageTextColor = UNDEFINED;
+    private int systemMessageFontSize = UNDEFINED;
+
+    // input bar
+    private boolean showInputBar = true;
+    private boolean hideCustom = false;
+    private boolean hideRecordVideo = false;
+    private boolean hideTakePhoto = false;
+    private boolean hideAlbum = false;
+    private boolean hideFile = false;
+    private boolean hideAudioCall = false;
+    private boolean hideVideoCall = false;
+    private boolean hideRoomKit = false;
+    private boolean hidePoll = false;
+    private boolean hideGroupNote = false;
+    private TUIChatConfigClassic.ChatInputMoreDataSource chatInputMoreDataSource;
+
+    // long press menu
+    private boolean enableReply = true;
+    private boolean enableQuote = true;
+    private boolean enableEmojiReaction = true;
+    private boolean enablePin = true;
+    private boolean enableRecall = true;
+    private boolean enableTranslate = true;
+    private boolean enableConvert = true;
+    private boolean enableForward = true;
+    private boolean enableSelect = true;
+    private boolean enableCopy = true;
+    private boolean enableDelete = true;
+    private boolean enableSpeakerModeSwitch = true;
+
+    /**
+     * Hide the items in the pop-up menu when user presses the message.
+     * @param items The items to be hidden.
+     */
+    public static void hideItemsWhenLongPressMessage(@LongPressPopMenuItem int... items) {
+        for (int i : items) {
+            switch (i) {
+                case REPLY: {
+                    getInstance().enableReply = false;
+                    break;
+                }
+                case QUOTE: {
+                    getInstance().enableQuote = false;
+                    break;
+                }
+                case EMOJI_REACTION: {
+                    getInstance().enableEmojiReaction = false;
+                    break;
+                }
+                case PIN: {
+                    getInstance().enablePin = false;
+                    break;
+                }
+                case RECALL: {
+                    getInstance().enableRecall = false;
+                    break;
+                }
+                case TRANSLATE: {
+                    getInstance().enableTranslate = false;
+                    break;
+                }
+                case CONVERT: {
+                    getInstance().enableConvert = false;
+                    break;
+                }
+                case FORWARD: {
+                    getInstance().enableForward = false;
+                    break;
+                }
+                case SELECT: {
+                    getInstance().enableSelect = false;
+                    break;
+                }
+                case COPY: {
+                    getInstance().enableCopy = false;
+                    break;
+                }
+                case DELETE: {
+                    getInstance().enableDelete = false;
+                    break;
+                }
+                case SPEAKER_MODE_SWITCH: {
+                    getInstance().enableSpeakerModeSwitch = false;
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Get whether to enable the reply button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableReply() {
+        return getInstance().enableReply;
+    }
+
+    /**
+     * Get whether to enable the quote button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableQuote() {
+        return getInstance().enableQuote;
+    }
+
+    /**
+     * Get whether to enable the emoji reaction button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableEmojiReaction() {
+        return getInstance().enableEmojiReaction;
+    }
+
+    /**
+     * Get whether to enable the pin button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnablePin() {
+        return getInstance().enablePin;
+    }
+
+    /**
+     * Get whether to enable the recall button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableRecall() {
+        return getInstance().enableRecall;
+    }
+
+    /**
+     * Get whether to enable the translate button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableTranslate() {
+        return getInstance().enableTranslate;
+    }
+
+    /**
+     * Get whether to enable the voice to text button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableConvert() {
+        return getInstance().enableConvert;
+    }
+
+    /**
+     * Get whether to enable the forward button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableForward() {
+        return getInstance().enableForward;
+    }
+
+    /**
+     * Get whether to enable the select button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableSelect() {
+        return getInstance().enableSelect;
+    }
+
+    /**
+     * Get whether to enable the copy button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableCopy() {
+        return getInstance().enableCopy;
+    }
+
+    /**
+     * Get whether to enable the delete button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableDelete() {
+        return getInstance().enableDelete;
+    }
+
+    /**
+     * Get whether to enable the speaker mode switch button.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableSpeakerModeSwitch() {
+        return getInstance().enableSpeakerModeSwitch;
+    }
+
+    /**
+     * Set event listeners for Chat from external sources, to listen for various events in Chat and respond accordingly, such as
+     * listening for avatar click events, long-press message events, etc.
+     */
+    public static void setChatEventListener(ChatEventListener chatEventListener) {
+        TUIChatConfigs.getChatEventConfig().setChatEventListener(chatEventListener);
+    }
+
+    /**
+     *  Enable the display "Alice is typing..." on one-to-one chat interface.
+     *  The default value is true.
+     *  This configuration takes effect in all one-to-one chat message list interfaces.
+     */
+    public static void setEnableTypingIndicator(boolean enableTypingIndicator) {
+        getInstance().enableTypingIndicator = enableTypingIndicator;
+    }
+
+    /**
+     * Get whether to enable the display "Alice is typing..." on one-to-one chat interface.
+     * @return
+     */
+    public static boolean isEnableTypingIndicator() {
+        return getInstance().enableTypingIndicator;
+    }
+
+    /**
+     * Set the background of the Chat interface.
+     * @param background
+     */
+    public static void setBackground(Drawable background) {
+        getInstance().background = background;
+    }
+
+
+    /**
+     * Get the background of the Chat interface.
+     * @return
+     */
+    public static Drawable getBackground() {
+        return newDrawable(getInstance().background);
+    }
+
+    /**
+     * Set the top view of the Chat interface.
+     * @param customTopView
+     */
+    public static void setCustomTopView(View customTopView) {
+        TUIChatConfigs.getNoticeLayoutConfig().setCustomNoticeLayout(customTopView);
+    }
+
+    /**
+     * Set whether to use the system camera. The default is false.
+     * @param useSystemCamera
+     */
+    public static void setUseSystemCamera(boolean useSystemCamera) {
+        getInstance().useSystemCamera = useSystemCamera;
+    }
+
+    /**
+     * Get whether to use the system camera.
+     * @return true if use system camera, false otherwise.
+     */
+    public static boolean isUseSystemCamera() {
+        return getInstance().useSystemCamera;
+    }
+
+    /**
+     * Set whether to play the sound message via the speaker by default. The default is true.
+     * @param playingSoundMessageViaSpeakerByDefault
+     */
+    public static void setPlayingSoundMessageViaSpeakerByDefault(boolean playingSoundMessageViaSpeakerByDefault) {
+        TUIChatConfigs.getGeneralConfig().setEnableSoundMessageSpeakerMode(playingSoundMessageViaSpeakerByDefault);
+    }
+
+    /**
+     * Set whether to exclude the message from the unread count. The default is false.
+     * @param excludedFromUnreadCount
+     */
+    public static void setExcludedFromUnreadCount(boolean excludedFromUnreadCount) {
+        TUIChatConfigs.getGeneralConfig().setExcludedFromUnreadCount(excludedFromUnreadCount);
+    }
+
+    /**
+     * Set whether to exclude the message from the last message. The default is false.
+     * @param excludedFromLastMessage
+     */
+    public static void setExcludedFromLastMessage(boolean excludedFromLastMessage) {
+        TUIChatConfigs.getGeneralConfig().setExcludedFromLastMessage(excludedFromLastMessage);
+    }
+
+    /**
+     * Set the maximum duration of the audio recording. The default is 60 seconds.
+     * @param maxAudioRecordDuration
+     */
+    public static void setMaxAudioRecordDuration(int maxAudioRecordDuration) {
+        TUIChatConfigs.getGeneralConfig().setAudioRecordMaxTime(maxAudioRecordDuration);
+    }
+
+    /**
+     * Set the maximum duration of the video recording. The default is 15 seconds.
+     * @param maxVideoRecordDuration
+     */
+    public static void setMaxVideoRecordDuration(int maxVideoRecordDuration) {
+        TUIChatConfigs.getGeneralConfig().setVideoRecordMaxTime(maxVideoRecordDuration);
+    }
+
+    /**
+     * Set whether to enable the message read receipt. The default is false.
+     * @param messageReadReceiptNeeded
+     */
+    public static void setMessageReadReceiptNeeded(boolean messageReadReceiptNeeded) {
+        TUIChatConfigs.getGeneralConfig().setMsgNeedReadReceipt(messageReadReceiptNeeded);
+    }
+
+    /**
+     * Set the time interval for the allowed message recall. The default is 120 seconds.
+     * @param timeIntervalForAllowedMessageRecall
+     */
+    public static void setTimeIntervalForAllowedMessageRecall(int timeIntervalForAllowedMessageRecall) {
+        getInstance().timeIntervalForAllowedMessageRecall = timeIntervalForAllowedMessageRecall;
+    }
+
+    /**
+     * Get the time interval for the allowed message recall.
+     * @return time interval for the allowed message recall
+     */
+    public static int getTimeIntervalForAllowedMessageRecall() {
+        return getInstance().timeIntervalForAllowedMessageRecall;
+    }
+
+    /**
+     * Set whether to enable the float window for the call. The default is false.
+     * @param enableFloatWindowForCall
+     */
+    public static void setEnableFloatWindowForCall(boolean enableFloatWindowForCall) {
+        TUIChatConfigs.getGeneralConfig().setEnableFloatWindowForCall(enableFloatWindowForCall);
+    }
+
+    /**
+     * Set whether to enable the multi-device for the call. The default is false.
+     * @param enableMultiDeviceForCall
+     */
+    public static void setEnableMultiDeviceForCall(boolean enableMultiDeviceForCall) {
+        TUIChatConfigs.getGeneralConfig().setEnableMultiDeviceForCall(enableMultiDeviceForCall);
+    }
+
+    /**
+     * Set whether to enable the incoming banner. The default is false.
+     * @param enableIncomingBanner
+     */
+    public static void setEnableIncomingBanner(boolean enableIncomingBanner) {
+        TUIChatConfigs.getGeneralConfig().setEnableIncomingBanner(enableIncomingBanner);
+    }
+
+    /**
+     * Set whether to enable the virtual background for the call. The default is false.
+     * @param enableVirtualBackgroundForCall
+     */
+    public static void setEnableVirtualBackgroundForCall(boolean enableVirtualBackgroundForCall) {
+        TUIChatConfigs.getGeneralConfig().setEnableVirtualBackgroundForCall(enableVirtualBackgroundForCall);
+    }
+
+    /**
+     * Set whether to use the Android private ring. The default is false.
+     * @param enableAndroidCustomRing
+     */
+    public static void setEnableAndroidCustomRing(boolean enableAndroidCustomRing) {
+        TUIChatConfigs.getGeneralConfig().setEnableAndroidPrivateRing(enableAndroidCustomRing);
+    }
+
+    /**
+     * Register custom message
+     * @param businessID Custom message businessID (note that it must be unique)
+     * @param messageBeanClass Custom message MessageBean type
+     * @param messageViewHolderClass Custom message MessageViewHolder type
+     * @param isUseEmptyViewGroup Set whether to use an empty layout. By default, an empty layout is not used. If set to use an empty layout, the custom message
+     *     will not display user avatars, message bubbles, and other content.
+     */
+    public static void registerCustomMessage(String businessID, Class<? extends TUIMessageBean> messageBeanClass,
+        Class<? extends RecyclerView.ViewHolder> messageViewHolderClass, boolean isUseEmptyViewGroup) {
+        Map<String, Object> param = new HashMap<>();
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.MESSAGE_BUSINESS_ID, businessID);
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.MESSAGE_BEAN_CLASS, messageBeanClass);
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.MESSAGE_VIEW_HOLDER_CLASS, messageViewHolderClass);
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.IS_NEED_EMPTY_VIEW_GROUP, isUseEmptyViewGroup);
+        String serviceName = TUIConstants.TUIChat.Method.RegisterCustomMessage.CLASSIC_SERVICE_NAME;
+        TUICore.callService(serviceName, TUIConstants.TUIChat.Method.RegisterCustomMessage.METHOD_NAME, param);
+    }
+
+    /**
+     * Add sticker group.
+     * @param groupID the face group ID
+     * @param faceGroup the face group
+     */
+    public static void addStickerGroup(int groupID, FaceGroup<? extends ChatFace> faceGroup) {
+        FaceManager.addFaceGroup(groupID, faceGroup);
+    }
+
+    public interface ChatInputMoreDataSource {
+        /**
+         *  Implement this method to add new items to the more menu of the specified model.
+         * @param chatInfo the chat model
+         * @return the items to be added
+         */
+        default List<InputMoreItem> inputBarShouldAddNewItemToMoreMenuOfInfo(ChatInfo chatInfo) {
+            return new ArrayList<>();
+        }
+
+        /**
+         *  Implement this method to hide items in more menu of the specified model.
+         *  @param chatInfo the chat model
+         *  @return the items to be hidden
+         */
+        default @InputMoreMenuItem List<Integer> inputBarShouldHideItemsInMoreMenuOfInfo(ChatInfo chatInfo) {
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Get whether to show the input bar.
+     * @return whether to show the input bar
+     */
+    public static boolean isShowInputBar() {
+        return getInstance().showInputBar;
+    }
+
+    /**
+     * Set whether to show the input bar. The default value is true.
+     * @param showInputBar whether to show the input bar
+     */
+    public static void setShowInputBar(boolean showInputBar) {
+        getInstance().showInputBar = showInputBar;
+    }
+
+    /**
+     * Set the data source of the more menu in the input bar.
+     * @param chatInputMoreDataSource the input more data source
+     * @see ChatInputMoreDataSource
+     */
+    public static void setChatInputMoreDataSource(ChatInputMoreDataSource chatInputMoreDataSource) {
+        getInstance().chatInputMoreDataSource = chatInputMoreDataSource;
+    }
+
+    /**
+     * Get the data source of the more menu in the input bar.
+     * @return the input more data source
+     * @see ChatInputMoreDataSource
+     */
+    public static ChatInputMoreDataSource getChatInputMoreDataSource() {
+        return getInstance().chatInputMoreDataSource;
+    }
+
+    /**
+     * Hide items in more menu
+     * @param items the items to be hidden
+     */
+    public static void hideItemsInMoreMenu(@InputMoreMenuItem int... items) {
+        for (int item : items) {
+            switch (item) {
+                case CUSTOM: {
+                    getInstance().hideCustom = true;
+                    break;
+                }
+                case RECORD_VIDEO: {
+                    getInstance().hideRecordVideo = true;
+                    break;
+                }
+                case TAKE_PHOTO: {
+                    getInstance().hideTakePhoto = true;
+                    break;
+                }
+                case ALBUM: {
+                    getInstance().hideAlbum = true;
+                    break;
+                }
+                case FILE: {
+                    getInstance().hideFile = true;
+                    break;
+                }
+                case AUDIO_CALL: {
+                    getInstance().hideAudioCall = true;
+                    break;
+                }
+                case VIDEO_CALL: {
+                    getInstance().hideVideoCall = true;
+                    break;
+                }
+                case ROOM_KIT: {
+                    getInstance().hideRoomKit = true;
+                    break;
+                }
+                case GROUP_NOTE: {
+                    getInstance().hideGroupNote = true;
+                    break;
+                }
+                case POLL: {
+                    getInstance().hidePoll = true;
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Get whether to show the custom button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarCustom() {
+        return !getInstance().hideCustom;
+    }
+
+    /**
+     * Get whether to show the record video button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarRecordVideo() {
+        return !getInstance().hideRecordVideo;
+    }
+
+    /**
+     * Get whether to show the take photo button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarTakePhoto() {
+        return !getInstance().hideTakePhoto;
+    }
+
+    /**
+     * Get whether to show the album button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarAlbum() {
+        return !getInstance().hideAlbum;
+    }
+
+    /**
+     * Get whether to show the file button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarFile() {
+        return !getInstance().hideFile;
+    }
+
+    /**
+     * Get whether to show the audio call button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarAudioCall() {
+        return !getInstance().hideAudioCall;
+    }
+
+    /**
+     * Get whether to show the video call button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarVideoCall() {
+        return !getInstance().hideVideoCall;
+    }
+
+    /**
+     * Get whether to show the room button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarRoomKit() {
+        return !getInstance().hideRoomKit;
+    }
+
+    /**
+     * Get whether to show the group note button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarGroupNote() {
+        return !getInstance().hideGroupNote;
+    }
+
+    /**
+     * Get whether to show the poll button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarPoll() {
+        return !getInstance().hidePoll;
+    }
+
+    /**
+     * Set the text color of the sent text message
+     * @param color text color
+     */
+    public static void setSendTextMessageColor(int color) {
+        getInstance().sendTextMessageColor = color;
+    }
+
+    /**
+     * Get the text color of the sent text message
+     * @return text color
+     */
+    public static int getSendTextMessageColor() {
+        return getInstance().sendTextMessageColor;
+    }
+
+    /**
+     * Set the font size of the sent text message
+     * @param size font size
+     */
+    public static void setSendTextMessageFontSize(int size) {
+        getInstance().sendTextMessageFontSize = size;
+    }
+
+    /**
+     * Get the font size of the sent text message
+     * @return font size
+     */
+    public static int getSendTextMessageFontSize() {
+        return getInstance().sendTextMessageFontSize;
+    }
+
+    /**
+     * Set the text color of the received text message
+     * @param color text color
+     */
+    public static void setReceiveTextMessageColor(int color) {
+        getInstance().receiveTextMessageColor = color;
+    }
+
+    /**
+     * Get the text color of the received text message
+     * @return text color
+     */
+    public static int getReceiveTextMessageColor() {
+        return getInstance().receiveTextMessageColor;
+    }
+
+    /**
+     * Set the font size of the received text message
+     * @param size
+     */
+    public static void setReceiveTextMessageFontSize(int size) {
+        getInstance().receiveTextMessageFontSize = size;
+    }
+
+    /**
+     * Get the font size of the received text message
+     * @return font size
+     */
+    public static int getReceiveTextMessageFontSize() {
+        return getInstance().receiveTextMessageFontSize;
+    }
+
+    /**
+     * Set the background of the system message
+     * @param drawable the background of the system message
+     */
+    public static void setSystemMessageBackground(Drawable drawable) {
+        getInstance().systemMessageBackground = drawable;
+    }
+
+    /**
+     * Get the background of the system message
+     * @return the background
+     */
+    public static Drawable getSystemMessageBackground() {
+        return newDrawable(getInstance().systemMessageBackground);
+    }
+
+    /**
+     * Set the text color of the system message
+     * @param color the text color of the system message
+     */
+    public static void setSystemMessageTextColor(int color) {
+        getInstance().systemMessageTextColor = color;
+    }
+
+    /**
+     * Get the text color of the system message
+     * @return text color
+     */
+    public static int getSystemMessageTextColor() {
+        return getInstance().systemMessageTextColor;
+    }
+
+    /**
+     * Set the font size of the system message
+     * @param size the font size of the system message
+     */
+    public static void setSystemMessageFontSize(int size) {
+        getInstance().systemMessageFontSize = size;
+    }
+
+    /**
+     * Get the font size of the system message
+     * @return font size
+     */
+    public static int getSystemMessageFontSize() {
+        return getInstance().systemMessageFontSize;
+    }
+
+    /**
+     * Set the data source of the chat shortcut menu
+     * @param shortcutViewDataSource
+     * @see ShortcutMenuConfig.ChatShortcutViewDataSource
+     */
+    public static void setChatShortcutViewDataSource(ShortcutMenuConfig.ChatShortcutViewDataSource shortcutViewDataSource) {
+        ShortcutMenuConfig.setShortcutViewDataSource(shortcutViewDataSource);
+    }
+}

+ 758 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/config/minimalistui/TUIChatConfigMinimalist.java

@@ -0,0 +1,758 @@
+package com.tencent.qcloud.tuikit.tuichat.config.minimalistui;
+
+import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import androidx.annotation.IntDef;
+import androidx.recyclerview.widget.RecyclerView;
+import com.tencent.qcloud.tuicore.TUIConstants;
+import com.tencent.qcloud.tuicore.TUICore;
+import com.tencent.qcloud.tuikit.timcommon.bean.ChatFace;
+import com.tencent.qcloud.tuikit.timcommon.bean.FaceGroup;
+import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
+import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
+import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
+import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
+import com.tencent.qcloud.tuikit.tuichat.config.classicui.TUIChatConfigClassic;
+import com.tencent.qcloud.tuikit.tuichat.interfaces.ChatEventListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TUIChatConfigMinimalist {
+    private TUIChatConfigMinimalist() {}
+
+    private static final class TUIChatConfigMinimalistHolder {
+        private static final TUIChatConfigMinimalist INSTANCE = new TUIChatConfigMinimalist();
+    }
+
+    private static TUIChatConfigMinimalist getInstance() {
+        return TUIChatConfigMinimalistHolder.INSTANCE;
+    }
+
+    public static final int UNDEFINED = -1;
+
+    public static final int REPLY = 1;
+    public static final int QUOTE = 2;
+    public static final int EMOJI_REACTION = 3;
+    public static final int PIN = 4;
+    public static final int RECALL = 5;
+    public static final int TRANSLATE = 6;
+    public static final int CONVERT = 7;
+    public static final int FORWARD = 8;
+    public static final int SELECT = 9;
+    public static final int COPY = 10;
+    public static final int DELETE = 11;
+    public static final int INFO = 12;
+    public static final int SPEAKER_MODE_SWITCH = 13;
+
+    @IntDef({REPLY, QUOTE, EMOJI_REACTION, PIN, RECALL, TRANSLATE, CONVERT, FORWARD, SELECT, COPY, DELETE, INFO, SPEAKER_MODE_SWITCH})
+    public @interface LongPressPopMenuItem {}
+
+    public static final int CUSTOM = 1;
+    public static final int RECORD_VIDEO = 2;
+    public static final int TAKE_PHOTO = 3;
+    public static final int ALBUM = 4;
+    public static final int FILE = 5;
+
+    @IntDef({CUSTOM, RECORD_VIDEO, TAKE_PHOTO, ALBUM, FILE})
+    public @interface InputMoreMenuItem {}
+
+    private boolean enableTypingIndicator = true;
+    private Drawable background;
+    private boolean useSystemCamera;
+    private int timeIntervalForAllowedMessageRecall = 120;
+    private boolean enableAudioCall = true;
+    private boolean enableVideoCall = true;
+    private int sendTextMessageColor = UNDEFINED;
+    private int sendTextMessageFontSize = UNDEFINED;
+    private int receiveTextMessageColor = UNDEFINED;
+    private int receiveTextMessageFontSize = UNDEFINED;
+    // message style
+    private Drawable systemMessageBackground;
+    private int systemMessageTextColor = UNDEFINED;
+    private int systemMessageFontSize = UNDEFINED;
+    private int avatarCornerRadius = UNDEFINED;
+
+    // input bar
+    private boolean showInputBar = true;
+    private boolean hideCustom = false;
+    private boolean hideRecordVideo = false;
+    private boolean hideTakePhoto = false;
+    private boolean hideAlbum = false;
+    private boolean hideFile = false;
+    private TUIChatConfigMinimalist.ChatInputMoreDataSource chatInputMoreDataSource;
+
+    // long press menu
+    private boolean enableReply = true;
+    private boolean enableQuote = true;
+    private boolean enableEmojiReaction = true;
+    private boolean enablePin = true;
+    private boolean enableRecall = true;
+    private boolean enableTranslate = true;
+    private boolean enableConvert = true;
+    private boolean enableForward = true;
+    private boolean enableSelect = true;
+    private boolean enableCopy = true;
+    private boolean enableDelete = true;
+    private boolean enableInfo = true;
+    private boolean enableSpeakerModeSwitch = true;
+
+    /**
+     * Hide the items in the pop-up menu when user presses the message.
+     * @param items The items to be hidden.
+     */
+    public static void hideItemsWhenLongPressMessage(@LongPressPopMenuItem int... items) {
+        for (int i : items) {
+            switch (i) {
+                case REPLY: {
+                    getInstance().enableReply = false;
+                    break;
+                }
+                case QUOTE: {
+                    getInstance().enableQuote = false;
+                    break;
+                }
+                case EMOJI_REACTION: {
+                    getInstance().enableEmojiReaction = false;
+                    break;
+                }
+                case PIN: {
+                    getInstance().enablePin = false;
+                    break;
+                }
+                case RECALL: {
+                    getInstance().enableRecall = false;
+                    break;
+                }
+                case TRANSLATE: {
+                    getInstance().enableTranslate = false;
+                    break;
+                }
+                case CONVERT: {
+                    getInstance().enableConvert = false;
+                    break;
+                }
+                case FORWARD: {
+                    getInstance().enableForward = false;
+                    break;
+                }
+                case SELECT: {
+                    getInstance().enableSelect = false;
+                    break;
+                }
+                case COPY: {
+                    getInstance().enableCopy = false;
+                    break;
+                }
+                case DELETE: {
+                    getInstance().enableDelete = false;
+                    break;
+                }
+                case INFO: {
+                    getInstance().enableInfo = false;
+                    break;
+                }
+                case SPEAKER_MODE_SWITCH: {
+                    getInstance().enableSpeakerModeSwitch = false;
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Get whether to enable the reply button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableReply() {
+        return getInstance().enableReply;
+    }
+
+    /**
+     * Get whether to enable the quote button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableQuote() {
+        return getInstance().enableQuote;
+    }
+
+    /**
+     * Get whether to enable the emoji reaction button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableEmojiReaction() {
+        return getInstance().enableEmojiReaction;
+    }
+
+    /**
+     * Get whether to enable the pin button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnablePin() {
+        return getInstance().enablePin;
+    }
+
+    /**
+     * Get whether to enable the recall button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableRecall() {
+        return getInstance().enableRecall;
+    }
+
+    /**
+     * Get whether to enable the translate button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableTranslate() {
+        return getInstance().enableTranslate;
+    }
+
+    /**
+     * Get whether to enable the voice to text button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableConvert() {
+        return getInstance().enableConvert;
+    }
+
+    /**
+     * Get whether to enable the forward button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableForward() {
+        return getInstance().enableForward;
+    }
+
+    /**
+     * Get whether to enable the select button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableSelect() {
+        return getInstance().enableSelect;
+    }
+
+    /**
+     * Get whether to enable the copy button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableCopy() {
+        return getInstance().enableCopy;
+    }
+
+    /**
+     * Get whether to enable the delete button on the pop menu.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableDelete() {
+        return getInstance().enableDelete;
+    }
+
+    /**
+     * Get whether to enable the speaker mode switch button.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableInfo() {
+        return getInstance().enableInfo;
+    }
+
+    /**
+     * Get whether to enable the speaker mode switch button.
+     * @return true if enabled, false otherwise.
+     */
+    public static boolean isEnableSpeakerModeSwitch() {
+        return getInstance().enableSpeakerModeSwitch;
+    }
+
+    /**
+     * Set event listeners for Chat from external sources, to listen for various events in Chat and respond accordingly, such as
+     * listening for avatar click events, long-press message events, etc.
+     */
+    public static void setChatEventListener(ChatEventListener chatEventListener) {
+        TUIChatConfigs.getChatEventConfig().setChatEventListener(chatEventListener);
+    }
+
+    /**
+     *  Enable the display "Alice is typing..." on one-to-one chat interface.
+     *  The default value is true.
+     *  This configuration takes effect in all one-to-one chat message list interfaces.
+     */
+    public static void setEnableTypingIndicator(boolean enableTypingIndicator) {
+        getInstance().enableTypingIndicator = enableTypingIndicator;
+    }
+
+    /**
+     * Get whether to enable the display "Alice is typing..." on one-to-one chat interface.
+     * @return
+     */
+    public static boolean isEnableTypingIndicator() {
+        return getInstance().enableTypingIndicator;
+    }
+
+    /**
+     * Set the background of the Chat interface.
+     * @param background
+     */
+    public static void setBackground(Drawable background) {
+        getInstance().background = background;
+    }
+
+    /**
+     * Get the background of the Chat interface.
+     * @return
+     */
+    public static Drawable getBackground() {
+        return newDrawable(getInstance().background);
+    }
+
+    /**
+     * Set the top view of the Chat interface.
+     * @param customTopView
+     */
+    public static void setCustomTopView(View customTopView) {
+        TUIChatConfigs.getNoticeLayoutConfig().setCustomNoticeLayout(customTopView);
+    }
+
+    /**
+     * Set whether to use the system camera. The default is false.
+     * @param useSystemCamera
+     */
+    public static void setUseSystemCamera(boolean useSystemCamera) {
+        getInstance().useSystemCamera = useSystemCamera;
+    }
+
+    /**
+     * Get whether to use the system camera.
+     * @return true if use system camera, false otherwise.
+     */
+    public static boolean isUseSystemCamera() {
+        return getInstance().useSystemCamera;
+    }
+
+    /**
+     * Set whether to play the sound message via the speaker by default. The default is true.
+     * @param playingSoundMessageViaSpeakerByDefault
+     */
+    public static void setPlayingSoundMessageViaSpeakerByDefault(boolean playingSoundMessageViaSpeakerByDefault) {
+        TUIChatConfigs.getGeneralConfig().setEnableSoundMessageSpeakerMode(playingSoundMessageViaSpeakerByDefault);
+    }
+
+    /**
+     * Set whether to exclude the message from the unread count. The default is false.
+     * @param excludedFromUnreadCount
+     */
+    public static void setExcludedFromUnreadCount(boolean excludedFromUnreadCount) {
+        TUIChatConfigs.getGeneralConfig().setExcludedFromUnreadCount(excludedFromUnreadCount);
+    }
+
+    /**
+     * Set whether to exclude the message from the last message. The default is false.
+     * @param excludedFromLastMessage
+     */
+    public static void setExcludedFromLastMessage(boolean excludedFromLastMessage) {
+        TUIChatConfigs.getGeneralConfig().setExcludedFromLastMessage(excludedFromLastMessage);
+    }
+
+    /**
+     * Set the maximum duration of the audio recording. The default is 60 seconds.
+     * @param maxAudioRecordDuration
+     */
+    public static void setMaxAudioRecordDuration(int maxAudioRecordDuration) {
+        TUIChatConfigs.getGeneralConfig().setAudioRecordMaxTime(maxAudioRecordDuration);
+    }
+
+    /**
+     * Set the maximum duration of the video recording. The default is 15 seconds.
+     * @param maxVideoRecordDuration
+     */
+    public static void setMaxVideoRecordDuration(int maxVideoRecordDuration) {
+        TUIChatConfigs.getGeneralConfig().setVideoRecordMaxTime(maxVideoRecordDuration);
+    }
+
+    /**
+     * Set whether to enable the message read receipt. The default is false.
+     * @param messageReadReceiptNeeded
+     */
+    public static void setMessageReadReceiptNeeded(boolean messageReadReceiptNeeded) {
+        TUIChatConfigs.getGeneralConfig().setMsgNeedReadReceipt(messageReadReceiptNeeded);
+    }
+
+    /**
+     * Set the time interval for the allowed message recall. The default is 120 seconds.
+     * @param timeIntervalForAllowedMessageRecall
+     */
+    public static void setTimeIntervalForAllowedMessageRecall(int timeIntervalForAllowedMessageRecall) {
+        getInstance().timeIntervalForAllowedMessageRecall = timeIntervalForAllowedMessageRecall;
+    }
+
+    /**
+     * Get the time interval for the allowed message recall.
+     * @return time interval for the allowed message recall
+     */
+    public static int getTimeIntervalForAllowedMessageRecall() {
+        return getInstance().timeIntervalForAllowedMessageRecall;
+    }
+
+    /**
+     * Set whether to hide the video call button. The default is false.
+     * @param hideVideoCall
+     */
+    public static void hideVideoCallButton(boolean hideVideoCall) {
+        getInstance().enableVideoCall = !hideVideoCall;
+    }
+
+    /**
+     * Set whether to hide the audio call button. The default is false.
+     * @param hideAudioCall
+     */
+    public static void hideAudioCallButton(boolean hideAudioCall)  {
+        getInstance().enableAudioCall = !hideAudioCall;
+    }
+
+    /**
+     * Get whether to enable the audio call button.
+     * @return true if enable, false otherwise.
+     */
+    public static boolean isEnableAudioCall() {
+        return getInstance().enableAudioCall;
+    }
+
+    /**
+     * Get whether to enable the video call button.
+     * @return true if enable, false otherwise.
+     */
+    public static boolean isEnableVideoCall() {
+        return getInstance().enableVideoCall;
+    }
+
+    /**
+     * Set whether to enable the float window for the call. The default is false.
+     * @param enableFloatWindowForCall
+     */
+    public static void setEnableFloatWindowForCall(boolean enableFloatWindowForCall) {
+        TUIChatConfigs.getGeneralConfig().setEnableFloatWindowForCall(enableFloatWindowForCall);
+    }
+
+    /**
+     * Set whether to enable the multi-device for the call. The default is false.
+     * @param enableMultiDeviceForCall
+     */
+    public static void setEnableMultiDeviceForCall(boolean enableMultiDeviceForCall) {
+        TUIChatConfigs.getGeneralConfig().setEnableMultiDeviceForCall(enableMultiDeviceForCall);
+    }
+
+    /**
+     * Set whether to enable the incoming banner. The default is false.
+     * @param enableIncomingBanner
+     */
+    public static void setEnableIncomingBanner(boolean enableIncomingBanner) {
+        TUIChatConfigs.getGeneralConfig().setEnableIncomingBanner(enableIncomingBanner);
+    }
+
+    /**
+     * Set whether to enable the virtual background for the call. The default is false.
+     * @param enableVirtualBackgroundForCall
+     */
+    public static void setEnableVirtualBackgroundForCall(boolean enableVirtualBackgroundForCall) {
+        TUIChatConfigs.getGeneralConfig().setEnableVirtualBackgroundForCall(enableVirtualBackgroundForCall);
+    }
+
+    /**
+     * Set whether to use the Android private ring. The default is false.
+     * @param enableAndroidCustomRing
+     */
+    public static void setEnableAndroidCustomRing(boolean enableAndroidCustomRing) {
+        TUIChatConfigs.getGeneralConfig().setEnableAndroidPrivateRing(enableAndroidCustomRing);
+    }
+
+    /**
+     * Register custom message
+     * @param businessID Custom message businessID (note that it must be unique)
+     * @param messageBeanClass Custom message MessageBean type
+     * @param messageViewHolderClass Custom message MessageViewHolder type
+     * @param isUseEmptyViewGroup Set whether to use an empty layout. By default, an empty layout is not used. If set to use an empty layout, the custom message
+     *     will not display user avatars, message bubbles, and other content.
+     */
+    public static void registerCustomMessage(String businessID, Class<? extends TUIMessageBean> messageBeanClass,
+        Class<? extends RecyclerView.ViewHolder> messageViewHolderClass, boolean isUseEmptyViewGroup) {
+        Map<String, Object> param = new HashMap<>();
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.MESSAGE_BUSINESS_ID, businessID);
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.MESSAGE_BEAN_CLASS, messageBeanClass);
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.MESSAGE_VIEW_HOLDER_CLASS, messageViewHolderClass);
+        param.put(TUIConstants.TUIChat.Method.RegisterCustomMessage.IS_NEED_EMPTY_VIEW_GROUP, isUseEmptyViewGroup);
+        String serviceName = TUIConstants.TUIChat.Method.RegisterCustomMessage.MINIMALIST_SERVICE_NAME;
+        TUICore.callService(serviceName, TUIConstants.TUIChat.Method.RegisterCustomMessage.METHOD_NAME, param);
+    }
+
+    /**
+     * Add sticker group.
+     * @param groupID the face group ID
+     * @param faceGroup the face group
+     */
+    public static void addStickerGroup(int groupID, FaceGroup<? extends ChatFace> faceGroup) {
+        FaceManager.addFaceGroup(groupID, faceGroup);
+    }
+
+    public interface ChatInputMoreDataSource {
+        /**
+         *  Implement this method to add new items to the more menu of the specified model.
+         * @param chatInfo the chat model
+         * @return the items to be added
+         */
+        default List<InputMoreItem> inputBarShouldAddNewItemToMoreMenuOfInfo(ChatInfo chatInfo) {
+            return new ArrayList<>();
+        }
+
+        /**
+         *  Implement this method to hide items in more menu of the specified model.
+         *  @param chatInfo the chat model
+         *  @return the items to be hidden
+         */
+        default @InputMoreMenuItem List<Integer> inputBarShouldHideItemsInMoreMenuOfInfo(ChatInfo chatInfo) {
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Get whether to show the input bar.
+     * @return whether to show the input bar
+     */
+    public static boolean isShowInputBar() {
+        return getInstance().showInputBar;
+    }
+
+    /**
+     * Set whether to show the input bar. The default value is true.
+     * @param showInputBar whether to show the input bar
+     */
+    public static void setShowInputBar(boolean showInputBar) {
+        getInstance().showInputBar = showInputBar;
+    }
+
+    /**
+     * Set the data source of the more menu in the input bar.
+     * @param chatInputMoreDataSource the input more data source
+     * @see TUIChatConfigClassic.ChatInputMoreDataSource
+     */
+    public static void setChatInputMoreDataSource(ChatInputMoreDataSource chatInputMoreDataSource) {
+        getInstance().chatInputMoreDataSource = chatInputMoreDataSource;
+    }
+
+    /**
+     * Get the data source of the more menu in the input bar.
+     * @return the input more data source
+     * @see TUIChatConfigClassic.ChatInputMoreDataSource
+     */
+    public static ChatInputMoreDataSource getChatInputMoreDataSource() {
+        return getInstance().chatInputMoreDataSource;
+    }
+
+    /**
+     * Hide items in more menu
+     * @param items the items to be hidden
+     */
+    public static void hideItemsInMoreMenu(@InputMoreMenuItem int... items) {
+        for (int item : items) {
+            switch (item) {
+                case CUSTOM: {
+                    getInstance().hideCustom = true;
+                    break;
+                }
+                case RECORD_VIDEO: {
+                    getInstance().hideRecordVideo = true;
+                    break;
+                }
+                case TAKE_PHOTO: {
+                    getInstance().hideTakePhoto = true;
+                    break;
+                }
+                case ALBUM: {
+                    getInstance().hideAlbum = true;
+                    break;
+                }
+                case FILE: {
+                    getInstance().hideFile = true;
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Get whether to show the custom button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarCustom() {
+        return !getInstance().hideCustom;
+    }
+
+    /**
+     * Get whether to show the record video button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarRecordVideo() {
+        return !getInstance().hideRecordVideo;
+    }
+
+    /**
+     * Get whether to show the take photo button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarTakePhoto() {
+        return !getInstance().hideTakePhoto;
+    }
+
+    /**
+     * Get whether to show the album button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarAlbum() {
+        return !getInstance().hideAlbum;
+    }
+
+    /**
+     * Get whether to show the file button in the input bar
+     * @return true: show; false: hide
+     */
+    public static boolean isShowInputBarFile() {
+        return !getInstance().hideFile;
+    }
+
+    /**
+     * Set the text color of the sent text message
+     * @param color text color
+     */
+    public static void setSendTextMessageColor(int color) {
+        getInstance().sendTextMessageColor = color;
+    }
+
+    /**
+     * Get the text color of the sent text message
+     * @return text color
+     */
+    public static int getSendTextMessageColor() {
+        return getInstance().sendTextMessageColor;
+    }
+
+    /**
+     * Set the font size of the sent text message
+     * @param size font size
+     */
+    public static void setSendTextMessageFontSize(int size) {
+        getInstance().sendTextMessageFontSize = size;
+    }
+
+    /**
+     * Get the font size of the sent text message
+     * @return font size
+     */
+    public static int getSendTextMessageFontSize() {
+        return getInstance().sendTextMessageFontSize;
+    }
+
+    /**
+     * Set the text color of the received text message
+     * @param color text color
+     */
+    public static void setReceiveTextMessageColor(int color) {
+        getInstance().receiveTextMessageColor = color;
+    }
+
+    /**
+     * Get the text color of the received text message
+     * @return text color
+     */
+    public static int getReceiveTextMessageColor() {
+        return getInstance().receiveTextMessageColor;
+    }
+
+    /**
+     * Set the font size of the received text message
+     * @param size
+     */
+    public static void setReceiveTextMessageFontSize(int size) {
+        getInstance().receiveTextMessageFontSize = size;
+    }
+
+    /**
+     * Get the font size of the received text message
+     * @return font size
+     */
+    public static int getReceiveTextMessageFontSize() {
+        return getInstance().receiveTextMessageFontSize;
+    }
+
+    /**
+     * Set the background of the system message
+     * @param drawable the background of the system message
+     */
+    public static void setSystemMessageBackground(Drawable drawable) {
+        getInstance().systemMessageBackground = drawable;
+    }
+
+    /**
+     * Get the background of the system message
+     * @return the background
+     */
+    public static Drawable getSystemMessageBackground() {
+        return newDrawable(getInstance().systemMessageBackground);
+    }
+
+    /**
+     * Set the text color of the system message
+     * @param color the text color of the system message
+     */
+    public static void setSystemMessageTextColor(int color) {
+        getInstance().systemMessageTextColor = color;
+    }
+
+    /**
+     * Get the text color of the system message
+     * @return text color
+     */
+    public static int getSystemMessageTextColor() {
+        return getInstance().systemMessageTextColor;
+    }
+
+    /**
+     * Set the font size of the system message
+     * @param size the font size of the system message
+     */
+    public static void setSystemMessageFontSize(int size) {
+        getInstance().systemMessageFontSize = size;
+    }
+
+    /**
+     * Get the font size of the system message
+     * @return font size
+     */
+    public static int getSystemMessageFontSize() {
+        return getInstance().systemMessageFontSize;
+    }
+
+    /**
+     * Set the corner radius of the avatar on the chat page's header
+     * @param radius
+     */
+    public static void setAvatarCornerRadius(int radius) {
+        getInstance().avatarCornerRadius = radius;
+    }
+
+    /**
+     * Get the corner radius of the avatar on the chat page's header
+     * @return  radius
+     */
+    public static int getAvatarCornerRadius() {
+        return getInstance().avatarCornerRadius;
+    }
+}

+ 15 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/AlbumPickerListener.java

@@ -0,0 +1,15 @@
+package com.tencent.qcloud.tuikit.tuichat.interfaces;
+
+import android.net.Uri;
+
+
+public interface AlbumPickerListener {
+
+    void onFinished(Uri originalUri, Uri transcodeUri);
+
+    void onProgress(Uri originalUri, int progress);
+
+    void onOriginalMediaPicked(Uri originalUri);
+
+    void onCancel();
+}

+ 10 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/IAlbumPicker.java

@@ -0,0 +1,10 @@
+package com.tencent.qcloud.tuikit.tuichat.interfaces;
+
+
+import androidx.activity.result.ActivityResultCaller;
+
+public abstract class IAlbumPicker {
+
+    public abstract void pickMedia(ActivityResultCaller activityResultCaller, AlbumPickerListener listener);
+}
+

+ 10 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/IMultimediaRecorder.java

@@ -0,0 +1,10 @@
+package com.tencent.qcloud.tuikit.tuichat.interfaces;
+
+import androidx.activity.result.ActivityResultCaller;
+
+public abstract class IMultimediaRecorder {
+
+    public abstract void openRecorder(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener);
+
+    public abstract void openCamera(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener);
+}

+ 10 - 0
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/interfaces/MultimediaRecorderListener.java

@@ -0,0 +1,10 @@
+package com.tencent.qcloud.tuikit.tuichat.interfaces;
+
+import android.net.Uri;
+
+public interface MultimediaRecorderListener {
+
+    void onSuccess(Uri uri);
+
+    void onFailed(int errorCode, String errorMessage);
+}

+ 0 - 165
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/component/EmojiIndicatorView.java

@@ -1,165 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.minimalistui.component;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-
-import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
-import com.tencent.qcloud.tuikit.tuichat.R;
-
-import java.util.ArrayList;
-
-public class EmojiIndicatorView extends LinearLayout {
-    private Context mContext;
-    private ArrayList<ImageView> mImageViews;
-    private Bitmap bmpSelect;
-    private Bitmap bmpNomal;
-    private int mHeight = 16;
-    private int mMaxHeight;
-    private AnimatorSet mPlayToAnimatorSet;
-    private AnimatorSet mPlayByInAnimatorSet;
-    private AnimatorSet mPlayByOutAnimatorSet;
-
-    public EmojiIndicatorView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        this.mContext = context;
-        this.setOrientation(HORIZONTAL);
-        mMaxHeight = (int) ScreenUtil.dp2px(mHeight, getResources().getDisplayMetrics());
-        bmpSelect = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_select);
-        bmpNomal = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_nomal);
-    }
-
-    public EmojiIndicatorView(Context context) {
-        this(context, null);
-    }
-
-    public void init(int count) {
-        mImageViews = new ArrayList<ImageView>();
-        this.removeAllViews();
-        for (int i = 0; i < count; i++) {
-            RelativeLayout rl = new RelativeLayout(mContext);
-            LayoutParams params = new LayoutParams(mMaxHeight, mMaxHeight);
-            RelativeLayout.LayoutParams layoutParams =
-                new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
-            layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
-            ImageView imageView = new ImageView(mContext);
-            if (i == 0) {
-                imageView.setImageBitmap(bmpSelect);
-                rl.addView(imageView, layoutParams);
-            } else {
-                imageView.setImageBitmap(bmpNomal);
-                rl.addView(imageView, layoutParams);
-            }
-            this.addView(rl, params);
-            mImageViews.add(imageView);
-        }
-    }
-
-    public void setIndicatorCount(int count) {
-        if (mImageViews == null || count > mImageViews.size()) {
-            return;
-        }
-        for (int i = 0; i < mImageViews.size(); i++) {
-            if (i >= count) {
-                mImageViews.get(i).setVisibility(GONE);
-                ((View) mImageViews.get(i).getParent()).setVisibility(GONE);
-            } else {
-                mImageViews.get(i).setVisibility(VISIBLE);
-                ((View) mImageViews.get(i).getParent()).setVisibility(VISIBLE);
-            }
-        }
-    }
-
-    public void playTo(int position) {
-        for (ImageView iv : mImageViews) {
-            iv.setImageBitmap(bmpNomal);
-        }
-        mImageViews.get(position).setImageBitmap(bmpSelect);
-        final ImageView imageViewStrat = mImageViews.get(position);
-        ObjectAnimator animIn1 = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 0.25f, 1.0f);
-        ObjectAnimator animIn2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 0.25f, 1.0f);
-
-        if (mPlayToAnimatorSet != null && mPlayToAnimatorSet.isRunning()) {
-            mPlayToAnimatorSet.cancel();
-            mPlayToAnimatorSet = null;
-        }
-        mPlayToAnimatorSet = new AnimatorSet();
-        mPlayToAnimatorSet.play(animIn1).with(animIn2);
-        mPlayToAnimatorSet.setDuration(100);
-        mPlayToAnimatorSet.start();
-    }
-
-    public void playBy(int startPosition, int nextPosition) {
-        boolean isShowInAnimOnly = false;
-        if (startPosition < 0 || nextPosition < 0 || nextPosition == startPosition) {
-            startPosition = nextPosition = 0;
-        }
-
-        if (startPosition < 0) {
-            isShowInAnimOnly = true;
-            startPosition = nextPosition = 0;
-        }
-
-        final ImageView imageViewStrat = mImageViews.get(startPosition);
-        final ImageView imageViewNext = mImageViews.get(nextPosition);
-
-        ObjectAnimator anim1 = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 1.0f, 0.25f);
-        ObjectAnimator anim2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 1.0f, 0.25f);
-
-        if (mPlayByOutAnimatorSet != null && mPlayByOutAnimatorSet.isRunning()) {
-            mPlayByOutAnimatorSet.cancel();
-            mPlayByOutAnimatorSet = null;
-        }
-        mPlayByOutAnimatorSet = new AnimatorSet();
-        mPlayByOutAnimatorSet.play(anim1).with(anim2);
-        mPlayByOutAnimatorSet.setDuration(100);
-
-        ObjectAnimator animIn1 = ObjectAnimator.ofFloat(imageViewNext, "scaleX", 0.25f, 1.0f);
-        ObjectAnimator animIn2 = ObjectAnimator.ofFloat(imageViewNext, "scaleY", 0.25f, 1.0f);
-
-        if (mPlayByInAnimatorSet != null && mPlayByInAnimatorSet.isRunning()) {
-            mPlayByInAnimatorSet.cancel();
-            mPlayByInAnimatorSet = null;
-        }
-        mPlayByInAnimatorSet = new AnimatorSet();
-        mPlayByInAnimatorSet.play(animIn1).with(animIn2);
-        mPlayByInAnimatorSet.setDuration(100);
-
-        if (isShowInAnimOnly) {
-            mPlayByInAnimatorSet.start();
-            return;
-        }
-
-        anim1.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {}
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                imageViewStrat.setImageBitmap(bmpNomal);
-                ObjectAnimator animFil1l = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 1.0f);
-                ObjectAnimator animFill2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 1.0f);
-                AnimatorSet mFillAnimatorSet = new AnimatorSet();
-                mFillAnimatorSet.play(animFil1l).with(animFill2);
-                mFillAnimatorSet.start();
-                imageViewNext.setImageBitmap(bmpSelect);
-                mPlayByInAnimatorSet.start();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {}
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {}
-        });
-        mPlayByOutAnimatorSet.start();
-    }
-}

+ 0 - 54
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/component/noticelayout/INoticeLayout.java

@@ -1,54 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.minimalistui.component.noticelayout;
-
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-/**
- *
- * The notification area {@link com.tencent.qcloud.tuikit.tuichat.minimalistui.component.noticelayout.NoticeLayout} has a fixed position and can only be
- * displayed or hidden. The position will not change with the scrolling of the chat content. It can be used to display pending group messages, or some
- * broadcasts. This area is divided into two parts, which can be used to display content topics and auxiliary topics. Click events can be set up in response to
- * user actions.
- */
-public interface INoticeLayout {
-    /**
-     *
-     * Get parent view
-     *
-     * @return
-     */
-    RelativeLayout getParentLayout();
-
-    /**
-     *
-     * Get the subject information of the notification View
-     *
-     * @return
-     */
-    TextView getContent();
-
-    /**
-     *
-     * Get notifications for further actions View
-     *
-     * @return
-     */
-    TextView getContentExtra();
-
-    /**
-     *
-     * Set the click event for the notification
-     *
-     * @param l
-     */
-    void setOnNoticeClickListener(View.OnClickListener l);
-
-    /**
-     *
-     * Set whether the notification area is always displayed
-     *
-     * @param show  true
-     */
-    void alwaysShow(boolean show);
-}

+ 5 - 10
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/component/noticelayout/NoticeLayout.java

@@ -9,12 +9,12 @@ import androidx.annotation.Nullable;
 
 import com.tencent.qcloud.tuikit.tuichat.R;
 
-public class NoticeLayout extends RelativeLayout implements INoticeLayout {
+public class NoticeLayout extends RelativeLayout {
 
     private RelativeLayout mNoticeLayout;
     private TextView mContentText;
     private TextView mContentExtraText;
-    private boolean mAwaysShow;
+    private boolean mAlwaysShow;
 
     public NoticeLayout(Context context) {
         super(context);
@@ -38,39 +38,34 @@ public class NoticeLayout extends RelativeLayout implements INoticeLayout {
         mContentExtraText = findViewById(R.id.notice_content_extra);
     }
 
-    @Override
     public RelativeLayout getParentLayout() {
         return mNoticeLayout;
     }
 
-    @Override
     public TextView getContent() {
         return mContentText;
     }
 
-    @Override
     public TextView getContentExtra() {
         return mContentExtraText;
     }
 
-    @Override
     public void setOnNoticeClickListener(OnClickListener l) {
         setOnClickListener(l);
     }
 
     @Override
     public void setVisibility(int visibility) {
-        if (mAwaysShow) {
+        if (mAlwaysShow) {
             super.setVisibility(VISIBLE);
         } else {
             super.setVisibility(visibility);
         }
     }
 
-    @Override
     public void alwaysShow(boolean show) {
-        mAwaysShow = show;
-        if (mAwaysShow) {
+        mAlwaysShow = show;
+        if (mAlwaysShow) {
             super.setVisibility(VISIBLE);
         }
     }

+ 1 - 1
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/interfaces/IMessageLayout.java

@@ -13,7 +13,7 @@ import java.util.List;
  * display function of the message. This class provides a large number of methods for customization requirements, including appearance settings, event clicks,
  * and display of custom messages.
  */
-public interface IMessageLayout extends IMessageProperties {
+public interface IMessageLayout {
 
     void setAdapter(MessageAdapter adapter);
 

+ 7 - 6
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/page/MessageDetailMinimalistActivity.java

@@ -35,7 +35,6 @@ import com.tencent.qcloud.tuikit.tuichat.interfaces.IMessageDetailListener;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.MinimalistUIService;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.message.viewholder.MessageViewHolderFactory;
 import com.tencent.qcloud.tuikit.tuichat.presenter.MessageReceiptPresenter;
-import com.tencent.qcloud.tuikit.tuichat.presenter.ReplyPresenter;
 import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
 
 import java.util.ArrayList;
@@ -45,11 +44,10 @@ public class MessageDetailMinimalistActivity extends BaseMinimalistLightActivity
     private static final String TAG = MessageDetailMinimalistActivity.class.getSimpleName();
 
     private MessageReceiptPresenter presenter;
-    private ReplyPresenter replyPresenter;
 
     private View readStatusArea;
     private View readTitle;
-    private View unreadtitle;
+    private View unreadTitle;
     private FrameLayout messageArea;
     private RecyclerView readList;
     private RecyclerView unreadList;
@@ -85,7 +83,7 @@ public class MessageDetailMinimalistActivity extends BaseMinimalistLightActivity
         readStatusArea = findViewById(R.id.read_status_area);
         readList = findViewById(R.id.read_list);
         unreadList = findViewById(R.id.unread_list);
-        unreadtitle = findViewById(R.id.unread_title);
+        unreadTitle = findViewById(R.id.unread_title);
         readTitle = findViewById(R.id.read_title);
         ImageView backIcon = findViewById(R.id.back_icon);
         backIcon.getBackground().setAutoMirrored(true);
@@ -148,7 +146,7 @@ public class MessageDetailMinimalistActivity extends BaseMinimalistLightActivity
                     readTitle.setVisibility(View.GONE);
                 }
                 if (info.getUnreadCount() <= 0) {
-                    unreadtitle.setVisibility(View.GONE);
+                    unreadTitle.setVisibility(View.GONE);
                 }
             }
 
@@ -191,7 +189,7 @@ public class MessageDetailMinimalistActivity extends BaseMinimalistLightActivity
         groupMemberInfo.setFriendRemark(chatInfo.getChatName());
         groupMemberInfo.setIconUrl(chatInfo.getFaceUrl());
         if (messageBean.isPeerRead()) {
-            unreadtitle.setVisibility(View.GONE);
+            unreadTitle.setVisibility(View.GONE);
             readMemberList.add(groupMemberInfo);
             readAdapter.setData(readMemberList);
             readAdapter.notifyDataSetChanged();
@@ -210,6 +208,9 @@ public class MessageDetailMinimalistActivity extends BaseMinimalistLightActivity
     }
 
     private void setMsgAbstract() {
+        if (this.isDestroyed()) {
+            return;
+        }
         messageArea.removeAllViews();
         int type = MinimalistUIService.getInstance().getViewType(messageBean.getClass());
         RecyclerView.ViewHolder holder = MessageViewHolderFactory.getInstance(messageArea, null, type);

+ 3 - 3
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/page/TUIBaseChatMinimalistActivity.java

@@ -28,14 +28,14 @@ public abstract class TUIBaseChatMinimalistActivity extends BaseMinimalistLightA
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.chat_activity);
-        chat(getIntent());
+        initChat(getIntent());
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         TUIChatLog.i(TAG, "onNewIntent");
         super.onNewIntent(intent);
-        chat(intent);
+        initChat(intent);
     }
 
     @Override
@@ -44,7 +44,7 @@ public abstract class TUIBaseChatMinimalistActivity extends BaseMinimalistLightA
         super.onResume();
     }
 
-    private void chat(Intent intent) {
+    private void initChat(Intent intent) {
         Bundle bundle = intent.getExtras();
         TUIChatLog.i(TAG, "bundle: " + bundle + " intent: " + intent);
 

+ 24 - 78
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/page/TUIBaseChatMinimalistFragment.java

@@ -2,9 +2,6 @@ package com.tencent.qcloud.tuikit.tuichat.minimalistui.page;
 
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -15,9 +12,8 @@ import android.view.inputmethod.InputMethodManager;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.util.Supplier;
+import androidx.fragment.app.Fragment;
 import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.target.CustomTarget;
-import com.bumptech.glide.request.transition.Transition;
 import com.tencent.imsdk.v2.V2TIMMessage;
 import com.tencent.qcloud.tuicore.TUIConfig;
 import com.tencent.qcloud.tuicore.TUIConstants;
@@ -25,16 +21,16 @@ import com.tencent.qcloud.tuicore.TUICore;
 import com.tencent.qcloud.tuicore.TUILogin;
 import com.tencent.qcloud.tuicore.util.ToastUtil;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
 import com.tencent.qcloud.tuikit.timcommon.component.interfaces.IUIKitCallback;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
-import com.tencent.qcloud.tuikit.timcommon.util.ImageUtil;
+import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
 import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
 import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.CallingMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.MergeMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioPlayer;
+import com.tencent.qcloud.tuikit.tuichat.config.minimalistui.TUIChatConfigMinimalist;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.ChatView;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.input.InputView;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.message.MessageRecyclerView;
@@ -48,8 +44,9 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
 
-public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
+public abstract class TUIBaseChatMinimalistFragment extends Fragment {
     private static final String TAG = TUIBaseChatMinimalistFragment.class.getSimpleName();
 
     protected View baseView;
@@ -57,7 +54,6 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
     protected ChatView chatView;
     protected ChatInfo chatInfo;
     private MessageRecyclerView messageRecyclerView;
-    private int messageViewBackgroundHeight;
     protected String mChatBackgroundUrl;
     protected String mChatBackgroundThumbnailUrl;
 
@@ -67,11 +63,6 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
         TUIChatLog.i(TAG, "onCreateView " + this);
 
         baseView = inflater.inflate(R.layout.chat_minimalist_fragment, container, false);
-        
-        //        // Example of setting various properties of ChatLayout through api
-        //        ChatLayoutSetting helper = new ChatLayoutSetting(getActivity());
-        //        helper.setGroupId(mChatInfo.getId());
-        //        helper.customizeChatLayout(mChatLayout);
         return baseView;
     }
 
@@ -163,25 +154,6 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
             public void onClickCapture() {
                 startCapture();
             }
-
-            @Override
-            public void onUpdateChatBackground() {
-                setChatViewBackground(mChatBackgroundUrl);
-            }
-        });
-
-        messageRecyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                if (messageViewBackgroundHeight == 0) {
-                    messageViewBackgroundHeight = messageRecyclerView.getHeight();
-                }
-                if (messageViewBackgroundHeight < messageRecyclerView.getHeight()) {
-                    messageViewBackgroundHeight = messageRecyclerView.getHeight();
-                    setChatViewBackground(mChatBackgroundUrl);
-                }
-            }
         });
     }
 
@@ -196,7 +168,7 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
     }
 
     protected void onRecallClicked(TUIMessageBean messageInfo) {
-        if (messageInfo == null) {
+        if (messageInfo == null || TextUtils.isEmpty(messageInfo.getUserId())) {
             return;
         }
         CallingMessageBean callingMessageBean = (CallingMessageBean) messageInfo;
@@ -225,9 +197,7 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
         }
     }
 
-    protected void onUserIconLongClicked(TUIMessageBean messageBean) {
-
-    }
+    protected void onUserIconLongClicked(TUIMessageBean messageBean) {}
 
     protected void onUserIconClicked(TUIMessageBean message) {
         if (null == message) {
@@ -329,6 +299,13 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
             TUIChatLog.e(TAG, "initChatViewBackground getChatInfo is null");
             return;
         }
+
+        Drawable chatBackground = TUIChatConfigMinimalist.getBackground();
+        if (chatBackground != null) {
+            setChatBackground(chatBackground);
+            return;
+        }
+
         DataStoreUtil.getInstance().getValueAsync(getChatInfo().getId(), new DataStoreUtil.GetResult<String>() {
             @Override
             public void onSuccess(String result) {
@@ -337,27 +314,16 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
 
             @Override
             public void onFail() {
-                TUIChatLog.e(TAG, "initChatViewBackground onFail");
+                TUIChatLog.w(TAG, "Chat background not found");
             }
         }, String.class);
     }
 
     protected void setChatViewBackground(String uri) {
-        TUIChatLog.d(TAG, "setChatViewBackground uri = " + uri);
         if (TextUtils.isEmpty(uri)) {
             return;
         }
 
-        if (chatView == null) {
-            TUIChatLog.e(TAG, "setChatViewBackground chatview is null");
-            return;
-        }
-
-        if (messageRecyclerView == null) {
-            TUIChatLog.e(TAG, "setChatViewBackground messageRecyclerView is null");
-            return;
-        }
-
         String[] list = uri.split(",");
         if (list.length > 0) {
             mChatBackgroundThumbnailUrl = list[0];
@@ -373,35 +339,16 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
             return;
         }
 
-        messageRecyclerView.post(new Runnable() {
-            @Override
-            public void run() {
-                int imageWidth = messageRecyclerView.getWidth();
-                int imageHeight = messageRecyclerView.getHeight();
-                if (imageHeight > messageViewBackgroundHeight) {
-                    messageViewBackgroundHeight = imageHeight;
-                }
-                TUIChatLog.d(TAG, "messageRecyclerView  width = " + imageWidth + ", height = " + messageViewBackgroundHeight);
-                if (imageWidth == 0 || messageViewBackgroundHeight == 0) {
-                    return;
-                }
-                Glide.with(getContext()).asBitmap().load(mChatBackgroundUrl).into(new CustomTarget<Bitmap>(imageWidth, messageViewBackgroundHeight) {
-                    @Override
-                    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
-                        TUIChatLog.d(TAG, "messageRecyclerView onGlobalLayout url = " + mChatBackgroundUrl);
-                        Bitmap srcBitmap = ImageUtil.zoomImg(resource, imageWidth, messageViewBackgroundHeight);
-                        messageRecyclerView.setBackground(new BitmapDrawable(getResources(), resource) {
-                            @Override
-                            public void draw(@NonNull Canvas canvas) {
-                                // TUIChatLog.d(TAG, "draw canvas =" + canvas.getClipBounds());
-                                canvas.drawBitmap(srcBitmap, canvas.getClipBounds(), canvas.getClipBounds(), null);
-                            }
-                        });
-                    }
+        setChatBackground(mChatBackgroundUrl);
+    }
 
-                    @Override
-                    public void onLoadCleared(@Nullable Drawable placeholder) {}
-                });
+    private void setChatBackground(Object backgroundRes) {
+        ThreadUtils.execute(() -> {
+            try {
+                Drawable drawable = Glide.with(baseView.getContext()).asDrawable().load(backgroundRes).submit().get();
+                chatView.setChatBackground(drawable);
+            } catch (ExecutionException | InterruptedException e) {
+                TUIChatLog.e(TAG, "load background failed");
             }
         });
     }
@@ -451,5 +398,4 @@ public abstract class TUIBaseChatMinimalistFragment extends BaseFragment {
     public ChatView getChatView() {
         return chatView;
     }
-
 }

+ 0 - 242
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/setting/ChatLayoutSetting.java

@@ -1,242 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.minimalistui.setting;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.FrameLayout;
-import com.google.gson.Gson;
-import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
-import com.tencent.qcloud.tuikit.tuichat.bean.CustomHelloMessage;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
-import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
-import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.ChatView;
-import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.input.InputView;
-import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.message.MessageRecyclerView;
-import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder;
-
-public class ChatLayoutSetting {
-    private static final String TAG = ChatLayoutSetting.class.getSimpleName();
-
-    private Context mContext;
-    private String groupId;
-
-    public ChatLayoutSetting(Context context) {
-        mContext = context;
-    }
-
-    public void setGroupId(String groupId) {
-        this.groupId = groupId;
-    }
-
-    public void customizeMessageLayout(final MessageRecyclerView messageRecyclerView) {
-        if (messageRecyclerView == null) {
-            return;
-        }
-    }
-
-    public void customizeChatLayout(final ChatView layout) {
-
-        // Set custom view of chat interface as security prompt
-        ViewGroup customNoticeLayout = TUIChatConfigs.getConfigs().getNoticeLayoutConfig().getCustomNoticeLayout();
-        FrameLayout customView = layout.getCustomView();
-        if (customNoticeLayout != null && customView.getVisibility() == View.GONE) {
-            ViewParent viewParent = customNoticeLayout.getParent();
-            if (viewParent instanceof ViewGroup) {
-                ViewGroup parentView = (ViewGroup) viewParent;
-                parentView.removeAllViews();
-            }
-            customView.addView(customNoticeLayout);
-            customView.setVisibility(View.VISIBLE);
-        }
-
-        
-        //====== MessageLayout example ======//
-        MessageRecyclerView messageRecyclerView = layout.getMessageLayout();
-        
-        //        messageRecyclerView.setBackground(new ColorDrawable(0xFFEFE5D4));
-        
-        
-        // Set the default avatar, the default is the same as your friend's and your own avatar
-        //        messageRecyclerView.setAvatar(R.drawable.ic_more_file);
-        
-        messageRecyclerView.setAvatarRadius(100);
-        
-        //        messageRecyclerView.setAvatarSize(new int[]{48, 48});
-        //
-        
-        //        ////// Set the nickname style (the other party is consistent with their own style) //////
-        //        messageRecyclerView.setNameFontSize(12);
-        //        messageRecyclerView.setNameFontColor(0xFF8B5A2B);
-        //
-        
-        
-        //        // Set the background of your own chat bubble
-        //        messageRecyclerView.setRightBubble(new ColorDrawable(0xFFCCE4FC));
-        
-        //        // Set the background of friends chat bubbles
-        //        messageRecyclerView.setLeftBubble(new ColorDrawable(0xFFE4E7EB));
-        //
-        
-        
-        //        // Set the font size of chat content, friends and yourself use a font size
-        //        messageRecyclerView.setChatContextFontSize(15);
-        
-        //        // Set your own chat content font color
-        //        messageRecyclerView.setRightChatContentFontColor(0xFFA9A9A9);
-        
-        //        // Set friend chat content font color
-        //        messageRecyclerView.setLeftChatContentFontColor(0xFFA020F0);
-        //
-        
-        
-        //        // Set the background of the chat timeline
-        //        messageRecyclerView.setChatTimeBubble(new ColorDrawable(0xFFE4E7EB));
-        
-        //        // Set the font size of chat time
-        //        messageRecyclerView.setChatTimeFontSize(12);
-        
-        //        // Set the font color of chat time
-        //        messageRecyclerView.setChatTimeFontColor(0xFF7E848C);
-        //
-        
-        
-        //        // Set the background of the prompt
-        //        messageRecyclerView.setTipsMessageBubble(new ColorDrawable(0xFFE4E7EB));
-        
-        //        // Set the font size of the prompt
-        //        messageRecyclerView.setTipsMessageFontSize(12);
-        
-        //        // Set the font color of the prompt
-        //        messageRecyclerView.setTipsMessageFontColor(0xFF7E848C);
-        //
-
-        //
-        
-        //        // Add a PopMenuAction
-        //        PopMenuAction action = new PopMenuAction();
-        //        action.setActionName("test");
-        //        action.setActionClickListener(new PopActionClickListener() {
-        //            @Override
-        //            public void onActionClick(int position, Object data) {
-        
-        //            }
-        //        });
-        //        messageRecyclerView.addPopAction(action);
-        //
-        //        final MessageRecyclerView.OnItemClickListener l = messageRecyclerView.getOnItemClickListener();
-        //        messageRecyclerView.setOnItemClickListener(new MessageRecyclerView.OnItemClickListener() {
-        //            @Override
-        //            public void onMessageLongClick(View view, int position, MessageInfo messageInfo) {
-        //                l.onMessageLongClick(view, position, messageInfo);
-        
-        //            }
-        //
-        //            @Override
-        //            public void onUserIconClick(View view, int position, MessageInfo messageInfo) {
-        //                l.onUserIconClick(view, position, messageInfo);
-        
-        //            }
-        //        });
-
-        
-        //====== InputLayout example ======//
-        final InputView inputView = layout.getInputLayout();
-
-        
-        //        // To hide the entrance of audio input, you can open the following code to test
-        //        inputView.disableAudioInput(true);
-        
-        //        // To hide the entry of expression input, you can open the following code test
-        //        inputView.disableEmojiInput(true);
-        
-        //        // To hide the entrance of more functions, you can open the following code test
-        //        inputView.disableMoreInput(true);
-        
-        //        // You can replace the entry of more functions with custom events, you can open the following code test
-        //        inputView.replaceMoreInput(new View.OnClickListener() {
-        //            @Override
-        //            public void onClick(View v) {
-        
-        
-        //                layout.sendMessage(info, false);
-        //            }
-        //        });
-        
-        //        // You can replace more functions with custom fragments, you can open the following code to test
-        //        inputView.replaceMoreInput(new CustomInputFragment().setChatLayout(layout));
-        //
-        
-        //        // You can disable various functions on more panels, you can open the following code test
-        //        inputView.disableCaptureAction(true);
-        //        inputView.disableSendFileAction(true);
-        //        inputView.disableSendPhotoAction(true);
-        //        inputView.disableVideoRecordAction(true);
-
-        
-        // You can add some functions yourself, you can open the following code to test
-
-        
-        // Add a welcome prompt with rich text
-        if (TUIChatConfigs.getConfigs().getGeneralConfig().isEnableWelcomeCustomMessage()) {
-            InputMoreActionUnit unit = new InputMoreActionUnit() {};
-            unit.setIconResId(R.drawable.chat_minimalist_more_action_custom_icon);
-            unit.setName(inputView.getResources().getString(R.string.test_custom_action));
-            unit.setActionId(CustomHelloMessage.CUSTOM_HELLO_ACTION_ID);
-            unit.setPriority(10);
-            unit.setOnClickListener(unit.new OnActionClickListener() {
-                @Override
-                public void onClick() {
-                    Gson gson = new Gson();
-                    CustomHelloMessage customHelloMessage = new CustomHelloMessage();
-                    customHelloMessage.version = TUIChatConstants.version;
-
-                    String data = gson.toJson(customHelloMessage);
-                    TUIMessageBean info = ChatMessageBuilder.buildCustomMessage(data, customHelloMessage.text, customHelloMessage.text.getBytes());
-                    layout.sendMessage(info, false);
-                }
-            });
-            inputView.addAction(unit);
-        }
-    }
-
-    //    public static class CustomInputFragment extends BaseInputFragment {
-    //        @Nullable
-    //        @Override
-    //        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
-    //            View baseView = inflater.inflate(R.layout.test_chat_input_custom_fragment, container, false);
-    //            Button btn1 = baseView.findViewById(R.id.test_send_message_btn1);
-    //            btn1.setOnClickListener(new View.OnClickListener() {
-    //                @Override
-    //                public void onClick(View v) {
-    
-    //                    if (getChatLayout() != null) {
-    //                        Gson gson = new Gson();
-    //                        CustomHelloMessage customHelloMessage = new CustomHelloMessage();
-    //                        String data = gson.toJson(customHelloMessage);
-    //                        MessageInfo info = ChatMessageInfoUtil.buildCustomMessage(data);
-    //                        getChatLayout().sendMessage(info, false);
-    //                    }
-    //                }
-    //            });
-    //            Button btn2 = baseView.findViewById(R.id.test_send_message_btn2);
-    //            btn2.setOnClickListener(new View.OnClickListener() {
-    //                @Override
-    //                public void onClick(View v) {
-    
-    //                    if (getChatLayout() != null) {
-    //                        Gson gson = new Gson();
-    //                        CustomHelloMessage customHelloMessage = new CustomHelloMessage();
-    //                        String data = gson.toJson(customHelloMessage);
-    //                        MessageInfo info = ChatMessageInfoUtil.buildCustomMessage(data);
-    //                        getChatLayout().sendMessage(info, false);
-    //                    }
-    //                }
-    //            });
-    //            return baseView;
-    //        }
-
-    //    }
-}

+ 66 - 16
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/ChatView.java

@@ -6,6 +6,8 @@ import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.text.Spannable;
@@ -19,6 +21,8 @@ import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -31,7 +35,6 @@ import androidx.recyclerview.widget.RecyclerView;
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.request.RequestOptions;
 import com.google.gson.Gson;
-import com.tencent.imsdk.v2.V2TIMConversation;
 import com.tencent.imsdk.v2.V2TIMGroupAtInfo;
 import com.tencent.qcloud.tuicore.TUIConstants;
 import com.tencent.qcloud.tuicore.TUICore;
@@ -65,6 +68,7 @@ import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder;
 import com.tencent.qcloud.tuikit.tuichat.component.pinned.GroupPinnedView;
 import com.tencent.qcloud.tuikit.tuichat.component.progress.ProgressPresenter;
 import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
+import com.tencent.qcloud.tuikit.tuichat.config.minimalistui.TUIChatConfigMinimalist;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.OnEmptySpaceClickListener;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.OnGestureScrollListener;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.component.dialog.ChatBottomSelectSheet;
@@ -72,7 +76,6 @@ import com.tencent.qcloud.tuikit.tuichat.minimalistui.component.noticelayout.Not
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.interfaces.IChatLayout;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.page.MessageDetailMinimalistActivity;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.page.TUIBaseChatMinimalistFragment;
-import com.tencent.qcloud.tuikit.tuichat.minimalistui.setting.ChatLayoutSetting;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.input.InputView;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.message.MessageAdapter;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.message.MessageRecyclerView;
@@ -109,6 +112,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
     protected View mRecordingGroup;
     protected TextView mRecordingTips;
     private MessageRecyclerView mMessageRecyclerView;
+    private ImageView chatBackgroundView;
     private InputView mInputView;
     private NoticeLayout mNoticeLayout;
     private LinearLayout mJumpMessageLayout;
@@ -143,7 +147,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
     public ChatPresenter.TypingListener typingListener = new ChatPresenter.TypingListener() {
         @Override
         public void onTyping(int status) {
-            if (!TUIChatConfigs.getGeneralConfig().isEnableTypingStatus()) {
+            if (!TUIChatConfigMinimalist.isEnableTypingIndicator()) {
                 return;
             }
 
@@ -193,9 +197,10 @@ public class ChatView extends LinearLayout implements IChatLayout {
         inflate(getContext(), R.layout.tuichat_chat_minimalist_layout, this);
 
         mMessageRecyclerView = findViewById(R.id.chat_message_layout);
+        chatBackgroundView = findViewById(R.id.chat_background_view);
         mInputView = findViewById(R.id.chat_input_layout);
         mInputView.setChatLayout(this);
-        boolean enableMainPageInputBar = TUIChatConfigs.getGeneralConfig().isEnableMainPageInputBar();
+        boolean enableMainPageInputBar = TUIChatConfigMinimalist.isShowInputBar();
         mInputView.setVisibility(enableMainPageInputBar ? VISIBLE : GONE);
         mRecordingGroup = findViewById(R.id.voice_recording_view);
         mRecordingTips = findViewById(R.id.recording_tips);
@@ -543,6 +548,10 @@ public class ChatView extends LinearLayout implements IChatLayout {
     }
 
     private void loadAvatar() {
+        int avatarRadius = TUIChatConfigMinimalist.getAvatarCornerRadius();
+        if (avatarRadius != TUIChatConfigMinimalist.UNDEFINED) {
+            chatAvatar.setRadius(avatarRadius);
+        }
         String chatId = mChatInfo.getId();
         if (TUIChatUtils.isGroupChat(mChatInfo.getType())) {
             if (mChatInfo.getIconUrlList() == null || mChatInfo.getIconUrlList().isEmpty()) {
@@ -600,8 +609,10 @@ public class ChatView extends LinearLayout implements IChatLayout {
         } else {
             param.put(TUIConstants.TUIChat.Extension.ChatNavigationMoreItem.GROUP_ID, mChatInfo.getId());
         }
-        param.put(TUIConstants.TUIChat.Extension.ChatNavigationMoreItem.FILTER_VIDEO_CALL, !TUIChatConfigs.getConfigs().getGeneralConfig().isEnableVideoCall());
-        param.put(TUIConstants.TUIChat.Extension.ChatNavigationMoreItem.FILTER_VOICE_CALL, !TUIChatConfigs.getConfigs().getGeneralConfig().isEnableAudioCall());
+        param.put(TUIConstants.TUIChat.Extension.ChatNavigationMoreItem.FILTER_VIDEO_CALL,
+            !mChatInfo.isEnableVideoCall() || !TUIChatConfigMinimalist.isEnableVideoCall());
+        param.put(TUIConstants.TUIChat.Extension.ChatNavigationMoreItem.FILTER_VOICE_CALL,
+            !mChatInfo.isEnableAudioCall() || !TUIChatConfigMinimalist.isEnableAudioCall());
 
         List<TUIExtensionInfo> extensionInfos = TUICore.getExtensionList(TUIConstants.TUIChat.Extension.ChatNavigationMoreItem.MINIMALIST_EXTENSION_ID, param);
         Collections.sort(extensionInfos, new Comparator<TUIExtensionInfo>() {
@@ -936,7 +947,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
             }
         });
 
-        getMessageLayout().setLoadMoreMessageHandler(new MessageRecyclerView.OnLoadMoreHandler() {
+        getMessageLayout().setChatDelegate(new MessageRecyclerView.ChatDelegate() {
             @Override
             public void displayBackToLastMessage(boolean display) {
                 ChatView.this.displayBackToLastMessage(display);
@@ -958,6 +969,11 @@ public class ChatView extends LinearLayout implements IChatLayout {
                     mClickLastMessageShow = false;
                 }
             }
+
+            @Override
+            public void hideSoftInput() {
+                getInputLayout().hideSoftInput();
+            }
         });
 
         getMessageLayout().setEmptySpaceClickListener(new OnEmptySpaceClickListener() {
@@ -1007,7 +1023,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
 
             @Override
             public void onUserTyping(boolean status, long curTime) {
-                if (!TUIChatConfigs.getGeneralConfig().isEnableTypingStatus()) {
+                if (!TUIChatConfigMinimalist.isEnableTypingIndicator()) {
                     return;
                 }
 
@@ -1044,9 +1060,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
             }
 
             private void showContinueRecord() {
-                post(() -> {
-                    mRecordingTips.setText(TUIChatService.getAppContext().getString(R.string.left_cancle_send));
-                });
+                post(() -> mRecordingTips.setText(TUIChatService.getAppContext().getString(R.string.left_cancle_send)));
             }
 
             private void stopRecording() {
@@ -1133,18 +1147,30 @@ public class ChatView extends LinearLayout implements IChatLayout {
                 ChatView.this.scrollToEnd();
             }
         });
-        getInputLayout().clearCustomActionList();
         if (getMessageLayout().getAdapter() == null) {
             mAdapter = new MessageAdapter();
             mAdapter.setFragment(fragment);
             mMessageRecyclerView.setAdapter(mAdapter);
         }
-        ChatLayoutSetting chatLayoutSetting = new ChatLayoutSetting(getContext());
-        chatLayoutSetting.customizeChatLayout(this);
+        setCustomTopView();
         initListener();
         resetForwardState();
     }
 
+    private void setCustomTopView() {
+        View customNoticeLayout = TUIChatConfigs.getNoticeLayoutConfig().getCustomNoticeLayout();
+        FrameLayout customView = getCustomView();
+        if (customNoticeLayout != null && customView.getVisibility() == View.GONE) {
+            ViewParent viewParent = customNoticeLayout.getParent();
+            if (viewParent instanceof ViewGroup) {
+                ViewGroup parentView = (ViewGroup) viewParent;
+                parentView.removeAllViews();
+            }
+            customView.addView(customNoticeLayout);
+            customView.setVisibility(View.VISIBLE);
+        }
+    }
+
     public void scrollToEnd() {
         getMessageLayout().scrollToEnd();
     }
@@ -1228,7 +1254,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
         forwardArea.setVisibility(GONE);
         forwardText.setText("");
 
-        boolean enableMainPageInputBar = TUIChatConfigs.getConfigs().getGeneralConfig().isEnableMainPageInputBar();
+        boolean enableMainPageInputBar = TUIChatConfigMinimalist.isShowInputBar();
         getInputLayout().setVisibility(enableMainPageInputBar ? VISIBLE : GONE);
     }
 
@@ -1436,6 +1462,31 @@ public class ChatView extends LinearLayout implements IChatLayout {
         });
     }
 
+    public void setChatBackground(Drawable drawable) {
+        chatBackgroundView.post(() -> {
+            int vw = chatBackgroundView.getWidth();
+            int vh = chatBackgroundView.getHeight();
+            int dw = drawable.getIntrinsicWidth();
+            int dh = drawable.getIntrinsicHeight();
+            float scale;
+            float dx = 0;
+            float dy = 0;
+
+            if (dw * vh > vw * dh) {
+                scale = (float) vh / (float) dh;
+                dx = (vw - dw * scale) * 0.5f;
+            } else {
+                scale = (float) vw / (float) dw;
+                dy = (vh - dh * scale) * 0.5f;
+            }
+            Matrix matrix = new Matrix();
+            matrix.setScale(scale, scale);
+            matrix.postTranslate(Math.round(dx), Math.round(dy));
+            chatBackgroundView.setImageMatrix(matrix);
+            chatBackgroundView.setImageDrawable(drawable);
+        });
+    }
+
     public void sendTypingStatusMessage(boolean status) {
         if (mChatInfo == null || TextUtils.isEmpty(getChatInfo().getId())) {
             TUIChatLog.e(TAG, "sendTypingStatusMessage receiver is invalid");
@@ -1446,7 +1497,6 @@ public class ChatView extends LinearLayout implements IChatLayout {
         MessageTyping typingMessageBean = new MessageTyping();
         typingMessageBean.setTypingStatus(status);
         String data = gson.toJson(typingMessageBean);
-        TUIChatLog.d(TAG, "sendTypingStatusMessage data = " + data);
         TUIMessageBean msg = ChatMessageBuilder.buildCustomMessage(data, "", null);
 
         presenter.sendTypingStatusMessage(msg, mChatInfo.getId(), new IUIKitCallback<TUIMessageBean>() {

+ 0 - 17
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/input/BaseInputFragment.java

@@ -1,17 +0,0 @@
-package com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.input;
-
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
-import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.IChatLayout;
-
-public class BaseInputFragment extends BaseFragment {
-    private IChatLayout mChatLayout;
-
-    public IChatLayout getChatLayout() {
-        return mChatLayout;
-    }
-
-    public BaseInputFragment setChatLayout(IChatLayout layout) {
-        mChatLayout = layout;
-        return this;
-    }
-}

+ 55 - 78
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/input/InputView.java

@@ -2,11 +2,9 @@ package com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.input;
 
 import android.annotation.SuppressLint;
 import android.app.Activity;
-import android.app.AlertDialog;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -14,7 +12,6 @@ import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -56,8 +53,9 @@ import com.tencent.qcloud.tuikit.tuichat.R;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
 import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
 import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
+import com.tencent.qcloud.tuikit.tuichat.bean.CustomHelloMessage;
 import com.tencent.qcloud.tuikit.tuichat.bean.DraftInfo;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
 import com.tencent.qcloud.tuikit.tuichat.bean.ReplyPreviewBean;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.FileMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder;
@@ -66,6 +64,7 @@ import com.tencent.qcloud.tuikit.tuichat.component.face.FaceFragment;
 import com.tencent.qcloud.tuikit.tuichat.component.inputedittext.TIMMentionEditText;
 import com.tencent.qcloud.tuikit.tuichat.config.GeneralConfig;
 import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
+import com.tencent.qcloud.tuikit.tuichat.config.minimalistui.TUIChatConfigMinimalist;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.interfaces.IChatLayout;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.input.inputmore.InputMoreDialogFragment;
 import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.input.waveview.VoiceWaveView;
@@ -120,12 +119,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
     private int times;
 
     protected ChatInfo mChatInfo;
-    protected List<InputMoreActionUnit> mInputMoreActionList = new ArrayList<>();
-    protected List<InputMoreActionUnit> mInputMoreCustomActionList = new ArrayList<>();
-    private boolean mSendPhotoDisable;
-    private boolean mCaptureDisable;
-    private boolean mVideoRecordDisable;
-    private boolean mSendFileDisable;
+    protected List<InputMoreItem> mInputMoreActionList = new ArrayList<>();
 
     private InputMoreDialogFragment mInputMoreFragment;
     private FaceFragment faceFragment;
@@ -323,7 +317,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         mTextInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
             @Override
             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-                
                 if (actionId == EditorInfo.IME_ACTION_SEND
                     || (actionId == EditorInfo.IME_ACTION_UNSPECIFIED && event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
                         && event.getAction() == KeyEvent.ACTION_DOWN)) {
@@ -348,12 +341,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         });
     }
 
-    private void updateChatBackground() {
-        if (mOnInputViewListener != null) {
-            mOnInputViewListener.onUpdateChatBackground();
-        }
-    }
-
     private void sendTextMessage() {
         if (mSendEnable) {
             if (mMessageHandler != null) {
@@ -370,7 +357,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
                         exitReply();
                     } else {
                         if (TUIChatUtils.isGroupChat(mChatLayout.getChatInfo().getType()) && !mTextInput.getMentionIdList().isEmpty()) {
-                            
                             //  When sending, get the ID list from the map by getting the nickname list that matches the @ in the input box.
                             List<String> atUserList = new ArrayList<>(mTextInput.getMentionIdList());
                             if (atUserList.isEmpty()) {
@@ -454,7 +440,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
                 FaceUtil.handlerEmojiText(mTextInput, text, true);
                 mTextInput.setSelection(selectedIndex + displayInputString.length());
             }
-            
+
             // Afterwards @, the soft keyboard is to be displayed. Activity does not have onResume, so the soft keyboard cannot be displayed
             ThreadUtils.postOnUiThreadDelayed(new Runnable() {
                 @Override
@@ -647,7 +633,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
     }
 
     private void startCapture() {
-        if (TUIChatConfigs.getConfigs().getGeneralConfig().isUseSystemCamera()) {
+        if (TUIChatConfigMinimalist.isUseSystemCamera()) {
             if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
                 PermissionHelper.requestPermission(PermissionHelper.PERMISSION_STORAGE, new PermissionHelper.PermissionCallback() {
                     @Override
@@ -730,7 +716,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
     }
 
     private void startVideoRecord() {
-        if (TUIChatConfigs.getConfigs().getGeneralConfig().isUseSystemCamera()) {
+        if (TUIChatConfigMinimalist.isUseSystemCamera()) {
             if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
                 PermissionHelper.requestPermission(PermissionHelper.PERMISSION_STORAGE, new PermissionHelper.PermissionCallback() {
                     @Override
@@ -1470,9 +1456,17 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
 
     protected void assembleActions() {
         mInputMoreActionList.clear();
-        InputMoreActionUnit actionUnit;
-        if (!mSendPhotoDisable) {
-            actionUnit = new InputMoreActionUnit() {
+
+        List<Integer> excludeItems = new ArrayList<>();
+        TUIChatConfigMinimalist.ChatInputMoreDataSource dataSource = TUIChatConfigMinimalist.getChatInputMoreDataSource();
+        if (dataSource != null) {
+            excludeItems.addAll(dataSource.inputBarShouldHideItemsInMoreMenuOfInfo(mChatInfo));
+            mInputMoreActionList.addAll(dataSource.inputBarShouldAddNewItemToMoreMenuOfInfo(mChatInfo));
+        }
+
+        InputMoreItem actionUnit;
+        if (!excludeItems.contains(TUIChatConfigMinimalist.ALBUM) && TUIChatConfigMinimalist.isShowInputBarAlbum()) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
                     startSendPhoto();
@@ -1485,8 +1479,8 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        if (!mCaptureDisable) {
-            actionUnit = new InputMoreActionUnit() {
+        if (!excludeItems.contains(TUIChatConfigMinimalist.TAKE_PHOTO) && TUIChatConfigMinimalist.isShowInputBarTakePhoto()) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
                     startCaptureCheckPermission();
@@ -1499,8 +1493,8 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        if (!mVideoRecordDisable) {
-            actionUnit = new InputMoreActionUnit() {
+        if (!excludeItems.contains(TUIChatConfigMinimalist.RECORD_VIDEO) && TUIChatConfigMinimalist.isShowInputBarRecordVideo()) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
                     startVideoRecordCheckPermission();
@@ -1513,8 +1507,8 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        if (!mSendFileDisable) {
-            actionUnit = new InputMoreActionUnit() {
+        if (!excludeItems.contains(TUIChatConfigMinimalist.FILE) && TUIChatConfigMinimalist.isShowInputBarFile()) {
+            actionUnit = new InputMoreItem() {
                 @Override
                 public void onAction(String chatInfoId, int chatType) {
                     startSendFile();
@@ -1527,19 +1521,43 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             mInputMoreActionList.add(actionUnit);
         }
 
-        mInputMoreActionList.addAll(mInputMoreCustomActionList);
+        if (!excludeItems.contains(TUIChatConfigMinimalist.CUSTOM) && TUIChatConfigMinimalist.isShowInputBarCustom()) {
+            InputMoreItem helloMessage = new InputMoreItem() {};
+            helloMessage.setIconResId(R.drawable.chat_minimalist_more_action_custom_icon);
+            helloMessage.setName(getResources().getString(R.string.test_custom_action));
+            helloMessage.setActionId(CustomHelloMessage.CUSTOM_HELLO_ACTION_ID);
+            helloMessage.setPriority(10);
+            helloMessage.setOnClickListener(helloMessage.new OnActionClickListener() {
+                @Override
+                public void onClick() {
+                    Gson gson = new Gson();
+                    CustomHelloMessage customHelloMessage = new CustomHelloMessage();
+                    customHelloMessage.version = TUIChatConstants.version;
+
+                    String data = gson.toJson(customHelloMessage);
+                    TUIMessageBean info = ChatMessageBuilder.buildCustomMessage(data, customHelloMessage.text, customHelloMessage.text.getBytes());
+                    mChatLayout.sendMessage(info, false);
+                }
+            });
+            mInputMoreActionList.add(helloMessage);
+        }
+
         mInputMoreActionList.addAll(getExtensionInputMoreList());
-        Collections.sort(mInputMoreActionList, new Comparator<InputMoreActionUnit>() {
+        Collections.sort(mInputMoreActionList, new Comparator<InputMoreItem>() {
             @Override
-            public int compare(InputMoreActionUnit o1, InputMoreActionUnit o2) {
+            public int compare(InputMoreItem o1, InputMoreItem o2) {
                 return o2.getPriority() - o1.getPriority();
             }
         });
     }
 
-    private List<InputMoreActionUnit> getExtensionInputMoreList() {
-        List<InputMoreActionUnit> list = new ArrayList<>();
-
+    private List<InputMoreItem> getExtensionInputMoreList() {
+        List<InputMoreItem> list = new ArrayList<>();
+        List<Integer> excludeItems = new ArrayList<>();
+        TUIChatConfigMinimalist.ChatInputMoreDataSource dataSource = TUIChatConfigMinimalist.getChatInputMoreDataSource();
+        if (dataSource != null) {
+            excludeItems.addAll(dataSource.inputBarShouldHideItemsInMoreMenuOfInfo(mChatInfo));
+        }
         Map<String, Object> param = new HashMap<>();
         param.put(TUIConstants.TUIChat.Extension.InputMore.CONTEXT, getContext());
         if (ChatInfo.TYPE_C2C == mChatInfo.getType()) {
@@ -1551,17 +1569,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_VIDEO_CALL, true);
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_VOICE_CALL, true);
             param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_ROOM, true);
-        } else {
-            param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_VIDEO_CALL,
-                    !TUIChatConfigs.getGeneralConfig().isEnableVideoCall() || !getChatInfo().isEnableVideoCall());
-            param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_VOICE_CALL,
-                    !TUIChatConfigs.getGeneralConfig().isEnableAudioCall() || !getChatInfo().isEnableAudioCall());
-            param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_ROOM,
-                    !TUIChatConfigs.getGeneralConfig().isEnableRoomKit() || !getChatInfo().isEnableRoom());
-            param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_GROUP_NOTE,
-                    !TUIChatConfigs.getGeneralConfig().isEnableGroupNote() || !getChatInfo().isEnableGroupNote());
-            param.put(TUIConstants.TUIChat.Extension.InputMore.FILTER_POLL,
-                    !TUIChatConfigs.getGeneralConfig().isEnablePoll() || !getChatInfo().isEnablePoll());
         }
         param.put(TUIConstants.TUIChat.Extension.InputMore.INPUT_MORE_LISTENER, chatInputMoreListener);
         List<TUIExtensionInfo> extensionList = TUICore.getExtensionList(TUIConstants.TUIChat.Extension.InputMore.MINIMALIST_EXTENSION_ID, param);
@@ -1570,7 +1577,7 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
                 String name = extensionInfo.getText();
                 int icon = (int) extensionInfo.getIcon();
                 int priority = extensionInfo.getWeight();
-                InputMoreActionUnit unit = new InputMoreActionUnit() {
+                InputMoreItem unit = new InputMoreItem() {
                     @Override
                     public void onAction(String chatInfoId, int chatType) {
                         TUIExtensionEventListener extensionListener = extensionInfo.getExtensionListener();
@@ -1617,34 +1624,10 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         mMoreInputDisable = disable;
     }
 
-    public void replaceMoreInput(BaseInputFragment fragment) {
-        mMoreInputEvent = fragment;
-    }
-
     public void replaceMoreInput(OnClickListener listener) {
         mMoreInputEvent = listener;
     }
 
-    public void disableSendPhotoAction(boolean disable) {
-        mSendPhotoDisable = disable;
-    }
-
-    public void disableCaptureAction(boolean disable) {
-        mCaptureDisable = disable;
-    }
-
-    public void disableVideoRecordAction(boolean disable) {
-        mVideoRecordDisable = disable;
-    }
-
-    public void disableSendFileAction(boolean disable) {
-        mSendFileDisable = disable;
-    }
-
-    public void addAction(InputMoreActionUnit action) {
-        mInputMoreCustomActionList.add(action);
-    }
-
     public EditText getInputText() {
         return mTextInput;
     }
@@ -1693,7 +1676,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         replyPreviewBar.setVisibility(View.GONE);
         isQuoteModel = false;
         quotePreviewBar.setVisibility(View.GONE);
-        updateChatBackground();
     }
 
     private void showInputMoreLayout() {
@@ -1712,10 +1694,6 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
         }
     }
 
-    public void clearCustomActionList() {
-        mInputMoreCustomActionList.clear();
-    }
-
     public ChatInfo getChatInfo() {
         return mChatInfo;
     }
@@ -1749,6 +1727,5 @@ public class InputView extends LinearLayout implements View.OnClickListener, Tex
 
         void onClickCapture();
 
-        void onUpdateChatBackground();
     }
 }

+ 4 - 4
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/input/inputmore/InputMoreDialogFragment.java

@@ -21,13 +21,13 @@ import androidx.recyclerview.widget.RecyclerView;
 import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager;
 import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
 import com.tencent.qcloud.tuikit.tuichat.R;
-import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreActionUnit;
+import com.tencent.qcloud.tuikit.tuichat.bean.InputMoreItem;
 import java.util.ArrayList;
 import java.util.List;
 
 public class InputMoreDialogFragment extends DialogFragment {
     private Dialog moreDialog;
-    private List<InputMoreActionUnit> mInputMoreList = new ArrayList<>();
+    private List<InputMoreItem> mInputMoreList = new ArrayList<>();
     private RecyclerView mInputActionView;
     private SelectAdapter mAdapter;
 
@@ -75,7 +75,7 @@ public class InputMoreDialogFragment extends DialogFragment {
         super.onCreate(savedInstanceState);
     }
 
-    public void setActions(List<InputMoreActionUnit> actions) {
+    public void setActions(List<InputMoreItem> actions) {
         this.mInputMoreList = actions;
     }
 
@@ -89,7 +89,7 @@ public class InputMoreDialogFragment extends DialogFragment {
 
         @Override
         public void onBindViewHolder(@NonNull SelectAdapter.SelectViewHolder holder, int position) {
-            InputMoreActionUnit actionUnit = mInputMoreList.get(position);
+            InputMoreItem actionUnit = mInputMoreList.get(position);
             holder.itemText.setText(actionUnit.getName());
             if (actionUnit.getIconResId() > 0) {
                 holder.itemImage.setImageResource(actionUnit.getIconResId());

+ 15 - 11
Android/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/minimalistui/widget/message/MessageAdapter.java

@@ -5,17 +5,16 @@ import android.view.ViewGroup;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.RecyclerView;
 import com.tencent.qcloud.tuicore.TUILogin;
 import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
-import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment;
 import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
 import com.tencent.qcloud.tuikit.timcommon.interfaces.UserFaceUrlCache;
 import com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message.MessageBaseHolder;
 import com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message.MessageContentHolder;
-import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
 import com.tencent.qcloud.tuikit.tuichat.bean.message.TipsMessageBean;
 import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
 import com.tencent.qcloud.tuikit.tuichat.interfaces.IMessageAdapter;
@@ -42,17 +41,16 @@ public class MessageAdapter extends RecyclerView.Adapter implements IMessageAdap
     protected boolean isShowMultiSelectCheckBox = false;
 
     private boolean isForwardMode = false;
-    private boolean isReplyDetailMode = false;
 
     private ChatPresenter presenter;
-    private BaseFragment fragment;
+    private Fragment fragment;
     private UserFaceUrlCache faceUrlCache;
 
     public void setPresenter(ChatPresenter chatPresenter) {
         this.presenter = chatPresenter;
     }
 
-    public void setFragment(BaseFragment fragment) {
+    public void setFragment(Fragment fragment) {
         this.fragment = fragment;
     }
 
@@ -60,10 +58,6 @@ public class MessageAdapter extends RecyclerView.Adapter implements IMessageAdap
         isForwardMode = forwardMode;
     }
 
-    public void setReplyDetailMode(boolean replyDetailMode) {
-        isReplyDetailMode = replyDetailMode;
-    }
-
     public ArrayList<TUIMessageBean> getSelectedItem() {
         if (mSelectedPositions == null || mSelectedPositions.size() == 0) {
             return null;
@@ -133,8 +127,7 @@ public class MessageAdapter extends RecyclerView.Adapter implements IMessageAdap
         if (holder instanceof MessageContentHolder) {
             MessageContentHolder messageContentHolder = (MessageContentHolder) holder;
             messageContentHolder.isForwardMode = isForwardMode;
-            messageContentHolder.isMessageDetailMode = isReplyDetailMode;
-            messageContentHolder.setShowRead(TUIChatConfigs.getConfigs().getGeneralConfig().isMsgNeedReadReceipt());
+            messageContentHolder.setShowRead(TUIChatConfigs.getGeneralConfig().isMsgNeedReadReceipt());
             messageContentHolder.setNeedShowBottom(presenter.isNeedShowBottom());
             messageContentHolder.setRecyclerView(mRecycleView);
             messageContentHolder.setFragment(fragment);
@@ -180,6 +173,11 @@ public class MessageAdapter extends RecyclerView.Adapter implements IMessageAdap
                 baseHolder.msgContentFrame.setOnClickListener(null);
             }
         } else {
+            if (messageBean.hasRiskContent()) {
+                baseHolder.mMutiSelectCheckBox.setEnabled(false);
+            } else {
+                baseHolder.mMutiSelectCheckBox.setEnabled(true);
+            }
             baseHolder.mMutiSelectCheckBox.setVisibility(View.VISIBLE);
             baseHolder.mMutiSelectCheckBox.setChecked(isItemChecked(messageBean));
             baseHolder.mMutiSelectCheckBox.setOnClickListener(new View.OnClickListener() {
@@ -243,11 +241,17 @@ public class MessageAdapter extends RecyclerView.Adapter implements IMessageAdap
     }
 
     public void changeCheckedStatus(TUIMessageBean messageBean) {
+        if (messageBean.hasRiskContent()) {
+            setItemChecked(messageBean, false);
+            return;
+        }
+
         if (isItemChecked(messageBean)) {
             setItemChecked(messageBean, false);
         } else {
             setItemChecked(messageBean, true);
         }
+
         onViewNeedRefresh(IMessageRecyclerView.DATA_CHANGE_TYPE_UPDATE, messageBean);
     }
 

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff