Skip to content

Commit

Permalink
Fix overlay touch detection (#6372)
Browse files Browse the repository at this point in the history
When the Overlay was displayed with `statusBar.drawBehind: false`, the StatusBar height wasn't accounted for when calculating if touch events were inside the view's bounds.

To begin with, StatusBar height had to be accounted for because we used `MotionEvent.rawY` coordinates instead of `MotionEvent.y` coordinate which already take into account the view's position within the hierarchy, which compensates for StatusBar height. 

fixes #5976
  • Loading branch information
guyca authored Jul 8, 2020
1 parent a641b09 commit 5e03718
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 60 deletions.
7 changes: 6 additions & 1 deletion lib/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}

flavorDimensions "RNN.reactNativeVersion"
productFlavors {
Expand Down Expand Up @@ -154,7 +157,7 @@ allprojects { p ->
}

dependencies {
implementation "androidx.core:core-ktx:1.1.0"
implementation "androidx.core:core-ktx:1.3.0"
implementation "org.jetbrains.kotlin:$kotlinStdlib:$kotlinVersion"

implementation 'androidx.appcompat:appcompat:1.1.0'
Expand All @@ -174,5 +177,7 @@ dependencies {
testImplementation 'org.assertj:assertj-core:3.8.0'
testImplementation 'com.squareup.assertj:assertj-android:1.1.1'
testImplementation 'org.mockito:mockito-core:2.28.2'

testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativenavigation.utils

import android.graphics.Rect
import android.view.MotionEvent
import android.view.View

val hitRect = Rect()

fun MotionEvent.coordinatesInsideView(view: View?): Boolean {
view ?: return false
view.getHitRect(hitRect)
return hitRect.contains(x.toInt(), y.toInt())
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.reactnativenavigation.views.touch

import android.view.MotionEvent
import androidx.annotation.VisibleForTesting
import com.reactnativenavigation.options.params.Bool
import com.reactnativenavigation.options.params.NullBool
import com.reactnativenavigation.react.ReactView
import com.reactnativenavigation.utils.coordinatesInsideView
import com.reactnativenavigation.views.component.ComponentLayout

open class OverlayTouchDelegate(private val component: ComponentLayout, private val reactView: ReactView) {
var interceptTouchOutside: Bool = NullBool()

fun onInterceptTouchEvent(event: MotionEvent): Boolean {
return when (interceptTouchOutside.hasValue() && event.actionMasked == MotionEvent.ACTION_DOWN) {
true -> handleDown(event)
false -> component.superOnInterceptTouchEvent(event)
}
}

@VisibleForTesting
open fun handleDown(event: MotionEvent) = when (event.coordinatesInsideView(reactView.getChildAt(0))) {
true -> component.superOnInterceptTouchEvent(event)
false -> interceptTouchOutside.isFalse
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.reactnativenavigation.utils

import android.app.Activity
import android.view.MotionEvent
import android.view.View
import android.view.View.MeasureSpec
import android.widget.FrameLayout
import androidx.core.view.marginTop
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import com.reactnativenavigation.BaseTest
import org.assertj.core.api.Java6Assertions.assertThat
import org.junit.Test

class MotionEventTest : BaseTest() {
private lateinit var uut: MotionEvent
private lateinit var activity: Activity
private lateinit var parent: FrameLayout
private val x = 173f
private val y = 249f

override fun beforeEach() {
uut = MotionEvent.obtain(0L, 0, 0, x, y, 0)
activity = newActivity()
parent = FrameLayout(activity)
activity.setContentView(parent)
}

@Test
fun coordinatesInsideView() {
val v: View = mock()
assertThat(uut.coordinatesInsideView(v)).isFalse()
}

@Test
fun coordinatesInsideView_inside() {
val view = View(activity)
parent.addView(view, 200, 300)
assertThat(uut.coordinatesInsideView(view)).isTrue()
}

@Test
fun coordinatesInsideView_outside() {
val view = View(activity)
parent.addView(view, 200, 300)
view.top = (y + 1).toInt()
assertThat(uut.coordinatesInsideView(view)).isFalse()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,31 @@
import android.view.MotionEvent;

import com.reactnativenavigation.BaseTest;
import com.reactnativenavigation.mocks.SimpleOverlay;
import com.reactnativenavigation.options.params.Bool;
import com.reactnativenavigation.react.ReactView;
import com.reactnativenavigation.views.component.ComponentLayout;
import com.reactnativenavigation.views.touch.OverlayTouchDelegate;

import org.junit.Test;
import org.mockito.Mockito;

import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

public class TouchDelegateTest extends BaseTest {
public class OverlayTouchDelegateTest extends BaseTest {
private OverlayTouchDelegate uut;
private final int x = 10;
private final int y = 10;
private final MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, x, y, 0);
private final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, x, y, 0);
private SimpleOverlay reactView;
private ComponentLayout component;

@Override
public void beforeEach() {
super.beforeEach();
reactView = spy(new SimpleOverlay(newActivity()));
component = Mockito.mock(ComponentLayout.class);
ReactView reactView = mock(ReactView.class);
component = mock(ComponentLayout.class);
uut = spy(new OverlayTouchDelegate(component, reactView));
}

Expand Down
2 changes: 1 addition & 1 deletion playground/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

buildscript {
ext {
kotlinVersion = "1.3.61"
kotlinVersion = "1.3.72"
RNNKotlinVersion = kotlinVersion
detoxKotlinVersion = kotlinVersion
}
Expand Down
10 changes: 8 additions & 2 deletions playground/src/screens/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export default function Toast({ componentId }: NavigationComponentProps) {

return (
<View style={styles.root}>
<TouchableOpacity testID={TOAST_OK_BTN_OUTER} onPress={() => dismiss('Outer button clicked')}>
<TouchableOpacity
style={styles.outerTouchable}
testID={TOAST_OK_BTN_OUTER}
onPress={() => dismiss('Outer button clicked')}
>
<View style={styles.toast}>
<Text style={styles.text}>This a very important message!</Text>
<TouchableOpacity
Expand All @@ -37,11 +41,13 @@ const styles = StyleSheet.create({
flexDirection: 'column-reverse',
backgroundColor: '#3e434aa1',
},
outerTouchable: {
margin: 16,
},
toast: {
elevation: 2,
flexDirection: 'row',
height: 40,
margin: 16,
borderRadius: 20,
backgroundColor: Colors.accent,
alignItems: 'center',
Expand Down

0 comments on commit 5e03718

Please sign in to comment.