diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb32626..0277d24 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,10 +21,10 @@ This allows the open-source version to thrive while keeping the project sustaina ## How to Submit a Pull Request -1. Fork the repository and create your branch from `main`. +1. Fork the repository. 2. Make your changes and commit them with clear, descriptive messages. 3. Open a Pull Request! -4. **Sign the CLA:** When you open your first PR, the [CLA Assistant](https://cla-assistant.io/) bot will automatically comment with a link. Simply click the link, sign in with GitHub, and click "I agree." **This is a one-time process** — once signed, it covers all your future contributions to Episteme. +4. **Sign the CLA:** When you open your first PR, the [CLA Assistant](https://cla-assistant.io/) bot will automatically comment with a link. Simply click the link, sign in with GitHub, and click "I agree." **This is a one-time process**, once signed, it covers all your future contributions to Episteme. ## Questions? 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 9c497c4..0a8b850 100644 --- a/app/src/main/java/com/aryan/reader/pdf/AnnotationDock.kt +++ b/app/src/main/java/com/aryan/reader/pdf/AnnotationDock.kt @@ -35,6 +35,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Redo import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.DoNotTouch +import androidx.compose.material.icons.filled.TouchApp import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.Icon @@ -68,7 +70,9 @@ fun AnnotationDock( modifier: Modifier = Modifier, isSticky: Boolean = false, isMinimized: Boolean, - onToggleMinimize: () -> Unit + onToggleMinimize: () -> Unit, + isStylusOnlyMode: Boolean, + onToggleStylusOnlyMode: () -> Unit ) { val showFullDock = isSticky || !isMinimized @@ -141,6 +145,29 @@ fun AnnotationDock( horizontalArrangement = Arrangement.spacedBy(spacing), modifier = Modifier.alpha(toolsAlpha) ) { + + // Stylus Only Toggle + if (!isMinimized) { + val iconVector = if (isStylusOnlyMode) Icons.Default.DoNotTouch else Icons.Default.TouchApp + val iconTint = if (isStylusOnlyMode) Color(0xFFE57373) else Color.White + + Box( + modifier = Modifier + .size(buttonSize) + .clip(CircleShape) + .background(Color.White.copy(alpha = if (isStylusOnlyMode) 0.15f else 0f)) + .clickable(onClick = onToggleStylusOnlyMode), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = iconVector, + contentDescription = "Stylus Only Mode", + tint = iconTint, + modifier = Modifier.size(iconSize) + ) + } + } + // Pen Group val isPenActive = !isMinimized && (selectedTool == InkType.PEN || selectedTool == InkType.FOUNTAIN_PEN || 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 c936fd7..f3dc497 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt @@ -81,6 +81,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.changedToUp import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChanged @@ -416,6 +417,7 @@ internal fun PdfPageComposable( draggingBoxId: String? = null, isScrollLocked: Boolean = false, isVisible: Boolean = true, + isStylusOnlyMode: Boolean = false ) { SideEffect { Timber.tag("PdfDrawPerf").v("PdfPageComposable Recompose: Page $pageIndex") } val pdfDocumentItem = pdfDocument.item @@ -2237,9 +2239,14 @@ internal fun PdfPageComposable( isZoomEnabled, isVerticalScroll, isEditMode, - selectedTool + selectedTool, + isStylusOnlyMode ) { - if (isEditMode && selectedTool != InkType.TEXT) return@pointerInput + val isTapDetectionAllowed = !isEditMode || + selectedTool == InkType.TEXT || + isStylusOnlyMode + + if (!isTapDetectionAllowed) return@pointerInput detectTapGestures(onTap = { tapOffset -> Timber.d( @@ -2600,7 +2607,8 @@ internal fun PdfPageComposable( offset, isScrolling, isVerticalScroll, - selectedTool + selectedTool, + isStylusOnlyMode ) { val canDraw = isEditMode && selectedTool != InkType.TEXT && !isScrolling && !isVerticalScroll && actualBitmapWidthPx > 0 && actualBitmapHeightPx > 0 @@ -2611,6 +2619,13 @@ internal fun PdfPageComposable( try { awaitEachGesture { val down = awaitFirstDown(requireUnconsumed = false) + + Timber.tag("PointerTypeDebug").d("Page $pageIndex: Input Type detected: ${down.type}") + + if (isStylusOnlyMode && down.type == PointerType.Touch) { + return@awaitEachGesture + } + val dragPointerId = down.id val startPos = down.position var dragStarted = false 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 4c12eb8..c34da36 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfVerticalReader.kt @@ -88,6 +88,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChanged import androidx.compose.ui.input.pointer.util.VelocityTracker @@ -216,7 +217,8 @@ internal fun PdfVerticalReader( isAutoScrollTempPaused: Boolean = false, isScrollLocked: Boolean = false, autoScrollSpeed: Float = 1.0f, - onInteractionListener: () -> Unit = {} + onInteractionListener: () -> Unit = {}, + isStylusOnlyMode: Boolean = false ) { SideEffect { Timber.tag("PdfDrawPerf").v("LIST: PdfVerticalReader Recomposing.") } var globalEraserPosition by remember { mutableStateOf(null) } @@ -726,13 +728,17 @@ internal fun PdfVerticalReader( } } - val globalDrawingModifier = Modifier.pointerInput(isEditMode, layoutInfo, selectedTool) { + val globalDrawingModifier = Modifier.pointerInput(isEditMode, layoutInfo, selectedTool, isStylusOnlyMode) { if (!isEditMode) return@pointerInput if (selectedTool == InkType.TEXT) return@pointerInput awaitEachGesture { val down = awaitFirstDown(requireUnconsumed = false) + if (isStylusOnlyMode && down.type == PointerType.Touch) { + return@awaitEachGesture + } + fun getPageAndPoint(screenOffset: Offset): Pair? { val zoom = zoomAnimatable.value val panX = panXAnimatable.value @@ -814,31 +820,31 @@ internal fun PdfVerticalReader( modifier = Modifier .fillMaxSize() .then(globalDrawingModifier) - .pointerInput(isEditMode, selectedTool) { + .pointerInput(isEditMode, selectedTool, isStylusOnlyMode) { Timber.tag("PdfTouchDebug").v( "VerticalReader: TapPointerInput init. isEditMode=$isEditMode" ) - if (isEditMode && selectedTool != InkType.TEXT) return@pointerInput + + val isTapDetectionAllowed = !isEditMode || + selectedTool == InkType.TEXT || + isStylusOnlyMode + + if (!isTapDetectionAllowed) return@pointerInput detectTapGestures(onTap = { if (!isEditMode) { - Timber.tag("PdfTouchDebug").d( - "VerticalReader: Tap detected (Toggling bars)" - ) + Timber.tag("PdfTouchDebug").d("VerticalReader: Tap detected") selectionClearTrigger++ onPageClick() } else if (selectedTool == InkType.TEXT) { onPageClick() } }, onDoubleTap = { offset -> - if (!isEditMode) { - Timber.tag("PdfTouchDebug").d("VerticalReader: DoubleTap detected") - onDoubleTapToZoom(offset) - } + Timber.tag("PdfTouchDebug").d("VerticalReader: DoubleTap detected") + onDoubleTapToZoom(offset) }) } - - .pointerInput(totalDocHeight, isEditMode, selectedTool, isScrollLocked) { + .pointerInput(totalDocHeight, isEditMode, selectedTool, isScrollLocked, isStylusOnlyMode) { val tracker = VelocityTracker() val decay = exponentialDecay() val touchSlop = viewConfiguration.touchSlop @@ -849,11 +855,14 @@ internal fun PdfVerticalReader( ) val down = awaitFirstDown(requireUnconsumed = false) - Timber.tag("PdfTouchDebug").v( - "VerticalReader: DOWN received. Pressed=${down.pressed}, Consumed=${down.isConsumed}" - ) - if (isEditMode && selectedTool != InkType.TEXT && down.pressed) { + Timber.tag("PointerTypeDebug").d("VerticalReader: Input Type detected: ${down.type}") + + val isDrawingGesture = isEditMode && + selectedTool != InkType.TEXT && + (!isStylusOnlyMode || down.type != PointerType.Touch) + + if (isDrawingGesture && down.pressed) { val event = awaitPointerEvent() Timber.tag("PdfTouchDebug").v( "VerticalReader: EditMode check. Event changes: ${event.changes.size}" @@ -959,8 +968,9 @@ internal fun PdfVerticalReader( } } - val shouldScroll = - (panLocked || gestureDisambiguationMode != 0) && (!isEditMode || selectedTool == InkType.TEXT || isMultiTouch) + val isTouchInput = event.changes.all { it.type == PointerType.Touch } + val shouldScroll = (panLocked || gestureDisambiguationMode != 0) && + (!isEditMode || selectedTool == InkType.TEXT || isMultiTouch || (isStylusOnlyMode && isTouchInput)) if (shouldScroll) { if (!isInteracting) { @@ -1432,6 +1442,7 @@ internal fun PdfVerticalReader( onOcrModelDownloading = onOcrModelDownloading, selectedTool = selectedTool, richTextController = richTextController, + isStylusOnlyMode = isStylusOnlyMode, textBoxes = textBoxes.filter { it.pageIndex == page.index }, selectedTextBoxId = selectedTextBoxId, onTextBoxChange = onTextBoxChange, 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 b983052..3d04013 100644 --- a/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt +++ b/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt @@ -291,12 +291,23 @@ private const val PDF_AUTO_SCROLL_LOCKED_KEY = "pdf_auto_scroll_locked" private const val PDF_AUTO_SCROLL_USE_SLIDER_KEY = "pdf_auto_scroll_use_slider" 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 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) } } +private fun saveStylusOnlyMode(context: Context, isEnabled: Boolean) { + val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit { putBoolean(STYLUS_ONLY_MODE_KEY, isEnabled) } +} + +private fun loadStylusOnlyMode(context: Context): Boolean { + val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE) + return prefs.getBoolean(STYLUS_ONLY_MODE_KEY, false) +} + private fun loadPdfAutoScrollMinSpeed(context: Context): Float { val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE) return prefs.getFloat(PDF_AUTO_SCROLL_MIN_SPEED_KEY, 0.1f) @@ -649,6 +660,7 @@ fun PdfViewerScreen( var isAutoScrollLocked by remember { mutableStateOf(loadPdfAutoScrollLocked(context)) } var autoScrollUseSlider by remember { mutableStateOf(loadPdfAutoScrollUseSlider(context)) } + var isStylusOnlyMode by remember { mutableStateOf(loadStylusOnlyMode(context)) } var currentTtsMode by remember { mutableStateOf(loadTtsMode(context)) } var showTtsSettingsSheet by remember { mutableStateOf(false) } @@ -3298,6 +3310,7 @@ fun PdfViewerScreen( } }, richTextController = richTextController, + isStylusOnlyMode = isStylusOnlyMode, isEditMode = isDrawingActive, textBoxes = textBoxes.filter { it.pageIndex == pageIndex }, selectedTextBoxId = selectedTextBoxId, @@ -3649,6 +3662,7 @@ fun PdfViewerScreen( }, selectedTool = selectedTool, richTextController = richTextController, + isStylusOnlyMode = isStylusOnlyMode, isEditMode = isDrawingActive, textBoxes = textBoxes, selectedTextBoxId = selectedTextBoxId, @@ -4894,6 +4908,11 @@ fun PdfViewerScreen( activePenColor = dockPenColor, activeHighlighterColor = dockHighlighterColor, lastPenTool = lastPenTool, + isStylusOnlyMode = isStylusOnlyMode, + onToggleStylusOnlyMode = { + isStylusOnlyMode = !isStylusOnlyMode + saveStylusOnlyMode(context, isStylusOnlyMode) + }, onToolClick = { clickedTool -> if (clickedTool == InkType.ERASER) { annotationSettingsRepo.updateSelectedTool(