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

feat: 文字选择菜单优化

Merge PR #1739: 文字选择菜单优化 (含线程安全和生命周期修复)
Kudomaga 4 месяцев назад
Родитель
Сommit
1a3a48a4d3
20 измененных файлов с 757 добавлено и 517 удалено
  1. 0 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  2. 0 6
      app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt
  3. 0 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt
  4. 140 88
      app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt
  5. 15 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt
  6. 1 14
      app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt
  7. 3 9
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  8. 133 55
      app/src/main/java/io/legado/app/ui/book/read/TextActionSelectionMenu.kt
  9. 0 25
      app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt
  10. 433 41
      app/src/main/java/io/legado/app/ui/book/read/sheet/TextSelectMenuConfigSheet.kt
  11. 0 4
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfig.kt
  12. 0 4
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt
  13. 0 32
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt
  14. 0 16
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt
  15. 0 5
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadMenuConfig.kt
  16. 0 189
      app/src/main/java/io/legado/app/ui/config/readConfig/TextSelectMenuFilterSheet.kt
  17. 8 5
      app/src/main/res/values-zh-rCN/strings.xml
  18. 8 5
      app/src/main/res/values-zh-rHK/strings.xml
  19. 8 5
      app/src/main/res/values-zh-rTW/strings.xml
  20. 8 5
      app/src/main/res/values/strings.xml

+ 0 - 1
app/src/main/java/io/legado/app/constant/PreferKey.kt

