Feature: Disable annotation features while PDF search is active. To improve the search experience and prevent UI overlap between the search bar and the annotation toolbar, this CL introduces the following changes: - UI: Hides the annotation toolbar automatically when text search is initiated. - Interaction: Disables PDF ink interaction while search is active, ensuring touch events (like swipes) are used for result navigation rather than drawing. - State Restoration: Automatically restores the annotation toolbar to its previous dock position once search is closed. Bug: b/477204335 Test: ./gradlew :pdf:pdf-ink:connectedAndroidTest Change-Id: Ia9866740efbc350a9e336bdeb69e1d610d23a1b3
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt index 50bb509..99c1f01 100644 --- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt +++ b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt
@@ -52,6 +52,7 @@ import androidx.test.espresso.action.Tap import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.swipeUp import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed @@ -61,6 +62,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import com.google.common.truth.Truth.assertThat +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import org.hamcrest.CoreMatchers.not import org.junit.After @@ -312,6 +315,81 @@ } } + @Test + fun test_annotationToolbarHidden_onSearchActive() { + if (!isRequiredSdkExtensionAvailable()) return + + loadDocumentAndSetupFragment() + enterEditMode() + + // assert annotation toolbar is visible in edit mode + onView(withId(R.id.annotationToolbar)).check(matches(isDisplayed())) + performDragAndDrop( + toolbarId = R.id.annotationToolbar, + to = ToolbarViewActions.DragTarget.LEFT, + ) + onIdle() + + // Enable search on fragment + scenario.onFragment { fragment -> fragment.isTextSearchActive = true } + + // assert annotation toolbar is hidden when search is initiated + onView(withId(R.id.annotationToolbar)).check(matches(not(isDisplayed()))) + + // disable search on fragment + scenario.onFragment { fragment -> fragment.isTextSearchActive = false } + + // assert toolbar is shown again at the previous position + onView(withId(R.id.annotationToolbar)).check(matches(isDisplayed())) + scenario.onFragment { fragment -> + assertEquals(DOCK_STATE_START, fragment.annotationToolbar.dockState) + } + } + + @Test + fun test_annotationInteractionDisabled_onSearchActive() { + if (!isRequiredSdkExtensionAvailable()) return + + loadDocumentAndSetupFragment() + enterEditMode() + + var firstVisiblePage: Int + var pdfView: PdfView? = null + + // Enable search on fragment + scenario.onFragment { fragment -> + fragment.isTextSearchActive = true + pdfView = fragment.getPdfViewInstance() + } + requireNotNull(pdfView) + // extract first visible page initially + firstVisiblePage = pdfView.firstVisiblePage + + // Swipe up to verify the PDF scrolls; ink interaction should be disabled during search + onView(isRoot()).perform(swipeUp()) + // extract first visible page after swipe + val firstVisiblePageAfterSwipe = pdfView.firstVisiblePage + + assertNotEquals(firstVisiblePage, firstVisiblePageAfterSwipe) + } + + @Test + fun test_annotationToolbar_isHidden_forFormFilling() { + if (!isRequiredSdkExtensionAvailable()) return + + loadDocumentAndSetupFragment(file = FORM_WITH_CHECKBOX_PDF) + + // Click on a form widget to start form filling journey + onView(withId(PdfFragmentR.id.pdfContentLayout)) + .perform(clickOnPdfPoint(PdfPoint(0, PointF(145f, 80f)))) + + scenario.onFragment { fragment -> fragment.pdfFormFillingIdlingResource.increment() } + onIdle() + + // assert annotation toolbar is hidden + onView(withId(R.id.annotationToolbar)).check(matches(not(isDisplayed()))) + } + private fun longClickAtCenter() { onView(isRoot()) .perform( @@ -332,6 +410,7 @@ companion object { private const val TEST_DOCUMENT_FILE = "sample.pdf" private const val FORM_PDF = "text_form.pdf" + private const val FORM_WITH_CHECKBOX_PDF = "sample_form.pdf" private const val REQUIRED_EXTENSION_VERSION = 18 fun isRequiredSdkExtensionAvailable(): Boolean {
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditableDocumentViewModel.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditableDocumentViewModel.kt index 9fa4451..b04e667 100644 --- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditableDocumentViewModel.kt +++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditableDocumentViewModel.kt
@@ -134,6 +134,14 @@ internal var visiblePageRange: IntRange = 0..0 + internal val shouldShowAnnotationToolbar: StateFlow<Boolean> = + combine(pdfEditModeFlow, isTextSearchActiveFlow) { pdfEditMode, isTextSearchActive -> + pdfEditMode is PdfEditMode.Enabled && + pdfEditMode.journey == EDITING_JOURNEY_ANNOTATIONS && + !isTextSearchActive + } + .stateIn(viewModelScope, SharingStarted.Eagerly, false) + /** Reactive state that combines multiple flows to determine if interaction is enabled. */ internal val isAnnotationInteractionEnabled: StateFlow<Boolean> = combine( @@ -141,12 +149,14 @@ areAnnotationsVisibleFlow, _applyEditsStatus, _isPdfViewGestureActive, - ) { pdfEditMode, isVisible, status, isGestureActive -> + isTextSearchActiveFlow, + ) { pdfEditMode, isVisible, status, isGestureActive, isTextSearchActive -> (pdfEditMode is PdfEditMode.Enabled && pdfEditMode.journey == EDITING_JOURNEY_ANNOTATIONS) && isVisible && status != ApplyEditsState.InProgress && - !isGestureActive + !isGestureActive && + !isTextSearchActive } .stateIn(viewModelScope, SharingStarted.Eagerly, false)
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt index 22c7173..024d3a1 100644 --- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt +++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt
@@ -35,6 +35,7 @@ import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.ink.authoring.InProgressStrokeId import androidx.ink.authoring.InProgressStrokesView @@ -219,7 +220,7 @@ private val toolbarLayoutChangeListener = View.OnLayoutChangeListener { - v, + _, left, top, right, @@ -387,6 +388,12 @@ private fun setupUiStateCollectors() { collectFlowOnLifecycleScope { + documentViewModel.shouldShowAnnotationToolbar.collect { + updateAnnotationToolbarVisibility(it) + } + } + + collectFlowOnLifecycleScope { documentViewModel.pdfEditModeFlow.collect { editMode -> if (editMode is PdfEditMode.Enabled) onEnterEditMode() else onExitEditMode() updateUiForEditMode(editMode) @@ -455,13 +462,17 @@ } } + private fun updateAnnotationToolbarVisibility(isAnnotationToolbarVisible: Boolean) { + toolbarCoordinator.isVisible = isAnnotationToolbarVisible + annotationToolbar.isVisible = isAnnotationToolbarVisible + } + private fun updateUiForAnnotationsEditMode(isEnabled: Boolean) { PdfFeatureFlags.isMultiTouchScrollEnabled = isEnabled - annotationToolbar.visibility = if (isEnabled) VISIBLE else GONE - if (isEnabled) { pdfView.clearCurrentSelection() + // Wait for the toolbar to be laid out, as we need to utilize its width and height annotationToolbar.post { wetStrokesView.maskPath = createToolbarMaskPath() } } else {
diff --git a/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModel.kt b/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModel.kt index b276f62..49c8a14 100644 --- a/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModel.kt +++ b/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModel.kt
@@ -130,6 +130,9 @@ internal val isTextSearchActiveFromState: Boolean get() = state[TEXT_SEARCH_STATE_KEY] ?: false + protected val isTextSearchActiveFlow: StateFlow<Boolean> = + state.getStateFlow(TEXT_SEARCH_STATE_KEY, false) + /** isImmersiveModeFromState as set in [state] */ internal val isImmersiveModeDesired: Boolean get() = state[IMMERSIVE_MODE_STATE_KEY] ?: false