expo-scroll-forwarder 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,10 +4,9 @@ import android.animation.ValueAnimator
4
4
  import android.content.Context
5
5
  import android.view.GestureDetector
6
6
  import android.view.MotionEvent
7
- import android.view.View
8
- import android.widget.ScrollView
9
7
  import android.view.animation.DecelerateInterpolator
10
8
  import androidx.core.view.GestureDetectorCompat
9
+ import com.facebook.react.views.scroll.ReactScrollView
11
10
  import expo.modules.kotlin.AppContext
12
11
  import expo.modules.kotlin.views.ExpoView
13
12
  import kotlin.math.abs
@@ -20,7 +19,7 @@ class ExpoScrollForwarderView(context: Context, appContext: AppContext) : ExpoVi
20
19
  tryFindScrollView()
21
20
  }
22
21
 
23
- private var targetScrollView: ScrollView? = null
22
+ private var reactScrollView: ReactScrollView? = null
24
23
  private var gestureDetector: GestureDetectorCompat
25
24
  private var isScrolling = false
26
25
  private var initialScrollY = 0
@@ -33,7 +32,7 @@ class ExpoScrollForwarderView(context: Context, appContext: AppContext) : ExpoVi
33
32
  }
34
33
 
35
34
  override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
36
- val scrollView = targetScrollView ?: return super.onInterceptTouchEvent(ev)
35
+ val scrollView = reactScrollView ?: return super.onInterceptTouchEvent(ev)
37
36
 
38
37
  when (ev.action) {
39
38
  MotionEvent.ACTION_DOWN -> {
@@ -65,7 +64,7 @@ class ExpoScrollForwarderView(context: Context, appContext: AppContext) : ExpoVi
65
64
  }
66
65
 
67
66
  override fun onTouchEvent(event: MotionEvent): Boolean {
68
- val scrollView = targetScrollView ?: return super.onTouchEvent(event)
67
+ val scrollView = reactScrollView ?: return super.onTouchEvent(event)
69
68
 
70
69
  gestureDetector.onTouchEvent(event)
71
70
 
@@ -135,7 +134,7 @@ class ExpoScrollForwarderView(context: Context, appContext: AppContext) : ExpoVi
135
134
  }
136
135
  }
137
136
 
138
- private fun startDecayAnimation(scrollView: ScrollView, velocity: Float) {
137
+ private fun startDecayAnimation(scrollView: ReactScrollView, velocity: Float) {
139
138
  var currentVelocity = velocity.coerceIn(-5000f, 5000f)
140
139
  val startOffset = scrollView.scrollY
141
140
  var currentOffset = startOffset.toFloat()
@@ -180,9 +179,22 @@ class ExpoScrollForwarderView(context: Context, appContext: AppContext) : ExpoVi
180
179
  }
181
180
  }
182
181
 
183
- private fun triggerRefresh(scrollView: ScrollView) {
184
- // Simply scroll to refresh position and let React Native's RefreshControl handle it
185
- scrollView.scrollY = -140
182
+ private fun triggerRefresh(scrollView: ReactScrollView) {
183
+ // Trigger refresh by finding and activating the refresh control
184
+ try {
185
+ for (i in 0 until scrollView.childCount) {
186
+ val child = scrollView.getChildAt(i)
187
+ if (child.javaClass.simpleName.contains("Refresh")) {
188
+ // Try to invoke setRefreshing via reflection
189
+ val method = child.javaClass.getMethod("setRefreshing", Boolean::class.javaPrimitiveType)
190
+ method.invoke(child, true)
191
+ break
192
+ }
193
+ }
194
+ } catch (e: Exception) {
195
+ // Fallback: just scroll to trigger position
196
+ scrollView.scrollY = -140
197
+ }
186
198
  }
187
199
 
188
200
  private fun stopDecayAnimation() {
@@ -193,7 +205,7 @@ class ExpoScrollForwarderView(context: Context, appContext: AppContext) : ExpoVi
193
205
  private fun tryFindScrollView() {
194
206
  val tag = scrollViewTag ?: return
195
207
 
196
- targetScrollView = appContext?.findView(tag) as? ScrollView
208
+ reactScrollView = appContext?.findView(tag) as? ReactScrollView
197
209
  }
198
210
 
199
211
  inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
@@ -1,4 +1,5 @@
1
1
  import ExpoModulesCore
2
+ import UIKit
2
3
 
3
4
  class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
4
5
  var scrollViewTag: Int? {
@@ -8,6 +9,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
8
9
  }
9
10
 
10
11
  private var rctScrollView: RCTScrollView?
12
+ private var rctRefreshCtrl: UIRefreshControl?
11
13
  private var cancelGestureRecognizers: [UIGestureRecognizer]?
12
14
  private var animTimer: Timer?
13
15
  private var initialOffset: CGFloat = 0.0
@@ -36,7 +38,6 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
36
38
  if gestureRecognizer is UIPanGestureRecognizer, otherGestureRecognizer is UIPanGestureRecognizer {
37
39
  return false
38
40
  }
39
-
40
41
  return true
41
42
  }
42
43
 
@@ -68,7 +69,6 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
68
69
  if sv.contentOffset.y < 0 {
69
70
  sv.contentOffset.y = 0
70
71
  }
71
-
72
72
  self.initialOffset = sv.contentOffset.y
73
73
  }
