Browse Source

refactor and optimization

imndx 6 months ago
parent
commit
25de2d6f79
24 changed files with 488 additions and 277 deletions
  1. 52 20
      chat/src/main/java/cn/wildfire/chat/app/main/MainActivity.java
  2. 1 32
      chat/src/main/res/menu/main.xml
  3. BIN
      chat/src/main/res/mipmap-xxhdpi/ic_add_friend.png
  4. BIN
      chat/src/main/res/mipmap-xxhdpi/ic_qr_code.png
  5. BIN
      chat/src/main/res/mipmap-xxhdpi/ic_start_chat.png
  6. 1 0
      chat/src/main/res/values-en/strings.xml
  7. 1 0
      chat/src/main/res/values/strings.xml
  8. 5 5
      menu/src/main/java/cn/wildfirechat/uikit/menu/Display.java
  9. 3 3
      menu/src/main/java/cn/wildfirechat/uikit/menu/GridMenuDividerItemDecoration.java
  10. 51 0
      menu/src/main/java/cn/wildfirechat/uikit/menu/LitemMenuDividerItemDecoration.java
  11. 1 1
      menu/src/main/java/cn/wildfirechat/uikit/menu/OnMenuItemClickListener.java
  12. 249 0
      menu/src/main/java/cn/wildfirechat/uikit/menu/PopupMenu.java
  13. 8 19
      menu/src/main/java/cn/wildfirechat/uikit/menu/PopupMenuAdapter.java
  14. 10 6
      menu/src/main/java/cn/wildfirechat/uikit/menu/VerticalContextMenu.java
  15. 30 0
      menu/src/main/java/cn/wildfirechat/uikit/menu/VerticalContextMenuItem.java
  16. 0 153
      menu/src/main/java/com/noober/menu/HorizontalContextMenu.java
  17. 0 30
      menu/src/main/java/com/noober/menu/VerticalContextMenuItem.java
  18. 1 1
      menu/src/main/res/layout/popup_grid_menu.xml
  19. 0 0
      menu/src/main/res/layout/popup_grid_menu_item.xml
  20. 35 0
      menu/src/main/res/layout/popup_list_menu.xml
  21. 34 0
      menu/src/main/res/layout/popup_list_menu_item.xml
  22. 4 5
      uikit/src/main/java/cn/wildfire/chat/kit/conversation/ConversationMessageAdapter.java
  23. 2 2
      uikit/src/main/java/cn/wildfire/chat/kit/conversationlist/ConversationListAdapter.java
  24. BIN
      webrtc/libwebrtc_2.aar

+ 52 - 20
chat/src/main/java/cn/wildfire/chat/app/main/MainActivity.java

@@ -14,6 +14,7 @@ import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.Build;
 import android.text.TextUtils;
+import android.util.Pair;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -35,6 +36,7 @@ import com.afollestad.materialdialogs.MaterialDialog;
 import com.google.android.material.bottomnavigation.BottomNavigationMenuView;
 import com.google.android.material.bottomnavigation.BottomNavigationView;
 import com.king.zxing.Intents;
+import cn.wildfirechat.uikit.menu.PopupMenu;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -233,8 +235,8 @@ public class MainActivity extends WfcBaseActivity {
         super.afterMenus(menu);
         boolean isEnableSecretChat = ChatManager.Instance().isEnableSecretChat();
         if (!isEnableSecretChat) {
-            secretChatMenuItem = menu.findItem(R.id.secretChat);
-            secretChatMenuItem.setEnabled(false);
+//            secretChatMenuItem = menu.findItem(R.id.secretChat);
+//            secretChatMenuItem.setEnabled(false);
         }
     }
 
@@ -425,30 +427,60 @@ public class MainActivity extends WfcBaseActivity {
         });
     }
 
