From a3f807f2b15e67e09ee87e46349182737d1cfa8a Mon Sep 17 00:00:00 2001
From: AndyG <anderson@discordapp.com>
Date: Wed, 11 May 2022 13:46:23 -0700
Subject: [PATCH 1/4] have clicks working to show keyboard on android 7

---
 .../react/views/textinput/ReactEditText.java  |  9 ++
 .../textinput/ReactEditTextClickDetector.java | 92 +++++++++++++++++++
 2 files changed, 101 insertions(+)
 create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditTextClickDetector.java

diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
index c3fd32dd896038..230be23a2013b1 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
@@ -123,6 +123,8 @@ public class ReactEditText extends AppCompatEditText
   private static final KeyListener sKeyListener = QwertyKeyListener.getInstanceForFullKeyboard();
   private @Nullable EventDispatcher mEventDispatcher;
 
+  private final ReactEditTextClickDetector clickDetector = new ReactEditTextClickDetector(this);
+
   public ReactEditText(Context context) {
     super(context);
     setFocusableInTouchMode(false);
@@ -207,6 +209,13 @@ public boolean onTouchEvent(MotionEvent ev) {
         // Disallow parent views to intercept touch events, until we can detect if we should be
         // capturing these touches or not.
         this.getParent().requestDisallowInterceptTouchEvent(true);
+        clickDetector.handleDown(ev);
+        break;
+      case MotionEvent.ACTION_UP:
+        clickDetector.handleUp(ev);
+        break;
+      case MotionEvent.ACTION_CANCEL:
+        clickDetector.cancelPress();
         break;
       case MotionEvent.ACTION_MOVE:
         if (mDetectScrollMovement) {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditTextClickDetector.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditTextClickDetector.java
new file mode 100644
index 00000000000000..74fdce25d21827
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditTextClickDetector.java
@@ -0,0 +1,92 @@
+package com.facebook.react.views.textinput;
+
+import android.os.Build;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+class ReactEditTextClickDetector {
+
+  private static final long MAX_CLICK_DURATION_MS = 250L;
+  private static final int MAX_CLICK_DISTANCE_DP = 12;
+
+  private final ReactEditText reactEditText;
+  private final float screenDensity;
+
+  @Nullable
+  private TimestampedMotionEvent currentDownEvent;
+
+  public ReactEditTextClickDetector(final ReactEditText reactEditText) {
+    this.reactEditText = reactEditText;
+    screenDensity = reactEditText.getResources().getDisplayMetrics().density;
+  }
+
+  void handleDown(final MotionEvent downEvent) {
+    currentDownEvent = new TimestampedMotionEvent(downEvent);
+  }
+
+  void cancelPress() {
+    currentDownEvent = null;
+  }
+
+  void handleUp(final MotionEvent upEvent) {
+    if (currentDownEvent == null) {
+      return;
+    }
+
+    final TimestampedMotionEvent downEvent = currentDownEvent;
+    currentDownEvent = null;
+
+    // for now, if we're not forcing showing the keyboard on clicks, we don't care if it was a
+    // click. we also early return if the view is not enabled.
+    if (!(forceShowKeyboardOnClicks() && reactEditText.isEnabled())) {
+      return;
+    }
+
+    // make sure the press event was close enough in time
+    final long now = System.currentTimeMillis();
+    final long timeDelta = now - downEvent.timestamp;
+    if (timeDelta > MAX_CLICK_DURATION_MS) {
+      return;
+    }
+
+    // make sure the press event was close enough in distance
+    final float oldX = downEvent.motionEvent.getRawX();
+    final float oldY = downEvent.motionEvent.getRawY();
+    final float newX = upEvent.getRawX();
+    final float newY = upEvent.getRawY();
+
+    // distance = sqrt((x2 − x1)^2 + (y2 − y1)^2)
+    final double distancePx = Math.sqrt(
+      Math.pow((newX - oldX), 2) + Math.pow((newY - oldY), 2)
+    );
+
+    double distanceDp = distancePx / screenDensity;
+    if (distanceDp > MAX_CLICK_DISTANCE_DP) {
+      return;
+    }
+
+    reactEditText.showSoftKeyboard();
+  }
+
+  /**
+   * There is a bug on Android 7/8/9 where clicking the view while it is already
+   * focused does not show the keyboard. On those API levels, we force showing
+   * the keyboard when we detect a click.
+   */
+  private static boolean forceShowKeyboardOnClicks() {
+    return Build.VERSION.SDK_INT <= Build.VERSION_CODES.P;
+  }
+
+  private static class TimestampedMotionEvent {
+
+    final long timestamp;
+    final MotionEvent motionEvent;
+
+    TimestampedMotionEvent(final long timestamp, final MotionEvent motionEvent) {
+      this.timestamp = timestamp;
+      this.motionEvent = motionEvent;
+    }
+  }
+}

From 3054f587d79f7b077e46234487c4bdf77b2127b6 Mon Sep 17 00:00:00 2001
From: Ankit Kumar <ankit.kumar@discordapp.com>
Date: Wed, 22 Jun 2022 09:28:40 -0400
Subject: [PATCH 2/4] Add preventClipping prop

---
 .../react/views/view/ReactViewGroup.java      | 22 ++++++++++++++++++-
 .../react/views/view/ReactViewManager.java    |  8 +++++++
 2 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
index 8aedfecf772327..7cdfafc989d153 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
@@ -131,6 +131,7 @@ public void onLayoutChange(
   private int mLayoutDirection;
   private float mBackfaceOpacity = 1.f;
   private String mBackfaceVisibility = "visible";
+  private boolean mPreventClipping = false;
 
   public ReactViewGroup(Context context) {
     super(context);
@@ -279,6 +280,19 @@ public void setBorderStyle(@Nullable String style) {
     getOrCreateReactViewBackground().setBorderStyle(style);
   }
 
+  public void setPreventClipping(boolean preventClipping) {
+    mPreventClipping = preventClipping;
+
+    // TODO(apkumar)
+    //
+    // It would be nice to trigger the LayoutChangeListener at this point.
+  }
+
+  public boolean getPreventClipping() {
+    return mPreventClipping;
+  }
+
+
   @Override
   public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
     if (removeClippedSubviews == mRemoveClippedSubviews) {
@@ -366,7 +380,13 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa
     // it won't be size and located properly.
     Animation animation = child.getAnimation();
     boolean isAnimating = animation != null && !animation.hasEnded();
-    if (!intersects && child.getParent() != null && !isAnimating) {
+
+    boolean preventClipping = false;
+    if (child instanceof ReactViewGroup) {
+      preventClipping = ((ReactViewGroup)child).getPreventClipping();
+    }
+
+    if (!intersects && child.getParent() != null && !isAnimating && !preventClipping) {
       // We can try saving on invalidate call here as the view that we remove is out of visible area
       // therefore invalidation is not necessary.
       super.removeViewsInLayout(idx - clippedSoFar, 1);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
index ba4a1d5cb9567a..b9025cad26627c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
@@ -53,11 +53,19 @@ public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
   private static final int CMD_SET_PRESSED = 2;
   private static final String HOTSPOT_UPDATE_KEY = "hotspotUpdate";
 
+  @ReactProp(name = "preventClipping")
+  public void setPreventClipping(ReactViewGroup view, boolean preventClipping) {
+    view.setPreventClipping(preventClipping);
+  }
+
   @ReactProp(name = "accessible")
   public void setAccessible(ReactViewGroup view, boolean accessible) {
     view.setFocusable(accessible);
   }
 
+  @ReactProp(name = "preventClipping")
+  public void setPreventClipping(ReactViewGroup view, bool
+
   @ReactProp(name = "hasTVPreferredFocus")
   public void setTVPreferredFocus(ReactViewGroup view, boolean hasTVPreferredFocus) {
     if (hasTVPreferredFocus) {

From 3d69214daab80a1470fb27c64079b7210b65837d Mon Sep 17 00:00:00 2001
From: Ankit Kumar <ankit.kumar@discordapp.com>
Date: Wed, 22 Jun 2022 09:30:49 -0400
Subject: [PATCH 3/4] Fix typo

---
 .../java/com/facebook/react/views/view/ReactViewManager.java   | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
index b9025cad26627c..af592385c66ed3 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
@@ -63,9 +63,6 @@ public void setAccessible(ReactViewGroup view, boolean accessible) {
     view.setFocusable(accessible);
   }
 
-  @ReactProp(name = "preventClipping")
-  public void setPreventClipping(ReactViewGroup view, bool
-
   @ReactProp(name = "hasTVPreferredFocus")
   public void setTVPreferredFocus(ReactViewGroup view, boolean hasTVPreferredFocus) {
     if (hasTVPreferredFocus) {

From 222f1405cc57a0f4fcc4a9503e4821139c04c085 Mon Sep 17 00:00:00 2001
From: Ankit Kumar <ankit.kumar@discordapp.com>
Date: Wed, 22 Jun 2022 13:39:26 -0400
Subject: [PATCH 4/4] more preventClipping cases

---
 .../java/com/facebook/react/views/view/ReactViewGroup.java    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
index 7cdfafc989d153..aaf7d05acd50b2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
@@ -391,11 +391,11 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa
       // therefore invalidation is not necessary.
       super.removeViewsInLayout(idx - clippedSoFar, 1);
       needUpdateClippingRecursive = true;
-    } else if (intersects && child.getParent() == null) {
+    } else if ((intersects || preventClipping) && child.getParent() == null) {
       super.addViewInLayout(child, idx - clippedSoFar, sDefaultLayoutParam, true);
       invalidate();
       needUpdateClippingRecursive = true;
-    } else if (intersects) {
+    } else if (intersects || preventClipping) {
       // If there is any intersection we need to inform the child to update its clipping rect
       needUpdateClippingRecursive = true;
     }