2
0
Эх сурвалжийг харах

优化消息长按菜单等

imndx 6 сар өмнө
parent
commit
86728ce0d1
28 өөрчлөгдсөн 457 нэмэгдсэн , 101 устгасан
  1. 1 1
      menu/README.md
  2. 141 0
      menu/src/main/java/com/noober/menu/HorizontalContextMenu.java
  3. 84 0
      menu/src/main/java/com/noober/menu/HorizontalContextMenuAdapter.java
  4. 9 0
      menu/src/main/java/com/noober/menu/OnMenuItemClickListener.java
  5. 32 52
      menu/src/main/java/com/noober/menu/VerticalContextMenu.java
  6. 1 1
      menu/src/main/java/com/noober/menu/VerticalContextMenuItem.java
  7. BIN
      menu/src/main/res/drawable-xxhdpi/ic_cm_arrow.png
  8. 9 0
      menu/src/main/res/drawable/horizontal_context_menu_item_bg_selector.xml
  9. BIN
      menu/src/main/res/drawable/ic_cm_arrow.png
  10. 6 0
      menu/src/main/res/drawable/shape_color_4c4c4c_radius_8.xml
  11. 46 0
      menu/src/main/res/layout/horizontal_context_menu.xml
  12. 35 0
      menu/src/main/res/layout/horizontal_context_menu_item.xml
  13. 2 0
      menu/src/main/res/values/color.xml
  14. 40 41
      uikit/src/main/java/cn/wildfire/chat/kit/conversation/ConversationMessageAdapter.java
  15. 2 0
      uikit/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/MessageContentViewHolder.java
  16. 37 0
      uikit/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NormalMessageContentViewHolder.java
  17. 5 0
      uikit/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NotificationMessageContentViewHolder.java
  18. 7 6
      uikit/src/main/java/cn/wildfire/chat/kit/conversationlist/ConversationListAdapter.java
  19. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_cancel_upload.png
  20. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_collect.png
  21. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_copy.png
  22. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_delete.png
  23. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_forward.png
  24. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_quote.png
  25. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_resend.png
  26. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_rollback.png
  27. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_select.png
  28. BIN
      uikit/src/main/res/mipmap-xhdpi/ic_msg_select_all.png

+ 1 - 1
menu/README.md

@@ -1 +1 @@
-本模块基于 [FloatMenu](https://github.com/JavaNoober/FloatMenu) 开发
+本模块基于 [FloatMenu](https://github.com/JavaNoober/FloatMenu) 和 [SelectTextHelper](https://github.com/ITxiaoguang/SelectTextHelper) 开发

+ 141 - 0
menu/src/main/java/com/noober/menu/HorizontalContextMenu.java

@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2025 WildFireChat. All rights reserved.
+ */
+
+package com.noober.menu;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+
+public class HorizontalContextMenu {
+
+    private final PopupWindow mWindow;
+    private final Context mContext;
+    private final View mTargetView;
+    private final int[] mTempCoors = new int[2];
+    private final int mWidth;
+    private final int mHeight;
+    private final HorizontalContextMenuAdapter listAdapter;
+    private final RecyclerView rvContent;
+    private final ImageView ivArrow;
+    private final ImageView ivArrowUp;
+    private final int mPopSpanCount;
+
+    @SuppressLint("InflateParams")
+    public HorizontalContextMenu(Context context, View targetView, List<Pair<Integer, String>> menuItems,
+                                 OnMenuItemClickListener menuItemClickListener,
+                                 int popSpanCount, int popBgResource, int popArrowImg,
+                                 int popAnimationStyle) {
+
+        View contentView = LayoutInflater.from(context).inflate(R.layout.horizontal_context_menu, null);
+        rvContent = contentView.findViewById(R.id.rv_content);
+        ivArrow = contentView.findViewById(R.id.iv_arrow);
+        ivArrowUp = contentView.findViewById(R.id.iv_arrow_up);
+        mPopSpanCount = popSpanCount;
+        mContext = context;
+        mTargetView = targetView;
+
+        if (popBgResource != 0) {
+            rvContent.setBackgroundResource(popBgResource);
+        }
+        if (popArrowImg != 0) {
+            ivArrow.setBackgroundResource(popArrowImg);
+        }
+
+        int size = menuItems.size();
+        mWidth = Display.dip2px(context, 66) * Math.min(size, mPopSpanCount);
+        int row = (int) Math.ceil((double) size / mPopSpanCount);
+        mHeight = Display.dip2px(context, 85) * row;
+
+        mWindow = new PopupWindow(contentView,
+            ViewGroup.LayoutParams.WRAP_CONTENT,
+            ViewGroup.LayoutParams.WRAP_CONTENT, false);
+        mWindow.setClippingEnabled(false);
+        mWindow.setOutsideTouchable(true);
+
+        if (popAnimationStyle != 0) {
+            mWindow.setAnimationStyle(popAnimationStyle);
+        }
+
+        listAdapter = new HorizontalContextMenuAdapter(context, menuItems);
+        listAdapter.setOnclickItemListener(position -> {
+            dismiss();
+            menuItemClickListener.onClick(position);
+        });
+        rvContent.setAdapter(listAdapter);
+    }
+
+    public void show() {
+        int deviceWidth = Display.getScreenMetrics(mContext).x;
+        int deviceHeight = Display.getScreenMetrics(mContext).y;
+        int size = listAdapter.getItemCount();
+        if (size > mPopSpanCount) {
+            rvContent.setLayoutManager(new GridLayoutManager(mContext,
+                mPopSpanCount, GridLayoutManager.VERTICAL, false));
+        } else {
+            rvContent.setLayoutManager(new GridLayoutManager(mContext,
+                size, GridLayoutManager.VERTICAL, false));
+        }
+
+        mTargetView.getLocationInWindow(mTempCoors);
+        Log.d("jyj", mTempCoors[0] + " " + mTempCoors[1]);
+        int posX;
+        int startX = mTempCoors[0];
+        // 默认放在targetView的上面
+        int posY = mTempCoors[1] - mHeight;
+
+        ImageView arrow = ivArrow;
+        // 状态栏高度
+        if (posY < Display.dip2px(mContext, 60)) {
+            posY = mTempCoors[1] + mTargetView.getHeight();
+            if (posY > deviceHeight - mHeight) {
+                posY = deviceHeight - mHeight;
+            }
+
+            ivArrow.setVisibility(View.GONE);
+            ivArrowUp.setVisibility(View.VISIBLE);
+            arrow = ivArrowUp;
+        }
+
+        posX = (startX + (mTempCoors[0] + mTargetView.getWidth())) / 2 - mWidth / 2;
+
+        if (posX <= 0) {
+            posX = Display.dip2px(mContext, 20);
+        } else if (posX + mWidth >= deviceWidth - Display.dip2px(mContext, 20)) {
+            posX = deviceWidth - mWidth - Display.dip2px(mContext, 20);
+        }
+
+        mWindow.showAtLocation(mTargetView, Gravity.NO_GRAVITY, posX, posY);
+
+        int targetViewCenterX = mTempCoors[0] + mTargetView.getWidth() / 2;
+        if (targetViewCenterX > posX) {
+            arrow.setTranslationX(targetViewCenterX - posX - Display.dip2px(mContext, 20));
+        } else {
+            // never
+            arrow.setTranslationX(posX - targetViewCenterX - arrow.getWidth());
+        }
+
+    }
+
+    public void dismiss() {
+        mWindow.dismiss();
+    }
+
+    public boolean isShowing() {
+        return mWindow.isShowing();
+    }
+
+}

+ 84 - 0
menu/src/main/java/com/noober/menu/HorizontalContextMenuAdapter.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2025 WildFireChat. All rights reserved.
+ */
+
+package com.noober.menu;
+
+import android.content.Context;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+
+/**
+ * 弹窗 适配器
+ * hxg 2020.9.13 qq:929842234@qq.com
+ */
+public class HorizontalContextMenuAdapter extends RecyclerView.Adapter<HorizontalContextMenuAdapter.ViewHolder> {
+    private final Context mContext;
+    private final List<Pair<Integer, String>> mMenuItems;
+    private boolean itemWrapContent = false;
+    private onClickItemListener listener;
+
+    public HorizontalContextMenuAdapter(Context context, List<Pair<Integer, String>> menuItems) {
+        this.mContext = context;
+        this.mMenuItems = menuItems;
+    }
+
+    public void setItemWrapContent(boolean itemWrapContent) {
+        this.itemWrapContent = itemWrapContent;
+    }
+
+    public void setOnclickItemListener(onClickItemListener listener) {
+        this.listener = listener;
+    }
+
+    public interface onClickItemListener {
+        void onClick(int position);
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(mContext).inflate(R.layout.horizontal_context_menu_item, parent, false);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        int drawableId = mMenuItems.get(position).first;
+        String text = mMenuItems.get(position).second;
+        if (itemWrapContent) {
+            ViewGroup.LayoutParams params = holder.tv_pop_func.getLayoutParams();
+            params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            holder.tv_pop_func.setLayoutParams(params);
+            holder.tv_pop_func.setPadding(Display.dip2px(mContext, 8f), 0, Display.dip2px(mContext, 8f), 0);
+        }
+        holder.iv_pop_icon.setBackgroundResource(drawableId);
+        holder.tv_pop_func.setText(text);
+        holder.ll_pop_item.setOnClickListener(v -> listener.onClick(position));
+    }
+
+    @Override
+    public int getItemCount() {
+        return mMenuItems != null ? mMenuItems.size() : 0;
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+        LinearLayout ll_pop_item;
+        ImageView iv_pop_icon;
+        TextView tv_pop_func;
+
+        public ViewHolder(View itemView) {
+            super(itemView);
+            ll_pop_item = itemView.findViewById(R.id.ll_pop_item);
+            iv_pop_icon = itemView.findViewById(R.id.iv_pop_icon);
+            tv_pop_func = itemView.findViewById(R.id.tv_pop_func);
+        }
+    }
+}

+ 9 - 0
menu/src/main/java/com/noober/menu/OnMenuItemClickListener.java

@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2025 WildFireChat. All rights reserved.
+ */
+
+package com.noober.menu;
+
+public interface OnMenuItemClickListener {
+    void onClick(int position);
+}

+ 32 - 52
menu/src/main/java/com/noober/menu/FloatListContextMenu.java → menu/src/main/java/com/noober/menu/VerticalContextMenu.java

@@ -34,7 +34,7 @@ import java.util.List;
  * Created by xiaoqi on 2017/12/11.
  */
 
-public class FloatListContextMenu extends PopupWindow {
+public class VerticalContextMenu extends PopupWindow {
 
     /**
      * Menu tag name in XML.
@@ -58,7 +58,7 @@ public class FloatListContextMenu extends PopupWindow {
     private final int VERTICAL_OFFSET;
 
     private Context context;
-    private List<MenuItem> menuItemList;
+    private List<VerticalContextMenuItem> verticalContextMenuItemList;
     private View view;
     private Point screenPoint;
     private int clickX;
@@ -66,18 +66,13 @@ public class FloatListContextMenu extends PopupWindow {
     private int menuWidth;
     private int menuHeight;
     private LinearLayout menuLayout;
-    private OnItemClickListener onItemClickListener;
+    private OnMenuItemClickListener onItemClickListener;
 
-
-    public interface OnItemClickListener {
-        void onClick(View v, int position);
-    }
-
-    public FloatListContextMenu(Activity activity) {
+    public VerticalContextMenu(Activity activity) {
         this(activity, activity.findViewById(android.R.id.content));
     }
 
-    public FloatListContextMenu(Context context, View view) {
+    public VerticalContextMenu(Context context, View view) {
         super(context);
         setOutsideTouchable(true);
         setFocusable(true);
@@ -88,7 +83,7 @@ public class FloatListContextMenu extends PopupWindow {
         VERTICAL_OFFSET = Display.dip2px(context, 10);
         DEFAULT_MENU_WIDTH = Display.dip2px(context, 140);
         screenPoint = Display.getScreenMetrics(context);
-        menuItemList = new ArrayList<>();
+        verticalContextMenuItemList = new ArrayList<>();
     }
 
     public void inflate(int menuRes) {
@@ -120,42 +115,42 @@ public class FloatListContextMenu extends PopupWindow {
     }
 
     public void items(int itemWidth, String... items) {
-        menuItemList.clear();
+        verticalContextMenuItemList.clear();
         for (int i = 0; i < items.length; i++) {
-            MenuItem menuModel = new MenuItem();
+            VerticalContextMenuItem menuModel = new VerticalContextMenuItem();
             menuModel.setItem(items[i]);
-            menuItemList.add(menuModel);
+            verticalContextMenuItemList.add(menuModel);
         }
         generateLayout(itemWidth);
     }
 
-    public <T extends MenuItem> void items(List<T> itemList) {
-        menuItemList.clear();
-        menuItemList.addAll(itemList);
+    public <T extends VerticalContextMenuItem> void items(List<T> itemList) {
+        verticalContextMenuItemList.clear();
+        verticalContextMenuItemList.addAll(itemList);
         generateLayout(DEFAULT_MENU_WIDTH);
     }
 
-    public <T extends MenuItem> void items(List<T> itemList, int itemWidth) {
-        menuItemList.clear();
-        menuItemList.addAll(itemList);
+    public <T extends VerticalContextMenuItem> void items(List<T> itemList, int itemWidth) {
+        verticalContextMenuItemList.clear();
+        verticalContextMenuItemList.addAll(itemList);
         generateLayout(itemWidth);
     }
 
     private void generateLayout(int itemWidth) {
         menuLayout = new LinearLayout(context);
-        menuLayout.setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.bg_shadow));
+        menuLayout.setBackground(ContextCompat.getDrawable(context, R.drawable.bg_shadow));
         menuLayout.setOrientation(LinearLayout.VERTICAL);
         int padding = Display.dip2px(context, 12);
-        for (int i = 0; i < menuItemList.size(); i++) {
+        for (int i = 0; i < verticalContextMenuItemList.size(); i++) {
             TextView textView = new TextView(context);
             textView.setClickable(true);
-            textView.setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.selector_item));
+            textView.setBackground(ContextCompat.getDrawable(context, R.drawable.selector_item));
             textView.setPadding(padding, padding, padding, padding);
             textView.setWidth(itemWidth);
             textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
             textView.setTextSize(15);
             textView.setTextColor(Color.BLACK);
-            MenuItem menuModel = menuItemList.get(i);
+            VerticalContextMenuItem menuModel = verticalContextMenuItemList.get(i);
             if (menuModel.getItemResId() != View.NO_ID) {
                 Drawable drawable = ContextCompat.getDrawable(context, menuModel.getItemResId());
                 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
@@ -163,9 +158,16 @@ public class FloatListContextMenu extends PopupWindow {
                 textView.setCompoundDrawables(drawable, null, null, null);
             }
             textView.setText(menuModel.getItem());
-            if (onItemClickListener != null) {
-                textView.setOnClickListener(new ItemOnClickListener(i));
-            }
+            int finalPosition = i;
+            textView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    dismiss();
+                    if (onItemClickListener != null) {
+                        onItemClickListener.onClick(finalPosition);
+                    }
+                }
+            });
             menuLayout.addView(textView);
         }
         int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
@@ -248,12 +250,12 @@ public class FloatListContextMenu extends PopupWindow {
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MenuItem);
         CharSequence itemTitle = a.getText(R.styleable.MenuItem_menu_title);
         int itemIconResId = a.getResourceId(R.styleable.MenuItem_icon, View.NO_ID);
-        MenuItem menu = new MenuItem();
+        VerticalContextMenuItem menu = new VerticalContextMenuItem();
         menu.setItem(String.valueOf(itemTitle));
         if (itemIconResId != View.NO_ID) {
             menu.setItemResId(itemIconResId);
         }
-        menuItemList.add(menu);
+        verticalContextMenuItemList.add(menu);
         a.recycle();
     }
 
@@ -306,14 +308,8 @@ public class FloatListContextMenu extends PopupWindow {
         super.setOnDismissListener(onDismissListener);
     }
 
-    public void setOnItemClickListener(final OnItemClickListener onItemClickListener) {
+    public void setOnItemClickListener(final OnMenuItemClickListener onItemClickListener) {
         this.onItemClickListener = onItemClickListener;
-        if (onItemClickListener != null) {
-            for (int i = 0; i < menuLayout.getChildCount(); i++) {
-                View view = menuLayout.getChildAt(i);
-                view.setOnClickListener(new ItemOnClickListener(i));
-            }
-        }
     }
 
     class MenuTouchListener implements View.OnTouchListener {
@@ -327,20 +323,4 @@ public class FloatListContextMenu extends PopupWindow {
             return false;
         }
     }
-
-    class ItemOnClickListener implements View.OnClickListener {
-        int position;
-
-        public ItemOnClickListener(int position) {
-            this.position = position;
-        }
-
-        @Override
-        public void onClick(View v) {
-            dismiss();
-            if (onItemClickListener != null) {
-                onItemClickListener.onClick(v, position);
-            }
-        }
-    }
 }

+ 1 - 1
menu/src/main/java/com/noober/menu/MenuItem.java → menu/src/main/java/com/noober/menu/VerticalContextMenuItem.java

@@ -6,7 +6,7 @@ import android.view.View;
  * Created by xiaoqi on 2017/12/19.
  */
 
-public class MenuItem {
+public class VerticalContextMenuItem {
 
 	private String item;
 	private int itemResId = View.NO_ID;

BIN
menu/src/main/res/drawable-xxhdpi/ic_cm_arrow.png


+ 9 - 0
menu/src/main/res/drawable/horizontal_context_menu_item_bg_selector.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@color/h_item_bg_pressed" android:state_pressed="true" />
+    <item android:drawable="@color/h_item_bg" android:state_pressed="false" />
+    <item android:drawable="@color/h_item_bg_pressed" android:state_selected="true" />
+    <item android:drawable="@color/h_item_bg" android:state_selected="false" />
+    <item android:drawable="@color/h_item_bg_pressed" android:state_focused="true" />
+    <item android:drawable="@color/h_item_bg" android:state_focused="false" />
+</selector>

BIN
menu/src/main/res/drawable/ic_cm_arrow.png


+ 6 - 0
menu/src/main/res/drawable/shape_color_4c4c4c_radius_8.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="8dp" />
+    <solid android:color="#4c4c4c" />
+</shape>

+ 46 - 0
menu/src/main/res/layout/horizontal_context_menu.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/easy_alert_dialog_layout"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@null"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/iv_arrow_up"
+        android:layout_width="14dp"
+        android:layout_height="7dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="-3dp"
+        android:background="@drawable/ic_cm_arrow"
+        android:rotationX="180"
+        android:visibility="gone"
+        tools:ignore="ContentDescription" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="12dp"
+        android:layout_marginEnd="12dp"
+        android:background="@drawable/shape_color_4c4c4c_radius_8"
+        android:overScrollMode="never"
+        android:paddingStart="12dp"
+        android:paddingEnd="12dp"
+        android:scrollbars="none"
+        tools:itemCount="3"
+        tools:listitem="@layout/horizontal_context_menu_item" />
+
+    <ImageView
+        android:id="@+id/iv_arrow"
+        android:layout_width="14dp"
+        android:layout_height="7dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="-1dp"
+        android:layout_marginEnd="16dp"
+        android:background="@drawable/ic_cm_arrow"
+        tools:ignore="ContentDescription" />
+
+</LinearLayout>

+ 35 - 0
menu/src/main/res/layout/horizontal_context_menu_item.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/ll_pop_item"
+    android:layout_width="60dp"
+    android:paddingTop="12dp"
+    android:paddingBottom="12dp"
+    android:layout_height="80dp"
+    android:background="@drawable/horizontal_context_menu_item_bg_selector"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/iv_pop_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="4dp"
+        android:scaleType="centerInside"
+        tools:ignore="ContentDescription" />
+
+    <TextView
+        android:id="@+id/tv_pop_func"
+        android:layout_width="52dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:maxLines="2"
+        android:paddingStart="2dp"
+        android:paddingEnd="2dp"
+        android:textColor="@android:color/white"
+        android:textSize="12sp"
+        tools:text="置顶叫我阿胶吃哦就" />
+
+</LinearLayout>

+ 2 - 0
menu/src/main/res/values/color.xml

@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <color name="gray">#DEDEDE</color>
+    <color name="h_item_bg">#4c4c4c</color>
+    <color name="h_item_bg_pressed">#444444</color>
 </resources>

+ 40 - 41
uikit/src/main/java/cn/wildfire/chat/kit/conversation/ConversationMessageAdapter.java

@@ -6,6 +6,7 @@ package cn.wildfire.chat.kit.conversation;
 
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -17,8 +18,8 @@ import android.widget.Toast;
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.afollestad.materialdialogs.DialogAction;
 import com.afollestad.materialdialogs.MaterialDialog;
+import com.noober.menu.HorizontalContextMenu;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -26,6 +27,7 @@ import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -162,8 +164,8 @@ public class ConversationMessageAdapter extends RecyclerView.Adapter<RecyclerVie
         if (index >= 0) {
             updateMessage(index, message);
             return;
-        }else {
-            if(!messages.isEmpty() && messages.get(0).message.serverTime > message.message.serverTime){
+        } else {
+            if (!messages.isEmpty() && messages.get(0).message.serverTime > message.message.serverTime) {
                 Log.d(TAG, "msg timestamp < the first msg's timestamp, maybe update old message content, ignore");
                 return;
             }
@@ -343,6 +345,7 @@ public class ConversationMessageAdapter extends RecyclerView.Adapter<RecyclerVie
         }
         try {
             View view = viewStub.inflate();
+            view.setTag("messageContentView");
             if (view instanceof ImageView) {
                 ((ImageView) view).setImageDrawable(null);
             }
@@ -491,49 +494,45 @@ public class ConversationMessageAdapter extends RecyclerView.Adapter<RecyclerVie
                     return false;
                 }
 
-                Collections.sort(contextMenus, (o1, o2) -> o1.contextMenuItem.priority() - o2.contextMenuItem.priority());
+                List<Pair<Integer, String>> items = new ArrayList<>(contextMenus.size());
+                Collections.sort(contextMenus, Comparator.comparingInt(o -> o.contextMenuItem.priority()));
                 List<String> titles = new ArrayList<>(contextMenus.size());
                 for (ContextMenuItemWrapper itemWrapper : contextMenus) {
-                    titles.add(viewHolder.contextMenuTitle(fragment.getContext(), itemWrapper.contextMenuItem.tag()));
+                    items.add(new Pair<>(viewHolder.contextMenuIcon(fragment.getContext(), itemWrapper.contextMenuItem.tag()), viewHolder.contextMenuTitle(fragment.getContext(), itemWrapper.contextMenuItem.tag())));
                 }
-                new MaterialDialog.Builder(fragment.getContext()).items(titles).itemsCallback(new MaterialDialog.ListCallback() {
-                    @Override
-                    public void onSelection(MaterialDialog dialog, View v, int position, CharSequence text) {
-                        try {
-                            ContextMenuItemWrapper menuItem = contextMenus.get(position);
-                            if (menuItem.contextMenuItem.confirm()) {
-                                String content;
-                                content = viewHolder.contextConfirmPrompt(fragment.getContext(), menuItem.contextMenuItem.tag());
-                                new MaterialDialog.Builder(fragment.getContext())
-                                    .content(content)
-                                    .negativeText(R.string.delete_message_dialog_cancel)
-                                    .positiveText(R.string.delete_message_dialog_confirm)
-                                    .onPositive(new MaterialDialog.SingleButtonCallback() {
-                                        @Override
-                                        public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
-                                            try {
-                                                menuItem.method.invoke(viewHolder, itemView, message);
-                                            } catch (IllegalAccessException e) {
-                                                e.printStackTrace();
-                                            } catch (InvocationTargetException e) {
-                                                e.printStackTrace();
-                                            }
-                                        }
-                                    })
-                                    .build()
-                                    .show();
-
-                            } else {
-                                contextMenus.get(position).method.invoke(viewHolder, itemView, message);
-                            }
-                        } catch (IllegalAccessException e) {
-                            e.printStackTrace();
-                        } catch (InvocationTargetException e) {
-                            e.printStackTrace();
-                        }
 
+                new HorizontalContextMenu(fragment.getContext(), itemView.findViewWithTag("messageContentView"), items, position1 -> {
+                    try {
+                        ContextMenuItemWrapper menuItem = contextMenus.get(position1);
+                        if (menuItem.contextMenuItem.confirm()) {
+                            String content;
+                            content = viewHolder.contextConfirmPrompt(fragment.getContext(), menuItem.contextMenuItem.tag());
+                            new MaterialDialog.Builder(fragment.getContext())
+                                .content(content)
+                                .negativeText(R.string.delete_message_dialog_cancel)
+                                .positiveText(R.string.delete_message_dialog_confirm)
+                                .onPositive((dialog, which) -> {
+                                    try {
+                                        menuItem.method.invoke(viewHolder, itemView, message);
+                                    } catch (IllegalAccessException e) {
+                                        e.printStackTrace();
+                                    } catch (InvocationTargetException e) {
+                                        e.printStackTrace();
+                                    }
+                                })
+                                .build()
+                                .show();
+
+                        } else {
+                            contextMenus.get(position1).method.invoke(viewHolder, itemView, message);
+                        }
+                    } catch (IllegalAccessException e) {
+                        e.printStackTrace();
+                    } catch (InvocationTargetException e) {
+                        e.printStackTrace();
                     }
-                }).show();
+                }, 5, 0, 0, 0)
+                    .show();
                 return true;
             }
         };

+ 2 - 0
uikit/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/MessageContentViewHolder.java

@@ -60,6 +60,8 @@ public abstract class MessageContentViewHolder extends RecyclerView.ViewHolder {
 
     public abstract String contextMenuTitle(Context context, String tag);
 
+    public abstract int contextMenuIcon(Context context, String tag);
+
     public abstract String contextConfirmPrompt(Context context, String tag);
 
     public void onViewRecycled() {

+ 37 - 0
uikit/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NormalMessageContentViewHolder.java

@@ -294,6 +294,43 @@ public abstract class NormalMessageContentViewHolder extends MessageContentViewH
         return title;
     }
 
+    @Override
+    public int contextMenuIcon(Context context, String tag) {
+        int resId = 0;
+        switch (tag) {
+            case MessageContextMenuItemTags.TAG_RECALL:
+                resId = R.mipmap.ic_msg_rollback;
+                break;
+            case MessageContextMenuItemTags.TAG_DELETE:
+                resId = R.mipmap.ic_msg_delete;
+                break;
+            case MessageContextMenuItemTags.TAG_FORWARD:
+                resId = R.mipmap.ic_msg_forward;
+                break;
+            case MessageContextMenuItemTags.TAG_QUOTE:
+                resId = R.mipmap.ic_msg_quote;
+                break;
+            case MessageContextMenuItemTags.TAG_MULTI_CHECK:
+                resId = R.mipmap.ic_msg_select;
+                break;
+            case MessageContextMenuItemTags.TAG_CHANNEL_PRIVATE_CHAT:
+                resId = R.mipmap.ic_msg_select;
+                break;
+            case MessageContextMenuItemTags.TAG_FAV:
+                resId = R.mipmap.ic_msg_collect;
+                break;
+            case MessageContextMenuItemTags.TAG_CLIP:
+                resId = R.mipmap.ic_msg_copy;
+                break;
+            case MessageContextMenuItemTags.TAG_SPEECH_TO_TEXT:
+                resId = R.mipmap.ic_msg_quote;
+                break;
+            default:
+                break;
+        }
+        return resId;
+    }
+
     @Override
     public String contextConfirmPrompt(Context context, String tag) {
         String title = context.getString(R.string.message_unknown_option);

+ 5 - 0
uikit/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NotificationMessageContentViewHolder.java

@@ -31,4 +31,9 @@ public abstract class NotificationMessageContentViewHolder extends MessageConten
     public String contextConfirmPrompt(Context context, String tag) {
         return null;
     }
+
+    @Override
+    public int contextMenuIcon(Context context, String tag) {
+        return 0;
+    }
 }

+ 7 - 6
uikit/src/main/java/cn/wildfire/chat/kit/conversationlist/ConversationListAdapter.java

@@ -15,7 +15,8 @@ import androidx.recyclerview.widget.RecyclerView;
 
 import com.afollestad.materialdialogs.DialogAction;
 import com.afollestad.materialdialogs.MaterialDialog;
-import com.noober.menu.FloatListContextMenu;
+import com.noober.menu.OnMenuItemClickListener;
+import com.noober.menu.VerticalContextMenu;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -203,7 +204,7 @@ public class ConversationListAdapter extends RecyclerView.Adapter<RecyclerView.V
         if (!viewHolderClazz.isAnnotationPresent(EnableContextMenu.class)) {
             return;
         }
-        FloatListContextMenu floatListContextMenu = new FloatListContextMenu(fragment.getActivity(), itemView);
+        VerticalContextMenu verticalContextMenu = new VerticalContextMenu(fragment.getActivity(), itemView);
         View.OnLongClickListener listener = new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
@@ -246,10 +247,10 @@ public class ConversationListAdapter extends RecyclerView.Adapter<RecyclerView.V
                     titles.add(viewHolder.contextMenuTitle(fragment.getContext(), itemWrapper.contextMenuItem.tag()));
                 }
 
-                floatListContextMenu.items(titles);
-                floatListContextMenu.setOnItemClickListener(new FloatListContextMenu.OnItemClickListener() {
+                verticalContextMenu.items(titles);
+                verticalContextMenu.setOnItemClickListener(new OnMenuItemClickListener() {
                     @Override
-                    public void onClick(View v, int position) {
+                    public void onClick(int position) {
                         try {
                             ContextMenuItemWrapper menuItem = contextMenus.get(position);
                             if (menuItem.contextMenuItem.confirm()) {
@@ -285,7 +286,7 @@ public class ConversationListAdapter extends RecyclerView.Adapter<RecyclerView.V
 
                     }
                 });
-                floatListContextMenu.show();
+                verticalContextMenu.show();
                 return true;
             }
         };

BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_cancel_upload.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_collect.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_copy.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_delete.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_forward.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_quote.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_resend.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_rollback.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_select.png


BIN
uikit/src/main/res/mipmap-xhdpi/ic_msg_select_all.png