Pdf auto scroll (#5)

* docs: Revamp README and add APK naming convention

This commit introduces a complete overhaul of the `README.md` to improve project presentation. Key changes include:
*   Adds a new header with the app icon and a Google Play badge.
*   Updates the feature graphic and includes a note distinguishing the OSS version from the Play Store version.
*   Refines the "Open Source Libraries" section to be more structured.
*   Moves the main preview image into the `docs` directory.

Additionally, the `app/build.gradle.kts` file is updated to configure a standardized naming convention for output APK files, including flavor, version, and build type.

* refactor copyright headers and fix README icon alignment

This commit introduces two main changes across the codebase:

The `spotless/copyright.kt` template have been updated for consistency:
*   The author name is simplified from "Episteme Authors" to "Episteme".
*   The contact email prefix is shortened from "Electronic mail:" to "mail:".

Second, the `README.md` file is adjusted to correct the vertical alignment of the app icon with the project title.

* Added auto-scroll feature for PDF vertical reader
This commit is contained in:
Aryan 2026-02-26 10:21:17 +05:30 committed by GitHub
parent 1f35cd8138
commit 1b048c9073
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 127 additions and 7 deletions

View file

@ -17,6 +17,7 @@
*
* mail: epistemereader@gmail.com
*/
// PdfVerticalReader.kt
@file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH", "VariableNeverRead")
package com.aryan.reader.pdf
@ -210,7 +211,10 @@ internal fun PdfVerticalReader(
onTextBoxSelect: (String) -> Unit = {},
bottomContentPaddingPx: Float = 0f,
topContentPaddingPx: Float = 0f,
onTextBoxMoved: (String, Int, Rect) -> Unit = { _, _, _ -> }
onTextBoxMoved: (String, Int, Rect) -> Unit = { _, _, _ -> },
isAutoScrollPlaying: Boolean = false,
autoScrollSpeed: Float = 1.0f,
onInteractionListener: () -> Unit = {}
) {
SideEffect { Timber.tag("PdfDrawPerf").v("LIST: PdfVerticalReader Recomposing.") }
var globalEraserPosition by remember { mutableStateOf<Offset?>(null) }
@ -480,9 +484,46 @@ internal fun PdfVerticalReader(
}
}
LaunchedEffect(isAutoScrollPlaying, autoScrollSpeed, totalDocHeight, screenHeight) {
if (isAutoScrollPlaying) {
val baseSpeedPxPerSec = 30f
var lastFrameTime = withFrameNanos { it }
while (isActive) {
val frameTime = withFrameNanos { it }
val deltaSeconds = (frameTime - lastFrameTime) / 1_000_000_000f
lastFrameTime = frameTime
if (deltaSeconds > 0.1f) continue
val pixelMove = (baseSpeedPxPerSec * autoScrollSpeed) * deltaSeconds
val currentPanY = panYAnimatable.value
val currentZoom = zoomAnimatable.value
val zoomedDocHeight = totalDocHeight * currentZoom
val minPanY = (screenHeight - footerHeightPx - zoomedDocHeight).coerceAtMost(headerHeightPx)
val newPanY = (currentPanY - pixelMove).coerceIn(minPanY, headerHeightPx)
panYAnimatable.snapTo(newPanY)
@Suppress("ControlFlowWithEmptyBody") if (newPanY <= minPanY + 0.1f) {
// Reached end
}
}
}
}
var highResScale by remember { mutableFloatStateOf(1f) }
var isInteracting by remember { mutableStateOf(false) }
LaunchedEffect(isInteracting) {
if (isInteracting && isAutoScrollPlaying) {
onInteractionListener()
}
}
LaunchedEffect(isInteracting) {
Timber.tag("PdfTouchDebug").i("VerticalReader: isInteracting changed to $isInteracting")
}

View file

@ -17,6 +17,7 @@
*
* mail: epistemereader@gmail.com
*/
// PdfViewerScreen.kt
@file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH")
package com.aryan.reader.pdf
@ -159,6 +160,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
@ -227,6 +229,7 @@ import com.aryan.reader.SearchTopBar
import com.aryan.reader.SummarizationPopup
import com.aryan.reader.SummarizationResult
import com.aryan.reader.SyncUpdateInfo
import com.aryan.reader.epubreader.AutoScrollControls
import com.aryan.reader.fetchAiDefinition
import com.aryan.reader.paginatedreader.TtsChunk
import com.aryan.reader.pdf.data.AnnotationSettingsRepository
@ -280,6 +283,17 @@ private const val OCR_LANGUAGE_SELECTED_KEY = "ocr_language_selected_key"
private const val DOCK_LOCATION_KEY = "dock_location"
private const val DOCK_OFFSET_X_KEY = "dock_offset_x"
private const val DOCK_OFFSET_Y_KEY = "dock_offset_y"
private const val PDF_AUTO_SCROLL_SPEED_KEY = "pdf_auto_scroll_speed"
private fun savePdfAutoScrollSpeed(context: Context, speed: Float) {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit { putFloat(PDF_AUTO_SCROLL_SPEED_KEY, speed) }
}
private fun loadPdfAutoScrollSpeed(context: Context): Float {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
return prefs.getFloat(PDF_AUTO_SCROLL_SPEED_KEY, 3.0f)
}
private fun generateShortId(): String {
return Random.nextInt(1000, 9999).toString()
@ -586,6 +600,23 @@ fun PdfViewerScreen(
}
}
var isDockDragging by remember { mutableStateOf(false) }
var isAutoScrollModeActive by remember { mutableStateOf(false) }
var isAutoScrollPlaying by remember { mutableStateOf(false) }
var autoScrollSpeed by remember { mutableFloatStateOf(loadPdfAutoScrollSpeed(context)) }
var isAutoScrollCollapsed by remember { mutableStateOf(false) }
val onAutoScrollInteraction = remember {
{
if (isAutoScrollPlaying) {
isAutoScrollPlaying = false
}
}
}
var paginationDraggingBoxId by remember { mutableStateOf<String?>(null) }
val customFonts by viewModel.customFonts.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
@ -609,9 +640,6 @@ fun PdfViewerScreen(
var dockLocation by remember { mutableStateOf(initialDockLocation) }
var dockOffset by remember { mutableStateOf(initialDockOffset) }
var snapPreviewLocation by remember { mutableStateOf<DockLocation?>(null) }
var isDockDragging by remember { mutableStateOf(false) }
var paginationDraggingBoxId by remember { mutableStateOf<String?>(null) }
var paginationDraggingOffset by remember { mutableStateOf(Offset.Zero) }
var paginationDraggingSize by remember { mutableStateOf(Size.Zero) }
var paginationDragPageHeight by remember { mutableFloatStateOf(0f) }
@ -1074,6 +1102,7 @@ fun PdfViewerScreen(
val onSingleTapStable = remember {
{
onAutoScrollInteraction()
if (selectedTextBoxId != null) {
val box = textBoxes.find { it.id == selectedTextBoxId }
if (box != null && box.text.trim().isEmpty()) {
@ -3610,7 +3639,10 @@ fun PdfViewerScreen(
val oldBox = textBoxes[idx]
textBoxes[idx] = oldBox.copy(pageIndex = newPageIndex, relativeBounds = newBounds)
}
}
},
isAutoScrollPlaying = isAutoScrollPlaying,
autoScrollSpeed = autoScrollSpeed,
onInteractionListener = onAutoScrollInteraction
)
}
}
@ -4077,6 +4109,17 @@ fun PdfViewerScreen(
}
})
HorizontalDivider()
DropdownMenuItem(
text = { Text("Auto Scroll") },
enabled = !isTtsSessionActive && displayMode == DisplayMode.VERTICAL_SCROLL,
onClick = {
showMoreMenu = false
isAutoScrollModeActive = true
isAutoScrollPlaying = true
showBars = false
}
)
HorizontalDivider()
DropdownMenuItem(text = {
Text(
if (isBookmarked) "Remove bookmark"
@ -5506,6 +5549,42 @@ fun PdfViewerScreen(
}
}
}
val autoScrollPadding by animateDpAsState(
targetValue = if (showBars) (56.dp + 16.dp) else 16.dp,
label = "AutoScrollPadding"
)
val alignmentBias by animateFloatAsState(
targetValue = if (isAutoScrollCollapsed) 1f else 0f,
label = "AutoScrollAlignAnimation"
)
AnimatedVisibility(
visible = isAutoScrollModeActive,
enter = slideInVertically { it } + fadeIn(),
exit = slideOutVertically { it } + fadeOut(),
modifier = Modifier
.align(BiasAlignment(alignmentBias, 1f))
.padding(bottom = autoScrollPadding)
.padding(horizontal = 16.dp)
) {
AutoScrollControls(
isPlaying = isAutoScrollPlaying,
onPlayPauseToggle = { isAutoScrollPlaying = !isAutoScrollPlaying },
speed = autoScrollSpeed,
onSpeedChange = {
autoScrollSpeed = it
savePdfAutoScrollSpeed(context, it)
},
onClose = {
isAutoScrollModeActive = false
isAutoScrollPlaying = false
showBars = true
},
isCollapsed = isAutoScrollCollapsed,
onCollapseChange = { isAutoScrollCollapsed = it }
)
}
}
}
}