From 4e517720030184ef8c003ffad9b3ca5100619d2e Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 15:20:38 -0700 Subject: Make bio area scrollable on iOS (#2931) * fix dampen logic prevent ghost presses handle refreshes, animations, and clamps handle most cases for cancelling the scroll animation handle animations save point simplify remove unnecessary context readme apply offset on pan find the RCTScrollView send props, add native gesture recognizer get the react tag wrap the profile in context create module * fix swiping to go back * remove debug * use `findNodeHandle` * create an expo module view * port most of it to expo modules * finish most of expomodules impl * experiments * remove refresh ability for now * remove rn module * changes * cleanup a few issues allow swipe back gesture clean up types always run animation if the final offset is < 0 separate logic update patch readme get the `RCTRefreshControl` working nicely * gate new header * organize --- src/screens/Profile/Sections/Feed.tsx | 34 +++++++++++----- src/screens/Profile/Sections/Labels.tsx | 13 ++++++- src/view/com/feeds/ProfileFeedgens.tsx | 43 +++++++++++++------- src/view/com/lists/ProfileLists.tsx | 43 +++++++++++++------- src/view/screens/Profile.tsx | 69 +++++++++++++++++++++++---------- 5 files changed, 144 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/screens/Profile/Sections/Feed.tsx b/src/screens/Profile/Sections/Feed.tsx index 0a5e2208d..bc106fcfb 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 e9cf9e535..a006b11c0 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 {logger} from '#/logger' +import {isNative} from '#/platform/detection' 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 {isNative} from '#/platform/detection' -import {useLingui} from '@lingui/react' +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} -- cgit 1.4.1