Desktop app (#308)
* Implement build profiles and feature policy for offline desktop builds * Introduce unified cross-platform Settings Hub * Refactor main settings into a hierarchical page-based navigation model * Refactor library projection to use shared multiplatform logic * Refactor UI state consumption by removing intermediate screen models * Introduce AndroidSharedStateBridge to centralize state mapping and reduction logic * Refactor state management for tabs, selection, and pinning to use shared bridge logic * Refactor file type management and validation into a centralized shared module * Centralize file type resolution and improve handling of unknown types * Centralize book import logic with SharedImportPlanner * Refactor magnifier geometry logic and coordinate mapping * Properly handle orientation changes in scroll-locked PDF reader * Add screen orientation controls to EPUB and PDF readers * Implement right-to-left (RTL) pagination support and refactor reader menus * Separate right-to-left pagination settings for PDF and EPUB * Ensure PDF page data is scoped by document key for multi tab support * Implement theme-aware link styling for the epub reader * Implement jump history for back and forward navigation in the epub reader * Improve locator handling and navigation logic in paginated reader mode * Implement stable pagination navigation and location tracking * Centralize banner message management and auto-dismiss logic in MainViewModel * Implement zoom and pan state preservation for PDF pan lock mode * Enhance reader navigation UI and workspace layout management in desktop app * Refactor reader navigation sidebar and relocate search controls in desktop app * Enhance reader UI with redesigned selection menus and bottom sheet overlays * Implement custom highlight palettes and reader theme customization in desktop app * Implement cross-platform modal layer and refine reader UI styling * Improve highlight accuracy and implement metadata enrichment on book open in desktop app * Implement two-page spread layout for paginated reader on desktop * Implement persistent caching for book loading and pagination in desktop app * Implement persistent caching for book loading and pagination in desktop app * Optimize reader settings updates by separating layout and appearance changes in desktop app * Improve desktop window branding and native Windows styling * Enhance reader selection interactions and UI across EPUB and PDF viewers in desktop app * Refine selection handle positioning and interaction logic * Implement EPUB selection debug logging and improve handle targeting * Optimize desktop book loading performance and UI responsiveness * Implement anchored zoom gestures and rendering optimizations for the Desktop PDF viewer. * Implement smooth zoom preview for the PDF reader in desktop app * Optimize PDF rendering performance and responsiveness in the desktop reader * Implement conditional diagnostic logging and update desktop build configuration * Implemented hierarchical TOC, custom scrollbars, and improved desktop modal handling * Added management options for annotations and highlights in the sidebar in desktop app * Implemented `SharedStableOutlinedTextField` and updated text input fields to use `TextFieldValue` for improved cursor and selection stability. * Refined library filters and enhanced OPDS functionality in desktop app * Improved EPUB pagination measurement and implemented layout diagnostic logging for desktop app * Added PPTX support including document parsing, rendering, and indexing * Improved PPTX rendering and layout accuracy * Implemented text autofit support for PPTX rendering * Enhanced PPTX rendering with support for custom geometry, automatic numbering, table styles, and image opacity * Improved EPUB pagination accuracy and added layout telemetry in desktop app * Improved folder synchronization with metadata-only mode and hashed sidecar management in desktop app * Implemented rich text font scaling and migrated desktop ink tools to custom pointer input handling * Implemented billing account obfuscation * Implemented hierarchical folder navigation and improved library selection functionality in desktop app * Implemented platform-aware directory resolution and multi-platform native library support for desktop * Added full-screen mode for the reader workspace * Added PDF zoom indicator and interactive vertical scrollbar with page tooltips * Refactored speech bubble prefetching to use a limited radius and improved ML detector initialization and lifecycle management * Updated PDF indexing to replace existing page text and removed search result item keys * Implemented "preparing" foreground notification for TTS service * Optimized PDF rendering performance by pre-calculating page-specific annotations * Refactored desktop packaging tasks and improved distribution configuration * Optimized EPUB parser memory usage and added path traversal protection * Refactored WorkManager monitoring logic and added work pruning * Implemented comprehensive resource cleanup and memory management for WebView-based components to prevent memory leaks * Implemented bitmap size limits and scaling to prevent canvas rendering errors * Split long text paragraphs into multiple semantic blocks during HTML parsing * Implemented local ActionMode for text selection to prevent platform crashes * Refactored PPTX text layout, optimized HtmlParser block detection, and improved banner dismissal logic * Added desktop startup splash screen and deferred WebView initialization * Reorganized settings hub and added separate PDF reader defaults * Implemented embedded cover extraction and metadata support for MOBI and FB2 formats * Implemented batching for MetadataExtractionWorker and optimized EPUB metadata extraction performance. * Implemented procedurally generated book covers and replaced static placeholders * Redesigned search UI with a top bar and results overlay in desktop app * Added PDF page gap and overlay visibility options and implemented DesktopBookImporter * Refactored PDF reader UI with tabbed inspector and improved theme background handling in desktop * Implemented PDF viewport persistence for zoom and scroll positions in desktop app * Improved desktop fullscreen implementation and state restoration * Implemented desktop window state persistence * Implemented flavor-based branding and ProGuard configuration for desktop builds * Implemented precise reader positioning and improved highlight rendering logic in desktop app * Added support for user-editable book metadata * Enhanced book metadata support and integrated info/edit dialogs * Implemented embedded EPUB metadata editing * Improved highlight mapping and added custom scrollbar styling for the reader. * Reduced desktop WebView bundle size by excluding unused locales and runtime files * Added neutral pan mode as the default PDF interaction state. * Refactored library empty states and updated primary navigation tabs in desktop app * Implemented native paginated reader and unified content rendering architecture in desktop epub reader * Implemented native EPUB image rendering for desktop and improved block layout spacing with margin collapsing. * Improved pagination overflow detection in desktop * Implemented multi-block text selection with interactive handles and CFI support in desktop epub pagination
This commit is contained in:
parent
c0d0e57e79
commit
b20ade9946
247 changed files with 43321 additions and 7087 deletions
235
app/src/main/java/com/aryan/reader/AndroidSharedStateBridge.kt
Normal file
235
app/src/main/java/com/aryan/reader/AndroidSharedStateBridge.kt
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
package com.aryan.reader
|
||||
|
||||
import com.aryan.reader.data.RecentFileItem
|
||||
import com.aryan.reader.data.TagEntity
|
||||
import com.aryan.reader.shared.AppAction as SharedAppAction
|
||||
import com.aryan.reader.shared.LibraryAction as SharedLibraryAction
|
||||
import com.aryan.reader.shared.SharedFolderPathResolver
|
||||
import com.aryan.reader.shared.SharedLibraryProjectionInput
|
||||
import com.aryan.reader.shared.SharedLibraryStateProjector
|
||||
import com.aryan.reader.shared.SharedReaderScreenState
|
||||
import com.aryan.reader.shared.reduce
|
||||
|
||||
internal object AndroidSharedStateBridge {
|
||||
fun prepareLibraryProjection(
|
||||
input: LibraryProjectionInput,
|
||||
folderPathResolver: FolderPathResolver
|
||||
): AndroidSharedLibraryProjectionContext {
|
||||
val taggedBooks = input.recentFilesFromDb.withResolvedTags(input.dbTags, input.tagRefs)
|
||||
val androidBooksById = taggedBooks
|
||||
.filterNot { it.bookId.endsWith("_reflow") }
|
||||
.associateBy { it.bookId }
|
||||
val projectionState = input.state.withAndroidFolderFallbacks(androidBooksById.values)
|
||||
val sharedInput = SharedLibraryProjectionInput(
|
||||
state = projectionState.toSharedReaderScreenState(
|
||||
rawBooks = taggedBooks,
|
||||
dbTags = input.dbTags
|
||||
),
|
||||
booksFromStore = taggedBooks
|
||||
.filterNot { it.bookId.endsWith("_reflow") }
|
||||
.map { it.toSharedProjectionBookItem() },
|
||||
shelfRecords = input.dbShelves.map { it.toSharedShelfRecord() },
|
||||
shelfRefs = input.shelfRefs.map { it.toSharedBookShelfRef() },
|
||||
tags = input.dbTags.map { it.toSharedTag() }
|
||||
)
|
||||
return AndroidSharedLibraryProjectionContext(
|
||||
projectionState = projectionState,
|
||||
sharedInput = sharedInput,
|
||||
androidBooksById = androidBooksById,
|
||||
tagEntitiesById = input.dbTags.associateBy { it.id },
|
||||
folderKeys = projectionState.syncedFolders.map { AndroidSharedFolderProjectionKey(it.uriString, it.name) },
|
||||
folderPathResolver = SharedFolderPathResolver { item ->
|
||||
androidBooksById[item.id]?.let(folderPathResolver::relativeFolderSegments).orEmpty()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun projectLibrary(context: AndroidSharedLibraryProjectionContext): SharedReaderScreenState {
|
||||
return SharedLibraryStateProjector(context.folderPathResolver).project(context.sharedInput)
|
||||
}
|
||||
|
||||
fun toAndroidState(
|
||||
base: ReaderScreenState,
|
||||
sharedState: SharedReaderScreenState,
|
||||
androidBooksById: Map<String, RecentFileItem>,
|
||||
tagEntitiesById: Map<String, TagEntity>
|
||||
): ReaderScreenState {
|
||||
return sharedState.toAndroidReaderScreenState(
|
||||
base = base,
|
||||
androidBooksById = androidBooksById,
|
||||
tagEntitiesById = tagEntitiesById
|
||||
)
|
||||
}
|
||||
|
||||
fun reduceLibraryAction(
|
||||
current: ReaderScreenState,
|
||||
projectedState: ReaderScreenState,
|
||||
action: SharedLibraryAction
|
||||
): ReaderScreenState {
|
||||
val rawBooks = projectedState.rawLibraryFiles.ifEmpty { current.rawLibraryFiles }
|
||||
val androidBooksById = (rawBooks + current.contextualActionItems).associateBy { it.bookId }
|
||||
val reduced = current.toBridgeSharedState(projectedState).reduce(action)
|
||||
return current.copy(
|
||||
searchQuery = reduced.searchQuery,
|
||||
sortOrder = reduced.sortOrder.toAndroidSortOrder(),
|
||||
libraryFilters = reduced.libraryFilters.toAndroidLibraryFilters(),
|
||||
contextualActionItems = reduced.selectedBookIds.mapNotNullTo(mutableSetOf()) { androidBooksById[it] },
|
||||
contextualActionShelfIds = reduced.selectedShelfIds,
|
||||
libraryScreenStartPage = reduced.libraryScreenStartPage,
|
||||
recentFilesLimit = reduced.recentFilesLimit
|
||||
)
|
||||
}
|
||||
|
||||
fun reduceAppAction(
|
||||
current: ReaderScreenState,
|
||||
projectedState: ReaderScreenState,
|
||||
action: SharedAppAction
|
||||
): ReaderScreenState {
|
||||
val reduced = current.toBridgeSharedState(projectedState).reduce(action)
|
||||
return current.copy(
|
||||
appThemeMode = reduced.appThemeMode.toAndroidAppThemeMode(),
|
||||
appContrastOption = reduced.appContrastOption.toAndroidAppContrastOption(),
|
||||
appTextDimFactorLight = reduced.appTextDimFactorLight,
|
||||
appTextDimFactorDark = reduced.appTextDimFactorDark,
|
||||
appSeedColor = reduced.appSeedColor,
|
||||
customAppThemes = reduced.customAppThemes.map { it.toAndroidCustomAppTheme() }
|
||||
)
|
||||
}
|
||||
|
||||
fun setTabsEnabled(
|
||||
current: ReaderScreenState,
|
||||
projectedState: ReaderScreenState,
|
||||
enabled: Boolean
|
||||
): ReaderScreenState {
|
||||
val reduced = current.toBridgeSharedState(projectedState).reduce(SharedAppAction.TabsEnabledChanged(enabled))
|
||||
if (enabled) return current.withTabStateFrom(reduced)
|
||||
|
||||
val activeTab = current.activeTabBookId
|
||||
return current.copy(
|
||||
isTabsEnabled = reduced.isTabsEnabled,
|
||||
openTabIds = if (activeTab == null) emptyList() else listOf(activeTab),
|
||||
activeTabBookId = activeTab
|
||||
)
|
||||
}
|
||||
|
||||
fun openBookTab(
|
||||
current: ReaderScreenState,
|
||||
projectedState: ReaderScreenState,
|
||||
bookId: String
|
||||
): ReaderScreenState {
|
||||
val reduced = current.toBridgeSharedState(projectedState).reduce(SharedAppAction.BookTabOpened(bookId))
|
||||
return current.withTabStateFrom(reduced)
|
||||
}
|
||||
|
||||
fun closeBookTab(
|
||||
current: ReaderScreenState,
|
||||
projectedState: ReaderScreenState,
|
||||
bookId: String
|
||||
): ReaderScreenState {
|
||||
val reduced = current.toBridgeSharedState(projectedState).reduce(SharedAppAction.BookTabClosed(bookId))
|
||||
return current.withTabStateFrom(reduced)
|
||||
}
|
||||
|
||||
fun closeAllTabs(current: ReaderScreenState, projectedState: ReaderScreenState): ReaderScreenState {
|
||||
val reduced = current.toBridgeSharedState(projectedState).reduce(SharedAppAction.AllTabsClosed)
|
||||
return current.withTabStateFrom(reduced)
|
||||
}
|
||||
|
||||
fun togglePinsForSelectedBooks(
|
||||
current: ReaderScreenState,
|
||||
projectedState: ReaderScreenState,
|
||||
isHome: Boolean
|
||||
): ReaderScreenState {
|
||||
val selectedIds = current.contextualActionItems.mapTo(linkedSetOf()) { it.bookId }
|
||||
if (selectedIds.isEmpty()) return current
|
||||
|
||||
val currentPins = if (isHome) current.pinnedHomeBookIds else current.pinnedLibraryBookIds
|
||||
val idsToToggle = if (selectedIds.all { it in currentPins }) {
|
||||
selectedIds
|
||||
} else {
|
||||
selectedIds - currentPins
|
||||
}
|
||||
val reduced = idsToToggle.fold(current.toBridgeSharedState(projectedState)) { state, bookId ->
|
||||
state.reduce(
|
||||
if (isHome) {
|
||||
SharedAppAction.HomePinToggled(bookId)
|
||||
} else {
|
||||
SharedAppAction.LibraryPinToggled(bookId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return if (isHome) {
|
||||
current.copy(
|
||||
pinnedHomeBookIds = reduced.pinnedHomeBookIds,
|
||||
contextualActionItems = emptySet()
|
||||
)
|
||||
} else {
|
||||
current.copy(
|
||||
pinnedLibraryBookIds = reduced.pinnedLibraryBookIds,
|
||||
contextualActionItems = emptySet()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun replaceBookSelectionWithVisibleBooks(
|
||||
current: ReaderScreenState,
|
||||
projectedState: ReaderScreenState,
|
||||
visibleBooks: Collection<RecentFileItem>
|
||||
): ReaderScreenState {
|
||||
val visibleIds = visibleBooks.mapTo(linkedSetOf()) { it.bookId }
|
||||
val selectedIds = current.contextualActionItems.mapTo(linkedSetOf()) { it.bookId }
|
||||
val action = if (visibleIds.isNotEmpty() && selectedIds.containsAll(visibleIds)) {
|
||||
SharedLibraryAction.SelectionCleared
|
||||
} else {
|
||||
SharedLibraryAction.BookSelectionReplaced(visibleIds)
|
||||
}
|
||||
return reduceLibraryAction(
|
||||
current = current,
|
||||
projectedState = projectedState,
|
||||
action = action
|
||||
)
|
||||
}
|
||||
|
||||
private fun ReaderScreenState.toBridgeSharedState(projectedState: ReaderScreenState): SharedReaderScreenState {
|
||||
return toSharedReaderScreenState(
|
||||
rawBooks = projectedState.rawLibraryFiles.ifEmpty { rawLibraryFiles },
|
||||
dbTags = projectedState.allTags.ifEmpty { allTags }
|
||||
)
|
||||
}
|
||||
|
||||
private fun ReaderScreenState.withTabStateFrom(sharedState: SharedReaderScreenState): ReaderScreenState {
|
||||
return copy(
|
||||
isTabsEnabled = sharedState.isTabsEnabled,
|
||||
openTabIds = sharedState.openTabIds,
|
||||
activeTabBookId = sharedState.activeTabBookId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class AndroidSharedLibraryProjectionContext(
|
||||
val projectionState: ReaderScreenState,
|
||||
val sharedInput: SharedLibraryProjectionInput,
|
||||
val androidBooksById: Map<String, RecentFileItem>,
|
||||
val tagEntitiesById: Map<String, TagEntity>,
|
||||
val folderKeys: List<AndroidSharedFolderProjectionKey>,
|
||||
val folderPathResolver: SharedFolderPathResolver
|
||||
)
|
||||
|
||||
internal data class AndroidSharedFolderProjectionKey(
|
||||
val uriString: String,
|
||||
val name: String
|
||||
)
|
||||
|
||||
private fun ReaderScreenState.withAndroidFolderFallbacks(books: Collection<RecentFileItem>): ReaderScreenState {
|
||||
val knownFolders = syncedFolders.mapTo(mutableSetOf()) { it.uriString }
|
||||
val missingFolders = books
|
||||
.mapNotNull { it.sourceFolderUri }
|
||||
.filterTo(linkedSetOf()) { it !in knownFolders }
|
||||
.map { uri -> SyncedFolder(uriString = uri, name = "Local Folder", lastScanTime = 0L) }
|
||||
return if (missingFolders.isEmpty()) {
|
||||
this
|
||||
} else {
|
||||
copy(syncedFolders = syncedFolders + missingFolders)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue