tangled mirror of catsky-🐱 Soothing soft social-app fork with all the niche toggles! (Unofficial); for issues and PRs please put them on github:NekoDrone/catsky-social
···6363### Tips
64646565- Copy the `.env.example` to `.env` and fill in any necessary tokens. (The Sentry token is NOT required; see instructions below if you want to enable Sentry.)
6666-- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release`.
6666+- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release` on Android or `--configuration Release` on iOS.
6767- If you want to use Expo EAS on your own builds without ejecting from Expo, make sure to change the `owner` and `extra.eas.projectId` properties. If you do not have an Expo account, you may remove these properties.
6868- `npx react-native info` Checks what has been installed.
6969- If the Android simulator frequently hangs or is very sluggish, [bump its memory limit](https://stackoverflow.com/a/40068396)
···164164- TextEncoder / TextDecoder
165165- react-native-url-polyfill
166166- Array#findLast (on web)
167167-- atob (on native)
168167169168### Sentry sourcemaps
170169
···11import ExpoModulesCore
22+import React
2334// This view will be used as a native component. Make sure to inherit from `ExpoView`
45// to apply the proper styling (e.g. border radius and shadows).
···11+# @sentry/react-native/scripts/expo-upload-sourcemaps.js patch
22+33+Lets us specify the output directory for the sourcemaps via an environment variable.
44+55+# @sentry/react-native/dist/js/tools/sentryMetroSerializer.js patch
66+77+Patch of this: https://github.com/getsentry/sentry-react-native/issues/5180#issuecomment-3311772038
88+99+Will be fixed in an upcoming release of @sentry/react-native - remove when available.
···11-diff --git a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
22-index eae3989..432745a 100644
33---- a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
44-+++ b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
55-@@ -416,6 +416,10 @@ void NativeProxy::progressLayoutAnimation(
66- tag, newPropsJNI, isSharedTransition);
77- }
88-99-+void NativeProxy::endLayoutAnimation(int tag, bool shouldRemove) {
1010-+ layoutAnimations_->cthis()->endLayoutAnimation(tag, shouldRemove);
1111-+}
1212-+
1313- PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
1414- #ifdef RCT_NEW_ARCH_ENABLED
1515- // nothing
1616-@@ -455,14 +459,7 @@ PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
1717- auto progressLayoutAnimation =
1818- bindThis(&NativeProxy::progressLayoutAnimation);
1919-2020-- auto endLayoutAnimation = [weakThis = weak_from_this()](
2121-- int tag, bool removeView) {
2222-- auto strongThis = weakThis.lock();
2323-- if (!strongThis) {
2424-- return;
2525-- }
2626-- strongThis->layoutAnimations_->cthis()->endLayoutAnimation(tag, removeView);
2727-- };
2828-+ auto endLayoutAnimation = bindThis(&NativeProxy::endLayoutAnimation);
2929-3030- auto maybeFlushUiUpdatesQueueFunction =
3131- bindThis(&NativeProxy::maybeFlushUIUpdatesQueue);
3232-diff --git a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
3333-index 2ee2cc8..2edb5c9 100644
3434---- a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
3535-+++ b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
3636-@@ -234,6 +234,8 @@ class NativeProxy : public jni::HybridClass<NativeProxy>,
3737- const jsi::Object &newProps,
3838- bool isSharedTransition);
3939-4040-+ void endLayoutAnimation(int tag, bool shouldRemove);
4141-+
4242- /***
4343- * Wraps a method of `NativeProxy` in a function object capturing `this`
4444- * @tparam TReturn return type of passed method
+326
patches/react-native-screens+4.16.0.patch
···11+diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm
22+index b62a2e2..cb469db 100644
33+--- a/node_modules/react-native-screens/ios/RNSScreen.mm
44++++ b/node_modules/react-native-screens/ios/RNSScreen.mm
55+@@ -729,9 +729,26 @@ - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingFor
66+ #endif
77+ }
88+99+-#if !RCT_NEW_ARCH_ENABLED
1010++- (void)willMoveToWindow:(UIWindow *)newWindow
1111++{
1212++ if (@available(iOS 26, *)) {
1313++ // In iOS 26, as soon as another screen appears in transition, it is interactable
1414++ // To avoid glitches resulting from clicking buttons mid transition, we temporarily disable all interactions
1515++ // Disabling interactions for parent navigation controller won't be enough in case of nested stack
1616++ // Furthermore, a stack put inside a modal will exist in an entirely different hierarchy
1717++ // To be sure, we block interactions on the whole window.
1818++ // Note that newWindows is nil when moving from instead of moving to, and Obj-C handles nil correctly
1919++ newWindow.userInteractionEnabled = false;
2020++ }
2121++}
2222++
2323+ - (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
2424+ {
2525++ if (@available(iOS 26, *)) {
2626++ // Disable interactions to disallow multiple modals dismissed at once; see willMoveToWindow
2727++ presentationController.containerView.window.userInteractionEnabled = false;
2828++ }
2929++#if !RCT_NEW_ARCH_ENABLED
3030+ // On Paper, we need to call both "cancel" and "reset" here because RN's gesture
3131+ // recognizer does not handle the scenario when it gets cancelled by other top
3232+ // level gesture recognizer. In this case by the modal dismiss gesture.
3333+@@ -744,8 +761,8 @@ - (void)presentationControllerWillDismiss:(UIPresentationController *)presentati
3434+ // down.
3535+ [_touchHandler cancel];
3636+ [_touchHandler reset];
3737+-}
3838+ #endif // !RCT_NEW_ARCH_ENABLED
3939++}
4040+4141+ - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
4242+ {
4343+@@ -757,6 +774,10 @@ - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presenta
4444+4545+ - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController
4646+ {
4747++ if (@available(iOS 26, *)) {
4848++ // Reenable interactions; see presentationControllerWillDismiss
4949++ presentationController.containerView.window.userInteractionEnabled = true;
5050++ }
5151+ // NOTE(kkafar): We should consider depracating the use of gesture cancel here & align
5252+ // with usePreventRemove API of react-navigation v7.
5353+ [self notifyGestureCancel];
5454+@@ -767,6 +788,11 @@ - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)pr
5555+5656+ - (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
5757+ {
5858++ if (@available(iOS 26, *)) {
5959++ // Reenable interactions; see presentationControllerWillDismiss
6060++ // Dismissed screen doesn't hold a reference to window, but presentingViewController.view does
6161++ presentationController.presentingViewController.view.window.userInteractionEnabled = true;
6262++ }
6363+ if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
6464+ [_reactSuperview performSelector:@selector(presentationControllerDidDismiss:) withObject:presentationController];
6565+ }
6666+@@ -1518,6 +1544,10 @@ - (void)viewWillDisappear:(BOOL)animated
6767+6868+ - (void)viewDidAppear:(BOOL)animated
6969+ {
7070++ if (@available(iOS 26, *)) {
7171++ // Reenable interactions, see willMoveToWindow
7272++ self.view.window.userInteractionEnabled = true;
7373++ }
7474+ [super viewDidAppear:animated];
7575+ if (!_isSwiping || _shouldNotify) {
7676+ // we are going forward or dismissing without swipe
7777+diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm
7878+index 229dc58..10b365b 100644
7979+--- a/node_modules/react-native-screens/ios/RNSScreenStack.mm
8080++++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm
8181+@@ -62,26 +62,6 @@ @interface RNSScreenStackView () <
8282+8383+ @implementation RNSNavigationController
8484+8585+-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
8686+-- (void)viewDidLoad
8787+-{
8888+- // iOS 26 introduces new gesture recognizer which replaces our RNSPanGestureRecognizer.
8989+- // The problem is that we are not able to handle it here for various reasons:
9090+- // - the new recognizer comes with its own delegate and our current approach is to wire
9191+- // all recognizers to RNSScreenStackView; to be 100% sure we don't break the logic,
9292+- // we would have to decorate its delegate and call it after our code, which would
9393+- // break other recognizers that the stack view is the delegate for
9494+- // - when RNSScreenStackView.setupGestureHandler method is called, the recognizer hasn't been
9595+- // loaded yet and there is no other place to configure in a not "hacky" way
9696+- // - the official docs warn us to not use it for anything other than "setting up failure requirements with it"
9797+- // - we expose fullScreenGestureEnabled prop to enable/disable the feature,
9898+- // so we need control over the delegate
9999+- if (@available(iOS 26.0, *)) {
100100+- self.interactiveContentPopGestureRecognizer.enabled = NO;
101101+- }
102102+-}
103103+-#endif // iOS 26
104104+-
105105+ #if !TARGET_OS_TV
106106+ - (UIViewController *)childViewControllerForStatusBarStyle
107107+ {
108108+@@ -219,50 +199,6 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController
109109+ return false;
110110+ }
111111+112112+-#pragma mark - UINavigationBarDelegate
113113+-
114114+-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
115115+-- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
116116+-{
117117+- if (@available(iOS 26, *)) {
118118+- // To prevent popping multiple screens when back button is pressed repeatedly,
119119+- // We allow for pop operation to proceed only if no transition is in progress,
120120+- // which we check indirectly by checking if transitionCoordinator is set.
121121+- // If it's not, we are safe to proceed.
122122+- if (self.transitionCoordinator == nil) {
123123+- // We still need to disable interactions for back button so click effects are not applied,
124124+- // and there is unfortunately no better place for it currently
125125+- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
126126+- if (button != nil) {
127127+- button.userInteractionEnabled = false;
128128+- }
129129+-
130130+- return true;
131131+- }
132132+-
133133+- return false;
134134+- }
135135+-
136136+- return true;
137137+-}
138138+-
139139+-- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
140140+-{
141141+- if (@available(iOS 26, *)) {
142142+- // Reset interactions on back button -> see navigationBar:shouldPopItem
143143+- // IMPORTANT: This reset won't execute when preventNativeDismiss is on.
144144+- // However, on iOS 26, unlike in previous versions, the back button instance changes
145145+- // when handling preventNativeDismiss and userIteractionEnabled is reset.
146146+- // The instance also changes when regular screen pop happens, but in that case
147147+- // the value of userInteractionEnabled is carried on, and we reset it here.
148148+- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
149149+- if (button != nil) {
150150+- button.userInteractionEnabled = true;
151151+- }
152152+- }
153153+-}
154154+-#endif // Check for iOS >= 26
155155+-
156156+ #pragma mark - RNSFrameCorrectionProvider
157157+158158+ #ifdef RNS_GAMMA_ENABLED
159159+@@ -327,7 +263,7 @@ @implementation RNSScreenStackView {
160160+ UINavigationController *_controller;
161161+ NSMutableArray<RNSScreenView *> *_reactSubviews;
162162+ BOOL _invalidated;
163163+- BOOL _isFullWidthSwiping;
164164++ BOOL _isFullWidthSwipingWithPanGesture; // used only for content swipe with RNSPanGestureRecognizer
165165+ RNSPercentDrivenInteractiveTransition *_interactionController;
166166+ __weak RNSScreenStackManager *_manager;
167167+ BOOL _updateScheduled;
168168+@@ -522,6 +458,11 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller
169169+ [self addSubview:controller.view];
170170+ #if !TARGET_OS_TV
171171+ _controller.interactivePopGestureRecognizer.delegate = self;
172172++ #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
173173++ if (@available(iOS 26, *)) {
174174++ _controller.interactiveContentPopGestureRecognizer.delegate = self;
175175++ }
176176++#endif // Check for iOS >= 26.0
177177+ #endif
178178+ [controller didMoveToParentViewController:parentView.reactViewController];
179179+ // On iOS pre 12 we observed that `willShowViewController` delegate method does not always
180180+@@ -943,7 +884,7 @@ - (void)dismissOnReload
181181+ // when preventing the native dismiss with back button, we have to return the animator.
182182+ // Also, we need to return the animator when full width swiping even if the animation is not custom,
183183+ // otherwise the screen will be just popped immediately due to no animation
184184+- ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwiping ||
185185++ ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwipingWithPanGesture ||
186186+ [RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation] || _customAnimation)) {
187187+ return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
188188+ }
189189+@@ -967,23 +908,39 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
190190+ }
191191+ RNSScreenView *topScreen = _reactSubviews.lastObject;
192192+193193++ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
194194++ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
195195++
196196+ #if TARGET_OS_TV || TARGET_OS_VISION
197197+ [self cancelTouchesInParent];
198198+ return YES;
199199+ #else
200200+- // RNSPanGestureRecognizer will receive events iff topScreen.fullScreenSwipeEnabled == YES;
201201+- // Events are filtered in gestureRecognizer:shouldReceivePressOrTouchEvent: method
202202+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
203203+- if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
204204+- _isFullWidthSwiping = YES;
205205+- [self cancelTouchesInParent];
206206+- return YES;
207207++ // On iOS < 26, we have a custom full screen swipe recognizer that functions similarily
208208++ // to interactiveContentPopGestureRecognizer introduced in iOS 26.
209209++ // On iOS >= 26, we want to use the native one, but we are unable to handle custom animations
210210++ // with native interactiveContentPopGestureRecognizer, so we have to fallback to the old implementation.
211211++ // In this case, the old one should behave as close as the new native one, having only the difference
212212++ // in animation, and without any customization that is exclusive for it (e.g. gestureResponseDistance).
213213++ if (@available(iOS 26, *)) {
214214++ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
215215++ _isFullWidthSwipingWithPanGesture = YES;
216216++ [self cancelTouchesInParent];
217217++ return YES;
218218++ }
219219++ return NO;
220220++ } else {
221221++ if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
222222++ _isFullWidthSwipingWithPanGesture = YES;
223223++ [self cancelTouchesInParent];
224224++ return YES;
225225++ }
226226++ return NO;
227227+ }
228228+- return NO;
229229+ }
230230+231231+ // Now we're dealing with RNSScreenEdgeGestureRecognizer (or _UIParallaxTransitionPanGestureRecognizer)
232232+- if (topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]) {
233233++ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
234234+ if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) {
235235+ UIRectEdge edges = ((RNSScreenEdgeGestureRecognizer *)gestureRecognizer).edges;
236236+ BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft;
237237+@@ -1028,7 +985,9 @@ - (void)setupGestureHandlers
238238+ rightEdgeSwipeGestureRecognizer.delegate = self;
239239+ [self addGestureRecognizer:rightEdgeSwipeGestureRecognizer];
240240+241241+- // gesture recognizer for full width swipe gesture
242242++ // Starting from iOS 26, RNSPanGestureRecognizer has been mostly replaced by native
243243++ // interactiveContentPopGestureRecognizer. It still needs to handle custom dismiss animations,
244244++ // which we are not able to handle with the latter.
245245+ RNSPanGestureRecognizer *panRecognizer = [[RNSPanGestureRecognizer alloc] initWithTarget:self
246246+ action:@selector(handleSwipe:)];
247247+ panRecognizer.delegate = self;
248248+@@ -1091,7 +1050,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
249249+ [_interactionController cancelInteractiveTransition];
250250+ }
251251+ _interactionController = nil;
252252+- _isFullWidthSwiping = NO;
253253++ _isFullWidthSwipingWithPanGesture = NO;
254254+ }
255255+ default: {
256256+ break;
257257+@@ -1225,14 +1184,6 @@ - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognize
258258+ // Be careful when adding another type of gesture recognizer.
259259+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePressOrTouchEvent:(NSObject *)event
260260+ {
261261+- if (@available(iOS 26, *)) {
262262+- // in iOS 26, you can swipe to pop screen before the previous one finished transitioning;
263263+- // this prevents from registering the second gesture
264264+- if ([self isTransitionInProgress]) {
265265+- return NO;
266266+- }
267267+- }
268268+-
269269+ RNSScreenView *topScreen = _reactSubviews.lastObject;
270270+271271+ for (RNSScreenView *s in _reactSubviews.reverseObjectEnumerator) {
272272+@@ -1249,10 +1200,30 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
273273+ return NO;
274274+ }
275275+276276++ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
277277++ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
278278++#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
279279++ if (@available(iOS 26, *)) {
280280++ // On iOS 26, fullScreenSwipeEnabled takes no effect, and depending on whether custom animations are on,
281281++ // we select either interactiveContentPopGestureRecognizer or RNSPanGestureRecognizer
282282++ if (([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] &&
283283++ !customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) ||
284284++ (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
285285++ customAnimationOnSwipePropSetAndSelectedAnimationIsCustom)) {
286286++ return NO;
287287++ }
288288++ } else {
289289++ // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
290290++ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
291291++ return topScreen.fullScreenSwipeEnabled;
292292++ }
293293++ }
294294++#else // check for iOS >= 26
295295+ // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
296296+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
297297+ return topScreen.fullScreenSwipeEnabled;
298298+ }
299299++#endif // check for iOS >= 26
300300+301301+ // RNSScreenEdgeGestureRecognizer || _UIParallaxTransitionPanGestureRecognizer
302302+ return YES;
303303+@@ -1268,15 +1239,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
304304+ return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:touch];
305305+ }
306306+307307+-- (BOOL)isTransitionInProgress
308308+-{
309309+- if (_controller.transitionCoordinator != nil) {
310310+- return YES;
311311+- }
312312+-
313313+- return NO;
314314+-}
315315+-
316316+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
317317+ shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
318318+ {
319319+@@ -1289,7 +1251,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
320320+ if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) {
321321+ return NO;
322322+ }
323323+-
324324+ return YES;
325325+ }
326326+ return NO;
···99import {type Alf, applyFonts, atoms, flatten} from '#/alf'
10101111/**
1212- * Util to calculate lineHeight from a text size atom and a leading atom
1313- *
1414- * Example:
1515- * `leading(atoms.text_md, atoms.leading_normal)` // => 24
1616- */
1717-export function leading<
1818- Size extends {fontSize?: number},
1919- Leading extends {lineHeight?: number},
2020->(textSize: Size, leading: Leading) {
2121- const size = textSize?.fontSize || atoms.text_md.fontSize
2222- const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
2323- return Math.round(size * lineHeight)
2424-}
2525-2626-/**
2712 * Ensures that `lineHeight` defaults to a relative value of `1`, or applies
2813 * other relative leading atoms.
2914 *
-8
src/alf/util/__tests__/colors.test.ts
···11import {jest} from '@jest/globals'
2233-import {logger} from '#/logger'
43import {transparentifyColor} from '../colorGeneration'
55-66-jest.mock('#/logger', () => ({
77- logger: {warn: jest.fn()},
88-}))
94105describe('transparentifyColor', () => {
116 beforeEach(() => {
···4136 const unsupported = 'blue'
4237 const result = transparentifyColor(unsupported, 0.5)
4338 expect(result).toBe(unsupported)
4444- expect(logger.warn).toHaveBeenCalledWith(
4545- `Could not make '${unsupported}' transparent`,
4646- )
4739 })
4840})
+1-62
src/alf/util/platform.ts
···11-import {Platform} from 'react-native'
22-33-import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
44-55-/**
66- * Identity function on web. Returns nothing on other platforms.
77- *
88- * Note: Platform splitting does not tree-shake away the other platforms,
99- * so don't do stuff like e.g. rely on platform-specific imports. Use
1010- * platform-split files instead.
1111- */
1212-export function web(value: any) {
1313- if (isWeb) {
1414- return value
1515- }
1616-}
1717-1818-/**
1919- * Identity function on iOS. Returns nothing on other platforms.
2020- *
2121- * Note: Platform splitting does not tree-shake away the other platforms,
2222- * so don't do stuff like e.g. rely on platform-specific imports. Use
2323- * platform-split files instead.
2424- */
2525-export function ios(value: any) {
2626- if (isIOS) {
2727- return value
2828- }
2929-}
3030-3131-/**
3232- * Identity function on Android. Returns nothing on other platforms..
3333- *
3434- * Note: Platform splitting does not tree-shake away the other platforms,
3535- * so don't do stuff like e.g. rely on platform-specific imports. Use
3636- * platform-split files instead.
3737- */
3838-export function android(value: any) {
3939- if (isAndroid) {
4040- return value
4141- }
4242-}
4343-4444-/**
4545- * Identity function on iOS and Android. Returns nothing on web.
4646- *
4747- * Note: Platform splitting does not tree-shake away the other platforms,
4848- * so don't do stuff like e.g. rely on platform-specific imports. Use
4949- * platform-split files instead.
5050- */
5151-export function native(value: any) {
5252- if (isNative) {
5353- return value
5454- }
5555-}
5656-5757-/**
5858- * Note: Platform splitting does not tree-shake away the other platforms,
5959- * so don't do stuff like e.g. rely on platform-specific imports. Use
6060- * platform-split files instead.
6161- */
6262-export const platform = Platform.select
11+export {android, ios, native, platform, web} from '@bsky.app/alf'
+1-1
src/alf/util/systemUI.ts
···11import * as SystemUI from 'expo-system-ui'
22+import {type Theme} from '@bsky.app/alf'
2334import {isAndroid} from '#/platform/detection'
44-import {type Theme} from '../types'
5566export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
77 if (isAndroid) {
+2-13
src/alf/util/themeSelector.ts
···11-import {type ThemeName} from '#/alf/types'
11+import {utils} from '@bsky.app/alf'
2233-export function select<T>(name: ThemeName, options: Record<ThemeName, T>) {
44- switch (name) {
55- case 'light':
66- return options.light
77- case 'dark':
88- return options.dark || options.dim
99- case 'dim':
1010- return options.dim || options.dark
1111- default:
1212- throw new Error(`select(theme, options) received unknown theme ${name}`)
1313- }
1414-}
33+export const select = utils.select
+1-1
src/alf/util/useColorModeTheme.ts
···11import React from 'react'
22import {type ColorSchemeName, useColorScheme} from 'react-native'
33+import {type ThemeName} from '@bsky.app/alf'
3445import {isWeb} from '#/platform/detection'
56import {useThemePrefs} from '#/state/shell'
67import {dark, dim, light} from '#/alf/themes'
77-import {type ThemeName} from '#/alf/types'
8899export function useColorModeTheme(): ThemeName {
1010 const theme = useThemeName()
···267267 scrollEventThrottle={50}
268268 onScroll={isAndroid ? onScroll : undefined}
269269 keyboardShouldPersistTaps="handled"
270270- stickyHeaderIndices={header ? [0] : undefined}>
270270+ // TODO: figure out why this positions the header absolutely (rather than stickily)
271271+ // on Android. fine to disable for now, because we don't have any
272272+ // dialogs that use this that actually scroll -sfn
273273+ stickyHeaderIndices={ios(header ? [0] : undefined)}>
271274 {header}
272275 {children}
273276 </KeyboardAwareScrollView>
···22 Children,
33 cloneElement,
44 isValidElement,
55- type ReactElement,
66- type ReactNode,
75 useCallback,
86 useEffect,
97 useMemo,
···2624 * screen reader support is enabled. THIS SHOULD BE USED SPARINGLY, only when
2725 * no better option is available.
2826 */
2929-export function FocusScope({children}: {children: ReactNode}) {
2727+export function FocusScope({children}: {children: React.ReactNode}) {
3028 const {screenReaderEnabled} = useA11y()
31293230 return screenReaderEnabled ? <FocusTrap>{children}</FocusTrap> : children
···4139 * they have reached the start or end of the content and tell them how to
4240 * remain within the active content section.
4341 */
4444-function FocusTrap({children}: {children: ReactNode}) {
4242+function FocusTrap({children}: {children: React.ReactNode}) {
4543 const {_} = useLingui()
4644 const child = useRef<View>(null)
4745···5351 const decoratedChildren = useMemo(() => {
5452 return Children.toArray(children).map((node, i) => {
5553 if (i === 0 && isValidElement(node)) {
5656- const n = node as ReactElement<any>
5454+ const n = node as React.ReactElement<any>
5755 if (n.props.ref !== undefined) {
5856 throw new Error(
5957 'FocusScope needs to override the ref on its first child.',
+1-2
src/components/FocusScope/index.web.tsx
···11-import {type ReactNode} from 'react'
21import {FocusScope as RadixFocusScope} from 'radix-ui/internal'
3243/*
···65 * use this in Dialogs and such already. It's here as a convenient counterpart
76 * to the hacky native solution.
87 */
99-export function FocusScope({children}: {children: ReactNode}) {
88+export function FocusScope({children}: {children: React.ReactNode}) {
109 return (
1110 <RadixFocusScope.FocusScope loop asChild trapped>
1211 {children}
···6262 </View>
63636464 <View style={[a.gap_sm, a.pb_lg]}>
6565- <Text style={[a.text_xl, a.leading_snug, a.font_heavy]}>
6565+ <Text style={[a.text_xl, a.leading_snug, a.font_bold]}>
6666 <Trans>
6767 You must complete age assurance in order to access this screen.
6868 </Trans>
···6262 <Dialog.ScrollableInner label={_(msg`How should we open this link?`)}>
6363 <View style={[a.gap_2xl]}>
6464 <View style={[a.gap_sm]}>
6565- <Text style={[a.font_heavy, a.text_2xl]}>
6565+ <Text style={[a.font_bold, a.text_2xl]}>
6666 <Trans>How should we open this link?</Trans>
6767 </Text>
6868 <Text style={[t.atoms.text_contrast_high, a.leading_snug, a.text_md]}>
···8282 style={[
8383 t.atoms.text_contrast_medium,
8484 a.text_sm,
8585- a.font_bold,
8585+ a.font_semi_bold,
8686 ]}>
8787 {' '}
8888 </Text>
···101101 style={[
102102 t.atoms.text,
103103 a.text_sm,
104104- a.font_bold,
104104+ a.font_semi_bold,
105105 {opacity: 0.7}, // NOTE: we use opacity 0.7 instead of a color to match the color of the home pager tab bar
106106 ]}>
107107 {topic.topic}
···11import {type ReactNode} from 'react'
22import {createContext, useContext} from 'react'
33import {type TextStyle, type ViewStyle} from 'react-native'
44+import {type ThemeName} from '@bsky.app/alf'
4555-import {type ThemeName} from '#/alf/types'
66import {darkTheme, defaultTheme, dimTheme} from './themes'
7788export type ColorScheme = 'light' | 'dark'
+1-1
src/lib/api/upload-blob.ts
···11-import {copyAsync} from 'expo-file-system'
11+import {copyAsync} from 'expo-file-system/legacy'
22import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api'
3344import {safeDeleteAsync} from '#/lib/media/manip'
-15
src/lib/hooks/useAnimatedScrollHandler_FIXED.ts
···11-// Be warned. This Hook is very buggy unless used in a very constrained way.
22-// To use it safely:
33-//
44-// - DO NOT pass its return value as a prop to any user-defined component.
55-// - DO NOT pass its return value to more than a single component.
66-//
77-// In other words, the only safe way to use it is next to the leaf Reanimated View.
88-//
99-// Relevant bug reports:
1010-// - https://github.com/software-mansion/react-native-reanimated/issues/5345
1111-// - https://github.com/software-mansion/react-native-reanimated/issues/5360
1212-// - https://github.com/software-mansion/react-native-reanimated/issues/5364
1313-//
1414-// It's great when it works though.
1515-export {useAnimatedScrollHandler} from 'react-native-reanimated'
···1010 makeDirectoryAsync,
1111 StorageAccessFramework,
1212 writeAsStringAsync,
1313-} from 'expo-file-system'
1313+} from 'expo-file-system/legacy'
1414import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
1515import * as MediaLibrary from 'expo-media-library'
1616import * as Sharing from 'expo-sharing'
···22 documentDirectory,
33 getInfoAsync,
44 readDirectoryAsync,
55-} from 'expo-file-system'
55+} from 'expo-file-system/legacy'
66import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool'
7788import {compressIfNeeded} from './manip'
+1-1
src/lib/media/video/upload.ts
···11-import {createUploadTask, FileSystemUploadType} from 'expo-file-system'
11+import {createUploadTask, FileSystemUploadType} from 'expo-file-system/legacy'
22import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api'
33import {type I18n} from '@lingui/core'
44import {msg} from '@lingui/macro'
+1-1
src/lib/media/video/util.ts
···4040}
41414242export function extToMime(ext: string) {
4343- switch (ext) {
4343+ switch (ext.toLowerCase()) {
4444 case 'mp4':
4545 return 'video/mp4'
4646 case 'webm':
···133133134134 return (
135135 <View style={[a.align_start]} testID="onboardingInterests">
136136- <Text style={[a.font_heavy, a.text_3xl]}>
136136+ <Text style={[a.font_bold, a.text_3xl]}>
137137 <Trans comment="Accounts suggested to the user for them to follow">
138138 Suggested for you
139139 </Trans>
···11import {Platform} from 'react-native'
22import {setStringAsync} from 'expo-clipboard'
33-import * as FileSystem from 'expo-file-system'
33+import * as FileSystem from 'expo-file-system/legacy'
44import {Image} from 'expo-image'
55import {msg, Trans} from '@lingui/macro'
66import {useLingui} from '@lingui/react'
···88import {atoms as a, useTheme} from '#/alf'
99import * as TextField from '#/components/forms/TextField'
1010import {StarterPack} from '#/components/icons/StarterPack'
1111-import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition'
1111+import {ScreenTransition} from '#/components/ScreenTransition'
1212import {Text} from '#/components/Typography'
13131414export function StepDetails() {
···2323 })
24242525 return (
2626- <ScreenTransition direction={state.transitionDirection}>
2626+ <ScreenTransition direction={state.transitionDirection} enabledWeb>
2727 <View style={[a.px_xl, a.gap_xl, a.mt_4xl]}>
2828 <View style={[a.gap_md, a.align_center, a.px_md, a.mb_md]}>
2929 <StarterPack width={90} gradient="sky" />
+5-2
src/screens/StarterPack/Wizard/StepFeeds.tsx
···1717import {SearchInput} from '#/components/forms/SearchInput'
1818import {useThrottledValue} from '#/components/hooks/useThrottledValue'
1919import {Loader} from '#/components/Loader'
2020-import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition'
2020+import {ScreenTransition} from '#/components/ScreenTransition'
2121import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardListCard'
2222import {Text} from '#/components/Typography'
2323···7979 }
80808181 return (
8282- <ScreenTransition style={[a.flex_1]} direction={state.transitionDirection}>
8282+ <ScreenTransition
8383+ style={[a.flex_1]}
8484+ direction={state.transitionDirection}
8585+ enabledWeb>
8386 <View style={[a.border_b, t.atoms.border_contrast_medium]}>
8487 <View style={[a.py_sm, a.px_md, {height: 60}]}>
8588 <SearchInput
+5-2
src/screens/StarterPack/Wizard/StepProfiles.tsx
···1313import {atoms as a, useTheme} from '#/alf'
1414import {SearchInput} from '#/components/forms/SearchInput'
1515import {Loader} from '#/components/Loader'
1616-import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition'
1616+import {ScreenTransition} from '#/components/ScreenTransition'
1717import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard'
1818import {Text} from '#/components/Typography'
1919import type * as bsky from '#/types/bsky'
···6464 }
65656666 return (
6767- <ScreenTransition style={[a.flex_1]} direction={state.transitionDirection}>
6767+ <ScreenTransition
6868+ style={[a.flex_1]}
6969+ direction={state.transitionDirection}
7070+ enabledWeb>
6871 <View style={[a.border_b, t.atoms.border_contrast_medium]}>
6972 <View style={[a.py_sm, a.px_md, {height: 60}]}>
7073 <SearchInput
+1-1
src/state/gallery.ts
···33 deleteAsync,
44 makeDirectoryAsync,
55 moveAsync,
66-} from 'expo-file-system'
66+} from 'expo-file-system/legacy'
77import {
88 type Action,
99 type ActionCrop,
···1919 useAnimatedProps,
2020 useAnimatedReaction,
2121 useAnimatedRef,
2222+ useAnimatedScrollHandler,
2223 useAnimatedStyle,
2324 useSharedValue,
2425} from 'react-native-reanimated'
2526import {useSafeAreaFrame} from 'react-native-safe-area-context'
2627import {Image} from 'expo-image'
27282828-import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
2929import {
3030 type Dimensions as ImageDimensions,
3131 type ImageSource,
+1-9
src/view/com/modals/Modal.tsx
···88import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
99import {FullWindowOverlay} from '#/components/FullWindowOverlay'
1010import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
1111-import * as CreateOrEditListModal from './CreateOrEditList'
1211import * as DeleteAccountModal from './DeleteAccount'
1313-import * as InviteCodesModal from './InviteCodes'
1412import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
1513import * as UserAddRemoveListsModal from './UserAddRemoveLists'
1614···51495250 let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
5351 let element
5454- if (activeModal?.name === 'create-or-edit-list') {
5555- snapPoints = CreateOrEditListModal.snapPoints
5656- element = <CreateOrEditListModal.Component {...activeModal} />
5757- } else if (activeModal?.name === 'user-add-remove-lists') {
5252+ if (activeModal?.name === 'user-add-remove-lists') {
5853 snapPoints = UserAddRemoveListsModal.snapPoints
5954 element = <UserAddRemoveListsModal.Component {...activeModal} />
6055 } else if (activeModal?.name === 'delete-account') {
6156 snapPoints = DeleteAccountModal.snapPoints
6257 element = <DeleteAccountModal.Component />
6363- } else if (activeModal?.name === 'invite-codes') {
6464- snapPoints = InviteCodesModal.snapPoints
6565- element = <InviteCodesModal.Component />
6658 } else if (activeModal?.name === 'content-languages-settings') {
6759 snapPoints = ContentLanguagesSettingsModal.snapPoints
6860 element = <ContentLanguagesSettingsModal.Component />
+1-7
src/view/com/modals/Modal.web.tsx
···77import {useModalControls, useModals} from '#/state/modals'
88import {useTheme} from '#/alf'
99import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
1010-import * as CreateOrEditListModal from './CreateOrEditList'
1110import * as DeleteAccountModal from './DeleteAccount'
1212-import * as InviteCodesModal from './InviteCodes'
1311import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
1412import * as UserAddRemoveLists from './UserAddRemoveLists'
1513···5048 }
51495250 let element
5353- if (modal.name === 'create-or-edit-list') {
5454- element = <CreateOrEditListModal.Component {...modal} />
5555- } else if (modal.name === 'user-add-remove-lists') {
5151+ if (modal.name === 'user-add-remove-lists') {
5652 element = <UserAddRemoveLists.Component {...modal} />
5753 } else if (modal.name === 'delete-account') {
5854 element = <DeleteAccountModal.Component />
5959- } else if (modal.name === 'invite-codes') {
6060- element = <InviteCodesModal.Component />
6155 } else if (modal.name === 'content-languages-settings') {
6256 element = <ContentLanguagesSettingsModal.Component />
6357 } else {
···33import {
44 type FlatListPropsWithLayout,
55 runOnJS,
66+ useAnimatedScrollHandler,
67 useSharedValue,
78} from 'react-native-reanimated'
89import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video'
9101010-import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
1111import {useDedupe} from '#/lib/hooks/useDedupe'
1212import {useScrollHandlers} from '#/lib/ScrollContext'
1313import {addStyle} from '#/lib/styles'