feat(pdf-viewer): implement pan lock to disable horizontal movement (#9)
- Add `isScrollLocked` state to `PdfViewerScreen` to toggle panning behavior. - Replace "Import SVG" debug icon with a Lock/Unlock toggle in the Top App Bar. - Update `PdfVerticalReader` and `PdfPageComposable` to strictly zero out X-axis pan deltas and fling velocities when locked. - Maintain zoom and vertical scrolling functionality while panning is locked.
This commit is contained in:
parent
f2daebe0a5
commit
ea31765612
3 changed files with 34 additions and 35 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
*
|
*
|
||||||
* mail: epistemereader@gmail.com
|
* mail: epistemereader@gmail.com
|
||||||
*/
|
*/
|
||||||
|
// PdfPageComposable
|
||||||
@file:Suppress(
|
@file:Suppress(
|
||||||
"RemoveRedundantQualifierName", "COMPOSE_APPLIER_CALL_MISMATCH", "UnusedVariable", "unused"
|
"RemoveRedundantQualifierName", "COMPOSE_APPLIER_CALL_MISMATCH", "UnusedVariable", "unused"
|
||||||
)
|
)
|
||||||
|
|
@ -434,6 +435,7 @@ internal fun PdfPageComposable(
|
||||||
onTextBoxDragEnd: () -> Unit = {},
|
onTextBoxDragEnd: () -> Unit = {},
|
||||||
onDragPageTurn: (Int) -> Unit = {},
|
onDragPageTurn: (Int) -> Unit = {},
|
||||||
draggingBoxId: String? = null,
|
draggingBoxId: String? = null,
|
||||||
|
isScrollLocked: Boolean = false,
|
||||||
isVisible: Boolean = true,
|
isVisible: Boolean = true,
|
||||||
) {
|
) {
|
||||||
SideEffect { Timber.tag("PdfDrawPerf").v("PdfPageComposable Recompose: Page $pageIndex") }
|
SideEffect { Timber.tag("PdfDrawPerf").v("PdfPageComposable Recompose: Page $pageIndex") }
|
||||||
|
|
@ -2422,7 +2424,8 @@ internal fun PdfPageComposable(
|
||||||
isZoomEnabled,
|
isZoomEnabled,
|
||||||
isVerticalScroll,
|
isVerticalScroll,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
onTwoFingerSwipe
|
onTwoFingerSwipe,
|
||||||
|
isScrollLocked
|
||||||
) {
|
) {
|
||||||
if (!isZoomEnabled || isVerticalScroll || actualBitmapWidthPx == 0 || activeDraggingHandle != null) return@pointerInput
|
if (!isZoomEnabled || isVerticalScroll || actualBitmapWidthPx == 0 || activeDraggingHandle != null) return@pointerInput
|
||||||
|
|
||||||
|
|
@ -2453,7 +2456,8 @@ internal fun PdfPageComposable(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
val panChange = event.calculatePan()
|
val rawPanChange = event.calculatePan()
|
||||||
|
val panChange = if (isScrollLocked) Offset(0f, rawPanChange.y) else rawPanChange
|
||||||
val zoomChange = event.calculateZoom()
|
val zoomChange = event.calculateZoom()
|
||||||
|
|
||||||
if (scale > 1f) {
|
if (scale > 1f) {
|
||||||
|
|
@ -2622,6 +2626,7 @@ internal fun PdfPageComposable(
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
launch {
|
launch {
|
||||||
|
if (!isScrollLocked) {
|
||||||
Animatable(startX).animateDecay(
|
Animatable(startX).animateDecay(
|
||||||
velocity.x, decay
|
velocity.x, decay
|
||||||
) {
|
) {
|
||||||
|
|
@ -2631,6 +2636,7 @@ internal fun PdfPageComposable(
|
||||||
offset = offset.copy(x = newX)
|
offset = offset.copy(x = newX)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
launch {
|
launch {
|
||||||
Animatable(startY).animateDecay(
|
Animatable(startY).animateDecay(
|
||||||
velocity.y, decay
|
velocity.y, decay
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,7 @@ internal fun PdfVerticalReader(
|
||||||
onTextBoxMoved: (String, Int, Rect) -> Unit = { _, _, _ -> },
|
onTextBoxMoved: (String, Int, Rect) -> Unit = { _, _, _ -> },
|
||||||
isAutoScrollPlaying: Boolean = false,
|
isAutoScrollPlaying: Boolean = false,
|
||||||
isAutoScrollTempPaused: Boolean = false,
|
isAutoScrollTempPaused: Boolean = false,
|
||||||
|
isScrollLocked: Boolean = false,
|
||||||
autoScrollSpeed: Float = 1.0f,
|
autoScrollSpeed: Float = 1.0f,
|
||||||
onInteractionListener: () -> Unit = {}
|
onInteractionListener: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
|
@ -837,7 +838,7 @@ internal fun PdfVerticalReader(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
.pointerInput(totalDocHeight, isEditMode, selectedTool) {
|
.pointerInput(totalDocHeight, isEditMode, selectedTool, isScrollLocked) {
|
||||||
val tracker = VelocityTracker()
|
val tracker = VelocityTracker()
|
||||||
val decay = exponentialDecay<Float>()
|
val decay = exponentialDecay<Float>()
|
||||||
val touchSlop = viewConfiguration.touchSlop
|
val touchSlop = viewConfiguration.touchSlop
|
||||||
|
|
@ -916,7 +917,9 @@ internal fun PdfVerticalReader(
|
||||||
}
|
}
|
||||||
|
|
||||||
val zoomChange = event.calculateZoom()
|
val zoomChange = event.calculateZoom()
|
||||||
val panChange = event.calculatePan()
|
val rawPanChange = event.calculatePan()
|
||||||
|
val panChange = if (isScrollLocked) Offset(0f, rawPanChange.y) else rawPanChange
|
||||||
|
|
||||||
val centroid = event.calculateCentroid(useCurrent = false)
|
val centroid = event.calculateCentroid(useCurrent = false)
|
||||||
val panMagnitude = panChange.getDistance()
|
val panMagnitude = panChange.getDistance()
|
||||||
val currentCentroidSize = event.calculateCentroidSize(
|
val currentCentroidSize = event.calculateCentroidSize(
|
||||||
|
|
@ -1065,7 +1068,7 @@ internal fun PdfVerticalReader(
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
launch {
|
launch {
|
||||||
val rawX = velocity.x * flingSensitivity
|
val rawX = velocity.x * flingSensitivity
|
||||||
val flingX = if (abs(rawX) > minFlingVelocity) rawX
|
val flingX = if (abs(rawX) > minFlingVelocity && !isScrollLocked) rawX
|
||||||
else 0f
|
else 0f
|
||||||
|
|
||||||
if (flingX != 0f) panXAnimatable.animateDecay(
|
if (flingX != 0f) panXAnimatable.animateDecay(
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@ import androidx.compose.material.icons.filled.FullscreenExit
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
|
import androidx.compose.material.icons.filled.Lock
|
||||||
|
import androidx.compose.material.icons.filled.LockOpen
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Save
|
import androidx.compose.material.icons.filled.Save
|
||||||
|
|
@ -588,6 +590,7 @@ fun PdfViewerScreen(
|
||||||
var showBars by remember { mutableStateOf(true) }
|
var showBars by remember { mutableStateOf(true) }
|
||||||
var isFullScreen by remember { mutableStateOf(false) }
|
var isFullScreen by remember { mutableStateOf(false) }
|
||||||
var documentPassword by remember { mutableStateOf<String?>(null) }
|
var documentPassword by remember { mutableStateOf<String?>(null) }
|
||||||
|
var isScrollLocked by remember { mutableStateOf(false) }
|
||||||
var showPasswordDialog by remember { mutableStateOf(false) }
|
var showPasswordDialog by remember { mutableStateOf(false) }
|
||||||
var isPasswordError by remember { mutableStateOf(false) }
|
var isPasswordError by remember { mutableStateOf(false) }
|
||||||
LocalView.current
|
LocalView.current
|
||||||
|
|
@ -3208,6 +3211,7 @@ fun PdfViewerScreen(
|
||||||
virtualPage = virtualPage,
|
virtualPage = virtualPage,
|
||||||
totalPages = totalDisplayPages,
|
totalPages = totalDisplayPages,
|
||||||
isDarkMode = isPdfDarkMode,
|
isDarkMode = isPdfDarkMode,
|
||||||
|
isScrollLocked = isScrollLocked,
|
||||||
onScaleChanged = { newScale ->
|
onScaleChanged = { newScale ->
|
||||||
if (pagerState.currentPage == pageIndex) {
|
if (pagerState.currentPage == pageIndex) {
|
||||||
currentPageScale = newScale
|
currentPageScale = newScale
|
||||||
|
|
@ -3597,6 +3601,7 @@ fun PdfViewerScreen(
|
||||||
state = verticalReaderState,
|
state = verticalReaderState,
|
||||||
pdfDocument = docHolder,
|
pdfDocument = docHolder,
|
||||||
isDarkMode = isPdfDarkMode,
|
isDarkMode = isPdfDarkMode,
|
||||||
|
isScrollLocked = isScrollLocked,
|
||||||
totalPages = totalDisplayPages,
|
totalPages = totalDisplayPages,
|
||||||
pageAspectRatios = ratiosHolder,
|
pageAspectRatios = ratiosHolder,
|
||||||
virtualPages = virtualPages,
|
virtualPages = virtualPages,
|
||||||
|
|
@ -4016,6 +4021,14 @@ fun PdfViewerScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = { isScrollLocked = !isScrollLocked }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isScrollLocked) Icons.Default.Lock else Icons.Default.LockOpen,
|
||||||
|
contentDescription = if (isScrollLocked) "Unlock Panning" else "Lock Panning",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(onClick = { isFullScreen = true }) {
|
IconButton(onClick = { isFullScreen = true }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Fullscreen,
|
imageVector = Icons.Default.Fullscreen,
|
||||||
|
|
@ -4066,29 +4079,6 @@ fun PdfViewerScreen(
|
||||||
tint = Color(0xFFE91E63)
|
tint = Color(0xFFE91E63)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(onClick = {
|
|
||||||
val page = if (displayMode == DisplayMode.PAGINATION) pagerState.currentPage else verticalReaderState.currentPage
|
|
||||||
val demoAnnos = DemoAnnotationGenerator.generateDemoAnnotations(page)
|
|
||||||
|
|
||||||
val existing = allAnnotations[page] ?: emptyList()
|
|
||||||
allAnnotations = allAnnotations + (page to (existing + demoAnnos))
|
|
||||||
|
|
||||||
demoAnnos.forEach { annot ->
|
|
||||||
undoStack.add(HistoryAction.Add(page, annot))
|
|
||||||
}
|
|
||||||
redoStack.clear()
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
|
||||||
snackbarHostState.showSnackbar("Demo annotations injected!")
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Edit,
|
|
||||||
contentDescription = "Inject Demo Annotations",
|
|
||||||
tint = Color(0xFF2E7D32)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue