Auto scroll update (#28)

* Updated auto-scroll controls UI and refined pause behavior.

- Added semi-transparent look to auto-scroll
- Reduced auto-scroll temporary pause duration from 1000ms to 300ms.
- Updated `AutoScrollControls` styling with background transparency and adjusted elevations/borders.
- Refined `CircularProgressIndicator` visibility to only show when active and paused.
- Enhanced the `Slider` appearance in `AutoScrollControls` with a custom track and thumb.
- Improved play/pause logic in `PdfViewerScreen` to properly reset temporary pause states and jobs.
- Cleaned up redundant comments and formatting in `EpubReaderControls.kt`.

* feat: add file-specific auto-scroll speed profiles for EPUB and PDF

- Updated AutoScrollControls UI with a dropdown to toggle between Global and Local speed modes.
- Implemented persistent local auto-scroll settings (Current, Min, and Max speed) keyed by book ID.
- Ensured "Local mode" preference is sticky per-file, while maintaining Global defaults for new files.

* Enabled auto-scroll toggling via single tap in EPUB and PDF readers
This commit is contained in:
Aryan 2026-03-04 16:40:02 +05:30 committed by GitHub
parent b47b6a9164
commit 0b7bb53a28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 435 additions and 107 deletions

View file

@ -68,6 +68,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.ChevronLeft import androidx.compose.material.icons.filled.ChevronLeft
import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.ChevronRight
@ -102,6 +103,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
@ -740,7 +742,6 @@ fun SpeedDropdown(
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AutoScrollControls( fun AutoScrollControls(
@ -759,15 +760,19 @@ fun AutoScrollControls(
onLockToggle: () -> Unit, onLockToggle: () -> Unit,
useSlider: Boolean, useSlider: Boolean,
onInputModeToggle: () -> Unit, onInputModeToggle: () -> Unit,
isLocalMode: Boolean,
onLocalModeToggle: (Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isTempPaused: Boolean = false, isTempPaused: Boolean = false,
) { ) {
val backgroundAlpha = 0.6f
Surface( Surface(
shape = RoundedCornerShape(28.dp), shape = RoundedCornerShape(28.dp),
color = MaterialTheme.colorScheme.surfaceContainerHigh, color = MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = backgroundAlpha),
tonalElevation = 8.dp, tonalElevation = 0.dp,
shadowElevation = 6.dp, shadowElevation = 0.dp,
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f)),
modifier = modifier modifier = modifier
.widthIn(max = 400.dp) .widthIn(max = 400.dp)
.animateContentSize() .animateContentSize()
@ -780,14 +785,12 @@ fun AutoScrollControls(
label = "AutoScrollUnified" label = "AutoScrollUnified"
) { collapsed -> ) { collapsed ->
if (collapsed) { if (collapsed) {
// COLLAPSED STATE: Ultra-Compact Pill
Row( Row(
modifier = Modifier modifier = Modifier
.padding(horizontal = 6.dp, vertical = 6.dp), .padding(horizontal = 6.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
// Expand Button
IconButton( IconButton(
onClick = { onCollapseChange(false) }, onClick = { onCollapseChange(false) },
modifier = Modifier.size(36.dp) modifier = Modifier.size(36.dp)
@ -799,7 +802,6 @@ fun AutoScrollControls(
) )
} }
// Play/Pause Mini
Box(modifier = Modifier.size(36.dp), contentAlignment = Alignment.Center) { Box(modifier = Modifier.size(36.dp), contentAlignment = Alignment.Center) {
FilledIconButton( FilledIconButton(
onClick = onPlayPauseToggle, onClick = onPlayPauseToggle,
@ -815,7 +817,7 @@ fun AutoScrollControls(
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
) )
} }
if (isTempPaused) { if (isTempPaused && isPlaying) {
CircularProgressIndicator( CircularProgressIndicator(
modifier = Modifier.size(36.dp), modifier = Modifier.size(36.dp),
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f), color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f),
@ -825,26 +827,76 @@ fun AutoScrollControls(
} }
} }
} else { } else {
// EXPANDED STATE: Card-like Layout
Column( Column(
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
// Top Row: Utility Actions (Right aligned) // Top Row: Label & Tools
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// Label Box {
Text( var showModeMenu by remember { mutableStateOf(false) }
text = "Auto Scroll", Row(
style = MaterialTheme.typography.labelLarge, verticalAlignment = Alignment.CenterVertically,
color = MaterialTheme.colorScheme.primary modifier = Modifier
) .clip(RoundedCornerShape(8.dp))
.clickable { showModeMenu = true }
.padding(4.dp)
) {
Text(
text = if (isLocalMode) "Local Speed" else "Global Speed",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary
)
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = "Select Mode",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(20.dp)
)
}
DropdownMenu(
expanded = showModeMenu,
onDismissRequest = { showModeMenu = false }
) {
DropdownMenuItem(
text = {
Column {
Text("Global Speed", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
Text("Applies to all files", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
}
},
onClick = {
onLocalModeToggle(false)
showModeMenu = false
},
trailingIcon = {
if (!isLocalMode) Icon(Icons.Default.Check, contentDescription = null)
}
)
HorizontalDivider()
DropdownMenuItem(
text = {
Column {
Text("Local Speed", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
Text("Saved for this file only", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
}
},
onClick = {
onLocalModeToggle(true)
showModeMenu = false
},
trailingIcon = {
if (isLocalMode) Icon(Icons.Default.Check, contentDescription = null)
}
)
}
}
// Tools Row
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
// Lock
IconButton( IconButton(
onClick = onLockToggle, onClick = onLockToggle,
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
@ -856,7 +908,6 @@ fun AutoScrollControls(
tint = if (isLocked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant tint = if (isLocked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
// Swap Input
IconButton( IconButton(
onClick = onInputModeToggle, onClick = onInputModeToggle,
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
@ -868,7 +919,6 @@ fun AutoScrollControls(
tint = MaterialTheme.colorScheme.onSurfaceVariant tint = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
// Collapse
IconButton( IconButton(
onClick = { onCollapseChange(true) }, onClick = { onCollapseChange(true) },
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
@ -880,7 +930,6 @@ fun AutoScrollControls(
tint = MaterialTheme.colorScheme.onSurfaceVariant tint = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
// Close
IconButton( IconButton(
onClick = onClose, onClick = onClose,
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
@ -897,13 +946,13 @@ fun AutoScrollControls(
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
// Bottom Row: Primary Controls // Bottom Row: Controls
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp) horizontalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
// 1. Play/Pause Button // Play/Pause
Box(modifier = Modifier.size(48.dp), contentAlignment = Alignment.Center) { Box(modifier = Modifier.size(48.dp), contentAlignment = Alignment.Center) {
FilledIconButton( FilledIconButton(
onClick = onPlayPauseToggle, onClick = onPlayPauseToggle,
@ -919,7 +968,7 @@ fun AutoScrollControls(
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
if (isTempPaused) { if (isTempPaused && isPlaying) {
CircularProgressIndicator( CircularProgressIndicator(
modifier = Modifier.size(48.dp), modifier = Modifier.size(48.dp),
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f), color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f),
@ -928,14 +977,13 @@ fun AutoScrollControls(
} }
} }
// 2. Speed Controls (Weight to fill space) // Speed Controls
Box( Box(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
val speedOptions = listOf(0.1f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f) val speedOptions = listOf(0.1f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f)
// Min/Max Dropdowns
Row( Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp), modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
@ -958,7 +1006,6 @@ fun AutoScrollControls(
val safeMax = maxSpeed.coerceAtLeast(minSpeed + 0.1f) val safeMax = maxSpeed.coerceAtLeast(minSpeed + 0.1f)
if (useSlider) { if (useSlider) {
// Slider Mode: Text + Slider
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
@ -978,20 +1025,42 @@ fun AutoScrollControls(
steps = if (steps > 0) steps else 0, steps = if (steps > 0) steps else 0,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
thumb = { thumb = {
Surface( Box(
modifier = Modifier.size(20.dp), modifier = Modifier
shape = CircleShape, .size(20.dp)
color = MaterialTheme.colorScheme.primary, .background(MaterialTheme.colorScheme.primary, CircleShape)
shadowElevation = 2.dp )
) {} },
track = { sliderState ->
val fraction = (sliderState.value - sliderState.valueRange.start) /
(sliderState.valueRange.endInclusive - sliderState.valueRange.start)
Box(
modifier = Modifier
.fillMaxWidth()
.height(4.dp)
.background(
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f),
RoundedCornerShape(2.dp)
),
contentAlignment = Alignment.CenterStart
) {
Box(
modifier = Modifier
.fillMaxWidth(fraction)
.height(4.dp)
.background(
MaterialTheme.colorScheme.primary,
RoundedCornerShape(2.dp)
)
)
}
} }
) )
} }
} else { } else {
// Stepper Mode: Segmented Pill
Surface( Surface(
shape = RoundedCornerShape(50), shape = RoundedCornerShape(50),
color = MaterialTheme.colorScheme.surfaceVariant, color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.height(48.dp).fillMaxWidth() modifier = Modifier.height(48.dp).fillMaxWidth()
) { ) {
Row( Row(
@ -1005,13 +1074,11 @@ fun AutoScrollControls(
) { ) {
Icon(Icons.Default.Remove, "Slower") Icon(Icons.Default.Remove, "Slower")
} }
Text( Text(
text = "%.1fx".format(speed), text = "%.1fx".format(speed),
style = MaterialTheme.typography.titleMedium.copy(fontFeatureSettings = "tnum"), style = MaterialTheme.typography.titleMedium.copy(fontFeatureSettings = "tnum"),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
IconButton( IconButton(
onClick = { onSpeedChange((speed + 0.1f).coerceAtMost(safeMax)) }, onClick = { onSpeedChange((speed + 0.1f).coerceAtMost(safeMax)) },
modifier = Modifier.size(48.dp) modifier = Modifier.size(48.dp)

View file

@ -181,6 +181,44 @@ private const val AUTO_SCROLL_MAX_SPEED_KEY = "auto_scroll_max_speed"
private const val PAGE_TURN_ANIMATION_KEY = "page_turn_animation_enabled" private const val PAGE_TURN_ANIMATION_KEY = "page_turn_animation_enabled"
private const val TTS_MODE_KEY = "tts_mode" private const val TTS_MODE_KEY = "tts_mode"
private const val AUTO_SCROLL_IS_LOCAL_PREFIX = "auto_scroll_is_local_"
private const val AUTO_SCROLL_LOCAL_SPEED_PREFIX = "auto_scroll_local_speed_"
private const val AUTO_SCROLL_LOCAL_MIN_PREFIX = "auto_scroll_local_min_"
private const val AUTO_SCROLL_LOCAL_MAX_PREFIX = "auto_scroll_local_max_"
private fun getBookIdForPrefs(title: String): String {
return title.hashCode().toString()
}
private fun saveAutoScrollLocalMode(context: Context, bookId: String, isLocal: Boolean) {
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
prefs.edit { putBoolean(AUTO_SCROLL_IS_LOCAL_PREFIX + bookId, isLocal) }
}
private fun loadAutoScrollLocalMode(context: Context, bookId: String): Boolean {
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
return prefs.getBoolean(AUTO_SCROLL_IS_LOCAL_PREFIX + bookId, false)
}
private fun saveAutoScrollLocalSettings(context: Context, bookId: String, speed: Float, min: Float, max: Float) {
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
prefs.edit {
putFloat(AUTO_SCROLL_LOCAL_SPEED_PREFIX + bookId, speed)
putFloat(AUTO_SCROLL_LOCAL_MIN_PREFIX + bookId, min)
putFloat(AUTO_SCROLL_LOCAL_MAX_PREFIX + bookId, max)
}
}
private fun loadAutoScrollLocalSettings(context: Context, bookId: String): Triple<Float, Float, Float>? {
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
if (!prefs.contains(AUTO_SCROLL_LOCAL_SPEED_PREFIX + bookId)) return null
val speed = prefs.getFloat(AUTO_SCROLL_LOCAL_SPEED_PREFIX + bookId, 3.0f)
val min = prefs.getFloat(AUTO_SCROLL_LOCAL_MIN_PREFIX + bookId, 0.1f)
val max = prefs.getFloat(AUTO_SCROLL_LOCAL_MAX_PREFIX + bookId, 10.0f)
return Triple(speed, min, max)
}
private fun savePageTurnAnimationSetting(context: Context, isEnabled: Boolean) { private fun savePageTurnAnimationSetting(context: Context, isEnabled: Boolean) {
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
prefs.edit { putBoolean(PAGE_TURN_ANIMATION_KEY, isEnabled) } prefs.edit { putBoolean(PAGE_TURN_ANIMATION_KEY, isEnabled) }
@ -349,11 +387,92 @@ fun EpubReaderHost(
} }
} }
var autoScrollSpeed by remember { mutableFloatStateOf(loadAutoScrollSpeed(context)) }
var autoScrollMinSpeed by remember { mutableFloatStateOf(loadAutoScrollMinSpeed(context)) }
var autoScrollMaxSpeed by remember { mutableFloatStateOf(loadAutoScrollMaxSpeed(context)) }
var isAutoScrollCollapsed by remember { mutableStateOf(false) } var isAutoScrollCollapsed by remember { mutableStateOf(false) }
val bookId = remember(epubBook.title) { getBookIdForPrefs(epubBook.title) }
var isAutoScrollLocal by remember { mutableStateOf(loadAutoScrollLocalMode(context, bookId)) }
val initialSettings = remember(isAutoScrollLocal) {
if (isAutoScrollLocal) {
loadAutoScrollLocalSettings(context, bookId) ?: Triple(
loadAutoScrollSpeed(context),
loadAutoScrollMinSpeed(context),
loadAutoScrollMaxSpeed(context)
)
} else {
Triple(
loadAutoScrollSpeed(context),
loadAutoScrollMinSpeed(context),
loadAutoScrollMaxSpeed(context)
)
}
}
var autoScrollSpeed by remember { mutableFloatStateOf(initialSettings.first) }
var autoScrollMinSpeed by remember { mutableFloatStateOf(initialSettings.second) }
var autoScrollMaxSpeed by remember { mutableFloatStateOf(initialSettings.third) }
val onToggleAutoScrollMode = { newIsLocal: Boolean ->
isAutoScrollLocal = newIsLocal
saveAutoScrollLocalMode(context, bookId, newIsLocal)
if (newIsLocal) {
val existingLocal = loadAutoScrollLocalSettings(context, bookId)
if (existingLocal == null) {
saveAutoScrollLocalSettings(context, bookId, autoScrollSpeed, autoScrollMinSpeed, autoScrollMaxSpeed)
} else {
autoScrollSpeed = existingLocal.first
autoScrollMinSpeed = existingLocal.second
autoScrollMaxSpeed = existingLocal.third
}
} else {
autoScrollSpeed = loadAutoScrollSpeed(context)
autoScrollMinSpeed = loadAutoScrollMinSpeed(context)
autoScrollMaxSpeed = loadAutoScrollMaxSpeed(context)
}
}
val updateSpeed = { newSpeed: Float ->
autoScrollSpeed = newSpeed
if (isAutoScrollLocal) {
saveAutoScrollLocalSettings(context, bookId, newSpeed, autoScrollMinSpeed, autoScrollMaxSpeed)
} else {
saveAutoScrollSpeed(context, newSpeed)
}
}
val updateMinSpeed = { newMin: Float ->
autoScrollMinSpeed = newMin
if (isAutoScrollLocal) {
var currentMax = autoScrollMaxSpeed
var currentSpeed = autoScrollSpeed
if (currentMax < newMin) { currentMax = newMin; autoScrollMaxSpeed = newMin }
if (currentSpeed < newMin) { currentSpeed = newMin; autoScrollSpeed = newMin }
else if (currentSpeed > currentMax) { currentSpeed = currentMax; autoScrollSpeed = currentMax }
saveAutoScrollLocalSettings(context, bookId, currentSpeed, newMin, currentMax)
} else {
saveAutoScrollMinSpeed(context, newMin)
}
}
val updateMaxSpeed = { newMax: Float ->
autoScrollMaxSpeed = newMax
if (isAutoScrollLocal) {
var currentMin = autoScrollMinSpeed
var currentSpeed = autoScrollSpeed
if (currentMin > newMax) { currentMin = newMax; autoScrollMinSpeed = newMax }
if (currentSpeed > newMax) { currentSpeed = newMax; autoScrollSpeed = newMax }
else if (currentSpeed < currentMin) { currentSpeed = currentMin; autoScrollSpeed = currentMin }
saveAutoScrollLocalSettings(context, bookId, currentSpeed, currentMin, newMax)
} else {
saveAutoScrollMaxSpeed(context, newMax)
}
}
var currentHighlightPalette by remember { var currentHighlightPalette by remember {
mutableStateOf(loadHighlightPalette(context)) mutableStateOf(loadHighlightPalette(context))
} }
@ -1740,6 +1859,11 @@ fun EpubReaderHost(
} }
}, },
onTap = { onTap = {
if (isAutoScrollModeActive) {
isAutoScrollPlaying = !isAutoScrollPlaying
Timber.d("Auto-scroll toggled via tap: $isAutoScrollPlaying")
}
if (System.currentTimeMillis() - lastHighlightClickTime > 500) { if (System.currentTimeMillis() - lastHighlightClickTime > 500) {
focusManager.clearFocus() focusManager.clearFocus()
if (volumeScrollEnabled && !searchState.isSearchActive) { if (volumeScrollEnabled && !searchState.isSearchActive) {
@ -1762,7 +1886,7 @@ fun EpubReaderHost(
Timber.d("Scroll/Drag detected, hiding bars.") Timber.d("Scroll/Drag detected, hiding bars.")
} }
if (isAutoScrollModeActive && isAutoScrollPlaying) { if (isAutoScrollModeActive && isAutoScrollPlaying) {
triggerAutoScrollTempPause(1000L) triggerAutoScrollTempPause(300L)
} }
}, },
onAutoScrollChapterEnd = { onAutoScrollChapterEnd = {
@ -2796,38 +2920,37 @@ fun EpubReaderHost(
speed = autoScrollSpeed, speed = autoScrollSpeed,
minSpeed = autoScrollMinSpeed, minSpeed = autoScrollMinSpeed,
maxSpeed = autoScrollMaxSpeed, maxSpeed = autoScrollMaxSpeed,
onSpeedChange = { onSpeedChange = { updateSpeed(it) },
autoScrollSpeed = it
saveAutoScrollSpeed(context, it)
},
onMinSpeedChange = { newMin -> onMinSpeedChange = { newMin ->
autoScrollMinSpeed = newMin updateMinSpeed(newMin)
saveAutoScrollMinSpeed(context, newMin) if (!isAutoScrollLocal) {
if (autoScrollMaxSpeed < newMin) { if (autoScrollMaxSpeed < newMin) {
autoScrollMaxSpeed = newMin autoScrollMaxSpeed = newMin
saveAutoScrollMaxSpeed(context, newMin) saveAutoScrollMaxSpeed(context, newMin)
} }
if (autoScrollSpeed < newMin) { if (autoScrollSpeed < newMin) {
autoScrollSpeed = newMin autoScrollSpeed = newMin
saveAutoScrollSpeed(context, newMin) saveAutoScrollSpeed(context, newMin)
} else if (autoScrollSpeed > autoScrollMaxSpeed) { } else if (autoScrollSpeed > autoScrollMaxSpeed) {
autoScrollSpeed = autoScrollMaxSpeed autoScrollSpeed = autoScrollMaxSpeed
saveAutoScrollSpeed(context, autoScrollMaxSpeed) saveAutoScrollSpeed(context, autoScrollMaxSpeed)
}
} }
}, },
onMaxSpeedChange = { newMax -> onMaxSpeedChange = { newMax ->
autoScrollMaxSpeed = newMax updateMaxSpeed(newMax)
saveAutoScrollMaxSpeed(context, newMax) if (!isAutoScrollLocal) {
if (autoScrollMinSpeed > newMax) { if (autoScrollMinSpeed > newMax) {
autoScrollMinSpeed = newMax autoScrollMinSpeed = newMax
saveAutoScrollMinSpeed(context, newMax) saveAutoScrollMinSpeed(context, newMax)
} }
if (autoScrollSpeed > newMax) { if (autoScrollSpeed > newMax) {
autoScrollSpeed = newMax autoScrollSpeed = newMax
saveAutoScrollSpeed(context, newMax) saveAutoScrollSpeed(context, newMax)
} else if (autoScrollSpeed < autoScrollMinSpeed) { } else if (autoScrollSpeed < autoScrollMinSpeed) {
autoScrollSpeed = autoScrollMinSpeed autoScrollSpeed = autoScrollMinSpeed
saveAutoScrollSpeed(context, autoScrollMinSpeed) saveAutoScrollSpeed(context, autoScrollMinSpeed)
}
} }
}, },
onClose = { onClose = {
@ -2846,7 +2969,9 @@ fun EpubReaderHost(
onInputModeToggle = { onInputModeToggle = {
autoScrollUseSlider = !autoScrollUseSlider autoScrollUseSlider = !autoScrollUseSlider
saveAutoScrollUseSlider(context, autoScrollUseSlider) saveAutoScrollUseSlider(context, autoScrollUseSlider)
} },
isLocalMode = isAutoScrollLocal,
onLocalModeToggle = onToggleAutoScrollMode
) )
} }

View file

@ -293,6 +293,40 @@ private const val PDF_AUTO_SCROLL_MIN_SPEED_KEY = "pdf_auto_scroll_min_speed"
private const val PDF_AUTO_SCROLL_MAX_SPEED_KEY = "pdf_auto_scroll_max_speed" private const val PDF_AUTO_SCROLL_MAX_SPEED_KEY = "pdf_auto_scroll_max_speed"
private const val STYLUS_ONLY_MODE_KEY = "stylus_only_mode" private const val STYLUS_ONLY_MODE_KEY = "stylus_only_mode"
private const val PDF_AUTO_SCROLL_IS_LOCAL_PREFIX = "pdf_as_local_"
private const val PDF_AUTO_SCROLL_LOCAL_SPEED_PREFIX = "pdf_as_local_speed_"
private const val PDF_AUTO_SCROLL_LOCAL_MIN_PREFIX = "pdf_as_local_min_"
private const val PDF_AUTO_SCROLL_LOCAL_MAX_PREFIX = "pdf_as_local_max_"
private fun savePdfAutoScrollLocalMode(context: Context, bookId: String, isLocal: Boolean) {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit { putBoolean(PDF_AUTO_SCROLL_IS_LOCAL_PREFIX + bookId, isLocal) }
}
private fun loadPdfAutoScrollLocalMode(context: Context, bookId: String): Boolean {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
return prefs.getBoolean(PDF_AUTO_SCROLL_IS_LOCAL_PREFIX + bookId, false)
}
private fun savePdfAutoScrollLocalSettings(context: Context, bookId: String, speed: Float, min: Float, max: Float) {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit {
putFloat(PDF_AUTO_SCROLL_LOCAL_SPEED_PREFIX + bookId, speed)
putFloat(PDF_AUTO_SCROLL_LOCAL_MIN_PREFIX + bookId, min)
putFloat(PDF_AUTO_SCROLL_LOCAL_MAX_PREFIX + bookId, max)
}
}
private fun loadPdfAutoScrollLocalSettings(context: Context, bookId: String): Triple<Float, Float, Float>? {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
if (!prefs.contains(PDF_AUTO_SCROLL_LOCAL_SPEED_PREFIX + bookId)) return null
val speed = prefs.getFloat(PDF_AUTO_SCROLL_LOCAL_SPEED_PREFIX + bookId, 3.0f)
val min = prefs.getFloat(PDF_AUTO_SCROLL_LOCAL_MIN_PREFIX + bookId, 0.1f)
val max = prefs.getFloat(PDF_AUTO_SCROLL_LOCAL_MAX_PREFIX + bookId, 10.0f)
return Triple(speed, min, max)
}
private fun savePdfAutoScrollMinSpeed(context: Context, speed: Float) { private fun savePdfAutoScrollMinSpeed(context: Context, speed: Float) {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE) val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit { putFloat(PDF_AUTO_SCROLL_MIN_SPEED_KEY, speed) } prefs.edit { putFloat(PDF_AUTO_SCROLL_MIN_SPEED_KEY, speed) }
@ -653,9 +687,6 @@ fun PdfViewerScreen(
var isAutoScrollPlaying by remember { mutableStateOf(false) } var isAutoScrollPlaying by remember { mutableStateOf(false) }
var isAutoScrollTempPaused by remember { mutableStateOf(false) } var isAutoScrollTempPaused by remember { mutableStateOf(false) }
val autoScrollResumeJob = remember { mutableStateOf<Job?>(null) } val autoScrollResumeJob = remember { mutableStateOf<Job?>(null) }
var autoScrollSpeed by remember { mutableFloatStateOf(loadPdfAutoScrollSpeed(context)) }
var autoScrollMinSpeed by remember { mutableFloatStateOf(loadPdfAutoScrollMinSpeed(context)) }
var autoScrollMaxSpeed by remember { mutableFloatStateOf(loadPdfAutoScrollMaxSpeed(context)) }
var isAutoScrollCollapsed by remember { mutableStateOf(false) } var isAutoScrollCollapsed by remember { mutableStateOf(false) }
var isAutoScrollLocked by remember { mutableStateOf(loadPdfAutoScrollLocked(context)) } var isAutoScrollLocked by remember { mutableStateOf(loadPdfAutoScrollLocked(context)) }
@ -681,7 +712,7 @@ fun PdfViewerScreen(
val onAutoScrollInteraction = remember { val onAutoScrollInteraction = remember {
{ {
if (isAutoScrollPlaying) { if (isAutoScrollPlaying) {
triggerAutoScrollTempPause(1000L) triggerAutoScrollTempPause(300L)
} }
} }
} }
@ -706,6 +737,98 @@ fun PdfViewerScreen(
derivedStateOf { isEditMode && !isDockMinimized } derivedStateOf { isEditMode && !isDockMinimized }
} }
var currentBookId by remember { mutableStateOf<String?>(null) }
val bookId = currentBookId ?: pdfUri.toString().hashCode().toString()
var isAutoScrollLocal by remember { mutableStateOf(loadPdfAutoScrollLocalMode(context, bookId)) }
LaunchedEffect(bookId) {
isAutoScrollLocal = loadPdfAutoScrollLocalMode(context, bookId)
}
val initialSettings = remember(isAutoScrollLocal, bookId) {
if (isAutoScrollLocal) {
loadPdfAutoScrollLocalSettings(context, bookId) ?: Triple(
loadPdfAutoScrollSpeed(context),
loadPdfAutoScrollMinSpeed(context),
loadPdfAutoScrollMaxSpeed(context)
)
} else {
Triple(
loadPdfAutoScrollSpeed(context),
loadPdfAutoScrollMinSpeed(context),
loadPdfAutoScrollMaxSpeed(context)
)
}
}
var autoScrollSpeed by remember { mutableFloatStateOf(initialSettings.first) }
var autoScrollMinSpeed by remember { mutableFloatStateOf(initialSettings.second) }
var autoScrollMaxSpeed by remember { mutableFloatStateOf(initialSettings.third) }
LaunchedEffect(initialSettings) {
autoScrollSpeed = initialSettings.first
autoScrollMinSpeed = initialSettings.second
autoScrollMaxSpeed = initialSettings.third
}
val onToggleAutoScrollMode = { newIsLocal: Boolean ->
isAutoScrollLocal = newIsLocal
savePdfAutoScrollLocalMode(context, bookId, newIsLocal)
if (newIsLocal) {
val existingLocal = loadPdfAutoScrollLocalSettings(context, bookId)
if (existingLocal == null) {
savePdfAutoScrollLocalSettings(context, bookId, autoScrollSpeed, autoScrollMinSpeed, autoScrollMaxSpeed)
} else {
autoScrollSpeed = existingLocal.first
autoScrollMinSpeed = existingLocal.second
autoScrollMaxSpeed = existingLocal.third
}
} else {
autoScrollSpeed = loadPdfAutoScrollSpeed(context)
autoScrollMinSpeed = loadPdfAutoScrollMinSpeed(context)
autoScrollMaxSpeed = loadPdfAutoScrollMaxSpeed(context)
}
}
val updateSpeed = { newSpeed: Float ->
autoScrollSpeed = newSpeed
if (isAutoScrollLocal) {
savePdfAutoScrollLocalSettings(context, bookId, newSpeed, autoScrollMinSpeed, autoScrollMaxSpeed)
} else {
savePdfAutoScrollSpeed(context, newSpeed)
}
}
val updateMinSpeed = { newMin: Float ->
autoScrollMinSpeed = newMin
if (isAutoScrollLocal) {
var currentMax = autoScrollMaxSpeed
var currentSpeed = autoScrollSpeed
if (currentMax < newMin) { currentMax = newMin; autoScrollMaxSpeed = newMin }
if (currentSpeed < newMin) { currentSpeed = newMin; autoScrollSpeed = newMin }
else if (currentSpeed > currentMax) { currentSpeed = currentMax; autoScrollSpeed = currentMax }
savePdfAutoScrollLocalSettings(context, bookId, currentSpeed, newMin, currentMax)
} else {
savePdfAutoScrollMinSpeed(context, newMin)
}
}
val updateMaxSpeed = { newMax: Float ->
autoScrollMaxSpeed = newMax
if (isAutoScrollLocal) {
var currentMin = autoScrollMinSpeed
var currentSpeed = autoScrollSpeed
if (currentMin > newMax) { currentMin = newMax; autoScrollMinSpeed = newMax }
if (currentSpeed > newMax) { currentSpeed = newMax; autoScrollSpeed = newMax }
else if (currentSpeed < currentMin) { currentSpeed = currentMin; autoScrollSpeed = currentMin }
savePdfAutoScrollLocalSettings(context, bookId, currentSpeed, currentMin, newMax)
} else {
savePdfAutoScrollMaxSpeed(context, newMax)
}
}
val (initialDockLocation, initialDockOffset) = remember(context) { loadDockState(context) } val (initialDockLocation, initialDockOffset) = remember(context) { loadDockState(context) }
var dockLocation by remember { mutableStateOf(initialDockLocation) } var dockLocation by remember { mutableStateOf(initialDockLocation) }
@ -816,7 +939,6 @@ fun PdfViewerScreen(
val pdfTextRepository = remember(context) { PdfTextRepository(context) } val pdfTextRepository = remember(context) { PdfTextRepository(context) }
val annotationRepository = remember(context) { PdfAnnotationRepository(context) } val annotationRepository = remember(context) { PdfAnnotationRepository(context) }
val textBoxRepository = remember(context) { PdfTextBoxRepository(context) } val textBoxRepository = remember(context) { PdfTextBoxRepository(context) }
var currentBookId by remember { mutableStateOf<String?>(null) }
var allAnnotations by remember { mutableStateOf<Map<Int, List<PdfAnnotation>>>(emptyMap()) } var allAnnotations by remember { mutableStateOf<Map<Int, List<PdfAnnotation>>>(emptyMap()) }
@ -1173,11 +1295,15 @@ fun PdfViewerScreen(
val onSingleTapStable = remember { val onSingleTapStable = remember {
{ {
if (isAutoScrollModeActive) {
isAutoScrollPlaying = !isAutoScrollPlaying
Timber.d("PDF Auto-scroll toggled via tap: $isAutoScrollPlaying")
}
if (selectedTextBoxId != null) { if (selectedTextBoxId != null) {
val box = textBoxes.find { it.id == selectedTextBoxId } val box = textBoxes.find { it.id == selectedTextBoxId }
if (box != null && box.text.trim().isEmpty()) { if (box != null && box.text.trim().isEmpty()) {
textBoxes.remove(box) textBoxes.remove(box)
Timber.tag("PdfTextBoxDebug").i("Removed empty text box: ${box.id}")
} }
selectedTextBoxId = null selectedTextBoxId = null
} else { } else {
@ -5662,42 +5788,50 @@ fun PdfViewerScreen(
AutoScrollControls( AutoScrollControls(
isPlaying = isAutoScrollPlaying, isPlaying = isAutoScrollPlaying,
isTempPaused = isAutoScrollTempPaused, isTempPaused = isAutoScrollTempPaused,
onPlayPauseToggle = { isAutoScrollPlaying = !isAutoScrollPlaying }, onPlayPauseToggle = {
if (isAutoScrollPlaying) {
isAutoScrollPlaying = false
isAutoScrollTempPaused = false
autoScrollResumeJob.value?.cancel()
} else {
isAutoScrollPlaying = true
isAutoScrollTempPaused = false
}
},
speed = autoScrollSpeed, speed = autoScrollSpeed,
minSpeed = autoScrollMinSpeed, minSpeed = autoScrollMinSpeed,
maxSpeed = autoScrollMaxSpeed, maxSpeed = autoScrollMaxSpeed,
onSpeedChange = { onSpeedChange = { updateSpeed(it) },
autoScrollSpeed = it
savePdfAutoScrollSpeed(context, it)
},
onMinSpeedChange = { newMin -> onMinSpeedChange = { newMin ->
autoScrollMinSpeed = newMin updateMinSpeed(newMin)
savePdfAutoScrollMinSpeed(context, newMin) if (!isAutoScrollLocal) {
if (autoScrollMaxSpeed < newMin) { if (autoScrollMaxSpeed < newMin) {
autoScrollMaxSpeed = newMin autoScrollMaxSpeed = newMin
savePdfAutoScrollMaxSpeed(context, newMin) savePdfAutoScrollMaxSpeed(context, newMin)
} }
if (autoScrollSpeed < newMin) { if (autoScrollSpeed < newMin) {
autoScrollSpeed = newMin autoScrollSpeed = newMin
savePdfAutoScrollSpeed(context, newMin) savePdfAutoScrollSpeed(context, newMin)
} else if (autoScrollSpeed > autoScrollMaxSpeed) { } else if (autoScrollSpeed > autoScrollMaxSpeed) {
autoScrollSpeed = autoScrollMaxSpeed autoScrollSpeed = autoScrollMaxSpeed
savePdfAutoScrollSpeed(context, autoScrollMaxSpeed) savePdfAutoScrollSpeed(context, autoScrollMaxSpeed)
}
} }
}, },
onMaxSpeedChange = { newMax -> onMaxSpeedChange = { newMax ->
autoScrollMaxSpeed = newMax updateMaxSpeed(newMax)
savePdfAutoScrollMaxSpeed(context, newMax) if (!isAutoScrollLocal) {
if (autoScrollMinSpeed > newMax) { if (autoScrollMinSpeed > newMax) {
autoScrollMinSpeed = newMax autoScrollMinSpeed = newMax
savePdfAutoScrollMinSpeed(context, newMax) savePdfAutoScrollMinSpeed(context, newMax)
} }
if (autoScrollSpeed > newMax) { if (autoScrollSpeed > newMax) {
autoScrollSpeed = newMax autoScrollSpeed = newMax
savePdfAutoScrollSpeed(context, newMax) savePdfAutoScrollSpeed(context, newMax)
} else if (autoScrollSpeed < autoScrollMinSpeed) { } else if (autoScrollSpeed < autoScrollMinSpeed) {
autoScrollSpeed = autoScrollMinSpeed autoScrollSpeed = autoScrollMinSpeed
savePdfAutoScrollSpeed(context, autoScrollMinSpeed) savePdfAutoScrollSpeed(context, autoScrollMinSpeed)
}
} }
}, },
onClose = { onClose = {
@ -5716,7 +5850,9 @@ fun PdfViewerScreen(
onInputModeToggle = { onInputModeToggle = {
autoScrollUseSlider = !autoScrollUseSlider autoScrollUseSlider = !autoScrollUseSlider
savePdfAutoScrollUseSlider(context, autoScrollUseSlider) savePdfAutoScrollUseSlider(context, autoScrollUseSlider)
} },
isLocalMode = isAutoScrollLocal,
onLocalModeToggle = onToggleAutoScrollMode
) )
} }
} }