From 8c7c8cb48e435774a2e93bbd47c8011585a73682 Mon Sep 17 00:00:00 2001 From: Aryan Date: Sun, 8 Mar 2026 20:19:59 +0530 Subject: [PATCH] Feature/highlighter snap (#48) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: fix Home and Library screen stutter by removing main thread file checks * feat(pdf): implement straight-line highlighter snap - Added "Straight Line" toggle to highlighter settings popup with persistent state. - Implemented "rubber-band" drawing effect for highlighters with cardinal direction snapping (0°, 90°, 180°, 270°). - Optimized gesture loops in Vertical and Paginated readers to respond instantly to setting toggles. - Rewrote eraser hit detection using point-to-line segment distance math for pixel-perfect erasure of straight lines. --- .../main/java/com/aryan/reader/HomeScreen.kt | 6 +- .../java/com/aryan/reader/LibraryScreen.kt | 6 +- .../com/aryan/reader/pdf/PdfPageComposable.kt | 16 ++- .../com/aryan/reader/pdf/PdfVerticalReader.kt | 20 ++- .../com/aryan/reader/pdf/PdfViewerScreen.kt | 135 +++++++++++++----- .../java/com/aryan/reader/pdf/PenIcons.kt | 27 ++-- .../com/aryan/reader/pdf/ToolSettingsPopup.kt | 47 +++++- .../pdf/data/AnnotationSettingsRepository.kt | 7 +- 8 files changed, 203 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/com/aryan/reader/HomeScreen.kt b/app/src/main/java/com/aryan/reader/HomeScreen.kt index 301f9e6..f5118fe 100644 --- a/app/src/main/java/com/aryan/reader/HomeScreen.kt +++ b/app/src/main/java/com/aryan/reader/HomeScreen.kt @@ -474,7 +474,7 @@ private fun RecentFilesContent( androidx.compose.material3.Button(onClick = onSelectFileClick) { Text("Select File") } - androidx.compose.material3.OutlinedButton(onClick = onNavigateToFolderSync) { + androidx.compose.material3.Button(onClick = onNavigateToFolderSync) { Text("Sync Folder") } } @@ -552,7 +552,7 @@ fun RecentFileCard( FileType.EPUB, FileType.MOBI, FileType.MD, FileType.TXT, FileType.HTML -> R.drawable.epub_placeholder } val imageModel = remember(item.coverImagePath) { - item.coverImagePath?.let { File(it) }?.takeIf { it.exists() } ?: placeholder + item.coverImagePath?.let { File(it) } ?: placeholder } Surface( @@ -584,7 +584,7 @@ fun RecentFileCard( .align(Alignment.TopEnd) .padding(8.dp) .background( - color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.9f), + color = MaterialTheme.colorScheme.secondaryContainer, shape = CircleShape ) .padding(4.dp) diff --git a/app/src/main/java/com/aryan/reader/LibraryScreen.kt b/app/src/main/java/com/aryan/reader/LibraryScreen.kt index de43cc0..4a0ae56 100644 --- a/app/src/main/java/com/aryan/reader/LibraryScreen.kt +++ b/app/src/main/java/com/aryan/reader/LibraryScreen.kt @@ -1044,7 +1044,7 @@ private fun ShelfCover(shelf: Shelf) { ) { if (booksForCovers.size <= 1) { val imageModel = remember(shelf.topBook?.coverImagePath) { - shelf.topBook?.coverImagePath?.let { File(it) }?.takeIf { it.exists() } ?: placeholder + shelf.topBook?.coverImagePath?.let { File(it) } ?: placeholder } AsyncImage( model = ImageRequest.Builder(context) @@ -1067,7 +1067,7 @@ private fun ShelfCover(shelf: Shelf) { ) { booksForCovers.forEachIndexed { index, book -> val imageModel = remember(book.coverImagePath) { - book.coverImagePath?.let { File(it) }?.takeIf { it.exists() } ?: placeholder + book.coverImagePath?.let { File(it) } ?: placeholder } Surface( shape = MaterialTheme.shapes.small, @@ -1160,7 +1160,7 @@ private fun LibraryListItem( FileType.EPUB, FileType.MOBI, FileType.MD, FileType.TXT, FileType.HTML -> R.drawable.epub_placeholder } val imageModel = remember(item.coverImagePath) { - item.coverImagePath?.let { File(it) }?.takeIf { it.exists() } ?: placeholder + item.coverImagePath?.let { File(it) } ?: placeholder } Surface( 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 4f6c1ea..6143429 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt @@ -417,7 +417,8 @@ internal fun PdfPageComposable( draggingBoxId: String? = null, isScrollLocked: Boolean = false, isVisible: Boolean = true, - isStylusOnlyMode: Boolean = false + isStylusOnlyMode: Boolean = false, + isHighlighterSnapEnabled: Boolean = false ) { SideEffect { Timber.tag("PdfDrawPerf").v("PdfPageComposable Recompose: Page $pageIndex") } val pdfDocumentItem = pdfDocument.item @@ -2608,7 +2609,8 @@ internal fun PdfPageComposable( isScrolling, isVerticalScroll, selectedTool, - isStylusOnlyMode + isStylusOnlyMode, + isHighlighterSnapEnabled ) { val canDraw = isEditMode && selectedTool != InkType.TEXT && !isScrolling && !isVerticalScroll && actualBitmapWidthPx > 0 && actualBitmapHeightPx > 0 @@ -4611,6 +4613,16 @@ class PdfDrawingState { currentPoints.clear() return finalAnnot } + + fun updateDrag(point: PdfPoint) { + if (currentPoints.isNotEmpty()) { + val start = currentPoints.first() + currentPoints.clear() + currentPoints.add(start) + currentPoints.add(point) + currentAnnotation = currentAnnotation?.copy(points = currentPoints.toList()) + } + } } @Composable 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 2425baf..c7a84bf 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt @@ -222,7 +222,8 @@ internal fun PdfVerticalReader( isScrollLocked: Boolean = false, autoScrollSpeed: Float = 1.0f, onInteractionListener: () -> Unit = {}, - isStylusOnlyMode: Boolean = false + isStylusOnlyMode: Boolean = false, + isHighlighterSnapEnabled: Boolean = false ) { SideEffect { Timber.tag("PdfDrawPerf").v("LIST: PdfVerticalReader Recomposing.") } var globalEraserPosition by remember { mutableStateOf(null) } @@ -748,7 +749,13 @@ internal fun PdfVerticalReader( } } - val globalDrawingModifier = Modifier.pointerInput(isEditMode, layoutInfo, selectedTool, isStylusOnlyMode) { + val globalDrawingModifier = Modifier.pointerInput( + isEditMode, + layoutInfo, + selectedTool, + isStylusOnlyMode, + isHighlighterSnapEnabled + ) { if (!isEditMode) return@pointerInput if (selectedTool == InkType.TEXT) return@pointerInput @@ -864,7 +871,14 @@ internal fun PdfVerticalReader( onDoubleTapToZoom(offset) }) } - .pointerInput(totalDocHeight, isEditMode, selectedTool, isScrollLocked, isStylusOnlyMode) { + .pointerInput( + totalDocHeight, + isEditMode, + selectedTool, + isScrollLocked, + isStylusOnlyMode, + isHighlighterSnapEnabled + ) { val tracker = VelocityTracker() val decay = exponentialDecay() val touchSlop = viewConfiguration.touchSlop 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 5b52fd6..c4bf0ce 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt @@ -89,7 +89,6 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.automirrored.filled.MenuBook import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowUpward import androidx.compose.material.icons.filled.Brush @@ -981,6 +980,8 @@ fun PdfViewerScreen( var showToolSettings by remember { mutableStateOf(false) } + val isHighlighterSnapEnabled = toolSettings.isHighlighterSnapEnabled + val selectedTool = toolSettings.getActiveTool() val lastPenTool = toolSettings.getLastPenTool() @@ -999,6 +1000,9 @@ fun PdfViewerScreen( val isCurrentToolHighlighter = selectedTool == InkType.HIGHLIGHTER || selectedTool == InkType.HIGHLIGHTER_ROUND + val currentSnapEnabled by rememberUpdatedState(isHighlighterSnapEnabled) + val currentIsHighlighter by rememberUpdatedState(isCurrentToolHighlighter) + val penPalette = remember(toolSettings.penPaletteArgb) { toolSettings.getPenPalette() } val highlighterPalette = remember(toolSettings.highlighterPaletteArgb) { toolSettings.getHighlighterPalette() } @@ -1272,6 +1276,36 @@ fun PdfViewerScreen( } } + val calculateSnappedPoint = remember(pageAspectRatios) { + { pageIndex: Int, currentPoint: PdfPoint, startPoint: PdfPoint? -> + if (startPoint == null) { + currentPoint + } else { + val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } + + val dx = (currentPoint.x - startPoint.x) * aspectRatio + val dy = (currentPoint.y - startPoint.y) + + val angleRad = kotlin.math.atan2(dy, dx) + val angleDeg = (angleRad * 180 / kotlin.math.PI) + val absAngle = kotlin.math.abs(angleDeg) + + val threshold = 10.0 + + val isHorizontal = absAngle < threshold || kotlin.math.abs(absAngle - 180.0) < threshold + val isVertical = kotlin.math.abs(absAngle - 90.0) < threshold + + if (isHorizontal) { + currentPoint.copy(y = startPoint.y) + } else if (isVertical) { + currentPoint.copy(x = startPoint.x) + } else { + currentPoint + } + } + } + } + val onDeletePage: () -> Unit = { coroutineScope.launch { if (currentBookId != null && currentPage in virtualPages.indices) { @@ -2095,13 +2129,42 @@ fun PdfViewerScreen( } fun isAnnotationHit( - annotation: PdfAnnotation, hitPoint: PdfPoint, threshold: Float = 0.05f + annotation: PdfAnnotation, + hitPoint: PdfPoint, + pageAspectRatio: Float, + threshold: Float = 0.025f ): Boolean { - return annotation.points.any { p -> - val dx = p.x - hitPoint.x - val dy = p.y - hitPoint.y - (dx * dx + dy * dy) < (threshold * threshold) + if (annotation.points.isEmpty()) return false + + if (annotation.points.size == 1) { + val p = annotation.points[0] + val dx = (p.x - hitPoint.x) * pageAspectRatio + val dy = (p.y - hitPoint.y) + return (dx * dx + dy * dy) < (threshold * threshold) } + + for (i in 0 until annotation.points.size - 1) { + val a = annotation.points[i] + val b = annotation.points[i + 1] + + val pax = (hitPoint.x - a.x) * pageAspectRatio + val pay = (hitPoint.y - a.y) + val bax = (b.x - a.x) * pageAspectRatio + val bay = (b.y - a.y) + + val segmentLenSq = (bax * bax + bay * bay).coerceAtLeast(1e-6f) + val t = (pax * bax + pay * bay) / segmentLenSq + val tClamped = t.coerceIn(0f, 1f) + + val closestX = bax * tClamped + val closestY = bay * tClamped + + val distSq = (pax - closestX) * (pax - closestX) + (pay - closestY) * (pay - closestY) + + if (distSq < (threshold * threshold)) return true + } + + return false } fun startTts(pageToReadOverride: Int? = null) { @@ -3407,10 +3470,10 @@ fun PdfViewerScreen( { point: PdfPoint -> if (currentSelectedTool == InkType.TEXT) { } else if (currentSelectedTool == InkType.ERASER) { - val existing = - allAnnotations[pageIndex] ?: emptyList() + val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } + val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit(it, point) + isAnnotationHit(it, point, aspectRatio) } if (toRemove.isNotEmpty()) { val batch = @@ -3427,10 +3490,13 @@ fun PdfViewerScreen( allAnnotations + (pageIndex to newList) } } else { - val pointWithTime = point.copy( - timestamp = System.currentTimeMillis() - ) - drawingState.onDraw(pointWithTime) + if (currentIsHighlighter && currentSnapEnabled) { + val startPoint = drawingState.currentAnnotation?.points?.firstOrNull() + val effectivePoint = calculateSnappedPoint(pageIndex, point, startPoint) + drawingState.updateDrag(effectivePoint.copy(timestamp = System.currentTimeMillis())) + } else { + drawingState.onDraw(point.copy(timestamp = System.currentTimeMillis())) + } } } } @@ -3451,13 +3517,10 @@ fun PdfViewerScreen( if (currentSelectedTool == InkType.TEXT) { } else if (currentSelectedTool == InkType.ERASER) { erasedAnnotationsFromStroke.clear() - - val existing = allAnnotations[pageIndex] - ?: emptyList() + val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } + val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit( - it, point - ) + isAnnotationHit(it, point, aspectRatio) } if (toRemove.isNotEmpty()) { val batch = @@ -3581,6 +3644,7 @@ fun PdfViewerScreen( }, richTextController = richTextController, isStylusOnlyMode = isStylusOnlyMode, + isHighlighterSnapEnabled = isHighlighterSnapEnabled, isEditMode = isDrawingActive, textBoxes = textBoxes.filter { it.pageIndex == pageIndex }, selectedTextBoxId = selectedTextBoxId, @@ -3791,10 +3855,10 @@ fun PdfViewerScreen( } else if (currentSelectedTool == InkType.ERASER) { erasedAnnotationsFromStroke.clear() - val existing = - allAnnotations[pageIndex] ?: emptyList() + val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } + val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit(it, point) + isAnnotationHit(it, point, aspectRatio) } if (toRemove.isNotEmpty()) { val batch = @@ -3826,13 +3890,13 @@ fun PdfViewerScreen( } } - val onDrawStable = remember { + val onDrawStable = remember(isHighlighterSnapEnabled, isCurrentToolHighlighter, calculateSnappedPoint) { { pageIndex: Int, point: PdfPoint -> if (currentSelectedTool == InkType.ERASER) { - val existing = - allAnnotations[pageIndex] ?: emptyList() + val aspectRatio = pageAspectRatios.getOrElse(pageIndex) { 1f } + val existing = allAnnotations[pageIndex] ?: emptyList() val toRemove = existing.filter { - isAnnotationHit(it, point) + isAnnotationHit(it, point, aspectRatio) } if (toRemove.isNotEmpty()) { val batch = @@ -3846,10 +3910,13 @@ fun PdfViewerScreen( allAnnotations + (pageIndex to newList) } } else { - val pointWithTime = point.copy( - timestamp = System.currentTimeMillis() - ) - drawingState.onDraw(pointWithTime) + if (currentIsHighlighter && currentSnapEnabled) { + val startPoint = drawingState.currentAnnotation?.points?.firstOrNull() + val effectivePoint = calculateSnappedPoint(pageIndex, point, startPoint) + drawingState.updateDrag(effectivePoint.copy(timestamp = System.currentTimeMillis())) + } else { + drawingState.onDraw(point.copy(timestamp = System.currentTimeMillis())) + } } } } @@ -3898,6 +3965,7 @@ fun PdfViewerScreen( allAnnotations = allAnnotationsProvider, drawingState = drawingState, onDrawStart = onDrawStartStable, + isHighlighterSnapEnabled = isHighlighterSnapEnabled, onDraw = onDrawStable, onDrawEnd = { val finalAnnotation = drawingState.onDrawEnd() @@ -5198,7 +5266,10 @@ fun PdfViewerScreen( } else { annotationSettingsRepo.updatePenPalette(newPalette) } - }) + }, + isHighlighterSnapEnabled = isHighlighterSnapEnabled, + onSnapToggle = { annotationSettingsRepo.updateHighlighterSnap(it) } + ) } snapPreviewLocation?.let { location -> @@ -5357,7 +5428,7 @@ fun PdfViewerScreen( saveStylusOnlyMode(context, isStylusOnlyMode) }, onToolClick = { clickedTool -> - if (clickedTool == InkType.ERASER) { + if (clickedTool == InkType.ERASER || clickedTool == InkType.TEXT) { annotationSettingsRepo.updateSelectedTool( clickedTool ) diff --git a/app/src/main/java/com/aryan/reader/pdf/PenIcons.kt b/app/src/main/java/com/aryan/reader/pdf/PenIcons.kt index e8ed110..149df40 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PenIcons.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PenIcons.kt @@ -60,7 +60,8 @@ fun PenIcon( isSelected: Boolean = false, strokeWidth: Float = 0.005f, forcedInkType: InkType? = null, - inkColor: Color? = null + inkColor: Color? = null, + isSnappingEnabled: Boolean = false ) { val animatedColor by animateColorAsState(targetValue = color, label = "color") @@ -129,10 +130,11 @@ fun PenIcon( drawInkSquiggle( type = type, forcedInkType = forcedInkType, - color = animatedInkColor, // Use the specific ink color + color = animatedInkColor, progress = inkProgress, startPoint = Offset(tipX, tipY), - baseStrokeWidth = strokeWidth + baseStrokeWidth = strokeWidth, + isStraight = isSnappingEnabled ) } } @@ -413,7 +415,8 @@ private fun DrawScope.drawInkSquiggle( color: Color, progress: Float, startPoint: Offset, - baseStrokeWidth: Float + baseStrokeWidth: Float, + isStraight: Boolean = false ) { val x = startPoint.x val y = startPoint.y - 2f @@ -422,13 +425,17 @@ private fun DrawScope.drawInkSquiggle( if (type == PenType.HIGHLIGHTER || type == PenType.HIGHLIGHTER_ROUND) { val waveWidth = 70f - val amplitude = 20f - cubicTo( - x + waveWidth * 0.35f, y - amplitude, - x + waveWidth * 0.65f, y + amplitude, - x + waveWidth, y - ) + if (isStraight) { + lineTo(x + waveWidth, y) + } else { + val amplitude = 20f + cubicTo( + x + waveWidth * 0.35f, y - amplitude, + x + waveWidth * 0.65f, y + amplitude, + x + waveWidth, y + ) + } } else { cubicTo( x + 35f, y - 40f, 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 00a55de..ee322f5 100644 --- a/app/src/main/java/com/aryan/reader/pdf/ToolSettingsPopup.kt +++ b/app/src/main/java/com/aryan/reader/pdf/ToolSettingsPopup.kt @@ -49,6 +49,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -103,7 +105,9 @@ fun ToolSettingsPopup( onColorChanged: (Color) -> Unit, onThicknessChanged: (Float) -> Unit, onPaletteChange: (List) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + isHighlighterSnapEnabled: Boolean = false, + onSnapToggle: (Boolean) -> Unit = {} ) { val isHighlighter = selectedTool == InkType.HIGHLIGHTER || selectedTool == InkType.HIGHLIGHTER_ROUND @@ -157,10 +161,9 @@ fun ToolSettingsPopup( tonalElevation = 0.dp ) { Column( - modifier = Modifier.padding(20.dp), // Reduced padding + modifier = Modifier.padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - // Pen Type Selector Box( modifier = Modifier .fillMaxWidth() @@ -179,7 +182,8 @@ fun ToolSettingsPopup( inkColor = highlighterColor, isSelected = selectedTool == InkType.HIGHLIGHTER, strokeWidth = activeToolThickness, - onClick = { onToolTypeChanged(InkType.HIGHLIGHTER) } + onClick = { onToolTypeChanged(InkType.HIGHLIGHTER) }, + isSnappingEnabled = isHighlighterSnapEnabled ) PenItem( @@ -189,7 +193,8 @@ fun ToolSettingsPopup( inkColor = highlighterRoundColor, isSelected = selectedTool == InkType.HIGHLIGHTER_ROUND, strokeWidth = activeToolThickness, - onClick = { onToolTypeChanged(InkType.HIGHLIGHTER_ROUND) } + onClick = { onToolTypeChanged(InkType.HIGHLIGHTER_ROUND) }, + isSnappingEnabled = isHighlighterSnapEnabled ) } else { PenItem( @@ -224,6 +229,32 @@ fun ToolSettingsPopup( Spacer(Modifier.height(16.dp)) + if (isHighlighter) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Straight Line", + color = Color.White, + style = MaterialTheme.typography.bodyMedium + ) + Switch( + checked = isHighlighterSnapEnabled, + onCheckedChange = onSnapToggle, + colors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = activeColor.copy(alpha = 1f), + uncheckedThumbColor = Color.Gray, + uncheckedTrackColor = Color(0xFF424242) + ), + modifier = Modifier.scale(0.8f) + ) + } + Spacer(Modifier.height(16.dp)) + } + // THICKNESS SLIDER StyledPropertySlider( value = activeToolThickness, @@ -811,7 +842,8 @@ private fun PenItem( strokeWidth: Float, onClick: () -> Unit, forcedInkType: InkType? = null, - inkColor: Color? = null + inkColor: Color? = null, + isSnappingEnabled: Boolean = false ) { val scale by animateFloatAsState( targetValue = if (isSelected) 1.15f else 0.9f, label = "scale" @@ -834,7 +866,8 @@ private fun PenItem( isSelected = isSelected, strokeWidth = strokeWidth, forcedInkType = forcedInkType, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + isSnappingEnabled = isSnappingEnabled ) } } 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 9f7313f..e8798ae 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 @@ -74,7 +74,8 @@ data class AnnotationToolSettings( "#8C64B5F6".toColorInt(), // Blue "#8CE1BEE7".toColorInt(), // Purple ), - val textStyle: TextStyleConfig = TextStyleConfig() + val textStyle: TextStyleConfig = TextStyleConfig(), + val isHighlighterSnapEnabled: Boolean = false ) { fun getActiveTool(): InkType = try { InkType.valueOf(selectedToolName) @@ -180,6 +181,10 @@ class AnnotationSettingsRepository(context: Context) { saveSettings(_settings.value.copy(highlighterPaletteArgb = colors.map { it.toArgb() })) } + fun updateHighlighterSnap(enabled: Boolean) { + saveSettings(_settings.value.copy(isHighlighterSnapEnabled = enabled)) + } + fun updateTextStyle(style: TextStyleConfig) { saveSettings(_settings.value.copy(textStyle = style)) }