v1.0.45-oss (#221)
* Implemented ML-based comic panel detection and a panel popup viewer. * Optimized `ComicPanelDetector` for performance and memory efficiency. * Optimized comic panel detection performance by introducing a dedicated single-thread dispatcher for ML tasks, replacing mutex-based synchronization. * Refactored system UI handling and layout padding in `PdfViewerScreen`. * Refactor `PdfViewerScreen.kt` by extracting components and logic into specialized files. * replace hardcoded padding with dynamic header height and adjust IME layout logic * Improve `PdfTextBox` interaction and visual consistency during zoom and pan. * improve PDF text box dragging and scaling behavior across zoom levels * optimize color scheme calculation using remember and expand text dimming coverage * Implement in-app language selection and per-app language preferences. * Improve PDF lock stability in `PdfVerticalReader` * Implement "Preserve Image Colors" option for PDF themes * Optimize TOC locate in ReaderDrawer * Update library and home screen UI components * smoother page turn animation in epub pagination mode * Implement automatic page skipping for TTS when no text is found in PDF viewer * Refine status bar handling and window insets across main screens * Optimize ViewModel initialization and integrate AndroidX SplashScreen * Move ComicPanelDetector to debug source set and introduce IPanelDetector interface * Optimize TOC scrolling in PdfNavigationDrawerContent * Bump version to 1.0.45(45)
This commit is contained in:
parent
71e3614ad6
commit
cb7de86224
33 changed files with 4795 additions and 3447 deletions
|
|
@ -184,11 +184,12 @@ private data class DividerLayout(val y: Float, val width: Float, val height: Flo
|
|||
@OptIn(FlowPreview::class)
|
||||
@Composable
|
||||
internal fun PdfVerticalReader(
|
||||
modifier: Modifier = Modifier,
|
||||
state: VerticalPdfReaderState,
|
||||
pdfDocument: StableHolder<ReaderDocument>,
|
||||
activeTheme: com.aryan.reader.ReaderTheme,
|
||||
excludeImages: Boolean = false,
|
||||
totalPages: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
virtualPages: List<VirtualPage> = emptyList(),
|
||||
pageAspectRatios: StableHolder<List<Float>>,
|
||||
headerHeight: Dp,
|
||||
|
|
@ -284,6 +285,11 @@ internal fun PdfVerticalReader(
|
|||
val dividerHeightDp = 8.dp
|
||||
val dividerHeightPx = with(density) { dividerHeightDp.toPx() }
|
||||
|
||||
var isFlinging by remember { mutableStateOf(false) }
|
||||
var isFastFlinging by remember { mutableStateOf(false) }
|
||||
var isInteracting by remember { mutableStateOf(false) }
|
||||
var isDragging by remember { mutableStateOf(false) }
|
||||
|
||||
val layoutState = remember(ratios, screenWidth, screenHeight, density) {
|
||||
data class LayoutResult(val pages: List<PdfPageLayout>, val totalHeight: Float)
|
||||
|
||||
|
|
@ -373,8 +379,41 @@ internal fun PdfVerticalReader(
|
|||
var isInitialLayout by remember { mutableStateOf(true) }
|
||||
val currentScaleProvider = remember(zoomAnimatable) { { zoomAnimatable.value } }
|
||||
|
||||
var hasRestoredLockedState by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(isScrollLocked, lockedState, totalDocHeight, screenWidth, isInteracting) {
|
||||
if (!hasRestoredLockedState && isScrollLocked && lockedState != null && totalDocHeight > 0f && screenWidth > 0f && !isInteracting) {
|
||||
val (savedScale, savedPanX, savedPanY) = lockedState
|
||||
|
||||
Timber.tag("PdfLockDiagnostic").i("RESTORING: Scale=$savedScale, X=$savedPanX, Y=$savedPanY")
|
||||
|
||||
val zoomedDocWidth = screenWidth * savedScale
|
||||
val minPanX = if (zoomedDocWidth < screenWidth) (screenWidth - zoomedDocWidth) / 2f else -(zoomedDocWidth - screenWidth)
|
||||
val maxPanX = if (zoomedDocWidth < screenWidth) minPanX else 0f
|
||||
|
||||
val zoomedDocHeight = totalDocHeight * savedScale
|
||||
val minPanY = (screenHeight - footerHeightPx - zoomedDocHeight).coerceAtMost(headerHeightPx)
|
||||
val maxPanY = headerHeightPx
|
||||
|
||||
zoomAnimatable.stop()
|
||||
panXAnimatable.stop()
|
||||
panYAnimatable.stop()
|
||||
|
||||
panXAnimatable.updateBounds(minPanX, maxPanX)
|
||||
panYAnimatable.updateBounds(minPanY, maxPanY)
|
||||
|
||||
zoomAnimatable.snapTo(savedScale)
|
||||
panXAnimatable.snapTo(savedPanX)
|
||||
panYAnimatable.snapTo(savedPanY.coerceIn(minPanY, maxPanY))
|
||||
|
||||
Timber.tag("PdfLockDiagnostic").d("RESTORE SNAP COMPLETE: Scale=${zoomAnimatable.value}, X=${panXAnimatable.value}, Y=${panYAnimatable.value}")
|
||||
|
||||
hasRestoredLockedState = true
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(layoutState.pages) {
|
||||
if (!isInitialLayout) {
|
||||
if (!isInitialLayout && !isScrollLocked) {
|
||||
val targetPageIdx = if (targetPageDuringResize.intValue != -1) {
|
||||
targetPageDuringResize.intValue
|
||||
} else {
|
||||
|
|
@ -410,21 +449,13 @@ internal fun PdfVerticalReader(
|
|||
launch { panXAnimatable.snapTo(targetPanX) }
|
||||
launch { panYAnimatable.snapTo(finalPanY) }
|
||||
}
|
||||
|
||||
panYAnimatable.updateBounds(lowerBound = minPanY, upperBound = headerHeightPx)
|
||||
state.currentPage = targetPageIdx
|
||||
if (isFit) onZoomChange(targetZoom)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInitialLayout) {
|
||||
delay(50)
|
||||
isResizing = false
|
||||
targetPageDuringResize.intValue = -1
|
||||
} else if (isScrollLocked && lockedState != null) {
|
||||
val (savedScale, savedPanX, _) = lockedState
|
||||
coroutineScope {
|
||||
launch { zoomAnimatable.snapTo(savedScale) }
|
||||
launch { panXAnimatable.snapTo(savedPanX) }
|
||||
}
|
||||
}
|
||||
isInitialLayout = false
|
||||
}
|
||||
|
|
@ -461,11 +492,6 @@ internal fun PdfVerticalReader(
|
|||
return clampValues(targetZoom, targetPanX, targetPanY)
|
||||
}
|
||||
|
||||
var isFlinging by remember { mutableStateOf(false) }
|
||||
var isFastFlinging by remember { mutableStateOf(false) }
|
||||
var isInteracting by remember { mutableStateOf(false) }
|
||||
var isDragging by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(
|
||||
totalDocHeight, screenHeight, headerHeightPx, footerHeightPx, zoomAnimatable.value, isInteracting, isFlinging, isResizing
|
||||
) {
|
||||
|
|
@ -483,6 +509,9 @@ internal fun PdfVerticalReader(
|
|||
panYAnimatable.snapTo(y)
|
||||
}
|
||||
if (x != currentPanX) {
|
||||
if (isScrollLocked) {
|
||||
Timber.tag("PdfLockDiagnostic").d("FORCED SNAP: X=$currentPanX to $x")
|
||||
}
|
||||
panXAnimatable.snapTo(x)
|
||||
}
|
||||
}
|
||||
|
|
@ -585,8 +614,8 @@ internal fun PdfVerticalReader(
|
|||
val dist = (draggingBoxOffset.y - topEdge).coerceAtMost(scrollZone)
|
||||
val ratio = 1f - (dist / scrollZone).coerceIn(0f, 1f)
|
||||
scrollDelta = -15f * ratio
|
||||
} else if (draggingBoxOffset.y + draggingBoxSize.height > bottomEdge - scrollZone) {
|
||||
val boxBottom = draggingBoxOffset.y + draggingBoxSize.height
|
||||
} else if (draggingBoxOffset.y + (draggingBoxSize.height * zoomAnimatable.value) > bottomEdge - scrollZone) {
|
||||
val boxBottom = draggingBoxOffset.y + (draggingBoxSize.height * zoomAnimatable.value)
|
||||
val dist = (bottomEdge - boxBottom).coerceAtMost(scrollZone)
|
||||
val ratio = 1f - (dist / scrollZone).coerceIn(0f, 1f)
|
||||
scrollDelta = 15f * ratio
|
||||
|
|
@ -744,6 +773,9 @@ internal fun PdfVerticalReader(
|
|||
}
|
||||
|
||||
if (!isAnimating) {
|
||||
if (isScrollLocked) {
|
||||
Timber.tag("PdfLockDiagnostic").v("CLAMP CHECK: X=${panXAnimatable.value} | Allowed Range=[$minPanX, $maxPanX]")
|
||||
}
|
||||
panYAnimatable.updateBounds(lowerBound = minPanY, upperBound = headerHeightPx)
|
||||
panXAnimatable.updateBounds(lowerBound = minPanX, upperBound = maxPanX)
|
||||
} else {
|
||||
|
|
@ -1575,6 +1607,7 @@ internal fun PdfVerticalReader(
|
|||
virtualPage = virtualPage,
|
||||
totalPages = totalPages,
|
||||
activeTheme = activeTheme,
|
||||
excludeImages = excludeImages,
|
||||
externalScale = highResScale,
|
||||
onScaleChanged = {},
|
||||
showAllTextHighlights = showAllTextHighlights,
|
||||
|
|
@ -1638,17 +1671,20 @@ internal fun PdfVerticalReader(
|
|||
val boxScreenY = pageScreenY + (localTopLeft.y * currentZoom)
|
||||
|
||||
draggingBoxSize = Size(
|
||||
box.relativeBounds.width * page.width * currentZoom,
|
||||
box.relativeBounds.height * page.height * currentZoom
|
||||
box.relativeBounds.width * page.width,
|
||||
box.relativeBounds.height * page.height
|
||||
)
|
||||
draggingBoxPageHeight = page.height * currentZoom
|
||||
draggingBoxPageHeight = page.height
|
||||
|
||||
draggingBoxOffset = Offset(boxScreenX, boxScreenY)
|
||||
draggingBoxTouchDelta = touchOffset * currentZoom
|
||||
draggingBoxId = box.id
|
||||
},
|
||||
onTextBoxDrag = { dragDelta ->
|
||||
draggingBoxOffset += dragDelta
|
||||
val currentZoom = zoomAnimatable.value
|
||||
val scaledDelta = dragDelta * currentZoom
|
||||
Timber.tag("PdfTextBoxDebug").v("VerticalReader onTextBoxDrag dragDelta=$dragDelta zoom=$currentZoom scaledDelta=$scaledDelta")
|
||||
draggingBoxOffset += scaledDelta
|
||||
},
|
||||
onTextBoxDragEnd = {
|
||||
scope.launch {
|
||||
|
|
@ -1681,9 +1717,9 @@ internal fun PdfVerticalReader(
|
|||
val rawRelY =
|
||||
(finalBoxY / currentZoom) / targetPage.height
|
||||
val relW =
|
||||
(draggingBoxSize.width / currentZoom) / targetPage.width
|
||||
draggingBoxSize.width / targetPage.width
|
||||
val relH =
|
||||
(draggingBoxSize.height / currentZoom) / targetPage.height
|
||||
draggingBoxSize.height / targetPage.height
|
||||
|
||||
val clampedW = relW.coerceAtMost(1f)
|
||||
val clampedH = relH.coerceAtMost(1f)
|
||||
|
|
@ -2051,7 +2087,8 @@ internal fun PdfVerticalReader(
|
|||
val fontScaleRatio =
|
||||
if (currentBoxHeight > 0) draggingBoxPageHeight / currentBoxHeight else 1f
|
||||
|
||||
val boxBottomY = draggingBoxOffset.y + draggingBoxSize.height
|
||||
val currentZoom = zoomAnimatable.value
|
||||
val boxBottomY = draggingBoxOffset.y + (draggingBoxSize.height * currentZoom)
|
||||
val spaceBelow = screenHeight - boxBottomY
|
||||
val overlayHandlePos =
|
||||
if (spaceBelow < with(density) { 60.dp.toPx() }) HandlePosition.TOP else HandlePosition.BOTTOM
|
||||
|
|
@ -2062,6 +2099,11 @@ internal fun PdfVerticalReader(
|
|||
draggingBoxOffset.x.roundToInt(), draggingBoxOffset.y.roundToInt()
|
||||
)
|
||||
}
|
||||
.graphicsLayer {
|
||||
scaleX = currentZoom
|
||||
scaleY = currentZoom
|
||||
transformOrigin = TransformOrigin(0f, 0f)
|
||||
}
|
||||
.zIndex(100f)) {
|
||||
ResizableTextBox(
|
||||
box = draggedBox.copy(
|
||||
|
|
@ -2073,6 +2115,7 @@ internal fun PdfVerticalReader(
|
|||
isDarkMode = isDarkMode,
|
||||
pageWidthPx = draggingBoxSize.width,
|
||||
pageHeightPx = draggingBoxSize.height,
|
||||
scale = currentZoom,
|
||||
handlePosition = overlayHandlePos,
|
||||
onBoundsChanged = {},
|
||||
onTextChanged = {},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue