Pdf text highlight (#49)
* feat(pdf): implement native text highlighting and interactive selection menu - Add `PdfUserHighlight` data model to track page index, character ranges, and normalized coordinates. - Redesign `PdfSelectionMenuPopup` to match the EPUB reader style, featuring a color palette row and a deletion option. - Integrate highlight rendering into the `PdfPageSelectionsLayer` using Canvas drawing. - Add hit-testing to `detectTapGestures` to allow users to tap existing highlights to change colors or remove them. * Implemented PDF highlight persistence, sync, and exporting. * Improved PDF highlight exporting and visibility logic - Fixed PDF highlight rendering by correctly calculating coordinates and merging adjacent rectangles into lines. - Updated `PdfExporter` to include highlights in the exported document and added debug logging. - Corrected visibility check in `PdfPageComposable` to account for vertical offsets. - Enhanced `PdfViewerScreen` highlight creation to use merged line rectangles * Refactored annotation synchronization logic and improved background sync reliability. - Refactored JSON bundle creation and extraction into reusable helper functions. - Improved stale file detection by checking all annotation types (ink, text, layout, highlights, text boxes). - Added background annotation downloading to `SyncWorker` - Implemented duplicate file cleanup in `GoogleDriveRepository` during uploads. - Added a `showFeedback` flag to `syncFolderMetadata` to control UI visibility during background operations. - Fixed a potential data loss edge case by aborting Firestore sync if the bundle upload fails. * Improved highlight interaction and management in PDF viewer - Implemented hit tolerance for highlight selection to improve touch detection accuracy. - Added logic to shift highlight page indices when adding or deleting pages. - Included highlights in the auto-pruning check for empty pages. - Added a "Highlights" tab to the navigation drawer to list, navigate to, and delete user highlights.
This commit is contained in:
parent
8c7c8cb48e
commit
ef1bfdc57a
11 changed files with 921 additions and 163 deletions
|
|
@ -52,6 +52,7 @@ class RecentFilesRepository(private val context: Context) {
|
|||
private val pdfRichTextRepository = PdfRichTextRepository(context)
|
||||
private val pageLayoutRepository = PageLayoutRepository(context)
|
||||
private val pdfTextBoxRepository = PdfTextBoxRepository(context)
|
||||
private val pdfHighlightRepository = com.aryan.reader.pdf.data.PdfHighlightRepository(context)
|
||||
|
||||
init {
|
||||
if (!coverCacheDir.exists()) {
|
||||
|
|
@ -87,6 +88,7 @@ class RecentFilesRepository(private val context: Context) {
|
|||
coverCacheDir.deleteRecursively()
|
||||
}
|
||||
coverCacheDir.mkdirs()
|
||||
pdfHighlightRepository.clearAll()
|
||||
Timber.d("Cleared all local book data and cover cache.")
|
||||
}
|
||||
|
||||
|
|
@ -182,15 +184,17 @@ class RecentFilesRepository(private val context: Context) {
|
|||
val richTextFile = pdfRichTextRepository.getFileForSync(bookId)
|
||||
val layoutFile = pageLayoutRepository.getLayoutFile(bookId)
|
||||
val textBoxFile = pdfTextBoxRepository.getFileForSync(bookId)
|
||||
val highlightFile = pdfHighlightRepository.getFileForSync(bookId)
|
||||
|
||||
val hasInk = inkFile?.exists() == true
|
||||
val hasRichText = richTextFile.exists()
|
||||
val hasLayout = layoutFile.exists()
|
||||
val hasTextBoxes = textBoxFile.exists()
|
||||
val hasHighlights = highlightFile.exists()
|
||||
|
||||
Timber.tag("FolderAnnotationSync").d("File checks -> hasInk: $hasInk, hasRichText: $hasRichText, hasLayout: $hasLayout, hasTextBoxes: $hasTextBoxes")
|
||||
Timber.tag("FolderAnnotationSync").d("File checks -> hasInk: $hasInk, hasRichText: $hasRichText, hasLayout: $hasLayout, hasTextBoxes: $hasTextBoxes, hasHighlights: $hasHighlights")
|
||||
|
||||
if (!hasInk && !hasRichText && !hasLayout && !hasTextBoxes) {
|
||||
if (!hasInk && !hasRichText && !hasLayout && !hasTextBoxes && !hasHighlights) {
|
||||
Timber.tag("FolderAnnotationSync").d("No annotations found locally for bookId: $bookId. Aborting sync.")
|
||||
return@withContext
|
||||
}
|
||||
|
|
@ -214,13 +218,15 @@ class RecentFilesRepository(private val context: Context) {
|
|||
if (hasRichText) putJsonSafe("text", richTextFile)
|
||||
if (hasLayout) putJsonSafe("layout", layoutFile)
|
||||
if (hasTextBoxes) putJsonSafe("textBoxes", textBoxFile)
|
||||
if (hasHighlights) putJsonSafe("highlights", highlightFile)
|
||||
|
||||
val tsInk = if(hasInk) inkFile.lastModified() else 0L
|
||||
val tsText = if(hasRichText) richTextFile.lastModified() else 0L
|
||||
val tsLayout = if(hasLayout) layoutFile.lastModified() else 0L
|
||||
val tsBox = if(hasTextBoxes) textBoxFile.lastModified() else 0L
|
||||
val tsHighlight = if(hasHighlights) highlightFile.lastModified() else 0L
|
||||
|
||||
val maxFileTs = maxOf(tsInk, tsText, tsLayout, tsBox)
|
||||
val maxFileTs = maxOf(tsInk, tsText, tsLayout, tsBox, tsHighlight)
|
||||
val finalTs = maxOf(maxFileTs, System.currentTimeMillis())
|
||||
|
||||
Timber.tag("FolderAnnotationSync").d("Pushing annotation bundle for $bookId to folder. finalTs=$finalTs")
|
||||
|
|
@ -263,6 +269,9 @@ class RecentFilesRepository(private val context: Context) {
|
|||
// 4. Text Boxes
|
||||
writeSafe("textBoxes", pdfTextBoxRepository.getFileForSync(bookId))
|
||||
|
||||
// 5. Highlights
|
||||
writeSafe("highlights", pdfHighlightRepository.getFileForSync(bookId))
|
||||
|
||||
Timber.tag("FolderAnnotationSync").i("Successfully imported annotation bundle for $bookId from folder.")
|
||||
} catch (e: Exception) {
|
||||
Timber.tag("FolderAnnotationSync").e(e, "Failed to import annotation bundle for $bookId")
|
||||
|
|
@ -294,10 +303,6 @@ class RecentFilesRepository(private val context: Context) {
|
|||
return@withContext recentFileDao.getFolderBooksWithoutCovers().map { it.toRecentFileItem() }
|
||||
}
|
||||
|
||||
suspend fun updateReflowPreference(bookId: String, isPreferred: Boolean) = withContext(Dispatchers.IO) {
|
||||
recentFileDao.updateReflowPreference(bookId, isPreferred)
|
||||
}
|
||||
|
||||
suspend fun detachAllFolderBooks() = withContext(Dispatchers.IO) {
|
||||
recentFileDao.detachAllFolderBooks()
|
||||
Timber.d("Detached all folder books. They are now standard local files.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue