diff --git a/app/build.gradle b/app/build.gradle
index d0090d7..854371c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -175,6 +175,9 @@ dependencies {
     implementation 'org.greenrobot:eventbus:3.3.1'
     //loading缁勪欢
     implementation 'com.github.ybq:Android-SpinKit:1.4.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+    // 寮曞叆 Google 鐨勫脊鎬ф祦寮忓竷灞€搴?+    implementation 'com.google.android.flexbox:flexbox:3.0.0'
 
 
     testImplementation 'junit:junit:4.13.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1043556..49bd922 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,54 +1,34 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright 2016 Hippo Seven
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~     http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
--->
-<!--    android:requestLegacyExternalStorage="true"-->
+<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools">
 
+    <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <!-- Android 10 (API 29) 鍙婁互涓婄増鏈紝鍙兘闇€瑕佹坊鍔?-->
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 10 (API 29) 鍙婁互涓婄増鏈紝鍙兘闇€瑕佹坊鍔?-->
+    <uses-permission
+        android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
         tools:ignore="ScopedStorage" />
-
     <uses-permission android:name="android.permission.USE_BIOMETRIC" />
     <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <!--  鐢ㄤ簬鑾峰彇wifi鐘舵€? -->
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <!--  浣跨敤绯荤粺鑷甫涓嬭浇鍣? -->
-    <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
-    <!--  鑷姩瀹夎apk  -->
+    <uses-permission android:name="android.permission.CAMERA" /> <!-- 鐢ㄤ簬鑾峰彇wifi鐘舵€?-->
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 浣跨敤绯荤粺鑷甫涓嬭浇鍣?-->
+    <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" /> <!-- 鑷姩瀹夎apk -->
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
 
     <uses-feature
         android:glEsVersion="0x00020000"
-        android:required="true" />
-
-    <!--  榧犳爣閫傞厤  -->
+        android:required="true" /> <!-- 榧犳爣閫傞厤 -->
     <uses-feature
         android:name="android.hardware.type.pc"
         android:required="false" />
+
     <application
-        android:name="com.hippo.ehviewer.EhApplication"
+        android:name=".EhApplication"
         android:allowBackup="true"
         android:fullBackupContent="@xml/backup_scheme"
         android:hardwareAccelerated="true"
@@ -60,8 +40,13 @@
         android:roundIcon="@mipmap/ic_launcher_round"
         android:theme="@style/AppTheme"
         tools:ignore="UnusedAttribute">
-        <!--        tools:targetApi="n">-->
-
+        <activity
+            android:name=".ui.TagSelectorActivity"
+            android:label="鏍囩閫夋嫨"
+            android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
+            xmlns:tools="http://schemas.android.com/tools"
+            tools:replace="android:theme" />
+        <!-- tools:targetApi="n"> -->
         <uses-library
             android:name="org.apache.http.legacy"
             android:required="false" />
@@ -71,7 +56,7 @@
             android:value="2.1" />
 
         <activity
-            android:name="com.hippo.ehviewer.ui.splash.SplashActivity"
+            android:name=".ui.splash.SplashActivity"
             android:alwaysRetainTaskState="true"
             android:configChanges="orientation|keyboardHidden|screenSize|uiMode"
             android:exported="true"
@@ -81,24 +66,23 @@
             android:windowSoftInputMode="stateHidden|adjustPan">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.LAUNCHER" />
                 <!-- LAUNCHER -->
-                <!--                <category android:name="android.intent.category.HOME" />-->
+                <!-- <category android:name="android.intent.category.HOME" /> -->
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-
         <activity
-            android:name="com.hippo.ehviewer.ui.MainActivity"
+            android:name=".ui.MainActivity"
             android:configChanges="screenSize|uiMode"
             android:exported="true"
             android:launchMode="singleTask"
             android:theme="@style/AppTheme.Main"
             android:windowSoftInputMode="stateHidden">
-
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <!--                <category android:name="android.intent.category.LAUNCHER"/>-->
+                <!-- <category android:name="android.intent.category.LAUNCHER"/> -->
             </intent-filter>
 
             <meta-data
@@ -118,27 +102,27 @@
                 <data android:host="g.e-hentai.org" />
                 <data android:host="lofi.e-hentai.org" />
             </intent-filter>
-
             <intent-filter android:label="@string/keyword_search">
                 <action android:name="android.intent.action.SEND" />
+
                 <category android:name="android.intent.category.DEFAULT" />
+
                 <data android:mimeType="text/plain" />
             </intent-filter>
-
             <intent-filter android:label="@string/image_search">
                 <action android:name="android.intent.action.SEND" />
+
                 <category android:name="android.intent.category.DEFAULT" />
+
                 <data android:mimeType="image/*" />
             </intent-filter>
         </activity>
-
         <activity
-            android:name="com.hippo.ehviewer.ui.GalleryActivity"
+            android:name=".ui.GalleryActivity"
             android:configChanges="screenSize|uiMode"
             android:exported="true"
             android:launchMode="singleTask"
             android:theme="@style/AppTheme.Gallery">
-
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
 
@@ -156,7 +140,6 @@
                 <data android:mimeType="application/x-rar-compressed" />
                 <data android:mimeType="application/x-zip-compressed" />
             </intent-filter>
-
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
 
@@ -170,7 +153,6 @@
                 <data android:pathPattern=".*\\.rar" />
                 <data android:pathPattern=".*\\.zip" />
             </intent-filter>
-
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
 
@@ -185,110 +167,94 @@
                 <data android:pathPattern=".*\\.rar" />
                 <data android:pathPattern=".*\\.zip" />
             </intent-filter>
-
             <intent-filter>
-                <action android:name="com.samsung.android.support.REMOTE_ACTION"/>
+                <action android:name="com.samsung.android.support.REMOTE_ACTION" />
             </intent-filter>
 
             <meta-data
                 android:name="com.samsung.android.support.REMOTE_ACTION"
-                android:resource="@xml/s_pen_actions"/>
+                android:resource="@xml/s_pen_actions" />
         </activity>
-
         <activity
-            android:name="com.hippo.ehviewer.ui.SettingsActivity"
+            android:name=".ui.SettingsActivity"
             android:configChanges="screenSize|uiMode"
             android:theme="@style/AppTheme.Settings" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.ExcludedLanguagesActivity"
+            android:name=".ui.ExcludedLanguagesActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/excluded_languages"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.DirPickerActivity"
+            android:name=".ui.DirPickerActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/dir_picker"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.LicenseActivity"
+            android:name=".ui.LicenseActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/license"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.FilterActivity"
+            android:name=".ui.FilterActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/filter"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.BlackListActivity"
+            android:name=".ui.BlackListActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/blacklist"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.SetSecurityActivity"
+            android:name=".ui.SetSecurityActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/set_pattern_protection"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.UConfigActivity"
+            android:name=".ui.UConfigActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/u_config"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.MyTagsActivity"
+            android:name=".ui.MyTagsActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/my_tags"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.HostsActivity"
+            android:name=".ui.HostsActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/hosts"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
+            android:name=".shortcuts.ShortcutsActivity"
             android:excludeFromRecents="true"
             android:launchMode="singleTask"
             android:taskAffinity=""
             android:theme="@android:style/Theme.Translucent.NoTitleBar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.wifi.WiFiServerActivity"
+            android:name=".ui.wifi.WiFiServerActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/wifi_server"
             android:theme="@style/AppTheme.Toolbar" />
-
         <activity
-            android:name="com.hippo.ehviewer.ui.wifi.WiFiClientActivity"
+            android:name=".ui.wifi.WiFiClientActivity"
             android:configChanges="screenSize|uiMode"
             android:label="@string/wifi_client"
             android:theme="@style/AppTheme.Toolbar" />
 
         <service
-            android:name="com.hippo.ehviewer.download.DownloadService"
-            android:label="@string/download_service_label"
-            android:foregroundServiceType="dataSync" />
+            android:name=".download.DownloadService"
+            android:foregroundServiceType="dataSync"
+            android:label="@string/download_service_label" />
 
         <provider
             android:name="com.hippo.content.FileProvider"
             android:authorities="${applicationId}.fileprovider"
             android:exported="false"
             android:grantUriPermissions="true">
-
             <meta-data
                 android:name="com.hippo.content.FILE_PROVIDER_PATHS"
                 android:resource="@xml/filepaths" />
-
         </provider>
-
     </application>
-</manifest>
+
+</manifest>
\ No newline at end of file
diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.java b/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.java
index 89a97d3..b364f16 100644
--- a/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.java
+++ b/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.java
@@ -452,4 +452,7 @@ public class EhTagDatabase {
 
         return searchList;
     }
+    public List<Tag> getTagList() {
+        return tagList;
+    }
 }
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/TagSelectorActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/TagSelectorActivity.java
new file mode 100644
index 0000000..1da251a
--- /dev/null
+++ b/app/src/main/java/com/hippo/ehviewer/ui/TagSelectorActivity.java
@@ -0,0 +1,450 @@
+package com.hippo.ehviewer.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.hippo.ehviewer.R;
+import com.hippo.ehviewer.client.EhTagDatabase;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class TagSelectorActivity extends AppCompatActivity {
+
+    private RecyclerView recyclerView;
+    private TagAdapter adapter;
+    private TextView tvSelectedCount;
+
+    private final Set<String> selectedTags = new HashSet<>();
+    private List<TagGroup> allGroups = new ArrayList<>();
+    private List<Object> displayList = new ArrayList<>();
+
+    private EhTagDatabase ehTags;
+
+    private static final String[] LOAD_PREFIXES = {
+            "",
+            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+            "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+            "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_tag_selector);
+
+        ehTags = EhTagDatabase.getInstance(this);
+
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+            getSupportActionBar().setTitle("鏍囩绛涢€?);
+        }
+        toolbar.setNavigationOnClickListener(v -> finish());
+
+        tvSelectedCount = findViewById(R.id.tv_selected_count);
+        recyclerView = findViewById(R.id.rv_tag_list);
+
+        initGroupsStructure();
+        buildDisplayList();
+
+        GridLayoutManager layoutManager = new GridLayoutManager(this, 3);
+        layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+            @Override
+            public int getSpanSize(int position) {
+                if (position >= displayList.size()) return 1;
+                int type = adapter.getItemViewType(position);
+                return (type == TagAdapter.TYPE_HEADER || type == TagAdapter.TYPE_FOOTER) ? 3 : 1;
+            }
+        });
+        recyclerView.setLayoutManager(layoutManager);
+
+        adapter = new TagAdapter();
+        recyclerView.setAdapter(adapter);
+
+        findViewById(R.id.btn_confirm_search).setOnClickListener(v -> {
+            StringBuilder sb = new StringBuilder();
+            for (String tag : selectedTags) {
+                sb.append(tag).append(" ");
+            }
+            Intent resultIntent = new Intent();
+            resultIntent.putExtra("selected_tags", sb.toString().trim());
+            setResult(Activity.RESULT_OK, resultIntent);
+            finish();
+        });
+
+        updateInfo();
+    }
+
+    private void updateInfo() {
+        tvSelectedCount.setText("宸查€? " + selectedTags.size());
+    }
+
+    // =================================================================
+    //  鍒濆鍖栵細绉婚櫎 Misc锛屽彧淇濈暀鏈夋晥鍒嗙被
+    // =================================================================
+    private void initGroupsStructure() {
+        allGroups.clear();
+
+        // 1. Language
+        TagGroup lang = new TagGroup("Language (璇█)", "language");
+        lang.addPinned("chinese", "涓枃");
+        lang.addPinned("english", "鑻辫");
+        lang.addPinned("japanese", "鏃ヨ");
+        lang.addPinned("korean", "闊╄");
+        lang.addPinned("russian", "淇勮");
+        lang.addPinned("spanish", "瑗跨彮鐗欒");
+        lang.addPinned("portuguese", "钁¤悇鐗欒");
+        lang.addPinned("thai", "娉拌");
+        lang.addPinned("vietnamese", "瓒婂崡璇?);
+
+        lang.initialCount = 9;
+        lang.visibleCount = 9;
+        allGroups.add(lang);
+
+        // 2. 鍏朵粬鍒嗙被 (绉婚櫎浜?Misc)
+        allGroups.add(new TagGroup("Female (濂虫€?", "female", 12));
+        allGroups.add(new TagGroup("Male (鐢锋€?", "male", 12));
+        allGroups.add(new TagGroup("Parody (鍘熶綔)", "parody", 12));
+        allGroups.add(new TagGroup("Character (瑙掕壊)", "character", 12));
+        allGroups.add(new TagGroup("Group (绀惧洟)", "group", 12));
+        allGroups.add(new TagGroup("Artist (鐢诲笀)", "artist", 12));
+        allGroups.add(new TagGroup("Cosplayer", "cosplayer", 12));
+
+        // 瑙﹀彂鑷姩鍔犺浇
+        for (TagGroup group : allGroups) {
+            if (!group.namespace.equals("language")) {
+                loadNextBatch(group);
+            }
+        }
+    }
+
+    private void loadNextBatch(TagGroup group) {
+        int targetCount = group.visibleCount + 12;
+
+        if (group.loadedTags.size() >= targetCount) {
+            group.visibleCount = targetCount;
+            refreshList();
+        } else {
+            group.visibleCount = targetCount;
+            if (group.prefixIndex < LOAD_PREFIXES.length) {
+                new LoadTagsTask(this, group).execute();
+            } else {
+                group.visibleCount = group.loadedTags.size();
+                refreshList();
+            }
+        }
+    }
+
+    private void collapseGroup(TagGroup group) {
+        group.visibleCount = group.initialCount;
+        refreshList();
+    }
+
+    private static class LoadTagsTask extends AsyncTask<Void, Void, List<TagItem>> {
+        private final WeakReference<TagSelectorActivity> activityRef;
+        private final TagGroup group;
+        private final String prefixToSearch;
+
+        LoadTagsTask(TagSelectorActivity activity, TagGroup group) {
+            activityRef = new WeakReference<>(activity);
+            this.group = group;
+            String p = LOAD_PREFIXES[group.prefixIndex];
+            this.prefixToSearch = group.namespace + ":" + p;
+        }
+
+        @Override
+        protected List<TagItem> doInBackground(Void... voids) {
+            TagSelectorActivity activity = activityRef.get();
+            if (activity == null || activity.ehTags == null) return new ArrayList<>();
+
+            List<TagItem> newItems = new ArrayList<>();
+            EhTagDatabase db = activity.ehTags;
+            List<Pair<String, String>> resultPairs = db.suggest(prefixToSearch);
+
+            if (resultPairs != null) {
+                for (Pair<String, String> pair : resultPairs) {
+                    String val1 = pair.first;
+                    String val2 = pair.second;
+                    String searchKey = null;
+                    String displayName = null;
+
+                    if (val1 != null && val1.startsWith(group.namespace + ":")) {
+                        searchKey = val1; displayName = val2;
+                    } else if (val2 != null && val2.startsWith(group.namespace + ":")) {
+                        searchKey = val2; displayName = val1;
+                    } else {
+                        searchKey = val1; displayName = val2;
+                    }
+
+                    if (searchKey == null) continue;
+                    if (!searchKey.startsWith(group.namespace + ":")) continue;
+
+                    String shortName = searchKey;
+                    if (shortName.contains(":")) {
+                        shortName = shortName.substring(shortName.indexOf(":") + 1);
+                    }
+
+                    if (TextUtils.isEmpty(displayName) || displayName.equals(searchKey)) {
+                        displayName = shortName.substring(0, 1).toUpperCase() + shortName.substring(1);
+                    } else {
+                        if (displayName.contains(":")) {
+                            displayName = displayName.substring(displayName.indexOf(":") + 1);
+                        }
+                    }
+
+                    if (isChinese(searchKey)) continue;
+
+                    newItems.add(new TagItem(searchKey, displayName, false));
+                }
+            }
+            return newItems;
+        }
+
+        private boolean isChinese(String str) {
+            if (str == null) return false;
+            for (char c : str.toCharArray()) {
+                if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) return true;
+            }
+            return false;
+        }
+
+        @Override
+        protected void onPostExecute(List<TagItem> newItems) {
+            TagSelectorActivity activity = activityRef.get();
+            if (activity == null) return;
+
+            boolean addedAny = false;
+            for (TagItem item : newItems) {
+                if (!group.addedKeys.contains(item.key)) {
+                    group.loadedTags.add(item);
+                    group.addedKeys.add(item.key);
+                    addedAny = true;
+                }
+            }
+
+            group.prefixIndex++;
+            if (group.loadedTags.size() < group.visibleCount && group.prefixIndex < LOAD_PREFIXES.length) {
+                new LoadTagsTask(activity, group).execute();
+            } else {
+                if (group.visibleCount > group.loadedTags.size()) {
+                    group.visibleCount = group.loadedTags.size();
+                }
+                activity.refreshList();
+            }
+        }
+    }
+
+    private void buildDisplayList() {
+        displayList = new ArrayList<>();
+        for (TagGroup group : allGroups) {
+            displayList.add(group);
+            int limit = Math.min(group.visibleCount, group.loadedTags.size());
+            for (int i = 0; i < limit; i++) {
+                displayList.add(group.loadedTags.get(i));
+            }
+            displayList.add(new FooterItem(group));
+        }
+    }
+
+    private void refreshList() {
+        buildDisplayList();
+        adapter.notifyDataSetChanged();
+    }
+
+    static class TagGroup {
+        String title;
+        String namespace;
+        int initialCount;
+        int visibleCount = 0;
+        int prefixIndex = 0;
+        List<TagItem> loadedTags = new ArrayList<>();
+        HashSet<String> addedKeys = new HashSet<>();
+
+        TagGroup(String title, String namespace) {
+            this(title, namespace, 0);
+        }
+
+        TagGroup(String title, String namespace, int initCount) {
+            this.title = title;
+            this.namespace = namespace;
+            this.initialCount = initCount;
+            this.visibleCount = initCount;
+        }
+
+        void addPinned(String shortKey, String display) {
+            String fullKey = namespace + ":" + shortKey;
+            if (!addedKeys.contains(fullKey)) {
+                loadedTags.add(new TagItem(fullKey, display, true));
+                addedKeys.add(fullKey);
+            }
+        }
+    }
+
+    static class TagItem {
+        String key; String show; boolean isPinned;
+        TagItem(String k, String s, boolean p) { key = k; show = s; isPinned = p;}
+    }
+
+    static class FooterItem {
+        TagGroup group; FooterItem(TagGroup g) { group = g; }
+    }
+
+    class TagAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+        static final int TYPE_HEADER = 0;
+        static final int TYPE_ITEM = 1;
+        static final int TYPE_FOOTER = 2;
+
+        @Override
+        public int getItemViewType(int position) {
+            Object obj = displayList.get(position);
+            if (obj instanceof TagGroup) return TYPE_HEADER;
+            if (obj instanceof FooterItem) return TYPE_FOOTER;
+            return TYPE_ITEM;
+        }
+
+        @NonNull
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            if (viewType == TYPE_HEADER) {
+                TextView tv = new TextView(parent.getContext());
+                tv.setPadding(32, 48, 16, 16);
+                tv.setTextSize(18);
+                tv.setTextColor(Color.parseColor("#009688"));
+                tv.getPaint().setFakeBoldText(true);
+                return new RecyclerView.ViewHolder(tv) {};
+            }
+            else if (viewType == TYPE_FOOTER) {
+                RelativeLayout layout = new RelativeLayout(parent.getContext());
+                layout.setPadding(32, 24, 32, 24);
+
+                TextView tvExpand = new TextView(parent.getContext());
+                tvExpand.setId(View.generateViewId());
+                tvExpand.setTextSize(14);
+                tvExpand.setTextColor(Color.parseColor("#757575"));
+                tvExpand.setGravity(Gravity.CENTER);
+                tvExpand.setPadding(24, 16, 24, 16);
+
+                RelativeLayout.LayoutParams lpExpand = new RelativeLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+                lpExpand.addRule(RelativeLayout.CENTER_HORIZONTAL);
+                layout.addView(tvExpand, lpExpand);
+
+                TextView tvCollapse = new TextView(parent.getContext());
+                tvCollapse.setId(View.generateViewId());
+                tvCollapse.setText("鈻?鏀惰捣");
+                tvCollapse.setTextSize(12);
+                tvCollapse.setTextColor(Color.parseColor("#9E9E9E"));
+                tvCollapse.setGravity(Gravity.CENTER);
+                tvCollapse.setPadding(24, 16, 0, 16);
+
+                RelativeLayout.LayoutParams lpCollapse = new RelativeLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+                lpCollapse.addRule(RelativeLayout.ALIGN_PARENT_END);
+                lpCollapse.addRule(RelativeLayout.CENTER_VERTICAL);
+                layout.addView(tvCollapse, lpCollapse);
+
+                return new FooterViewHolder(layout, tvExpand, tvCollapse);
+            }
+            else {
+                View view = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_tag_chip, parent, false);
+                return new TagViewHolder(view);
+            }
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+            int type = getItemViewType(position);
+            Object obj = displayList.get(position);
+
+            if (type == TYPE_HEADER) {
+                TagGroup group = (TagGroup) obj;
+                ((TextView) holder.itemView).setText(group.title);
+            }
+            else if (type == TYPE_FOOTER) {
+                FooterItem footer = (FooterItem) obj;
+                TagGroup group = footer.group;
+                FooterViewHolder vh = (FooterViewHolder) holder;
+
+                boolean hasMore = group.prefixIndex < LOAD_PREFIXES.length;
+                boolean isExpanded = group.visibleCount > group.initialCount;
+
+                if (!hasMore && group.visibleCount >= group.loadedTags.size()) {
+                    vh.tvExpand.setText("--- End ---");
+                    vh.tvExpand.setEnabled(false);
+                } else {
+                    vh.tvExpand.setEnabled(true);
+                    vh.tvExpand.setText("鈻?鏇村");
+                    vh.tvExpand.setOnClickListener(v -> {
+                        vh.tvExpand.setText("Loading...");
+                        loadNextBatch(group);
+                    });
+                }
+
+                if (isExpanded) {
+                    vh.tvCollapse.setVisibility(View.VISIBLE);
+                    vh.tvCollapse.setOnClickListener(v -> collapseGroup(group));
+                } else {
+                    vh.tvCollapse.setVisibility(View.GONE);
+                }
+            }
+            else {
+                TagItem item = (TagItem) obj;
+                TagViewHolder vh = (TagViewHolder) holder;
+                vh.tv.setText(item.show);
+                boolean isSelected = selectedTags.contains(item.key);
+                vh.tv.setSelected(isSelected);
+                vh.tv.setTextColor(isSelected ? Color.WHITE :
+                        (item.isPinned ? Color.BLACK : Color.parseColor("#616161")));
+
+                vh.itemView.setOnClickListener(v -> {
+                    if (isSelected) selectedTags.remove(item.key);
+                    else selectedTags.add(item.key);
+                    notifyItemChanged(position);
+                    updateInfo();
+                });
+            }
+        }
+
+        @Override
+        public int getItemCount() { return displayList.size(); }
+
+        class TagViewHolder extends RecyclerView.ViewHolder {
+            TextView tv;
+            TagViewHolder(View v) { super(v); tv = v.findViewById(R.id.tv_tag_name); }
+        }
+
+        class FooterViewHolder extends RecyclerView.ViewHolder {
+            TextView tvExpand;
+            TextView tvCollapse;
+            FooterViewHolder(View v, TextView expand, TextView collapse) {
+                super(v);
+                this.tvExpand = expand;
+                this.tvCollapse = collapse;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/gallery/list/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/gallery/list/GalleryListScene.java
index 91fdf7c..5be9e60 100644
--- a/app/src/main/java/com/hippo/ehviewer/ui/scene/gallery/list/GalleryListScene.java
+++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/gallery/list/GalleryListScene.java
@@ -48,6 +48,8 @@ import android.widget.PopupWindow;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -97,6 +99,7 @@ import com.hippo.ehviewer.event.SomethingNeedRefresh;
 import com.hippo.ehviewer.ui.CommonOperations;
 import com.hippo.ehviewer.ui.GalleryActivity;
 import com.hippo.ehviewer.ui.MainActivity;
+import com.hippo.ehviewer.ui.TagSelectorActivity;
 import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
 import com.hippo.ehviewer.ui.scene.BaseScene;
 import com.hippo.ehviewer.ui.scene.EhCallback;
@@ -180,6 +183,7 @@ public final class GalleryListScene extends BaseScene
     /*---------------
      Whole life cycle
      ---------------*/
+
     @Nullable
     private EhClient mClient;
     @Nullable
@@ -459,6 +463,7 @@ public final class GalleryListScene extends BaseScene
         EventBus.getDefault().unregister(this);
     }
 
+
     private void setSearchBarHint(Context context, SearchBar searchBar) {
         Resources resources = context.getResources();
         Drawable searchImage = DrawableManager.getVectorDrawable(context, R.drawable.v_magnify_x24);
@@ -475,6 +480,7 @@ public final class GalleryListScene extends BaseScene
     }
 
     private void setSearchBarSuggestionProvider(SearchBar searchBar) {
+        // --- 鍘熸湁浠ｇ爜淇濇寔涓嶅彉 ---
         searchBar.setSuggestionProvider(text -> {
             GalleryDetailUrlParser.Result result1 = GalleryDetailUrlParser.parse(text, false);
             if (result1 != null) {
@@ -486,7 +492,29 @@ public final class GalleryListScene extends BaseScene
             }
             return null;
         });
+        // --- 鍘熸湁浠ｇ爜缁撴潫 ---
+
+
     }
+    // 鐩殑锛氬缓绔嬩竴涓紓姝ョ洃鍚櫒锛屽儚鍚庣鐩戝惉 MQ 娑堟伅涓€鏍凤紝绛夊緟 TagSelectorActivity 鐨勮繑鍥炪€?+    private final ActivityResultLauncher<Intent> mTagSelectorLauncher = registerForActivityResult(
+            new ActivityResultContracts.StartActivityForResult(),
+            result -> {
+                // 閫昏緫锛氬彧鏈夊綋鐢ㄦ埛鍦ㄦ爣绛鹃〉鐐逛簡鈥滃紑濮嬫悳绱⑩€濓紙RESULT_OK锛夋椂鎵嶆墽琛?+                if (result.getResultCode() == android.app.Activity.RESULT_OK && result.getData() != null) {
+                    // 浠庝俊灏侀噷鍙栧嚭鏆楀彿 "tags_result"
+                    String selectedTags = result.getData().getStringExtra("selected_tags");
+
+                    if (mSearchBar != null && !TextUtils.isEmpty(selectedTags)) {
+                        // 1. 鎶婃爣绛惧～鍏ヤ富椤垫悳绱㈡
+                        mSearchBar.setText(selectedTags);
+                        // 2. 銆愭牳蹇冦€戣Е鍙戞悳绱€傝繖鐩稿綋浜庡湪鍚庣璋冪敤浜?searchService.execute()
+                        onApplySearch(selectedTags);
+                    }
+                }
+            }
+    );
+
 
     @Nullable
     private static String getSuitableTitleForUrlBuilder(
@@ -1345,6 +1373,7 @@ public final class GalleryListScene extends BaseScene
         if (STATE_NORMAL == mState) {
             view.toggle();
         }
+
     }
 
     private void showGoToDialog() {
@@ -1727,11 +1756,10 @@ public final class GalleryListScene extends BaseScene
         if (null == mSearchBar) {
             return;
         }
-
         if (mSearchBar.getState() == SearchBar.STATE_NORMAL) {
             setState(STATE_SEARCH);
         } else {
-            // Clear
+            // 鍙充晶鍥炬爣鎭㈠涓哄師鏈殑锛氭竻绌烘悳绱㈡鍐呭
             mSearchBar.setText("");
         }
     }
@@ -1847,6 +1875,15 @@ public final class GalleryListScene extends BaseScene
         startActivityForResult(Intent.createChooser(intent,
                 getString(R.string.select_image)), REQUEST_CODE_SELECT_IMAGE);
     }
+    @Override
+    public void onOpenTagSelector() {
+        Context context = getContext();
+        if (context != null) {
+            Intent intent = new Intent(context, TagSelectorActivity.class);
+            // 浣跨敤涔嬪墠瀹氫箟濂界殑鍚姩鍣ㄦ墦寮€椤甸潰
+            mTagSelectorLauncher.launch(intent);
+        }
+    }
 
     // SearchBarMover.Helper
     @Override
@@ -1884,12 +1921,17 @@ public final class GalleryListScene extends BaseScene
 
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (REQUEST_CODE_SELECT_IMAGE == requestCode) {
-            if (Activity.RESULT_OK == resultCode && null != mSearchLayout && null != data) {
-                mSearchLayout.setImageUri(data.getData());
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == 1001 && resultCode == Activity.RESULT_OK && data != null) {
+            String tags = data.getStringExtra("selected_tags");
+            if (!TextUtils.isEmpty(tags) && mSearchBar != null) {
+                // 璁剧疆鎼滅储鏂囧瓧
+                mSearchBar.setText(tags);
+
+                // 銆愭牳蹇冧慨姝ｃ€戣皟鐢?Scene 鍐呴儴鐨勫叕寮€鎼滅储澶勭悊鏂规硶
+                // 纭繚浣犵殑绫婚噷鏈夎繖涓柟娉曪紙閫氬父鏄?SearchBar.OnSearchListener 鐨勫洖璋冿級
+                onApplySearch(tags);
             }
-        } else {
-            super.onActivityResult(requestCode, resultCode, data);
         }
     }
 
diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java
index b7ff72b..5002691 100644
--- a/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java
+++ b/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java
@@ -27,6 +27,7 @@ import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 import android.widget.CompoundButton;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -110,6 +111,7 @@ public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnC
         super(context, attrs, defStyle);
         init(context);
     }
+    private Button mAdvancedTagSearchBtn;
 
     @SuppressLint("InflateParams")
     private void init(Context context) {
@@ -154,6 +156,9 @@ public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnC
         mActionView = mInflater.inflate(R.layout.search_action, null);
         mAction = (TextView) mActionView.findViewById(R.id.action);
         mAction.setOnClickListener(this);
+        //
+        mAdvancedTagSearchBtn = (Button) normalView.findViewById(R.id.btn_advanced_tag_search);
+        mAdvancedTagSearchBtn.setOnClickListener(this);
     }
 
     public void setHelper(Helper helper) {
@@ -332,6 +337,11 @@ public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnC
         } else if (mAction == v) {
             toggleSearchMode();
         }
+        else if (mAdvancedTagSearchBtn == v) {
+            if (mHelper != null) {
+                mHelper.onOpenTagSelector(); // 璋冪敤鎺ュ彛鏂规硶
+            }
+        }
     }
 
     private class SimpleHolder extends ViewHolder {
@@ -416,5 +426,6 @@ public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnC
     public interface Helper {
         void onChangeSearchMode();
         void onSelectImage();
+        void onOpenTagSelector();
     }
 }
