String extraction (#174)

* Refactored the EPUB reader to use localized string resources instead of hardcoded strings across multiple components.

* Refactored the PDF viewer and annotation components to use centralized string resources instead of hardcoded text.

* Updated `strings.xml` with extensive translation hints and localization metadata.
This commit is contained in:
Aryan 2026-04-12 16:34:47 +05:30 committed by GitHub
parent 17a0097d4a
commit b027331bd1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 716 additions and 313 deletions

View file

@ -40,6 +40,7 @@ android {
}
}
buildConfigField("boolean", "IS_PRO", "false")
buildConfigField("boolean", "IS_OFFLINE", "false")
}
flavorDimensions += "version"
@ -91,6 +92,12 @@ android {
"proguard-rules.pro"
)
}
create("releaseOffline") {
initWith(getByName("release"))
matchingFallbacks += listOf("release")
buildConfigField("boolean", "IS_OFFLINE", "true")
}
}
applicationVariants.all {

View file

@ -390,7 +390,7 @@ fun SearchTopBar(
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.content_desc_close_search)
contentDescription = stringResource(R.string.tooltip_close_search)
)
}
@ -426,7 +426,7 @@ fun SearchTopBar(
) {
Icon(
Icons.Default.Close,
contentDescription = stringResource(R.string.content_desc_clear_search)
contentDescription = stringResource(R.string.tooltip_clear_search)
)
}
}
@ -448,8 +448,8 @@ fun SearchTopBar(
Icon(
imageVector = if (searchState.showSearchResultsPanel) Icons.Default.ArrowDropUp else Icons.Default.ArrowDropDown,
contentDescription = stringResource(
if (searchState.showSearchResultsPanel) R.string.content_desc_hide_results
else R.string.content_desc_show_results
if (searchState.showSearchResultsPanel) R.string.tooltip_hide_results
else R.string.tooltip_show_results
)
)
}
@ -478,7 +478,7 @@ fun SearchNavigationControls(
onClick = { onNavigate(searchState.currentSearchResultIndex - 1) },
enabled = searchState.currentSearchResultIndex > 0
) {
Icon(Icons.Default.ArrowDropUp, contentDescription = stringResource(R.string.content_desc_prev_result))
Icon(Icons.Default.ArrowDropUp, contentDescription = stringResource(R.string.tooltip_prev_result))
}
Text(
@ -493,7 +493,7 @@ fun SearchNavigationControls(
onClick = { onNavigate(searchState.currentSearchResultIndex + 1) },
enabled = searchState.currentSearchResultIndex < searchState.searchResultsCount - 1
) {
Icon(Icons.Default.ArrowDropDown, contentDescription = stringResource(R.string.content_desc_next_result))
Icon(Icons.Default.ArrowDropDown, contentDescription = stringResource(R.string.tooltip_next_result))
}
}
}

View file

@ -18,6 +18,8 @@
* mail: epistemereader@gmail.com
*/
// LibraryScreen.kt
@file:Suppress("KotlinConstantConditions")
package com.aryan.reader
import android.net.Uri
@ -158,9 +160,19 @@ fun LibraryScreen(
val sortOrder = uiState.sortOrder
val shelves = uiState.shelves
val rawLibraryFiles = uiState.rawLibraryFiles
val tabTitles = remember {
buildList {
add(context.getString(R.string.tab_all_books))
add(context.getString(R.string.tab_shelves))
add(context.getString(R.string.tab_folders))
if (!BuildConfig.IS_OFFLINE) {
add(context.getString(R.string.tab_catalogs))
}
}
}
val pagerState = rememberPagerState(
initialPage = uiState.libraryScreenStartPage,
pageCount = { 4 }
pageCount = { tabTitles.size }
)
val containsFolderItems = remember(selectedItems) {
@ -254,6 +266,7 @@ fun LibraryScreen(
Box(modifier = Modifier.fillMaxSize()) {
LibraryScreenContent(
tabTitles = tabTitles,
recentFiles = uiState.allRecentFiles,
rawLibraryFiles = rawLibraryFiles,
shelves = shelves,
@ -494,6 +507,7 @@ fun ShelfScreen(
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LibraryScreenContent(
tabTitles: List<String>,
recentFiles: List<RecentFileItem>,
rawLibraryFiles: List<RecentFileItem>,
shelves: List<Shelf>,
@ -543,12 +557,6 @@ fun LibraryScreenContent(
val isBookContextualModeActive = selectedItems.isNotEmpty()
val isShelfContextualModeActive = selectedShelves.isNotEmpty()
var showSortMenu by remember { mutableStateOf(false) }
val tabTitles = listOf(
stringResource(R.string.tab_all_books),
stringResource(R.string.tab_shelves),
stringResource(R.string.tab_folders),
stringResource(R.string.tab_catalogs)
)
val searchFocusRequester = remember { FocusRequester() }
var textFieldValue by remember(isSearchActive) {
@ -811,13 +819,15 @@ fun LibraryScreenContent(
)
}
3 -> {
OpdsTab(
localLibraryFiles = rawLibraryFiles,
onBookDownloaded = onOpdsBookDownloaded,
onReadBook = onItemClick,
onStreamBook = onStreamOpdsBook,
onDeleteCatalogStreams = onDeleteCatalogStreams
)
if (!BuildConfig.IS_OFFLINE) {
OpdsTab(
localLibraryFiles = rawLibraryFiles,
onBookDownloaded = onOpdsBookDownloaded,
onReadBook = onItemClick,
onStreamBook = onStreamOpdsBook,
onDeleteCatalogStreams = onDeleteCatalogStreams
)
}
}
}
}

View file

@ -664,7 +664,7 @@ fun AboutDialog(onDismiss: () -> Unit) {
tint = MaterialTheme.colorScheme.primary
)
},
text = stringResource(R.string.about_privacy),
text = stringResource(R.string.legal_privacy_policy),
subtitle = stringResource(R.string.about_privacy_desc),
onClick = { uriHandler.openUri(PRIVACY_POLICY_URL) }
)
@ -680,7 +680,7 @@ fun AboutDialog(onDismiss: () -> Unit) {
tint = MaterialTheme.colorScheme.primary
)
},
text = stringResource(R.string.about_terms),
text = stringResource(R.string.legal_terms_of_service),
subtitle = stringResource(R.string.about_terms_desc),
onClick = { uriHandler.openUri(TERMS_URL) }
)

View file

@ -72,6 +72,7 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
@ -81,6 +82,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import androidx.core.net.toUri
import com.aryan.reader.R
import com.aryan.reader.ReaderTexture
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -415,8 +417,8 @@ fun ChapterWebView(
val urlToShow = showExternalLinkDialog!!
AlertDialog(
onDismissRequest = { showExternalLinkDialog = null },
title = { Text("External Link") },
text = { Text("You clicked on an external link:\n\n$urlToShow\n\nWhat would you like to do?") },
title = { Text(stringResource(R.string.dialog_external_link_title)) },
text = { Text(stringResource(R.string.dialog_external_link_desc, urlToShow)) },
confirmButton = {
Row(horizontalArrangement = Arrangement.End) {
TextButton(onClick = {
@ -425,23 +427,21 @@ fun ChapterWebView(
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Timber.e(e, "No activity found to handle intent for URL: $urlToShow")
Toast.makeText(
context, "No browser found to open the link.", Toast.LENGTH_LONG
).show()
Toast.makeText(context, context.getString(R.string.error_no_browser), Toast.LENGTH_LONG).show()
}
showExternalLinkDialog = null
}) { Text("Open") }
}) { Text(stringResource(R.string.action_open)) }
TextButton(onClick = {
val clipboard =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Copied Link", urlToShow)
clipboard.setPrimaryClip(clip)
showExternalLinkDialog = null
}) { Text("Copy") }
}) { Text(stringResource(R.string.action_copy)) }
}
},
dismissButton = {
TextButton(onClick = { showExternalLinkDialog = null }) { Text("Cancel") }
TextButton(onClick = { showExternalLinkDialog = null }) { Text(stringResource(R.string.action_cancel)) }
})
}

View file

