General improvements (#316)
* Added support for right text alignment in epub reader * Added tabs management to PDF navigation drawer * Implemented unified selection menu placement logic * Added reset functionality for toolbar customization * Improved highlight filtering in epub pagination * Migrated hardcoded UI strings to string resources * Extracted desktop application logic from Main.kt into modular files * Refactored Main.kt by extracting PDF and EPUB logic into specialized files * Added Spanish language support * Added system default option to app language selection
This commit is contained in:
parent
759d4b73a0
commit
056485a140
77 changed files with 9184 additions and 5947 deletions
|
|
@ -26,6 +26,7 @@ import android.graphics.Canvas
|
|||
import android.os.Build
|
||||
import android.webkit.WebView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
|
|
@ -153,26 +154,26 @@ import kotlinx.coroutines.withContext
|
|||
import timber.log.Timber
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
enum class ReaderTool(val title: String, val category: String) {
|
||||
DICTIONARY("External Apps", "Top Bar"),
|
||||
THEME("Theme Settings", "Top Bar"),
|
||||
SLIDER("Navigation Slider", "Bottom Bar"),
|
||||
TOC("Sidebar", "Bottom Bar"),
|
||||
FORMAT("Text Formatting", "Bottom Bar"),
|
||||
SEARCH("Search", "Bottom Bar"),
|
||||
AI_FEATURES("AI Features", "Bottom Bar"),
|
||||
TTS_CONTROLS("TTS Controls", "Bottom Bar"),
|
||||
READING_MODE("Reading Mode", "Overflow Menu"),
|
||||
BOOKMARK("Bookmark", "Overflow Menu"),
|
||||
TAP_TO_TURN("Tap to Turn Pages", "Overflow Menu"),
|
||||
VOLUME_SCROLL("Volume Button Scrolling", "Overflow Menu"),
|
||||
PAGE_TURN_ANIM("Realistic Page Turns", "Overflow Menu"),
|
||||
KEEP_SCREEN_ON("Keep Screen On", "Overflow Menu"),
|
||||
VISUAL_OPTIONS("Visual Options", "Overflow Menu"),
|
||||
SCREEN_ORIENTATION("Screen Orientation", "Top Bar"),
|
||||
AUTO_SCROLL("Auto Scroll", "Overflow Menu"),
|
||||
TTS_SETTINGS("TTS Settings", "Overflow Menu"),
|
||||
TTS_REPLACEMENTS("TTS Word Replacements", "Overflow Menu")
|
||||
enum class ReaderTool(@StringRes val titleRes: Int, val category: String) {
|
||||
DICTIONARY(R.string.tool_external_apps, "Top Bar"),
|
||||
THEME(R.string.tooltip_theme_desc, "Top Bar"),
|
||||
SLIDER(R.string.tool_navigation_slider, "Bottom Bar"),
|
||||
TOC(R.string.tool_sidebar, "Bottom Bar"),
|
||||
FORMAT(R.string.content_desc_text_formatting, "Bottom Bar"),
|
||||
SEARCH(R.string.action_search, "Bottom Bar"),
|
||||
AI_FEATURES(R.string.ai_features_title, "Bottom Bar"),
|
||||
TTS_CONTROLS(R.string.tool_tts_controls, "Bottom Bar"),
|
||||
READING_MODE(R.string.tool_reading_mode, "Overflow Menu"),
|
||||
BOOKMARK(R.string.content_desc_bookmark, "Overflow Menu"),
|
||||
TAP_TO_TURN(R.string.menu_tap_to_turn_pages, "Overflow Menu"),
|
||||
VOLUME_SCROLL(R.string.menu_volume_button_scrolling, "Overflow Menu"),
|
||||
PAGE_TURN_ANIM(R.string.menu_realistic_page_turns, "Overflow Menu"),
|
||||
KEEP_SCREEN_ON(R.string.menu_keep_screen_on, "Overflow Menu"),
|
||||
VISUAL_OPTIONS(R.string.menu_visual_options, "Overflow Menu"),
|
||||
SCREEN_ORIENTATION(R.string.menu_screen_orientation, "Top Bar"),
|
||||
AUTO_SCROLL(R.string.menu_auto_scroll, "Overflow Menu"),
|
||||
TTS_SETTINGS(R.string.menu_tts_settings, "Overflow Menu"),
|
||||
TTS_REPLACEMENTS(R.string.menu_tts_word_replacements, "Overflow Menu")
|
||||
}
|
||||
|
||||
enum class FlatItemType { SECTION_HEADER, TOOL, EMPTY_PLACEHOLDER, MORE_HEADER, MORE_TOOL }
|
||||
|
|
@ -182,7 +183,8 @@ data class FlatToolItem(
|
|||
val type: FlatItemType,
|
||||
val tool: ReaderTool? = null,
|
||||
val section: ToolbarSection? = null,
|
||||
val title: String? = null
|
||||
val title: String? = null,
|
||||
@StringRes val titleRes: Int? = null
|
||||
)
|
||||
|
||||
fun sanitizePlaceholders(list: List<FlatToolItem>): List<FlatToolItem> {
|
||||
|
|
@ -197,7 +199,7 @@ fun sanitizePlaceholders(list: List<FlatToolItem>): List<FlatToolItem> {
|
|||
}
|
||||
|
||||
ToolbarSection.entries.forEach { section ->
|
||||
result.add(FlatToolItem("header_${section.name}", FlatItemType.SECTION_HEADER, section = section, title = section.title))
|
||||
result.add(FlatToolItem("header_${section.name}", FlatItemType.SECTION_HEADER, section = section, titleRes = section.titleRes))
|
||||
|
||||
val tools = sectionMap[section] ?: emptyList()
|
||||
if (tools.isEmpty()) {
|
||||
|
|
@ -266,6 +268,51 @@ private val epubToolbarTools = setOf(
|
|||
ReaderTool.SCREEN_ORIENTATION
|
||||
)
|
||||
|
||||
internal fun defaultReaderHiddenTools(): Set<String> = setOf(ReaderTool.SCREEN_ORIENTATION.name)
|
||||
|
||||
internal fun defaultReaderToolOrder(): List<ReaderTool> = ReaderTool.entries.toList()
|
||||
|
||||
internal fun defaultReaderBottomTools(): Set<String> {
|
||||
return ReaderTool.entries.filter { it.category == "Bottom Bar" }.map { it.name }.toSet()
|
||||
}
|
||||
|
||||
internal fun buildReaderToolbarItems(
|
||||
hiddenTools: Set<String>,
|
||||
toolOrder: List<ReaderTool>,
|
||||
bottomTools: Set<String>
|
||||
): List<FlatToolItem> {
|
||||
val toolbarTools = toolOrder.filter { it in epubToolbarTools }
|
||||
val topTools = toolbarTools.filter { !bottomTools.contains(it.name) && !hiddenTools.contains(it.name) }
|
||||
val bottomToolsList = toolbarTools.filter { bottomTools.contains(it.name) && !hiddenTools.contains(it.name) }
|
||||
val hiddenToolsList = toolbarTools.filter { hiddenTools.contains(it.name) }
|
||||
val moreTools = toolOrder.filter { it !in epubToolbarTools }
|
||||
|
||||
val list = mutableListOf<FlatToolItem>()
|
||||
|
||||
ToolbarSection.entries.forEach { section ->
|
||||
val tools = when (section) {
|
||||
ToolbarSection.TOP -> topTools
|
||||
ToolbarSection.BOTTOM -> bottomToolsList
|
||||
ToolbarSection.HIDDEN -> hiddenToolsList
|
||||
}
|
||||
list.add(FlatToolItem("header_${section.name}", FlatItemType.SECTION_HEADER, section = section, titleRes = section.titleRes))
|
||||
if (tools.isEmpty()) {
|
||||
list.add(FlatToolItem("empty_${section.name}", FlatItemType.EMPTY_PLACEHOLDER, section = section))
|
||||
} else {
|
||||
tools.forEach { tool ->
|
||||
list.add(FlatToolItem("tool_${tool.name}", FlatItemType.TOOL, tool = tool, section = section))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list.add(FlatToolItem("more_header", FlatItemType.MORE_HEADER, titleRes = R.string.toolbar_more_menu))
|
||||
moreTools.forEach { tool ->
|
||||
list.add(FlatToolItem("more_${tool.name}", FlatItemType.MORE_TOOL, tool = tool))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EpubReaderTopBar(
|
||||
isVisible: Boolean,
|
||||
|
|
@ -478,7 +525,7 @@ fun EpubReaderTopBar(
|
|||
|
||||
if (hiddenToolbarTools.isNotEmpty()) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Hidden tools") },
|
||||
text = { Text(stringResource(R.string.toolbar_hidden_tools_menu)) },
|
||||
onClick = { showHiddenToolsExpanded = !showHiddenToolsExpanded },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
|
|
@ -1717,37 +1764,11 @@ fun CustomizeToolsSheet(
|
|||
|
||||
var flatItems by remember {
|
||||
mutableStateOf(
|
||||
run {
|
||||
val toolbarTools = toolOrder.filter { it in epubToolbarTools }
|
||||
val topTools = toolbarTools.filter { !bottomTools.contains(it.name) && !hiddenTools.contains(it.name) }
|
||||
val bottomToolsList = toolbarTools.filter { bottomTools.contains(it.name) && !hiddenTools.contains(it.name) }
|
||||
val hiddenToolsList = toolbarTools.filter { hiddenTools.contains(it.name) }
|
||||
val moreTools = toolOrder.filter { it !in epubToolbarTools }
|
||||
|
||||
val list = mutableListOf<FlatToolItem>()
|
||||
|
||||
ToolbarSection.entries.forEach { section ->
|
||||
val tools = when(section) {
|
||||
ToolbarSection.TOP -> topTools
|
||||
ToolbarSection.BOTTOM -> bottomToolsList
|
||||
ToolbarSection.HIDDEN -> hiddenToolsList
|
||||
}
|
||||
list.add(FlatToolItem("header_${section.name}", FlatItemType.SECTION_HEADER, section = section, title = section.title))
|
||||
if (tools.isEmpty()) {
|
||||
list.add(FlatToolItem("empty_${section.name}", FlatItemType.EMPTY_PLACEHOLDER, section = section))
|
||||
} else {
|
||||
tools.forEach { tool ->
|
||||
list.add(FlatToolItem("tool_${tool.name}", FlatItemType.TOOL, tool = tool, section = section))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list.add(FlatToolItem("more_header", FlatItemType.MORE_HEADER, title = "More menu"))
|
||||
moreTools.forEach { tool ->
|
||||
list.add(FlatToolItem("more_${tool.name}", FlatItemType.MORE_TOOL, tool = tool))
|
||||
}
|
||||
list
|
||||
}
|
||||
buildReaderToolbarItems(
|
||||
hiddenTools = hiddenTools,
|
||||
toolOrder = toolOrder,
|
||||
bottomTools = bottomTools
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1812,6 +1833,22 @@ fun CustomizeToolsSheet(
|
|||
}
|
||||
}
|
||||
|
||||
val resetToDefault = {
|
||||
val defaultHiddenTools = defaultReaderHiddenTools()
|
||||
val defaultToolOrder = defaultReaderToolOrder()
|
||||
val defaultBottomTools = defaultReaderBottomTools()
|
||||
|
||||
localHiddenTools = defaultHiddenTools
|
||||
flatItems = buildReaderToolbarItems(
|
||||
hiddenTools = defaultHiddenTools,
|
||||
toolOrder = defaultToolOrder,
|
||||
bottomTools = defaultBottomTools
|
||||
)
|
||||
onUpdate(defaultHiddenTools)
|
||||
onPlacementUpdate(defaultBottomTools)
|
||||
onOrderUpdate(defaultToolOrder)
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||
|
|
@ -1828,12 +1865,17 @@ fun CustomizeToolsSheet(
|
|||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Customize Toolbar",
|
||||
text = stringResource(R.string.title_customize_toolbar),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
TextButton(onClick = resetToDefault) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(stringResource(R.string.action_reset))
|
||||
}
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(Icons.Default.Close, contentDescription = stringResource(R.string.action_close))
|
||||
}
|
||||
|
|
@ -1866,8 +1908,9 @@ fun CustomizeToolsSheet(
|
|||
) {
|
||||
when (item.type) {
|
||||
FlatItemType.SECTION_HEADER -> {
|
||||
val titleRes = item.titleRes
|
||||
Text(
|
||||
text = item.title ?: "",
|
||||
text = if (titleRes != null) stringResource(titleRes) else item.title.orEmpty(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
|
|
@ -1883,7 +1926,7 @@ fun CustomizeToolsSheet(
|
|||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), RoundedCornerShape(12.dp)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("Drop tools here", color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.toolbar_drop_tools_here), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
FlatItemType.TOOL -> {
|
||||
|
|
@ -1900,8 +1943,9 @@ fun CustomizeToolsSheet(
|
|||
)
|
||||
}
|
||||
FlatItemType.MORE_HEADER -> {
|
||||
val titleRes = item.titleRes
|
||||
Text(
|
||||
text = item.title ?: "More menu",
|
||||
text = if (titleRes != null) stringResource(titleRes) else item.title ?: stringResource(R.string.toolbar_more_menu),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp, start = 4.dp)
|
||||
|
|
@ -1909,7 +1953,7 @@ fun CustomizeToolsSheet(
|
|||
}
|
||||
FlatItemType.MORE_TOOL -> {
|
||||
MoreToolVisibilityRow(
|
||||
title = item.tool!!.title,
|
||||
title = stringResource(item.tool!!.titleRes),
|
||||
visible = !localHiddenTools.contains(item.tool.name),
|
||||
onToggle = {
|
||||
localHiddenTools = if (localHiddenTools.contains(item.tool.name)) {
|
||||
|
|
@ -1952,14 +1996,14 @@ private fun ToolbarDragRow(
|
|||
ToolPreviewIcon(tool)
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Text(
|
||||
text = tool.title,
|
||||
text = stringResource(tool.titleRes),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Icon(
|
||||
Icons.Default.Menu,
|
||||
contentDescription = "Drag to reorder",
|
||||
contentDescription = stringResource(R.string.content_desc_drag_to_reorder),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
|
|
@ -2007,25 +2051,26 @@ private fun MoreToolVisibilityRow(
|
|||
}
|
||||
}
|
||||
|
||||
enum class ToolbarSection(val title: String) {
|
||||
TOP("Top Bar"),
|
||||
BOTTOM("Bottom Bar"),
|
||||
HIDDEN("Hidden Tools")
|
||||
enum class ToolbarSection(@StringRes val titleRes: Int) {
|
||||
TOP(R.string.toolbar_top_bar),
|
||||
BOTTOM(R.string.toolbar_bottom_bar),
|
||||
HIDDEN(R.string.toolbar_hidden_tools)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToolPreviewIcon(tool: ReaderTool) {
|
||||
val title = stringResource(tool.titleRes)
|
||||
when (tool) {
|
||||
ReaderTool.DICTIONARY -> Icon(painterResource(id = R.drawable.dictionary), contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.THEME -> Icon(painterResource(id = R.drawable.palette), contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.SLIDER -> Icon(painterResource(id = R.drawable.slider), contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.TOC -> Icon(Icons.Default.Menu, contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.FORMAT -> Icon(painterResource(id = R.drawable.format_size), contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.SEARCH -> Icon(Icons.Default.Search, contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.AI_FEATURES -> Icon(painterResource(id = R.drawable.ai), contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.TTS_CONTROLS -> Icon(painterResource(id = R.drawable.text_to_speech), contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.SCREEN_ORIENTATION -> Icon(Icons.Default.ScreenRotation, contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
else -> Icon(Icons.Default.MoreVert, contentDescription = tool.title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.DICTIONARY -> Icon(painterResource(id = R.drawable.dictionary), contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.THEME -> Icon(painterResource(id = R.drawable.palette), contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.SLIDER -> Icon(painterResource(id = R.drawable.slider), contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.TOC -> Icon(Icons.Default.Menu, contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.FORMAT -> Icon(painterResource(id = R.drawable.format_size), contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.SEARCH -> Icon(Icons.Default.Search, contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.AI_FEATURES -> Icon(painterResource(id = R.drawable.ai), contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.TTS_CONTROLS -> Icon(painterResource(id = R.drawable.text_to_speech), contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
ReaderTool.SCREEN_ORIENTATION -> Icon(Icons.Default.ScreenRotation, contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
else -> Icon(Icons.Default.MoreVert, contentDescription = title, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2050,7 +2095,7 @@ private fun HiddenEpubToolMenuItem(
|
|||
else -> true
|
||||
}
|
||||
DropdownMenuItem(
|
||||
text = { Text(tool.title) },
|
||||
text = { Text(stringResource(tool.titleRes)) },
|
||||
enabled = enabled,
|
||||
onClick = {
|
||||
showMoreMenu()
|
||||
|
|
@ -2198,7 +2243,11 @@ fun TtsOverlayControls(
|
|||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(
|
||||
if (activeMode == com.aryan.reader.tts.TtsPlaybackManager.TtsMode.CLOUD) "✨ Cloud" else "📱 Device",
|
||||
if (activeMode == com.aryan.reader.tts.TtsPlaybackManager.TtsMode.CLOUD) {
|
||||
stringResource(R.string.tts_mode_cloud_ai)
|
||||
} else {
|
||||
stringResource(R.string.tts_mode_device_native)
|
||||
},
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
|
|
@ -2211,7 +2260,7 @@ fun TtsOverlayControls(
|
|||
) {
|
||||
val voiceName = if (activeMode == com.aryan.reader.tts.TtsPlaybackManager.TtsMode.CLOUD) {
|
||||
GEMINI_TTS_SPEAKERS.find { it.id == ttsState.speakerId }?.name ?: ttsState.speakerId
|
||||
} else loadNativeVoice(context)?.split("-")?.lastOrNull() ?: "Default"
|
||||
} else loadNativeVoice(context)?.split("-")?.lastOrNull() ?: stringResource(R.string.label_default)
|
||||
|
||||
Text(
|
||||
voiceName,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue