diff --git a/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheetBehavior.kt b/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheetBehavior.kt new file mode 100644 index 0000000000..a8f8e6e684 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheetBehavior.kt @@ -0,0 +1,37 @@ +package com.itsaky.androidide.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.bottomsheet.BottomSheetBehavior + +class EditorBottomSheetBehavior( + context: Context, + attrs: AttributeSet?, +) : BottomSheetBehavior(context, attrs) { + private var gestureStartedInTopPadding = false + + override fun onInterceptTouchEvent( + parent: CoordinatorLayout, + child: V, + event: MotionEvent, + ): Boolean = !isGestureInTopPadding(child, event) && super.onInterceptTouchEvent(parent, child, event) + + override fun onTouchEvent( + parent: CoordinatorLayout, + child: V, + event: MotionEvent, + ): Boolean = !isGestureInTopPadding(child, event) && super.onTouchEvent(parent, child, event) + + private fun isGestureInTopPadding( + child: V, + event: MotionEvent, + ): Boolean { + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + gestureStartedInTopPadding = event.y < child.top + child.paddingTop + } + return gestureStartedInTopPadding + } +} diff --git a/app/src/main/res/layout-land/content_editor.xml b/app/src/main/res/layout-land/content_editor.xml index b2f0a7e74f..c5636fdb66 100644 --- a/app/src/main/res/layout-land/content_editor.xml +++ b/app/src/main/res/layout-land/content_editor.xml @@ -165,7 +165,7 @@ android:layout_height="match_parent" app:behavior_hideable="false" app:behavior_peekHeight="@dimen/editor_sheet_collapsed_height" - app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> + app:layout_behavior="com.itsaky.androidide.ui.EditorBottomSheetBehavior" /> + app:layout_behavior="com.itsaky.androidide.ui.EditorBottomSheetBehavior" /> () + + private val editor = + View(activity).apply { + isClickable = true + setOnTouchListener { _, event -> + editorActions += event.actionMasked + false + } + } + + private val behavior = + EditorBottomSheetBehavior(activity, null).apply { + peekHeight = PEEK_HEIGHT + isHideable = false + } + + private val sheet = FrameLayout(activity).apply { setPadding(0, TOP_PADDING, 0, 0) } + + private val parent = + CoordinatorLayout(activity).apply { + addView(editor, CoordinatorLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)) + addView( + sheet, + CoordinatorLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT).also { it.behavior = behavior }, + ) + } + + private fun laidOut() { + activity.setContentView(parent) + shadowOf(Looper.getMainLooper()).idle() + assertThat(behavior.state).isEqualTo(BottomSheetBehavior.STATE_COLLAPSED) + } + + private fun dragUpFrom(startY: Float) { + val downTime = SystemClock.uptimeMillis() + val x = parent.width / 2f + val events = + listOf(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, x, startY, 0)) + + (1..DRAG_STEPS).map { step -> + MotionEvent.obtain( + downTime, + downTime + step * STEP_MILLIS, + MotionEvent.ACTION_MOVE, + x, + startY - step * STEP_PX, + 0, + ) + } + events.forEach { event -> + parent.dispatchTouchEvent(event) + event.recycle() + } + } + + @Test + fun `a drag that starts in the sheet's top padding stays with the view underneath`() { + laidOut() + val collapsedTop = sheet.top + + dragUpFrom(collapsedTop + TOP_PADDING / 2f) + + assertThat(behavior.state).isEqualTo(BottomSheetBehavior.STATE_COLLAPSED) + assertThat(sheet.top).isEqualTo(collapsedTop) + assertThat(editorActions).doesNotContain(MotionEvent.ACTION_CANCEL) + assertThat(editorActions.count { it == MotionEvent.ACTION_MOVE }).isEqualTo(DRAG_STEPS) + } + + @Test + fun `a drag that starts on the sheet's visible top still drags the sheet`() { + laidOut() + val collapsedTop = sheet.top + + dragUpFrom(collapsedTop + TOP_PADDING + (PEEK_HEIGHT - TOP_PADDING) / 2f) + + assertThat(behavior.state).isEqualTo(BottomSheetBehavior.STATE_DRAGGING) + assertThat(sheet.top).isLessThan(collapsedTop) + } + + private companion object { + const val PEEK_HEIGHT = 200 + const val TOP_PADDING = 60 + const val DRAG_STEPS = 6 + const val STEP_PX = 20f + const val STEP_MILLIS = 16L + } +}