diff --git a/modules/expo-scroll-forwarder/expo-module.config.json b/modules/expo-scroll-forwarder/expo-module.config.json new file mode 100644 index 00000000..1fd49f79 --- /dev/null +++ b/modules/expo-scroll-forwarder/expo-module.config.json @@ -0,0 +1,6 @@ +{ + "platforms": ["ios"], + "ios": { + "modules": ["ExpoScrollForwarderModule"] + } +} diff --git a/modules/expo-scroll-forwarder/index.ts b/modules/expo-scroll-forwarder/index.ts new file mode 100644 index 00000000..a4ad4b85 --- /dev/null +++ b/modules/expo-scroll-forwarder/index.ts @@ -0,0 +1 @@ +export {ExpoScrollForwarderView} from './src/ExpoScrollForwarderView' diff --git a/modules/expo-scroll-forwarder/ios/ExpoScrollForwarder.podspec b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarder.podspec new file mode 100644 index 00000000..78ca9812 --- /dev/null +++ b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarder.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + s.name = 'ExpoScrollForwarder' + s.version = '1.0.0' + s.summary = 'Forward scroll gesture from UIView to UIScrollView' + s.description = 'Forward scroll gesture from UIView to UIScrollView' + s.author = 'bluesky-social' + s.homepage = 'https://github.com/bluesky-social/social-app' + s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderModule.swift b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderModule.swift new file mode 100644 index 00000000..c4ecc788 --- /dev/null +++ b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderModule.swift @@ -0,0 +1,13 @@ +import ExpoModulesCore + +public class ExpoScrollForwarderModule: Module { + public func definition() -> ModuleDefinition { + Name("ExpoScrollForwarder") + + View(ExpoScrollForwarderView.self) { + Prop("scrollViewTag") { (view: ExpoScrollForwarderView, prop: Int) in + view.scrollViewTag = prop + } + } + } +} diff --git a/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift new file mode 100644 index 00000000..9c0e2f87 --- /dev/null +++ b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift @@ -0,0 +1,215 @@ +import ExpoModulesCore + +// This view will be used as a native component. Make sure to inherit from `ExpoView` +// to apply the proper styling (e.g. border radius and shadows). +class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate { + var scrollViewTag: Int? { + didSet { + self.tryFindScrollView() + } + } + + private var rctScrollView: RCTScrollView? + private var rctRefreshCtrl: RCTRefreshControl? + private var cancelGestureRecognizers: [UIGestureRecognizer]? + private var animTimer: Timer? + private var initialOffset: CGFloat = 0.0 + private var didImpact: Bool = false + + required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + + let pg = UIPanGestureRecognizer(target: self, action: #selector(callOnPan(_:))) + pg.delegate = self + self.addGestureRecognizer(pg) + + let tg = UITapGestureRecognizer(target: self, action: #selector(callOnPress(_:))) + tg.isEnabled = false + tg.delegate = self + + let lpg = UILongPressGestureRecognizer(target: self, action: #selector(callOnPress(_:))) + lpg.minimumPressDuration = 0.01 + lpg.isEnabled = false + lpg.delegate = self + + self.cancelGestureRecognizers = [lpg, tg] + } + + + // We don't want to recognize the scroll pan gesture and the swipe back gesture together + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer is UIPanGestureRecognizer, otherGestureRecognizer is UIPanGestureRecognizer { + return false + } + + return true + } + + // We only want the "scroll" gesture to happen whenever the pan is vertical, otherwise it will + // interfere with the native swipe back gesture. + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + guard let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { + return true + } + + let velocity = gestureRecognizer.velocity(in: self) + return abs(velocity.y) > abs(velocity.x) + } + + // This will be used to cancel the scroll animation whenever we tap inside of the header. We don't need another + // recognizer for this one. + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.stopTimer() + } + + // This will be used to cancel the animation whenever we press inside of the scroll view. We don't want to change + // the scroll view gesture's delegate, so we add an additional recognizer to detect this. + @IBAction func callOnPress(_ sender: UITapGestureRecognizer) -> Void { + self.stopTimer() + } + + @IBAction func callOnPan(_ sender: UIPanGestureRecognizer) -> Void { + guard let rctsv = self.rctScrollView, let sv = rctsv.scrollView else { + return + } + + let translation = sender.translation(in: self).y + + if sender.state == .began { + if sv.contentOffset.y < 0 { + sv.contentOffset.y = 0 + } + + self.initialOffset = sv.contentOffset.y + } + + if sender.state == .changed { + sv.contentOffset.y = self.dampenOffset(-translation + self.initialOffset) + + if sv.contentOffset.y <= -130, !didImpact { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + + self.didImpact = true + } + } + + if sender.state == .ended { + let velocity = sender.velocity(in: self).y + self.didImpact = false + + if sv.contentOffset.y <= -130 { + self.rctRefreshCtrl?.forwarderBeginRefreshing() + return + } + + // A check for a velocity under 250 prevents animations from occurring when they wouldn't in a normal + // scroll view + if abs(velocity) < 250, sv.contentOffset.y >= 0 { + return + } + + self.startDecayAnimation(translation, velocity) + } + } + + func startDecayAnimation(_ translation: CGFloat, _ velocity: CGFloat) { + guard let sv = self.rctScrollView?.scrollView else { + return + } + + var velocity = velocity + + self.enableCancelGestureRecognizers() + + if velocity > 0 { + velocity = min(velocity, 5000) + } else { + velocity = max(velocity, -5000) + } + + var animTranslation = -translation + self.animTimer = Timer.scheduledTimer(withTimeInterval: 1.0 / 120, repeats: true) { timer in + velocity *= 0.9875 + animTranslation = (-velocity / 120) + animTranslation + + let nextOffset = self.dampenOffset(animTranslation + self.initialOffset) + + if nextOffset <= 0 { + if self.initialOffset <= 1 { + self.scrollToOffset(0) + } else { + sv.contentOffset.y = 0 + } + + self.stopTimer() + return + } else { + sv.contentOffset.y = nextOffset + } + + if abs(velocity) < 5 { + self.stopTimer() + } + } + } + + func dampenOffset(_ offset: CGFloat) -> CGFloat { + if offset < 0 { + return offset - (offset * 0.55) + } + + return offset + } + + func tryFindScrollView() { + guard let scrollViewTag = scrollViewTag else { + return + } + + // Before we switch to a different scrollview, we always want to remove the cancel gesture recognizer. + // Otherwise we might end up with duplicates when we switch back to that scrollview. + self.removeCancelGestureRecognizers() + + self.rctScrollView = self.appContext? + .findView(withTag: scrollViewTag, ofType: RCTScrollView.self) + self.rctRefreshCtrl = self.rctScrollView?.scrollView.refreshControl as? RCTRefreshControl + + self.addCancelGestureRecognizers() + } + + func addCancelGestureRecognizers() { + self.cancelGestureRecognizers?.forEach { r in + self.rctScrollView?.scrollView?.addGestureRecognizer(r) + } + } + + func removeCancelGestureRecognizers() { + self.cancelGestureRecognizers?.forEach { r in + self.rctScrollView?.scrollView?.removeGestureRecognizer(r) + } + } + + + func enableCancelGestureRecognizers() { + self.cancelGestureRecognizers?.forEach { r in + r.isEnabled = true + } + } + + func disableCancelGestureRecognizers() { + self.cancelGestureRecognizers?.forEach { r in + r.isEnabled = false + } + } + + func scrollToOffset(_ offset: Int, animated: Bool = true) -> Void { + self.rctScrollView?.scroll(toOffset: CGPoint(x: 0, y: offset), animated: animated) + } + + func stopTimer() -> Void { + self.disableCancelGestureRecognizers() + self.animTimer?.invalidate() + self.animTimer = nil + } +} diff --git a/modules/expo-scroll-forwarder/src/ExpoScrollForwarder.types.ts b/modules/expo-scroll-forwarder/src/ExpoScrollForwarder.types.ts new file mode 100644 index 00000000..26b9e755 --- /dev/null +++ b/modules/expo-scroll-forwarder/src/ExpoScrollForwarder.types.ts @@ -0,0 +1,6 @@ +import React from 'react' + +export interface ExpoScrollForwarderViewProps { + scrollViewTag: number | null + children: React.ReactNode +} diff --git a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx new file mode 100644 index 00000000..a91aebd4 --- /dev/null +++ b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx @@ -0,0 +1,13 @@ +import {requireNativeViewManager} from 'expo-modules-core' +import * as React from 'react' +import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types' + +const NativeView: React.ComponentType = + requireNativeViewManager('ExpoScrollForwarder') + +export function ExpoScrollForwarderView({ + children, + ...rest +}: ExpoScrollForwarderViewProps) { + return {children} +} diff --git a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.tsx b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.tsx new file mode 100644 index 00000000..93e69333 --- /dev/null +++ b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.tsx @@ -0,0 +1,7 @@ +import React from 'react' +import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types' +export function ExpoScrollForwarderView({ + children, +}: React.PropsWithChildren) { + return children +} diff --git a/patches/react-native+0.73.2.patch b/patches/react-native+0.73.2.patch index 8db23da0..db8b7da2 100644 --- a/patches/react-native+0.73.2.patch +++ b/patches/react-native+0.73.2.patch @@ -1,11 +1,22 @@ +diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h +index e9b330f..1ecdf0a 100644 +--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h ++++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h +@@ -16,4 +16,6 @@ + @property (nonatomic, copy) RCTDirectEventBlock onRefresh; + @property (nonatomic, weak) UIScrollView *scrollView; + ++- (void)forwarderBeginRefreshing; ++ + @end diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m -index b09e653..d290dab 100644 +index b09e653..4c32b31 100644 --- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m +++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m -@@ -198,6 +198,14 @@ - (void)refreshControlValueChanged +@@ -198,9 +198,53 @@ - (void)refreshControlValueChanged [self setCurrentRefreshingState:super.refreshing]; _refreshingProgrammatically = NO; - + + if (@available(iOS 17.4, *)) { + if (_currentRefreshingState) { + UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; @@ -16,4 +27,43 @@ index b09e653..d290dab 100644 + if (_onRefresh) { _onRefresh(nil); - } \ No newline at end of file + } + } + ++/* ++ This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native ++ libraries to perform a refresh of a scrollview and access the refresh control's onRefresh ++ function. ++ */ ++- (void)forwarderBeginRefreshing ++{ ++ _refreshingProgrammatically = NO; ++ ++ [self sizeToFit]; ++ ++ if (!self.scrollView) { ++ return; ++ } ++ ++ UIScrollView *scrollView = (UIScrollView *)self.scrollView; ++ ++ [UIView animateWithDuration:0.3 ++ delay:0 ++ options:UIViewAnimationOptionBeginFromCurrentState ++ animations:^(void) { ++ // Whenever we call this method, the scrollview will always be at a position of ++ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl ++ [scrollView setContentOffset:CGPointMake(0, -65)]; ++ } ++ completion:^(__unused BOOL finished) { ++ [super beginRefreshing]; ++ [self setCurrentRefreshingState:super.refreshing]; ++ ++ if (self->_onRefresh) { ++ self->_onRefresh(nil); ++ } ++ } ++ ]; ++} ++ + @end diff --git a/patches/react-native+0.73.2.patch.md b/patches/react-native+0.73.2.patch.md index 7f70baf2..9c93aee5 100644 --- a/patches/react-native+0.73.2.patch.md +++ b/patches/react-native+0.73.2.patch.md @@ -1,5 +1,13 @@ -# RefreshControl Patch +# ***This second part of this patch is load bearing, do not remove.*** + +## RefreshControl Patch - iOS 17.4 Haptic Regression Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh when using iOS 17.4 or higher. Since 17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update -in the RN repo: https://github.com/facebook/react-native/issues/43388 \ No newline at end of file +in the RN repo: https://github.com/facebook/react-native/issues/43388 + +## RefreshControl Path - ScrollForwarder + +Patching `RCTRefreshControl.m` and `RCTRefreshControl.h` to add a new `forwarderBeginRefreshing` method to the class. +This method is used by `ExpoScrollForwarder` to initiate a refresh of the underlying `UIScrollView` from inside that +module. diff --git a/src/screens/Profile/Sections/Feed.tsx b/src/screens/Profile/Sections/Feed.tsx index 0a5e2208..bc106fcf 100644 --- a/src/screens/Profile/Sections/Feed.tsx +++ b/src/screens/Profile/Sections/Feed.tsx @@ -1,18 +1,19 @@ import React from 'react' -import {View} from 'react-native' +import {findNodeHandle, View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {ListRef} from 'view/com/util/List' -import {Feed} from 'view/com/posts/Feed' -import {EmptyState} from 'view/com/util/EmptyState' +import {useQueryClient} from '@tanstack/react-query' + +import {isNative} from '#/platform/detection' import {FeedDescriptor} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' -import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' -import {useQueryClient} from '@tanstack/react-query' import {truncateAndInvalidate} from '#/state/queries/util' -import {Text} from '#/view/com/util/text/Text' import {usePalette} from 'lib/hooks/usePalette' -import {isNative} from '#/platform/detection' +import {Text} from '#/view/com/util/text/Text' +import {Feed} from 'view/com/posts/Feed' +import {EmptyState} from 'view/com/util/EmptyState' +import {ListRef} from 'view/com/util/List' +import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' import {SectionRef} from './types' interface FeedSectionProps { @@ -21,12 +22,20 @@ interface FeedSectionProps { isFocused: boolean scrollElRef: ListRef ignoreFilterFor?: string + setScrollViewTag: (tag: number | null) => void } export const ProfileFeedSection = React.forwardRef< SectionRef, FeedSectionProps >(function FeedSectionImpl( - {feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor}, + { + feed, + headerHeight, + isFocused, + scrollElRef, + ignoreFilterFor, + setScrollViewTag, + }, ref, ) { const {_} = useLingui() @@ -50,6 +59,13 @@ export const ProfileFeedSection = React.forwardRef< return }, [_]) + React.useEffect(() => { + if (isFocused && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [isFocused, scrollElRef, setScrollViewTag]) + return ( void } export const ProfileLabelsSection = React.forwardRef< SectionRef, @@ -44,6 +46,8 @@ export const ProfileLabelsSection = React.forwardRef< moderationOpts, scrollElRef, headerHeight, + isFocused, + setScrollViewTag, }, ref, ) { @@ -63,6 +67,13 @@ export const ProfileLabelsSection = React.forwardRef< scrollToTop: onScrollToTop, })) + React.useEffect(() => { + if (isFocused && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [isFocused, scrollElRef, setScrollViewTag]) + return ( {isLabelerLoading ? ( diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx index e9cf9e53..a006b11c 100644 --- a/src/view/com/feeds/ProfileFeedgens.tsx +++ b/src/view/com/feeds/ProfileFeedgens.tsx @@ -1,22 +1,29 @@ import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import { + findNodeHandle, + StyleProp, + StyleSheet, + View, + ViewStyle, +} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' -import {List, ListRef} from '../util/List' -import {FeedSourceCardLoaded} from './FeedSourceCard' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' -import {Text} from '../util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens' -import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' + import {cleanError} from '#/lib/strings/errors' import {useTheme} from '#/lib/ThemeContext' -import {usePreferencesQuery} from '#/state/queries/preferences' -import {hydrateFeedGenerator} from '#/state/queries/feed' -import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {logger} from '#/logger' import {isNative} from '#/platform/detection' -import {useLingui} from '@lingui/react' +import {hydrateFeedGenerator} from '#/state/queries/feed' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' +import {usePalette} from 'lib/hooks/usePalette' +import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {List, ListRef} from '../util/List' +import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' +import {Text} from '../util/text/Text' +import {FeedSourceCardLoaded} from './FeedSourceCard' const LOADING = {_reactKey: '__loading__'} const EMPTY = {_reactKey: '__empty__'} @@ -34,13 +41,14 @@ interface ProfileFeedgensProps { enabled?: boolean style?: StyleProp testID?: string + setScrollViewTag: (tag: number | null) => void } export const ProfileFeedgens = React.forwardRef< SectionRef, ProfileFeedgensProps >(function ProfileFeedgensImpl( - {did, scrollElRef, headerOffset, enabled, style, testID}, + {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, ref, ) { const pal = usePalette('default') @@ -169,6 +177,13 @@ export const ProfileFeedgens = React.forwardRef< [error, refetch, onPressRetryLoadMore, pal, preferences, _], ) + React.useEffect(() => { + if (enabled && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [enabled, scrollElRef, setScrollViewTag]) + return ( testID?: string + setScrollViewTag: (tag: number | null) => void } export const ProfileLists = React.forwardRef( function ProfileListsImpl( - {did, scrollElRef, headerOffset, enabled, style, testID}, + {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, ref, ) { const pal = usePalette('default') @@ -171,6 +179,13 @@ export const ProfileLists = React.forwardRef( [error, refetch, onPressRetryLoadMore, pal, _], ) + React.useEffect(() => { + if (enabled && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [enabled, scrollElRef, setScrollViewTag]) + return ( (null) + const postsSectionRef = React.useRef(null) const repliesSectionRef = React.useRef(null) const mediaSectionRef = React.useRef(null) @@ -297,12 +303,9 @@ function ProfileScreenLoaded({ openComposer({mention}) }, [openComposer, currentAccount, track, profile]) - const onPageSelected = React.useCallback( - (i: number) => { - setCurrentPage(i) - }, - [setCurrentPage], - ) + const onPageSelected = React.useCallback((i: number) => { + setCurrentPage(i) + }, []) const onCurrentPageSelected = React.useCallback( (index: number) => { @@ -315,21 +318,38 @@ function ProfileScreenLoaded({ // = const renderHeader = React.useCallback(() => { - return ( - - ) + if (shouldUseScrollableHeader) { + return ( + + + + ) + } else { + return ( + + ) + } }, [ + shouldUseScrollableHeader, + scrollViewTag, profile, labelerInfo, - descriptionRT, hasDescription, + descriptionRT, moderationOpts, hideBackButton, showPlaceholder, @@ -349,7 +369,7 @@ function ProfileScreenLoaded({ onCurrentPageSelected={onCurrentPageSelected} renderHeader={renderHeader}> {showFiltersTab - ? ({headerHeight, scrollElRef}) => ( + ? ({headerHeight, isFocused, scrollElRef}) => ( ) : null} @@ -369,6 +391,7 @@ function ProfileScreenLoaded({ scrollElRef={scrollElRef as ListRef} headerOffset={headerHeight} enabled={isFocused} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -381,6 +404,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -393,6 +417,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -405,6 +430,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -417,6 +443,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -428,6 +455,7 @@ function ProfileScreenLoaded({ scrollElRef={scrollElRef as ListRef} headerOffset={headerHeight} enabled={isFocused} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -439,6 +467,7 @@ function ProfileScreenLoaded({ scrollElRef={scrollElRef as ListRef} headerOffset={headerHeight} enabled={isFocused} + setScrollViewTag={setScrollViewTag} /> ) : null}