String extract (#148)
* FolderSyncWorker: update supported file types in getFileType * Extract hardcoded strings to `strings.xml` and `plurals.xml` * Extract hardcoded strings to `strings.xml` and `plurals.xml` * Extract hardcoded strings to `strings.xml` and `plurals.xml` in MainViewModel * Extract hardcoded strings to `strings.xml` and `plurals.xml` in Common.kt * Extract hardcoded strings to `strings.xml` and `plurals.xml` in Epub Reader
This commit is contained in:
parent
da1c5bce1e
commit
381193d774
20 changed files with 1388 additions and 855 deletions
|
|
@ -115,6 +115,8 @@ import androidx.compose.ui.graphics.graphicsLayer
|
|||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
|
|
@ -138,14 +140,16 @@ import java.text.SimpleDateFormat
|
|||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
private fun getBookCountString(count: Int): String {
|
||||
return if (count == 1) "1 book" else "$count books"
|
||||
return pluralStringResource(id = R.plurals.book_count, count, count)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LibraryScreen(
|
||||
viewModel: MainViewModel,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val selectedItems = uiState.contextualActionItems
|
||||
val isContextualModeActive = selectedItems.isNotEmpty()
|
||||
|
|
@ -187,7 +191,7 @@ fun LibraryScreen(
|
|||
try {
|
||||
pickFolderLauncher.launch(null)
|
||||
} catch (_: android.content.ActivityNotFoundException) {
|
||||
viewModel.showBanner("Your device doesn't support folder selection. You can still import files individually.", isError = true)
|
||||
viewModel.showBanner(context.getString(R.string.error_folder_selection_unsupported), isError = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +226,7 @@ fun LibraryScreen(
|
|||
try {
|
||||
fallbackFilePickerLauncher.launch("*/*")
|
||||
} catch (_: android.content.ActivityNotFoundException) {
|
||||
viewModel.showBanner("No file manager found. Please install a file manager app.", isError = true)
|
||||
viewModel.showBanner(context.getString(R.string.error_no_file_manager), isError = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -301,7 +305,7 @@ fun LibraryScreen(
|
|||
isLoading = uiState.isLoading,
|
||||
isRefreshing = uiState.isRefreshing,
|
||||
onOpdsBookDownloaded = { uri, title ->
|
||||
viewModel.showBanner("Downloaded $title")
|
||||
viewModel.showBanner(context.getString(R.string.banner_downloaded, title))
|
||||
viewModel.onFileSelected(uri, isFromRecent = false)
|
||||
},
|
||||
onStreamOpdsBook = { entry, catalog ->
|
||||
|
|
@ -542,7 +546,12 @@ fun LibraryScreenContent(
|
|||
val isBookContextualModeActive = selectedItems.isNotEmpty()
|
||||
val isShelfContextualModeActive = selectedShelves.isNotEmpty()
|
||||
var showSortMenu by remember { mutableStateOf(false) }
|
||||
val tabTitles = listOf("All Books", "Shelves", "Folders", "Catalogs")
|
||||
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) {
|
||||
|
|
@ -603,7 +612,7 @@ fun LibraryScreenContent(
|
|||
textFieldValue = it
|
||||
onSearchQueryChange(it.text)
|
||||
},
|
||||
placeholder = { Text("Search title or author...") },
|
||||
placeholder = { Text(stringResource(R.string.search_placeholder)) },
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(vertical = 4.dp)
|
||||
|
|
@ -627,8 +636,7 @@ fun LibraryScreenContent(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
CustomTopAppBar(
|
||||
title = { Text("Library") },
|
||||
CustomTopAppBar(title = { Text(stringResource(R.string.library_title)) },
|
||||
actions = {
|
||||
if (pagerState.currentPage == 0) {
|
||||
IconButton(onClick = onFilterClick) {
|
||||
|
|
@ -698,21 +706,21 @@ fun LibraryScreenContent(
|
|||
if (libraryFilters.fileTypes.isNotEmpty()) {
|
||||
AssistChip(
|
||||
onClick = { onRemoveFilter(libraryFilters.copy(fileTypes = emptySet())) },
|
||||
label = { Text("Types: ${libraryFilters.fileTypes.joinToString { it.name }}") },
|
||||
label = { Text(stringResource(R.string.filter_types, libraryFilters.fileTypes.joinToString { it.name })) },
|
||||
trailingIcon = { Icon(Icons.Default.Close, contentDescription = "Clear", modifier = Modifier.size(16.dp)) }
|
||||
)
|
||||
}
|
||||
if (libraryFilters.sourceFolders.isNotEmpty()) {
|
||||
AssistChip(
|
||||
onClick = { onRemoveFilter(libraryFilters.copy(sourceFolders = emptySet())) },
|
||||
label = { Text("Folders: ${libraryFilters.sourceFolders.size}") },
|
||||
label = { Text(stringResource(R.string.filter_folders, libraryFilters.sourceFolders.size)) },
|
||||
trailingIcon = { Icon(Icons.Default.Close, contentDescription = "Clear", modifier = Modifier.size(16.dp)) }
|
||||
)
|
||||
}
|
||||
if (libraryFilters.readStatus != ReadStatusFilter.ALL) {
|
||||
AssistChip(
|
||||
onClick = { onRemoveFilter(libraryFilters.copy(readStatus = ReadStatusFilter.ALL)) },
|
||||
label = { Text("Status: ${libraryFilters.readStatus.displayName}") },
|
||||
label = { Text(stringResource(R.string.filter_status, libraryFilters.readStatus.displayName)) },
|
||||
trailingIcon = { Icon(Icons.Default.Close, contentDescription = "Clear", modifier = Modifier.size(16.dp)) }
|
||||
)
|
||||
}
|
||||
|
|
@ -727,7 +735,7 @@ fun LibraryScreenContent(
|
|||
0 -> {
|
||||
if (recentFiles.isNotEmpty()) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text("Add file") },
|
||||
text = { Text(stringResource(R.string.fab_add_file)) },
|
||||
icon = { Icon(Icons.Default.Add, contentDescription = "Add file") },
|
||||
onClick = onSelectFileClick,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
|
|
@ -736,7 +744,7 @@ fun LibraryScreenContent(
|
|||
}
|
||||
1 -> {
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text("New shelf") },
|
||||
text = { Text(stringResource(R.string.fab_new_shelf)) },
|
||||
icon = { Icon(Icons.Default.Add, contentDescription = "New shelf") },
|
||||
onClick = onNewShelfClick,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
|
|
@ -757,12 +765,12 @@ fun LibraryScreenContent(
|
|||
0 -> {
|
||||
if (recentFiles.isEmpty() && searchQuery.isNotEmpty()) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text("No results found for \"$searchQuery\"")
|
||||
Text(stringResource(R.string.no_results_found, searchQuery))
|
||||
}
|
||||
} else if (recentFiles.isEmpty()) {
|
||||
EmptyState(
|
||||
title = "Your Library is Empty",
|
||||
message = "Select a PDF, EPUB, MOBI, or AZW3 file from your device to get started.",
|
||||
title = stringResource(R.string.your_library_empty),
|
||||
message = stringResource(R.string.library_empty_desc),
|
||||
onSelectFileClick = onSelectFileClick,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
|
|
@ -850,12 +858,12 @@ private fun CreateShelfDialog(onConfirm: (String) -> Unit, onDismiss: () -> Unit
|
|||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Create New Shelf") },
|
||||
title = { Text(stringResource(R.string.create_new_shelf)) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
placeholder = { Text("Shelf Name") },
|
||||
placeholder = { Text(stringResource(R.string.shelf_name_hint)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.focusRequester(focusRequester)
|
||||
)
|
||||
|
|
@ -865,12 +873,12 @@ private fun CreateShelfDialog(onConfirm: (String) -> Unit, onDismiss: () -> Unit
|
|||
onClick = { onConfirm(text) },
|
||||
enabled = text.isNotBlank()
|
||||
) {
|
||||
Text("Create")
|
||||
Text(stringResource(R.string.action_create))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -981,14 +989,14 @@ private fun ShelfDetailScreen(
|
|||
onDismissRequest = { showMoreMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Rename shelf") },
|
||||
text = { Text(stringResource(R.string.menu_rename_shelf)) },
|
||||
onClick = {
|
||||
onRenameShelf()
|
||||
showMoreMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Delete shelf") },
|
||||
text = { Text(stringResource(R.string.menu_delete_shelf)) },
|
||||
onClick = {
|
||||
onDeleteShelf()
|
||||
showMoreMenu = false
|
||||
|
|
@ -1006,7 +1014,7 @@ private fun ShelfDetailScreen(
|
|||
ExtendedFloatingActionButton(
|
||||
onClick = onAddBooksClick,
|
||||
icon = { Icon(Icons.Default.Add, contentDescription = null) },
|
||||
text = { Text("Add books") }
|
||||
text = { Text(stringResource(R.string.fab_add_books)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1016,7 +1024,7 @@ private fun ShelfDetailScreen(
|
|||
modifier = Modifier.fillMaxSize().padding(paddingValues),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("This shelf is empty", style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.shelf_empty), style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
|
|
@ -1059,7 +1067,7 @@ private fun AddBooksModeScreen(
|
|||
modifier = Modifier.statusBarsPadding(),
|
||||
topBar = {
|
||||
CustomTopAppBar(
|
||||
title = { Text("Add to $shelfName") },
|
||||
title = { Text(stringResource(R.string.add_to_shelf, shelfName)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
|
|
@ -1127,7 +1135,7 @@ private fun AddBooksModeScreen(
|
|||
floatingActionButton = {
|
||||
if (selectedBookUris.isNotEmpty()) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text("ADD (${selectedBookUris.size})") },
|
||||
text = { Text(stringResource(R.string.fab_add_count, selectedBookUris.size)) },
|
||||
icon = { Icon(Icons.Default.Check, contentDescription = "Add books") },
|
||||
onClick = onAddSelectedBooks
|
||||
)
|
||||
|
|
@ -1140,7 +1148,7 @@ private fun AddBooksModeScreen(
|
|||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = if (currentSource == AddBooksSource.UNSHELVED) "No unshelved books to add" else "All books are already in this shelf",
|
||||
text = if (currentSource == AddBooksSource.UNSHELVED) stringResource(R.string.no_unshelved_books) else stringResource(R.string.all_books_in_shelf),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
|
|
@ -1431,12 +1439,12 @@ private fun RenameShelfDialog(
|
|||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Rename Shelf") },
|
||||
title = { Text(stringResource(R.string.menu_rename_shelf)) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = { textFieldValue = it },
|
||||
placeholder = { Text("Shelf Name") },
|
||||
placeholder = { Text(stringResource(R.string.shelf_name_hint)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.focusRequester(focusRequester)
|
||||
)
|
||||
|
|
@ -1446,12 +1454,12 @@ private fun RenameShelfDialog(
|
|||
onClick = { onConfirm(textFieldValue.text) },
|
||||
enabled = textFieldValue.text.isNotBlank() && textFieldValue.text != initialName
|
||||
) {
|
||||
Text("Rename")
|
||||
Text(stringResource(R.string.action_rename))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -1470,13 +1478,13 @@ private fun DeleteShelfConfirmationDialog(
|
|||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Delete Shelf?") },
|
||||
text = { Text("Are you sure you want to delete the '$shelfName' shelf? All books will be moved to Unshelved.") },
|
||||
title = { Text(stringResource(R.string.dialog_delete_shelf)) },
|
||||
text = { Text(stringResource(R.string.dialog_delete_shelf_desc)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = onConfirm) { Text("Delete") }
|
||||
TextButton(onClick = onConfirm) { Text(stringResource(R.string.action_delete)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1488,16 +1496,16 @@ private fun RemoveFromShelfConfirmationDialog(
|
|||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val bookStr = if (count == 1) "book" else "books"
|
||||
val bookStr = pluralStringResource(id = R.plurals.book_word, count)
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Remove from Shelf?") },
|
||||
text = { Text("Are you sure you want to remove $count $bookStr from the '$shelfName' shelf? The book(s) will remain in your library and appear under Unshelved.") },
|
||||
title = { Text(stringResource(R.string.dialog_remove_from_shelf)) },
|
||||
text = { Text(stringResource(R.string.dialog_remove_from_shelf_desc, count, bookStr, shelfName)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = onConfirm) { Text("Remove") }
|
||||
TextButton(onClick = onConfirm) { Text(stringResource(R.string.action_remove)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1508,16 +1516,16 @@ private fun DeleteShelvesConfirmationDialog(
|
|||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val shelfStr = if (count == 1) "shelf" else "shelves"
|
||||
val shelfStr = pluralStringResource(id = R.plurals.shelf_count, count)
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Delete $shelfStr?") },
|
||||
text = { Text("Are you sure you want to delete the $count selected $shelfStr? All books within will be moved to Unshelved.") },
|
||||
title = { Text(stringResource(R.string.dialog_delete_shelves, shelfStr)) },
|
||||
text = { Text(stringResource(R.string.dialog_delete_shelves_desc, count, shelfStr)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = onConfirm) { Text("Delete") }
|
||||
TextButton(onClick = onConfirm) { Text(stringResource(R.string.action_delete)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1539,7 +1547,7 @@ private fun FolderSyncScreen(
|
|||
floatingActionButton = {
|
||||
if (syncedFolders.size < 3) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text("Add Folder") },
|
||||
text = { Text(stringResource(R.string.fab_add_folder)) },
|
||||
icon = { Icon(Icons.Default.Add, "Add") },
|
||||
onClick = onAddFolderClick
|
||||
)
|
||||
|
|
@ -1570,7 +1578,7 @@ private fun FolderSyncScreen(
|
|||
Icon(Icons.Default.Search, null, modifier = Modifier.size(18.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(if (isLoading) "Scanning..." else "Scan All")
|
||||
Text(if (isLoading) stringResource(R.string.scanning) else stringResource(R.string.scan_all))
|
||||
}
|
||||
|
||||
androidx.compose.material3.OutlinedButton(
|
||||
|
|
@ -1581,15 +1589,15 @@ private fun FolderSyncScreen(
|
|||
) {
|
||||
Icon(painterResource(id = R.drawable.sync), null, modifier = Modifier.size(18.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Sync Meta")
|
||||
Text(stringResource(R.string.sync_meta))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EmptyState(
|
||||
title = "Sync Local Folders",
|
||||
message = "Connect local folders to create a live library. Episteme will monitor files and sync progress.",
|
||||
title = stringResource(R.string.sync_local_folders),
|
||||
message = stringResource(R.string.sync_folders_desc),
|
||||
onSelectFileClick = onAddFolderClick,
|
||||
primaryButtonText = "Select Folder",
|
||||
primaryButtonText = stringResource(R.string.action_select_folder),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
|
|
@ -1632,7 +1640,7 @@ private fun FolderCard(
|
|||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
val dateFormat = remember { SimpleDateFormat("MMM d, h:mm a", Locale.getDefault()) }
|
||||
val lastScanText = if (folder.lastScanTime == 0L) "Never" else dateFormat.format(Date(folder.lastScanTime))
|
||||
val lastScanText = if (folder.lastScanTime == 0L) stringResource(R.string.never) else dateFormat.format(Date(folder.lastScanTime))
|
||||
|
||||
val folderFiles = remember(allRecentFiles, folder.uriString) {
|
||||
allRecentFiles.filter { it.sourceFolderUri == folder.uriString }
|
||||
|
|
@ -1676,14 +1684,14 @@ private fun FolderCard(
|
|||
}
|
||||
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Edit Filters") },
|
||||
text = { Text(stringResource(R.string.menu_edit_filters)) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onEditFiltersClick(folder)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Remove Folder") },
|
||||
text = { Text(stringResource(R.string.menu_remove_folder)) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onRemoveClick(folder)
|
||||
|
|
@ -1701,7 +1709,7 @@ private fun FolderCard(
|
|||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "LAST SYNC",
|
||||
text = stringResource(R.string.last_sync),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.Bold
|
||||
|
|
@ -1711,7 +1719,7 @@ private fun FolderCard(
|
|||
|
||||
Column(modifier = Modifier.weight(1f), horizontalAlignment = Alignment.End) {
|
||||
Text(
|
||||
text = "BOOKS",
|
||||
text = stringResource(R.string.books_count),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.Bold
|
||||
|
|
@ -1730,7 +1738,7 @@ private fun FolderCard(
|
|||
countsByType.forEach { (type, count) ->
|
||||
AssistChip(
|
||||
onClick = { },
|
||||
label = { Text("${type.name}: $count") }
|
||||
label = { Text(stringResource(R.string.folder_filter_count, type.name, count)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1749,11 +1757,11 @@ private fun EditFolderFiltersDialog(
|
|||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Filter File Types") },
|
||||
title = { Text(stringResource(R.string.filter_file_types)) },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = "Select the file types you want to sync from this folder:",
|
||||
stringResource(R.string.filter_file_types_desc),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
|
@ -1783,10 +1791,10 @@ private fun EditFolderFiltersDialog(
|
|||
TextButton(
|
||||
onClick = { onConfirm(selectedTypes) },
|
||||
enabled = selectedTypes.isNotEmpty()
|
||||
) { Text("Save") }
|
||||
) { Text(stringResource(R.string.action_save)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1812,9 +1820,9 @@ fun LibraryFilterSheet(
|
|||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text("Filter Library", style = MaterialTheme.typography.titleLarge)
|
||||
Text(stringResource(R.string.filter_library), style = MaterialTheme.typography.titleLarge)
|
||||
|
||||
Text("File Type", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.filter_file_type), style = MaterialTheme.typography.titleMedium)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
|
|
@ -1832,7 +1840,7 @@ fun LibraryFilterSheet(
|
|||
}
|
||||
|
||||
if (syncedFolders.isNotEmpty()) {
|
||||
Text("Source Folder", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.filter_source_folder), style = MaterialTheme.typography.titleMedium)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
|
|
@ -1850,7 +1858,7 @@ fun LibraryFilterSheet(
|
|||
}
|
||||
}
|
||||
|
||||
Text("Read Status", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.filter_read_status), style = MaterialTheme.typography.titleMedium)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
|
|
@ -1866,11 +1874,11 @@ fun LibraryFilterSheet(
|
|||
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(top = 16.dp), horizontalArrangement = Arrangement.End) {
|
||||
TextButton(onClick = { currentFilters = LibraryFilters() }) {
|
||||
Text("Clear All")
|
||||
Text(stringResource(R.string.clear_all))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
androidx.compose.material3.Button(onClick = { onApply(currentFilters); onDismiss() }) {
|
||||
Text("Apply")
|
||||
Text(stringResource(R.string.action_apply))
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
|
@ -1930,7 +1938,7 @@ fun OpdsTab(
|
|||
}
|
||||
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text("Add Catalog") },
|
||||
text = { Text(stringResource(R.string.fab_add_catalog)) },
|
||||
icon = { Icon(Icons.Default.Add, "Add") },
|
||||
onClick = {
|
||||
editingCatalog = null
|
||||
|
|
@ -1981,7 +1989,7 @@ fun OpdsTab(
|
|||
OutlinedTextField(
|
||||
value = query,
|
||||
onValueChange = { query = it },
|
||||
placeholder = { Text("Search catalog...") },
|
||||
placeholder = { Text(stringResource(R.string.search_catalog_placeholder)) },
|
||||
modifier = Modifier.weight(1f).padding(vertical = 4.dp)
|
||||
.focusRequester(searchFocusRequester),
|
||||
singleLine = true,
|
||||
|
|
@ -2017,7 +2025,7 @@ fun OpdsTab(
|
|||
)
|
||||
} else {
|
||||
Text(
|
||||
text = uiState.currentFeed?.title ?: "Loading...",
|
||||
text = uiState.currentFeed?.title ?: stringResource(R.string.status_loading),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
|
|
@ -2045,7 +2053,7 @@ fun OpdsTab(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("This feed is empty.")
|
||||
Text(stringResource(R.string.feed_empty))
|
||||
}
|
||||
} else {
|
||||
val facets = uiState.currentFeed?.facets ?: emptyList()
|
||||
|
|
@ -2066,7 +2074,7 @@ fun OpdsTab(
|
|||
FilterChip(
|
||||
selected = activeFacet?.isActive == true,
|
||||
onClick = { expanded = true },
|
||||
label = { Text("${groupName}: ${activeFacet?.title ?: "Select"}") },
|
||||
label = { Text(stringResource(R.string.filter_facet, groupName, activeFacet?.title ?: stringResource(R.string.action_select))) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
Icons.Default.ArrowDropDown,
|
||||
|
|
@ -2194,24 +2202,23 @@ fun OpdsTab(
|
|||
showCatalogDialog = false
|
||||
editingCatalog = null
|
||||
},
|
||||
title = { Text(if (isEditMode) "Edit Catalog" else "Add OPDS Catalog") },
|
||||
title = { Text(if (isEditMode) stringResource(R.string.edit_catalog) else stringResource(R.string.add_opds_catalog)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = newTitle,
|
||||
onValueChange = { newTitle = it },
|
||||
label = { Text("Catalog Name") },
|
||||
label = { Text(stringResource(R.string.catalog_name)) },
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = newUrl,
|
||||
onValueChange = { newUrl = it },
|
||||
label = { Text("URL") },
|
||||
placeholder = { Text("e.g. http://192.168.1.50:8080/opds") },
|
||||
label = { Text(stringResource(R.string.url)) },
|
||||
placeholder = { Text(stringResource(R.string.url_placeholder)) },
|
||||
singleLine = true
|
||||
)
|
||||
Text(
|
||||
"Authentication (Optional)",
|
||||
Text(stringResource(R.string.auth_optional),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
|
|
@ -2219,13 +2226,13 @@ fun OpdsTab(
|
|||
OutlinedTextField(
|
||||
value = newUsername,
|
||||
onValueChange = { newUsername = it },
|
||||
label = { Text("Username") },
|
||||
label = { Text(stringResource(R.string.username)) },
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = newPassword,
|
||||
onValueChange = { newPassword = it },
|
||||
label = { Text("Password") },
|
||||
label = { Text(stringResource(R.string.password)) },
|
||||
singleLine = true,
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(keyboardType = KeyboardType.Password)
|
||||
|
|
@ -2244,13 +2251,13 @@ fun OpdsTab(
|
|||
editingCatalog = null
|
||||
},
|
||||
enabled = newTitle.isNotBlank() && newUrl.isNotBlank()
|
||||
) { Text("Save") }
|
||||
) { Text(stringResource(R.string.action_save)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = {
|
||||
showCatalogDialog = false
|
||||
editingCatalog = null
|
||||
}) { Text("Cancel") }
|
||||
}) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -2259,14 +2266,14 @@ fun OpdsTab(
|
|||
val streamedBooksCount = localLibraryFiles.count { it.uriString?.contains("catalogId=${catalogToDelete!!.id}") == true }
|
||||
AlertDialog(
|
||||
onDismissRequest = { catalogToDelete = null },
|
||||
title = { Text("Delete Catalog") },
|
||||
title = { Text(stringResource(R.string.delete_catalog)) },
|
||||
text = {
|
||||
Column {
|
||||
Text("Are you sure you want to delete '${catalogToDelete!!.title}'?")
|
||||
Text(stringResource(R.string.delete_catalog_desc, catalogToDelete!!.title))
|
||||
if (streamedBooksCount > 0) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
"Deleting this catalog will also permanently remove $streamedBooksCount streaming books associated with it from your library.",
|
||||
stringResource(R.string.delete_catalog_warning, streamedBooksCount),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
|
|
@ -2282,10 +2289,10 @@ fun OpdsTab(
|
|||
catalogToDelete = null
|
||||
},
|
||||
colors = androidx.compose.material3.ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
|
||||
) { Text("Delete") }
|
||||
) { Text(stringResource(R.string.action_delete)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { catalogToDelete = null }) { Text("Cancel") }
|
||||
TextButton(onClick = { catalogToDelete = null }) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -2314,8 +2321,7 @@ fun OpdsCatalogCard(catalog: OpdsCatalog, onClick: () -> Unit, onEdit: (() -> Un
|
|||
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {
|
||||
Text(
|
||||
text = "Preset",
|
||||
Text(stringResource(R.string.preset_label),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
|
|
@ -2419,12 +2425,12 @@ fun OpdsBookCard(
|
|||
) {
|
||||
Icon(Icons.Default.Check, null, modifier = Modifier.size(16.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Read")
|
||||
Text(stringResource(R.string.action_read))
|
||||
}
|
||||
} else if (isDownloading) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Downloading...", style = MaterialTheme.typography.labelMedium)
|
||||
Text(stringResource(R.string.status_downloading), style = MaterialTheme.typography.labelMedium)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
if (progress != null) {
|
||||
Text("${(progress * 100).toInt()}%", style = MaterialTheme.typography.labelMedium)
|
||||
|
|
@ -2446,7 +2452,7 @@ fun OpdsBookCard(
|
|||
) {
|
||||
Icon(painterResource(id = R.drawable.play), null, modifier = Modifier.size(16.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Stream")
|
||||
Text(stringResource(R.string.action_stream))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2465,11 +2471,11 @@ fun OpdsBookCard(
|
|||
if (uniqueAcquisitions.isEmpty()) {
|
||||
Icon(Icons.Default.Info, null, modifier = Modifier.size(16.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Unavailable")
|
||||
Text(stringResource(R.string.action_unavailable))
|
||||
} else {
|
||||
Icon(Icons.Default.Add, null, modifier = Modifier.size(16.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Download")
|
||||
Text(stringResource(R.string.action_download))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2586,14 +2592,14 @@ fun OpdsBookDetailsSheet(
|
|||
) {
|
||||
Icon(Icons.Default.Check, contentDescription = "Read")
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Read", fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.action_read), fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
|
||||
if (isDownloading) {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Downloading...", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.status_downloading), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
if (progress != null) {
|
||||
Text("${(progress * 100).toInt()}%", style = MaterialTheme.typography.titleMedium)
|
||||
|
|
@ -2618,14 +2624,13 @@ fun OpdsBookDetailsSheet(
|
|||
) {
|
||||
Icon(painterResource(id = R.drawable.play), null, modifier = Modifier.size(18.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Stream Now", fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.action_stream_now), fontWeight = FontWeight.Bold)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
if (uniqueAcquisitions.isNotEmpty()) {
|
||||
Text(
|
||||
"Download Format",
|
||||
Text(stringResource(R.string.download_format),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
|
@ -2643,7 +2648,7 @@ fun OpdsBookDetailsSheet(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
Text("No supported formats available.", color = MaterialTheme.colorScheme.error)
|
||||
Text(stringResource(R.string.no_supported_formats), color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
|
||||
if (entry.categories.isNotEmpty()) {
|
||||
|
|
@ -2681,20 +2686,20 @@ fun OpdsBookDetailsSheet(
|
|||
) {
|
||||
entry.publisher?.takeIf { it.isNotBlank() }?.let {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text("PUBLISHER", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.publisher), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(it, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
entry.published?.takeIf { it.isNotBlank() }?.let {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text("PUBLISHED", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.published), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
val cleanDate = it.substringBefore("T")
|
||||
Text(cleanDate, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
}
|
||||
entry.language?.takeIf { it.isNotBlank() }?.let {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text("LANGUAGE", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.language), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(it.uppercase(), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
}
|
||||
|
|
@ -2703,7 +2708,7 @@ fun OpdsBookDetailsSheet(
|
|||
}
|
||||
|
||||
if (!entry.summary.isNullOrBlank()) {
|
||||
Text("Synopsis", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.synopsis), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
|
||||
val cleanSummary = remember(entry.summary) {
|
||||
val preProcessed = entry.summary
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue