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

Merge pull request #1506 from lalala-12138/feature/text-select-menu-filter

Feature/text select menu filter
Kudomaga 4 месяцев назад
Родитель
Сommit
3daed11f05

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

@@ -132,6 +132,8 @@ object PreferKey {
     const val brightness = "brightness"
     const val nightBrightness = "nightBrightness"
     const val expandTextMenu = "expandTextMenu"
+    const val showSelectMenuIcon = "showSelectMenuIcon"
+    const val textSelectMenuFilter = "textSelectMenuFilter"
     const val doublePageHorizontal = "doubleHorizontalPage"
     const val readUrlOpenInBrowser = "readUrlInBrowser"
     const val defaultBookTreeUri = "defaultBookTreeUri"

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

@@ -51,6 +51,8 @@ data class ReadPreferences(
     val optimizeRender: Boolean = false,
     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 = "",
@@ -230,6 +232,12 @@ class ReadSettingsRepository(
     suspend fun setExpandTextMenu(value: Boolean) =
         settingsRepository.putBoolean(PreferKey.expandTextMenu, value)
 
+    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)
 
@@ -420,6 +428,8 @@ class ReadSettingsRepository(
             optimizeRender = this[Keys.OptimizeRender] ?: false,
             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] ?: "",
@@ -518,6 +528,8 @@ class ReadSettingsRepository(
         val OptimizeRender = booleanPreferencesKey(PreferKey.optimizeRender)
         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)

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

@@ -684,6 +684,7 @@ sealed interface ReadBookSheet {
     data object AudioCacheCleanConfig : 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(
@@ -1197,6 +1198,12 @@ sealed interface ConfigUpdate {
     data class ExpandTextMenu(val value: Boolean) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }
+    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>()
     }

+ 15 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt

@@ -41,6 +41,7 @@ import io.legado.app.ui.widget.components.FontSelectSheet
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet
 import io.legado.app.ui.widget.components.log.AppLogSheet
+import io.legado.app.ui.config.readConfig.TextSelectMenuFilterSheet
 import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.flow.collectLatest
 
@@ -243,6 +244,10 @@ 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,
@@ -337,6 +342,16 @@ fun ReadBookScreen(
             )
         }
 
+        is ReadBookSheet.TextSelectMenuFilterConfig -> {
+            TextSelectMenuFilterSheet(
+                show = true,
+                onDismissRequest = dismissSheet,
+                onFilterChanged = {
+                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TextSelectMenuFilter(it)))
+                }
+            )
+        }
+
         is ReadBookSheet.PageAnim -> {
             PageAnimConfigSheet(
                 onDismissRequest = dismissSheet,

+ 12 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -3479,6 +3479,18 @@ class ReadBookViewModel(
                     readSettingsRepository.setExpandTextMenu(update.value)
                 }
             }
+            is ConfigUpdate.ShowSelectMenuIcon -> {
+                ReadConfig.showSelectMenuIcon = update.value
+                viewModelScope.launch {
+                    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 {

+ 39 - 10
app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt

@@ -23,6 +23,7 @@ import io.legado.app.constant.AppLog
 import io.legado.app.databinding.ItemTextBinding
 import io.legado.app.databinding.PopupActionMenuBinding
 import io.legado.app.ui.config.readConfig.ReadConfig
+import io.legado.app.utils.dpToPx
 import io.legado.app.utils.gone
 import io.legado.app.utils.isAbsUrl
 import io.legado.app.utils.printOnDebug
@@ -44,9 +45,10 @@ class TextActionMenu(
         setHasStableIds(true)
     }
 
-    private val menuItems: List<MenuItemImpl>
+    private var menuItems: List<MenuItemImpl> = emptyList()
     private val visibleMenuItems = arrayListOf<MenuItemImpl>()
     private val moreMenuItems = arrayListOf<MenuItemImpl>()
+    private val myMenu = MenuBuilder(context)
 
     init {
         @SuppressLint("InflateParams")
@@ -57,13 +59,7 @@ class TextActionMenu(
         isFocusable = false
         animationStyle = R.style.TextActionMenuAnimation
 
-        val myMenu = MenuBuilder(context)
-        val otherMenu = MenuBuilder(context)
         SupportMenuInflater(context).inflate(R.menu.content_select_action, myMenu)
-        onInitializeMenu(otherMenu)
-        menuItems = myMenu.visibleItems + otherMenu.visibleItems
-        visibleMenuItems.addAll(menuItems.subList(0, 6))
-        moreMenuItems.addAll(menuItems.subList(6, menuItems.size))
         binding.recyclerView.adapter = adapter
         binding.recyclerViewMore.adapter = adapter
         setOnDismissListener {
@@ -91,6 +87,18 @@ class TextActionMenu(
     }
 
     fun upMenu() {
+        val otherMenu = MenuBuilder(context)
+        onInitializeMenu(otherMenu)
+        menuItems = myMenu.visibleItems + otherMenu.visibleItems
+        visibleMenuItems.clear()
+        moreMenuItems.clear()
+        if (menuItems.size > 6) {
+            visibleMenuItems.addAll(menuItems.subList(0, 6))
+            moreMenuItems.addAll(menuItems.subList(6, menuItems.size))
+        } else {
+            visibleMenuItems.addAll(menuItems)
+        }
+
         if (expandTextMenu()) {
             adapter.setItems(menuItems)
             binding.ivMenuMore.gone()
@@ -172,6 +180,15 @@ class TextActionMenu(
         ) {
             with(binding) {
                 textView.text = item.title
+                val icon = item.icon
+                if (icon != null && ReadConfig.showSelectMenuIcon) {
+                    val size = 18.dpToPx()
+                    icon.setBounds(0, 0, size, size)
+                    textView.setCompoundDrawables(icon, null, null, null)
+                    textView.compoundDrawablePadding = 6.dpToPx()
+                } else {
+                    textView.setCompoundDrawables(null, null, null, null)
+                }
             }
         }
 
@@ -255,11 +272,23 @@ class TextActionMenu(
     private fun onInitializeMenu(menu: Menu) {
         kotlin.runCatching {
             var menuItemOrder = 100
+            val pm = context.packageManager
+            val filterSet = ReadConfig.textSelectMenuFilter.split(",").filter { it.isNotEmpty() }.toSet()
             for (resolveInfo in getSupportedActivities()) {
-                menu.add(
+                val componentName = "${resolveInfo.activityInfo.packageName}/${resolveInfo.activityInfo.name}"
+                if (filterSet.contains(componentName)) {
+                    continue
+                }
+                val menuItem = menu.add(
                     Menu.NONE, Menu.NONE,
-                    menuItemOrder++, resolveInfo.loadLabel(context.packageManager)
-                ).intent = createProcessTextIntentForResolveInfo(resolveInfo)
+                    menuItemOrder++, resolveInfo.loadLabel(pm)
+                )
+                menuItem.intent = createProcessTextIntentForResolveInfo(resolveInfo)
+                if (ReadConfig.showSelectMenuIcon) {
+                    menuItem.icon = kotlin.runCatching {
+                        resolveInfo.loadIcon(pm)
+                    }.getOrNull()
+                }
             }
         }.onFailure {
             context.toastOnUi("获取文字操作菜单出错:${it.localizedMessage}")

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

@@ -31,6 +31,7 @@ fun MoreConfigSheet(
     onIntent: (ReadBookIntent) -> Unit,
     onOpenClickRegionalConfig: () -> Unit,
     onOpenPageKeyConfig: () -> Unit,
+    onOpenTextSelectMenuFilterConfig: () -> Unit,
 ) {
     val readSettingsRepository: ReadSettingsRepository = koinInject()
     val preferences by readSettingsRepository.preferences.collectAsStateWithLifecycle(
@@ -137,13 +138,17 @@ fun MoreConfigSheet(
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ClickImgWay(it)))
                 },
                 onOpenClickRegionalConfig = onOpenClickRegionalConfig,
+                onOpenPageKeyConfig = onOpenPageKeyConfig,
+                onOpenTextSelectMenuFilterConfig = onOpenTextSelectMenuFilterConfig,
                 onDisableReturnKeyChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.DisableReturnKey(it)))
                 },
-                onOpenPageKeyConfig = onOpenPageKeyConfig,
                 onExpandTextMenuChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ExpandTextMenu(it)))
                 },
+                onShowSelectMenuIconChange = {
+                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowSelectMenuIcon(it)))
+                },
                 onShowReadTitleAdditionChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowReadTitleAddition(it)))
                 },
@@ -300,7 +305,9 @@ private fun OtherSettings(
     onOpenClickRegionalConfig: () -> Unit,
     onDisableReturnKeyChange: (Boolean) -> Unit,
     onOpenPageKeyConfig: () -> Unit,
+    onOpenTextSelectMenuFilterConfig: () -> Unit,
     onExpandTextMenuChange: (Boolean) -> Unit,
+    onShowSelectMenuIconChange: (Boolean) -> Unit,
     onShowReadTitleAdditionChange: (Boolean) -> Unit,
     onShowMenuIconChange: (Boolean) -> Unit,
 ) {
@@ -357,11 +364,20 @@ 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,

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

@@ -84,6 +84,12 @@ object ReadConfig {
     var expandTextMenu
         get() = ReadMenuConfig.expandTextMenu
         set(value) { ReadMenuConfig.expandTextMenu = value }
+    var showSelectMenuIcon
+        get() = ReadMenuConfig.showSelectMenuIcon
+        set(value) { ReadMenuConfig.showSelectMenuIcon = value }
+    var textSelectMenuFilter
+        get() = ReadMenuConfig.textSelectMenuFilter
+        set(value) { ReadMenuConfig.textSelectMenuFilter = value }
     var showReadTitleAddition
         get() = ReadMenuConfig.showReadTitleAddition
         set(value) { ReadMenuConfig.showReadTitleAddition = value }
@@ -264,6 +270,8 @@ object ReadConfig {
             readBarStyle = preferences.readBarStyle
             progressBarBehavior = preferences.progressBarBehavior
             expandTextMenu = preferences.expandTextMenu
+            showSelectMenuIcon = preferences.showSelectMenuIcon
+            textSelectMenuFilter = preferences.textSelectMenuFilter
             showReadTitleAddition = preferences.showReadTitleAddition
             showMenuIcon = preferences.showMenuIcon
             clickActionTL = preferences.clickActionTL

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

@@ -35,6 +35,8 @@ data class ReadConfigUiState(
     val optimizeRender: Boolean = false,
     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 = "",
@@ -76,6 +78,8 @@ sealed interface 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 PageKeysChanged(val prevKeys: String, val nextKeys: String) : ReadConfigIntent

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

@@ -369,6 +369,30 @@ fun ReadConfigScreen(
                     }
                 )
 
+                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,

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

@@ -197,6 +197,16 @@ class ReadConfigViewModel(
                     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)
@@ -263,6 +273,8 @@ class ReadConfigViewModel(
             optimizeRender = optimizeRender,
             disableReturnKey = disableReturnKey,
             expandTextMenu = expandTextMenu,
+            showSelectMenuIcon = showSelectMenuIcon,
+            textSelectMenuFilter = textSelectMenuFilter,
             showReadTitleAddition = showReadTitleAddition,
             autoReadSpeed = autoReadSpeed,
             prevKeys = prevKeys,

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

@@ -40,6 +40,16 @@ object ReadMenuConfig {
         false
     )
 
+    var showSelectMenuIcon by prefDelegate(
+        PreferKey.showSelectMenuIcon,
+        true
+    )
+
+    var textSelectMenuFilter by prefDelegate(
+        PreferKey.textSelectMenuFilter,
+        ""
+    )
+
     var showReadTitleAddition by prefDelegate(
         PreferKey.showReadTitleAddition,
         true

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

@@ -0,0 +1,175 @@
+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.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)
+                        NormalCard(
+                            onClick = {
+                                val nextSet = if (isEnabled) {
+                                    disabledSet + app.componentName
+                                } else {
+                                    disabledSet - app.componentName
+                                }
+                                pendingFilterString = nextSet.joinToString(",")
+                            },
+                            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(
+                                    checked = isEnabled,
+                                    onCheckedChange = { checked ->
+                                        val nextSet = if (checked) {
+                                            disabledSet - app.componentName
+                                        } else {
+                                            disabledSet + app.componentName
+                                        }
+                                        pendingFilterString = nextSet.joinToString(",")
+                                    }
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+data class TextSelectMenuAppInfo(
+    val title: String,
+    val componentName: String,
+    val icon: android.graphics.drawable.Drawable?
+)

+ 6 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -1098,6 +1098,12 @@
     <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="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>

+ 6 - 0
app/src/main/res/values/strings.xml

@@ -1130,6 +1130,12 @@
     <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="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>