From eaf0d4af00953846998c6e9d02172cabb2353bf4 Mon Sep 17 00:00:00 2001 From: Aryan Date: Thu, 19 Mar 2026 15:48:03 +0530 Subject: [PATCH] Added adjustable eraser thickness and improved tool selection logic for highlighter (#95) --- .../com/aryan/reader/pdf/AnnotationDock.kt | 13 +- .../com/aryan/reader/pdf/PdfPageComposable.kt | 10 +- .../com/aryan/reader/pdf/PdfVerticalReader.kt | 8 +- .../com/aryan/reader/pdf/PdfViewerScreen.kt | 20 +- .../com/aryan/reader/pdf/ToolSettingsPopup.kt | 272 ++++++++++-------- .../pdf/data/AnnotationSettingsRepository.kt | 9 + 6 files changed, 201 insertions(+), 131 deletions(-) diff --git a/app/src/main/java/com/aryan/reader/pdf/AnnotationDock.kt b/app/src/main/java/com/aryan/reader/pdf/AnnotationDock.kt index 0a8b850..4167c3f 100644 --- a/app/src/main/java/com/aryan/reader/pdf/AnnotationDock.kt +++ b/app/src/main/java/com/aryan/reader/pdf/AnnotationDock.kt @@ -67,6 +67,7 @@ fun AnnotationDock( canUndo: Boolean, canRedo: Boolean, lastPenTool: InkType, + lastHighlighterTool: InkType = InkType.HIGHLIGHTER, modifier: Modifier = Modifier, isSticky: Boolean = false, isMinimized: Boolean, @@ -180,7 +181,15 @@ fun AnnotationDock( description = "Pen", size = buttonSize, iconSize = iconSize, - onClick = { if(!isMinimized) onToolClick(lastPenTool) } + onClick = { + if(!isMinimized) { + if (selectedTool != InkType.PEN && selectedTool != InkType.FOUNTAIN_PEN && selectedTool != InkType.PENCIL) { + onToolClick(lastPenTool) + } else { + onToolClick(selectedTool) + } + } + } ) // Highlighter @@ -195,7 +204,7 @@ fun AnnotationDock( onClick = { if (!isMinimized) { if (selectedTool != InkType.HIGHLIGHTER && selectedTool != InkType.HIGHLIGHTER_ROUND) { - onToolClick(InkType.HIGHLIGHTER) + onToolClick(lastHighlighterTool) } else { onToolClick(selectedTool) } diff --git a/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt b/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt index a2bc33f..53624e5 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt @@ -438,6 +438,7 @@ internal fun PdfPageComposable( onHighlightUpdate: (String, PdfHighlightColor) -> Unit = { _,_ -> }, onHighlightDelete: (String) -> Unit = {}, onTts: (Int, Int) -> Unit = { _, _ -> }, + activeToolThickness: Float = 0f ) { val pdfDocumentItem = pdfDocument.item var bitmapState by remember { mutableStateOf(PdfThumbnailCache.get(pageIndex)) } @@ -3775,6 +3776,7 @@ internal fun PdfPageComposable( isEditMode = isEditMode, selectedTool = selectedTool, eraserPosition = eraserPosition, + activeToolThickness = activeToolThickness, richTextController = richTextController, textBoxes = textBoxes, selectedTextBoxId = selectedTextBoxId, @@ -4516,6 +4518,8 @@ private fun PdfPageRenderer( onHighlightUpdate: (String, PdfHighlightColor) -> Unit, onHighlightDelete: (String) -> Unit, onTts: (Int, Int) -> Unit, + activeToolThickness: Float + ) { Box(modifier = Modifier.fillMaxSize()) { Box( @@ -4704,7 +4708,11 @@ private fun PdfPageRenderer( if (isEditMode && selectedTool == InkType.ERASER && eraserPosition != null) { Canvas(modifier = Modifier.fillMaxSize()) { - val radiusPx = 8.dp.toPx() + val radiusPx = if (activeToolThickness > 0f && staticData.targetWidth > 0) { + activeToolThickness * staticData.targetWidth * scale // Calculate dynamic size based on tool settings scale + } else { + 8.dp.toPx() + } drawCircle( color = Color.White.copy(alpha = 0.3f), diff --git a/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt b/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt index 50db84d..1e00043 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt @@ -231,6 +231,7 @@ internal fun PdfVerticalReader( onHighlightUpdate: (String, PdfHighlightColor) -> Unit = { _,_ -> }, onHighlightDelete: (String) -> Unit = {}, onTts: (Int, Int) -> Unit = { _, _ -> }, + activeToolThickness: Float = 0f ) { SideEffect { Timber.tag("PdfDrawPerf").v("LIST: PdfVerticalReader Recomposing.") } var globalEraserPosition by remember { mutableStateOf(null) } @@ -1512,6 +1513,7 @@ internal fun PdfVerticalReader( onHighlightUpdate = onHighlightUpdate, onHighlightDelete = onHighlightDelete, onTts = onTts, + activeToolThickness = activeToolThickness, onTextBoxDragStart = { box, localTopLeft, touchOffset -> val currentZoom = zoomAnimatable.value val panX = panXAnimatable.value @@ -1911,7 +1913,11 @@ internal fun PdfVerticalReader( if (isEditMode && selectedTool == InkType.ERASER && globalEraserPosition != null) { Canvas(modifier = Modifier.fillMaxSize()) { val pos = globalEraserPosition!! - val radiusPx = 8.dp.toPx() + val radiusPx = if (activeToolThickness > 0f) { + activeToolThickness * screenWidth * zoomAnimatable.value + } else { + 8.dp.toPx() + } drawCircle(color = Color.White.copy(alpha = 0.3f), radius = radiusPx, center = pos) 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 3e1e3dc..f2851a2 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt @@ -1374,8 +1374,9 @@ fun PdfViewerScreen( val selectedTool = toolSettings.getActiveTool() val lastPenTool = toolSettings.getLastPenTool() + val lastHighlighterTool = toolSettings.getLastHighlighterTool() val dockPenColor = toolSettings.getToolColor(lastPenTool) - val dockHighlighterColor = toolSettings.getToolColor(InkType.HIGHLIGHTER) + val dockHighlighterColor = toolSettings.getToolColor(lastHighlighterTool) val activeToolColor = toolSettings.getToolColor(selectedTool) val activeToolThickness = toolSettings.getToolThickness(selectedTool) @@ -2714,7 +2715,7 @@ fun PdfViewerScreen( annotation: PdfAnnotation, hitPoint: PdfPoint, pageAspectRatio: Float, - threshold: Float = 0.025f + threshold: Float ): Boolean { if (annotation.points.isEmpty()) return false @@ -4242,7 +4243,7 @@ fun PdfViewerScreen( val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit(it, point, aspectRatio) + isAnnotationHit(it, point, aspectRatio, activeToolThickness) } if (toRemove.isNotEmpty()) { val batch = @@ -4289,7 +4290,7 @@ fun PdfViewerScreen( val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit(it, point, aspectRatio) + isAnnotationHit(it, point, aspectRatio, activeToolThickness) } if (toRemove.isNotEmpty()) { val batch = @@ -4405,6 +4406,7 @@ fun PdfViewerScreen( onHighlightUpdate = onHighlightUpdate, onHighlightDelete = onHighlightDelete, onTts = { pageIdx, charIdx -> startTtsWithPermissionCheck(pageIdx, charIdx) }, + activeToolThickness = currentStrokeWidthState, onTwoFingerSwipe = { direction -> coroutineScope.launch { val targetPage = @@ -4632,7 +4634,7 @@ fun PdfViewerScreen( val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit(it, point, aspectRatio) + isAnnotationHit(it, point, aspectRatio, activeToolThickness) } if (toRemove.isNotEmpty()) { val batch = @@ -4670,7 +4672,7 @@ fun PdfViewerScreen( val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit(it, point, aspectRatio) + isAnnotationHit(it, point, aspectRatio, activeToolThickness) } if (toRemove.isNotEmpty()) { val batch = @@ -4737,6 +4739,7 @@ fun PdfViewerScreen( onHighlightUpdate = onHighlightUpdate, onHighlightDelete = onHighlightDelete, onTts = { pageIdx, charIdx -> startTtsWithPermissionCheck(pageIdx, charIdx) }, + activeToolThickness = currentStrokeWidthState, onLinkClicked = onLinkClickedStable, onInternalLinkClicked = onInternalLinkNavStable, bookmarks = bookmarksHolder, @@ -6386,19 +6389,20 @@ fun PdfViewerScreen( activePenColor = dockPenColor, activeHighlighterColor = dockHighlighterColor, lastPenTool = lastPenTool, + lastHighlighterTool = lastHighlighterTool, isStylusOnlyMode = isStylusOnlyMode, onToggleStylusOnlyMode = { isStylusOnlyMode = !isStylusOnlyMode saveStylusOnlyMode(context, isStylusOnlyMode) }, onToolClick = { clickedTool -> - if (clickedTool == InkType.ERASER || clickedTool == InkType.TEXT) { + if (clickedTool == InkType.TEXT) { annotationSettingsRepo.updateSelectedTool( clickedTool ) showToolSettings = false } else if (selectedTool == clickedTool) { - if (clickedTool == InkType.PEN || clickedTool == InkType.FOUNTAIN_PEN || clickedTool == InkType.PENCIL || clickedTool == InkType.HIGHLIGHTER || clickedTool == InkType.HIGHLIGHTER_ROUND) { + if (clickedTool == InkType.PEN || clickedTool == InkType.FOUNTAIN_PEN || clickedTool == InkType.PENCIL || clickedTool == InkType.HIGHLIGHTER || clickedTool == InkType.HIGHLIGHTER_ROUND || clickedTool == InkType.ERASER) { showToolSettings = !showToolSettings } } else { diff --git a/app/src/main/java/com/aryan/reader/pdf/ToolSettingsPopup.kt b/app/src/main/java/com/aryan/reader/pdf/ToolSettingsPopup.kt index ee322f5..cde0510 100644 --- a/app/src/main/java/com/aryan/reader/pdf/ToolSettingsPopup.kt +++ b/app/src/main/java/com/aryan/reader/pdf/ToolSettingsPopup.kt @@ -110,6 +110,7 @@ fun ToolSettingsPopup( onSnapToggle: (Boolean) -> Unit = {} ) { val isHighlighter = selectedTool == InkType.HIGHLIGHTER || selectedTool == InkType.HIGHLIGHTER_ROUND + val isEraser = selectedTool == InkType.ERASER val activeColor = when (selectedTool) { InkType.FOUNTAIN_PEN -> fountainPenColor @@ -117,6 +118,7 @@ fun ToolSettingsPopup( InkType.PENCIL -> pencilColor InkType.HIGHLIGHTER -> highlighterColor InkType.HIGHLIGHTER_ROUND -> highlighterRoundColor + InkType.ERASER -> Color.White else -> markerColor } @@ -130,8 +132,11 @@ fun ToolSettingsPopup( } } - // Thickness settings - val thicknessRange = if (isHighlighter) 0.01f..0.06f else 0.001f..0.015f + val thicknessRange = when { + isHighlighter -> 0.01f..0.06f + isEraser -> 0.01f..0.1f + else -> 0.001f..0.015f + } @Suppress("UnusedExpression") if (isHighlighter) 0.005f else 0.001f var showColorPicker by remember { mutableStateOf(false) } @@ -164,65 +169,92 @@ fun ToolSettingsPopup( modifier = Modifier.padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(125.dp), - contentAlignment = Alignment.BottomCenter - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(28.dp), - verticalAlignment = Alignment.Bottom + if (isEraser) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(125.dp), + contentAlignment = Alignment.Center ) { - if (isHighlighter) { - PenItem( - type = PenType.HIGHLIGHTER, - forcedInkType = InkType.HIGHLIGHTER, - color = highlighterColor.copy(alpha = 1f), - inkColor = highlighterColor, - isSelected = selectedTool == InkType.HIGHLIGHTER, - strokeWidth = activeToolThickness, - onClick = { onToolTypeChanged(InkType.HIGHLIGHTER) }, - isSnappingEnabled = isHighlighterSnapEnabled - ) + val radiusDp = (activeToolThickness * 1000).coerceIn(10f, 100f).dp + Box( + modifier = Modifier.size(radiusDp), + contentAlignment = Alignment.Center + ) { + Canvas(modifier = Modifier.fillMaxSize()) { + drawCircle( + color = Color.White.copy(alpha = 0.3f), + radius = size.width / 2 + ) + drawCircle( + color = Color.White, + radius = size.width / 2, + style = Stroke(width = 2.dp.toPx()) + ) + } + } + } + } else { + Box( + modifier = Modifier + .fillMaxWidth() + .height(125.dp), + contentAlignment = Alignment.BottomCenter + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(28.dp), + verticalAlignment = Alignment.Bottom + ) { + if (isHighlighter) { + PenItem( + type = PenType.HIGHLIGHTER, + forcedInkType = InkType.HIGHLIGHTER, + color = highlighterColor.copy(alpha = 1f), + inkColor = highlighterColor, + isSelected = selectedTool == InkType.HIGHLIGHTER, + strokeWidth = activeToolThickness, + onClick = { onToolTypeChanged(InkType.HIGHLIGHTER) }, + isSnappingEnabled = isHighlighterSnapEnabled + ) - PenItem( - type = PenType.HIGHLIGHTER_ROUND, - forcedInkType = InkType.HIGHLIGHTER_ROUND, - color = highlighterRoundColor.copy(alpha = 1f), - inkColor = highlighterRoundColor, - isSelected = selectedTool == InkType.HIGHLIGHTER_ROUND, - strokeWidth = activeToolThickness, - onClick = { onToolTypeChanged(InkType.HIGHLIGHTER_ROUND) }, - isSnappingEnabled = isHighlighterSnapEnabled - ) - } else { - PenItem( - type = PenType.FOUNTAIN_PEN, - forcedInkType = InkType.FOUNTAIN_PEN, - color = fountainPenColor, - isSelected = selectedTool == InkType.FOUNTAIN_PEN, - strokeWidth = activeToolThickness, - onClick = { onToolTypeChanged(InkType.FOUNTAIN_PEN) } - ) + PenItem( + type = PenType.HIGHLIGHTER_ROUND, + forcedInkType = InkType.HIGHLIGHTER_ROUND, + color = highlighterRoundColor.copy(alpha = 1f), + inkColor = highlighterRoundColor, + isSelected = selectedTool == InkType.HIGHLIGHTER_ROUND, + strokeWidth = activeToolThickness, + onClick = { onToolTypeChanged(InkType.HIGHLIGHTER_ROUND) }, + isSnappingEnabled = isHighlighterSnapEnabled + ) + } else { + PenItem( + type = PenType.FOUNTAIN_PEN, + forcedInkType = InkType.FOUNTAIN_PEN, + color = fountainPenColor, + isSelected = selectedTool == InkType.FOUNTAIN_PEN, + strokeWidth = activeToolThickness, + onClick = { onToolTypeChanged(InkType.FOUNTAIN_PEN) } + ) - PenItem( - type = PenType.MARKER, - forcedInkType = InkType.PEN, - color = markerColor, - isSelected = selectedTool == InkType.PEN, - strokeWidth = activeToolThickness, - onClick = { onToolTypeChanged(InkType.PEN) } - ) + PenItem( + type = PenType.MARKER, + forcedInkType = InkType.PEN, + color = markerColor, + isSelected = selectedTool == InkType.PEN, + strokeWidth = activeToolThickness, + onClick = { onToolTypeChanged(InkType.PEN) } + ) - PenItem( - type = PenType.PENCIL, - forcedInkType = InkType.PENCIL, - color = pencilColor, - isSelected = selectedTool == InkType.PENCIL, - strokeWidth = activeToolThickness, - onClick = { onToolTypeChanged(InkType.PENCIL) } - ) + PenItem( + type = PenType.PENCIL, + forcedInkType = InkType.PENCIL, + color = pencilColor, + isSelected = selectedTool == InkType.PENCIL, + strokeWidth = activeToolThickness, + onClick = { onToolTypeChanged(InkType.PENCIL) } + ) + } } } } @@ -262,7 +294,7 @@ fun ToolSettingsPopup( valueRange = thicknessRange, isOpacity = false, trackColor = Color(0xFF424242), thumbColor = Color(0xFF757575), - activeColor = activeColor + activeColor = if (isEraser) Color.White else activeColor ) // Darkness (Opacity) Slider for Highlighters @@ -281,80 +313,82 @@ fun ToolSettingsPopup( ) } - Spacer(Modifier.height(16.dp)) // Reduced spacing + if (!isEraser) { + Spacer(Modifier.height(16.dp)) // Reduced spacing - // --- Color Palette --- - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { + // --- Color Palette --- Row( - modifier = Modifier.weight(1f), - horizontalArrangement = Arrangement.SpaceBetween + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically ) { - activePalette.take(6).forEachIndexed { index, color -> - val isSelected = index == selectedPaletteIndex + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.SpaceBetween + ) { + activePalette.take(6).forEachIndexed { index, color -> + val isSelected = index == selectedPaletteIndex - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .size(circleSize) - .testTag("Palette_Item_$index") - .pointerInput(color) { - detectTapGestures( - onTap = { - currentOnColorChanged(color) - }, - onLongPress = { - colorPickerSlotIndex = index - showColorPicker = true - } - ) - } - ) { - Canvas(modifier = Modifier.fillMaxSize()) { - drawCircle(color = color.copy(alpha = 1f)) - if (isSelected) { - drawCircle( - color = Color.White, - radius = size.minDimension / 2, - style = Stroke(width = 2.dp.toPx()) - ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(circleSize) + .testTag("Palette_Item_$index") + .pointerInput(color) { + detectTapGestures( + onTap = { + currentOnColorChanged(color) + }, + onLongPress = { + colorPickerSlotIndex = index + showColorPicker = true + } + ) + } + ) { + Canvas(modifier = Modifier.fillMaxSize()) { + drawCircle(color = color.copy(alpha = 1f)) + if (isSelected) { + drawCircle( + color = Color.White, + radius = size.minDimension / 2, + style = Stroke(width = 2.dp.toPx()) + ) + } } } } } - } - Spacer(Modifier.width(16.dp)) + Spacer(Modifier.width(16.dp)) - // Divider - Box( - modifier = Modifier - .width(1.dp) - .height(circleSize) - .background(Color.White.copy(alpha = 0.15f)) - ) + // Divider + Box( + modifier = Modifier + .width(1.dp) + .height(circleSize) + .background(Color.White.copy(alpha = 0.15f)) + ) - Spacer(Modifier.width(16.dp)) + Spacer(Modifier.width(16.dp)) - // Spectrum / Color Wheel Button - val rainbowColors = listOf( - Color.Red, Color.Magenta, Color.Blue, Color.Cyan, Color.Green, Color.Yellow, Color.Red - ) - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .size(circleSize) - .clip(CircleShape) - .background(Brush.sweepGradient(rainbowColors)) - .clickable { - if (selectedPaletteIndex != -1) { - colorPickerSlotIndex = selectedPaletteIndex - showColorPicker = true + // Spectrum / Color Wheel Button + val rainbowColors = listOf( + Color.Red, Color.Magenta, Color.Blue, Color.Cyan, Color.Green, Color.Yellow, Color.Red + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(circleSize) + .clip(CircleShape) + .background(Brush.sweepGradient(rainbowColors)) + .clickable { + if (selectedPaletteIndex != -1) { + colorPickerSlotIndex = selectedPaletteIndex + showColorPicker = true + } } - } - ) {} + ) {} + } } } } diff --git a/app/src/main/java/com/aryan/reader/pdf/data/AnnotationSettingsRepository.kt b/app/src/main/java/com/aryan/reader/pdf/data/AnnotationSettingsRepository.kt index e8798ae..032c5b8 100644 --- a/app/src/main/java/com/aryan/reader/pdf/data/AnnotationSettingsRepository.kt +++ b/app/src/main/java/com/aryan/reader/pdf/data/AnnotationSettingsRepository.kt @@ -59,6 +59,7 @@ data class TextStyleConfig( data class AnnotationToolSettings( val selectedToolName: String = "PEN", val lastActivePenType: String = "PEN", + val lastActiveHighlighterType: String = "HIGHLIGHTER", val toolConfigs: Map = emptyMap(), val penPaletteArgb: List = listOf( android.graphics.Color.BLACK, @@ -101,6 +102,12 @@ data class AnnotationToolSettings( fun getPenPalette(): List = penPaletteArgb.map { Color(it) } fun getHighlighterPalette(): List = highlighterPaletteArgb.map { Color(it) } + + fun getLastHighlighterTool(): InkType = try { + InkType.valueOf(lastActiveHighlighterType) + } catch (_: Exception) { + InkType.HIGHLIGHTER + } } class AnnotationSettingsRepository(context: Context) { @@ -154,6 +161,8 @@ class AnnotationSettingsRepository(context: Context) { if (tool == InkType.PEN || tool == InkType.FOUNTAIN_PEN || tool == InkType.PENCIL) { currentSettings = currentSettings.copy(lastActivePenType = tool.name) + } else if (tool == InkType.HIGHLIGHTER || tool == InkType.HIGHLIGHTER_ROUND) { + currentSettings = currentSettings.copy(lastActiveHighlighterType = tool.name) // ADD THIS } saveSettings(currentSettings)