+    private void showMoreActionMenu() {
+        List<Pair<Integer, String>> menuItems = new ArrayList<>();
+        menuItems.add(new Pair<>(R.mipmap.ic_start_chat, getString(R.string.start_group_chat)));
+        menuItems.add(new Pair<>(R.mipmap.ic_start_chat, getString(R.string.start_secret_chat)));
+        menuItems.add(new Pair<>(R.mipmap.ic_add_friend, getString(R.string.add_friend)));
+        menuItems.add(new Pair<>(R.mipmap.ic_qr_code, getString(R.string.scan_qrcode)));
+        PopupMenu moreActionsMenu = new PopupMenu(this, menuItems, position -> {
+            switch (position) {
+                case 0:
+                    createConversation();
+                    break;
+                case 1:
+                    boolean isEnableSecretChat = ChatManager.Instance().isEnableSecretChat();
+                    if (isEnableSecretChat) {
+                        pickContactToCreateSecretConversation();
+                    } else {
+                        Toast.makeText(this, R.string.e2e_not_enable, Toast.LENGTH_SHORT).show();
+                    }
+                    break;
+                case 2:
+                    searchUser();
+                    break;
+                case 3:
+                    String[] permissions = new String[]{Manifest.permission.CAMERA};
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                        if (!checkPermission(permissions)) {
+                            requestPermissions(permissions, 100);
+                        }
+                    }
+                    startActivityForResult(new Intent(MainActivity.this, ScanQRCodeActivity.class), REQUEST_CODE_SCAN_QR_CODE);
+                default:
+                    break;
+            }
+        });
+        View view = findViewById(R.id.more);
+        View toolbar = findViewById(R.id.toolbar);
+        int[] location = new int[2];
+        toolbar.getLocationOnScreen(location);
+        int y = location[1] + toolbar.getHeight();
+        view.getLocationOnScreen(location);
+        int yOffset = y - (location[1] + view.getHeight());
+
+        moreActionsMenu.showAsListMenu(view, 0, yOffset);
+    }
+
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
+            case R.id.more:
+                showMoreActionMenu();
+                return true;
             case R.id.search:
                 showSearchPortal();
                 break;
-            case R.id.chat:
-                createConversation();
-                break;
-            case R.id.secretChat:
-                pickContactToCreateSecretConversation();
-                break;
-            case R.id.add_contact:
-                searchUser();
-                break;
-            case R.id.scan_qrcode:
-                String[] permissions = new String[]{Manifest.permission.CAMERA};
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                    if (!checkPermission(permissions)) {
-                        requestPermissions(permissions, 100);
-                        return true;
-                    }
-                }
-                startActivityForResult(new Intent(this, ScanQRCodeActivity.class), REQUEST_CODE_SCAN_QR_CODE);
             default:
                 break;
         }

+ 1 - 32
chat/src/main/res/menu/main.xml

@@ -11,36 +11,5 @@
         android:id="@+id/more"
         android:icon="@mipmap/ic_add"
         android:title="@string/more"
-        app:showAsAction="always">
-        <menu>
-            <item
-                android:id="@+id/chat"
-                android:icon="@mipmap/ic_create_group_chat"
-                android:orderInCategory="100"
-                android:title="@string/start_group_chat"
-                app:iconTint="@color/colorPrimary"
-                app:showAsAction="never" />
-
-            <item
-                android:id="@+id/secretChat"
-                android:icon="@mipmap/ic_create_group_chat"
-                android:orderInCategory="100"
-                android:title="@string/start_secret_chat"
-                app:iconTint="@color/colorPrimary"
-                app:showAsAction="never" />
-
-            <item
-                android:id="@+id/add_contact"
-                android:icon="@mipmap/ic_add"
-                android:orderInCategory="101"
-                android:title="@string/add_friend"
-                app:iconTint="@color/colorPrimary" />
-            <item
-                android:id="@+id/scan_qrcode"
-                android:icon="@mipmap/qr_code"
-                android:orderInCategory="101"
-                android:title="@string/scan_qrcode"
-                app:iconTint="@color/colorPrimary" />
-        </menu>
-    </item>
+        app:showAsAction="always" />
 </menu>

BIN
chat/src/main/res/mipmap-xxhdpi/ic_add_friend.png


BIN
chat/src/main/res/mipmap-xxhdpi/ic_qr_code.png


BIN
chat/src/main/res/mipmap-xxhdpi/ic_start_chat.png


+ 1 - 0
chat/src/main/res/values-en/strings.xml

@@ -208,4 +208,5 @@
     <string name="language_change_tip">Language settings will take effect after restarting the app</string>
     <string name="confirm">Confirm</string>
     <string name="retry_after_seconds">%1$d seconds to retry</string>
+    <string name="e2e_not_enable">Secret chat not enable</string>
 </resources>

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

@@ -206,4 +206,5 @@
     <string name="language_change_tip">语言设置将在重启应用后生效</string>
     <string name="confirm">确认</string>
     <string name="retry_after_seconds">%1$d秒后重试</string>
+    <string name="e2e_not_enable">密聊功能未启用</string>
 </resources>

+ 5 - 5
menu/src/main/java/com/noober/menu/Display.java → menu/src/main/java/cn/wildfirechat/uikit/menu/Display.java

@@ -1,13 +1,13 @@
-package com.noober.menu;
+/*
+ * Copyright (c) 2025 WildFireChat. All rights reserved.
+ */
+
+package cn.wildfirechat.uikit.menu;
 
 import android.content.Context;
 import android.graphics.Point;
 import android.util.DisplayMetrics;
 
-/**
- * Created by xiaoqi on 2017/12/11.
- */
-
 public class Display {
 
     public static Point getScreenMetrics(Context context) {

+ 3 - 3
menu/src/main/java/com/noober/menu/DividerItemDecoration.java → menu/src/main/java/cn/wildfirechat/uikit/menu/GridMenuDividerItemDecoration.java

@@ -2,7 +2,7 @@
  * Copyright (c) 2025 WildFireChat. All rights reserved.
  */
 
-package com.noober.menu;
+package cn.wildfirechat.uikit.menu;
 
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -13,11 +13,11 @@ import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+public class GridMenuDividerItemDecoration extends RecyclerView.ItemDecoration {
 
     private final Drawable mDivider;
 
-    public DividerItemDecoration(Drawable divider) {
+    public GridMenuDividerItemDecoration(Drawable divider) {
         mDivider = divider;
     }
 

+ 51 - 0
menu/src/main/java/cn/wildfirechat/uikit/menu/LitemMenuDividerItemDecoration.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2025 WildFireChat. All rights reserved.
+ */
+
+package cn.wildfirechat.uikit.menu;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class LitemMenuDividerItemDecoration extends RecyclerView.ItemDecoration {
+
+    private final Drawable mDivider;
+
+    public LitemMenuDividerItemDecoration(Drawable divider) {
+        mDivider = divider;
+    }
+
+    @Override
+    public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+        int childCount = parent.getChildCount();
+
+        for (int i = 0; i < childCount - 1; i++) {
+            View child = parent.getChildAt(i);
+
+
+//             Draw bottom divider
+            int left = child.getLeft();
+            int right = child.getRight();
+            int top = child.getBottom();
+            int bottom = top + mDivider.getIntrinsicHeight();
+            mDivider.setBounds(left + Display.dip2px(parent.getContext(), 36)/* icon width + padding*/, top, right, bottom);
+            mDivider.draw(canvas);
+        }
+    }
+
+    @Override
+    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+//        int spanCount = ((GridLayoutManager) parent.getLayoutManager()).getSpanCount();
+//        int position = parent.getChildAdapterPosition(view);
+//
+//        if ((position + 1) % spanCount != 0) {
+//            outRect.right = mDivider.getIntrinsicWidth();
+//        }
+//        outRect.bottom = mDivider.getIntrinsicHeight();
+    }
+}

+ 1 - 1
menu/src/main/java/com/noober/menu/OnMenuItemClickListener.java → menu/src/main/java/cn/wildfirechat/uikit/menu/OnMenuItemClickListener.java

@@ -2,7 +2,7 @@
  * Copyright (c) 2025 WildFireChat. All rights reserved.
  */
 
-package com.noober.menu;
+package cn.wildfirechat.uikit.menu;
 
 public interface OnMenuItemClickListener {
     void onClick(int position);

+ 249 - 0
menu/src/main/java/cn/wildfirechat/uikit/menu/PopupMenu.java

@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2025 WildFireChat. All rights reserved.
+ */
+
+package cn.wildfirechat.uikit.menu;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+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.core.content.ContextCompat;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.noober.menu.R;
+
+import java.util.List;
+
+public class PopupMenu {
+
+    private PopupWindow mWindow;
+    private final Context mContext;
+    private final int[] mTargetViewLocation = new int[2];
+    private List<Pair<Integer, String>> mMenuItems;
+    private PopupMenuAdapter listAdapter;
+    private RecyclerView rvContent;
+    private ImageView ivArrow;
+    private ImageView ivArrowUp;
+    private int popBgResource;
+    private int popArrowImg;
+    private int popAnimationStyle;
+    private final OnMenuItemClickListener mMenuItemClickListener;
+
+    @SuppressLint("InflateParams")
+    public PopupMenu(Context context, List<Pair<Integer, String>> menuItems,
+                     OnMenuItemClickListener menuItemClickListener) {
+        this(context, menuItems, menuItemClickListener, 0, 0, 0);
+    }
+
+    public PopupMenu(Context context, List<Pair<Integer, String>> menuItems,
+                     OnMenuItemClickListener menuItemClickListener,
+                     int popBgResource, int popArrowImg,
+                     int popAnimationStyle) {
+
+        mContext = context;
+        mMenuItems = menuItems;
+        mMenuItemClickListener = menuItemClickListener;
+        this.popBgResource = popBgResource;
+        this.popArrowImg = popArrowImg;
+        this.popAnimationStyle = popAnimationStyle;
+    }
+
+    public void showAsListMenu(View anchorView) {
+        this.showAsListMenu(anchorView, 0, 0);
+    }
+
+    /**
+     * @param anchorView 锚点view
+     * @param xOffset    x轴偏移量,在自动计算的基础上进行的偏移
+     * @param yOffset    y轴偏移量,在自动计算的基础上进行的偏移
+     */
+    public void showAsListMenu(View anchorView, int xOffset, int yOffset) {
+        this.initContentView(mContext, false);
+        listAdapter = new PopupMenuAdapter(mContext, mMenuItems, false);
+        listAdapter.setOnclickItemListener(position -> {
+            dismiss();
+            if (mMenuItemClickListener != null) {
+                mMenuItemClickListener.onClick(position);
+            }
+        });
+
+        rvContent.setAdapter(listAdapter);
+        int deviceWidth = Display.getScreenMetrics(mContext).x;
+        int deviceHeight = Display.getScreenMetrics(mContext).y;
+        int size = listAdapter.getItemCount();
+        rvContent.setLayoutManager(new LinearLayoutManager(mContext));
+
+        // 设置分割线
+        Drawable dividerDrawable = ContextCompat.getDrawable(mContext, R.drawable.divider);
+        LitemMenuDividerItemDecoration litemMenuDividerItemDecoration = new LitemMenuDividerItemDecoration(dividerDrawable);
+        rvContent.addItemDecoration(litemMenuDividerItemDecoration);
+
+        int contentViewWidth = Display.dip2px(mContext, 140) + Display.dip2px(mContext, 24)/* margin star and end*/;
+        int row = size;
+        int contentViewHeight = Display.dip2px(mContext, 50) * row + Display.dip2px(mContext, 7)/* arrow image*/;
+
+
+        anchorView.getLocationInWindow(mTargetViewLocation);
+        int posX;
+        int targetViewStartX = mTargetViewLocation[0];
+        int targetViewCenterX = targetViewStartX + anchorView.getWidth() / 2;
+        // 默认放在anchorView的上面
+        int posY = mTargetViewLocation[1] - contentViewHeight + yOffset;
+
+        ImageView arrow = ivArrow;
+        // 状态栏高度
+        if (posY < Display.dip2px(mContext, 60)) {
+            posY = mTargetViewLocation[1] + anchorView.getHeight() + yOffset;
+            if (posY > deviceHeight - contentViewHeight) {
+                posY = deviceHeight - contentViewHeight;
+            }
+
+            ivArrow.setVisibility(View.GONE);
+            ivArrowUp.setVisibility(View.VISIBLE);
+            arrow = ivArrowUp;
+        } else {
+            ivArrow.setVisibility(View.VISIBLE);
+            ivArrowUp.setVisibility(View.GONE);
+        }
+
+        posX = (targetViewStartX + anchorView.getWidth() / 2) - contentViewWidth / 2 + xOffset;
+
+        if (posX <= 0) {
+            posX = Display.dip2px(mContext, 10);
+            mWindow.showAtLocation(anchorView, Gravity.LEFT | Gravity.TOP, posX, posY);
+        } else if (posX + contentViewWidth >= deviceWidth - Display.dip2px(mContext, 10)) {
+            posX = Display.dip2px(mContext, 10);
+            mWindow.showAtLocation(anchorView, Gravity.RIGHT | Gravity.TOP, posX, posY);
+        } else {
+            mWindow.showAtLocation(anchorView, Gravity.LEFT | Gravity.TOP, posX, posY);
+        }
+
+        ImageView finalArrow = arrow;
+        mWindow.getContentView().post(() -> {
+            int[] location = new int[2];
+            mWindow.getContentView().getLocationOnScreen(location);
+            float arrowTranslationX = targetViewCenterX - location[0] - Display.dip2px(mContext, 10);
+            finalArrow.setTranslationX(arrowTranslationX);
+        });
+    }
+
+
+    public void showAsGridMenu(View anchorView, int spanCount) {
+        this.initContentView(mContext, true);
+        listAdapter = new PopupMenuAdapter(mContext, mMenuItems, true);
+        listAdapter.setOnclickItemListener(position -> {
+            dismiss();
+            if (mMenuItemClickListener != null) {
+                mMenuItemClickListener.onClick(position);
+            }
+        });
+
+        rvContent.setAdapter(listAdapter);
+        int deviceWidth = Display.getScreenMetrics(mContext).x;
+        int deviceHeight = Display.getScreenMetrics(mContext).y;
+        int size = listAdapter.getItemCount();
+        if (size > spanCount) {
+            rvContent.setLayoutManager(new GridLayoutManager(mContext,
+                spanCount, GridLayoutManager.VERTICAL, false));
+        } else {
+            rvContent.setLayoutManager(new GridLayoutManager(mContext,
+                size, GridLayoutManager.VERTICAL, false));
+        }
+
+        // 设置分割线
+        Drawable dividerDrawable = ContextCompat.getDrawable(mContext, R.drawable.divider);
+        GridMenuDividerItemDecoration gridMenuDividerItemDecoration = new GridMenuDividerItemDecoration(dividerDrawable);
+        rvContent.addItemDecoration(gridMenuDividerItemDecoration);
+
+        int contentViewWidth = Display.dip2px(mContext, 60) * Math.min(size, spanCount) + Display.dip2px(mContext, 24)/* margin star and end*/;
+        int row = (int) Math.ceil((double) size / spanCount);
+        int contentViewHeight = Display.dip2px(mContext, 80) * row + Display.dip2px(mContext, 7)/* arrow image*/;
+
+
+        anchorView.getLocationInWindow(mTargetViewLocation);
+        int posX;
+        int targetViewStartX = mTargetViewLocation[0];
+        int targetViewCenterX = targetViewStartX + anchorView.getWidth() / 2;
+        // 默认放在anchorView的上面
+        int posY = mTargetViewLocation[1] - contentViewHeight;
+
+        ImageView arrow = ivArrow;
+        // 状态栏高度
+        if (posY < Display.dip2px(mContext, 60)) {
+            posY = mTargetViewLocation[1] + anchorView.getHeight();
+            if (posY > deviceHeight - contentViewHeight) {
+                posY = deviceHeight - contentViewHeight;
+            }
+
+            ivArrow.setVisibility(View.GONE);
+            ivArrowUp.setVisibility(View.VISIBLE);
+            arrow = ivArrowUp;
+        } else {
+            ivArrow.setVisibility(View.VISIBLE);
+            ivArrowUp.setVisibility(View.GONE);
+        }
+
+        posX = (targetViewStartX + anchorView.getWidth() / 2) - contentViewWidth / 2;
+
+        if (posX <= 0) {
+            posX = Display.dip2px(mContext, 10);
+            mWindow.showAtLocation(anchorView, Gravity.LEFT | Gravity.TOP, posX, posY);
+        } else if (posX + contentViewWidth >= deviceWidth - Display.dip2px(mContext, 10)) {
+            posX = Display.dip2px(mContext, 10);
+            mWindow.showAtLocation(anchorView, Gravity.RIGHT | Gravity.TOP, posX, posY);
+        } else {
+            mWindow.showAtLocation(anchorView, Gravity.LEFT | Gravity.TOP, posX, posY);
+        }
+
+        ImageView finalArrow = arrow;
+        mWindow.getContentView().post(() -> {
+            int[] location = new int[2];
+            mWindow.getContentView().getLocationOnScreen(location);
+            float arrowTranslationX = targetViewCenterX - location[0] - Display.dip2px(mContext, 10);
+            finalArrow.setTranslationX(arrowTranslationX);
+        });
+    }
+
+    public void dismiss() {
+        mWindow.dismiss();
+    }
+
+    public boolean isShowing() {
+        return mWindow.isShowing();
+    }
+
+    private void initContentView(Context context, boolean gridMenu) {
+        View contentView = LayoutInflater.from(context).inflate(gridMenu ? R.layout.popup_grid_menu : R.layout.popup_list_menu, null);
+        rvContent = contentView.findViewById(R.id.rv_content);
+        ivArrow = contentView.findViewById(R.id.iv_arrow);
+        ivArrowUp = contentView.findViewById(R.id.iv_arrow_up);
+        if (popBgResource != 0) {
+            rvContent.setBackgroundResource(popBgResource);
+        }
+        if (popArrowImg != 0) {
+            ivArrow.setBackgroundResource(popArrowImg);
+        }
+
+        mWindow = new PopupWindow(contentView,
+            ViewGroup.LayoutParams.WRAP_CONTENT,
+            ViewGroup.LayoutParams.WRAP_CONTENT, false);
+        mWindow.setClippingEnabled(false);
+        mWindow.setOutsideTouchable(true);
+        mWindow.setFocusable(true);
+
+        if (popAnimationStyle != 0) {
+            mWindow.setAnimationStyle(popAnimationStyle);
+        }
+    }
+
+}

+ 8 - 19
menu/src/main/java/com/noober/menu/HorizontalContextMenuAdapter.java → menu/src/main/java/cn/wildfirechat/uikit/menu/PopupMenuAdapter.java

@@ -2,7 +2,7 @@
  * Copyright (c) 2025 WildFireChat. All rights reserved.
  */
 
-package com.noober.menu;
+package cn.wildfirechat.uikit.menu;
 
 import android.content.Context;
 import android.util.Pair;
@@ -15,25 +15,20 @@ import android.widget.TextView;
 
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.noober.menu.R;
+
 import java.util.List;
 
-/**
- * 弹窗 适配器
- * hxg 2020.9.13 qq:929842234@qq.com
- */
-public class HorizontalContextMenuAdapter extends RecyclerView.Adapter<HorizontalContextMenuAdapter.ViewHolder> {
+public class PopupMenuAdapter extends RecyclerView.Adapter<PopupMenuAdapter.ViewHolder> {
     private final Context mContext;
     private final List<Pair<Integer, String>> mMenuItems;
-    private boolean itemWrapContent = false;
     private onClickItemListener listener;
+    private final boolean isGridMenu;
 
-    public HorizontalContextMenuAdapter(Context context, List<Pair<Integer, String>> menuItems) {
+    public PopupMenuAdapter(Context context, List<Pair<Integer, String>> menuItems, boolean gridMenu) {
         this.mContext = context;
         this.mMenuItems = menuItems;
-    }
-
-    public void setItemWrapContent(boolean itemWrapContent) {
-        this.itemWrapContent = itemWrapContent;
+        this.isGridMenu = gridMenu;
     }
 
     public void setOnclickItemListener(onClickItemListener listener) {
@@ -46,7 +41,7 @@ public class HorizontalContextMenuAdapter extends RecyclerView.Adapter<Horizonta
 
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View view = LayoutInflater.from(mContext).inflate(R.layout.horizontal_context_menu_item, parent, false);
+        View view = LayoutInflater.from(mContext).inflate(isGridMenu ? R.layout.popup_grid_menu_item : R.layout.popup_list_menu_item, parent, false);
         return new ViewHolder(view);
     }
 
@@ -54,12 +49,6 @@ public class HorizontalContextMenuAdapter extends RecyclerView.Adapter<Horizonta
     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));

+ 10 - 6
menu/src/main/java/com/noober/menu/VerticalContextMenu.java → menu/src/main/java/cn/wildfirechat/uikit/menu/VerticalContextMenu.java

@@ -1,5 +1,4 @@
-package com.noober.menu;
-
+package cn.wildfirechat.uikit.menu;
 
 import android.app.Activity;
 import android.content.Context;
@@ -24,6 +23,8 @@ import android.widget.TextView;
 
 import androidx.core.content.ContextCompat;
 
+import com.noober.menu.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -269,10 +270,13 @@ public class VerticalContextMenu extends PopupWindow {
         a.recycle();
     }
 
-    public void show(Point point) {
-        clickX = point.x;
-        clickY = point.y;
-        show();
+    public void show(View anchorView) {
+        //it is must ,other wise 'setOutsideTouchable' will not work under Android5.0
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            setBackgroundDrawable(new BitmapDrawable());
+        }
+        menuLayout.setBackgroundResource(R.layout.popup_grid_menu);
+        showAsDropDown(anchorView, -20, 20, Gravity.RIGHT | Gravity.TOP);
     }
 
     public void show() {

+ 30 - 0
menu/src/main/java/cn/wildfirechat/uikit/menu/VerticalContextMenuItem.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2025 WildFireChat. All rights reserved.
+ */
+
+package cn.wildfirechat.uikit.menu;
+
+import android.view.View;
+
+public class VerticalContextMenuItem {
+
+    private String item;
+    private int itemResId = View.NO_ID;
+
+
+    public String getItem() {
+        return item;
+    }
+
+    public void setItem(String item) {
+        this.item = item;
+    }
+
+    public int getItemResId() {
+        return itemResId;
+    }
+
+    public void setItemResId(int itemResId) {
+        this.itemResId = itemResId;
+    }
+}

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

@@ -1,153 +0,0 @@
-/*
- * Copyright (c) 2025 WildFireChat. All rights reserved.
- */
-
-package com.noober.menu;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-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.core.content.ContextCompat;
-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[] mTargetViewLocation = new int[2];
-    private final int mWidth;
-    private final int mHeight;
-    private final HorizontalContextMenuAdapter listAdapter;
-    private final RecyclerView rvContent;
-    private final View mContentView;
-    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);
-        mContentView = contentView;
-        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));
-        }
-
-        // 设置分割线
-        Drawable dividerDrawable = ContextCompat.getDrawable(mContext, R.drawable.divider);
-        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(dividerDrawable);
-        rvContent.addItemDecoration(dividerItemDecoration);
-
-        mTargetView.getLocationInWindow(mTargetViewLocation);
-        int posX;
-        int targetViewStartX = mTargetViewLocation[0];
-        int targetViewCenterX = targetViewStartX + mTargetView.getWidth() / 2;
-        // 默认放在targetView的上面
-        int posY = mTargetViewLocation[1] - mHeight;
-
-        ImageView arrow = ivArrow;
-        // 状态栏高度
-        if (posY < Display.dip2px(mContext, 60)) {
-            posY = mTargetViewLocation[1] + mTargetView.getHeight();
-            if (posY > deviceHeight - mHeight) {
-                posY = deviceHeight - mHeight;
-            }
-
-            ivArrow.setVisibility(View.GONE);
-            ivArrowUp.setVisibility(View.VISIBLE);
-            arrow = ivArrowUp;
-        } else {
-            ivArrow.setVisibility(View.VISIBLE);
-            ivArrowUp.setVisibility(View.GONE);
-        }
-
-        posX = (targetViewStartX + mTargetView.getWidth() / 2) - mWidth / 2;
-
-        if (posX <= 0) {
-            posX = Display.dip2px(mContext, 10);
-            mWindow.showAtLocation(mTargetView, Gravity.LEFT | Gravity.TOP, posX, posY);
-        } else if (posX + mWidth >= deviceWidth - Display.dip2px(mContext, 10)) {
-            posX = Display.dip2px(mContext, 10);
-            mWindow.showAtLocation(mTargetView, Gravity.RIGHT | Gravity.TOP, posX, posY);
-        } else {
-            mWindow.showAtLocation(mTargetView, Gravity.LEFT | Gravity.TOP, posX, posY);
-        }
-
-        ImageView finalArrow = arrow;
-        mWindow.getContentView().post(() -> {
-            int[] location = new int[2];
-            mWindow.getContentView().getLocationOnScreen(location);
-            float arrowTranslationX = targetViewCenterX - location[0] - Display.dip2px(mContext, 10);
-            finalArrow.setTranslationX(arrowTranslationX);
-        });
-    }
-
-    public void dismiss() {
-        mWindow.dismiss();
-    }
-
-    public boolean isShowing() {
-        return mWindow.isShowing();
-    }
-
-}

+ 0 - 30
menu/src/main/java/com/noober/menu/VerticalContextMenuItem.java

@@ -1,30 +0,0 @@
-package com.noober.menu;
-
-import android.view.View;
-
-/**
- * Created by xiaoqi on 2017/12/19.
- */
-
-public class VerticalContextMenuItem {
-
-	private String item;
-	private int itemResId = View.NO_ID;
-
-
-	public String getItem() {
-		return item;
-	}
-
-	public void setItem(String item) {
-		this.item = item;
-	}
-
-	public int getItemResId() {
-		return itemResId;
-	}
-
-	public void setItemResId(int itemResId) {
-		this.itemResId = itemResId;
-	}
-}

+ 1 - 1
menu/src/main/res/layout/horizontal_context_menu.xml → menu/src/main/res/layout/popup_grid_menu.xml

@@ -28,7 +28,7 @@
         android:paddingEnd="12dp"
         android:scrollbars="none"
         tools:itemCount="3"
-        tools:listitem="@layout/horizontal_context_menu_item" />
+        tools:listitem="@layout/popup_grid_menu_item" />
 
     <ImageView
         android:id="@+id/iv_arrow"

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


+ 35 - 0
menu/src/main/res/layout/popup_list_menu.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/easy_alert_dialog_layout"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/iv_arrow_up"
+        android:layout_width="14dp"
+        android:layout_height="7dp"
+        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:background="@drawable/shape_color_4c4c4c_radius_8"
+        android:clipToOutline="true"
+        android:overScrollMode="never"
+        android:scrollbars="none"
+        tools:listitem="@layout/popup_list_menu_item" />
+
+    <ImageView
+        android:id="@+id/iv_arrow"
+        android:layout_width="14dp"
+        android:layout_height="7dp"
+        android:background="@drawable/ic_cm_arrow"
+        tools:ignore="ContentDescription" />
+
+</LinearLayout>

