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:
Aryan 2026-05-20 22:14:01 +05:30 committed by GitHub
parent dc5196526f
commit 9510293ac3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
245 changed files with 37538 additions and 12460 deletions

View file

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