@ -68,7 +68,6 @@ import com.aryan.reader.epub.EpubChapter
import org.json.JSONArray
import org.json.JSONObject
import java.util.UUID
import kotlin.math.min
private const val BOOKMARK_PREFS_NAME = "epub_reader_bookmarks"
@ -445,10 +444,10 @@ fun PaletteManagerDialog(
}
},
confirmButton = {
TextButton(onClick = { onSave(tempPalette) }) { Text("Save") }
TextButton(onClick = { onSave(tempPalette) }) { Text(stringResource(R.string.action_save)) }
},
dismissButton = {
TextButton(onClick = onDismiss) { Text("Cancel") }
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
}
)
}
@ -528,10 +527,10 @@ fun AnnotationBottomSheet(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
BottomSheetToolButton(icon = R.drawable.copy, label = "Copy", onClick = onCopy, effectiveText = effectiveText)
BottomSheetToolButton(icon = R.drawable.dictionary, label = "Dict", onClick = onDictionary, effectiveText = effectiveText)
BottomSheetToolButton(icon = R.drawable.translate, label = "Translate", onClick = onTranslate, effectiveText = effectiveText)
BottomSheetToolButton(icon = R.drawable.search, label = "Search", onClick = onSearch, effectiveText = effectiveText)
BottomSheetToolButton(icon = R.drawable.copy, label = stringResource(R.string.action_copy), onClick = onCopy, effectiveText = effectiveText)
BottomSheetToolButton(icon = R.drawable.dictionary, label = stringResource(R.string.label_dict), onClick = onDictionary, effectiveText = effectiveText)
BottomSheetToolButton(icon = R.drawable.translate, label = stringResource(R.string.dict_translate), onClick = onTranslate, effectiveText = effectiveText)
BottomSheetToolButton(icon = R.drawable.search, label = stringResource(R.string.action_search), onClick = onSearch, effectiveText = effectiveText)
}
Spacer(Modifier.height(16.dp))
@ -540,7 +539,7 @@ fun AnnotationBottomSheet(
OutlinedTextField(
value = noteText,
onValueChange = { noteText = it },
placeholder = { Text("Add a note...", color = effectiveText.copy(alpha = 0.5f)) },
placeholder = { Text(stringResource(R.string.placeholder_add_note), color = effectiveText.copy(alpha = 0.5f)) },
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 100.dp),
@ -652,23 +651,23 @@ fun PaginatedTextSelectionMenu(
}
val actions = mutableListOf<MenuActionItem>()
actions.add(MenuActionItem(iconRes = R.drawable.copy, label = "Copy", onClick = onCopy))
actions.add(MenuActionItem(iconRes = R.drawable.copy, label = stringResource(R.string.action_copy), onClick = onCopy))
if (onTts != null) {
actions.add(MenuActionItem(imageVector = Icons.AutoMirrored.Filled.VolumeUp, label = "Speak", onClick = onTts))
actions.add(MenuActionItem(imageVector = Icons.AutoMirrored.Filled.VolumeUp, label = stringResource(R.string.label_speak), onClick = onTts))
}
actions.add(MenuActionItem(iconRes = R.drawable.dictionary, label = "Dict", onClick = onDictionary))
actions.add(MenuActionItem(iconRes = R.drawable.translate, label = "Translate", onClick = onTranslate))
actions.add(MenuActionItem(iconRes = R.drawable.search, label = "Search", onClick = onSearch))
actions.add(MenuActionItem(iconRes = R.drawable.dictionary, label = stringResource(R.string.label_dict), onClick = onDictionary))
actions.add(MenuActionItem(iconRes = R.drawable.translate, label = stringResource(R.string.dict_translate), onClick = onTranslate))
actions.add(MenuActionItem(iconRes = R.drawable.search, label = stringResource(R.string.action_search), onClick = onSearch))
if (onNote != null) {
actions.add(MenuActionItem(imageVector = Icons.Default.Edit, label = "Note", onClick = onNote))
actions.add(MenuActionItem(imageVector = Icons.Default.Edit, label = stringResource(R.string.label_note), onClick = onNote))
}
if (onSelectAll != null) {
actions.add(MenuActionItem(iconRes = R.drawable.select_all, label = "Select All", onClick = onSelectAll))
actions.add(MenuActionItem(iconRes = R.drawable.select_all, label = stringResource(R.string.select_all), onClick = onSelectAll))
}
if (onDelete != null) {
actions.add(MenuActionItem(imageVector = Icons.Default.Delete, label = "Remove", onClick = onDelete, isError = true))
actions.add(MenuActionItem(imageVector = Icons.Default.Delete, label = stringResource(R.string.action_remove), onClick = onDelete, isError = true))
}
Column(modifier = Modifier.padding(bottom = 4.dp)) {
@ -753,7 +752,7 @@ fun HighlightColorRow(
if (selectedColor == colorEnum) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Selected",
contentDescription = stringResource(R.string.content_desc_selected),
tint = if (colorEnum == HighlightColor.WHITE || colorEnum == HighlightColor.YELLOW) Color.Black else Color.White,
modifier = Modifier.size(18.dp)
)
@ -825,13 +824,13 @@ fun PaginatedTextSelectionMenu(
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "Note",
contentDescription = stringResource(R.string.label_note),
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(14.dp)
)
Spacer(Modifier.width(6.dp))
Text(
"Note",
stringResource(R.string.label_note),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
@ -851,24 +850,24 @@ fun PaginatedTextSelectionMenu(
}
val actions = mutableListOf<MenuActionItem>()
actions.add(MenuActionItem(iconRes = R.drawable.copy, label = "Copy", onClick = onCopy))
actions.add(MenuActionItem(iconRes = R.drawable.copy, label = stringResource(R.string.action_copy), onClick = onCopy))
if (onTts != null) {
actions.add(MenuActionItem(imageVector = Icons.AutoMirrored.Filled.VolumeUp, label = "Speak", onClick = onTts))
actions.add(MenuActionItem(imageVector = Icons.AutoMirrored.Filled.VolumeUp, label = stringResource(R.string.label_speak), onClick = onTts))
}
actions.add(MenuActionItem(iconRes = R.drawable.dictionary, label = "Dict", onClick = onDictionary))
actions.add(MenuActionItem(iconRes = R.drawable.translate, label = "Translate", onClick = onTranslate))
actions.add(MenuActionItem(iconRes = R.drawable.search, label = "Search", onClick = onSearch))
actions.add(MenuActionItem(iconRes = R.drawable.dictionary, label = stringResource(R.string.label_dict), onClick = onDictionary))
actions.add(MenuActionItem(iconRes = R.drawable.translate, label = stringResource(R.string.dict_translate), onClick = onTranslate))
actions.add(MenuActionItem(iconRes = R.drawable.search, label = stringResource(R.string.action_search), onClick = onSearch))
if (onNote != null) {
val noteLabel = if (existingNote.isNullOrBlank()) "Note" else "Edit"
val noteLabel = if (existingNote.isNullOrBlank()) stringResource(R.string.label_note) else stringResource(R.string.label_edit)
actions.add(MenuActionItem(imageVector = Icons.Default.Edit, label = noteLabel, onClick = onNote))
}
if (onSelectAll != null) {
actions.add(MenuActionItem(iconRes = R.drawable.select_all, label = "Select All", onClick = onSelectAll))
actions.add(MenuActionItem(iconRes = R.drawable.select_all, label = stringResource(R.string.select_all), onClick = onSelectAll))
}
if (onDelete != null) {
actions.add(MenuActionItem(imageVector = Icons.Default.Delete, label = "Remove", onClick = onDelete, isError = true))
actions.add(MenuActionItem(imageVector = Icons.Default.Delete, label = stringResource(R.string.action_remove), onClick = onDelete, isError = true))
}
Column(modifier = Modifier.padding(bottom = 4.dp)) {

View file

@ -19,6 +19,8 @@
*/
package com.aryan.reader.epubreader
import android.content.Context
import com.aryan.reader.R
import timber.log.Timber
import com.aryan.reader.epub.EpubBook
import com.aryan.reader.paginatedreader.LocatorConverter
@ -40,6 +42,7 @@ data class ChapterLoadingResult(
* the initial chunk to display based on navigation state (CFI, overrides, etc.).
*/
suspend fun loadChapterContent(
context: Context,
epubBook: EpubBook,
chapterIndex: Int,
chunkTargetOverride: Int?,
@ -64,12 +67,12 @@ suspend fun loadChapterContent(
chunkOfElements.joinToString(separator = "\n") { it.outerHtml() }
}
if (chunkedList.isEmpty()) {
head to listOf("<body><p>This chapter is empty.</p></body>")
head to listOf("<body><p>${context.getString(R.string.chapter_empty)}</p></body>")
} else {
head to chunkedList
}
} else {
"" to listOf("<h1>Chapter not found</h1>")
"" to listOf("<h1>${context.getString(R.string.chapter_not_found)}</h1>")
}
var targetChunk = 0
@ -104,7 +107,7 @@ suspend fun loadChapterContent(
Timber.e(e, "Failed to parse chapter")
ChapterLoadingResult(
head = "",
chunks = listOf("<h1>Error loading chapter</h1><p>${e.message}</p>"),
chunks = listOf("<h1>${context.getString(R.string.error_loading_chapter)}</h1><p>${e.message}</p>"),
startChunkIndex = 0,
isSuccess = false,
errorMessage = e.message

View file

@ -277,7 +277,7 @@ fun EpubReaderTopBar(
onDismissRequest = { showMoreMenu = false }
) {
DropdownMenuItem(
text = { Text("Customize Toolbar") },
text = { Text(stringResource(R.string.title_customize_toolbar)) },
onClick = {
showMoreMenu = false
onCustomizeTools()
@ -1078,7 +1078,7 @@ fun AutoScrollControls(
) {
Icon(
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (isPlaying) stringResource(R.string.content_desc_pause_playback) else stringResource(R.string.content_desc_start_playback),
contentDescription = if (isPlaying) stringResource(R.string.tooltip_tts_pause) else stringResource(R.string.content_desc_start_playback),
modifier = Modifier.size(20.dp)
)
}
@ -1395,7 +1395,7 @@ fun CustomizeToolsSheet(
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Select the tools you want to keep visible. Unchecking a tool hides it from the UI to give you a distraction-free reading space.",
text = stringResource(R.string.desc_customize_toolbar),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@ -1495,17 +1495,19 @@ fun TtsControlsSheet(
ttsController.sliceAndRetainPosition()
}
val ttsSample = stringResource(R.string.tts_sample_text)
ModalBottomSheet(
onDismissRequest = onDismiss,
contentWindowInsets = { WindowInsets.navigationBars }
) {
Column(modifier = Modifier.padding(horizontal = 24.dp).padding(bottom = 24.dp)) {
Text("Voice Adjustments", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.tts_voice_adjustments), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(16.dp))
// Rate Slider
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Speed (${"%.1f".format(rate)}x)", modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
Text(stringResource(R.string.tts_speed_label, "%.1f".format(rate)), modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
IconButton(onClick = {
rate = 1.0f
ttsController.pause()
@ -1534,7 +1536,7 @@ fun TtsControlsSheet(
// Pitch Slider
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Pitch (${"%.1f".format(pitch)}x)", modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
Text(stringResource(R.string.tts_pitch_label, "%.1f".format(pitch)), modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
IconButton(onClick = {
pitch = 1.0f
ttsController.pause()
@ -1568,7 +1570,7 @@ fun TtsControlsSheet(
if (ttsState.isPlaying) ttsController.pause()
tts?.setSpeechRate(rate)
tts?.setPitch(pitch)
tts?.speak("This is how your current voice settings sound.", TextToSpeech.QUEUE_FLUSH, null, null)
tts?.speak(ttsSample, TextToSpeech.QUEUE_FLUSH, null, null)
},
modifier = Modifier.fillMaxWidth(),
enabled = isTtsReady,
@ -1611,14 +1613,14 @@ fun TtsControlsSheet(
} else {
Icon(
painter = painterResource(if (ttsState.isPlaying) R.drawable.pause else R.drawable.play),
contentDescription = if (ttsState.isPlaying) "Pause Book" else "Play Book",
contentDescription = if (ttsState.isPlaying) stringResource(R.string.tts_pause_book) else stringResource(R.string.tts_resume_book),
modifier = Modifier.size(32.dp)
)
}
}
Spacer(Modifier.height(8.dp))
Text(
text = if (ttsState.isPlaying) "Pause Book" else "Resume Book",
text = if (ttsState.isPlaying) stringResource(R.string.tts_pause_book) else stringResource(R.string.tts_resume_book),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@ -1635,7 +1637,7 @@ fun TtsControlsSheet(
) {
Icon(Icons.Default.Settings, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("System Voice / Engine Settings")
Text(stringResource(R.string.tts_system_settings))
}
}
}

View file

@ -551,7 +551,7 @@ private fun BookmarksList(
onDismissRequest = { bookmarkMenuExpandedFor = null }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_rename)) },
text = { Text(stringResource(R.string.action_rename)) },
onClick = {
showRenameBookmarkDialog = bookmark
bookmarkMenuExpandedFor = null
@ -680,12 +680,12 @@ private fun HighlightsList(
androidx.compose.material3.FilterChip(
selected = !filterWithNotesOnly,
onClick = { filterWithNotesOnly = false },
label = { Text("All") }
label = { Text(stringResource(R.string.filter_all)) }
)
androidx.compose.material3.FilterChip(
selected = filterWithNotesOnly,
onClick = { filterWithNotesOnly = true },
label = { Text("With Notes") }
label = { Text(stringResource(R.string.filter_with_notes)) }
)
}
@ -773,7 +773,7 @@ private fun HighlightsList(
)
HorizontalDivider()
DropdownMenuItem(
text = { Text(if (highlight.note.isNullOrBlank()) "Add Note" else "Edit Note") },
text = { Text(if (highlight.note.isNullOrBlank()) stringResource(R.string.menu_add_note) else stringResource(R.string.menu_edit_note)) },
onClick = {
onEditNote(highlight)
highlightMenuExpandedFor = null

View file

@ -702,7 +702,7 @@ fun EpubReaderHost(
if (!selectedDictPackage.isNullOrEmpty()) {
ExternalDictionaryHelper.launchDictionary(context, selectedDictPackage!!, word)
} else {
Toast.makeText(context, "Please select a dictionary app first.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.toast_select_dictionary_first), Toast.LENGTH_SHORT).show()
showDictionarySettingsSheet = true
}
}
@ -712,7 +712,7 @@ fun EpubReaderHost(
if (!selectedTranslatePackage.isNullOrEmpty()) {
ExternalDictionaryHelper.launchTranslate(context, selectedTranslatePackage!!, text)
} else {
Toast.makeText(context, "Please select a translate app first.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.toast_select_translate_first), Toast.LENGTH_SHORT).show()
showDictionarySettingsSheet = true
}
}
@ -721,7 +721,7 @@ fun EpubReaderHost(
if (!selectedSearchPackage.isNullOrEmpty()) {
ExternalDictionaryHelper.launchSearch(context, selectedSearchPackage!!, text)
} else {
Toast.makeText(context, "Please select a search app first.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.toast_select_search_first), Toast.LENGTH_SHORT).show()
showDictionarySettingsSheet = true
}
}
@ -1371,6 +1371,7 @@ fun EpubReaderHost(
activeFragmentId = null
val result = loadChapterContent(
context = context,
epubBook = epubBook,
chapterIndex = currentChapterIndex,
chunkTargetOverride = chunkTargetOverride,
@ -2835,7 +2836,7 @@ fun EpubReaderHost(
if (pullToTurnEnabled && currentChapterIndex > 0) {
ChapterChangeIndicator(
text = "Release for Previous Chapter",
text = stringResource(R.string.release_for_previous_chapter),
progress = pullToPrevProgress,
isPullingDown = true,
modifier = Modifier
@ -2846,7 +2847,7 @@ fun EpubReaderHost(
if (pullToTurnEnabled && currentChapterIndex < chapters.size - 1) {
ChapterChangeIndicator(
text = "Release for Next Chapter",
text = stringResource(R.string.release_for_next_chapter),
progress = pullToNextProgress,
isPullingDown = false,
modifier = Modifier

View file

@ -481,7 +481,7 @@ fun ReaderTextFormatPanel(
// FONT & ALIGNMENT SECTION
Text(
text = "FONT & ALIGNMENT",
text = stringResource(R.string.section_font_alignment),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold,
@ -504,7 +504,7 @@ fun ReaderTextFormatPanel(
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Aa",
text = stringResource(R.string.label_aa_preview),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSecondaryContainer,
@ -570,7 +570,7 @@ fun ReaderTextFormatPanel(
// LAYOUT & SPACING SECTION
Text(
text = "LAYOUT & SPACING",
text = stringResource(R.string.section_layout_spacing),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold,
@ -581,7 +581,7 @@ fun ReaderTextFormatPanel(
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
// Size
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Text("Font Size", style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp))
Text(stringResource(R.string.label_font_size), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp))
Slider(
value = currentFontSize,
onValueChange = onFontSizeChange,
@ -589,11 +589,11 @@ fun ReaderTextFormatPanel(
steps = 24,
modifier = Modifier.weight(1f)
)
Text(if (currentFontSize in 0.99f..1.01f) "Orig" else "%.1fx".format(currentFontSize), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp), textAlign = TextAlign.End)
Text(if (currentFontSize in 0.99f..1.01f) stringResource(R.string.label_original) else "%.1fx".format(currentFontSize), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp), textAlign = TextAlign.End)
}
// Lines
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Text("Line Height", style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp))
Text(stringResource(R.string.label_line_height), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp))
Slider(
value = currentLineHeight,
onValueChange = onLineHeightChange,
@ -601,11 +601,11 @@ fun ReaderTextFormatPanel(
steps = 19,
modifier = Modifier.weight(1f)
)
Text(if (currentLineHeight <= 1.01f) "Orig" else "%.1fx".format(currentLineHeight), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp), textAlign = TextAlign.End)
Text(if (currentLineHeight <= 1.01f) stringResource(R.string.label_original) else "%.1fx".format(currentLineHeight), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp), textAlign = TextAlign.End)
}
// Paragraph Gap
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Text("Paragraph Gap", style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp))
Text(stringResource(R.string.label_paragraph_gap), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp))
Slider(
value = currentParagraphGap,
onValueChange = onParagraphGapChange,
@ -613,7 +613,7 @@ fun ReaderTextFormatPanel(
steps = 29,
modifier = Modifier.weight(1f)
)
Text(if (currentParagraphGap in 0.99f..1.01f) "Orig" else "%.1fx".format(currentParagraphGap), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp), textAlign = TextAlign.End)
Text(if (currentParagraphGap in 0.99f..1.01f) stringResource(R.string.label_original) else "%.1fx".format(currentParagraphGap), style = MaterialTheme.typography.labelMedium, modifier = Modifier.width(40.dp), textAlign = TextAlign.End)
}
}
Spacer(Modifier.height(8.dp))

View file

@ -11,6 +11,7 @@ import android.os.Build
import android.widget.Toast
import timber.log.Timber
import androidx.core.net.toUri
import com.aryan.reader.R
data class ExternalDictionaryApp(
val label: String,
@ -72,7 +73,7 @@ object ExternalDictionaryHelper {
sortedApps.add(
0,
ExternalDictionaryApp(
label = "Search",
label = context.getString(R.string.dict_app_label_search),
packageName = GOOGLE_SEARCH_PKG,
icon = null
)
@ -127,7 +128,7 @@ object ExternalDictionaryHelper {
} catch (e: Exception) {
Timber.e(e, "Failed to launch dictionary app: $packageName")
Toast.makeText(context, "Error opening dictionary", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.error_opening_dictionary), Toast.LENGTH_SHORT).show()
}
}
@ -172,7 +173,7 @@ object ExternalDictionaryHelper {
launchGenericSend(context, packageName, query)
} catch (e: Exception) {
Timber.e(e, "Failed to launch translate app: $packageName")
Toast.makeText(context, "Error opening translate app", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.error_opening_translate), Toast.LENGTH_SHORT).show()
}
}
@ -210,7 +211,7 @@ object ExternalDictionaryHelper {
launchGenericSend(context, packageName, query)
} catch (e: Exception) {
Timber.e(e, "Failed to launch search app: $packageName")
Toast.makeText(context, "Error opening search app", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.error_opening_search), Toast.LENGTH_SHORT).show()
}
}

View file

@ -51,6 +51,7 @@ import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
@ -112,7 +113,7 @@ fun AnnotationDock(
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close Edit Mode",
contentDescription = stringResource(R.string.content_desc_close_edit_mode),
tint = Color.White,
modifier = Modifier.size(iconSize)
)
@ -130,7 +131,7 @@ fun AnnotationDock(
) {
Icon(
imageVector = visIcon,
contentDescription = "Toggle Visibility",
contentDescription = stringResource(R.string.content_desc_toggle_visibility),
tint = visTint,
modifier = Modifier.size(iconSize)
)
@ -166,7 +167,7 @@ fun AnnotationDock(
) {
Icon(
imageVector = iconVector,
contentDescription = "Stylus Only Mode",
contentDescription = stringResource(R.string.content_desc_stylus_only_mode),
tint = iconTint,
modifier = Modifier.size(iconSize)
)
@ -182,7 +183,7 @@ fun AnnotationDock(
iconRes = R.drawable.pen,
isActive = isPenActive,
tintColor = if(isMinimized) Color.Gray else activePenColor,
description = "Pen",
description = stringResource(R.string.content_desc_pen),
size = buttonSize,
iconSize = iconSize,
onClick = {
@ -202,7 +203,7 @@ fun AnnotationDock(
iconRes = R.drawable.marker,
isActive = isHighlighterActive,
tintColor = if(isMinimized) Color.Gray else activeHighlighterColor.copy(alpha = 1f),
description = "Highlighter",
description = stringResource(R.string.content_desc_highlighter),
size = buttonSize,
iconSize = iconSize,
onClick = {
@ -221,7 +222,7 @@ fun AnnotationDock(
iconRes = R.drawable.keyboard,
isActive = !isMinimized && selectedTool == InkType.TEXT,
tintColor = if(isMinimized) Color.Gray else Color.White,
description = "Text",
description = stringResource(R.string.content_desc_text),
size = buttonSize,
iconSize = iconSize,
onClick = { if(!isMinimized) onToolClick(InkType.TEXT) }
@ -232,7 +233,7 @@ fun AnnotationDock(
iconRes = R.drawable.eraser,
isActive = !isMinimized && selectedTool == InkType.ERASER,
tintColor = if(isMinimized) Color.Gray else Color.White,
description = "Eraser",
description = stringResource(R.string.content_desc_eraser),
size = buttonSize,
iconSize = iconSize,
onClick = { if(!isMinimized) onToolClick(InkType.ERASER) }
@ -249,7 +250,7 @@ fun AnnotationDock(
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.Undo,
contentDescription = "Undo",
contentDescription = stringResource(R.string.content_desc_undo),
tint = if (canUndo && !isMinimized) Color.White else Color.White.copy(alpha = 0.3f),
modifier = Modifier.size(iconSize)
)
@ -265,7 +266,7 @@ fun AnnotationDock(
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.Redo,
contentDescription = "Redo",
contentDescription = stringResource(R.string.content_desc_redo),
tint = if (canRedo && !isMinimized) Color.White else Color.White.copy(alpha = 0.3f),
modifier = Modifier.size(iconSize)
)
@ -286,7 +287,7 @@ fun AnnotationDock(
) {
Icon(
imageVector = Icons.Default.VisibilityOff,
contentDescription = "Show Dock",
contentDescription = stringResource(R.string.content_desc_show_dock),
tint = Color.White,
modifier = Modifier.size(20.dp)
)

View file

@ -93,6 +93,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@ -1589,6 +1590,10 @@ internal fun PdfPageComposable(
}
}
val errorSelection = stringResource(R.string.error_selection)
val errorOcrSelection = stringResource(R.string.error_ocr_selection)
val errorProcessingPage = stringResource(R.string.error_processing_page)
BoxWithConstraints(
modifier = modifier
.onGloballyPositioned { layoutCoordinates = it }
@ -2359,8 +2364,7 @@ internal fun PdfPageComposable(
Timber.e(
e, "Long press: Error during OCR text selection"
)
pageErrorMessage =
"OCR selection error: ${e.localizedMessage}"
pageErrorMessage = errorOcrSelection
} finally {
isPerformingOcrForSelection = false
ocrRipplePosition = null
@ -2381,7 +2385,7 @@ internal fun PdfPageComposable(
e,
"Error during long press text selection on page $pageIndex"
)
pageErrorMessage = "Selection error: ${e.localizedMessage}"
pageErrorMessage = errorSelection
customMenuState = null
selectionCharRange.value = null
selectedWordScreenRects = emptyList()
@ -3467,7 +3471,7 @@ internal fun PdfPageComposable(
}
} catch (e: Exception) {
if (e is CancellationException) throw e
pageErrorMessage = "Error processing page: ${e.localizedMessage}"
pageErrorMessage = errorProcessingPage
} finally {
isLoadingPage = false
localBitmap?.recycle()
@ -3869,7 +3873,7 @@ internal fun PdfPageComposable(
else -> {
Text(
text = "Unable to display page ${pageIndex + 1}.",
text = stringResource(R.string.error_unable_to_display_page),
modifier = Modifier
.padding(16.dp)
.align(Alignment.Center)

View file

@ -1412,7 +1412,7 @@ fun PdfViewerScreen(
} catch (e: Exception) {
Timber.tag("PdfPrint").e(e, "Failed to initialize print job")
coroutineScope.launch {
snackbarHostState.showSnackbar("Could not open print settings")
snackbarHostState.showSnackbar(context.getString(R.string.error_open_print_settings))
}
}
}
@ -5593,10 +5593,10 @@ fun PdfViewerScreen(
verticalReaderState.currentPage
}
val titleText = when {
isLoadingDocument -> "Loading PDF..."
errorMessage != null -> "Error loading PDF"
isLoadingDocument -> stringResource(R.string.loading_pdf)
errorMessage != null -> stringResource(R.string.error_loading_pdf)
totalPages > 0 && pagerState.pageCount > 0 -> "Page ${currentPageForDisplay + 1} of $totalPages"
totalPages > 0 && pagerState.pageCount == 0 -> "Loading page..."
totalPages > 0 && pagerState.pageCount == 0 -> stringResource(R.string.loading_page)
else -> "PDF Viewer"
}
Text(
@ -5610,12 +5610,12 @@ fun PdfViewerScreen(
if (!hiddenTools.contains(PdfReaderTool.THEME.name)) {
TooltipIconButton(
text = "Theme",
description = "Theme Settings",
text = stringResource(R.string.tooltip_theme),
description = stringResource(R.string.tooltip_theme_desc),
onClick = { showThemePanel = true }) {
Icon(
painter = painterResource(id = R.drawable.palette),
contentDescription = "Theme Settings",
contentDescription = stringResource(R.string.tooltip_theme_desc),
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
@ -5637,7 +5637,7 @@ fun PdfViewerScreen(
}) {
Icon(
imageVector = if (isScrollLocked) Icons.Default.Lock else Icons.Default.LockOpen,
contentDescription = if (isScrollLocked) "Unlock Panning" else "Lock Panning",
contentDescription = if (isScrollLocked) stringResource(R.string.tooltip_unlock_pan) else stringResource(R.string.tooltip_lock_pan),
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
@ -5674,7 +5674,7 @@ fun PdfViewerScreen(
if (BuildConfig.DEBUG) {
TooltipIconButton(
text = "Pen Playground",
text = stringResource(R.string.pen_playground),
onClick = { showPenPlayground = true }) {
Icon(
imageVector = Icons.Default.Star,
@ -5683,7 +5683,7 @@ fun PdfViewerScreen(
)
}
TooltipIconButton(text = "Import SVG", onClick = {
TooltipIconButton(text = stringResource(R.string.import_svg), onClick = {
val page =
if (displayMode == DisplayMode.PAGINATION) pagerState.currentPage else verticalReaderState.currentPage
@ -5712,16 +5712,16 @@ fun PdfViewerScreen(
}
redoStack.clear()
snackbarHostState.showSnackbar("Imported ${svgAnnotations.size} SVG strokes!")
snackbarHostState.showSnackbar(context.getString(R.string.msg_imported_svg_strokes))
} else {
snackbarHostState.showSnackbar("Failed to import SVG or empty.")
snackbarHostState.showSnackbar(context.getString(R.string.error_import_svg_failed))
}
}
}
}) {
Icon(
imageVector = Icons.Default.Brush,
contentDescription = "Import SVG",
contentDescription = stringResource(R.string.import_svg),
tint = Color(0xFFE91E63)
)
}
@ -5735,7 +5735,7 @@ fun PdfViewerScreen(
onClick = { showMoreMenu = true }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "More Options"
contentDescription = stringResource(R.string.tooltip_more_options)
)
}
@ -5744,19 +5744,19 @@ fun PdfViewerScreen(
expanded = showMoreMenu,
onDismissRequest = { showMoreMenu = false }) {
DropdownMenuItem(
text = { Text("Customize Toolbar") },
text = { Text(stringResource(R.string.title_customize_toolbar)) },
onClick = {
showMoreMenu = false
showCustomizeToolsSheet = true
},
leadingIcon = {
Icon(Icons.Default.Settings, contentDescription = null, modifier = Modifier.size(20.dp))
Icon(Icons.Default.Settings, contentDescription = stringResource(R.string.title_customize_toolbar), modifier = Modifier.size(20.dp))
}
)
HorizontalDivider()
if (BuildConfig.IS_PRO && !hiddenTools.contains(PdfReaderTool.OCR_LANGUAGE.name)) {
DropdownMenuItem(
text = { Text("OCR Language") },
text = { Text(stringResource(R.string.menu_ocr_language)) },
onClick = {
showMoreMenu = false
hasSelectedOcrLanguage = true
@ -5767,7 +5767,7 @@ fun PdfViewerScreen(
if (!hiddenTools.contains(PdfReaderTool.READING_MODE.name)) {
DropdownMenuItem(
text = { Text("Reading Mode: Vertical scroll") },
text = { Text(stringResource(R.string.menu_reading_mode_vertical)) },
enabled = !isTtsSessionActive,
onClick = {
displayMode = DisplayMode.VERTICAL_SCROLL
@ -5777,13 +5777,13 @@ fun PdfViewerScreen(
if (displayMode == DisplayMode.VERTICAL_SCROLL) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Selected"
contentDescription = stringResource(R.string.content_desc_selected)
)
}
})
HorizontalDivider()
DropdownMenuItem(
text = { Text("Reading Mode: Paginated") },
text = { Text(stringResource(R.string.menu_reading_mode_paginated)) },
enabled = !isTtsSessionActive,
onClick = {
displayMode = DisplayMode.PAGINATION
@ -5793,7 +5793,7 @@ fun PdfViewerScreen(
if (displayMode == DisplayMode.PAGINATION) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Selected"
contentDescription = stringResource(R.string.content_desc_selected)
)
}
})
@ -5801,7 +5801,7 @@ fun PdfViewerScreen(
}
if (!hiddenTools.contains(PdfReaderTool.KEEP_SCREEN_ON.name)) {
DropdownMenuItem(
text = { Text("Keep Screen On") },
text = { Text(stringResource(R.string.menu_keep_screen_on)) },
onClick = {
isKeepScreenOn = !isKeepScreenOn
saveKeepScreenOn(context, isKeepScreenOn)
@ -5811,7 +5811,7 @@ fun PdfViewerScreen(
if (isKeepScreenOn) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Selected"
contentDescription = stringResource(R.string.content_desc_selected)
)
}
})
@ -5819,7 +5819,7 @@ fun PdfViewerScreen(
}
if (!hiddenTools.contains(PdfReaderTool.AUTO_SCROLL.name)) {
DropdownMenuItem(
text = { Text("Auto Scroll") },
text = { Text(stringResource(R.string.menu_auto_scroll)) },
enabled = !isTtsSessionActive && displayMode == DisplayMode.VERTICAL_SCROLL,
onClick = {
showMoreMenu = false
@ -5832,7 +5832,7 @@ fun PdfViewerScreen(
}
if (!hiddenTools.contains(PdfReaderTool.TTS_SETTINGS.name)) {
DropdownMenuItem(
text = { Text("TTS Voice Settings") },
text = { Text(stringResource(R.string.menu_tts_voice_settings)) },
onClick = {
showMoreMenu = false
showDeviceVoiceSettingsSheet = true
@ -5848,7 +5848,7 @@ fun PdfViewerScreen(
if (BuildConfig.DEBUG) {
DropdownMenuItem(
text = { Text("TTS Settings (Debug)") },
text = { Text(stringResource(R.string.menu_tts_settings_debug)) },
onClick = {
showMoreMenu = false
showTtsSettingsSheet = true
@ -5867,8 +5867,8 @@ fun PdfViewerScreen(
if (!hiddenTools.contains(PdfReaderTool.BOOKMARK.name)) {
DropdownMenuItem(text = {
Text(
if (isBookmarked) "Remove bookmark"
else "Bookmark this page"
if (isBookmarked) stringResource(R.string.menu_remove_bookmark)
else stringResource(R.string.menu_bookmark_this_page)
)
}, onClick = {
showMoreMenu = false
@ -5878,7 +5878,7 @@ fun PdfViewerScreen(
}
if (!hiddenTools.contains(PdfReaderTool.PAGE_MANAGEMENT.name)) {
DropdownMenuItem(
text = { Text("Insert Blank Page") },
text = { Text(stringResource(R.string.menu_insert_blank_page)) },
onClick = {
showMoreMenu = false
onInsertPage()
@ -5888,7 +5888,7 @@ fun PdfViewerScreen(
virtualPages.getOrNull(currentPage) is VirtualPage.BlankPage
if (canDelete) {
DropdownMenuItem(
text = { Text("Delete Page") },
text = { Text(stringResource(R.string.menu_delete_page)) },
onClick = {
showMoreMenu = false
onDeletePage()
@ -5905,9 +5905,9 @@ fun PdfViewerScreen(
text = {
Text(
when {
isReflowingThisBook -> "Generating... ${(reflowProgressValue * 100).toInt()}%"
hasReflowFile -> "Open Text View"
else -> "Generate Text View"
isReflowingThisBook -> stringResource(R.string.generating_reflow_progress)
hasReflowFile -> stringResource(R.string.action_open_text_view)
else -> stringResource(R.string.action_generate_text_view)
}
)
},
@ -5964,7 +5964,7 @@ fun PdfViewerScreen(
}
if (!hiddenTools.contains(PdfReaderTool.SHARE.name)) {
DropdownMenuItem(
text = { Text("Share") },
text = { Text(stringResource(R.string.action_share)) },
onClick = {
showMoreMenu = false
showShareDialog = true
@ -5979,7 +5979,7 @@ fun PdfViewerScreen(
}
if (uiState.selectedFileType == FileType.PDF && !hiddenTools.contains(PdfReaderTool.SAVE_COPY.name)) {
DropdownMenuItem(
text = { Text("Save copy to device") },
text = { Text(stringResource(R.string.action_save_copy_to_device)) },
onClick = {
showMoreMenu = false
showSaveDialog = true
@ -5993,7 +5993,7 @@ fun PdfViewerScreen(
}
if (uiState.selectedFileType == FileType.PDF && !hiddenTools.contains(PdfReaderTool.PRINT.name)) {
DropdownMenuItem(
text = { Text("Print") },
text = { Text(stringResource(R.string.action_print)) },
onClick = {
showMoreMenu = false
onPrintDocument()
@ -6066,7 +6066,7 @@ fun PdfViewerScreen(
},
modifier = Modifier.size(20.dp)
) {
Icon(Icons.Default.Close, contentDescription = "Close Tab", modifier = Modifier.size(16.dp), tint = contentColor)
Icon(Icons.Default.Close, contentDescription = stringResource(R.string.close_tab), modifier = Modifier.size(16.dp), tint = contentColor)
}
}
}
@ -6113,7 +6113,7 @@ fun PdfViewerScreen(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Generating Text View...",
text = stringResource(R.string.generating_text_view),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f)
@ -6162,7 +6162,7 @@ fun PdfViewerScreen(
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Indexing pages... ${(backgroundIndexingProgress * 100).toInt()}% done. Search results will update automatically.",
text = stringResource(R.string.msg_indexing_pages_progress),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
@ -6358,7 +6358,7 @@ fun PdfViewerScreen(
) {
Icon(
painter = painterResource(id = R.drawable.slider),
contentDescription = "Navigate with slider"
contentDescription = stringResource(R.string.content_desc_navigate_slider)
)
}
}
@ -6393,7 +6393,7 @@ fun PdfViewerScreen(
) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search"
contentDescription = stringResource(R.string.action_search)
)
}
}
@ -6438,7 +6438,7 @@ fun PdfViewerScreen(
) {
Icon(
painter = painterResource(id = R.drawable.ai),
contentDescription = "AI Features"
contentDescription = stringResource(R.string.tooltip_ai)
)
}
DropdownMenu(
@ -6446,7 +6446,7 @@ fun PdfViewerScreen(
onDismissRequest = { showAiFeaturesMenu = false }) {
DropdownMenuItem(
text = {
Text("Summarize Page (Page ${currentPage + 1})")
Text(stringResource(R.string.action_summarize_page))
}, onClick = {
showAiFeaturesMenu = false
if (isProUser) {
@ -6526,7 +6526,7 @@ fun PdfViewerScreen(
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)
)
}
@ -6552,21 +6552,21 @@ fun PdfViewerScreen(
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)
)
}
// Tune button for BASE mode
if (currentTtsMode == TtsPlaybackManager.TtsMode.BASE) {
TooltipIconButton(
text = "Voice Adjustments",
text = stringResource(R.string.tts_voice_adjustments),
description = "Adjust voice speed and pitch",
onClick = { showTtsControlsSheet = true }
) {
Icon(
imageVector = Icons.Default.Tune,
contentDescription = "Voice Adjustments"
contentDescription = stringResource(R.string.tts_voice_adjustments)
)
}
}
@ -6721,19 +6721,13 @@ fun PdfViewerScreen(
.fillMaxSize()
.then(
when {
isDockDragging -> Modifier // Positioned manually
// via offset during
// drag
dockLocation == DockLocation.TOP -> Modifier // Aligned via Box
// Scope
dockLocation == DockLocation.BOTTOM -> Modifier // Aligned via Box
// Scope
else -> Modifier // Positioned manually
// via offset
isDockDragging -> Modifier
dockLocation == DockLocation.TOP -> Modifier
dockLocation == DockLocation.BOTTOM -> Modifier
else -> Modifier
}
)
) {
// Calculate drag offset to apply if floating/dragging
val dragModifier =
if (isDockDragging || dockLocation == DockLocation.FLOATING) {
Modifier.offset {
@ -6742,10 +6736,9 @@ fun PdfViewerScreen(
)
}
} else {
Modifier // Sticky positions use alignment below
Modifier
}
// Calculate Alignment for Sticky states
val alignModifier = when {
isDockDragging || dockLocation == DockLocation.FLOATING -> Modifier
dockLocation == DockLocation.TOP -> Modifier.align(Alignment.TopCenter)
@ -6759,7 +6752,7 @@ fun PdfViewerScreen(
} else {
Modifier.padding(
horizontal = 16.dp
) // Original padding for floating capsule
)
}
val paddingModifier =
@ -7209,10 +7202,10 @@ fun PdfViewerScreen(
if (showPermissionRationaleDialog) {
AlertDialog(
onDismissRequest = { showPermissionRationaleDialog = false },
title = { Text("Permission Required") },
title = { Text(stringResource(R.string.dialog_permission_required)) },
text = {
Text(
"To show playback controls while the app is in the background, please grant the notification permission."
stringResource(R.string.dialog_permission_notification_desc)
)
},
confirmButton = {
@ -7222,14 +7215,14 @@ fun PdfViewerScreen(
permissionLauncher.launch(
Manifest.permission.POST_NOTIFICATIONS
)
}) { Text("Continue") }
}) { Text(stringResource(R.string.action_continue)) }
},
dismissButton = {
TextButton(
onClick = {
showPermissionRationaleDialog = false
startTts()
}) { Text("Not now") }
}) { Text(stringResource(R.string.action_not_now)) }
})
}
if (showSummarizationUpsellDialog) {
@ -7252,11 +7245,11 @@ fun PdfViewerScreen(
onClick = {
showSummarizationUpsellDialog = false
onNavigateToPro()
}) { Text("Learn More") }
}) { Text(stringResource(R.string.action_learn_more)) }
},
dismissButton = {
TextButton(onClick = { showSummarizationUpsellDialog = false }) {
Text("Not Now")
Text(stringResource(R.string.action_not_now))
}
})
}
@ -7287,13 +7280,13 @@ fun PdfViewerScreen(
.padding(bottom = 16.dp)
) {
Text(
text = "Add PDF to Tab",
text = stringResource(R.string.title_add_pdf_to_tab),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(16.dp)
)
if (pdfFiles.isEmpty()) {
Text(
"No other PDFs found in your library.",
stringResource(R.string.msg_no_other_pdfs_found),
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@ -7345,7 +7338,7 @@ fun PdfViewerScreen(
if (!selectedDictPackage.isNullOrEmpty()) {
ExternalDictionaryHelper.launchDictionary(context, selectedDictPackage!!, text)
} else {
Toast.makeText(context, "Select an offline dictionary first.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.toast_select_offline_dict_first), Toast.LENGTH_SHORT).show()
showDictionarySettingsSheet = true
}
}
@ -7358,19 +7351,19 @@ fun PdfViewerScreen(
painter = painterResource(id = R.drawable.ai),
contentDescription = null
)
}, title = { Text("Unlock Smart Dictionary") }, text = {
}, title = { Text(stringResource(R.string.ai_unlock_smart_dict)) }, text = {
Text(
"Defining entire phrases and paragraphs up to 2000 characters is a Pro feature. Upgrade to get instant definitions for any selected text."
stringResource(R.string.ai_unlock_smart_dict_desc)
)
}, confirmButton = {
TextButton(
onClick = {
showDictionaryUpsellDialog = false
onNavigateToPro()
}) { Text("Learn More") }
}) { Text(stringResource(R.string.action_learn_more)) }
}, dismissButton = {
TextButton(onClick = { showDictionaryUpsellDialog = false }) {
Text("Not Now")
Text(stringResource(R.string.action_not_now))
}
})
}
@ -7380,12 +7373,10 @@ fun PdfViewerScreen(
AlertDialog(
onDismissRequest = { showReindexDialog = null },
icon = { Icon(Icons.Default.Info, contentDescription = null) },
title = { Text("Re-index Document?") },
title = { Text(stringResource(R.string.title_reindex_document)) },
text = {
Text(
"You are changing the OCR script to ${newLanguage.displayName}.\n\n" +
"To ensure search accuracy, we need to clear the existing index and re-scan pages that require OCR using this new language.\n\n" +
"This will happen in the background."
stringResource(R.string.desc_reindex_document_warning)
)
},
confirmButton = {
@ -7412,11 +7403,11 @@ fun PdfViewerScreen(
showOcrLanguageDialog = false
}
}
) { Text("Re-index") }
) { Text(stringResource(R.string.action_reindex)) }
},
dismissButton = {
TextButton(onClick = { showReindexDialog = null }) {
Text("Cancel")
Text(stringResource(R.string.action_cancel))
}
}
)
@ -7625,8 +7616,8 @@ fun PdfViewerScreen(
val url = clickedLinkUrl!!
AlertDialog(
onDismissRequest = { clickedLinkUrl = null },
title = { Text("External Link") },
text = { Text("You are about to navigate to:\n$url") },
title = { Text(stringResource(R.string.dialog_external_link_title)) },
text = { Text(stringResource(R.string.desc_external_link_warning)) },
confirmButton = {
TextButton(
onClick = {
@ -7636,7 +7627,7 @@ fun PdfViewerScreen(
Timber.e(e, "Failed to open URI")
}
clickedLinkUrl = null
}) { Text("Visit") }
}) { Text(stringResource(R.string.action_visit)) }
},
dismissButton = {
Row {
@ -7644,9 +7635,9 @@ fun PdfViewerScreen(
onClick = {
clipboardManager.setText(AnnotatedString(url))
clickedLinkUrl = null
}) { Text("Copy") }
}) { Text(stringResource(R.string.action_copy)) }
TextButton(onClick = { clickedLinkUrl = null }) {
Text("Cancel")
Text(stringResource(R.string.action_cancel))
}
}
})
@ -7655,8 +7646,8 @@ fun PdfViewerScreen(
if (showSaveDialog) {
AlertDialog(
onDismissRequest = { showSaveDialog = false },
title = { Text("Save to Device") },
text = { Text("Choose format to save:") },
title = { Text(stringResource(R.string.title_save_to_device)) },
text = { Text(stringResource(R.string.desc_choose_format_save)) },
confirmButton = {
TextButton(
onClick = {
@ -7666,7 +7657,7 @@ fun PdfViewerScreen(
originalFileName, isAnnotated = true
)
saveLauncher.launch(suggestedName)
}) { Text("With Annotations") }
}) { Text(stringResource(R.string.action_with_annotations)) }
},
dismissButton = {
Row {
@ -7678,7 +7669,7 @@ fun PdfViewerScreen(
originalFileName, isAnnotated = false
)
saveLauncher.launch(suggestedName)
}) { Text("Original") }
}) { Text(stringResource(R.string.action_original)) }
Spacer(Modifier.width(8.dp))
@ -7686,7 +7677,7 @@ fun PdfViewerScreen(
onClick = {
showSaveDialog = false
pendingSaveMode = null
}) { Text("Cancel") }
}) { Text(stringResource(R.string.action_cancel)) }
}
})
}
@ -7694,8 +7685,8 @@ fun PdfViewerScreen(
if (showShareDialog) {
AlertDialog(
onDismissRequest = { showShareDialog = false },
title = { Text("Share PDF") },
text = { Text("Choose format to share:") },
title = { Text(stringResource(R.string.share_chooser_title)) },
text = { Text(stringResource(R.string.desc_choose_format_share)) },
confirmButton = {
TextButton(
onClick = {
@ -7721,7 +7712,7 @@ fun PdfViewerScreen(
)
isShareLoading = false
}
}) { Text("With Annotations") }
}) { Text(stringResource(R.string.action_with_annotations)) }
},
dismissButton = {
Row {
@ -7742,10 +7733,10 @@ fun PdfViewerScreen(
)
isShareLoading = false
}
}) { Text("Original") }
}) { Text(stringResource(R.string.action_original)) }
Spacer(Modifier.width(8.dp))
TextButton(onClick = { showShareDialog = false }) {
Text("Cancel")
Text(stringResource(R.string.action_cancel))
}
}
})
@ -7773,7 +7764,7 @@ fun PdfViewerScreen(
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Preparing PDF...",
text = stringResource(R.string.msg_preparing_pdf),
style = MaterialTheme.typography.bodyLarge
)
}
@ -8087,27 +8078,27 @@ private fun PasswordDialog(isError: Boolean, onDismiss: () -> Unit, onConfirm: (
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
AlertDialog(onDismissRequest = onDismiss, title = { Text("Password Protected") }, text = {
AlertDialog(onDismissRequest = onDismiss, title = { Text(stringResource(R.string.title_password_protected)) }, text = {
Column {
Text("This document is encrypted. Please enter the password to view it.")
Text(stringResource(R.string.desc_password_protected))
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
label = { Text(stringResource(R.string.password)) },
singleLine = true,
visualTransformation = if (passwordVisible) VisualTransformation.None
else PasswordVisualTransformation(),
keyboardActions = KeyboardActions(onDone = { onConfirm(password) }),
isError = isError,
supportingText = if (isError) {
{ Text("Incorrect password") }
{ Text(stringResource(R.string.error_incorrect_password)) }
} else null,
trailingIcon = {
val image = if (passwordVisible) Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
val description = if (passwordVisible) "Hide password" else "Show password"
val description = if (passwordVisible) stringResource(R.string.content_desc_hide_password) else stringResource(R.string.content_desc_show_password)
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description)
@ -8118,9 +8109,9 @@ private fun PasswordDialog(isError: Boolean, onDismiss: () -> Unit, onConfirm: (
}
}, confirmButton = {
Button(onClick = { onConfirm(password) }, enabled = password.isNotBlank()) {
Text("Open")
Text(stringResource(R.string.action_open))
}
}, dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } })
}, dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) } })
}
@Composable
@ -8137,13 +8128,12 @@ fun PenPlayground(onClose: () -> Unit) {
Color.Black // Black
)
// Dark Card Background
Surface(
modifier = Modifier
.fillMaxWidth(0.95f)
.padding(16.dp),
shape = RoundedCornerShape(28.dp),
color = Color(0xFF1E1E1E), // Deep Matte Dark Grey
color = Color(0xFF1E1E1E),
shadowElevation = 16.dp,
tonalElevation = 0.dp
) {
@ -8151,13 +8141,11 @@ fun PenPlayground(onClose: () -> Unit) {
modifier = Modifier.padding(vertical = 24.dp, horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Header with Close Button
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Placeholder icon (Star) on left
Icon(
imageVector = Icons.Default.Star,
contentDescription = null,
@ -8169,7 +8157,7 @@ fun PenPlayground(onClose: () -> Unit) {
Icon(
painter = painterResource(
id = R.drawable.close
), // Ensure you have a close icon or use Icons.Default.Close
),
contentDescription = "Close", tint = Color.Gray
)
}
@ -8181,19 +8169,17 @@ fun PenPlayground(onClose: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(140.dp), // Height for pens + ink stroke space
.height(140.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.Bottom
) {
PenType.entries.forEach { type ->
val isSelected = selectedPen == type
// Selected pens float up slightly
val offsetY by animateDpAsState(
targetValue = if (isSelected) (-20).dp else 0.dp, label = "offset"
)
// Selected pens scale up
val scale by animateFloatAsState(
targetValue = if (isSelected) 1.2f else 1.0f, label = "scale"
)
@ -8205,13 +8191,13 @@ fun PenPlayground(onClose: () -> Unit) {
.scale(scale)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null // Remove ripple for cleaner look
indication = null
) { selectedPen = type }) {
// Drawing Area
Box(
modifier = Modifier
.width(40.dp)
.height(120.dp), // Tall enough for stroke + pen
.height(120.dp),
contentAlignment = Alignment.BottomCenter
) {
PenIcon(
@ -8229,7 +8215,6 @@ fun PenPlayground(onClose: () -> Unit) {
Spacer(Modifier.height(24.dp))
// Subtle Divider
HorizontalDivider(
modifier = Modifier.padding(horizontal = 12.dp),
color = Color.White.copy(alpha = 0.1f),
@ -8292,10 +8277,10 @@ private fun OcrLanguageSelectionDialog(
onDismiss: () -> Unit,
onLanguageSelected: (OcrLanguage) -> Unit
) {
AlertDialog(onDismissRequest = onDismiss, title = { Text("Select OCR Language") }, text = {
AlertDialog(onDismissRequest = onDismiss, title = { Text(stringResource(R.string.title_select_ocr_language)) }, text = {
Column(Modifier.selectableGroup()) {
Text(
"Choose the primary language/script of this document for better text recognition results.",
stringResource(R.string.desc_select_ocr_language),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
@ -8317,7 +8302,7 @@ private fun OcrLanguageSelectionDialog(
)
Spacer(Modifier.width(8.dp))
Text(
"You can change this later in More Options > OCR Language.",
stringResource(R.string.desc_ocr_language_change_later),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
@ -8349,7 +8334,7 @@ private fun OcrLanguageSelectionDialog(
}
}
}
}, confirmButton = { TextButton(onClick = onDismiss) { Text("Cancel") } })
}, confirmButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) } })
}
@Composable
@ -8454,7 +8439,7 @@ fun PdfSearchResultsPanel(
} else {
Column {
Text(
text = "Results found on ${totalPageCount}+ pages",
text = stringResource(R.string.msg_results_found_pages),
style = MaterialTheme.typography.titleSmall,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
)
@ -8506,7 +8491,7 @@ fun PdfSearchResultsList(
Surface(modifier = modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
if (results.isEmpty()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("No results found.", style = MaterialTheme.typography.bodyLarge)
Text(stringResource(R.string.search_no_results_simple), style = MaterialTheme.typography.bodyLarge)
}
} else {
Column {
@ -8555,14 +8540,14 @@ fun PdfCustomizeToolsSheet(
) {
Column(modifier = Modifier.padding(horizontal = 24.dp).padding(bottom = 24.dp)) {
Text(
text = "Customize Toolbar",
text = stringResource(R.string.title_customize_toolbar),
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Select the tools you want to keep visible. Unchecking a tool hides it from the UI to give you a distraction-free reading space.",
text = stringResource(R.string.desc_customize_toolbar),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)

View file

@ -97,6 +97,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
@ -211,7 +212,6 @@ fun TextAnnotationDock(
) {
when (activePopup) {
ActivePopup.FONT_FAMILY -> {
// Re-using the logic from your EPUB FontSelectionSheetContent but adapted for a Popup
Surface(
shape = RoundedCornerShape(16.dp),
color = Color(0xFF1E1E1E),
@ -229,12 +229,12 @@ fun TextAnnotationDock(
Tab(
selected = selectedTabIndex == 0,
onClick = { selectedTabIndex = 0 },
text = { Text("Presets", fontSize = 12.sp) }
text = { Text(stringResource(R.string.tab_presets), fontSize = 12.sp) }
)
Tab(
selected = selectedTabIndex == 1,
onClick = { selectedTabIndex = 1 },
text = { Text("Imported", fontSize = 12.sp) }
text = { Text(stringResource(R.string.tab_imported), fontSize = 12.sp) }
)
}
@ -244,7 +244,7 @@ fun TextAnnotationDock(
item {
val isSelected = currentFontName == "Default" || currentFontName == null
FontItem(
name = "Default System Font",
name = stringResource(R.string.font_default_system),
isSelected = isSelected,
fontFamily = FontFamily.Default,
onClick = {
@ -281,12 +281,12 @@ fun TextAnnotationDock(
) {
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(8.dp))
Text("Import", fontSize = 12.sp)
Text(stringResource(R.string.action_import), fontSize = 12.sp)
}
if (customFonts.isEmpty()) {
Text(
"No fonts imported",
stringResource(R.string.msg_no_fonts_imported),
color = Color.Gray,
modifier = Modifier.fillMaxWidth().padding(16.dp),
textAlign = TextAlign.Center,
@ -320,7 +320,7 @@ fun TextAnnotationDock(
ActivePopup.COLOR -> {
if (activeMenuMode == ColorMenuMode.PALETTE) {
ColorPickerBubble(
title = "Font color",
title = stringResource(R.string.label_font_color),
currentColor = currentStyle.color.takeIf { it != Color.Unspecified }
?: Color.Black,
palette = textColorPalette,
@ -359,7 +359,7 @@ fun TextAnnotationDock(
ActivePopup.BACKGROUND -> {
if (activeMenuMode == ColorMenuMode.PALETTE) {
ColorPickerBubble(
title = "Highlight",
title = stringResource(R.string.label_highlight_color),
currentColor = when (currentStyle.background) {
Color.Unspecified, Color.Transparent -> Color.Transparent
else -> currentStyle.background

View file

@ -70,6 +70,7 @@ import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
@ -80,6 +81,7 @@ import androidx.compose.ui.window.DialogProperties
import com.aryan.reader.BrightnessSlider
import com.aryan.reader.ColorComparePill
import com.aryan.reader.HexInput
import com.aryan.reader.R
import com.aryan.reader.RgbInputColumn
import com.aryan.reader.SpectrumBox
import kotlin.math.roundToInt
@ -262,7 +264,7 @@ fun ToolSettingsPopup(
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Straight Line",
text = stringResource(R.string.label_straight_line),
color = Color.White,
style = MaterialTheme.typography.bodyMedium
)
@ -465,7 +467,7 @@ private fun ColorPickerDialog(
.padding(horizontal = 24.dp, vertical = 8.dp)
) {
Text(
text = "Spectrum",
text = stringResource(R.string.label_spectrum),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
color = Color.White
@ -520,7 +522,7 @@ private fun ColorPickerDialog(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
"Hex",
stringResource(R.string.theme_color_hex),
color = Color.Gray,
fontSize = 12.sp,
maxLines = 1
@ -537,19 +539,19 @@ private fun ColorPickerDialog(
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
RgbInputColumn(
label = "Red",
label = stringResource(R.string.color_red),
value = currentColor.red,
onValueChange = { r -> updateFromColor(currentColor.copy(red = r)) },
modifier = Modifier.weight(1f)
)
RgbInputColumn(
label = "Green",
label = stringResource(R.string.color_green),
value = currentColor.green,
onValueChange = { g -> updateFromColor(currentColor.copy(green = g)) },
modifier = Modifier.weight(1f)
)
RgbInputColumn(
label = "Blue",
label = stringResource(R.string.color_blue),
value = currentColor.blue,
onValueChange = { b -> updateFromColor(currentColor.copy(blue = b)) },
modifier = Modifier.weight(1f)
@ -565,7 +567,7 @@ private fun ColorPickerDialog(
verticalAlignment = Alignment.CenterVertically
) {
TextButton(onClick = onDismiss) {
Text("Cancel", color = Color.Gray)
Text(stringResource(R.string.action_cancel), color = Color.Gray)
}
Spacer(Modifier.width(8.dp))
Button(
@ -575,7 +577,7 @@ private fun ColorPickerDialog(
contentColor = Color.Black
)
) {
Text("Done")
Text(stringResource(R.string.action_done))
}
}
}

View file

@ -1,17 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Shown on book cards and in various dialogs. %1$d = number of books. -->
<plurals name="book_count">
<item quantity="one">%1$d book</item>
<item quantity="other">%1$d books</item>
</plurals>
<!-- Word-only plural (no number prefix) used in sentences. Example: "remove 3 books from shelf". -->
<plurals name="book_word">
<item quantity="one">book</item>
<item quantity="other">books</item>
</plurals>
<!-- Shown when deleting multiple shelves. %1$d = number of shelves. -->
<plurals name="shelf_count">
<item quantity="one">%1$d shelf</item>
<item quantity="other">%1$d shelves</item>
</plurals>
<!-- Shown in the search results summary bar. %1$d = total number of results found. -->
<plurals name="search_results_count">
<item quantity="one">%1$d result found</item>
<item quantity="other">%1$d results found</item>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- This instruction strips the internet permission specifically for the releaseOffline build type -->
<uses-permission android:name="android.permission.INTERNET" tools:node="remove" />
</manifest>