diff --git a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt index a5b97dd..8c8d67a 100644 --- a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt +++ b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt @@ -68,6 +68,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack 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.ChevronLeft import androidx.compose.material.icons.filled.ChevronRight @@ -102,6 +103,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer @@ -740,7 +742,6 @@ fun SpeedDropdown( } } - @OptIn(ExperimentalMaterial3Api::class) @Composable fun AutoScrollControls( @@ -759,15 +760,19 @@ fun AutoScrollControls( onLockToggle: () -> Unit, useSlider: Boolean, onInputModeToggle: () -> Unit, + isLocalMode: Boolean, + onLocalModeToggle: (Boolean) -> Unit, modifier: Modifier = Modifier, isTempPaused: Boolean = false, ) { + val backgroundAlpha = 0.6f + Surface( shape = RoundedCornerShape(28.dp), - color = MaterialTheme.colorScheme.surfaceContainerHigh, - tonalElevation = 8.dp, - shadowElevation = 6.dp, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)), + color = MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = backgroundAlpha), + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f)), modifier = modifier .widthIn(max = 400.dp) .animateContentSize() @@ -780,14 +785,12 @@ fun AutoScrollControls( label = "AutoScrollUnified" ) { collapsed -> if (collapsed) { - // COLLAPSED STATE: Ultra-Compact Pill Row( modifier = Modifier .padding(horizontal = 6.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - // Expand Button IconButton( onClick = { onCollapseChange(false) }, modifier = Modifier.size(36.dp) @@ -799,7 +802,6 @@ fun AutoScrollControls( ) } - // Play/Pause Mini Box(modifier = Modifier.size(36.dp), contentAlignment = Alignment.Center) { FilledIconButton( onClick = onPlayPauseToggle, @@ -815,7 +817,7 @@ fun AutoScrollControls( modifier = Modifier.size(20.dp) ) } - if (isTempPaused) { + if (isTempPaused && isPlaying) { CircularProgressIndicator( modifier = Modifier.size(36.dp), color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f), @@ -825,26 +827,76 @@ fun AutoScrollControls( } } } else { - // EXPANDED STATE: Card-like Layout Column( modifier = Modifier.padding(16.dp) ) { - // Top Row: Utility Actions (Right aligned) + // Top Row: Label & Tools Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - // Label - Text( - text = "Auto Scroll", - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.primary - ) + Box { + var showModeMenu by remember { mutableStateOf(false) } + Row( + verticalAlignment = Alignment.CenterVertically, + 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)) { - // Lock IconButton( onClick = onLockToggle, modifier = Modifier.size(32.dp) @@ -856,7 +908,6 @@ fun AutoScrollControls( tint = if (isLocked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant ) } - // Swap Input IconButton( onClick = onInputModeToggle, modifier = Modifier.size(32.dp) @@ -868,7 +919,6 @@ fun AutoScrollControls( tint = MaterialTheme.colorScheme.onSurfaceVariant ) } - // Collapse IconButton( onClick = { onCollapseChange(true) }, modifier = Modifier.size(32.dp) @@ -880,7 +930,6 @@ fun AutoScrollControls( tint = MaterialTheme.colorScheme.onSurfaceVariant ) } - // Close IconButton( onClick = onClose, modifier = Modifier.size(32.dp) @@ -897,13 +946,13 @@ fun AutoScrollControls( Spacer(Modifier.height(16.dp)) - // Bottom Row: Primary Controls + // Bottom Row: Controls Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - // 1. Play/Pause Button + // Play/Pause Box(modifier = Modifier.size(48.dp), contentAlignment = Alignment.Center) { FilledIconButton( onClick = onPlayPauseToggle, @@ -919,7 +968,7 @@ fun AutoScrollControls( modifier = Modifier.size(24.dp) ) } - if (isTempPaused) { + if (isTempPaused && isPlaying) { CircularProgressIndicator( modifier = Modifier.size(48.dp), color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f), @@ -928,14 +977,13 @@ fun AutoScrollControls( } } - // 2. Speed Controls (Weight to fill space) + // Speed Controls Box( modifier = Modifier.weight(1f), contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val speedOptions = listOf(0.1f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f) - // Min/Max Dropdowns Row( modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp), horizontalArrangement = Arrangement.SpaceBetween @@ -958,7 +1006,6 @@ fun AutoScrollControls( val safeMax = maxSpeed.coerceAtLeast(minSpeed + 0.1f) if (useSlider) { - // Slider Mode: Text + Slider Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) @@ -978,20 +1025,42 @@ fun AutoScrollControls( steps = if (steps > 0) steps else 0, modifier = Modifier.weight(1f), thumb = { - Surface( - modifier = Modifier.size(20.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.primary, - shadowElevation = 2.dp - ) {} + Box( + modifier = Modifier + .size(20.dp) + .background(MaterialTheme.colorScheme.primary, CircleShape) + ) + }, + 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 { - // Stepper Mode: Segmented Pill Surface( shape = RoundedCornerShape(50), - color = MaterialTheme.colorScheme.surfaceVariant, + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), modifier = Modifier.height(48.dp).fillMaxWidth() ) { Row( @@ -1005,13 +1074,11 @@ fun AutoScrollControls( ) { Icon(Icons.Default.Remove, "Slower") } - Text( text = "%.1fx".format(speed), style = MaterialTheme.typography.titleMedium.copy(fontFeatureSettings = "tnum"), textAlign = TextAlign.Center ) - IconButton( onClick = { onSpeedChange((speed + 0.1f).coerceAtMost(safeMax)) }, modifier = Modifier.size(48.dp) diff --git a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt index 4bb56d5..57bb784 100644 --- a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt +++ b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt @@ -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 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? { + 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) { val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE) 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) } + 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 { mutableStateOf(loadHighlightPalette(context)) } @@ -1740,6 +1859,11 @@ fun EpubReaderHost( } }, onTap = { + if (isAutoScrollModeActive) { + isAutoScrollPlaying = !isAutoScrollPlaying + Timber.d("Auto-scroll toggled via tap: $isAutoScrollPlaying") + } + if (System.currentTimeMillis() - lastHighlightClickTime > 500) { focusManager.clearFocus() if (volumeScrollEnabled && !searchState.isSearchActive) { @@ -1762,7 +1886,7 @@ fun EpubReaderHost( Timber.d("Scroll/Drag detected, hiding bars.") } if (isAutoScrollModeActive && isAutoScrollPlaying) { - triggerAutoScrollTempPause(1000L) + triggerAutoScrollTempPause(300L) } }, onAutoScrollChapterEnd = { @@ -2796,38 +2920,37 @@ fun EpubReaderHost( speed = autoScrollSpeed, minSpeed = autoScrollMinSpeed, maxSpeed = autoScrollMaxSpeed, - onSpeedChange = { - autoScrollSpeed = it - saveAutoScrollSpeed(context, it) - }, + onSpeedChange = { updateSpeed(it) }, onMinSpeedChange = { newMin -> - autoScrollMinSpeed = newMin - saveAutoScrollMinSpeed(context, newMin) - if (autoScrollMaxSpeed < newMin) { - autoScrollMaxSpeed = newMin - saveAutoScrollMaxSpeed(context, newMin) - } - if (autoScrollSpeed < newMin) { - autoScrollSpeed = newMin - saveAutoScrollSpeed(context, newMin) - } else if (autoScrollSpeed > autoScrollMaxSpeed) { - autoScrollSpeed = autoScrollMaxSpeed - saveAutoScrollSpeed(context, autoScrollMaxSpeed) + updateMinSpeed(newMin) + if (!isAutoScrollLocal) { + if (autoScrollMaxSpeed < newMin) { + autoScrollMaxSpeed = newMin + saveAutoScrollMaxSpeed(context, newMin) + } + if (autoScrollSpeed < newMin) { + autoScrollSpeed = newMin + saveAutoScrollSpeed(context, newMin) + } else if (autoScrollSpeed > autoScrollMaxSpeed) { + autoScrollSpeed = autoScrollMaxSpeed + saveAutoScrollSpeed(context, autoScrollMaxSpeed) + } } }, onMaxSpeedChange = { newMax -> - autoScrollMaxSpeed = newMax - saveAutoScrollMaxSpeed(context, newMax) - if (autoScrollMinSpeed > newMax) { - autoScrollMinSpeed = newMax - saveAutoScrollMinSpeed(context, newMax) - } - if (autoScrollSpeed > newMax) { - autoScrollSpeed = newMax - saveAutoScrollSpeed(context, newMax) - } else if (autoScrollSpeed < autoScrollMinSpeed) { - autoScrollSpeed = autoScrollMinSpeed - saveAutoScrollSpeed(context, autoScrollMinSpeed) + updateMaxSpeed(newMax) + if (!isAutoScrollLocal) { + if (autoScrollMinSpeed > newMax) { + autoScrollMinSpeed = newMax + saveAutoScrollMinSpeed(context, newMax) + } + if (autoScrollSpeed > newMax) { + autoScrollSpeed = newMax + saveAutoScrollSpeed(context, newMax) + } else if (autoScrollSpeed < autoScrollMinSpeed) { + autoScrollSpeed = autoScrollMinSpeed + saveAutoScrollSpeed(context, autoScrollMinSpeed) + } } }, onClose = { @@ -2846,7 +2969,9 @@ fun EpubReaderHost( onInputModeToggle = { autoScrollUseSlider = !autoScrollUseSlider saveAutoScrollUseSlider(context, autoScrollUseSlider) - } + }, + isLocalMode = isAutoScrollLocal, + onLocalModeToggle = onToggleAutoScrollMode ) } diff --git a/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt b/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt index 3d04013..dddda4f 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt @@ -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 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? { + 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) { val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE) prefs.edit { putFloat(PDF_AUTO_SCROLL_MIN_SPEED_KEY, speed) } @@ -653,9 +687,6 @@ fun PdfViewerScreen( var isAutoScrollPlaying by remember { mutableStateOf(false) } var isAutoScrollTempPaused by remember { mutableStateOf(false) } val autoScrollResumeJob = remember { mutableStateOf(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 isAutoScrollLocked by remember { mutableStateOf(loadPdfAutoScrollLocked(context)) } @@ -681,7 +712,7 @@ fun PdfViewerScreen( val onAutoScrollInteraction = remember { { if (isAutoScrollPlaying) { - triggerAutoScrollTempPause(1000L) + triggerAutoScrollTempPause(300L) } } } @@ -706,6 +737,98 @@ fun PdfViewerScreen( derivedStateOf { isEditMode && !isDockMinimized } } + var currentBookId by remember { mutableStateOf(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) } var dockLocation by remember { mutableStateOf(initialDockLocation) } @@ -816,7 +939,6 @@ fun PdfViewerScreen( val pdfTextRepository = remember(context) { PdfTextRepository(context) } val annotationRepository = remember(context) { PdfAnnotationRepository(context) } val textBoxRepository = remember(context) { PdfTextBoxRepository(context) } - var currentBookId by remember { mutableStateOf(null) } var allAnnotations by remember { mutableStateOf>>(emptyMap()) } @@ -1173,11 +1295,15 @@ fun PdfViewerScreen( val onSingleTapStable = remember { { + if (isAutoScrollModeActive) { + isAutoScrollPlaying = !isAutoScrollPlaying + Timber.d("PDF Auto-scroll toggled via tap: $isAutoScrollPlaying") + } + if (selectedTextBoxId != null) { val box = textBoxes.find { it.id == selectedTextBoxId } if (box != null && box.text.trim().isEmpty()) { textBoxes.remove(box) - Timber.tag("PdfTextBoxDebug").i("Removed empty text box: ${box.id}") } selectedTextBoxId = null } else { @@ -5662,42 +5788,50 @@ fun PdfViewerScreen( AutoScrollControls( isPlaying = isAutoScrollPlaying, isTempPaused = isAutoScrollTempPaused, - onPlayPauseToggle = { isAutoScrollPlaying = !isAutoScrollPlaying }, + onPlayPauseToggle = { + if (isAutoScrollPlaying) { + isAutoScrollPlaying = false + isAutoScrollTempPaused = false + autoScrollResumeJob.value?.cancel() + } else { + isAutoScrollPlaying = true + isAutoScrollTempPaused = false + } + }, speed = autoScrollSpeed, minSpeed = autoScrollMinSpeed, maxSpeed = autoScrollMaxSpeed, - onSpeedChange = { - autoScrollSpeed = it - savePdfAutoScrollSpeed(context, it) - }, + onSpeedChange = { updateSpeed(it) }, onMinSpeedChange = { newMin -> - autoScrollMinSpeed = newMin - savePdfAutoScrollMinSpeed(context, newMin) - if (autoScrollMaxSpeed < newMin) { - autoScrollMaxSpeed = newMin - savePdfAutoScrollMaxSpeed(context, newMin) - } - if (autoScrollSpeed < newMin) { - autoScrollSpeed = newMin - savePdfAutoScrollSpeed(context, newMin) - } else if (autoScrollSpeed > autoScrollMaxSpeed) { - autoScrollSpeed = autoScrollMaxSpeed - savePdfAutoScrollSpeed(context, autoScrollMaxSpeed) + updateMinSpeed(newMin) + if (!isAutoScrollLocal) { + if (autoScrollMaxSpeed < newMin) { + autoScrollMaxSpeed = newMin + savePdfAutoScrollMaxSpeed(context, newMin) + } + if (autoScrollSpeed < newMin) { + autoScrollSpeed = newMin + savePdfAutoScrollSpeed(context, newMin) + } else if (autoScrollSpeed > autoScrollMaxSpeed) { + autoScrollSpeed = autoScrollMaxSpeed + savePdfAutoScrollSpeed(context, autoScrollMaxSpeed) + } } }, onMaxSpeedChange = { newMax -> - autoScrollMaxSpeed = newMax - savePdfAutoScrollMaxSpeed(context, newMax) - if (autoScrollMinSpeed > newMax) { - autoScrollMinSpeed = newMax - savePdfAutoScrollMinSpeed(context, newMax) - } - if (autoScrollSpeed > newMax) { - autoScrollSpeed = newMax - savePdfAutoScrollSpeed(context, newMax) - } else if (autoScrollSpeed < autoScrollMinSpeed) { - autoScrollSpeed = autoScrollMinSpeed - savePdfAutoScrollSpeed(context, autoScrollMinSpeed) + updateMaxSpeed(newMax) + if (!isAutoScrollLocal) { + if (autoScrollMinSpeed > newMax) { + autoScrollMinSpeed = newMax + savePdfAutoScrollMinSpeed(context, newMax) + } + if (autoScrollSpeed > newMax) { + autoScrollSpeed = newMax + savePdfAutoScrollSpeed(context, newMax) + } else if (autoScrollSpeed < autoScrollMinSpeed) { + autoScrollSpeed = autoScrollMinSpeed + savePdfAutoScrollSpeed(context, autoScrollMinSpeed) + } } }, onClose = { @@ -5716,7 +5850,9 @@ fun PdfViewerScreen( onInputModeToggle = { autoScrollUseSlider = !autoScrollUseSlider savePdfAutoScrollUseSlider(context, autoScrollUseSlider) - } + }, + isLocalMode = isAutoScrollLocal, + onLocalModeToggle = onToggleAutoScrollMode ) } }