diff --git a/app/src/main/res/drawable/bg_tag_selector.xml b/app/src/main/res/drawable/bg_tag_selector.xml
new file mode 100644
index 0000000..c4c894f
--- /dev/null
+++ b/app/src/main/res/drawable/bg_tag_selector.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true">
+        <shape android:shape="rectangle">
+            <solid android:color="#009688" />
+            <corners android:radius="16dp" />
+        </shape>
+    </item>
+
+    <item>
+        <shape android:shape="rectangle">
+            <stroke android:width="1dp" android:color="#BDBDBD" />
+            <solid android:color="#00000000" />
+            <corners android:radius="16dp" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_tag_chip.xml b/app/src/main/res/layout/item_tag_chip.xml
new file mode 100644
index 0000000..9e54399
--- /dev/null
+++ b/app/src/main/res/layout/item_tag_chip.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tv_tag_name"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="4dp"
+    android:background="@drawable/bg_tag_selector"
+    android:gravity="center"
+    android:paddingStart="8dp"
+    android:paddingTop="6dp"
+    android:paddingEnd="8dp"
+    android:paddingBottom="6dp"
+    android:text="Tag Name"
+    android:textColor="@color/primary_text_default_material_light"
+    android:textSize="14sp" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_normal.xml b/app/src/main/res/layout/search_normal.xml
index b4db5c0..13f5f9c 100644
--- a/app/src/main/res/layout/search_normal.xml
+++ b/app/src/main/res/layout/search_normal.xml
@@ -9,6 +9,7 @@
   ~      http://www.apache.org/licenses/LICENSE-2.0
   ~
   ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
