my fork of the bluesky client
1diff --git a/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm b/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm
2index caa5540..c5d4e67 100644
3--- a/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm
4+++ b/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm
5@@ -73,7 +73,7 @@ @implementation RCTFileReaderModule
6 } else {
7 NSString *type = [RCTConvert NSString:blob[@"type"]];
8 NSString *text = [NSString stringWithFormat:@"data:%@;base64,%@",
9- type != nil && [type length] > 0 ? type : @"application/octet-stream",
10+ ![type isEqual:[NSNull null]] && [type length] > 0 ? type : @"application/octet-stream",
11 [data base64EncodedStringWithOptions:0]];
12
13 resolve(text);
14diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
15index b0d71dc..41b9a0e 100644
16--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
17+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
18@@ -377,10 +377,6 @@ - (void)textInputDidBeginEditing
19 self.backedTextInputView.attributedText = [NSAttributedString new];
20 }
21
22- if (_selectTextOnFocus) {
23- [self.backedTextInputView selectAll:nil];
24- }
25-
26 [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
27 reactTag:self.reactTag
28 text:[self.backedTextInputView.attributedText.string copy]
29@@ -611,6 +607,10 @@ - (UIView *)reactAccessibilityElement
30 - (void)reactFocus
31 {
32 [self.backedTextInputView reactFocus];
33+
34+ if (_selectTextOnFocus) {
35+ [self.backedTextInputView selectAll:nil];
36+ }
37 }
38
39 - (void)reactBlur
40diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
41index e9b330f..ec5f58c 100644
42--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
43+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
44@@ -15,5 +15,8 @@
45 @property (nonatomic, copy) NSString *title;
46 @property (nonatomic, copy) RCTDirectEventBlock onRefresh;
47 @property (nonatomic, weak) UIScrollView *scrollView;
48+@property (nonatomic, copy) UIColor *customTintColor;
49+
50+- (void)forwarderBeginRefreshing;
51
52 @end
53diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
54index b09e653..d2b4e05 100644
55--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
56+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
57@@ -22,6 +22,7 @@ @implementation RCTRefreshControl {
58 NSString *_title;
59 UIColor *_titleColor;
60 CGFloat _progressViewOffset;
61+ UIColor *_customTintColor;
62 }
63
64 - (instancetype)init
65@@ -56,6 +57,12 @@ - (void)layoutSubviews
66 _isInitialRender = false;
67 }
68
69+- (void)didMoveToSuperview
70+{
71+ [super didMoveToSuperview];
72+ [self setTintColor:_customTintColor];
73+}
74+
75 - (void)beginRefreshingProgrammatically
76 {
77 UInt64 beginRefreshingTimestamp = _currentRefreshingStateTimestamp;
78@@ -203,4 +210,58 @@ - (void)refreshControlValueChanged
79 }
80 }
81
82+- (void)setCustomTintColor:(UIColor *)customTintColor
83+{
84+ _customTintColor = customTintColor;
85+ [self setTintColor:customTintColor];
86+}
87+
88+// Fix for https://github.com/facebook/react-native/issues/43388
89+// A bug in iOS 17.4 causes the haptic to not play when refreshing if the tintColor
90+// is set before the refresh control gets added to the scrollview. We'll call this
91+// function whenever the superview changes. We'll also call it if the value of customTintColor
92+// changes.
93+- (void)setTintColor:(UIColor *)tintColor
94+{
95+ if ([self.superview isKindOfClass:[UIScrollView class]] && self.tintColor != tintColor) {
96+ [super setTintColor:tintColor];
97+ }
98+}
99+
100+/*
101+ This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native
102+ libraries to perform a refresh of a scrollview and access the refresh control's onRefresh
103+ function.
104+ */
105+- (void)forwarderBeginRefreshing
106+{
107+ _refreshingProgrammatically = NO;
108+
109+ [self sizeToFit];
110+
111+ if (!self.scrollView) {
112+ return;
113+ }
114+
115+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
116+
117+ [UIView animateWithDuration:0.3
118+ delay:0
119+ options:UIViewAnimationOptionBeginFromCurrentState
120+ animations:^(void) {
121+ // Whenever we call this method, the scrollview will always be at a position of
122+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
123+ [scrollView setContentOffset:CGPointMake(0, -65)];
124+ }
125+ completion:^(__unused BOOL finished) {
126+ [super beginRefreshing];
127+ [self setCurrentRefreshingState:super.refreshing];
128+
129+ if (self->_onRefresh) {
130+ self->_onRefresh(nil);
131+ }
132+ }
133+ ];
134+}
135+
136 @end
137diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
138index 40aaf9c..1c60164 100644
139--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
140+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
141@@ -22,11 +22,12 @@ - (UIView *)view
142
143 RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock)
144 RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL)
145-RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
146 RCT_EXPORT_VIEW_PROPERTY(title, NSString)
147 RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
148 RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat)
149
150+RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor)
151+
152 RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing)
153 {
154 [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
155diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
156index 1aead51..39e6244 100644
157--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
158+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
159@@ -159,31 +159,6 @@ - (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view
160 return !shouldDisableScrollInteraction;
161 }
162
163-/*
164- * Automatically centers the content such that if the content is smaller than the
165- * ScrollView, we force it to be centered, but when you zoom or the content otherwise
166- * becomes larger than the ScrollView, there is no padding around the content but it
167- * can still fill the whole view.
168- */
169-- (void)setContentOffset:(CGPoint)contentOffset
170-{
171- UIView *contentView = [self contentView];
172- if (contentView && _centerContent && !CGSizeEqualToSize(contentView.frame.size, CGSizeZero)) {
173- CGSize subviewSize = contentView.frame.size;
174- CGSize scrollViewSize = self.bounds.size;
175- if (subviewSize.width <= scrollViewSize.width) {
176- contentOffset.x = -(scrollViewSize.width - subviewSize.width) / 2.0;
177- }
178- if (subviewSize.height <= scrollViewSize.height) {
179- contentOffset.y = -(scrollViewSize.height - subviewSize.height) / 2.0;
180- }
181- }
182-
183- super.contentOffset = CGPointMake(
184- RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"),
185- RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
186-}
187-
188 - (void)setFrame:(CGRect)frame
189 {
190 // Preserving and revalidating `contentOffset`.
191@@ -427,6 +402,11 @@ - (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews
192 // Does nothing
193 }
194
195+- (void)setFrame:(CGRect)frame {
196+ [super setFrame:frame];
197+ [self centerContentIfNeeded];
198+}
199+
200 - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
201 {
202 [super insertReactSubview:view atIndex:atIndex];
203@@ -444,6 +424,8 @@ - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
204 RCTApplyTransformationAccordingLayoutDirection(_contentView, self.reactLayoutDirection);
205 [_scrollView addSubview:view];
206 }
207+
208+ [self centerContentIfNeeded];
209 }
210
211 - (void)removeReactSubview:(UIView *)subview
212@@ -652,9 +634,46 @@ -(void)delegateMethod : (UIScrollView *)scrollView \
213 }
214
215 RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin)
216-RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
217 RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
218
219+-(void)scrollViewDidZoom : (UIScrollView *)scrollView
220+{
221+ [self centerContentIfNeeded];
222+
223+ RCT_SEND_SCROLL_EVENT(onScroll, nil);
224+ RCT_FORWARD_SCROLL_EVENT(scrollViewDidZoom : scrollView);
225+}
226+
227+/*
228+ * Automatically centers the content such that if the content is smaller than the
229+ * ScrollView, we force it to be centered, but when you zoom or the content otherwise
230+ * becomes larger than the ScrollView, there is no padding around the content but it
231+ * can still fill the whole view.
232+ *
233+ * PATCHED: This deviates from the original React Native implementation to fix two issues:
234+ *
235+ * - The scroll view was swallowing any taps immediately after pinching
236+ * - The scroll view was jittering when crossing the full screen threshold while pinching
237+ *
238+ * This implementation is based on https://petersteinberger.com/blog/2013/how-to-center-uiscrollview/.
239+ */
240+-(void)centerContentIfNeeded
241+{
242+ if (_scrollView.centerContent &&
243+ !CGSizeEqualToSize(self.contentSize, CGSizeZero) &&
244+ !CGSizeEqualToSize(self.bounds.size, CGSizeZero)
245+ ) {
246+ CGFloat top = 0, left = 0;
247+ if (self.contentSize.width < self.bounds.size.width) {
248+ left = (self.bounds.size.width - self.contentSize.width) * 0.5f;
249+ }
250+ if (self.contentSize.height < self.bounds.size.height) {
251+ top = (self.bounds.size.height - self.contentSize.height) * 0.5f;
252+ }
253+ _scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left);
254+ }
255+}
256+
257 - (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener
258 {
259 [_scrollListeners addObject:scrollListener];
260@@ -913,6 +932,7 @@ - (void)updateContentSizeIfNeeded
261 CGSize contentSize = self.contentSize;
262 if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) {
263 _scrollView.contentSize = contentSize;
264+ [self centerContentIfNeeded];
265 }
266 }
267
268diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
269index 5f5e1ab..aac00b6 100644
270--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
271+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
272@@ -99,8 +99,9 @@ public class JavaTimerManager {
273 }
274
275 // If the JS thread is busy for multiple frames we cancel any other pending runnable.
276- if (mCurrentIdleCallbackRunnable != null) {
277- mCurrentIdleCallbackRunnable.cancel();
278+ IdleCallbackRunnable currentRunnable = mCurrentIdleCallbackRunnable;
279+ if (currentRunnable != null) {
280+ currentRunnable.cancel();
281 }
282
283 mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos);