fix refreshing

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