about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/view/com/util/ViewSelector.tsx200
-rw-r--r--src/view/screens/Profile.tsx12
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}