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:
parent
17a0097d4a
commit
b027331bd1
20 changed files with 716 additions and 313 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +819,7 @@ fun LibraryScreenContent(
|
|||
)
|
||||
}
|
||||
3 -> {
|
||||
if (!BuildConfig.IS_OFFLINE) {
|
||||
OpdsTab(
|
||||
localLibraryFiles = rawLibraryFiles,
|
||||
onBookDownloaded = onOpdsBookDownloaded,
|
||||
|
|
@ -823,6 +832,7 @@ fun LibraryScreenContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShelvesScreen(
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)) }
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
8
app/src/releaseOffline/AndroidManifest.xml
Normal file
8
app/src/releaseOffline/AndroidManifest.xml
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue