Update v1.0.49 (#330)
* Added PDF top tab strip visibility toggle and fixed WebView hit test NPE * Refactored desktop reader screens and state management into specialized components * Added image gallery to reader sidebar and refactored desktop PDF UI components * Implemented EPUB image gallery * Refactored reader and library models to use shared common types, centralizing file type resolution and texture management while removing redundant mapping logic * Standardized UI styling and refactored app navigation layout * Implemented auto-hiding reader chrome and activity tracking in desktop * Refactored reader panels into distinct left and right modal layers with platform-specific sizing and keyboard navigation support. * Added PPTX support for desktop and refactored parsing into a shared module * Implement paid AI features and account management for the desktop application. * Implement AI Hub and enhanced Cloud TTS integration for Desktop * Implement streaming support for AI definition and summarization features * Implement support for password-protected PDFs and file actions in the desktop reader. * Implement cloud synchronization for desktop using Firestore and Google Drive * Implement PDF reflow and "Text View" for the desktop reader * Refactor OPDS logic to use SharedOpdsController * Optimize PDF tile rendering performance * Implement two-page spread support for PDF pagination * Implement two-page spread support for the PDF viewer * Improved shared spread zoom in PDF viewer * Improve PDF spread navigation with fling support and configurable page gaps * Add brightness control to PDF and EPUB readers * Refactor folder synchronization to use shared logic engine * Implement safe string formatting and validation for localized resources * Implement TTS chunk skip navigation * Implement deep-linking and playback controls for TTS media sessions * Implement start index for TTS playback * Improve TTS navigation, prefetching, and notification duration reporting * Implement TTS mini playback bar for background reading * Implement multi-window reader support for the desktop application * Improve desktop modal window management and visibility syncing * Implement localized string support for Desktop and shared UI * Implement language selection and persistence for Desktop * Implement plural string support for Desktop and migrate hardcoded counts to plurals.xml * Implement localized banner messages and UI strings using resource-backed SharedText * Implement compact badge styling for small book covers * Refactor PDF native interaction and improve HTML import memory safety * fix language persistence * Refactor reader overflow menus to use section-based logic * Refactor PDF layout remapping and improve text box interaction * Improve CFI resolution and TTS resume accuracy using dynamic chunk offsets * Centralize PDF annotation export mapping and improve metadata handling * Add support for threaded comments in PDF highlight annotations * Flatten highlight comments into a single thread for PDF export and allow author editing * Integrate page slider into reader chrome and persist toggle state * Handle fragments and queries in EPUB chapter paths * Implement dynamic, theme-aware coloring for the reader slider * Implement customizable app-wide font preference * Implement one-hand zoom gestures in the PDF viewer * Implement File Information dialog for PDF and EPUB readers * Bump version to 1.0.49 (53) * Refactor PDF reader logic into modular components * Add ProGuard rules to prevent R8 optimization issues in EPUB reader screens * Add option to use PDF filenames as display names * Fix preservation of PDF filename display preference in library projection
This commit is contained in:
parent
dc5196526f
commit
9510293ac3
245 changed files with 37538 additions and 12460 deletions
|
|
@ -31,6 +31,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.aryan.reader.shared.AppAction
|
||||
import com.aryan.reader.shared.BannerMessage
|
||||
import com.aryan.reader.shared.BookItem
|
||||
import com.aryan.reader.shared.ReaderPlatform
|
||||
import com.aryan.reader.shared.SharedFolderPathResolver
|
||||
import com.aryan.reader.shared.SharedReaderScreenState
|
||||
import com.aryan.reader.shared.Shelf
|
||||
|
|
@ -42,10 +43,10 @@ import com.aryan.reader.shared.Tag
|
|||
import com.aryan.reader.shared.reader.ReaderSettings
|
||||
import com.aryan.reader.shared.reduce
|
||||
import com.aryan.reader.shared.ui.NonReaderLibraryTab
|
||||
import com.aryan.reader.shared.ui.SharedHomeScreen
|
||||
import com.aryan.reader.shared.ui.SharedLibraryScreen
|
||||
import com.aryan.reader.shared.ui.SharedShelvesScreen
|
||||
import com.aryan.reader.shared.ui.SharedStableOutlinedTextField
|
||||
import com.aryan.reader.shared.ui.readerString
|
||||
import java.io.File
|
||||
|
||||
internal fun BookItem.hasEmbeddedMetadataChange(updated: BookItem): Boolean {
|
||||
|
|
@ -116,8 +117,7 @@ internal fun resolvedDesktopReaderSettings(
|
|||
|
||||
@Composable
|
||||
internal fun DesktopReaderOpeningScreen(
|
||||
opening: DesktopReaderOpening,
|
||||
onReturnToLibrary: () -> Unit
|
||||
opening: DesktopReaderOpening
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(32.dp),
|
||||
|
|
@ -129,7 +129,7 @@ internal fun DesktopReaderOpeningScreen(
|
|||
) {
|
||||
CircularProgressIndicator()
|
||||
Text(
|
||||
text = "Opening ${opening.title}",
|
||||
text = readerString("desktop_opening_title", "Opening %1\$s", opening.title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
textAlign = TextAlign.Center
|
||||
|
|
@ -140,9 +140,6 @@ internal fun DesktopReaderOpeningScreen(
|
|||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
TextButton(onClick = onReturnToLibrary) {
|
||||
Text("Return to library")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -150,6 +147,9 @@ internal fun DesktopReaderOpeningScreen(
|
|||
@Composable
|
||||
internal fun HomeScreen(
|
||||
state: SharedReaderScreenState,
|
||||
selectedLibraryTab: NonReaderLibraryTab,
|
||||
onLibraryTabChange: (NonReaderLibraryTab) -> Unit,
|
||||
onStateChange: (SharedReaderScreenState) -> Unit,
|
||||
onImportBooks: () -> Unit,
|
||||
onImportFolder: () -> Unit,
|
||||
onRead: (BookItem) -> Unit,
|
||||
|
|
@ -158,34 +158,40 @@ internal fun HomeScreen(
|
|||
onRemoveSelected: () -> Unit,
|
||||
onShowBookInfo: (BookItem) -> Unit,
|
||||
onEditBook: (BookItem) -> Unit,
|
||||
onCreateShelf: () -> Unit,
|
||||
onCreateSmartShelf: () -> Unit,
|
||||
onRenameShelf: (Shelf) -> Unit,
|
||||
onDeleteShelf: (Shelf) -> Unit,
|
||||
onRemoveFolder: (Shelf) -> Unit,
|
||||
onTagSelectedBooks: () -> Unit,
|
||||
onAddSelectedBooksToShelf: () -> Unit,
|
||||
onOpenTab: (BookItem) -> Unit,
|
||||
onCloseTab: (BookItem) -> Unit,
|
||||
onCloseAllTabs: () -> Unit,
|
||||
onRecentLimitChange: (Int) -> Unit,
|
||||
onTogglePinned: (BookItem) -> Unit,
|
||||
onOpenSettings: () -> Unit
|
||||
onSyncFolderMetadata: () -> Unit,
|
||||
onScanFolders: () -> Unit,
|
||||
onTogglePinned: (BookItem) -> Unit
|
||||
) {
|
||||
SharedHomeScreen(
|
||||
LibraryScreen(
|
||||
state = state,
|
||||
selectedLibraryTab = selectedLibraryTab,
|
||||
onLibraryTabChange = onLibraryTabChange,
|
||||
onStateChange = onStateChange,
|
||||
onImportBooks = onImportBooks,
|
||||
onImportFolder = onImportFolder,
|
||||
onOpenBook = onRead,
|
||||
onToggleSelection = onSelect,
|
||||
onRead = onRead,
|
||||
onSelect = onSelect,
|
||||
onClearSelection = onClearSelection,
|
||||
onRemoveSelected = onRemoveSelected,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onCreateShelf = onCreateShelf,
|
||||
onCreateSmartShelf = onCreateSmartShelf,
|
||||
onRenameShelf = onRenameShelf,
|
||||
onDeleteShelf = onDeleteShelf,
|
||||
onRemoveFolder = onRemoveFolder,
|
||||
onTagSelectedBooks = onTagSelectedBooks,
|
||||
onAddSelectedBooksToShelf = onAddSelectedBooksToShelf,
|
||||
onOpenTab = onOpenTab,
|
||||
onCloseTab = onCloseTab,
|
||||
onCloseAllTabs = onCloseAllTabs,
|
||||
onRecentLimitChange = onRecentLimitChange,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onOpenSettings = onOpenSettings,
|
||||
showActiveTabs = false
|
||||
onSyncFolderMetadata = onSyncFolderMetadata,
|
||||
onScanFolders = onScanFolders,
|
||||
onTogglePinned = onTogglePinned
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +243,7 @@ internal fun LibraryScreen(
|
|||
onSyncFolderMetadata = onSyncFolderMetadata,
|
||||
onScanFolders = onScanFolders,
|
||||
onTogglePinned = onTogglePinned,
|
||||
platform = ReaderPlatform.DESKTOP,
|
||||
useImportEmptyStateWhenLibraryEmpty = true
|
||||
)
|
||||
}
|
||||
|
|
@ -298,7 +305,7 @@ internal fun SmartShelfDialog(
|
|||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Create smart shelf") },
|
||||
title = { Text(readerString("desktop_create_smart_shelf", "Create smart shelf")) },
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
||||
|
|
@ -307,7 +314,7 @@ internal fun SmartShelfDialog(
|
|||
SharedStableOutlinedTextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text("Shelf name") },
|
||||
label = { Text(readerString("shelf_name_hint", "Shelf name")) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
|
@ -315,29 +322,29 @@ internal fun SmartShelfDialog(
|
|||
FilterChip(
|
||||
selected = matchAll,
|
||||
onClick = { matchAll = true },
|
||||
label = { Text("All") }
|
||||
label = { Text(readerString("filter_all", "All")) }
|
||||
)
|
||||
FilterChip(
|
||||
selected = !matchAll,
|
||||
onClick = { matchAll = false },
|
||||
label = { Text("Any") }
|
||||
label = { Text(readerString("desktop_match_any", "Any")) }
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
TextButton(
|
||||
onClick = { rules = rules + DesktopSmartRuleDraft() },
|
||||
enabled = rules.size < 4
|
||||
) {
|
||||
Text("Add rule")
|
||||
Text(readerString("tts_replacements_add_rule", "Add rule"))
|
||||
}
|
||||
}
|
||||
rules.forEachIndexed { index, draft ->
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
SmartRuleDropdown(
|
||||
label = "Field",
|
||||
label = readerString("desktop_field", "Field"),
|
||||
selected = draft.field,
|
||||
options = SmartField.entries.toList(),
|
||||
optionLabel = { it.desktopLabel() },
|
||||
optionLabel = { it.localizedLabel() },
|
||||
onSelected = { field ->
|
||||
rules = rules.updateAt(index) {
|
||||
val operator = smartOperatorsFor(field).first()
|
||||
|
|
@ -346,24 +353,24 @@ internal fun SmartShelfDialog(
|
|||
}
|
||||
)
|
||||
SmartRuleDropdown(
|
||||
label = "Operator",
|
||||
label = readerString("desktop_operator", "Operator"),
|
||||
selected = draft.operator,
|
||||
options = smartOperatorsFor(draft.field),
|
||||
optionLabel = { it.desktopLabel() },
|
||||
optionLabel = { it.localizedLabel() },
|
||||
onSelected = { operator ->
|
||||
rules = rules.updateAt(index) { copy(operator = operator) }
|
||||
}
|
||||
)
|
||||
if (rules.size > 1) {
|
||||
TextButton(onClick = { rules = rules.filterIndexed { i, _ -> i != index } }) {
|
||||
Text("Remove")
|
||||
Text(readerString("action_remove", "Remove"))
|
||||
}
|
||||
}
|
||||
}
|
||||
SharedStableOutlinedTextField(
|
||||
value = draft.value,
|
||||
onValueChange = { value -> rules = rules.updateAt(index) { copy(value = value) } },
|
||||
label = { Text(draft.field.valueLabel()) },
|
||||
label = { Text(draft.field.localizedValueLabel()) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
selectionKey = index
|
||||
|
|
@ -379,12 +386,12 @@ internal fun SmartShelfDialog(
|
|||
},
|
||||
enabled = name.isNotBlank() && validRules.isNotEmpty()
|
||||
) {
|
||||
Text("Create")
|
||||
Text(readerString("action_create", "Create"))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
Text(readerString("action_cancel", "Cancel"))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -395,13 +402,14 @@ private fun <T> SmartRuleDropdown(
|
|||
label: String,
|
||||
selected: T,
|
||||
options: List<T>,
|
||||
optionLabel: (T) -> String,
|
||||
optionLabel: @Composable (T) -> String,
|
||||
onSelected: (T) -> Unit
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
TextButton(onClick = { expanded = true }) {
|
||||
Text("$label: ${optionLabel(selected)}")
|
||||
val selectedLabel = optionLabel(selected)
|
||||
Text(readerString("filter_facet", "%1\$s: %2\$s", label, selectedLabel))
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
options.forEach { option ->
|
||||
|
|
@ -424,34 +432,37 @@ private fun smartOperatorsFor(field: SmartField): List<SmartOperator> {
|
|||
}
|
||||
}
|
||||
|
||||
private fun SmartField.desktopLabel(): String {
|
||||
@Composable
|
||||
private fun SmartField.localizedLabel(): String {
|
||||
return when (this) {
|
||||
SmartField.TITLE -> "Title"
|
||||
SmartField.AUTHOR -> "Author"
|
||||
SmartField.PROGRESS -> "Progress"
|
||||
SmartField.FILE_TYPE -> "File type"
|
||||
SmartField.FOLDER -> "Folder"
|
||||
SmartField.TAG -> "Tag"
|
||||
SmartField.TITLE -> readerString("label_title", "Title")
|
||||
SmartField.AUTHOR -> readerString("author", "Author")
|
||||
SmartField.PROGRESS -> readerString("desktop_progress", "Progress")
|
||||
SmartField.FILE_TYPE -> readerString("filter_file_type", "File type")
|
||||
SmartField.FOLDER -> readerString("desktop_smart_field_folder", "Folder")
|
||||
SmartField.TAG -> readerString("content_desc_tag", "Tag")
|
||||
}
|
||||
}
|
||||
|
||||
private fun SmartField.valueLabel(): String {
|
||||
@Composable
|
||||
private fun SmartField.localizedValueLabel(): String {
|
||||
return when (this) {
|
||||
SmartField.PROGRESS -> "Percent"
|
||||
SmartField.FILE_TYPE -> "Type, e.g. PDF"
|
||||
SmartField.FOLDER -> "Folder path"
|
||||
SmartField.TAG -> "Tag name"
|
||||
SmartField.TITLE -> "Title text"
|
||||
SmartField.AUTHOR -> "Author text"
|
||||
SmartField.PROGRESS -> readerString("desktop_percent", "Percent")
|
||||
SmartField.FILE_TYPE -> readerString("desktop_type_example_pdf", "Type, e.g. PDF")
|
||||
SmartField.FOLDER -> readerString("desktop_folder_path", "Folder path")
|
||||
SmartField.TAG -> readerString("desktop_tag_name", "Tag name")
|
||||
SmartField.TITLE -> readerString("desktop_title_text", "Title text")
|
||||
SmartField.AUTHOR -> readerString("desktop_author_text", "Author text")
|
||||
}
|
||||
}
|
||||
|
||||
private fun SmartOperator.desktopLabel(): String {
|
||||
@Composable
|
||||
private fun SmartOperator.localizedLabel(): String {
|
||||
return when (this) {
|
||||
SmartOperator.EQUALS -> "Equals"
|
||||
SmartOperator.CONTAINS -> "Contains"
|
||||
SmartOperator.GREATER_THAN -> "Greater than"
|
||||
SmartOperator.LESS_THAN -> "Less than"
|
||||
SmartOperator.EQUALS -> readerString("desktop_equals", "Equals")
|
||||
SmartOperator.CONTAINS -> readerString("desktop_contains", "Contains")
|
||||
SmartOperator.GREATER_THAN -> readerString("desktop_greater_than", "Greater than")
|
||||
SmartOperator.LESS_THAN -> readerString("desktop_less_than", "Less than")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue