diff options
author | Kadi Kraman <hellokadi@gmail.com> | 2023-06-01 18:00:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-01 11:00:00 -0500 |
commit | d4e7355cca05483bfba8c30dbb9b4cdd2be14e51 (patch) | |
tree | eddb6e696458d7663c3271300f0781d33b4e8012 /src | |
parent | 792d7e1a55ff82a09e198ea4da7e4e73cf08f574 (diff) | |
download | voidsky-d4e7355cca05483bfba8c30dbb9b4cdd2be14e51.tar.zst |
fix: support scroll to top on profile screen (#725)
* Support scroll to top on profile screen * Refactor types * Remove async * Improve types
Diffstat (limited to 'src')
-rw-r--r-- | src/view/com/util/ViewSelector.tsx | 200 | ||||
-rw-r--r-- | src/view/screens/Profile.tsx | 12 |
2 files changed, 120 insertions, 92 deletions
diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx index 5b671d06c..705178a8a 100644 --- a/src/view/com/util/ViewSelector.tsx +++ b/src/view/com/util/ViewSelector.tsx @@ -13,103 +13,123 @@ const HEADER_ITEM = {_reactKey: '__header__'} const SELECTOR_ITEM = {_reactKey: '__selector__'} const STICKY_HEADER_INDICES = [1] -export function ViewSelector({ - sections, - items, - refreshing, - renderHeader, - renderItem, - ListFooterComponent, - onSelectView, - onScroll, - onRefresh, - onEndReached, -}: { - sections: string[] - items: any[] - refreshing?: boolean - swipeEnabled?: boolean - renderHeader?: () => JSX.Element - renderItem: (item: any) => JSX.Element - ListFooterComponent?: - | React.ComponentType<any> - | React.ReactElement - | null - | undefined - onSelectView?: (viewIndex: number) => void - onScroll?: OnScrollCb - onRefresh?: () => void - onEndReached?: (info: {distanceFromEnd: number}) => void -}) { - const pal = usePalette('default') - const [selectedIndex, setSelectedIndex] = useState<number>(0) +export type ViewSelectorHandle = { + scrollToTop: () => void +} - // events - // = +export const ViewSelector = React.forwardRef< + ViewSelectorHandle, + { + sections: string[] + items: any[] + refreshing?: boolean + swipeEnabled?: boolean + renderHeader?: () => JSX.Element + renderItem: (item: any) => JSX.Element + ListFooterComponent?: + | React.ComponentType<any> + | React.ReactElement + | null + | undefined + onSelectView?: (viewIndex: number) => void + onScroll?: OnScrollCb + onRefresh?: () => void + onEndReached?: (info: {distanceFromEnd: number}) => void + } +>( + ( + { + sections, + items, + refreshing, + renderHeader, + renderItem, + ListFooterComponent, + onSelectView, + onScroll, + onRefresh, + onEndReached, + }, + ref, + ) => { + const pal = usePalette('default') + const [selectedIndex, setSelectedIndex] = useState<number>(0) + const flatListRef = React.useRef<FlatList>(null) - const keyExtractor = React.useCallback(item => item._reactKey, []) + // events + // = - const onPressSelection = React.useCallback( - (index: number) => setSelectedIndex(clamp(index, 0, sections.length)), - [setSelectedIndex, sections], - ) - useEffect(() => { - onSelectView?.(selectedIndex) - }, [selectedIndex, onSelectView]) + const keyExtractor = React.useCallback(item => item._reactKey, []) + + const onPressSelection = React.useCallback( + (index: number) => setSelectedIndex(clamp(index, 0, sections.length)), + [setSelectedIndex, sections], + ) + useEffect(() => { + onSelectView?.(selectedIndex) + }, [selectedIndex, onSelectView]) - // rendering - // = + React.useImperativeHandle(ref, () => ({ + scrollToTop: () => { + flatListRef.current?.scrollToOffset({offset: 0}) + }, + })) - const renderItemInternal = React.useCallback( - ({item}: {item: any}) => { - if (item === HEADER_ITEM) { - if (renderHeader) { - return renderHeader() + // rendering + // = + + const renderItemInternal = React.useCallback( + ({item}: {item: any}) => { + if (item === HEADER_ITEM) { + if (renderHeader) { + return renderHeader() + } + return <View /> + } else if (item === SELECTOR_ITEM) { + return ( + <Selector + items={sections} + selectedIndex={selectedIndex} + onSelect={onPressSelection} + /> + ) + } else { + return renderItem(item) } - return <View /> - } else if (item === SELECTOR_ITEM) { - return ( - <Selector - items={sections} - selectedIndex={selectedIndex} - onSelect={onPressSelection} - /> - ) - } else { - return renderItem(item) - } - }, - [sections, selectedIndex, onPressSelection, renderHeader, renderItem], - ) + }, + [sections, selectedIndex, onPressSelection, renderHeader, renderItem], + ) - const data = React.useMemo( - () => [HEADER_ITEM, SELECTOR_ITEM, ...items], - [items], - ) - return ( - <FlatList - data={data} - keyExtractor={keyExtractor} - renderItem={renderItemInternal} - ListFooterComponent={ListFooterComponent} - // NOTE sticky header disabled on android due to major performance issues -prf - stickyHeaderIndices={isAndroid ? undefined : STICKY_HEADER_INDICES} - onScroll={onScroll} - onEndReached={onEndReached} - refreshControl={ - <RefreshControl - refreshing={refreshing!} - onRefresh={onRefresh} - tintColor={pal.colors.text} - /> - } - onEndReachedThreshold={0.6} - contentContainerStyle={s.contentContainer} - removeClippedSubviews={true} - scrollIndicatorInsets={{right: 1}} // fixes a bug where the scroll indicator is on the middle of the screen https://github.com/bluesky-social/social-app/pull/464 - /> - ) -} + const data = React.useMemo( + () => [HEADER_ITEM, SELECTOR_ITEM, ...items], + [items], + ) + return ( + <FlatList + ref={flatListRef} + data={data} + keyExtractor={keyExtractor} + renderItem={renderItemInternal} + ListFooterComponent={ListFooterComponent} + // NOTE sticky header disabled on android due to major performance issues -prf + stickyHeaderIndices={isAndroid ? undefined : STICKY_HEADER_INDICES} + onScroll={onScroll} + onEndReached={onEndReached} + refreshControl={ + <RefreshControl + refreshing={refreshing!} + onRefresh={onRefresh} + tintColor={pal.colors.text} + /> + } + onEndReachedThreshold={0.6} + contentContainerStyle={s.contentContainer} + removeClippedSubviews={true} + scrollIndicatorInsets={{right: 1}} // fixes a bug where the scroll indicator is on the middle of the screen https://github.com/bluesky-social/social-app/pull/464 + /> + ) + }, +) export function Selector({ selectedIndex, diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index a34ceb32c..c5ad286c7 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -4,7 +4,7 @@ import {observer} from 'mobx-react-lite' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {withAuthRequired} from 'view/com/auth/withAuthRequired' -import {ViewSelector} from '../com/util/ViewSelector' +import {ViewSelector, ViewSelectorHandle} from '../com/util/ViewSelector' import {CenteredView} from '../com/util/Views' import {ScreenHider} from 'view/com/util/moderation/ScreenHider' import {ProfileUiModel, Sections} from 'state/models/ui/profile' @@ -35,6 +35,7 @@ export const ProfileScreen = withAuthRequired( observer(({route}: Props) => { const store = useStores() const {screen, track} = useAnalytics() + const viewSelectorRef = React.useRef<ViewSelectorHandle>(null) useEffect(() => { screen('Profile') @@ -47,12 +48,17 @@ export const ProfileScreen = withAuthRequired( ) useSetTitle(combinedDisplayName(uiState.profile)) + const onSoftReset = React.useCallback(() => { + viewSelectorRef.current?.scrollToTop() + }, []) + useEffect(() => { setHasSetup(false) }, [route.params.name]) useFocusEffect( React.useCallback(() => { + const softResetSub = store.onScreenSoftReset(onSoftReset) let aborted = false store.shell.setMinimalShellMode(false) const feedCleanup = uiState.feed.registerListeners() @@ -69,8 +75,9 @@ export const ProfileScreen = withAuthRequired( return () => { aborted = true feedCleanup() + softResetSub.remove() } - }, [hasSetup, uiState, store]), + }, [store, onSoftReset, uiState, hasSetup]), ) // events @@ -247,6 +254,7 @@ export const ProfileScreen = withAuthRequired( /> ) : uiState.profile.hasLoaded ? ( <ViewSelector + ref={viewSelectorRef} swipeEnabled={false} sections={uiState.selectorItems} items={uiState.uiItems} |