Skip to content

Commit

Permalink
Migrate com.facebook.react.views.text.ReactSwipeRefreshLayout to Kotl…
Browse files Browse the repository at this point in the history
…in (facebook#47610)

Summary:
Pull Request resolved: facebook#47610

As per title.

Changelog: [Internal]

Reviewed By: tdn120

Differential Revision: D65661766

fbshipit-source-id: 3d56f94f90e7b70160e4ca7898aa0a49997ddb87
  • Loading branch information
fabriziocucci authored and facebook-github-bot committed Nov 14, 2024
1 parent 184eb17 commit db3c1a4
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 153 deletions.
4 changes: 2 additions & 2 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -7113,14 +7113,14 @@ public final class com/facebook/react/views/scroll/ScrollEventType$Companion {
public final fun getJSEventName (Lcom/facebook/react/views/scroll/ScrollEventType;)Ljava/lang/String;
}

public class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout : androidx/swiperefreshlayout/widget/SwipeRefreshLayout {
public final class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout : androidx/swiperefreshlayout/widget/SwipeRefreshLayout {
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
public fun canChildScrollUp ()Z
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
public fun onLayout (ZIIII)V
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
public fun requestDisallowInterceptTouchEvent (Z)V
public fun setProgressViewOffset (F)V
public final fun setProgressViewOffset (F)V
public fun setRefreshing (Z)V
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.swiperefresh

import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.events.NativeGestureUtil

/** Basic extension of [SwipeRefreshLayout] with ReactNative-specific functionality. */
public class ReactSwipeRefreshLayout(reactContext: ReactContext) :
SwipeRefreshLayout(reactContext) {

private var didLayout: Boolean = false
private var refreshing: Boolean = false
private var progressViewOffset: Float = 0f
private val touchSlop: Int = ViewConfiguration.get(reactContext).scaledTouchSlop
private var prevTouchX: Float = 0f
private var intercepted: Boolean = false
private var nativeGestureStarted: Boolean = false

public override fun setRefreshing(refreshing: Boolean) {
this.refreshing = refreshing

// `setRefreshing` must be called after the initial layout otherwise it
// doesn't work when mounting the component with `refreshing = true`.
// Known Android issue: https://code.google.com/p/android/issues/detail?id=77712
if (didLayout) {
super.setRefreshing(refreshing)
}
}

public fun setProgressViewOffset(offset: Float) {
progressViewOffset = offset

// The view must be measured before calling `getProgressCircleDiameter` so
// don't do it before the initial layout.
if (didLayout) {
val diameter = progressCircleDiameter
val start = Math.round(PixelUtil.toPixelFromDIP(offset)) - diameter
val end = Math.round(PixelUtil.toPixelFromDIP(offset + DEFAULT_CIRCLE_TARGET)) - diameter
setProgressViewOffset(false, start, end)
}
}

public override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)

if (!didLayout) {
didLayout = true

// Update values that must be set after initial layout.
setProgressViewOffset(progressViewOffset)
setRefreshing(refreshing)
}
}

public override fun canChildScrollUp(): Boolean {
val firstChild = getChildAt(0)
return firstChild?.canScrollVertically(-1) ?: super.canChildScrollUp()
}

/**
* [SwipeRefreshLayout] overrides [ViewGroup.requestDisallowInterceptTouchEvent] and swallows it.
* This means that any component underneath SwipeRefreshLayout will now interact incorrectly with
* Views that are above SwipeRefreshLayout. We fix that by transmitting the call to this View's
* parents.
*/
public override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
}

public override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (shouldInterceptTouchEvent(ev) && super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev)
nativeGestureStarted = true

// If the pull-to-refresh gesture is interrupted by a parent with its own
// onInterceptTouchEvent then the refresh indicator gets stuck on-screen
// so we ask the parent to not intercept this touch event after it started
parent?.requestDisallowInterceptTouchEvent(true)

return true
}
return false
}

public override fun onTouchEvent(ev: MotionEvent): Boolean {
if (ev.actionMasked == MotionEvent.ACTION_UP && nativeGestureStarted) {
NativeGestureUtil.notifyNativeGestureEnded(this, ev)
nativeGestureStarted = false
}
return super.onTouchEvent(ev)
}

/**
* [SwipeRefreshLayout] completely bypasses ViewGroup's "disallowIntercept" by overriding
* [ViewGroup.onInterceptTouchEvent] and never calling super.onInterceptTouchEvent(). This means
* that horizontal scrolls will always be intercepted, even though they shouldn't, so we have to
* check for that manually here.
*/
private fun shouldInterceptTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
prevTouchX = ev.x
intercepted = false
}
MotionEvent.ACTION_MOVE -> {
val eventX = ev.x
val xDiff = Math.abs(eventX - prevTouchX)

if (intercepted || xDiff > touchSlop) {
intercepted = true
return false
}
}
}
return true
}

private companion object {
private const val DEFAULT_CIRCLE_TARGET = 64f
}
}

0 comments on commit db3c1a4

Please sign in to comment.