From 349096acd065a2c79974786e9d141f298feb906b Mon Sep 17 00:00:00 2001 From: Hailey Date: Mon, 12 May 2025 15:21:07 -0700 Subject: [PATCH] fix refreshing --- .../ios/ScrollForwarderView.mm | 54 ++++++++++++++----- .../src/ScrollForwarderViewNativeComponent.ts | 6 +-- src/screens/Profile/Sections/Feed.tsx | 8 ++- src/view/com/posts/PostFeed.tsx | 23 ++++++-- src/view/screens/Profile.tsx | 19 +++++-- 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/modules/react-native-scroll-forwarder/ios/ScrollForwarderView.mm b/modules/react-native-scroll-forwarder/ios/ScrollForwarderView.mm index 33a76b0c1e..840cd04ff3 100644 --- a/modules/react-native-scroll-forwarder/ios/ScrollForwarderView.mm +++ b/modules/react-native-scroll-forwarder/ios/ScrollForwarderView.mm @@ -34,7 +34,6 @@ static const CGFloat kMinimumVelocity = 5.0; CGFloat _currentVelocity; CGFloat _accumulatedTranslation; - bool _refreshing; bool _didImpact; } @@ -102,6 +101,12 @@ static const CGFloat kMinimumVelocity = 5.0; [self tryFindScrollView]; } + if (oldViewProps.refreshing != newViewProps.refreshing) { + if (!newViewProps.refreshing) { + [self endRefreshing]; + } + } + [super updateProps:props oldProps:oldProps]; } @@ -211,11 +216,10 @@ static const CGFloat kMinimumVelocity = 5.0; if (gesture.state == UIGestureRecognizerStateEnded) { CGPoint velocity = [gesture velocityInView:self]; - // TODO: enable this again -// if (sv.contentOffset.y <= -kPullThreshold) { -// [self refresh]; -// return; -// } + if (sv.contentOffset.y <= -kPullThreshold) { + [self refresh]; + return; + } if (sv.contentOffset.y < 0) { CGPoint newOffset = CGPointMake(sv.contentOffset.x, 0); @@ -354,29 +358,51 @@ static const CGFloat kMinimumVelocity = 5.0; return nil; } +- (UIRefreshControl *)refreshContorl +{ + if (!_svcv) return nil; + return _svcv.scrollView.refreshControl; +} + - (void)refresh { - if (!_svcv) return; + __weak ScrollForwarderView *weakSelf = self; - UIScrollView *sv = _svcv.scrollView; - UIRefreshControl *rc = sv.refreshControl; + [_svcv.scrollView.refreshControl beginRefreshing]; - if (!rc) return; - [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void) { + if (!weakSelf) return; + + __strong ScrollForwarderView *self = weakSelf; + // 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 - [sv setContentOffset:CGPointMake(0, -65)]; + // -130 or less. Scrolling back to -80 simulates the default behavior of RCTRefreshControl + [self->_svcv.scrollView setContentOffset:CGPointMake(0, -65)]; } completion:^(__unused BOOL finished) { - [rc beginRefreshing]; + __strong ScrollForwarderView *self = weakSelf; + + if (self->_eventEmitter != nullptr) { + std::dynamic_pointer_cast(self->_eventEmitter) + ->onRefresh(facebook::react::ScrollForwarderViewEventEmitter::OnRefresh{}); + } } ]; } +- (void)endRefreshing +{ + UIRefreshControl *rc = [self refreshContorl]; + + CGPoint newOffset = CGPointMake(_svcv.scrollView.contentOffset.x, 0.0); + [self scrollToOffset:newOffset animated:true]; + + [rc endRefreshing]; +} + Class ScrollForwarderViewCls(void) { return ScrollForwarderView.class; diff --git a/modules/react-native-scroll-forwarder/src/ScrollForwarderViewNativeComponent.ts b/modules/react-native-scroll-forwarder/src/ScrollForwarderViewNativeComponent.ts index 333969fd3e..9c6348de32 100644 --- a/modules/react-native-scroll-forwarder/src/ScrollForwarderViewNativeComponent.ts +++ b/modules/react-native-scroll-forwarder/src/ScrollForwarderViewNativeComponent.ts @@ -1,4 +1,4 @@ -import {type ViewProps} from 'react-native' +import {type ViewProps} from 'react-native' import { type BubblingEventHandler, type Int32, @@ -8,9 +8,9 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNati type OnRefreshEvent = {} export interface NativeProps extends ViewProps { - scrollViewTag?: Int32 - onRefresh?: BubblingEventHandler + scrollViewTag: Int32 | null refreshing?: boolean + onRefresh?: BubblingEventHandler } export default codegenNativeComponent('ScrollForwarderView') diff --git a/src/screens/Profile/Sections/Feed.tsx b/src/screens/Profile/Sections/Feed.tsx index e0c3e221f4..6e388ecc9d 100644 --- a/src/screens/Profile/Sections/Feed.tsx +++ b/src/screens/Profile/Sections/Feed.tsx @@ -9,7 +9,7 @@ import {isIOS, isNative} from '#/platform/detection' import {type FeedDescriptor} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {truncateAndInvalidate} from '#/state/queries/util' -import {PostFeed} from '#/view/com/posts/PostFeed' +import {PostFeed, PostFeedRef} from '#/view/com/posts/PostFeed' import {EmptyState} from '#/view/com/util/EmptyState' import {type ListRef} from '#/view/com/util/List' import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' @@ -24,6 +24,8 @@ interface FeedSectionProps { scrollElRef: ListRef ignoreFilterFor?: string setScrollViewTag: (tag: number | null) => void + postFeedRef?: React.RefObject + onRefreshEnd?: () => void } export const ProfileFeedSection = React.forwardRef< SectionRef, @@ -36,6 +38,8 @@ export const ProfileFeedSection = React.forwardRef< scrollElRef, ignoreFilterFor, setScrollViewTag, + postFeedRef, + onRefreshEnd, }, ref, ) { @@ -91,6 +95,8 @@ export const ProfileFeedSection = React.forwardRef< shouldUseAdjustedNumToRender ? adjustedInitialNumToRender : undefined } isVideoFeed={isVideoFeed} + ref={postFeedRef} + onRefreshEnd={onRefreshEnd} /> {(isScrolledDown || hasNew) && ( Promise +} + // DISABLED need to check if this is causing random feed refreshes -prf // const REFRESH_AFTER = STALE.HOURS.ONE const CHECK_LATEST_AFTER = STALE.SECONDS.THIRTY @@ -176,6 +180,7 @@ let PostFeed = ({ savedFeedConfig, initialNumToRender: initialNumToRenderOverride, isVideoFeed = false, + ref, }: { feed: FeedDescriptor feedParams?: FeedParams @@ -198,6 +203,7 @@ let PostFeed = ({ savedFeedConfig?: AppBskyActorDefs.SavedFeed initialNumToRender?: number isVideoFeed?: boolean + ref?: React.ForwardedRef }): React.ReactNode => { const {_} = useLingui() const queryClient = useQueryClient() @@ -583,22 +589,31 @@ let PostFeed = ({ // events // = + // - const onRefresh = React.useCallback(async () => { + const refreshFeed = async () => { logEvent('feed:refresh', { feedType: feedType, feedUrl: feed, reason: 'pull-to-refresh', }) - setIsPTRing(true) try { await refetch() onHasNew?.(false) } catch (err) { logger.error('Failed to refresh posts feed', {message: err}) } + } + + const onRefresh = async () => { + setIsPTRing(true) + await refreshFeed() setIsPTRing(false) - }, [refetch, setIsPTRing, onHasNew, feed, feedType]) + } + + useImperativeHandle(ref, () => ({ + refreshFeed, + })) const onEndReached = React.useCallback(async () => { if (isFetching || !hasNextPage || isError) return diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index da27fe47fc..c76625a3b7 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo} from 'react' +import React, {useCallback, useMemo, useRef, useState} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' import { @@ -46,8 +46,8 @@ import * as Layout from '#/components/Layout' import {ScreenHider} from '#/components/moderation/ScreenHider' import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks' import {navigate} from '#/Navigation' -import {ExpoScrollForwarderView} from '../../../modules/expo-scroll-forwarder' import {ScrollForwarderView} from 'modules/react-native-scroll-forwarder/src' +import {PostFeedRef} from '../com/posts/PostFeed' interface SectionRef { scrollToTop: () => void @@ -179,6 +179,7 @@ function ProfileScreenLoaded({ enabled: !!profile.associated?.labeler, }) const [currentPage, setCurrentPage] = React.useState(0) + const [isRefreshing, setIsRefreshing] = useState(false) const {_} = useLingui() const [scrollViewTag, setScrollViewTag] = React.useState(null) @@ -335,6 +336,14 @@ function ProfileScreenLoaded({ scrollSectionToTop(index) } + const postFeedRef = useRef() + + const onRefresh = async () => { + setIsRefreshing(true) + await postFeedRef.current?.refreshFeed() + setIsRefreshing(false) + } + // rendering // = @@ -344,7 +353,10 @@ function ProfileScreenLoaded({ setMinimumHeight: (height: number) => void }) => { return ( - + ) : null}