@@ -135,7 +135,6 @@ object PreferKey {
     const val nightBrightness = "nightBrightness"
     const val expandTextMenu = "expandTextMenu"
     const val showSelectMenuIcon = "showSelectMenuIcon"
-    const val textSelectMenuFilter = "textSelectMenuFilter"
     const val textSelectMenuConfig = "textSelectMenuConfig"
     const val doublePageHorizontal = "doubleHorizontalPage"
     const val readUrlOpenInBrowser = "readUrlInBrowser"

+ 0 - 6
app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt

@@ -53,7 +53,6 @@ data class ReadPreferences(
     val disableReturnKey: Boolean = false,
     val expandTextMenu: Boolean = false,
     val showSelectMenuIcon: Boolean = true,
-    val textSelectMenuFilter: String = "",
     val showReadTitleAddition: Boolean = true,
     val autoReadSpeed: Int = 10,
     val prevKeys: String = "",
@@ -239,9 +238,6 @@ class ReadSettingsRepository(
     suspend fun setShowSelectMenuIcon(value: Boolean) =
         settingsRepository.putBoolean(PreferKey.showSelectMenuIcon, value)
 
-    suspend fun setTextSelectMenuFilter(value: String) =
-        settingsRepository.putString(PreferKey.textSelectMenuFilter, value)
-
     suspend fun setShowReadTitleAddition(value: Boolean) =
         settingsRepository.putBoolean(PreferKey.showReadTitleAddition, value)
 
@@ -434,7 +430,6 @@ class ReadSettingsRepository(
             disableReturnKey = this[Keys.DisableReturnKey] ?: false,
             expandTextMenu = this[Keys.ExpandTextMenu] ?: false,
             showSelectMenuIcon = this[Keys.ShowSelectMenuIcon] ?: true,
-            textSelectMenuFilter = this[Keys.TextSelectMenuFilter] ?: "",
             showReadTitleAddition = this[Keys.ShowReadTitleAddition] ?: true,
             autoReadSpeed = this[Keys.AutoReadSpeed] ?: 10,
             prevKeys = this[Keys.PrevKeys] ?: "",
@@ -535,7 +530,6 @@ class ReadSettingsRepository(
         val DisableReturnKey = booleanPreferencesKey(PreferKey.disableReturnKey)
         val ExpandTextMenu = booleanPreferencesKey(PreferKey.expandTextMenu)
         val ShowSelectMenuIcon = booleanPreferencesKey(PreferKey.showSelectMenuIcon)
-        val TextSelectMenuFilter = stringPreferencesKey(PreferKey.textSelectMenuFilter)
         val ShowReadTitleAddition = booleanPreferencesKey(PreferKey.showReadTitleAddition)
         val AutoReadSpeed = intPreferencesKey(PreferKey.autoReadSpeed)
         val PrevKeys = stringPreferencesKey(PreferKey.prevKeys)

+ 0 - 4
app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt

@@ -857,7 +857,6 @@ sealed interface ReadBookSheet {
     data object ParagraphIntervalConfig : ReadBookSheet
     data object ClickActionConfig : ReadBookSheet
     data object PageKeyConfig : ReadBookSheet
-    data object TextSelectMenuFilterConfig : ReadBookSheet
     data object InfoConfig : ReadBookSheet
     data class Dict(val word: String) : ReadBookSheet
     data class Bookmark(
@@ -1380,9 +1379,6 @@ sealed interface ConfigUpdate {
     data class ShowSelectMenuIcon(val value: Boolean) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }
-    data class TextSelectMenuFilter(val value: String) : ConfigUpdate {
-        override val actions = emptySet<ConfigUpdateAction>()
-    }
     data class ShowReadTitleAddition(val value: Boolean) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }

+ 140 - 88
app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt

@@ -1,6 +1,8 @@
 package io.legado.app.ui.book.read
 
 import android.annotation.SuppressLint
+import android.app.SearchManager
+import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.os.Build
 import android.view.Gravity
@@ -12,6 +14,7 @@ import android.view.WindowInsets
 import android.view.WindowManager
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.ContextCompat
+import androidx.core.net.toUri
 import androidx.core.view.HapticFeedbackConstantsCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.doOnAttach
@@ -51,28 +54,27 @@ import io.legado.app.utils.GSON
 import io.legado.app.utils.buildMainHandler
 import io.legado.app.utils.fromJsonObject
 import io.legado.app.utils.invisible
+import io.legado.app.utils.isAbsUrl
 import io.legado.app.utils.longToastOnUi
 import io.legado.app.utils.navigationBarGravity
+import io.legado.app.utils.printOnDebug
+import io.legado.app.utils.sendToClip
 import io.legado.app.utils.setLightStatusBar
 import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
+import io.legado.app.utils.share
 import io.legado.app.utils.sysBattery
 import io.legado.app.utils.sysScreenOffTime
 import io.legado.app.utils.throttle
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.visible
-import io.legado.app.utils.sendToClip
-import io.legado.app.utils.share
-import io.legado.app.utils.isAbsUrl
-import io.legado.app.utils.printOnDebug
-import android.app.SearchManager
-import android.content.Context
-import android.content.Intent
-import androidx.core.net.toUri
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
 
 
 /**
@@ -118,8 +120,13 @@ class ReadBookController(
     private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
     private val _textMenuState = MutableStateFlow<TextMenuState?>(null)
     val textMenuState = _textMenuState.asStateFlow()
+    private var textMenuRequestVersion = 0L
+    private val menuMutex = Mutex()
+    @Volatile
+    private var cachedActionMenuItems: List<ActionMenuItem>? = null
 
     fun dismissTextActionMenu() {
+        textMenuRequestVersion++
         _textMenuState.value = null
     }
     private val popupAction by lazy { PopupAction(activity) }
@@ -183,6 +190,7 @@ class ReadBookController(
         }
         newRefs.readView.upTime()
         newRefs.readView.upBattery(activity.sysBattery)
+        refreshActionMenuItems()
     }
 
     fun onMenuVisibilityChanged(visible: Boolean) {
@@ -294,15 +302,30 @@ class ReadBookController(
 
     override fun showTextActionMenu() {
         val r = refs ?: return
-        _textMenuState.value = TextMenuState(
-            selectedText = selectedText,
-            startX = r.textMenuPosition.x.toInt(),
-            startTopY = r.textMenuPosition.y.toInt(),
-            startBottomY = r.cursorLeft.y.toInt() + r.cursorLeft.height,
-            endX = r.cursorRight.x.toInt(),
-            endBottomY = r.cursorRight.y.toInt() + r.cursorRight.height,
-            items = getActionMenuItems()
-        )
+        val text = selectedText
+        val startX = r.textMenuPosition.x.toInt()
+        val startTopY = r.textMenuPosition.y.toInt()
+        val startBottomY = r.cursorLeft.y.toInt() + r.cursorLeft.height
+        val endX = r.cursorRight.x.toInt()
+        val endBottomY = r.cursorRight.y.toInt() + r.cursorRight.height
+        val requestVersion = ++textMenuRequestVersion
+
+        activity.lifecycleScope.launch {
+            val items = getActionMenuItems()
+            val readView = refs?.readView
+            if (textMenuRequestVersion != requestVersion || readView?.isTextSelected != true) {
+                return@launch
+            }
+            _textMenuState.value = TextMenuState(
+                selectedText = text,
+                startX = startX,
+                startTopY = startTopY,
+                startBottomY = startBottomY,
+                endX = endX,
+                endBottomY = endBottomY,
+                items = items
+            )
+        }
     }
 
     override fun autoPageStop() {
@@ -503,10 +526,17 @@ class ReadBookController(
 
     override fun onTouch(v: View?, event: MotionEvent?): Boolean {
         val r = refs ?: return false
-        if (v == null || event == null || !r.readView.isTextSelected) {
+        if (v == null || event == null) {
             return false
         }
-        when (event.action) {
+        val action = event.action
+        if (!r.readView.isTextSelected
+            && action != MotionEvent.ACTION_UP
+            && action != MotionEvent.ACTION_CANCEL
+        ) {
+            return false
+        }
+        when (action) {
             MotionEvent.ACTION_DOWN -> dismissTextActionMenu()
             MotionEvent.ACTION_MOVE -> {
                 when (v.id) {
@@ -536,9 +566,14 @@ class ReadBookController(
                 }
             }
 
-            MotionEvent.ACTION_UP -> {
+            MotionEvent.ACTION_UP,
+            MotionEvent.ACTION_CANCEL -> {
                 r.readView.curPage.resetReverseCursor()
-                showTextActionMenu()
+                if (r.readView.isTextSelected) {
+                    showTextActionMenu()
+                } else {
+                    dismissTextActionMenu()
+                }
             }
         }
         return true
@@ -619,88 +654,101 @@ class ReadBookController(
         refs?.readView?.cancelSelect()
     }
 
-    fun getActionMenuItems(): List<ActionMenuItem> {
-        val items = mutableListOf<ActionMenuItem>()
-        items.add(ActionMenuItem(R.id.menu_copy, activity.getString(android.R.string.copy)))
-        items.add(ActionMenuItem(R.id.menu_share_str, activity.getString(R.string.share)))
-        items.add(ActionMenuItem(R.id.menu_browser, activity.getString(R.string.browser)))
-        items.add(ActionMenuItem(R.id.menu_aloud, activity.getString(R.string.read_aloud)))
-        items.add(ActionMenuItem(R.id.menu_bookmark, activity.getString(R.string.bookmark)))
-        items.add(ActionMenuItem(R.id.menu_dict, activity.getString(R.string.dict)))
-        items.add(ActionMenuItem(R.id.menu_replace, activity.getString(R.string.replace)))
-        items.add(ActionMenuItem(R.id.menu_edit, activity.getString(R.string.edit)))
-        items.add(ActionMenuItem(R.id.menu_ai_clean, activity.getString(R.string.ai_text_clean)))
-        items.add(ActionMenuItem(R.id.menu_ai_rewrite, activity.getString(R.string.ai_text_rewrite)))
-        items.add(ActionMenuItem(R.id.menu_search_content, activity.getString(R.string.search_content)))
-
-        val thirdPartyItems = mutableListOf<ActionMenuItem>()
-        kotlin.runCatching {
-            val pm = activity.packageManager
-            val filterSet = ReadConfig.textSelectMenuFilter.split(",").filter { it.isNotEmpty() }.toSet()
-            val intent = Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain")
-            val resolveInfos = pm.queryIntentActivities(intent, 0)
-            for (resolveInfo in resolveInfos) {
-                val componentName = "${resolveInfo.activityInfo.packageName}/${resolveInfo.activityInfo.name}"
-                if (filterSet.contains(componentName)) {
-                    continue
+    suspend fun getActionMenuItems(): List<ActionMenuItem> = withContext(IO) {
+        menuMutex.withLock {
+            cachedActionMenuItems?.let { return@withContext it }
+
+            val items = mutableListOf<ActionMenuItem>()
+            items.add(ActionMenuItem(R.id.menu_copy, activity.getString(android.R.string.copy)))
+            items.add(ActionMenuItem(R.id.menu_share_str, activity.getString(R.string.share)))
+            items.add(ActionMenuItem(R.id.menu_browser, activity.getString(R.string.browser)))
+            items.add(ActionMenuItem(R.id.menu_aloud, activity.getString(R.string.read_aloud)))
+            items.add(ActionMenuItem(R.id.menu_bookmark, activity.getString(R.string.bookmark)))
+            items.add(ActionMenuItem(R.id.menu_dict, activity.getString(R.string.dict)))
+            items.add(ActionMenuItem(R.id.menu_replace, activity.getString(R.string.replace)))
+            items.add(ActionMenuItem(R.id.menu_edit, activity.getString(R.string.edit)))
+            items.add(ActionMenuItem(R.id.menu_ai_clean, activity.getString(R.string.ai_text_clean)))
+            items.add(ActionMenuItem(R.id.menu_ai_rewrite, activity.getString(R.string.ai_text_rewrite)))
+            items.add(ActionMenuItem(R.id.menu_search_content, activity.getString(R.string.search_content)))
+
+            val thirdPartyItems = mutableListOf<ActionMenuItem>()
+            runCatching {
+                val pm = activity.packageManager
+                val intent = Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain")
+                val resolveInfos = pm.queryIntentActivities(intent, 0)
+                for (resolveInfo in resolveInfos) {
+                    val processIntent = Intent()
+                        .setAction(Intent.ACTION_PROCESS_TEXT)
+                        .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
+                        .setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)
+                    
+                    val title = resolveInfo.loadLabel(pm).toString()
+                    val icon = if (ReadConfig.showSelectMenuIcon) {
+                        runCatching { resolveInfo.loadIcon(pm) }.getOrNull()
+                    } else null
+
+                    thirdPartyItems.add(ActionMenuItem(id = -1, title = title, iconDrawable = icon, intent = processIntent))
                 }
-                val processIntent = Intent()
-                    .setAction(Intent.ACTION_PROCESS_TEXT)
-                    .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
-                    .setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)
-                
-                val title = resolveInfo.loadLabel(pm).toString()
-                val icon = if (ReadConfig.showSelectMenuIcon) {
-                    kotlin.runCatching { resolveInfo.loadIcon(pm) }.getOrNull()
-                } else null
-
-                thirdPartyItems.add(ActionMenuItem(id = -1, title = title, iconDrawable = icon, intent = processIntent))
             }
-        }
 
-        val allItems = items + thirdPartyItems
-        val configStr = ReadConfig.textSelectMenuConfig
-        if (configStr.isEmpty()) {
-            return allItems
+            val allItems = items + thirdPartyItems
+            val configStr = ReadConfig.textSelectMenuConfig
+            val result = if (configStr.isEmpty()) {
+                allItems
+            } else {
+                runCatching {
+                    val savedConfigs = GSON.fromJsonObject<List<SelectionMenuConfigItem>>(configStr).getOrNull() ?: emptyList()
+                    val savedMap = savedConfigs.associateBy { it.id }
+
+                    val sortedItems = mutableListOf<ActionMenuItem>()
+                    for (saved in savedConfigs) {
+                        val found = allItems.find { it.uniqueId == saved.id }
+                        if (found != null) {
+                            val resolvedShowState = saved.showState ?: if (saved.enabled == false) 1 else 0
+                            sortedItems.add(found.copy(showState = resolvedShowState))
+                        }
+                    }
+                    for (item in allItems) {
+                        val uniqueId = item.uniqueId
+                        if (!savedMap.containsKey(uniqueId)) {
+                            sortedItems.add(item)
+                        }
+                    }
+                    sortedItems
+                }.getOrDefault(allItems)
+            }
+            cachedActionMenuItems = result
+            result
         }
+    }
 
-        return kotlin.runCatching {
-            val savedConfigs = GSON.fromJsonObject<List<SelectionMenuConfigItem>>(configStr).getOrNull() ?: emptyList()
-            val savedMap = savedConfigs.associateBy { it.id }
-
-            val sortedItems = mutableListOf<ActionMenuItem>()
-            for (saved in savedConfigs) {
-                val found = allItems.find { it.uniqueId == saved.id }
-                if (found != null) {
-                    sortedItems.add(found.copy(enabled = saved.enabled))
-                }
+    fun refreshActionMenuItems() {
+        activity.lifecycleScope.launch {
+            menuMutex.withLock {
+                cachedActionMenuItems = null
             }
-            for (item in allItems) {
-                val uniqueId = item.uniqueId
-                if (!savedMap.containsKey(uniqueId)) {
-                    sortedItems.add(item)
-                }
+            val menuItems = getActionMenuItems()
+            _textMenuState.value?.let { currentState ->
+                _textMenuState.value = currentState.copy(items = menuItems)
             }
-            sortedItems
-        }.getOrDefault(allItems)
+        }
     }
 
     fun saveMenuConfig(items: List<ActionMenuItem>) {
         val configs = items.map { item ->
             SelectionMenuConfigItem(
                 id = item.uniqueId,
-                enabled = item.enabled
+                enabled = item.showState == 0,
+                showState = item.showState
             )
         }
         ReadConfig.textSelectMenuConfig = GSON.toJson(configs)
-        _textMenuState.value?.let { currentState ->
-            _textMenuState.value = currentState.copy(items = getActionMenuItems())
-        }
+        refreshActionMenuItems()
     }
 
     fun onTextMenuItemClick(item: ActionMenuItem) {
         if (item.intent != null) {
-            kotlin.runCatching {
+            runCatching {
                 item.intent.putExtra(Intent.EXTRA_PROCESS_TEXT, selectedText)
                 activity.startActivity(item.intent)
             }.onFailure { e ->
@@ -711,7 +759,7 @@ class ReadBookController(
                 R.id.menu_copy -> activity.sendToClip(selectedText)
                 R.id.menu_share_str -> activity.share(selectedText)
                 R.id.menu_browser -> {
-                    kotlin.runCatching {
+                    runCatching {
                         val intent = if (selectedText.isAbsUrl()) {
                             Intent(Intent.ACTION_VIEW).apply {
                                 data = selectedText.toUri()
@@ -1380,8 +1428,11 @@ data class ActionMenuItem(
     val title: String,
     val iconDrawable: android.graphics.drawable.Drawable? = null,
     val intent: Intent? = null,
-    val enabled: Boolean = true
+    val showState: Int = 0 // 0: 一级, 1: 折叠, 2: 隐藏
 ) {
+    val enabled: Boolean
+        get() = showState == 0
+
     val uniqueId: String
         get() = if (intent != null) {
             val comp = intent.component
@@ -1406,5 +1457,6 @@ data class ActionMenuItem(
 
 data class SelectionMenuConfigItem(
     val id: String,
-    val enabled: Boolean
+    val enabled: Boolean? = null,
+    val showState: Int? = null
 )

+ 15 - 4
app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt

@@ -43,6 +43,7 @@ import io.legado.app.ui.book.info.BookInfoActivity
 import io.legado.app.ui.book.read.page.ContentTextView
 import io.legado.app.ui.book.read.page.ReadView
 import io.legado.app.ui.book.read.page.entities.PageDirection
+import io.legado.app.ui.book.read.sheet.TextSelectMenuConfigSheet
 import io.legado.app.ui.book.searchContent.SearchContentResult
 import io.legado.app.ui.book.source.edit.BookSourceEditActivity
 import io.legado.app.ui.book.toc.TocActivityResult
@@ -50,7 +51,6 @@ import io.legado.app.ui.browser.WebViewActivity
 import io.legado.app.ui.login.SourceLoginActivity
 import io.legado.app.ui.replace.ReplaceEditRoute
 import io.legado.app.ui.replace.ReplaceRuleActivity
-import io.legado.app.ui.book.read.sheet.TextSelectMenuConfigSheet
 import io.legado.app.utils.StartActivityContract
 import io.legado.app.utils.takePersistablePermissionSafely
 import io.legado.app.utils.toastOnUi
@@ -489,6 +489,7 @@ fun ReadBookRouteScreen(
             textMenuState?.let { menuState ->
                 TextActionSelectionMenu(
                     menuState = menuState,
+                    expandTextMenu = readPreferences.expandTextMenu,
                     onDismiss = { controller.dismissTextActionMenu() },
                     onItemClick = { item -> controller.onTextMenuItemClick(item) },
                     onOpenManage = {
@@ -498,16 +499,26 @@ fun ReadBookRouteScreen(
                     }
                 )
             }
-            val configItems = remember(showSelectMenuConfigSheet) {
+            var configItems by remember { mutableStateOf<List<ActionMenuItem>>(emptyList()) }
+            LaunchedEffect(showSelectMenuConfigSheet) {
                 if (showSelectMenuConfigSheet) {
-                    controller.getActionMenuItems()
+                    configItems = controller.getActionMenuItems()
                 } else {
-                    emptyList()
+                    configItems = emptyList()
                 }
             }
             TextSelectMenuConfigSheet(
                 show = showSelectMenuConfigSheet,
                 items = configItems,
+                expandTextMenu = readPreferences.expandTextMenu,
+                showSelectMenuIcon = readPreferences.showSelectMenuIcon,
+                onExpandTextMenuChange = {
+                    viewModel.onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ExpandTextMenu(it)))
+                },
+                onShowSelectMenuIconChange = {
+                    viewModel.onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowSelectMenuIcon(it)))
+                    controller.refreshActionMenuItems()
+                },
                 onDismissRequest = { showSelectMenuConfigSheet = false },
                 onSaved = { items -> controller.saveMenuConfig(items) }
             )

+ 1 - 14
app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt

@@ -40,7 +40,6 @@ import io.legado.app.ui.book.read.sheet.SpeakEngineConfigSheet
 import io.legado.app.ui.book.read.sheet.TitleBarIconSheet
 import io.legado.app.ui.book.read.sheet.ToolButtonConfigSheet
 import io.legado.app.ui.book.read.sheet.UnderlineConfigSheet
-import io.legado.app.ui.config.readConfig.TextSelectMenuFilterSheet
 import io.legado.app.ui.dict.DictSheet
 import io.legado.app.ui.widget.components.FontFolderState
 import io.legado.app.ui.widget.components.FontSelectSheet
@@ -283,10 +282,6 @@ fun ReadBookScreen(
             onIntent(ReadBookIntent.DismissSheet)
             onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.PageKeyConfig))
         },
-        onOpenTextSelectMenuFilterConfig = {
-            onIntent(ReadBookIntent.DismissSheet)
-            onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.TextSelectMenuFilterConfig))
-        },
     )
     ReadAloudConfigSheet(
         show = state.activeSheet is ReadBookSheet.ReadAloudConfig,
@@ -406,15 +401,7 @@ fun ReadBookScreen(
             )
         }
 
-        is ReadBookSheet.TextSelectMenuFilterConfig -> {
-            TextSelectMenuFilterSheet(
-                show = true,
-                onDismissRequest = dismissSheet,
-                onFilterChanged = {
-                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TextSelectMenuFilter(it)))
-                }
-            )
-        }
+
 
         is ReadBookSheet.PageAnim -> {
             PageAnimConfigSheet(

+ 3 - 9
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -37,12 +37,12 @@ import io.legado.app.data.repository.ReadBookStyleConfigRepository
 import io.legado.app.data.repository.ReadPreferences
 import io.legado.app.data.repository.ReadSettingsRepository
 import io.legado.app.data.repository.UploadRepository
-import io.legado.app.domain.gateway.BookContentProcessGateway
 import io.legado.app.domain.gateway.AiPromptPresetGateway
-import io.legado.app.domain.model.TextProcessAction
-import io.legado.app.domain.model.TextProcessAnchor
+import io.legado.app.domain.gateway.BookContentProcessGateway
 import io.legado.app.domain.model.AiTaskType
 import io.legado.app.domain.model.ReadingProgress
+import io.legado.app.domain.model.TextProcessAction
+import io.legado.app.domain.model.TextProcessAnchor
 import io.legado.app.domain.usecase.AiTextFactoryUseCase
 import io.legado.app.domain.usecase.ChangeBookSourceUseCase
 import io.legado.app.domain.usecase.CleanSelectedTextUseCase
@@ -4970,12 +4970,6 @@ class ReadBookViewModel(
                     readSettingsRepository.setShowSelectMenuIcon(update.value)
                 }
             }
-            is ConfigUpdate.TextSelectMenuFilter -> {
-                ReadConfig.textSelectMenuFilter = update.value
-                viewModelScope.launch {
-                    readSettingsRepository.setTextSelectMenuFilter(update.value)
-                }
-            }
             is ConfigUpdate.ShowReadTitleAddition -> {
                 ReadConfig.showReadTitleAddition = update.value
                 viewModelScope.launch {

+ 133 - 55
app/src/main/java/io/legado/app/ui/book/read/TextActionSelectionMenu.kt

@@ -1,22 +1,40 @@
 package io.legado.app.ui.book.read
 
-import io.legado.app.R
 import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.MoreHoriz
+import androidx.compose.material.icons.filled.MoreVert
 import androidx.compose.material.icons.filled.Settings
-import androidx.compose.material3.VerticalDivider
 import androidx.compose.material3.Icon
-import androidx.compose.runtime.*
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalWindowInfo
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntRect
@@ -28,6 +46,7 @@ import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupPositionProvider
 import androidx.compose.ui.window.PopupProperties
 import coil.compose.AsyncImage
+import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.card.NormalCard
 import io.legado.app.ui.widget.components.text.AppText
@@ -40,14 +59,23 @@ enum class MenuMode {
 @Composable
 fun TextActionSelectionMenu(
     menuState: TextMenuState,
+    expandTextMenu: Boolean,
     onDismiss: () -> Unit,
     onItemClick: (ActionMenuItem) -> Unit,
     onOpenManage: () -> Unit
 ) {
+
+    val localDensity = LocalDensity.current
+    val containerSize = LocalWindowInfo.current.containerSize
+    val maxMenuWidth = with(localDensity){ containerSize.width.toDp() } - 32.dp
     var mode by remember { mutableStateOf(MenuMode.Quick) }
     val draftItems = menuState.items
+    
+    val primaryItems = remember(draftItems) { draftItems.filter { it.showState == 0 } }
+    val collapsedItems = remember(draftItems) { draftItems.filter { it.showState == 1 } }
+    val activeMultiItems = remember(draftItems) { draftItems.filter { it.showState == 0 || it.showState == 1 } }
 
-    val density = LocalDensity.current.density
+    val density = localDensity.density
     val positionProvider = remember(menuState, density) {
         TextMenuPositionProvider(
             density = density,
@@ -68,43 +96,97 @@ fun TextActionSelectionMenu(
             dismissOnClickOutside = true
         )
     ) {
-        val widthModifier = Modifier.widthIn(max = 300.dp)
 
         NormalCard(
-            modifier = widthModifier.wrapContentHeight(),
-            containerColor = LegadoTheme.colorScheme.surface,
+            modifier = Modifier.widthIn(max = maxMenuWidth),
+            containerColor = LegadoTheme.colorScheme.surfaceBright,
             elevation = 6.dp,
-            cornerRadius = 12.dp
+            cornerRadius = 12.dp,
         ) {
-            if (mode == MenuMode.Quick) {
-                QuickMenuView(
-                    items = draftItems,
+            if (expandTextMenu) {
+                MultiLineMenuView(
+                    items = activeMultiItems,
                     onItemClick = onItemClick,
-                    onMoreClick = { mode = MenuMode.More }
-                )
-            } else {
-                MoreMenuView(
-                    items = draftItems,
-                    onItemClick = onItemClick,
-                    onBack = { mode = MenuMode.Quick },
                     onManageClick = {
                         onDismiss()
                         onOpenManage()
                     }
                 )
+            } else {
+                if (mode == MenuMode.Quick) {
+                    QuickMenuView(
+                        items = primaryItems,
+                        hasMore = collapsedItems.isNotEmpty(),
+                        onItemClick = onItemClick,
+                        onMoreClick = { mode = MenuMode.More },
+                        onSettingsClick = {
+                            onDismiss()
+                            onOpenManage()
+                        }
+                    )
+                } else {
+                    MoreMenuView(
+                        items = collapsedItems,
+                        onItemClick = onItemClick,
+                        onBack = { mode = MenuMode.Quick },
+                        onManageClick = {
+                            onDismiss()
+                            onOpenManage()
+                        }
+                    )
+                }
             }
         }
     }
 }
 
 @Composable
-private fun QuickMenuView(
+private fun MultiLineMenuView(
     items: List<ActionMenuItem>,
     onItemClick: (ActionMenuItem) -> Unit,
-    onMoreClick: () -> Unit
+    onManageClick: () -> Unit
 ) {
-    val enabledItems = remember(items) { items.filter { it.enabled } }
+    FlowRow(
+        modifier = Modifier
+            .padding(all = 8.dp)
+            .heightIn(max = 300.dp)
+            .verticalScroll(rememberScrollState()),
+        horizontalArrangement = Arrangement.spacedBy(4.dp),
+        verticalArrangement = Arrangement.spacedBy(4.dp)
+    ) {
+        items.forEach { item ->
+            QuickMenuItem(item = item, onClick = { onItemClick(item) })
+        }
 
+        Row(
+            modifier = Modifier
+                .clickable(onClick = onManageClick)
+                .padding(horizontal = 12.dp, vertical = 8.dp),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Icon(
+                imageVector = Icons.Default.Settings,
+                contentDescription = stringResource(R.string.edit_menu_items),
+                tint = LegadoTheme.colorScheme.onSurface,
+                modifier = Modifier.size(16.dp)
+            )
+            Spacer(modifier = Modifier.width(4.dp))
+            AppText(
+                text = stringResource(R.string.edit_menu_items),
+                fontSize = 13.sp,
+            )
+        }
+    }
+}
+
+@Composable
+private fun QuickMenuView(
+    items: List<ActionMenuItem>,
+    hasMore: Boolean,
+    onItemClick: (ActionMenuItem) -> Unit,
+    onMoreClick: () -> Unit,
+    onSettingsClick: () -> Unit
+) {
     Row(
         modifier = Modifier
             .wrapContentWidth()
@@ -115,30 +197,31 @@ private fun QuickMenuView(
             modifier = Modifier
                 .weight(1f, fill = false)
                 .padding(vertical = 6.dp),
-            horizontalArrangement = Arrangement.spacedBy(4.dp),
             verticalAlignment = Alignment.CenterVertically,
         ) {
-            items(enabledItems, key = { it.uniqueId }) { item ->
+            items(items, key = { it.uniqueId }) { item ->
                 QuickMenuItem(item = item, onClick = { onItemClick(item) })
             }
         }
-        VerticalDivider(
-            color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.4f),
-            modifier = Modifier
-                .height(20.dp)
-                .width(1.dp)
-        )
+        if (items.isNotEmpty()) {
+            VerticalDivider(
+                color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.4f),
+                modifier = Modifier
+                    .height(20.dp)
+                    .width(1.dp)
+            )
+        }
         Box(
             modifier = Modifier
-                .clickable(onClick = onMoreClick)
-                .padding(horizontal = 12.dp, vertical = 8.dp),
+                .clickable(onClick = if (hasMore) onMoreClick else onSettingsClick)
+                .padding(horizontal = 10.dp, vertical = 8.dp),
             contentAlignment = Alignment.Center
         ) {
             Icon(
-                imageVector = Icons.Default.MoreHoriz,
-                contentDescription = stringResource(R.string.more_menu),
+                imageVector = if (hasMore) Icons.Default.MoreVert else Icons.Default.Settings,
+                contentDescription = stringResource(if (hasMore) R.string.more_menu else R.string.setting),
                 tint = LegadoTheme.colorScheme.onSurface,
-                modifier = Modifier.size(18.dp)
+                modifier = Modifier.size(16.dp)
             )
         }
     }
@@ -152,7 +235,7 @@ private fun QuickMenuItem(
     Row(
         modifier = Modifier
             .clickable(onClick = onClick)
-            .padding(horizontal = 12.dp, vertical = 8.dp),
+            .padding(horizontal = 10.dp, vertical = 8.dp),
         verticalAlignment = Alignment.CenterVertically
     ) {
         if (item.iconDrawable != null) {
@@ -161,13 +244,13 @@ private fun QuickMenuItem(
                 contentDescription = item.title,
                 modifier = Modifier
                     .size(16.dp)
-                    .padding(end = 4.dp)
             )
+
+            Spacer(modifier = Modifier.width(4.dp))
         }
         AppText(
             text = item.title,
             fontSize = 13.sp,
-            color = LegadoTheme.colorScheme.onSurface,
             maxLines = 1
         )
     }
@@ -180,25 +263,24 @@ private fun MoreMenuView(
     onBack: () -> Unit,
     onManageClick: () -> Unit
 ) {
-    val disabledItems = remember(items) { items.filter { !it.enabled } }
     val scrollState = rememberScrollState()
 
     Column(
         modifier = Modifier
             .width(IntrinsicSize.Max)
-            .padding(vertical = 4.dp)
+            .padding(vertical = 4.dp, horizontal = 10.dp)
     ) {
         Box(
             modifier = Modifier
                 .fillMaxWidth()
                 .clickable(onClick = onBack)
-                .padding(horizontal = 16.dp, vertical = 8.dp),
+                .padding(vertical = 8.dp),
             contentAlignment = Alignment.CenterStart
         ) {
             Icon(
                 imageVector = Icons.AutoMirrored.Filled.ArrowBack,
                 contentDescription = stringResource(R.string.back),
-                tint = LegadoTheme.colorScheme.onSurfaceVariant,
+                tint = LegadoTheme.colorScheme.onSurface,
                 modifier = Modifier.size(18.dp)
             )
         }
@@ -209,7 +291,7 @@ private fun MoreMenuView(
                 .verticalScroll(scrollState)
                 .fillMaxWidth()
         ) {
-            disabledItems.forEach { item ->
+            items.forEach { item ->
                 MoreMenuItem(item = item, onClick = { onItemClick(item) })
             }
         }
@@ -218,22 +300,20 @@ private fun MoreMenuView(
             modifier = Modifier
                 .fillMaxWidth()
                 .clickable(onClick = onManageClick)
-                .padding(horizontal = 16.dp, vertical = 10.dp),
+                .padding(vertical = 8.dp),
             verticalAlignment = Alignment.CenterVertically
         ) {
             Icon(
                 imageVector = Icons.Default.Settings,
                 contentDescription = stringResource(R.string.edit_menu_items),
-                tint = LegadoTheme.colorScheme.primary,
+                tint = LegadoTheme.colorScheme.onSurface,
                 modifier = Modifier
-                    .size(18.dp)
-                    .padding(end = 8.dp)
+                    .size(16.dp)
             )
+            Spacer(modifier = Modifier.width(8.dp))
             AppText(
                 text = stringResource(R.string.edit_menu_items),
                 fontSize = 13.sp,
-                color = LegadoTheme.colorScheme.primary,
-                fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
             )
         }
     }
@@ -248,7 +328,7 @@ private fun MoreMenuItem(
         modifier = Modifier
             .fillMaxWidth()
             .clickable(onClick = onClick)
-            .padding(horizontal = 16.dp, vertical = 10.dp),
+            .padding(vertical = 8.dp),
         verticalAlignment = Alignment.CenterVertically
     ) {
         if (item.iconDrawable != null) {
@@ -256,15 +336,13 @@ private fun MoreMenuItem(
                 model = item.iconDrawable,
                 contentDescription = item.title,
                 modifier = Modifier
-                    .size(18.dp)
-                    .padding(end = 8.dp)
+                    .size(16.dp)
             )
+            Spacer(modifier = Modifier.width(8.dp))
         }
         AppText(
             text = item.title,
             fontSize = 13.sp,
-            color = LegadoTheme.colorScheme.onSurface,
-            maxLines = 1
         )
     }
 }

+ 0 - 25
app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt

@@ -31,7 +31,6 @@ fun MoreConfigSheet(
     onIntent: (ReadBookIntent) -> Unit,
     onOpenClickRegionalConfig: () -> Unit,
     onOpenPageKeyConfig: () -> Unit,
-    onOpenTextSelectMenuFilterConfig: () -> Unit,
 ) {
     val readSettingsRepository: ReadSettingsRepository = koinInject()
     val preferences by readSettingsRepository.preferences.collectAsStateWithLifecycle(
@@ -145,16 +144,9 @@ fun MoreConfigSheet(
                 },
                 onOpenClickRegionalConfig = onOpenClickRegionalConfig,
                 onOpenPageKeyConfig = onOpenPageKeyConfig,
-                onOpenTextSelectMenuFilterConfig = onOpenTextSelectMenuFilterConfig,
                 onDisableReturnKeyChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.DisableReturnKey(it)))
                 },
-                onExpandTextMenuChange = {
-                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ExpandTextMenu(it)))
-                },
-                onShowSelectMenuIconChange = {
-                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowSelectMenuIcon(it)))
-                },
                 onShowReadTitleAdditionChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowReadTitleAddition(it)))
                 },
@@ -313,9 +305,6 @@ private fun OtherSettings(
     onOpenClickRegionalConfig: () -> Unit,
     onDisableReturnKeyChange: (Boolean) -> Unit,
     onOpenPageKeyConfig: () -> Unit,
-    onOpenTextSelectMenuFilterConfig: () -> Unit,
-    onExpandTextMenuChange: (Boolean) -> Unit,
-    onShowSelectMenuIconChange: (Boolean) -> Unit,
     onShowReadTitleAdditionChange: (Boolean) -> Unit,
     onShowMenuIconChange: (Boolean) -> Unit,
 ) {
@@ -384,20 +373,6 @@ private fun OtherSettings(
         title = stringResource(R.string.custom_page_key),
         onClick = onOpenPageKeyConfig,
     )
-    TinyClickableSettingItem(
-        title = stringResource(R.string.text_select_menu_filter),
-        onClick = onOpenTextSelectMenuFilterConfig,
-    )
-    TinySwitchSettingItem(
-        title = stringResource(R.string.expand_text_menu),
-        checked = preferences.expandTextMenu,
-        onCheckedChange = onExpandTextMenuChange,
-    )
-    TinySwitchSettingItem(
-        title = stringResource(R.string.show_select_menu_icon),
-        checked = preferences.showSelectMenuIcon,
-        onCheckedChange = onShowSelectMenuIconChange,
-    )
     TinySwitchSettingItem(
         title = stringResource(R.string.show_read_title_addition),
         checked = preferences.showReadTitleAddition,

+ 433 - 41
app/src/main/java/io/legado/app/ui/book/read/sheet/TextSelectMenuConfigSheet.kt

@@ -1,39 +1,75 @@
 package io.legado.app.ui.book.read.sheet
 
 import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.foundation.layout.*
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyItemScope
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.KeyboardArrowDown
 import androidx.compose.material.icons.filled.Menu
-import androidx.compose.material.icons.filled.Visibility
+import androidx.compose.material.icons.filled.Remove
 import androidx.compose.material.icons.filled.VisibilityOff
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import coil.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.book.read.ActionMenuItem
 import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.TinySwitch
 import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
 import io.legado.app.ui.widget.components.card.NormalCard
 import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem
+import io.legado.app.ui.widget.components.settingItem.TinySettingItem
 import io.legado.app.ui.widget.components.text.AppText
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 import sh.calvin.reorderable.ReorderableItem
 import sh.calvin.reorderable.rememberReorderableLazyListState
+import kotlin.time.Duration.Companion.milliseconds
 
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
 @Composable
 fun TextSelectMenuConfigSheet(
     show: Boolean,
     items: List<ActionMenuItem>,
+    expandTextMenu: Boolean,
+    showSelectMenuIcon: Boolean,
+    onExpandTextMenuChange: (Boolean) -> Unit,
+    onShowSelectMenuIconChange: (Boolean) -> Unit,
     onDismissRequest: () -> Unit,
     onSaved: (List<ActionMenuItem>) -> Unit
 ) {
@@ -41,10 +77,37 @@ fun TextSelectMenuConfigSheet(
         mutableStateOf(items)
     }
 
+    var group1Expanded by remember(show) { mutableStateOf(true) }
+    var group2Expanded by remember(show) { mutableStateOf(true) }
+    var group3Expanded by remember(show) { mutableStateOf(false) }
+
+    val group1Items = remember(draftItems) { draftItems.filter { it.showState == 0 } }
+    val group2Items = remember(draftItems) { draftItems.filter { it.showState == 1 } }
+    val group3Items = remember(draftItems) { draftItems.filter { it.showState == 2 } }
+
     val lazyListState = rememberLazyListState()
+    val coroutineScope = rememberCoroutineScope()
+
+    LaunchedEffect(show) {
+        if (show) {
+            lazyListState.scrollToItem(0)
+        }
+    }
     val reorderableState = rememberReorderableLazyListState(lazyListState) { from, to ->
-        draftItems = draftItems.toMutableList().apply {
-            add(to.index, removeAt(from.index))
+        val fromKey = from.key
+        val toKey = to.key
+
+        val fromIndexInDraft = draftItems.indexOfFirst { it.uniqueId == fromKey }
+        val toIndexInDraft = draftItems.indexOfFirst { it.uniqueId == toKey }
+
+        if (fromIndexInDraft != -1 && toIndexInDraft != -1) {
+            val fromItem = draftItems[fromIndexInDraft]
+            val toItem = draftItems[toIndexInDraft]
+            if (fromItem.showState == toItem.showState) {
+                draftItems = draftItems.toMutableList().apply {
+                    add(toIndexInDraft, removeAt(fromIndexInDraft))
+                }
+            }
         }
     }
 
@@ -66,28 +129,189 @@ fun TextSelectMenuConfigSheet(
                     .weight(1f)
                     .padding(horizontal = 16.dp),
             ) {
-                items(draftItems, key = { it.uniqueId }) { item ->
-                    ReorderableItem(reorderableState, key = item.uniqueId) { isDragging ->
-                        val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
-                        NormalCard(
-                            elevation = elevation,
-                            cornerRadius = 12.dp,
-                            containerColor = LegadoTheme.colorScheme.surfaceContainerLow
-                        ) {
-                            TextSelectMenuConfigItemRow(
-                                item = item,
-                                onToggleEnabled = {
-                                    draftItems = draftItems.map {
-                                        if (it.uniqueId == item.uniqueId) {
-                                            it.copy(enabled = !it.enabled)
-                                        } else it
-                                    }
-                                },
-                                dragHandleModifier = Modifier.draggableHandle()
+                item {
+                    NormalCard(
+                        cornerRadius = 12.dp,
+                        containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .animateItem()
+                            .padding(top = 12.dp, bottom = 4.dp)
+                    ) {
+                        Column {
+                            Row(
+                                modifier = Modifier
+                                    .fillMaxWidth()
+                                    .clickable { onExpandTextMenuChange(!expandTextMenu) }
+                                    .padding(horizontal = 16.dp, vertical = 12.dp),
+                                verticalAlignment = Alignment.CenterVertically
+                            ) {
+                                AppText(
+                                    text = stringResource(R.string.expand_text_menu),
+                                    style = LegadoTheme.typography.titleSmallEmphasized,
+                                    modifier = Modifier.weight(1f),
+                                    maxLines = 1,
+                                    overflow = TextOverflow.Ellipsis
+                                )
+                                TinySwitch(
+                                    checked = expandTextMenu,
+                                    onCheckedChange = onExpandTextMenuChange
+                                )
+                            }
+
+                            HorizontalDivider(
+                                modifier = Modifier.padding(horizontal = 16.dp),
+                                color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.3f)
                             )
+
+                            Row(
+                                modifier = Modifier
+                                    .fillMaxWidth()
+                                    .clickable { onShowSelectMenuIconChange(!showSelectMenuIcon) }
+                                    .padding(horizontal = 16.dp, vertical = 12.dp),
+                                verticalAlignment = Alignment.CenterVertically
+                            ) {
+                                AppText(
+                                    text = stringResource(R.string.show_select_menu_icon),
+                                    style = LegadoTheme.typography.titleSmallEmphasized,
+                                    modifier = Modifier.weight(1f),
+                                    maxLines = 1,
+                                    overflow = TextOverflow.Ellipsis
+                                )
+                                TinySwitch(
+                                    checked = showSelectMenuIcon,
+                                    onCheckedChange = onShowSelectMenuIconChange
+                                )
+                            }
+                        }
+                    }
+                }
+
+                stickyHeader(key = "group1") {
+                    SheetHeader(
+                        isCollapsed = !group1Expanded,
+                        onToggle = { group1Expanded = !group1Expanded },
+                        title = stringResource(R.string.primary_menu)
+                    )
+                }
+
+                if (group1Expanded) {
+                    items(group1Items, key = { it.uniqueId }) { item ->
+                        ReorderableItem(reorderableState, key = item.uniqueId) { isDragging ->
+                            val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
+                            NormalCard(
+                                elevation = elevation,
+                                cornerRadius = 12.dp,
+                                containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
+                                modifier = Modifier.animateItem()
+                            ) {
+                                PrimaryMenuItemRow(
+                                    item = item,
+                                    showIcon = showSelectMenuIcon,
+                                    onMoveToFolded = {
+                                        draftItems = draftItems.map {
+                                            if (it.uniqueId == item.uniqueId) it.copy(showState = 1) else it
+                                        }
+                                    },
+                                    onMoveToHidden = {
+                                        draftItems = draftItems.map {
+                                            if (it.uniqueId == item.uniqueId) it.copy(showState = 2) else it
+                                        }
+                                    },
+                                    dragHandleModifier = Modifier.draggableHandle()
+                                )
+                            }
                         }
                     }
                 }
+
+
+                stickyHeader(key = "group2") {
+                    val subtitle = if (expandTextMenu) stringResource(R.string.merged_in_multiline) else null
+                    SheetHeader(
+                        isCollapsed = !group2Expanded,
+                        onToggle = { group2Expanded = !group2Expanded },
+                        title = stringResource(R.string.collapsed_menu),
+                        subtitle = subtitle
+                    )
+                }
+
+                if (group2Expanded) {
+                    items(group2Items, key = { it.uniqueId }) { item ->
+                        ReorderableItem(reorderableState, key = item.uniqueId) { isDragging ->
+                            val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
+                            NormalCard(
+                                elevation = elevation,
+                                cornerRadius = 12.dp,
+                                containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
+                                modifier = Modifier.animateItem()
+                            ) {
+                                FoldedMenuItemRow(
+                                    item = item,
+                                    showIcon = showSelectMenuIcon,
+                                    onMoveToPrimary = {
+                                        draftItems = draftItems.map {
+                                            if (it.uniqueId == item.uniqueId) it.copy(showState = 0) else it
+                                        }
+                                    },
+                                    onMoveToHidden = {
+                                        draftItems = draftItems.map {
+                                            if (it.uniqueId == item.uniqueId) it.copy(showState = 2) else it
+                                        }
+                                    },
+                                    dragHandleModifier = Modifier.draggableHandle()
+                                )
+                            }
+                        }
+                    }
+                }
+
+
+                item (key="group_3"){
+                    Box(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .animateItem()
+                            .background(LegadoTheme.colorScheme.surfaceContainer)
+                            .padding(top = 12.dp)
+                    ) {
+                        TinySettingItem(
+                            title = stringResource(R.string.hidden_items),
+                            color = LegadoTheme.colorScheme.surfaceContainerHigh,
+                            expanded = group3Expanded,
+                            onExpandChange = { expanded ->
+                                group3Expanded = expanded
+                                if (expanded) {
+                                    coroutineScope.launch {
+                                        delay(300.milliseconds)
+                                        val targetIndex = lazyListState.layoutInfo.totalItemsCount - 1
+                                        if (targetIndex >= 0) {
+                                            lazyListState.animateScrollToItem(targetIndex)
+                                        }
+                                    }
+                                }
+                            },
+                            expandContent = {
+                                HiddenItemsFlowView(
+                                    items = group3Items,
+                                    showIcon = showSelectMenuIcon,
+                                    onRestore = { item ->
+                                        draftItems = draftItems.map {
+                                            if (it.uniqueId == item.uniqueId) it.copy(showState = 0) else it
+                                        }
+                                        coroutineScope.launch {
+                                            delay(200.milliseconds)
+                                            val targetIndex = lazyListState.layoutInfo.totalItemsCount - 1
+                                            if (targetIndex >= 0) {
+                                                lazyListState.scrollToItem(targetIndex)
+                                            }
+                                        }
+                                    }
+                                )
+                            }
+                        )
+                    }
+                }
             }
 
             ConfirmDismissButtonsRow(
@@ -105,19 +329,20 @@ fun TextSelectMenuConfigSheet(
 }
 
 @Composable
-private fun TextSelectMenuConfigItemRow(
+private fun PrimaryMenuItemRow(
     item: ActionMenuItem,
-    onToggleEnabled: () -> Unit,
+    showIcon: Boolean,
+    onMoveToFolded: () -> Unit,
+    onMoveToHidden: () -> Unit,
     dragHandleModifier: Modifier = Modifier
 ) {
-    val alpha = if (item.enabled) 1f else 0.38f
     Row(
         verticalAlignment = Alignment.CenterVertically,
         modifier = Modifier
             .fillMaxWidth()
             .padding(all = 12.dp),
     ) {
-        if (item.iconDrawable != null) {
+        if (showIcon && item.iconDrawable != null) {
             Box(
                 contentAlignment = Alignment.Center,
                 modifier = Modifier.size(36.dp),
@@ -125,7 +350,6 @@ private fun TextSelectMenuConfigItemRow(
                 AsyncImage(
                     model = item.iconDrawable,
                     contentDescription = item.title,
-                    alpha = alpha,
                     modifier = Modifier.size(24.dp)
                 )
             }
@@ -134,7 +358,7 @@ private fun TextSelectMenuConfigItemRow(
         AppText(
             text = item.title,
             fontSize = 14.sp,
-            color = LegadoTheme.colorScheme.onSurface.copy(alpha = alpha),
+            color = LegadoTheme.colorScheme.onSurface,
             modifier = Modifier
                 .weight(1f)
                 .padding(horizontal = 12.dp),
@@ -142,21 +366,100 @@ private fun TextSelectMenuConfigItemRow(
         )
 
         IconButton(
-            onClick = onToggleEnabled,
+            onClick = onMoveToFolded,
             modifier = Modifier.size(36.dp),
         ) {
             Icon(
-                imageVector = if (item.enabled) {
-                    Icons.Default.Visibility
-                } else {
-                    Icons.Default.VisibilityOff
-                },
+                imageVector = Icons.Default.Remove,
                 contentDescription = null,
-                tint = if (item.enabled) {
-                    LegadoTheme.colorScheme.onSurface
-                } else {
-                    LegadoTheme.colorScheme.onSurfaceVariant
-                },
+                tint = LegadoTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.size(20.dp),
+            )
+        }
+
+        IconButton(
+            onClick = onMoveToHidden,
+            modifier = Modifier.size(36.dp),
+        ) {
+            Icon(
+                imageVector = Icons.Default.VisibilityOff,
+                contentDescription = null,
+                tint = LegadoTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.size(20.dp),
+            )
+        }
+
+        Box(
+            modifier = dragHandleModifier.size(36.dp),
+            contentAlignment = Alignment.Center
+        ) {
+            Icon(
+                imageVector = Icons.Default.Menu,
+                contentDescription = null,
+                tint = LegadoTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.size(20.dp),
+            )
+        }
+    }
+}
+
+@Composable
+private fun FoldedMenuItemRow(
+    item: ActionMenuItem,
+    showIcon: Boolean,
+    onMoveToPrimary: () -> Unit,
+    onMoveToHidden: () -> Unit,
+    dragHandleModifier: Modifier = Modifier
+) {
+    Row(
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(all = 12.dp),
+    ) {
+        if (showIcon && item.iconDrawable != null) {
+            Box(
+                contentAlignment = Alignment.Center,
+                modifier = Modifier.size(36.dp),
+            ) {
+                AsyncImage(
+                    model = item.iconDrawable,
+                    contentDescription = item.title,
+                    modifier = Modifier.size(24.dp)
+                )
+            }
+        }
+
+        AppText(
+            text = item.title,
+            fontSize = 14.sp,
+            color = LegadoTheme.colorScheme.onSurface,
+            modifier = Modifier
+                .weight(1f)
+                .padding(horizontal = 12.dp),
+            maxLines = 1
+        )
+
+        IconButton(
+            onClick = onMoveToPrimary,
+            modifier = Modifier.size(36.dp),
+        ) {
+            Icon(
+                imageVector = Icons.Default.Add,
+                contentDescription = null,
+                tint = LegadoTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.size(20.dp),
+            )
+        }
+
+        IconButton(
+            onClick = onMoveToHidden,
+            modifier = Modifier.size(36.dp),
+        ) {
+            Icon(
+                imageVector = Icons.Default.VisibilityOff,
+                contentDescription = null,
+                tint = LegadoTheme.colorScheme.onSurfaceVariant,
                 modifier = Modifier.size(20.dp),
             )
         }
@@ -174,3 +477,92 @@ private fun TextSelectMenuConfigItemRow(
         }
     }
 }
+
+@Composable
+private fun HiddenItemsFlowView(
+    items: List<ActionMenuItem>,
+    showIcon: Boolean,
+    modifier: Modifier = Modifier,
+    onRestore: (ActionMenuItem) -> Unit
+) {
+    FlowRow(
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(vertical = 8.dp),
+        horizontalArrangement = Arrangement.spacedBy(8.dp),
+        verticalArrangement = Arrangement.spacedBy(8.dp)
+    ) {
+        items.forEach { item ->
+            NormalCard(
+                onClick = { onRestore(item) },
+                cornerRadius = 8.dp,
+                containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
+            ) {
+                Row(
+                    verticalAlignment = Alignment.CenterVertically,
+                    modifier = Modifier.padding(start = 8.dp, end = 4.dp, top = 4.dp, bottom = 4.dp)
+                ) {
+                    if (showIcon && item.iconDrawable != null) {
+                        AsyncImage(
+                            model = item.iconDrawable,
+                            contentDescription = item.title,
+                            modifier = Modifier
+                                .size(16.dp)
+                        )
+                        Spacer(modifier = Modifier.width(4.dp))
+                    }
+
+                    AppText(
+                        text = item.title,
+                        fontSize = 12.sp,
+                    )
+
+                    Icon(
+                        imageVector = Icons.Default.Add,
+                        contentDescription = null,
+                        tint = LegadoTheme.colorScheme.primary,
+                        modifier = Modifier.size(16.dp)
+                    )
+                }
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun LazyItemScope.SheetHeader(
+    title: String,
+    subtitle: String? = null,
+    isCollapsed: Boolean,
+    onToggle: () -> Unit
+) {
+    val rotation by animateFloatAsState(
+        targetValue = if (!isCollapsed) 180f else 0f,
+        label = "sheetHeaderArrow"
+    )
+    Box(
+        modifier = Modifier
+            .fillMaxWidth()
+            .animateItem()
+            .background(LegadoTheme.colorScheme.surfaceContainer)
+            .padding(top = 12.dp)
+    ) {
+        TinyClickableSettingItem(
+            title = title,
+            description = subtitle,
+            color = LegadoTheme.colorScheme.surfaceContainerHigh,
+            trailingContent = {
+                Icon(
+                    imageVector = Icons.Default.KeyboardArrowDown,
+                    contentDescription = null,
+                    tint = LegadoTheme.colorScheme.onSurfaceVariant,
+                    modifier = Modifier
+                        .size(20.dp)
+                        .rotate(rotation),
+                )
+            },
+            onClick = onToggle
+        )
+    }
+}

+ 0 - 4
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfig.kt

@@ -87,9 +87,6 @@ object ReadConfig {
     var showSelectMenuIcon
         get() = ReadMenuConfig.showSelectMenuIcon
         set(value) { ReadMenuConfig.showSelectMenuIcon = value }
-    var textSelectMenuFilter
-        get() = ReadMenuConfig.textSelectMenuFilter
-        set(value) { ReadMenuConfig.textSelectMenuFilter = value }
     var textSelectMenuConfig
         get() = ReadMenuConfig.textSelectMenuConfig
         set(value) { ReadMenuConfig.textSelectMenuConfig = value }
@@ -280,7 +277,6 @@ object ReadConfig {
             progressBarBehavior = preferences.progressBarBehavior
             expandTextMenu = preferences.expandTextMenu
             showSelectMenuIcon = preferences.showSelectMenuIcon
-            textSelectMenuFilter = preferences.textSelectMenuFilter
             showReadTitleAddition = preferences.showReadTitleAddition
             showMenuIcon = preferences.showMenuIcon
             clickActionTL = preferences.clickActionTL

+ 0 - 4
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt

@@ -37,7 +37,6 @@ data class ReadConfigUiState(
     val disableReturnKey: Boolean = false,
     val expandTextMenu: Boolean = false,
     val showSelectMenuIcon: Boolean = true,
-    val textSelectMenuFilter: String = "",
     val showReadTitleAddition: Boolean = true,
     val autoReadSpeed: Int = 10,
     val prevKeys: String = "",
@@ -78,9 +77,6 @@ sealed interface ReadConfigIntent {
     data class ClickImgWayChanged(val value: String) : ReadConfigIntent
     data class OptimizeRenderChanged(val value: Boolean) : ReadConfigIntent
     data class DisableReturnKeyChanged(val value: Boolean) : ReadConfigIntent
-    data class ExpandTextMenuChanged(val value: Boolean) : ReadConfigIntent
-    data class ShowSelectMenuIconChanged(val value: Boolean) : ReadConfigIntent
-    data class TextSelectMenuFilterChanged(val value: String) : ReadConfigIntent
     data class ShowReadTitleAdditionChanged(val value: Boolean) : ReadConfigIntent
     data class ShowMenuIconChanged(val value: Boolean) : ReadConfigIntent
     data class AutoSuggestDayNightChanged(val value: Boolean) : ReadConfigIntent

+ 0 - 32
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt

@@ -370,38 +370,6 @@ fun ReadConfigScreen(
                     onClick = { showPageKeySheet = true }
                 )
 
-                SwitchSettingItem(
-                    title = stringResource(R.string.expand_text_menu),
-                    checked = state.expandTextMenu,
-                    onCheckedChange = {
-                        viewModel.onIntent(ReadConfigIntent.ExpandTextMenuChanged(it))
-                    }
-                )
-
-                SwitchSettingItem(
-                    title = stringResource(R.string.show_select_menu_icon),
-                    checked = state.showSelectMenuIcon,
-                    onCheckedChange = {
-                        viewModel.onIntent(ReadConfigIntent.ShowSelectMenuIconChanged(it))
-                    }
-                )
-
-                var showSelectMenuFilterSheet by remember { mutableStateOf(false) }
-
-                ClickableSettingItem(
-                    title = stringResource(R.string.text_select_menu_filter),
-                    description = stringResource(R.string.text_select_menu_filter_desc),
-                    onClick = { showSelectMenuFilterSheet = true }
-                )
-
-                TextSelectMenuFilterSheet(
-                    show = showSelectMenuFilterSheet,
-                    onDismissRequest = { showSelectMenuFilterSheet = false },
-                    onFilterChanged = {
-                        viewModel.onIntent(ReadConfigIntent.TextSelectMenuFilterChanged(it))
-                    }
-                )
-
                 SwitchSettingItem(
                     title = stringResource(R.string.show_read_title_addition),
                     checked = state.showReadTitleAddition,

+ 0 - 16
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt

@@ -197,21 +197,6 @@ class ReadConfigViewModel(
                     readSettingsRepository.setDisableReturnKey(intent.value)
                 }
 
-                is ReadConfigIntent.ExpandTextMenuChanged -> {
-                    ReadConfig.expandTextMenu = intent.value
-                    readSettingsRepository.setExpandTextMenu(intent.value)
-                }
-
-                is ReadConfigIntent.ShowSelectMenuIconChanged -> {
-                    ReadConfig.showSelectMenuIcon = intent.value
-                    readSettingsRepository.setShowSelectMenuIcon(intent.value)
-                }
-
-                is ReadConfigIntent.TextSelectMenuFilterChanged -> {
-                    ReadConfig.textSelectMenuFilter = intent.value
-                    readSettingsRepository.setTextSelectMenuFilter(intent.value)
-                }
-
                 is ReadConfigIntent.ShowReadTitleAdditionChanged -> {
                     ReadConfig.showReadTitleAddition = intent.value
                     readSettingsRepository.setShowReadTitleAddition(intent.value)
@@ -280,7 +265,6 @@ class ReadConfigViewModel(
             disableReturnKey = disableReturnKey,
             expandTextMenu = expandTextMenu,
             showSelectMenuIcon = showSelectMenuIcon,
-            textSelectMenuFilter = textSelectMenuFilter,
             showReadTitleAddition = showReadTitleAddition,
             autoReadSpeed = autoReadSpeed,
             prevKeys = prevKeys,

+ 0 - 5
app/src/main/java/io/legado/app/ui/config/readConfig/ReadMenuConfig.kt

@@ -45,11 +45,6 @@ object ReadMenuConfig {
         true
     )
 
-    var textSelectMenuFilter by prefDelegate(
-        PreferKey.textSelectMenuFilter,
-        ""
-    )
-
     var textSelectMenuConfig by prefDelegate(
         PreferKey.textSelectMenuConfig,
         ""

+ 0 - 189
app/src/main/java/io/legado/app/ui/config/readConfig/TextSelectMenuFilterSheet.kt

@@ -1,189 +0,0 @@
-package io.legado.app.ui.config.readConfig
-
-import android.content.Intent
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.clearAndSetSemantics
-import androidx.compose.ui.semantics.role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
-import io.legado.app.R
-import io.legado.app.ui.theme.LegadoTheme
-import io.legado.app.ui.widget.components.AdaptiveSwitch
-import io.legado.app.ui.widget.components.button.series.SmallPlainButton
-import io.legado.app.ui.widget.components.card.NormalCard
-import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
-import io.legado.app.ui.widget.components.text.AppText
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun TextSelectMenuFilterSheet(
-    show: Boolean,
-    onDismissRequest: () -> Unit,
-    onFilterChanged: (String) -> Unit
-) {
-    val context = LocalContext.current
-    val pm = context.packageManager
-
-    // 查询所有注册了 ACTION_PROCESS_TEXT 的第三方 Activity
-    var apps by remember { mutableStateOf<List<TextSelectMenuAppInfo>>(emptyList()) }
-    LaunchedEffect(show) {
-        if (show) {
-            val list = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
-                val intent = Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain")
-                pm.queryIntentActivities(intent, 0).map { resolveInfo ->
-                    val title = resolveInfo.loadLabel(pm).toString()
-                    val componentName = "${resolveInfo.activityInfo.packageName}/${resolveInfo.activityInfo.name}"
-                    val icon = kotlin.runCatching { resolveInfo.loadIcon(pm) }.getOrNull()
-                    TextSelectMenuAppInfo(title, componentName, icon)
-                }
-            }
-            apps = list
-        } else {
-            apps = emptyList()
-        }
-    }
-
-    var pendingFilterString by remember(show) {
-        mutableStateOf(ReadConfig.textSelectMenuFilter)
-    }
-
-    val disabledSet = remember(pendingFilterString) {
-        pendingFilterString.split(",").filter { it.isNotEmpty() }.toSet()
-    }
-
-    AppModalBottomSheet(
-        show = show,
-        onDismissRequest = onDismissRequest,
-        title = stringResource(R.string.text_select_menu_filter),
-        endAction = {
-            SmallPlainButton(
-                icon = Icons.Default.Check,
-                onClick = {
-                    onFilterChanged(pendingFilterString)
-                    onDismissRequest()
-                }
-            )
-        },
-    ) {
-        Column(
-            modifier = Modifier
-                .fillMaxWidth()
-                .fillMaxHeight(0.8f)
-                .padding(horizontal = 16.dp, vertical = 8.dp)
-        ) {
-            if (apps.isEmpty()) {
-                Box(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .weight(1f),
-                    contentAlignment = Alignment.Center
-                ) {
-                    AppText(text = stringResource(R.string.no_apps_found))
-                }
-            } else {
-                LazyColumn(
-                    modifier = Modifier.weight(1f),
-                    verticalArrangement = Arrangement.spacedBy(8.dp)
-                ) {
-                    items(apps) { app ->
-                        val isEnabled = !disabledSet.contains(app.componentName)
-                        val switchStateDescription = stringResource(
-                            if (isEnabled) R.string.a11y_on else R.string.a11y_off
-                        )
-                        NormalCard(
-                            onClick = {
-                                val nextSet = if (isEnabled) {
-                                    disabledSet + app.componentName
-                                } else {
-                                    disabledSet - app.componentName
-                                }
-                                pendingFilterString = nextSet.joinToString(",")
-                            },
-                            modifier = Modifier.semantics(mergeDescendants = true) {
-                                role = Role.Switch
-                                stateDescription = switchStateDescription
-                            },
-                            cornerRadius = 12.dp,
-                            containerColor = LegadoTheme.colorScheme.surfaceContainerHigh,
-                        ) {
-                            Row(
-                                modifier = Modifier
-                                    .fillMaxWidth()
-                                    .padding(horizontal = 8.dp, vertical = 12.dp),
-                                verticalAlignment = Alignment.CenterVertically
-                            ) {
-                                Box(
-                                    modifier = Modifier
-                                        .size(40.dp)
-                                        .padding(4.dp),
-                                    contentAlignment = Alignment.Center
-                                ) {
-                                    AsyncImage(
-                                        model = app.icon,
-                                        contentDescription = null,
-                                        modifier = Modifier.fillMaxSize()
-                                    )
-                                }
-                                Column(
-                                    modifier = Modifier
-                                        .weight(1f)
-                                        .padding(horizontal = 8.dp)
-                                ) {
-                                    AppText(
-                                        text = app.title,
-                                        style = LegadoTheme.typography.titleMedium
-                                    )
-                                }
-                                AdaptiveSwitch(
-                                    modifier = Modifier.clearAndSetSemantics { },
-                                    checked = isEnabled,
-                                    onCheckedChange = { checked ->
-                                        val nextSet = if (checked) {
-                                            disabledSet - app.componentName
-                                        } else {
-                                            disabledSet + app.componentName
-                                        }
-                                        pendingFilterString = nextSet.joinToString(",")
-                                    },
-                                    includeStateSemantics = false
-                                )
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-data class TextSelectMenuAppInfo(
-    val title: String,
-    val componentName: String,
-    val icon: android.graphics.drawable.Drawable?
-)

+ 8 - 5
app/src/main/res/values-zh-rCN/strings.xml

@@ -1104,13 +1104,12 @@
     <string name="tts_speech_add">语速加</string>
     <string name="open_sys_dir_picker_error">打开系统文件夹选择器出错,将自动打开应用文件夹选择器</string>
     <string name="open_sys_doc_picker_error">打开系统文件选择器出错,将自动打开应用文件选择器</string>
-    <string name="expand_text_menu">展开文本选择菜单</string>
-    <string name="show_select_menu_icon">展示选择菜单图标</string>
-    <string name="text_select_menu_filter">选择菜单应用过滤</string>
-    <string name="text_select_menu_filter_desc">过滤文字选择操作菜单中显示的第三方应用</string>
+    <string name="expand_text_menu">多行展示</string>
+    <string name="show_select_menu_icon">展示图标</string>
+
     <string name="show_all">全部显示</string>
     <string name="hide_all">全不显示</string>
-    <string name="no_apps_found">未找到支持文字处理的第三方应用</string>
+
     <string name="book_tree_uri_t">书籍保存位置</string>
     <string name="book_tree_uri_s">从其它应用打开的书籍保存位置</string>
     <string name="select_book_folder">选择保存书籍的文件夹</string>
@@ -2457,4 +2456,8 @@
     <string name="about_open_github">打开 GitHub</string>
     <string name="edit_menu_items">编辑菜单项</string>
     <string name="edit_select_menu">编辑选择菜单</string>
+    <string name="primary_menu">一级菜单</string>
+    <string name="collapsed_menu">折叠菜单</string>
+    <string name="hidden_items">已隐藏项目</string>
+    <string name="merged_in_multiline">多行模式下将合并展示</string>
 </resources>

+ 8 - 5
app/src/main/res/values-zh-rHK/strings.xml

@@ -966,7 +966,7 @@
     <string name="tts_speech_add">語速加</string>
     <string name="open_sys_dir_picker_error">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>
     <string name="open_sys_doc_picker_error">打开系统文件选择器出错,自动打开应用文件选择器</string>
-    <string name="expand_text_menu">展开文本选择菜单</string>
+    <string name="expand_text_menu">多行展示</string>
     <string name="book_tree_uri_t">书籍保存位置</string>
     <string name="book_tree_uri_s">从其它应用打开的书籍保存位置</string>
     <string name="select_book_folder">选择保存书籍的文件夹</string>
@@ -1820,7 +1820,7 @@
     <string name="network_book_badge_setting">網路書籍標記</string>
     <string name="new_rule">新建規則</string>
     <string name="night_card_bg_color">夜間卡片背景色</string>
-    <string name="no_apps_found">找不到支援文字操作的第三方應用</string>
+
     <string name="no_bookmark">無書籤</string>
     <string name="no_bookmarks_to_export">沒有可匯出的書籤</string>
     <string name="no_crash_logs">無崩潰日誌</string>
@@ -2021,7 +2021,7 @@
     <string name="show_more_info">顯示更多資訊</string>
     <string name="show_original">顯示原文</string>
     <string name="show_password">顯示密碼</string>
-    <string name="show_select_menu_icon">顯示文字選擇選單圖標</string>
+    <string name="show_select_menu_icon">展示圖標</string>
     <string name="show_status">顯示狀態欄</string>
     <string name="show_swipe_animation">滑動時啟用動畫</string>
     <string name="show_synopsis">顯示簡介</string>
@@ -2086,8 +2086,7 @@
     <string name="text_accent_color">強調色</string>
     <string name="text_and_padding">字體與間距</string>
     <string name="text_dottedline">圓點底線</string>
-    <string name="text_select_menu_filter">文字選擇選單篩選</string>
-    <string name="text_select_menu_filter_desc">篩選選擇選單中顯示的第三方應用</string>
+
     <string name="text_shadow_color">陰影</string>
     <string name="text_shadow_radius">陰影半徑</string>
     <string name="text_shadow_set">陰影設定</string>
@@ -2369,4 +2368,8 @@
     <string name="about_open_github">開啟 GitHub</string>
     <string name="edit_menu_items">編輯選單項</string>
     <string name="edit_select_menu">編輯選擇選單</string>
+    <string name="primary_menu">一級選單</string>
+    <string name="collapsed_menu">折疊選單</string>
+    <string name="hidden_items">已隱藏項目</string>
+    <string name="merged_in_multiline">多行模式下將合併顯示</string>
 </resources>

+ 8 - 5
app/src/main/res/values-zh-rTW/strings.xml

@@ -967,7 +967,7 @@
     <string name="tts_speech_add">語速加</string>
     <string name="open_sys_dir_picker_error">打開系統資料夾選擇器出錯,自動打開應用程式資料夾選擇器</string>
     <string name="open_sys_doc_picker_error">打開系統檔案選擇器出錯,自動打開應用文件選擇器</string>
-    <string name="expand_text_menu">展開文字選擇選單</string>
+    <string name="expand_text_menu">多行展示</string>
     <string name="search_content_size">搜尋結果</string>
     <string name="search_content_empty">搜索内容为空,检查净化/简繁设置</string>
     <string name="book_tree_uri_t">書籍儲存位置</string>
@@ -1543,12 +1543,11 @@
     <string name="cannot_open_url">無法直接開啟此 URL</string>
     <string name="open_by_browser">已切換到瀏覽器開啟</string>
     <string name="open_by_webview">已切換到 WebView 開啟</string>
-    <string name="show_select_menu_icon">顯示文字選擇選單圖示</string>
-    <string name="text_select_menu_filter">文字選擇選單篩選</string>
-    <string name="text_select_menu_filter_desc">篩選選擇選單中顯示的第三方應用</string>
+    <string name="show_select_menu_icon">展示圖示</string>
+
     <string name="show_all">全部顯示</string>
     <string name="hide_all">全部隱藏</string>
-    <string name="no_apps_found">找不到支援文字操作的第三方應用</string>
+
     <string name="is_event_listener">事件監聽器</string>
     <string name="custom_button">自訂按鈕</string>
     <string name="rule_call_back">回呼 JS(callBackJs)</string>
@@ -2374,4 +2373,8 @@
     <string name="about_open_github">開啟 GitHub</string>
     <string name="edit_menu_items">編輯選單項</string>
     <string name="edit_select_menu">編輯選擇選單</string>
+    <string name="primary_menu">一級選單</string>
+    <string name="collapsed_menu">折疊選單</string>
+    <string name="hidden_items">已隱藏項目</string>
+    <string name="merged_in_multiline">多行模式下將合併顯示</string>
 </resources>

+ 8 - 5
app/src/main/res/values/strings.xml

@@ -1136,13 +1136,12 @@
     <string name="tts_speech_add">Increase speech speed</string>
     <string name="open_sys_dir_picker_error">Error opening system folder selector, automatically open application folder selector</string>
     <string name="open_sys_doc_picker_error">Error opening system file selector, automatically open application file selector</string>
-    <string name="expand_text_menu">Expand Text Selection Menu</string>
-    <string name="show_select_menu_icon">Show Text Selection Menu Icons</string>
-    <string name="text_select_menu_filter">Text Selection Menu Filter</string>
-    <string name="text_select_menu_filter_desc">Filter the third-party apps displayed in the selection menu</string>
+    <string name="expand_text_menu">Multiline Show</string>
+    <string name="show_select_menu_icon">Show Icons</string>
+
     <string name="show_all">Show All</string>
     <string name="hide_all">Hide All</string>
-    <string name="no_apps_found">No third-party apps supporting text actions found</string>
+
     <string name="book_tree_uri_t">Book Save Location</string>
     <string name="book_tree_uri_s">Save location for books opened from other apps</string>
     <string name="select_book_folder">Select the folder where the books are saved</string>
@@ -2462,4 +2461,8 @@
     <string name="about_open_github">Open GitHub</string>
     <string name="edit_menu_items">Edit Menu Items</string>
     <string name="edit_select_menu">Edit Selection Menu</string>
+    <string name="primary_menu">Primary Menu</string>
+    <string name="collapsed_menu">Collapsed Menu</string>
+    <string name="hidden_items">Hidden Items</string>
+    <string name="merged_in_multiline">Merged in multiline mode</string>
 </resources>