74
74
 
@@ -78,7 +78,6 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
78
78
  if sv.contentOffset.y <= -130, !didImpact {
79
79
  let generator = UIImpactFeedbackGenerator(style: .light)
80
80
  generator.impactOccurred()
81
-
82
81
  self.didImpact = true
83
82
  }
84
83
  }
@@ -88,8 +87,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
88
87
  self.didImpact = false
89
88
 
90
89
  if sv.contentOffset.y <= -130 {
91
- // Trigger refresh by setting content offset
92
- sv.setContentOffset(CGPoint(x: 0, y: -140), animated: true)
90
+ self.triggerRefresh()
93
91
  return
94
92
  }
95
93
 
@@ -101,23 +99,43 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
101
99
  }
102
100
  }
103
101
 
102
+ func triggerRefresh() {
103
+ guard let sv = self.rctScrollView?.scrollView,
104
+ let refreshControl = sv.refreshControl else {
105
+ return
106
+ }
107
+
108
+ // Animate to the proper refresh position
109
+ UIView.animate(withDuration: 0.3, delay: 0, options: .beginFromCurrentState, animations: {
110
+ sv.contentOffset = CGPoint(x: 0, y: -65)
111
+ }, completion: { [weak self] _ in
112
+ // Begin refreshing and trigger the control changed event
113
+ refreshControl.beginRefreshing()
114
+
115
+ // Use the proper RCT method if available (for React Native refresh control)
116
+ if refreshControl.responds(to: Selector(("forwarderBeginRefreshing"))) {
117
+ refreshControl.perform(Selector(("forwarderBeginRefreshing")))
118
+ } else {
119
+ // Fallback: trigger the valueChanged action manually
120
+ refreshControl.sendActions(for: .valueChanged)
121
+ }
122
+ })
123
+ }
124
+
104
125
  func startDecayAnimation(_ translation: CGFloat, _ velocity: CGFloat) {
105
126
  guard let sv = self.rctScrollView?.scrollView else {
106
127
  return
107
128
  }
108
129
 
109
130
  var velocity = velocity
110
-
111
131
  self.enableCancelGestureRecognizers()
112
132
 
113
- if velocity > 0 {
114
- velocity = min(velocity, 5000)
115
- } else {
116
- velocity = max(velocity, -5000)
117
- }
133
+ velocity = velocity > 0 ? min(velocity, 5000) : max(velocity, -5000)
118
134
 
119
135
  var animTranslation = -translation
120
- self.animTimer = Timer.scheduledTimer(withTimeInterval: 1.0 / 120, repeats: true) { _ in
136
+ self.animTimer = Timer.scheduledTimer(withTimeInterval: 1.0 / 120, repeats: true) { [weak self] _ in
137
+ guard let self = self else { return }
138
+
121
139
  velocity *= 0.9875
122
140
  animTranslation = (-velocity / 120) + animTranslation
123
141
 
@@ -129,7 +147,6 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
129
147
  } else {
130
148
  sv.contentOffset.y = 0
131
149
  }
132
-
133
150
  self.stopTimer()
134
151
  return
135
152
  } else {
@@ -146,7 +163,6 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
146
163
  if offset < 0 {
147
164
  return offset - (offset * 0.55)
148
165
  }
149
-
150
166
  return offset
151
167
  }
152
168
 
@@ -159,6 +175,8 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
159
175
 
160
176
  self.rctScrollView = self.appContext?
161
177
  .findView(withTag: scrollViewTag, ofType: RCTScrollView.self)
178
+
179
+ self.rctRefreshCtrl = self.rctScrollView?.scrollView?.refreshControl
162
180
 
163
181
  self.addCancelGestureRecognizers()
164
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-scroll-forwarder",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Forward scroll gestures from any view to a ScrollView in React Native and Expo.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",