+ 34 - 0
menu/src/main/res/layout/popup_list_menu_item.xml

@@ -0,0 +1,34 @@
+<?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="160dp"
+    android:layout_height="50dp"
+    android:layout_gravity="center_vertical"
+    android:background="@drawable/horizontal_context_menu_item_bg_selector"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:paddingStart="10dp"
+    android:paddingEnd="10dp">
+
+    <ImageView
+        android:id="@+id/iv_pop_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:backgroundTint="#ffffff"
+        android:scaleType="centerInside"
+        tools:ignore="ContentDescription" />
+
+    <TextView
+        android:id="@+id/tv_pop_func"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:maxLines="1"
+        android:textColor="@android:color/white"
+        android:textSize="16sp"
+        tools:text="" />
+
+</LinearLayout>

+ 4 - 5
uikit/src/main/java/cn/wildfire/chat/kit/conversation/ConversationMessageAdapter.java

@@ -19,7 +19,7 @@ import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.afollestad.materialdialogs.MaterialDialog;
-import com.noober.menu.HorizontalContextMenu;
+import cn.wildfirechat.uikit.menu.PopupMenu;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -496,12 +496,11 @@ public class ConversationMessageAdapter extends RecyclerView.Adapter<RecyclerVie
 
                 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) {
                     items.add(new Pair<>(viewHolder.contextMenuIcon(fragment.getContext(), itemWrapper.contextMenuItem.tag()), viewHolder.contextMenuTitle(fragment.getContext(), itemWrapper.contextMenuItem.tag())));
                 }
 
-                new HorizontalContextMenu(fragment.getContext(), itemView.findViewWithTag("messageContentView"), items, position1 -> {
+                new PopupMenu(fragment.getContext(), items, position1 -> {
                     try {
                         ContextMenuItemWrapper menuItem = contextMenus.get(position1);
                         if (menuItem.contextMenuItem.confirm()) {
@@ -531,8 +530,8 @@ public class ConversationMessageAdapter extends RecyclerView.Adapter<RecyclerVie
                     } catch (InvocationTargetException e) {
                         e.printStackTrace();
                     }
-                }, 5, 0, 0, 0)
-                    .show();
+                })
+                    .showAsGridMenu(itemView.findViewWithTag("messageContentView"), 5);
                 return true;
             }
         };

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

@@ -15,8 +15,8 @@ import androidx.recyclerview.widget.RecyclerView;
 
 import com.afollestad.materialdialogs.DialogAction;
 import com.afollestad.materialdialogs.MaterialDialog;
-import com.noober.menu.OnMenuItemClickListener;
-import com.noober.menu.VerticalContextMenu;
+import cn.wildfirechat.uikit.menu.OnMenuItemClickListener;
+import cn.wildfirechat.uikit.menu.VerticalContextMenu;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;

BIN
webrtc/libwebrtc_2.aar