@@ -21,12 +22,27 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
+    <!-- 1. 椤堕儴鐨勫垎绫昏壊鍧楄〃 -->
     <com.hippo.ehviewer.widget.CategoryTable
         android:id="@+id/search_category_table"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="@dimen/search_item_interval"/>
 
+    <!-- ============ 銆愬湪杩欓噷鎻掑叆浣犵殑澶氭爣绛炬悳绱㈡寜閽€?============ -->
+    <Button
+        android:id="@+id/btn_advanced_tag_search"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="8dp"
+        android:text="Multi-tag Search"
+        android:textStyle="bold"
+        android:textColor="#FFFFFF"
+        android:backgroundTint="?attr/colorPrimary"
+        style="@style/Widget.AppCompat.Button.Colored"/>
+    <!-- ======================================================== -->
+
+    <!-- 2. 涓棿鐨勬悳绱㈡ā寮忓崟閫夋 -->
     <com.hippo.widget.RadioGridGroup
         android:id="@+id/normal_search_mode"
         android:layout_width="wrap_content"
@@ -62,6 +78,7 @@
 
     </com.hippo.widget.RadioGridGroup>
 
+    <!-- 3. 搴曢儴鐨勫府鍔╁浘鏍囧拰楂樼骇閫夐」寮€鍏?-->
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -87,4 +104,4 @@
             android:text="@string/search_enable_advance"/>
 
     </LinearLayout>
-</LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3e2a1f3..872c25b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -566,8 +566,8 @@
     <string name="settings_eh_sign_out_tip">You have signed out. Restart the application to sign in again</string>
     <string name="settings_eh_identity_cookies">Identity cookies</string>
     <string name="settings_eh_identity_cookies_summary">Cookies can be used to sign in</string>
-<!--    <string name="settings_eh_lock_cookies_igneous">Lock the Cookie igneous field</string>-->
-<!--    <string name="settings_eh_lock_cookies_igneous_summary">It is not recommended to enable it except in special circumstances.</string>-->
+    <!--    <string name="settings_eh_lock_cookies_igneous">Lock the Cookie igneous field</string>-->
+    <!--    <string name="settings_eh_lock_cookies_igneous_summary">It is not recommended to enable it except in special circumstances.</string>-->
     <string name="settings_eh_identity_cookies_signed">Identity cookies can be used to sign in to this account.&lt;br&gt;&lt;b&gt;KEEP IT SAFE&lt;/b&gt;&lt;br&gt;&lt;br&gt;%s</string>
     <string name="settings_eh_identity_cookies_tourist">Not signed in.</string>
     <string name="settings_eh_identity_cookies_copy">Copy</string>
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ba711a8..a7543fa 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Mon Aug 01 20:20:36 SGT 2022
+#Thu Jan 29 16:22:23 CST 2026
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
+distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
