String extract (#148)
* FolderSyncWorker: update supported file types in getFileType * Extract hardcoded strings to `strings.xml` and `plurals.xml` * Extract hardcoded strings to `strings.xml` and `plurals.xml` * Extract hardcoded strings to `strings.xml` and `plurals.xml` in MainViewModel * Extract hardcoded strings to `strings.xml` and `plurals.xml` in Common.kt * Extract hardcoded strings to `strings.xml` and `plurals.xml` in Epub Reader
This commit is contained in:
parent
da1c5bce1e
commit
381193d774
20 changed files with 1388 additions and 855 deletions
|
|
@ -36,11 +36,13 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.aryan.reader.BuildConfig
|
||||
import com.aryan.reader.R
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -83,7 +85,7 @@ fun DictionarySettingsDialog(
|
|||
.padding(24.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Lookup Settings",
|
||||
text = stringResource(R.string.dict_lookup_settings),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
|
|
@ -98,7 +100,7 @@ fun DictionarySettingsDialog(
|
|||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = "Dictionary Engine",
|
||||
text = stringResource(R.string.dict_dictionary_engine),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
|
|
@ -114,29 +116,29 @@ fun DictionarySettingsDialog(
|
|||
onClick = { onToggleOnlineDictionary(true) },
|
||||
shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2)
|
||||
) {
|
||||
Text("Smart (AI)")
|
||||
Text(stringResource(R.string.dict_smart_ai))
|
||||
}
|
||||
SegmentedButton(
|
||||
selected = !useOnlineDictionary,
|
||||
onClick = { onToggleOnlineDictionary(false) },
|
||||
shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2)
|
||||
) {
|
||||
Text("External App")
|
||||
Text(stringResource(R.string.dict_external_app))
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = if (useOnlineDictionary)
|
||||
"Uses AI for definitions. Will fallback to the external app below if offline or if the selected phrase is too long."
|
||||
stringResource(R.string.dict_ai_description)
|
||||
else
|
||||
"Uses the selected app for dictionary lookups.",
|
||||
stringResource(R.string.dict_external_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = if (useOnlineDictionary) "Fallback App" else "Dictionary App",
|
||||
text = if (useOnlineDictionary) stringResource(R.string.dict_fallback_app) else stringResource(R.string.dict_dictionary_app),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
|
|
@ -146,13 +148,13 @@ fun DictionarySettingsDialog(
|
|||
apps = dictionaryApps,
|
||||
selectedPackageName = selectedDictionaryPackageName,
|
||||
onSelect = onSelectDictionaryPackage,
|
||||
placeholder = "Select an app"
|
||||
placeholder = stringResource(R.string.dict_select_app)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = "Dictionary",
|
||||
text = stringResource(R.string.tooltip_dictionary),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
|
|
@ -161,7 +163,7 @@ fun DictionarySettingsDialog(
|
|||
apps = dictionaryApps,
|
||||
selectedPackageName = selectedDictionaryPackageName,
|
||||
onSelect = onSelectDictionaryPackage,
|
||||
placeholder = "Select an app"
|
||||
placeholder = stringResource(R.string.dict_select_app)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -169,13 +171,13 @@ fun DictionarySettingsDialog(
|
|||
|
||||
// ── Translate ──
|
||||
Text(
|
||||
text = "Translate",
|
||||
text = stringResource(R.string.dict_translate),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "App used for translating selected text.",
|
||||
text = stringResource(R.string.dict_translate_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
|
|
@ -185,20 +187,20 @@ fun DictionarySettingsDialog(
|
|||
apps = dictionaryApps,
|
||||
selectedPackageName = selectedTranslatePackageName,
|
||||
onSelect = onSelectTranslatePackage,
|
||||
placeholder = "Select an app"
|
||||
placeholder = stringResource(R.string.dict_select_app)
|
||||
)
|
||||
|
||||
SectionDivider()
|
||||
|
||||
// ── Search ──
|
||||
Text(
|
||||
text = "Search",
|
||||
text = stringResource(R.string.tooltip_search),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "App used for web searches.",
|
||||
text = stringResource(R.string.dict_search_app_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
|
|
@ -208,7 +210,7 @@ fun DictionarySettingsDialog(
|
|||
apps = searchApps,
|
||||
selectedPackageName = selectedSearchPackageName,
|
||||
onSelect = onSelectSearchPackage,
|
||||
placeholder = "Select an app"
|
||||
placeholder = stringResource(R.string.dict_select_app)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +274,7 @@ private fun AppSelectionDropdown(
|
|||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
"None",
|
||||
stringResource(R.string.dict_none),
|
||||
color = if (!hasSelection) MaterialTheme.colorScheme.primary
|
||||
else MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
|
@ -281,7 +283,7 @@ private fun AppSelectionDropdown(
|
|||
{
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "Selected",
|
||||
contentDescription = stringResource(R.string.content_desc_selected),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
|
@ -327,7 +329,7 @@ private fun AppSelectionDropdown(
|
|||
{
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "Selected",
|
||||
contentDescription = stringResource(R.string.content_desc_selected),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,18 @@
|
|||
*/
|
||||
package com.aryan.reader.epubreader
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import timber.log.Timber
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.aryan.reader.AiDefinitionPopup
|
||||
import com.aryan.reader.AiDefinitionResult
|
||||
import com.aryan.reader.R
|
||||
|
|
@ -137,6 +142,7 @@ suspend fun executeRecapLogic(
|
|||
characterLimit: Int,
|
||||
summaryCacheManager: SummaryCacheManager,
|
||||
paginator: IPaginator?,
|
||||
context: Context,
|
||||
onProgressUpdate: (String) -> Unit,
|
||||
onResultUpdate: (String) -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
|
|
@ -216,6 +222,7 @@ suspend fun executeRecapLogic(
|
|||
fetchRecap(
|
||||
pastSummaries = pastSummaries,
|
||||
currentText = finalContextText,
|
||||
context = context,
|
||||
onUpdate = { chunk -> onResultUpdate(chunk) },
|
||||
onError = { error -> onError(error) },
|
||||
onFinish = { onFinish() }
|
||||
|
|
@ -250,7 +257,7 @@ fun EpubReaderAiOverlays(
|
|||
) {
|
||||
if (showSummarizationPopup) {
|
||||
SummarizationPopup(
|
||||
title = "Chapter Summary",
|
||||
title = stringResource(R.string.ai_chapter_summary),
|
||||
result = summarizationResult,
|
||||
isLoading = isSummarizationLoading,
|
||||
onDismiss = onDismissSummarization,
|
||||
|
|
@ -260,7 +267,7 @@ fun EpubReaderAiOverlays(
|
|||
|
||||
if (showRecapPopup) {
|
||||
SummarizationPopup(
|
||||
title = "Story Recap (Beta)",
|
||||
title = stringResource(R.string.ai_story_recap_beta),
|
||||
result = recapResult,
|
||||
isLoading = isRecapLoading,
|
||||
onDismiss = onDismissRecap,
|
||||
|
|
@ -272,16 +279,26 @@ fun EpubReaderAiOverlays(
|
|||
AlertDialog(
|
||||
onDismissRequest = onDismissSummarizationUpsell,
|
||||
icon = { Icon(painter = painterResource(id = R.drawable.summarize), contentDescription = null) },
|
||||
title = { Text("Unlock Chapter Summarization") },
|
||||
text = { Text("Get concise summaries of any chapter with Episteme Pro. Upgrade to start using this feature.") },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.ai_unlock_summarization),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.ai_unlock_summarization_desc),
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDismissSummarizationUpsell()
|
||||
onNavigateToPro()
|
||||
}) { Text("Learn More") }
|
||||
}) { Text(stringResource(R.string.action_learn_more)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissSummarizationUpsell) { Text("Not Now") }
|
||||
TextButton(onClick = onDismissSummarizationUpsell) { Text(stringResource(R.string.action_not_now)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -293,7 +310,6 @@ fun EpubReaderAiOverlays(
|
|||
isLoading = isAiDefinitionLoading,
|
||||
onDismiss = onDismissAiDefinition,
|
||||
isMainTtsActive = isTtsSessionActive,
|
||||
// Pass it down
|
||||
onOpenExternalDictionary = {
|
||||
selectedTextForAi?.let { text -> onOpenExternalDictionary(text) }
|
||||
}
|
||||
|
|
@ -304,16 +320,16 @@ fun EpubReaderAiOverlays(
|
|||
AlertDialog(
|
||||
onDismissRequest = onDismissDictionaryUpsell,
|
||||
icon = { Icon(painter = painterResource(id = R.drawable.ai), contentDescription = null) },
|
||||
title = { Text("Unlock Smart Dictionary") },
|
||||
text = { Text("Defining entire phrases and paragraphs up to 2000 characters is a Pro feature. Upgrade to get instant definitions for any selected text.") },
|
||||
title = { Text(stringResource(R.string.ai_unlock_smart_dict)) },
|
||||
text = { Text(stringResource(R.string.ai_unlock_smart_dict_desc)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDismissDictionaryUpsell()
|
||||
onNavigateToPro()
|
||||
}) { Text("Learn More") }
|
||||
}) { Text(stringResource(R.string.action_learn_more)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissDictionaryUpsell) { Text("Not Now") }
|
||||
TextButton(onClick = onDismissDictionaryUpsell) { Text(stringResource(R.string.action_not_now)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.edit
|
||||
import com.aryan.reader.R
|
||||
|
|
@ -365,7 +366,7 @@ fun BookmarkButton(
|
|||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.bookmark),
|
||||
contentDescription = "Bookmark",
|
||||
contentDescription = stringResource(R.string.content_desc_bookmark_icon),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
|
@ -406,14 +407,14 @@ fun PaletteManagerDialog(
|
|||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Customize Palette", style = MaterialTheme.typography.titleMedium) },
|
||||
title = { Text(stringResource(R.string.dialog_customize_palette), style = MaterialTheme.typography.titleMedium) },
|
||||
text = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Tap a slot to edit:", style = MaterialTheme.typography.bodySmall)
|
||||
Text(stringResource(R.string.palette_tap_slot_to_edit), style = MaterialTheme.typography.bodySmall)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
|
|
@ -427,7 +428,7 @@ fun PaletteManagerDialog(
|
|||
.background(colorEnum.color, CircleShape)
|
||||
.border(
|
||||
width = if (isSelected) 3.dp else 1.dp,
|
||||
color = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent, // Thin ring if selected
|
||||
color = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent,
|
||||
shape = CircleShape
|
||||
)
|
||||
.clip(CircleShape)
|
||||
|
|
@ -436,7 +437,7 @@ fun PaletteManagerDialog(
|
|||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "Selected Slot",
|
||||
contentDescription = stringResource(R.string.content_desc_selected_slot),
|
||||
tint = if (colorEnum == HighlightColor.WHITE) Color.Black else Color.White,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
|
|
@ -448,7 +449,7 @@ fun PaletteManagerDialog(
|
|||
HorizontalDivider()
|
||||
|
||||
// 2. Bottom Grid: Available Colors
|
||||
Text("Select a color for the slot:", style = MaterialTheme.typography.bodySmall)
|
||||
Text(stringResource(R.string.palette_select_color_for_slot), style = MaterialTheme.typography.bodySmall)
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = 40.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
|
|
|
|||
|
|
@ -211,15 +211,15 @@ fun EpubReaderTopBar(
|
|||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.dictionary),
|
||||
contentDescription = "Dictionary Settings"
|
||||
contentDescription = stringResource(R.string.content_desc_dictionary_settings)
|
||||
)
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = "Theme",
|
||||
description = "Theme Settings",
|
||||
text = stringResource(R.string.tooltip_theme),
|
||||
description = stringResource(R.string.tooltip_theme_desc),
|
||||
onClick = onOpenThemeSettings
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.palette), contentDescription = "Theme Settings")
|
||||
Icon(painter = painterResource(id = R.drawable.palette), contentDescription = stringResource(R.string.tooltip_theme_desc))
|
||||
}
|
||||
Box {
|
||||
var showMoreMenu by remember { mutableStateOf(false) }
|
||||
|
|
@ -228,7 +228,7 @@ fun EpubReaderTopBar(
|
|||
description = stringResource(R.string.tooltip_more_options_desc),
|
||||
onClick = { showMoreMenu = true }
|
||||
) {
|
||||
Icon(Icons.Default.MoreVert, contentDescription = "More Options")
|
||||
Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.content_desc_more_options))
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
|
|
@ -237,7 +237,7 @@ fun EpubReaderTopBar(
|
|||
) {
|
||||
if (onToggleReflow != null) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("View Original PDF") },
|
||||
text = { Text(stringResource(R.string.menu_view_original_pdf)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onToggleReflow()
|
||||
|
|
@ -256,7 +256,7 @@ fun EpubReaderTopBar(
|
|||
onDeleteReflow?.let {
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text("Delete Text View") },
|
||||
text = { Text(stringResource(R.string.menu_delete_text_view)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
it()
|
||||
|
|
@ -275,26 +275,26 @@ fun EpubReaderTopBar(
|
|||
}
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("Reading Mode: Vertical") },
|
||||
text = { Text(stringResource(R.string.menu_reading_mode_vertical)) },
|
||||
enabled = !isTtsActive,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onChangeRenderMode(RenderMode.VERTICAL_SCROLL)
|
||||
},
|
||||
trailingIcon = { if (currentRenderMode == RenderMode.VERTICAL_SCROLL) Icon(Icons.Default.Check, contentDescription = "Selected") }
|
||||
trailingIcon = { if (currentRenderMode == RenderMode.VERTICAL_SCROLL) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_selected)) }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Reading Mode: Paginated") },
|
||||
text = { Text(stringResource(R.string.menu_reading_mode_paginated)) },
|
||||
enabled = !isTtsActive,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onChangeRenderMode(RenderMode.PAGINATED)
|
||||
},
|
||||
trailingIcon = { if (currentRenderMode == RenderMode.PAGINATED) Icon(Icons.Default.Check, contentDescription = "Selected") }
|
||||
trailingIcon = { if (currentRenderMode == RenderMode.PAGINATED) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_selected)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text(if (isBookmarked) "Remove bookmark" else "Bookmark this page") },
|
||||
text = { Text(if (isBookmarked) stringResource(R.string.menu_remove_bookmark) else stringResource(R.string.menu_bookmark_this_page)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onToggleBookmark()
|
||||
|
|
@ -302,20 +302,20 @@ fun EpubReaderTopBar(
|
|||
)
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text("Tap to Turn Pages") },
|
||||
text = { Text(stringResource(R.string.menu_tap_to_turn_pages)) },
|
||||
enabled = currentRenderMode == RenderMode.PAGINATED,
|
||||
onClick = {
|
||||
onToggleTapToNavigate(!tapToNavigateEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (tapToNavigateEnabled) Icon(Icons.Default.Check, contentDescription = "Enabled") }
|
||||
trailingIcon = { if (tapToNavigateEnabled) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
if (currentRenderMode == RenderMode.VERTICAL_SCROLL) "Volume Button Scrolling"
|
||||
else "Volume Button Page Turn"
|
||||
if (currentRenderMode == RenderMode.VERTICAL_SCROLL) stringResource(R.string.menu_volume_button_scrolling)
|
||||
else stringResource(R.string.menu_volume_button_page_turn)
|
||||
)
|
||||
},
|
||||
enabled = true,
|
||||
|
|
@ -323,33 +323,33 @@ fun EpubReaderTopBar(
|
|||
onToggleVolumeScroll(!volumeScrollEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (volumeScrollEnabled) Icon(Icons.Default.Check, contentDescription = "Enabled") }
|
||||
trailingIcon = { if (volumeScrollEnabled) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("Realistic Page Turns") },
|
||||
text = { Text(stringResource(R.string.menu_realistic_page_turns)) },
|
||||
enabled = currentRenderMode == RenderMode.PAGINATED,
|
||||
onClick = {
|
||||
onTogglePageTurnAnimation(!isPageTurnAnimationEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (isPageTurnAnimationEnabled) Icon(Icons.Default.Check, contentDescription = "Enabled") }
|
||||
trailingIcon = { if (isPageTurnAnimationEnabled) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("Keep Screen On") },
|
||||
text = { Text(stringResource(R.string.menu_keep_screen_on)) },
|
||||
onClick = {
|
||||
onToggleKeepScreenOn(!isKeepScreenOn)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (isKeepScreenOn) Icon(Icons.Default.Check, contentDescription = "Enabled") }
|
||||
trailingIcon = { if (isKeepScreenOn) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("Visual Options") },
|
||||
text = { Text(stringResource(R.string.menu_visual_options)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenVisualOptions()
|
||||
|
|
@ -361,7 +361,7 @@ fun EpubReaderTopBar(
|
|||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("Auto Scroll") },
|
||||
text = { Text(stringResource(R.string.menu_auto_scroll)) },
|
||||
enabled = !isTtsActive && currentRenderMode == RenderMode.VERTICAL_SCROLL,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
|
|
@ -372,7 +372,7 @@ fun EpubReaderTopBar(
|
|||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("TTS Voice Settings") },
|
||||
text = { Text(stringResource(R.string.menu_tts_voice_settings)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenDeviceVoiceSettings()
|
||||
|
|
@ -384,7 +384,7 @@ fun EpubReaderTopBar(
|
|||
|
||||
if (BuildConfig.DEBUG) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("TTS Settings (Debug)") },
|
||||
text = { Text(stringResource(R.string.menu_tts_settings_debug)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenTtsSettings()
|
||||
|
|
@ -445,28 +445,28 @@ fun EpubReaderBottomBar(
|
|||
onClick = onOpenSlider,
|
||||
enabled = currentRenderMode != RenderMode.VERTICAL_SCROLL
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.slider), contentDescription = "Navigate with slider")
|
||||
Icon(painter = painterResource(id = R.drawable.slider), contentDescription = stringResource(R.string.content_desc_navigate_slider))
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_toc),
|
||||
description = stringResource(R.string.tooltip_toc_desc),
|
||||
onClick = onOpenDrawer
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Menu, contentDescription = "Chapters Menu")
|
||||
Icon(imageVector = Icons.Default.Menu, contentDescription = stringResource(R.string.content_desc_chapters_menu))
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_format),
|
||||
description = stringResource(R.string.tooltip_format_desc),
|
||||
onClick = onToggleFormat
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.format_size), contentDescription = "Text Formatting")
|
||||
Icon(painter = painterResource(id = R.drawable.format_size), contentDescription = stringResource(R.string.content_desc_text_formatting))
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_search),
|
||||
description = stringResource(R.string.tooltip_search_desc),
|
||||
onClick = onToggleSearch
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Search, contentDescription = "Search")
|
||||
Icon(imageVector = Icons.Default.Search, contentDescription = stringResource(R.string.tooltip_search))
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||
|
|
@ -485,7 +485,7 @@ fun EpubReaderBottomBar(
|
|||
onDismissRequest = { showAiFeaturesMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Chapter Summarization") },
|
||||
text = { Text(stringResource(R.string.menu_chapter_summarization)) },
|
||||
onClick = {
|
||||
showAiFeaturesMenu = false
|
||||
onSummarize()
|
||||
|
|
@ -494,7 +494,7 @@ fun EpubReaderBottomBar(
|
|||
if (BuildConfig.DEBUG && isProUser) {
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text("Recap (Beta)") },
|
||||
text = { Text(stringResource(R.string.menu_recap_beta)) },
|
||||
onClick = {
|
||||
showAiFeaturesMenu = false
|
||||
onRecap()
|
||||
|
|
@ -519,7 +519,7 @@ fun EpubReaderBottomBar(
|
|||
) {
|
||||
Icon(
|
||||
painter = if (isTtsSessionActive) painterResource(id = R.drawable.close) else painterResource(id = R.drawable.text_to_speech),
|
||||
contentDescription = if (isTtsSessionActive) "Stop TTS" else "Start TTS"
|
||||
contentDescription = if (isTtsSessionActive) stringResource(R.string.content_desc_stop_tts) else stringResource(R.string.content_desc_start_tts)
|
||||
)
|
||||
}
|
||||
if (isTtsSessionActive) {
|
||||
|
|
@ -537,7 +537,7 @@ fun EpubReaderBottomBar(
|
|||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = if (ttsState.isPlaying) R.drawable.pause else R.drawable.play),
|
||||
contentDescription = if (ttsState.isPlaying) "Pause TTS" else "Resume TTS"
|
||||
contentDescription = if (ttsState.isPlaying) stringResource(R.string.content_desc_pause_tts) else stringResource(R.string.content_desc_resume_tts)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -589,7 +589,7 @@ fun EpubReaderPageSlider(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Exit slider navigation"
|
||||
contentDescription = stringResource(R.string.content_desc_exit_slider)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -681,7 +681,7 @@ fun EpubReaderPageSlider(
|
|||
) {
|
||||
Image(
|
||||
bitmap = thumbnail.asImageBitmap(),
|
||||
contentDescription = "Start page thumbnail",
|
||||
contentDescription = stringResource(R.string.content_desc_start_page_thumbnail),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
|
|
@ -918,7 +918,7 @@ fun AutoScrollControls(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ChevronLeft,
|
||||
contentDescription = "Expand",
|
||||
contentDescription = stringResource(R.string.content_desc_expand),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
|
@ -934,7 +934,7 @@ fun AutoScrollControls(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
|
||||
contentDescription = if (isPlaying) "Pause" else "Play",
|
||||
contentDescription = if (isPlaying) stringResource(R.string.content_desc_pause_playback) else stringResource(R.string.content_desc_start_playback),
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
|
|
@ -967,13 +967,13 @@ fun AutoScrollControls(
|
|||
.padding(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (isLocalMode) "Local Speed" else "Global Speed",
|
||||
text = if (isLocalMode) stringResource(R.string.auto_scroll_local_speed) else stringResource(R.string.auto_scroll_global_speed),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowDropDown,
|
||||
contentDescription = "Select Mode",
|
||||
contentDescription = stringResource(R.string.content_desc_select_mode),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
|
|
@ -986,8 +986,8 @@ fun AutoScrollControls(
|
|||
DropdownMenuItem(
|
||||
text = {
|
||||
Column {
|
||||
Text("Global Speed", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text("Applies to all files", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.auto_scroll_global_speed), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.auto_scroll_applies_all_files), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
|
|
@ -1002,8 +1002,8 @@ fun AutoScrollControls(
|
|||
DropdownMenuItem(
|
||||
text = {
|
||||
Column {
|
||||
Text("Local Speed", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text("Saved for this file only", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.auto_scroll_local_speed), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.auto_scroll_saved_for_file), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
|
|
@ -1024,7 +1024,7 @@ fun AutoScrollControls(
|
|||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.music_note),
|
||||
contentDescription = if (isMusicianMode) "Disable Musician Mode" else "Enable Musician Mode",
|
||||
contentDescription = if (isMusicianMode) stringResource(R.string.content_desc_disable_musician_mode) else stringResource(R.string.content_desc_enable_musician_mode),
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = if (isMusicianMode) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
|
@ -1035,7 +1035,7 @@ fun AutoScrollControls(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.SwapHoriz,
|
||||
contentDescription = "Swap Controls",
|
||||
contentDescription = stringResource(R.string.content_desc_swap_controls),
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
|
@ -1046,7 +1046,7 @@ fun AutoScrollControls(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ChevronRight,
|
||||
contentDescription = "Collapse",
|
||||
contentDescription = stringResource(R.string.content_desc_collapse),
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
|
@ -1057,7 +1057,7 @@ fun AutoScrollControls(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = "Close",
|
||||
contentDescription = stringResource(R.string.action_close),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
|
|
@ -1085,7 +1085,7 @@ fun AutoScrollControls(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
|
||||
contentDescription = if (isPlaying) "Pause" else "Play",
|
||||
contentDescription = if (isPlaying) stringResource(R.string.tooltip_tts_pause) else stringResource(R.string.content_desc_start_playback),
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
|
@ -1110,13 +1110,13 @@ fun AutoScrollControls(
|
|||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
SpeedDropdown(
|
||||
label = "Min",
|
||||
label = stringResource(R.string.label_min),
|
||||
currentValue = minSpeed,
|
||||
options = speedOptions,
|
||||
onValueChange = onMinSpeedChange
|
||||
)
|
||||
SpeedDropdown(
|
||||
label = "Max",
|
||||
label = stringResource(R.string.label_max),
|
||||
currentValue = maxSpeed,
|
||||
options = speedOptions,
|
||||
onValueChange = onMaxSpeedChange
|
||||
|
|
@ -1193,7 +1193,7 @@ fun AutoScrollControls(
|
|||
onClick = { onSpeedChange((speed - 0.1f).coerceAtLeast(minSpeed)) },
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Remove, "Slower")
|
||||
Icon(Icons.Default.Remove, stringResource(R.string.content_desc_slower))
|
||||
}
|
||||
Text(
|
||||
text = "%.1fx".format(speed),
|
||||
|
|
@ -1204,7 +1204,7 @@ fun AutoScrollControls(
|
|||
onClick = { onSpeedChange((speed + 0.1f).coerceAtMost(safeMax)) },
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Add, "Faster")
|
||||
Icon(Icons.Default.Add, stringResource(R.string.content_desc_faster))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,11 +82,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastSumBy
|
||||
import com.aryan.reader.R
|
||||
import com.aryan.reader.RenderMode
|
||||
import com.aryan.reader.epub.EpubChapter
|
||||
import com.aryan.reader.epub.EpubTocEntry
|
||||
|
|
@ -224,17 +226,17 @@ fun EpubReaderDrawerSheet(
|
|||
Tab(
|
||||
selected = drawerPagerState.currentPage == 0,
|
||||
onClick = { drawerScope.launch { drawerPagerState.animateScrollToPage(0) } },
|
||||
text = { Text("Chapters") }
|
||||
text = { Text(stringResource(R.string.tab_chapters)) }
|
||||
)
|
||||
Tab(
|
||||
selected = drawerPagerState.currentPage == 1,
|
||||
onClick = { drawerScope.launch { drawerPagerState.animateScrollToPage(1) } },
|
||||
text = { Text("Bookmarks") }
|
||||
text = { Text(stringResource(R.string.tab_bookmarks)) }
|
||||
)
|
||||
Tab(
|
||||
selected = drawerPagerState.currentPage == 2,
|
||||
onClick = { drawerScope.launch { drawerPagerState.animateScrollToPage(2) } },
|
||||
text = { Text("Highlights") }
|
||||
text = { Text(stringResource(R.string.tab_highlights)) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +444,7 @@ private fun TocTreeItem(
|
|||
if (hasChildren) {
|
||||
Icon(
|
||||
imageVector = if (isExpanded) Icons.Default.KeyboardArrowDown else Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = if (isExpanded) "Collapse" else "Expand",
|
||||
contentDescription = if (isExpanded) stringResource(R.string.content_desc_collapse) else stringResource(R.string.content_desc_expand),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
|
@ -477,7 +479,7 @@ private fun BookmarksList(
|
|||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
"You haven't added any bookmarks yet.",
|
||||
stringResource(R.string.no_bookmarks_yet),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
|
@ -531,7 +533,7 @@ private fun BookmarksList(
|
|||
IconButton(onClick = { bookmarkMenuExpandedFor = bookmark }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "More options for bookmark"
|
||||
contentDescription = stringResource(R.string.content_desc_more_options_bookmark)
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
|
|
@ -539,14 +541,14 @@ private fun BookmarksList(
|
|||
onDismissRequest = { bookmarkMenuExpandedFor = null }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Rename") },
|
||||
text = { Text(stringResource(R.string.menu_rename)) },
|
||||
onClick = {
|
||||
showRenameBookmarkDialog = bookmark
|
||||
bookmarkMenuExpandedFor = null
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Delete") },
|
||||
text = { Text(stringResource(R.string.action_delete)) },
|
||||
onClick = {
|
||||
showDeleteConfirmDialogFor = bookmark
|
||||
bookmarkMenuExpandedFor = null
|
||||
|
|
@ -573,12 +575,12 @@ private fun BookmarksList(
|
|||
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRenameBookmarkDialog = null },
|
||||
title = { Text("Rename Bookmark") },
|
||||
title = { Text(stringResource(R.string.dialog_rename_bookmark)) },
|
||||
text = {
|
||||
androidx.compose.material3.OutlinedTextField(
|
||||
value = newTitle,
|
||||
onValueChange = { newTitle = it },
|
||||
label = { Text("New Name") },
|
||||
label = { Text(stringResource(R.string.label_new_name)) },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = currentName,
|
||||
|
|
@ -601,12 +603,12 @@ private fun BookmarksList(
|
|||
showRenameBookmarkDialog = null
|
||||
}
|
||||
) {
|
||||
Text("Save")
|
||||
Text(stringResource(R.string.action_save))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showRenameBookmarkDialog = null }) {
|
||||
Text("Cancel")
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -615,8 +617,8 @@ private fun BookmarksList(
|
|||
showDeleteConfirmDialogFor?.let { bookmarkToDelete ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteConfirmDialogFor = null },
|
||||
title = { Text("Delete Bookmark?") },
|
||||
text = { Text("Are you sure you want to permanently delete this bookmark?") },
|
||||
title = { Text(stringResource(R.string.dialog_delete_bookmark)) },
|
||||
text = { Text(stringResource(R.string.dialog_delete_bookmark_desc)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
|
|
@ -624,12 +626,12 @@ private fun BookmarksList(
|
|||
showDeleteConfirmDialogFor = null
|
||||
}
|
||||
) {
|
||||
Text("Delete")
|
||||
Text(stringResource(R.string.action_delete))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteConfirmDialogFor = null }) {
|
||||
Text("Cancel")
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -646,7 +648,7 @@ private fun HighlightsList(
|
|||
) {
|
||||
if (userHighlights.isEmpty()) {
|
||||
Box(modifier = Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) {
|
||||
Text("No highlights yet.", style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center)
|
||||
Text(stringResource(R.string.no_highlights_yet), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center)
|
||||
}
|
||||
} else {
|
||||
var highlightMenuExpandedFor by remember { mutableStateOf<UserHighlight?>(null) }
|
||||
|
|
@ -663,7 +665,7 @@ private fun HighlightsList(
|
|||
items = userHighlights.sortedBy { it.chapterIndex },
|
||||
key = { it.id }
|
||||
) { highlight ->
|
||||
val chapterTitle = chapters.getOrNull(highlight.chapterIndex)?.title ?: "Unknown Chapter"
|
||||
val chapterTitle = chapters.getOrNull(highlight.chapterIndex)?.title ?: stringResource(R.string.unknown_chapter)
|
||||
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
|
|
@ -694,7 +696,7 @@ private fun HighlightsList(
|
|||
IconButton(onClick = { highlightMenuExpandedFor = highlight }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "Options"
|
||||
contentDescription = stringResource(R.string.content_desc_options)
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
|
|
@ -702,7 +704,7 @@ private fun HighlightsList(
|
|||
onDismissRequest = { highlightMenuExpandedFor = null }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Delete") },
|
||||
text = { Text(stringResource(R.string.action_delete)) },
|
||||
onClick = {
|
||||
showHighlightDeleteDialogFor = highlight
|
||||
highlightMenuExpandedFor = null
|
||||
|
|
@ -726,8 +728,8 @@ private fun HighlightsList(
|
|||
showHighlightDeleteDialogFor?.let { highlightToDelete ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showHighlightDeleteDialogFor = null },
|
||||
title = { Text("Delete Highlight?") },
|
||||
text = { Text("Are you sure you want to permanently delete this highlight?") },
|
||||
title = { Text(stringResource(R.string.dialog_delete_highlight)) },
|
||||
text = { Text(stringResource(R.string.dialog_delete_highlight_desc)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
|
|
@ -735,12 +737,12 @@ private fun HighlightsList(
|
|||
showHighlightDeleteDialogFor = null
|
||||
}
|
||||
) {
|
||||
Text("Delete")
|
||||
Text(stringResource(R.string.action_delete))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showHighlightDeleteDialogFor = null }) {
|
||||
Text("Cancel")
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -120,18 +121,22 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
|
|
@ -144,6 +149,7 @@ import com.aryan.reader.BuiltInThemes
|
|||
import com.aryan.reader.CustomTopBanner
|
||||
import com.aryan.reader.DeviceVoiceSettingsSheet
|
||||
import com.aryan.reader.MainViewModel
|
||||
import com.aryan.reader.R
|
||||
import com.aryan.reader.ReaderThemePanel
|
||||
import com.aryan.reader.RenderMode
|
||||
import com.aryan.reader.SearchResult
|
||||
|
|
@ -157,6 +163,7 @@ import com.aryan.reader.fetchAiDefinition
|
|||
import com.aryan.reader.loadCustomThemes
|
||||
import com.aryan.reader.loadReaderThemeId
|
||||
import com.aryan.reader.paginatedreader.BookPaginator
|
||||
import com.aryan.reader.paginatedreader.CfiUtils
|
||||
import com.aryan.reader.paginatedreader.HeaderBlock
|
||||
import com.aryan.reader.paginatedreader.IPaginator
|
||||
import com.aryan.reader.paginatedreader.ListItemBlock
|
||||
|
|
@ -661,15 +668,13 @@ fun EpubReaderHost(
|
|||
isAiDefinitionLoading = true
|
||||
aiDefinitionResult = null
|
||||
fetchAiDefinition(
|
||||
text = word,
|
||||
onUpdate = { chunk ->
|
||||
val currentDefinition = aiDefinitionResult?.definition ?: ""
|
||||
aiDefinitionResult = AiDefinitionResult(definition = currentDefinition + chunk)
|
||||
},
|
||||
onError = { error ->
|
||||
aiDefinitionResult = AiDefinitionResult(error = error)
|
||||
},
|
||||
onFinish = { isAiDefinitionLoading = false }
|
||||
text = word, onUpdate = { chunk ->
|
||||
val currentDefinition = aiDefinitionResult?.definition ?: ""
|
||||
aiDefinitionResult =
|
||||
AiDefinitionResult(definition = currentDefinition + chunk)
|
||||
}, onError = { error ->
|
||||
aiDefinitionResult = AiDefinitionResult(error = error)
|
||||
}, onFinish = { isAiDefinitionLoading = false }, context = context
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -911,7 +916,7 @@ fun EpubReaderHost(
|
|||
}
|
||||
}
|
||||
|
||||
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
|
||||
val configuration = LocalConfiguration.current
|
||||
var lastOrientation by remember { mutableIntStateOf(configuration.orientation) }
|
||||
|
||||
LaunchedEffect(configuration.orientation) {
|
||||
|
|
@ -1122,8 +1127,8 @@ fun EpubReaderHost(
|
|||
var foundIdx = -1
|
||||
for (i in chunks.indices) {
|
||||
val c = chunks[i]
|
||||
val cPath = com.aryan.reader.paginatedreader.CfiUtils.getPath(c.sourceCfi)
|
||||
val bPath = com.aryan.reader.paginatedreader.CfiUtils.getPath(baseCfi)
|
||||
val cPath = CfiUtils.getPath(c.sourceCfi)
|
||||
val bPath = CfiUtils.getPath(baseCfi)
|
||||
if (cPath == bPath && startOffset >= c.startOffsetInSource && startOffset < c.startOffsetInSource + c.text.length) {
|
||||
foundIdx = i
|
||||
break
|
||||
|
|
@ -1309,9 +1314,10 @@ fun EpubReaderHost(
|
|||
characterLimit = charLimit,
|
||||
summaryCacheManager = summaryCacheManager,
|
||||
paginator = paginator,
|
||||
context = context,
|
||||
onProgressUpdate = { recapProgressMessage = it },
|
||||
onResultUpdate = { chunk ->
|
||||
isRecapLoading = false // Start showing content
|
||||
isRecapLoading = false
|
||||
val current = recapResult?.summary ?: ""
|
||||
recapResult = SummarizationResult(summary = current + chunk)
|
||||
},
|
||||
|
|
@ -1440,7 +1446,7 @@ fun EpubReaderHost(
|
|||
}
|
||||
}
|
||||
|
||||
val pageInfoBottomPadding by androidx.compose.animation.core.animateDpAsState(
|
||||
val pageInfoBottomPadding by animateDpAsState(
|
||||
targetValue = if (showBars && pageInfoMode == PageInfoMode.SYNC) 45.dp else 0.dp,
|
||||
label = "PageInfoBottomPadding"
|
||||
)
|
||||
|
|
@ -2002,8 +2008,9 @@ fun EpubReaderHost(
|
|||
if (systemUiMode == SystemUiMode.HIDDEN) {
|
||||
0.dp
|
||||
} else {
|
||||
val insets = androidx.core.view.ViewCompat.getRootWindowInsets(view)
|
||||
val ignoringVisibilityTopPx = insets?.getInsetsIgnoringVisibility(androidx.core.view.WindowInsetsCompat.Type.statusBars())?.top ?: 0
|
||||
val insets = ViewCompat.getRootWindowInsets(view)
|
||||
val ignoringVisibilityTopPx = insets?.getInsetsIgnoringVisibility(
|
||||
WindowInsetsCompat.Type.statusBars())?.top ?: 0
|
||||
val ignoringVisibilityTop = with(density) { ignoringVisibilityTopPx.toDp() }
|
||||
|
||||
if (ignoringVisibilityTop > 0.dp) {
|
||||
|
|
@ -2093,7 +2100,7 @@ fun EpubReaderHost(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("No chapters available for this book.")
|
||||
Text(stringResource(R.string.no_chapters_available))
|
||||
}
|
||||
} else {
|
||||
AnimatedContent(
|
||||
|
|
@ -3496,7 +3503,7 @@ fun EpubReaderHost(
|
|||
onDeleteReflow = onDeleteReflow
|
||||
)
|
||||
|
||||
val autoScrollPadding by androidx.compose.animation.core.animateDpAsState(
|
||||
val autoScrollPadding by animateDpAsState(
|
||||
targetValue = if (showBars) (bottomPadding + 45.dp + 16.dp) else 32.dp,
|
||||
label = "AutoScrollPadding"
|
||||
)
|
||||
|
|
@ -3854,7 +3861,7 @@ fun EpubReaderHost(
|
|||
CircularProgressIndicator()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "Navigating to position...",
|
||||
text = stringResource(R.string.navigating_to_position),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
|
|
@ -3865,8 +3872,8 @@ fun EpubReaderHost(
|
|||
if (showPermissionRationaleDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showPermissionRationaleDialog = false },
|
||||
title = { Text("Permission Required") },
|
||||
text = { Text("To show playback controls while the app is in the background, please grant the notification permission.") },
|
||||
title = { Text(stringResource(R.string.dialog_permission_required)) },
|
||||
text = { Text(stringResource(R.string.dialog_permission_notification_desc)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
|
|
@ -3874,7 +3881,7 @@ fun EpubReaderHost(
|
|||
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
) {
|
||||
Text("Continue")
|
||||
Text(stringResource(R.string.action_continue))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
|
|
@ -3884,7 +3891,7 @@ fun EpubReaderHost(
|
|||
startTts()
|
||||
}
|
||||
) {
|
||||
Text("Not now")
|
||||
Text(stringResource(R.string.action_not_now))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -3894,11 +3901,11 @@ fun EpubReaderHost(
|
|||
AlertDialog(
|
||||
onDismissRequest = { showJustifyWarningDialog = false },
|
||||
icon = { Icon(Icons.Default.Info, contentDescription = null) },
|
||||
title = { Text("Justified Text Limitation") },
|
||||
text = { Text("Using Justified alignment in Paginated Mode may cause text selection and highlights to be inaccurate due to layout limitations.") },
|
||||
title = { Text(stringResource(R.string.dialog_justified_text_limitation)) },
|
||||
text = { Text(stringResource(R.string.dialog_justified_text_limitation_desc)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showJustifyWarningDialog = false }) {
|
||||
Text("I Understand")
|
||||
Text(stringResource(R.string.action_i_understand))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -3919,7 +3926,7 @@ fun EpubReaderHost(
|
|||
CircularProgressIndicator()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
"Navigating to chapter...",
|
||||
stringResource(R.string.navigating_to_chapter),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
const val SETTINGS_PREFS_NAME = "epub_reader_settings"
|
||||
private const val TEXT_ALIGN_KEY = "reader_text_align"
|
||||
|
|
@ -389,13 +390,13 @@ fun ReaderTextFormatPanel(
|
|||
.padding(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (isLocalMode) "Local Format" else "Global Format",
|
||||
text = if (isLocalMode) stringResource(R.string.format_local) else stringResource(R.string.format_global),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowDropDown,
|
||||
contentDescription = "Select Mode",
|
||||
contentDescription = stringResource(R.string.content_desc_select_mode),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
|
|
@ -405,8 +406,8 @@ fun ReaderTextFormatPanel(
|
|||
DropdownMenuItem(
|
||||
text = {
|
||||
Column {
|
||||
Text("Global Format", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text("Applies to all files", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.format_global), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.auto_scroll_applies_all_files), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
},
|
||||
onClick = { onLocalModeToggle(false); showModeMenu = false },
|
||||
|
|
@ -416,8 +417,8 @@ fun ReaderTextFormatPanel(
|
|||
DropdownMenuItem(
|
||||
text = {
|
||||
Column {
|
||||
Text("Local Format", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text("Saved for this file only", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.format_local), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.auto_scroll_saved_for_file), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
},
|
||||
onClick = { onLocalModeToggle(true); showModeMenu = false },
|
||||
|
|
@ -428,10 +429,10 @@ fun ReaderTextFormatPanel(
|
|||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
TextButton(onClick = onReset, contentPadding = PaddingValues(horizontal = 8.dp)) {
|
||||
Text("Reset")
|
||||
Text(stringResource(R.string.action_reset))
|
||||
}
|
||||
IconButton(onClick = onClose, modifier = Modifier.size(32.dp)) {
|
||||
Icon(Icons.Default.Close, "Close", tint = MaterialTheme.colorScheme.error, modifier = Modifier.size(18.dp))
|
||||
Icon(Icons.Default.Close, stringResource(R.string.action_close), tint = MaterialTheme.colorScheme.error, modifier = Modifier.size(18.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -560,15 +561,15 @@ fun FontSelectionSheetContent(
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("Select Font", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.select_font), style = MaterialTheme.typography.titleMedium)
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(Icons.Default.Close, contentDescription = "Close")
|
||||
Icon(Icons.Default.Close, contentDescription = stringResource(R.string.action_close))
|
||||
}
|
||||
}
|
||||
|
||||
TabRow(selectedTabIndex = selectedTabIndex) {
|
||||
Tab(selected = selectedTabIndex == 0, onClick = { selectedTabIndex = 0 }, text = { Text("Presets") })
|
||||
Tab(selected = selectedTabIndex == 1, onClick = { selectedTabIndex = 1 }, text = { Text("Imported") })
|
||||
Tab(selected = selectedTabIndex == 0, onClick = { selectedTabIndex = 0 }, text = { Text(stringResource(R.string.tab_presets)) })
|
||||
Tab(selected = selectedTabIndex == 1, onClick = { selectedTabIndex = 1 }, text = { Text(stringResource(R.string.tab_imported)) })
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.heightIn(min = 200.dp, max = 400.dp)) {
|
||||
|
|
@ -582,7 +583,7 @@ fun FontSelectionSheetContent(
|
|||
Text(font.displayName, fontFamily = getComposeFontFamily(font, null))
|
||||
},
|
||||
trailingContent = {
|
||||
if (isSelected) Icon(Icons.Default.Check, contentDescription = "Selected", tint = MaterialTheme.colorScheme.primary)
|
||||
if (isSelected) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_selected), tint = MaterialTheme.colorScheme.primary)
|
||||
},
|
||||
modifier = Modifier.clickable { onFontSelected(font, null) },
|
||||
colors = if (isSelected) ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)) else ListItemDefaults.colors()
|
||||
|
|
@ -599,14 +600,14 @@ fun FontSelectionSheetContent(
|
|||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Import from Files")
|
||||
Text(stringResource(R.string.button_import_from_files))
|
||||
}
|
||||
}
|
||||
|
||||
if (customFonts.isEmpty()) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
"No imported fonts yet.",
|
||||
stringResource(R.string.no_imported_fonts_yet),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
|
@ -627,7 +628,7 @@ fun FontSelectionSheetContent(
|
|||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "Selected",
|
||||
contentDescription = stringResource(R.string.content_desc_selected),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
|
@ -674,16 +675,16 @@ fun VisualOptionsSheet(
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("Visual Options", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.menu_visual_options), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(Icons.Default.Close, contentDescription = "Close")
|
||||
Icon(Icons.Default.Close, contentDescription = stringResource(R.string.action_close))
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// System UI
|
||||
Text("System UI (Status & Navigation Bars)", style = MaterialTheme.typography.titleMedium)
|
||||
Text("Control the visibility of the device's system bars.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.visual_options_system_ui), style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.visual_options_system_ui_desc), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
OptionSegmentedControl(
|
||||
options = SystemUiMode.entries,
|
||||
|
|
@ -695,8 +696,8 @@ fun VisualOptionsSheet(
|
|||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Progress Bar
|
||||
Text("Progress Bar", style = MaterialTheme.typography.titleMedium)
|
||||
Text("The reading progress and chapter indicator at the bottom of the screen.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.visual_options_progress_bar), style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.visual_options_progress_bar_desc), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
OptionSegmentedControl(
|
||||
options = PageInfoMode.entries,
|
||||
|
|
@ -721,8 +722,8 @@ fun VisualOptionsSheet(
|
|||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text("Seamless Chapter Transition", style = MaterialTheme.typography.titleMedium)
|
||||
Text("Instantly load the next/previous chapter when scrolling past the end, without the pull-to-refresh animation.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.visual_options_seamless_chapter), style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.visual_options_seamless_chapter_desc), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Switch(checked = !pullToTurnEnabled, onCheckedChange = { onPullToTurnChange(!it) })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue