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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
184
|
-
//
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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