Epub improvements (#160)
* Improved navigation and search result highlighting in the EPUB reader * refactor: implement JIT chunk restoration for robust navigation - Fixes search and bookmark navigation failing in same-chapter transitions. - Introduced JIT (Just-in-Time) HTML restoration in `epub_reader.js` to re-populate virtualized chunks before CFI resolution or search scrolling. - Synchronized search highlighting and user highlights within newly-restored chunk segments. - Improved search navigation accuracy by mapping occurrences to relative chunk indices. * Implemented a custom text selection engine for the paginated EPUB reader to support cross-page selection and improved handle interaction. * Implemented management of external file behavior and improved library filtering. * Added support for toolbar customization in the EPUB reader. * Added support for toolbar customization in the PDF reader. * fix: rendering during auto-scroll and navigation on long pages * Added a "Scroll to Top" feature to the auto-scroll controls in both EPUB and PDF readers.
This commit is contained in:
parent
db7e05ce63
commit
6a60aec0ef
17 changed files with 3045 additions and 2060 deletions
|
|
@ -123,7 +123,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -1104,12 +1104,12 @@ internal fun PdfPageComposable(
|
|||
try {
|
||||
page = withContext(Dispatchers.IO) { pdfDocumentItem.openPage(pdfPageIndex) }
|
||||
|
||||
snapshotFlow { visibleScreenRect() }.collectLatest { currentVisibleRect ->
|
||||
snapshotFlow { visibleScreenRect() }.conflate().collect { currentVisibleRect ->
|
||||
val tileCalcStart = System.nanoTime()
|
||||
if (!isActive) return@collectLatest
|
||||
if (!isActive) return@collect
|
||||
|
||||
if (isScrolling && effectiveScale > 1f) {
|
||||
return@collectLatest
|
||||
return@collect
|
||||
}
|
||||
|
||||
val pxTl: Float
|
||||
|
|
@ -1131,7 +1131,7 @@ internal fun PdfPageComposable(
|
|||
oldTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
|
||||
}
|
||||
}
|
||||
return@collectLatest
|
||||
return@collect
|
||||
}
|
||||
} else {
|
||||
val pivotX = screenWidth / 2f
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ class VerticalPdfReaderState {
|
|||
internal var scrollToPageHandler: (suspend (Int) -> Unit)? = null
|
||||
internal var snapToPageHandler: (suspend (Int) -> Unit)? = null
|
||||
internal var scrollByHandler: (suspend (Float) -> Unit)? = null
|
||||
internal var scrollToTopHandler: (suspend () -> Unit)? = null
|
||||
internal var scrollToBottomHandler: (suspend () -> Unit)? = null
|
||||
|
||||
suspend fun scrollToPage(pageIndex: Int) {
|
||||
scrollToPageHandler?.invoke(pageIndex)
|
||||
|
|
@ -151,6 +153,14 @@ class VerticalPdfReaderState {
|
|||
suspend fun scrollBy(delta: Float) {
|
||||
scrollByHandler?.invoke(delta)
|
||||
}
|
||||
|
||||
suspend fun scrollToTop() {
|
||||
scrollToTopHandler?.invoke()
|
||||
}
|
||||
|
||||
suspend fun scrollToBottom() {
|
||||
scrollToBottomHandler?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -244,6 +254,8 @@ internal fun PdfVerticalReader(
|
|||
state.scrollToPageHandler = null
|
||||
state.snapToPageHandler = null
|
||||
state.scrollByHandler = null
|
||||
state.scrollToTopHandler = null
|
||||
state.scrollToBottomHandler = null
|
||||
}
|
||||
}
|
||||
var globalEraserPosition by remember { mutableStateOf<Offset?>(null) }
|
||||
|
|
@ -527,6 +539,23 @@ internal fun PdfVerticalReader(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
state.scrollToTopHandler = {
|
||||
panYAnimatable.animateTo(
|
||||
targetValue = headerHeightPx,
|
||||
animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
|
||||
)
|
||||
}
|
||||
|
||||
state.scrollToBottomHandler = {
|
||||
val currentZoom = zoomAnimatable.value
|
||||
val zoomedDocHeight = totalDocHeight * currentZoom
|
||||
val minPanY = (screenHeight - footerHeightPx - zoomedDocHeight).coerceAtMost(headerHeightPx)
|
||||
panYAnimatable.animateTo(
|
||||
targetValue = minPanY,
|
||||
animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var selectionClearTrigger by remember { mutableLongStateOf(0L) }
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -420,7 +420,7 @@ class PdfRichTextRepository(private val context: Context) {
|
|||
isItalic = sObj.optBoolean("i"),
|
||||
isUnderline = sObj.optBoolean("u"),
|
||||
isStrikethrough = sObj.optBoolean("st"),
|
||||
fontPath = sObj.optString("fp", null),
|
||||
fontPath = sObj.optString("fp"),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,8 +200,8 @@ object TextBoxSerializer {
|
|||
isItalic = obj.optBoolean("isItalic", false),
|
||||
isUnderline = obj.optBoolean("isUnderline", false),
|
||||
isStrikeThrough = obj.optBoolean("isStrikeThrough", false),
|
||||
fontPath = obj.optString("fontPath", null).takeIf { !it.isNullOrBlank() },
|
||||
fontName = obj.optString("fontName", null).takeIf { !it.isNullOrBlank() }
|
||||
fontPath = obj.optString("fontPath").takeIf { !it.isNullOrBlank() },
|
||||
fontName = obj.optString("fontName").takeIf { !it.isNullOrBlank() }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ object HighlightSerializer {
|
|||
color = try { PdfHighlightColor.valueOf(obj.getString("color")) } catch(_: Exception) { PdfHighlightColor.YELLOW },
|
||||
text = obj.optString("text", ""),
|
||||
range = Pair(obj.optInt("rangeStart", 0), obj.optInt("rangeEnd", 0)),
|
||||
note = obj.optString("note", null).takeIf { !it.isNullOrBlank() }
|
||||
note = obj.optString("note").takeIf { !it.isNullOrBlank() }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue