forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm
2index b62a2e2..cb469db 100644
3--- a/node_modules/react-native-screens/ios/RNSScreen.mm
4+++ b/node_modules/react-native-screens/ios/RNSScreen.mm
5@@ -729,9 +729,26 @@ - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingFor
6 #endif
7 }
8
9-#if !RCT_NEW_ARCH_ENABLED
10+- (void)willMoveToWindow:(UIWindow *)newWindow
11+{
12+ if (@available(iOS 26, *)) {
13+ // In iOS 26, as soon as another screen appears in transition, it is interactable
14+ // To avoid glitches resulting from clicking buttons mid transition, we temporarily disable all interactions
15+ // Disabling interactions for parent navigation controller won't be enough in case of nested stack
16+ // Furthermore, a stack put inside a modal will exist in an entirely different hierarchy
17+ // To be sure, we block interactions on the whole window.
18+ // Note that newWindows is nil when moving from instead of moving to, and Obj-C handles nil correctly
19+ newWindow.userInteractionEnabled = false;
20+ }
21+}
22+
23 - (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
24 {
25+ if (@available(iOS 26, *)) {
26+ // Disable interactions to disallow multiple modals dismissed at once; see willMoveToWindow
27+ presentationController.containerView.window.userInteractionEnabled = false;
28+ }
29+#if !RCT_NEW_ARCH_ENABLED
30 // On Paper, we need to call both "cancel" and "reset" here because RN's gesture
31 // recognizer does not handle the scenario when it gets cancelled by other top
32 // level gesture recognizer. In this case by the modal dismiss gesture.
33@@ -744,8 +761,8 @@ - (void)presentationControllerWillDismiss:(UIPresentationController *)presentati
34 // down.
35 [_touchHandler cancel];
36 [_touchHandler reset];
37-}
38 #endif // !RCT_NEW_ARCH_ENABLED
39+}
40
41 - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
42 {
43@@ -757,6 +774,10 @@ - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presenta
44
45 - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController
46 {
47+ if (@available(iOS 26, *)) {
48+ // Reenable interactions; see presentationControllerWillDismiss
49+ presentationController.containerView.window.userInteractionEnabled = true;
50+ }
51 // NOTE(kkafar): We should consider depracating the use of gesture cancel here & align
52 // with usePreventRemove API of react-navigation v7.
53 [self notifyGestureCancel];
54@@ -767,6 +788,11 @@ - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)pr
55
56 - (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
57 {
58+ if (@available(iOS 26, *)) {
59+ // Reenable interactions; see presentationControllerWillDismiss
60+ // Dismissed screen doesn't hold a reference to window, but presentingViewController.view does
61+ presentationController.presentingViewController.view.window.userInteractionEnabled = true;
62+ }
63 if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
64 [_reactSuperview performSelector:@selector(presentationControllerDidDismiss:) withObject:presentationController];
65 }
66@@ -1518,6 +1544,10 @@ - (void)viewWillDisappear:(BOOL)animated
67
68 - (void)viewDidAppear:(BOOL)animated
69 {
70+ if (@available(iOS 26, *)) {
71+ // Reenable interactions, see willMoveToWindow
72+ self.view.window.userInteractionEnabled = true;
73+ }
74 [super viewDidAppear:animated];
75 if (!_isSwiping || _shouldNotify) {
76 // we are going forward or dismissing without swipe
77diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm
78index 229dc58..10b365b 100644
79--- a/node_modules/react-native-screens/ios/RNSScreenStack.mm
80+++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm
81@@ -62,26 +62,6 @@ @interface RNSScreenStackView () <
82
83 @implementation RNSNavigationController
84
85-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
86-- (void)viewDidLoad
87-{
88- // iOS 26 introduces new gesture recognizer which replaces our RNSPanGestureRecognizer.
89- // The problem is that we are not able to handle it here for various reasons:
90- // - the new recognizer comes with its own delegate and our current approach is to wire
91- // all recognizers to RNSScreenStackView; to be 100% sure we don't break the logic,
92- // we would have to decorate its delegate and call it after our code, which would
93- // break other recognizers that the stack view is the delegate for
94- // - when RNSScreenStackView.setupGestureHandler method is called, the recognizer hasn't been
95- // loaded yet and there is no other place to configure in a not "hacky" way
96- // - the official docs warn us to not use it for anything other than "setting up failure requirements with it"
97- // - we expose fullScreenGestureEnabled prop to enable/disable the feature,
98- // so we need control over the delegate
99- if (@available(iOS 26.0, *)) {
100- self.interactiveContentPopGestureRecognizer.enabled = NO;
101- }
102-}
103-#endif // iOS 26
104-
105 #if !TARGET_OS_TV
106 - (UIViewController *)childViewControllerForStatusBarStyle
107 {
108@@ -219,50 +199,6 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController
109 return false;
110 }
111
112-#pragma mark - UINavigationBarDelegate
113-
114-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
115-- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
116-{
117- if (@available(iOS 26, *)) {
118- // To prevent popping multiple screens when back button is pressed repeatedly,
119- // We allow for pop operation to proceed only if no transition is in progress,
120- // which we check indirectly by checking if transitionCoordinator is set.
121- // If it's not, we are safe to proceed.
122- if (self.transitionCoordinator == nil) {
123- // We still need to disable interactions for back button so click effects are not applied,
124- // and there is unfortunately no better place for it currently
125- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
126- if (button != nil) {
127- button.userInteractionEnabled = false;
128- }
129-
130- return true;
131- }
132-
133- return false;
134- }
135-
136- return true;
137-}
138-
139-- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
140-{
141- if (@available(iOS 26, *)) {
142- // Reset interactions on back button -> see navigationBar:shouldPopItem
143- // IMPORTANT: This reset won't execute when preventNativeDismiss is on.
144- // However, on iOS 26, unlike in previous versions, the back button instance changes
145- // when handling preventNativeDismiss and userIteractionEnabled is reset.
146- // The instance also changes when regular screen pop happens, but in that case
147- // the value of userInteractionEnabled is carried on, and we reset it here.
148- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
149- if (button != nil) {
150- button.userInteractionEnabled = true;
151- }
152- }
153-}
154-#endif // Check for iOS >= 26
155-
156 #pragma mark - RNSFrameCorrectionProvider
157
158 #ifdef RNS_GAMMA_ENABLED
159@@ -327,7 +263,7 @@ @implementation RNSScreenStackView {
160 UINavigationController *_controller;
161 NSMutableArray<RNSScreenView *> *_reactSubviews;
162 BOOL _invalidated;
163- BOOL _isFullWidthSwiping;
164+ BOOL _isFullWidthSwipingWithPanGesture; // used only for content swipe with RNSPanGestureRecognizer
165 RNSPercentDrivenInteractiveTransition *_interactionController;
166 __weak RNSScreenStackManager *_manager;
167 BOOL _updateScheduled;
168@@ -522,6 +458,11 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller
169 [self addSubview:controller.view];
170 #if !TARGET_OS_TV
171 _controller.interactivePopGestureRecognizer.delegate = self;
172+ #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
173+ if (@available(iOS 26, *)) {
174+ _controller.interactiveContentPopGestureRecognizer.delegate = self;
175+ }
176+#endif // Check for iOS >= 26.0
177 #endif
178 [controller didMoveToParentViewController:parentView.reactViewController];
179 // On iOS pre 12 we observed that `willShowViewController` delegate method does not always
180@@ -943,7 +884,7 @@ - (void)dismissOnReload
181 // when preventing the native dismiss with back button, we have to return the animator.
182 // Also, we need to return the animator when full width swiping even if the animation is not custom,
183 // otherwise the screen will be just popped immediately due to no animation
184- ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwiping ||
185+ ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwipingWithPanGesture ||
186 [RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation] || _customAnimation)) {
187 return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
188 }
189@@ -967,23 +908,39 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
190 }
191 RNSScreenView *topScreen = _reactSubviews.lastObject;
192
193+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
194+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
195+
196 #if TARGET_OS_TV || TARGET_OS_VISION
197 [self cancelTouchesInParent];
198 return YES;
199 #else
200- // RNSPanGestureRecognizer will receive events iff topScreen.fullScreenSwipeEnabled == YES;
201- // Events are filtered in gestureRecognizer:shouldReceivePressOrTouchEvent: method
202 if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
203- if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
204- _isFullWidthSwiping = YES;
205- [self cancelTouchesInParent];
206- return YES;
207+ // On iOS < 26, we have a custom full screen swipe recognizer that functions similarily
208+ // to interactiveContentPopGestureRecognizer introduced in iOS 26.
209+ // On iOS >= 26, we want to use the native one, but we are unable to handle custom animations
210+ // with native interactiveContentPopGestureRecognizer, so we have to fallback to the old implementation.
211+ // In this case, the old one should behave as close as the new native one, having only the difference
212+ // in animation, and without any customization that is exclusive for it (e.g. gestureResponseDistance).
213+ if (@available(iOS 26, *)) {
214+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
215+ _isFullWidthSwipingWithPanGesture = YES;
216+ [self cancelTouchesInParent];
217+ return YES;
218+ }
219+ return NO;
220+ } else {
221+ if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
222+ _isFullWidthSwipingWithPanGesture = YES;
223+ [self cancelTouchesInParent];
224+ return YES;
225+ }
226+ return NO;
227 }
228- return NO;
229 }
230
231 // Now we're dealing with RNSScreenEdgeGestureRecognizer (or _UIParallaxTransitionPanGestureRecognizer)
232- if (topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]) {
233+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
234 if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) {
235 UIRectEdge edges = ((RNSScreenEdgeGestureRecognizer *)gestureRecognizer).edges;
236 BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft;
237@@ -1028,7 +985,9 @@ - (void)setupGestureHandlers
238 rightEdgeSwipeGestureRecognizer.delegate = self;
239 [self addGestureRecognizer:rightEdgeSwipeGestureRecognizer];
240
241- // gesture recognizer for full width swipe gesture
242+ // Starting from iOS 26, RNSPanGestureRecognizer has been mostly replaced by native
243+ // interactiveContentPopGestureRecognizer. It still needs to handle custom dismiss animations,
244+ // which we are not able to handle with the latter.
245 RNSPanGestureRecognizer *panRecognizer = [[RNSPanGestureRecognizer alloc] initWithTarget:self
246 action:@selector(handleSwipe:)];
247 panRecognizer.delegate = self;
248@@ -1091,7 +1050,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
249 [_interactionController cancelInteractiveTransition];
250 }
251 _interactionController = nil;
252- _isFullWidthSwiping = NO;
253+ _isFullWidthSwipingWithPanGesture = NO;
254 }
255 default: {
256 break;
257@@ -1225,14 +1184,6 @@ - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognize
258 // Be careful when adding another type of gesture recognizer.
259 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePressOrTouchEvent:(NSObject *)event
260 {
261- if (@available(iOS 26, *)) {
262- // in iOS 26, you can swipe to pop screen before the previous one finished transitioning;
263- // this prevents from registering the second gesture
264- if ([self isTransitionInProgress]) {
265- return NO;
266- }
267- }
268-
269 RNSScreenView *topScreen = _reactSubviews.lastObject;
270
271 for (RNSScreenView *s in _reactSubviews.reverseObjectEnumerator) {
272@@ -1249,10 +1200,30 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
273 return NO;
274 }
275
276+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
277+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
278+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
279+ if (@available(iOS 26, *)) {
280+ // On iOS 26, fullScreenSwipeEnabled takes no effect, and depending on whether custom animations are on,
281+ // we select either interactiveContentPopGestureRecognizer or RNSPanGestureRecognizer
282+ if (([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] &&
283+ !customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) ||
284+ (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
285+ customAnimationOnSwipePropSetAndSelectedAnimationIsCustom)) {
286+ return NO;
287+ }
288+ } else {
289+ // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
290+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
291+ return topScreen.fullScreenSwipeEnabled;
292+ }
293+ }
294+#else // check for iOS >= 26
295 // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
296 if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
297 return topScreen.fullScreenSwipeEnabled;
298 }
299+#endif // check for iOS >= 26
300
301 // RNSScreenEdgeGestureRecognizer || _UIParallaxTransitionPanGestureRecognizer
302 return YES;
303@@ -1268,15 +1239,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
304 return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:touch];
305 }
306
307-- (BOOL)isTransitionInProgress
308-{
309- if (_controller.transitionCoordinator != nil) {
310- return YES;
311- }
312-
313- return NO;
314-}
315-
316 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
317 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
318 {
319@@ -1289,7 +1251,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
320 if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) {
321 return NO;
322 }
323-
324 return YES;
325 }
326 return NO;