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:
Aryan 2026-04-05 09:32:59 +05:30 committed by GitHub
parent da1c5bce1e
commit 381193d774
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1388 additions and 855 deletions

View file

@ -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
)
}

View file

@ -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)) }
}
)
}

View file

@ -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),

View file

@ -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))
}
}
}

View file

@ -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))
}
}
)

View file

@ -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
)

View file

@ -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) })