book-reader/app/src/main/java/com/aryan/reader/AndroidSharedStateBridge.kt
Aryan 83dcafa4b6
Windows ga (#358)
* Enhance Cloud TTS with navigation controls and shared UI overlay in desktop app

* Refactor Cloud TTS voice settings and remove standalone settings overlay on desktop app

* Persist reader window state and improve slider interaction in desktop app

* Improve PDF page transitions and refine focus management in desktop app

* Refactor scrollbar interaction and adjust desktop modal focus handling

* Improve PDF sidecar synchronization and cross-platform metadata compatibility

* Refactor PDF annotation comment logic to shared module and implement Desktop UI

* Refactor reader screen to use tap-to-toggle and full-width styling in desktop app

* Refactor reader workspace layout and chrome-panel interactions

* Implement global search keyboard shortcuts and focusable chrome layers

* Implement flavor-specific legal links and update the About UI

* Refactor reader UI controls on desktop app

* Enhance desktop folder sync with background metadata extraction and improved error handling

* Refactor Library UI and remove redundant Home tab in desktop app

* Add custom tooltips to reader icon buttons in desktop app

* Integrate app theme controls into reader interfaces on desktop

* Add right-to-left pagination support and improve focus restoration on desktop app

* Improve EPUB pagination geometry and diagnostic logging for layout cutoffs on desktop app

* Update desktop reader defaults and implement settings migration

* Implement block-based position tracking in ReaderLocator

* Enhance EPUB highlighting reliability in desktop app

* Add support for custom reader themes and update highlight palette logic in desktop app

* Replace the Tools panel with a "More" dropdown menu and refactor account UI

* Implement account profile header in desktop sidebar

* Implement cloud sync reliability improvements and sidebar toggle on desktop app

* Improve EPUB annotation synchronization and highlight mapping accuracy in desktop app

* Integrate WebView2 for EPUB vertical rendering on Windows

* Refactor reader layout logic and enhance WebView2 diagnostics

* Improve vertical reading layout and WebView2 resizing on Desktop

* Refine vertical reading mode layout and margin handling

* Enhance reader locator precision and Desktop mode-switching reliability

* Implement chapter-level caching and warm-start pagination in desktop app

* Replace bundled KCEF with native system webviews via SWT

* Refactor EPUB page info bar visibility and layout logic

* Improve PDF toolbar persistence and fix tab reactivation logic

* Enable multi-selection and bulk operations for custom fonts

* Refactor instrumentation tests

* Add EPUB UI test fixture and initial instrumentation tests

* Expand EpubReader UI tests and improve accessibility

* Add instrumentation tests and test tags for library and reader screens

* Enhance OPDS parser logic and catalog integration

* Add support for toggling local synchronization on a per-folder basis.

* Implement tri-state sizing for the TTS overlay

* Persist TTS overlay size across sessions

* Refactor reader brightness control and add incremental step buttons

* Improve CSS support, pagination control, and style-aware semantic caching

* Improve link handling, interaction, and diagnostics in the paginated reader

* crash fixes

* Implement persistent pending removal for external files

* Implement book-specific word replacements

* Add native vertical reading mode with custom renderer

* Implement text selection and navigation improvements for the native vertical reader

* Implement locator-based navigation and improved vertical scrolling in native vertical mode in epub

* Implement lazy loading and chapter prefetching for native vertical reader

* Improve window lifecycle and disposal handling on Desktop

* Optimize vertical reading performance in desktop app

* Enhance TTS start accuracy and diagnostic logging on desktop

* Refactor AI settings visibility on desktop

* Improve pagination height measurement and enhance cutoff diagnostics

* Implement lifecycle management and improve justified text splitting for pagination

* Refine AI usage tracking and force AI feature visibility on Desktop

* Add descriptive context comments and usage examples to string and plural resources.

* Optimize performance and memory usage in search and state mapping

* Replace reader page sliders with minimal slider and navigation controls

* Add support for CBT comic archives

* Harden file path validation and XML parsing to prevent security vulnerabilities

* Implement local account profile caching and optimize desktop performance

* Improve desktop persistence reliability and add Linux secure storage support

* Improved PDF zoom stability and layout prediction during zoom commits

* Improved PDF spread layout prediction, reader focus restoration, and account profile caching

* Enhance highlight precision and scoping using block-local offsets and CFIs

* Enhance cloud book content synchronization and background downloads

* Implement granular timestamp tracking for reading positions and PDF annotations

* Restrict diagnostic logging and stack traces to debug builds

* Refine PDF page gaps and reader chrome interaction logic

* Refactor PDF highlight rendering and overhaul Desktop sidebar UI

* Implement a new interaction dock and undo/redo history for PDF annotations in desktop

* Enhance PDF color picker and improve navigation scroll restoration

* Add highlight palette customization and improve selection menu UI in desktop app epub reader

* Enhance desktop shelf management and library organization
2026-06-02 00:51:42 +05:30

238 lines
9.6 KiB
Kotlin

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,
includeReaderAnnotations = false
),
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,
libraryFilters = reduced.libraryFilters,
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,
appContrastOption = reduced.appContrastOption,
appTextDimFactorLight = reduced.appTextDimFactorLight,
appTextDimFactorDark = reduced.appTextDimFactorDark,
appSeedColor = reduced.appSeedColor,
appFontPreference = reduced.appFontPreference,
customAppThemes = reduced.customAppThemes
)
}
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 },
includeReaderAnnotations = false
)
}
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)
}
}