feat: 新增文字选择菜单第三方应用过滤功能并优化加载性能

This commit is contained in:
lyc
2026-06-20 19:28:48 +08:00
parent 0d8c5df795
commit 51e0f5d928
15 changed files with 365 additions and 11 deletions
@@ -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"
@@ -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)
@@ -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>()
}
@@ -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,
@@ -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 {
@@ -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}")
@@ -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,
@@ -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
@@ -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
@@ -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,
@@ -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,
@@ -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
@@ -0,0 +1,191 @@
package io.legado.app.ui.config.readConfig
import android.content.Intent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SelectAll
import androidx.compose.material.icons.filled.Deselect
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.series.SmallTonalButton
@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),
) {
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 = MaterialTheme.typography.titleMedium
)
}
Switch(
checked = isEnabled,
onCheckedChange = { checked ->
val nextSet = if (checked) {
disabledSet - app.componentName
} else {
disabledSet + app.componentName
}
pendingFilterString = nextSet.joinToString(",")
}
)
}
}
}
}
}
Row(
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
IconButton(onClick = { pendingFilterString = "" }) {
Icon(
imageVector = Icons.Default.SelectAll,
contentDescription = stringResource(R.string.show_all),
tint = LegadoTheme.colorScheme.primary
)
}
IconButton(onClick = {
val allNames = apps.joinToString(",") { it.componentName }
pendingFilterString = allNames
}) {
Icon(
imageVector = Icons.Default.Deselect,
contentDescription = stringResource(R.string.hide_all),
tint = LegadoTheme.colorScheme.error
)
}
}
ConfirmDismissButtonsRow(
modifier = Modifier.weight(1f),
onDismiss = onDismissRequest,
onConfirm = {
onFilterChanged(pendingFilterString)
onDismissRequest()
},
dismissText = stringResource(R.string.cancel),
confirmText = stringResource(R.string.action_save)
)
}
}
}
}
data class TextSelectMenuAppInfo(
val title: String,
val componentName: String,
val icon: android.graphics.drawable.Drawable?
)
@@ -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
View File
@@ -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>