Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
private var scrollWatcher: ScrollWatcher?
private var keyListener: InternalKeyListener? = null
private var detectScrollMovement = false
private var isParentInterceptDisallowed = false
private var onKeyPress = false
private val textAttributes: TextAttributes
private var typefaceDirty = false
Expand Down Expand Up @@ -314,12 +315,23 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
}

override fun onTouchEvent(ev: MotionEvent): Boolean {
// Disabled inputs must not block an ancestor from handling the touch.
if (!isEnabled) {
if (isParentInterceptDisallowed) {
parent?.requestDisallowInterceptTouchEvent(false)
isParentInterceptDisallowed = false
}
detectScrollMovement = false
return false
}

when (ev.action) {
MotionEvent.ACTION_DOWN -> {
detectScrollMovement = true
// Disallow parent views to intercept touch events, until we can detect if we should be
// capturing these touches or not.
this.parent.requestDisallowInterceptTouchEvent(true)
isParentInterceptDisallowed = true
}

MotionEvent.ACTION_MOVE ->
Expand All @@ -332,9 +344,13 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
) {
// We cannot scroll, let parent views take care of these touches.
this.parent.requestDisallowInterceptTouchEvent(false)
isParentInterceptDisallowed = false
}
detectScrollMovement = false
}

MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> isParentInterceptDisallowed = false
}
return super.onTouchEvent(ev)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import android.text.Spanned
import android.text.TextUtils
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.autofill.HintConstants
import androidx.core.content.res.ResourcesCompat.ID_NULL
Expand Down Expand Up @@ -224,6 +226,96 @@ class ReactTextInputPropertyTest {
assertThat(view.ellipsize).isEqualTo(TextUtils.TruncateAt.END)
}

@Test
fun testDisabledInputDoesNotClaimNativeTouches() {
val interceptionRequests = mutableListOf<Boolean>()
val parent =
object : FrameLayout(themedContext) {
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
interceptionRequests.add(disallowIntercept)
super.requestDisallowInterceptTouchEvent(disallowIntercept)
}
}
parent.addView(view)
view.isEnabled = false

for (action in
listOf(
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE,
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL,
)) {
val event = MotionEvent.obtain(0, 0, action, 10f, 10f, 0)
try {
assertThat(view.onTouchEvent(event)).isFalse()
} finally {
event.recycle()
}
}
assertThat(interceptionRequests).isEmpty()

// Re-enabling the same input must restore its normal touch handling.
view.isEnabled = true
val down = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 10f, 10f, 0)
try {
view.onTouchEvent(down)
} finally {
down.recycle()
}
assertThat(interceptionRequests).containsExactly(true)
}

@Test
fun testDisablingInputDuringGestureAllowsParentInterception() {
for (moveBeforeDisabling in listOf(false, true)) {
// Keep interception disallowed even if the input receives a MOVE before being disabled.
val input =
object : ReactEditText(themedContext) {
override fun canScrollVertically(direction: Int): Boolean = true
}
var interceptedMove = false
val parent =
object : FrameLayout(themedContext) {
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_MOVE) {
interceptedMove = true
return true
}
return false
}

override fun onTouchEvent(event: MotionEvent): Boolean = true
}
parent.addView(input)
parent.layout(0, 0, 100, 100)
input.layout(0, 0, 100, 100)

fun dispatch(action: Int): Boolean {
val event = MotionEvent.obtain(0, 0, action, 10f, 10f, 0)
return try {
parent.dispatchTouchEvent(event)
} finally {
event.recycle()
}
}

assertThat(dispatch(MotionEvent.ACTION_DOWN)).isTrue()
if (moveBeforeDisabling) {
dispatch(MotionEvent.ACTION_MOVE)
}
assertThat(interceptedMove).isFalse()

input.isEnabled = false
// The first disabled MOVE releases the parent's interception flag.
dispatch(MotionEvent.ACTION_MOVE)
assertThat(interceptedMove).isFalse()
dispatch(MotionEvent.ACTION_MOVE)
assertThat(interceptedMove).isTrue()
dispatch(MotionEvent.ACTION_UP)
}
}

@Test
fun testEditable() {
manager.updateProperties(view, buildStyles())
Expand Down
Loading