* Centralize library management logic and introduce support for plain text and HTML formats * Centralize library management logic and introduce support for plain text and HTML formats * Expand unit test coverage for library state management, UI models, and MainViewModel features. * Add comprehensive unit tests for PDF reader core logic, preferences, and data persistence * Add unit tests for EPUB parsing, content loading, search functionality, and reader JavaScript bridges. * Add unit tests for OPDS parsing and Smart Collection engine, and integrate Kover plugin * Add comprehensive unit tests * Centralize library snapshot serialization in the `shared` module and improve filtering and sorting logic. * Implement text selection, highlighting, and reading state persistence for PDF and EPUB engines in desktop version * Folder import support for desktop app * Introduce Smart Shelves with rule-based filtering in desktop version * Implement shared EPUB annotation serialization and highlight rendering * Centralize file type capabilities and platform-specific support logic * Refactor reader state management to use a central reducer * Implement customizable reader toolbar and advanced formatting settings in shared * Implement locator-based navigation and customizable highlight palette for desktop app * Enhance reader customization and expand search functionality in desktop app * Redesign reader settings and tools into a tabbed control panel in desktop app * Enhance reader navigation and highlight precision in desktop app * Implement bidirectional position synchronization and dynamic highlights in the desktop reader * Implement shared state management and enhanced search for the PDF reader in desktop app * Add vertical scroll support to the desktop PDF reader * Implement ink, text, and eraser annotation support in desktop PDF viewer * Implement PDF bookmarks, Table of Contents, and annotation editing in desktop app * Implement link handling and navigation for PDF and EPUB readers in desktop app * Implement PDF jump history for navigation in desktop app * Enhance PDF ink rendering and annotation capabilities in desktop app * Implement advanced PDF text annotations with inline editing and rich styling in desktop app * Add move handle and movement logic for PDF text annotations in desktop app * Implement local folder synchronization and metadata sidecar support in desktop app * Implement book metadata extraction and drag-and-drop import for Desktop * Implement dynamic and custom app theme management for desktop * Introduce canonical PDF annotation codec and support for multi-segment highlights * Implement rich text editing and pagination support for the PDF reader in desktop app * Improve PDF rich text pagination, synchronization, and observability in desktop * Hide trailing structural page breaks in rich text editor * Implement a unified JVM book loader and expand supported formats on Desktop * Add comic archive support for Desktop and enhance MOBI parsing * Implement shared OPDS catalog support and UI for Android and Desktop * Improve native WebView lifecycle and surface transition management on Desktop * Enable Compose Swing interop blending and simplify Desktop WebView management * Integrate BYOK AI features and Cloud TTS for desktop * Enhance Desktop TTS with streaming audio and improved secure storage for AI key * Implement scoped Cloud TTS with synchronized highlighting for EPUB and PDF in desktop app * Implement custom font management and utility screens in desktop app * Implement PDFium-based PDF annotation export * Remove PdfBox dependency and standardize PDF export via Pdfium * Implement local audio caching and playback controls for Gemini Cloud TTS in desktop app * Implement reader themes and custom texture support in desktop app * Redesign non-reader UI with responsive navigation and enhanced library management in desktop app * Introduce ReaderWorkspaceShell to unify EPUB and PDF reader layouts in desktop app * Exclude manual-only files from automated sync and import * Implement customizable Text-to-Speech (TTS) word replacements * Optimize reader performance with persistent layout caching and decoupled theme rendering * Improve position restoration during reader reconfiguration in epub pagination * Use independent thickness for eraser tool and stylus override
244 lines
8.9 KiB
Kotlin
244 lines
8.9 KiB
Kotlin
package com.aryan.reader
|
|
|
|
import com.aryan.reader.data.BookShelfCrossRef
|
|
import com.aryan.reader.data.BookTagCrossRef
|
|
import com.aryan.reader.data.RecentFileItem
|
|
import com.aryan.reader.data.ShelfEntity
|
|
import com.aryan.reader.data.TagEntity
|
|
import com.aryan.reader.shared.AddBooksSource as SharedAddBooksSource
|
|
import com.aryan.reader.shared.AppContrastOption as SharedAppContrastOption
|
|
import com.aryan.reader.shared.AppThemeMode as SharedAppThemeMode
|
|
import com.aryan.reader.shared.BannerMessage as SharedBannerMessage
|
|
import com.aryan.reader.shared.BookItem as SharedBookItem
|
|
import com.aryan.reader.shared.BookShelfRef as SharedBookShelfRef
|
|
import com.aryan.reader.shared.CustomAppTheme as SharedCustomAppTheme
|
|
import com.aryan.reader.shared.EpubAnnotationSerializer
|
|
import com.aryan.reader.shared.FileType as SharedFileType
|
|
import com.aryan.reader.shared.LibraryFilters as SharedLibraryFilters
|
|
import com.aryan.reader.shared.ReadStatusFilter as SharedReadStatusFilter
|
|
import com.aryan.reader.shared.RenderMode as SharedRenderMode
|
|
import com.aryan.reader.shared.SharedLibraryProjectionInput
|
|
import com.aryan.reader.shared.SharedReaderScreenState
|
|
import com.aryan.reader.shared.ShelfRecord
|
|
import com.aryan.reader.shared.SortOrder as SharedSortOrder
|
|
import com.aryan.reader.shared.SyncedFolder as SharedSyncedFolder
|
|
import com.aryan.reader.shared.Tag as SharedTag
|
|
|
|
fun RecentFileItem.toSharedBookItem(): SharedBookItem {
|
|
return SharedBookItem(
|
|
id = bookId,
|
|
path = uriString,
|
|
type = type.toSharedFileType(),
|
|
displayName = customName ?: displayName,
|
|
timestamp = timestamp,
|
|
coverImagePath = coverImagePath,
|
|
title = title,
|
|
author = author,
|
|
progressPercentage = progressPercentage,
|
|
isRecent = isRecent,
|
|
fileSize = fileSize,
|
|
sourceFolder = sourceFolderUri,
|
|
folderTextMetadataParsed = folderTextMetadataParsed,
|
|
seriesName = seriesName,
|
|
seriesIndex = seriesIndex,
|
|
tags = tags.map { it.toSharedTag() },
|
|
readerHighlights = EpubAnnotationSerializer.parseHighlightsJson(highlightsJson)
|
|
)
|
|
}
|
|
|
|
fun TagEntity.toSharedTag(): SharedTag {
|
|
return SharedTag(
|
|
id = id,
|
|
name = name,
|
|
color = color
|
|
)
|
|
}
|
|
|
|
fun SharedTag.toTagEntity(createdAt: Long): TagEntity {
|
|
return TagEntity(
|
|
id = id,
|
|
name = name,
|
|
color = color,
|
|
createdAt = createdAt
|
|
)
|
|
}
|
|
|
|
fun ShelfEntity.toSharedShelfRecord(): ShelfRecord {
|
|
return ShelfRecord(
|
|
id = id,
|
|
name = name,
|
|
isSmart = isSmart,
|
|
smartRulesJson = smartRulesJson
|
|
)
|
|
}
|
|
|
|
fun ShelfRecord.toShelfEntity(createdAt: Long, updatedAt: Long = createdAt): ShelfEntity {
|
|
return ShelfEntity(
|
|
id = id,
|
|
name = name,
|
|
isSmart = isSmart,
|
|
smartRulesJson = smartRulesJson,
|
|
createdAt = createdAt,
|
|
updatedAt = updatedAt
|
|
)
|
|
}
|
|
|
|
fun BookShelfCrossRef.toSharedBookShelfRef(): SharedBookShelfRef {
|
|
return SharedBookShelfRef(
|
|
bookId = bookId,
|
|
shelfId = shelfId,
|
|
addedAt = addedAt
|
|
)
|
|
}
|
|
|
|
fun ReaderScreenState.toSharedReaderScreenState(
|
|
rawBooks: List<RecentFileItem> = rawLibraryFiles,
|
|
dbTags: List<TagEntity> = allTags
|
|
): SharedReaderScreenState {
|
|
return SharedReaderScreenState(
|
|
selectedBookId = selectedBookId,
|
|
selectedUriString = selectedPdfUri?.toString() ?: selectedEpubUri?.toString(),
|
|
selectedFileType = selectedFileType?.toSharedFileType(),
|
|
isLoading = isLoading,
|
|
errorMessage = errorMessage,
|
|
renderMode = renderMode.toSharedRenderMode(),
|
|
sortOrder = sortOrder.toSharedSortOrder(),
|
|
viewingShelfId = viewingShelfId,
|
|
isAddingBooksToShelf = isAddingBooksToShelf,
|
|
showCreateShelfDialog = showCreateShelfDialog,
|
|
mainScreenStartPage = mainScreenStartPage,
|
|
libraryScreenStartPage = libraryScreenStartPage,
|
|
showRenameShelfDialogFor = showRenameShelfDialogFor,
|
|
showDeleteShelfDialogFor = showDeleteShelfDialogFor,
|
|
addBooksSource = addBooksSource.toSharedAddBooksSource(),
|
|
booksSelectedForAdding = booksSelectedForAdding,
|
|
selectedBookIds = contextualActionItems.mapTo(mutableSetOf()) { it.bookId },
|
|
selectedShelfIds = contextualActionShelfIds,
|
|
isProUser = isProUser,
|
|
credits = credits,
|
|
isSyncEnabled = isSyncEnabled,
|
|
isFolderSyncEnabled = isFolderSyncEnabled,
|
|
bannerMessage = bannerMessage?.toSharedBannerMessage(),
|
|
downloadingBookIds = downloadingBookIds,
|
|
uploadingBookIds = uploadingBookIds,
|
|
syncedFolders = syncedFolders.map { it.toSharedSyncedFolder() },
|
|
lastFolderScanTime = lastFolderScanTime,
|
|
hasUnreadFeedback = hasUnreadFeedback,
|
|
searchQuery = searchQuery,
|
|
isSearchActive = isSearchActive,
|
|
isRefreshing = isRefreshing,
|
|
reflowProgress = reflowProgress,
|
|
recentBooks = recentFiles.map { it.toSharedBookItem() },
|
|
libraryBooks = allRecentFiles.map { it.toSharedBookItem() },
|
|
rawLibraryBooks = rawBooks.map { it.toSharedBookItem() },
|
|
pinnedHomeBookIds = pinnedHomeBookIds,
|
|
pinnedLibraryBookIds = pinnedLibraryBookIds,
|
|
libraryFilters = libraryFilters.toSharedLibraryFilters(),
|
|
recentFilesLimit = recentFilesLimit,
|
|
isTabsEnabled = isTabsEnabled,
|
|
openTabIds = openTabIds,
|
|
openTabs = openTabs.map { it.toSharedBookItem() },
|
|
activeTabBookId = activeTabBookId,
|
|
showExternalFileSavePromptFor = showExternalFileSavePromptFor,
|
|
externalFileBehavior = externalFileBehavior,
|
|
useStrictFileFilter = useStrictFileFilter,
|
|
appThemeMode = appThemeMode.toSharedAppThemeMode(),
|
|
appContrastOption = appContrastOption.toSharedAppContrastOption(),
|
|
appTextDimFactorLight = appTextDimFactorLight,
|
|
appTextDimFactorDark = appTextDimFactorDark,
|
|
appSeedColor = appSeedColor,
|
|
customAppThemes = customAppThemes.map { it.toSharedCustomAppTheme() },
|
|
allTags = dbTags.map { it.toSharedTag() },
|
|
showTagSelectionDialogFor = showTagSelectionDialogFor
|
|
)
|
|
}
|
|
|
|
fun ReaderScreenState.toSharedLibraryProjectionInput(
|
|
recentFilesFromDb: List<RecentFileItem>,
|
|
dbShelves: List<ShelfEntity>,
|
|
shelfRefs: List<BookShelfCrossRef>,
|
|
dbTags: List<TagEntity>,
|
|
tagRefs: List<BookTagCrossRef>
|
|
): SharedLibraryProjectionInput {
|
|
val tagsById = dbTags.associateBy { it.id }
|
|
val bookTagsMap = tagRefs.groupBy { it.bookId }.mapValues { entry ->
|
|
entry.value.mapNotNull { tagsById[it.tagId] }
|
|
}
|
|
val taggedBooks = recentFilesFromDb.map { item ->
|
|
item.copy(tags = bookTagsMap[item.bookId].orEmpty())
|
|
}
|
|
return SharedLibraryProjectionInput(
|
|
state = toSharedReaderScreenState(
|
|
rawBooks = taggedBooks,
|
|
dbTags = dbTags
|
|
),
|
|
booksFromStore = taggedBooks
|
|
.filterNot { it.bookId.endsWith("_reflow") }
|
|
.map { it.toSharedBookItem() },
|
|
shelfRecords = dbShelves.map { it.toSharedShelfRecord() },
|
|
shelfRefs = shelfRefs.map { it.toSharedBookShelfRef() },
|
|
tags = dbTags.map { it.toSharedTag() }
|
|
)
|
|
}
|
|
|
|
fun FileType.toSharedFileType(): SharedFileType {
|
|
return runCatching { SharedFileType.valueOf(name) }.getOrDefault(SharedFileType.UNKNOWN)
|
|
}
|
|
|
|
private fun RenderMode.toSharedRenderMode(): SharedRenderMode {
|
|
return SharedRenderMode.valueOf(name)
|
|
}
|
|
|
|
private fun AddBooksSource.toSharedAddBooksSource(): SharedAddBooksSource {
|
|
return SharedAddBooksSource.valueOf(name)
|
|
}
|
|
|
|
private fun SortOrder.toSharedSortOrder(): SharedSortOrder {
|
|
return SharedSortOrder.valueOf(name)
|
|
}
|
|
|
|
private fun ReadStatusFilter.toSharedReadStatusFilter(): SharedReadStatusFilter {
|
|
return SharedReadStatusFilter.valueOf(name)
|
|
}
|
|
|
|
private fun LibraryFilters.toSharedLibraryFilters(): SharedLibraryFilters {
|
|
return SharedLibraryFilters(
|
|
fileTypes = fileTypes.mapTo(mutableSetOf()) { it.toSharedFileType() },
|
|
sourceFolders = sourceFolders,
|
|
readStatus = readStatus.toSharedReadStatusFilter(),
|
|
tagIds = tagIds
|
|
)
|
|
}
|
|
|
|
private fun SyncedFolder.toSharedSyncedFolder(): SharedSyncedFolder {
|
|
return SharedSyncedFolder(
|
|
uriString = uriString,
|
|
name = name,
|
|
lastScanTime = lastScanTime,
|
|
allowedFileTypes = allowedFileTypes.mapTo(mutableSetOf()) { it.toSharedFileType() }
|
|
)
|
|
}
|
|
|
|
private fun BannerMessage.toSharedBannerMessage(): SharedBannerMessage {
|
|
return SharedBannerMessage(
|
|
message = message,
|
|
isError = isError,
|
|
isPersistent = isPersistent
|
|
)
|
|
}
|
|
|
|
private fun AppThemeMode.toSharedAppThemeMode(): SharedAppThemeMode {
|
|
return SharedAppThemeMode.valueOf(name)
|
|
}
|
|
|
|
private fun AppContrastOption.toSharedAppContrastOption(): SharedAppContrastOption {
|
|
return SharedAppContrastOption.valueOf(name)
|
|
}
|
|
|
|
private fun CustomAppTheme.toSharedCustomAppTheme(): SharedCustomAppTheme {
|
|
return SharedCustomAppTheme(
|
|
id = id,
|
|
name = name,
|
|
seedColor = seedColor
|
|
)
|
|
}
|