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

Feature/text select menu filter
This commit is contained in:
Kudomaga
2026-06-21 00:35:36 +08:00
committed by GitHub
15 changed files with 349 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,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?
)
@@ -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>