about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/lists/ListItems.tsx6
-rw-r--r--src/view/com/pager/Pager.web.tsx13
-rw-r--r--src/view/com/pager/PagerWithHeader.tsx170
-rw-r--r--src/view/com/posts/Feed.tsx6
-rw-r--r--src/view/screens/ProfileFeed.tsx30
-rw-r--r--src/view/screens/ProfileList.tsx22
6 files changed, 160 insertions, 87 deletions
diff --git a/src/view/com/lists/ListItems.tsx b/src/view/com/lists/ListItems.tsx
index fe7b9b78a..eec30ec40 100644
--- a/src/view/com/lists/ListItems.tsx
+++ b/src/view/com/lists/ListItems.tsx
@@ -1,6 +1,7 @@
 import React, {MutableRefObject} from 'react'
 import {
   ActivityIndicator,
+  Dimensions,
   RefreshControl,
   StyleProp,
   View,
@@ -18,7 +19,6 @@ import {ListModel} from 'state/models/content/list'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {s} from 'lib/styles'
 import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
 import {logger} from '#/logger'
 import {useModalControls} from '#/state/modals'
@@ -226,7 +226,9 @@ export const ListItems = observer(function ListItemsImpl({
             progressViewOffset={headerOffset}
           />
         }
-        contentContainerStyle={s.contentContainer}
+        contentContainerStyle={{
+          paddingBottom: Dimensions.get('window').height - headerOffset,
+        }}
         style={{paddingTop: headerOffset}}
         onScroll={scrollHandler}
         onEndReached={onEndReached}
diff --git a/src/view/com/pager/Pager.web.tsx b/src/view/com/pager/Pager.web.tsx
index 7ec292667..3b5e9164a 100644
--- a/src/view/com/pager/Pager.web.tsx
+++ b/src/view/com/pager/Pager.web.tsx
@@ -49,7 +49,18 @@ export const Pager = React.forwardRef(function PagerImpl(
           onSelect: onTabBarSelect,
         })}
       {React.Children.map(children, (child, i) => (
-        <View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
+        <View
+          style={
+            selectedPage === i
+              ? s.flex1
+              : {
+                  position: 'absolute',
+                  pointerEvents: 'none',
+                  // @ts-ignore web-only
+                  visibility: 'hidden',
+                }
+          }
+          key={`page-${i}`}>
           {child}
         </View>
       ))}
diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx
index 8b9e0c85a..e93d91fed 100644
--- a/src/view/com/pager/PagerWithHeader.tsx
+++ b/src/view/com/pager/PagerWithHeader.tsx
@@ -1,17 +1,19 @@
 import * as React from 'react'
 import {
   LayoutChangeEvent,
-  NativeScrollEvent,
+  FlatList,
+  ScrollView,
   StyleSheet,
   View,
+  NativeScrollEvent,
 } from 'react-native'
 import Animated, {
-  Easing,
-  useAnimatedReaction,
   useAnimatedStyle,
   useSharedValue,
-  withTiming,
   runOnJS,
+  scrollTo,
+  useAnimatedRef,
+  AnimatedRef,
 } from 'react-native-reanimated'
 import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {TabBar} from './TabBar'
@@ -24,6 +26,7 @@ interface PagerWithHeaderChildParams {
   headerHeight: number
   onScroll: OnScrollHandler
   isScrolledDown: boolean
+  scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null>
 }
 
 export interface PagerWithHeaderProps {
@@ -54,28 +57,12 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
   ) {
     const {isMobile} = useWebMediaQueries()
     const [currentPage, setCurrentPage] = React.useState(0)
-    const scrollYs = React.useRef<Record<number, number>>({})
-    const scrollY = useSharedValue(scrollYs.current[currentPage] || 0)
     const [tabBarHeight, setTabBarHeight] = React.useState(0)
     const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
-    const [isScrolledDown, setIsScrolledDown] = React.useState(
-      scrollYs.current[currentPage] > SCROLLED_DOWN_LIMIT,
-    )
-
+    const [isScrolledDown, setIsScrolledDown] = React.useState(false)
+    const scrollY = useSharedValue(0)
     const headerHeight = headerOnlyHeight + tabBarHeight
 
-    // react to scroll updates
-    function onScrollUpdate(v: number) {
-      // track each page's current scroll position
-      scrollYs.current[currentPage] = Math.min(v, headerOnlyHeight)
-      // update the 'is scrolled down' value
-      setIsScrolledDown(v > SCROLLED_DOWN_LIMIT)
-    }
-    useAnimatedReaction(
-      () => scrollY.value,
-      v => runOnJS(onScrollUpdate)(v),
-    )
-
     // capture the header bar sizing
     const onTabBarLayout = React.useCallback(
       (evt: LayoutChangeEvent) => {
@@ -91,19 +78,17 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
     )
 
     // render the the header and tab bar
-    const headerTransform = useAnimatedStyle(
-      () => ({
-        transform: [
-          {
-            translateY: Math.min(
-              Math.min(scrollY.value, headerOnlyHeight) * -1,
-              0,
-            ),
-          },
-        ],
-      }),
-      [scrollY, headerHeight, tabBarHeight],
-    )
+    const headerTransform = useAnimatedStyle(() => ({
+      transform: [
+        {
+          translateY: Math.min(
+            Math.min(scrollY.value, headerOnlyHeight) * -1,
+            0,
+          ),
+        },
+      ],
+    }))
+
     const renderTabBar = React.useCallback(
       (props: RenderTabBarFnProps) => {
         return (
@@ -144,12 +129,38 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
       ],
     )
 
-    // props to pass into children render functions
-    function onScrollWorklet(e: NativeScrollEvent) {
-      'worklet'
-      scrollY.value = e.contentOffset.y
+    const scrollRefs = useSharedValue<AnimatedRef<any>[]>([])
+    const registerRef = (scrollRef: AnimatedRef<any>, index: number) => {
+      scrollRefs.modify(refs => {
+        'worklet'
+        refs[index] = scrollRef
+        return refs
+      })
     }
 
+    const onScrollWorklet = React.useCallback(
+      (e: NativeScrollEvent) => {
+        'worklet'
+        const nextScrollY = e.contentOffset.y
+        scrollY.value = nextScrollY
+
+        if (nextScrollY < headerOnlyHeight) {
+          const refs = scrollRefs.value
+          for (let i = 0; i < refs.length; i++) {
+            if (i !== currentPage) {
+              scrollTo(refs[i], 0, nextScrollY, false)
+            }
+          }
+        }
+
+        const nextIsScrolledDown = nextScrollY > SCROLLED_DOWN_LIMIT
+        if (isScrolledDown !== nextIsScrolledDown) {
+          runOnJS(setIsScrolledDown)(nextIsScrolledDown)
+        }
+      },
+      [currentPage, headerOnlyHeight, isScrolledDown, scrollRefs, scrollY],
+    )
+
     const onPageSelectedInner = React.useCallback(
       (index: number) => {
         setCurrentPage(index)
@@ -158,19 +169,9 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
       [onPageSelected, setCurrentPage],
     )
 
-    const onPageSelecting = React.useCallback(
-      (index: number) => {
-        setCurrentPage(index)
-        if (scrollY.value > headerHeight) {
-          scrollY.value = headerHeight
-        }
-        scrollY.value = withTiming(scrollYs.current[index] || 0, {
-          duration: 170,
-          easing: Easing.inOut(Easing.quad),
-        })
-      },
-      [scrollY, setCurrentPage, scrollYs, headerHeight],
-    )
+    const onPageSelecting = React.useCallback((index: number) => {
+      setCurrentPage(index)
+    }, [])
 
     return (
       <Pager
@@ -184,26 +185,18 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
         {toArray(children)
           .filter(Boolean)
           .map((child, i) => {
-            let output = null
-            if (
-              child != null &&
-              // Defer showing content until we know it won't jump.
-              isHeaderReady &&
-              headerOnlyHeight > 0 &&
-              tabBarHeight > 0
-            ) {
-              output = child({
-                headerHeight,
-                isScrolledDown,
-                onScroll: {
-                  onScroll: i === currentPage ? onScrollWorklet : noop,
-                },
-              })
-            }
-            // Pager children must be noncollapsible plain <View>s.
+            const isReady =
+              isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0
             return (
               <View key={i} collapsable={false}>
-                {output}
+                <PagerItem
+                  headerHeight={headerHeight}
+                  isReady={isReady}
+                  isScrolledDown={isScrolledDown}
+                  onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
+                  registerRef={(r: AnimatedRef<any>) => registerRef(r, i)}
+                  renderTab={child}
+                />
               </View>
             )
           })}
@@ -212,6 +205,43 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
   },
 )
 
+function PagerItem({
+  headerHeight,
+  isReady,
+  isScrolledDown,
+  onScrollWorklet,
+  renderTab,
+  registerRef,
+}: {
+  headerHeight: number
+  isReady: boolean
+  isScrolledDown: boolean
+  registerRef: (scrollRef: AnimatedRef<any>) => void
+  onScrollWorklet: (e: NativeScrollEvent) => void
+  renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
+}) {
+  const scrollElRef = useAnimatedRef()
+  registerRef(scrollElRef)
+
+  const scrollHandler = React.useMemo(
+    () => ({onScroll: onScrollWorklet}),
+    [onScrollWorklet],
+  )
+
+  if (!isReady || renderTab == null) {
+    return null
+  }
+
+  return renderTab({
+    headerHeight,
+    isScrolledDown,
+    onScroll: scrollHandler,
+    scrollElRef: scrollElRef as React.MutableRefObject<
+      FlatList<any> | ScrollView | null
+    >,
+  })
+}
+
 const styles = StyleSheet.create({
   tabBarMobile: {
     position: 'absolute',
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index 5b517f4c7..23ab2a7ba 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -2,6 +2,7 @@ import React, {MutableRefObject} from 'react'
 import {observer} from 'mobx-react-lite'
 import {
   ActivityIndicator,
+  Dimensions,
   RefreshControl,
   StyleProp,
   StyleSheet,
@@ -15,7 +16,6 @@ import {PostsFeedModel} from 'state/models/feeds/posts'
 import {FeedSlice} from './FeedSlice'
 import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
 import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
-import {s} from 'lib/styles'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
@@ -178,7 +178,9 @@ export const Feed = observer(function Feed({
             progressViewOffset={headerOffset}
           />
         }
-        contentContainerStyle={s.contentContainer}
+        contentContainerStyle={{
+          paddingBottom: Dimensions.get('window').height - headerOffset,
+        }}
         style={{paddingTop: headerOffset}}
         onScroll={onScroll != null ? scrollHandler : undefined}
         scrollEventThrottle={scrollEventThrottle}
diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx
index c1496e4ad..e1cc0e938 100644
--- a/src/view/screens/ProfileFeed.tsx
+++ b/src/view/screens/ProfileFeed.tsx
@@ -1,5 +1,11 @@
 import React, {useMemo, useCallback} from 'react'
-import {FlatList, StyleSheet, View, ActivityIndicator} from 'react-native'
+import {
+  Dimensions,
+  StyleSheet,
+  View,
+  ActivityIndicator,
+  FlatList,
+} from 'react-native'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 import {useNavigation} from '@react-navigation/native'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -343,16 +349,19 @@ export const ProfileFeedScreenInner = observer(
           isHeaderReady={feedInfo?.hasLoaded ?? false}
           renderHeader={renderHeader}
           onCurrentPageSelected={onCurrentPageSelected}>
-          {({onScroll, headerHeight, isScrolledDown}) => (
+          {({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
             <FeedSection
               ref={feedSectionRef}
               feed={feed}
               onScroll={onScroll}
               headerHeight={headerHeight}
               isScrolledDown={isScrolledDown}
+              scrollElRef={
+                scrollElRef as React.MutableRefObject<FlatList<any> | null>
+              }
             />
           )}
-          {({onScroll, headerHeight}) => (
+          {({onScroll, headerHeight, scrollElRef}) => (
             <AboutSection
               feedOwnerDid={feedOwnerDid}
               feedRkey={rkey}
@@ -360,6 +369,9 @@ export const ProfileFeedScreenInner = observer(
               headerHeight={headerHeight}
               onToggleLiked={onToggleLiked}
               onScroll={onScroll}
+              scrollElRef={
+                scrollElRef as React.MutableRefObject<ScrollView | null>
+              }
             />
           )}
         </PagerWithHeader>
@@ -387,14 +399,14 @@ interface FeedSectionProps {
   onScroll: OnScrollHandler
   headerHeight: number
   isScrolledDown: boolean
+  scrollElRef: React.MutableRefObject<FlatList<any> | null>
 }
 const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
   function FeedSectionImpl(
-    {feed, onScroll, headerHeight, isScrolledDown},
+    {feed, onScroll, headerHeight, isScrolledDown, scrollElRef},
     ref,
   ) {
     const hasNew = feed.hasNewLatest && !feed.isRefreshing
-    const scrollElRef = React.useRef<FlatList>(null)
 
     const onScrollToTop = useCallback(() => {
       scrollElRef.current?.scrollToOffset({offset: -headerHeight})
@@ -438,6 +450,7 @@ const AboutSection = observer(function AboutPageImpl({
   headerHeight,
   onToggleLiked,
   onScroll,
+  scrollElRef,
 }: {
   feedOwnerDid: string
   feedRkey: string
@@ -445,6 +458,7 @@ const AboutSection = observer(function AboutPageImpl({
   headerHeight: number
   onToggleLiked: () => void
   onScroll: OnScrollHandler
+  scrollElRef: React.MutableRefObject<ScrollView | null>
 }) {
   const pal = usePalette('default')
   const {_} = useLingui()
@@ -456,8 +470,12 @@ const AboutSection = observer(function AboutPageImpl({
 
   return (
     <ScrollView
+      ref={scrollElRef}
       scrollEventThrottle={1}
-      contentContainerStyle={{paddingTop: headerHeight}}
+      contentContainerStyle={{
+        paddingTop: headerHeight,
+        paddingBottom: Dimensions.get('window').height - headerHeight,
+      }}
       onScroll={scrollHandler}>
       <View
         style={[
diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx
index 497c1ae76..9180d21d5 100644
--- a/src/view/screens/ProfileList.tsx
+++ b/src/view/screens/ProfileList.tsx
@@ -175,18 +175,24 @@ export const ProfileListScreenInner = observer(
             isHeaderReady={list.hasLoaded}
             renderHeader={renderHeader}
             onCurrentPageSelected={onCurrentPageSelected}>
-            {({onScroll, headerHeight, isScrolledDown}) => (
+            {({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
               <FeedSection
                 ref={feedSectionRef}
+                scrollElRef={
+                  scrollElRef as React.MutableRefObject<FlatList<any> | null>
+                }
                 feed={feed}
                 onScroll={onScroll}
                 headerHeight={headerHeight}
                 isScrolledDown={isScrolledDown}
               />
             )}
-            {({onScroll, headerHeight, isScrolledDown}) => (
+            {({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
               <AboutSection
                 ref={aboutSectionRef}
+                scrollElRef={
+                  scrollElRef as React.MutableRefObject<FlatList<any> | null>
+                }
                 list={list}
                 descriptionRT={list.descriptionRT}
                 creator={list.data ? list.data.creator : undefined}
@@ -223,9 +229,12 @@ export const ProfileListScreenInner = observer(
             items={SECTION_TITLES_MOD}
             isHeaderReady={list.hasLoaded}
             renderHeader={renderHeader}>
-            {({onScroll, headerHeight, isScrolledDown}) => (
+            {({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
               <AboutSection
                 list={list}
+                scrollElRef={
+                  scrollElRef as React.MutableRefObject<FlatList<any> | null>
+                }
                 descriptionRT={list.descriptionRT}
                 creator={list.data ? list.data.creator : undefined}
                 isCurateList={list.isCuratelist}
@@ -557,14 +566,14 @@ interface FeedSectionProps {
   onScroll: OnScrollHandler
   headerHeight: number
   isScrolledDown: boolean
+  scrollElRef: React.MutableRefObject<FlatList<any> | null>
 }
 const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
   function FeedSectionImpl(
-    {feed, onScroll, headerHeight, isScrolledDown},
+    {feed, scrollElRef, onScroll, headerHeight, isScrolledDown},
     ref,
   ) {
     const hasNew = feed.hasNewLatest && !feed.isRefreshing
-    const scrollElRef = React.useRef<FlatList>(null)
 
     const onScrollToTop = useCallback(() => {
       scrollElRef.current?.scrollToOffset({offset: -headerHeight})
@@ -611,6 +620,7 @@ interface AboutSectionProps {
   onScroll: OnScrollHandler
   headerHeight: number
   isScrolledDown: boolean
+  scrollElRef: React.MutableRefObject<FlatList<any> | null>
 }
 const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
   function AboutSectionImpl(
@@ -624,13 +634,13 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
       onScroll,
       headerHeight,
       isScrolledDown,
+      scrollElRef,
     },
     ref,
   ) {
     const pal = usePalette('default')
     const {_} = useLingui()
     const {isMobile} = useWebMediaQueries()
-    const scrollElRef = React.useRef<FlatList>(null)
 
     const onScrollToTop = useCallback(() => {
       scrollElRef.current?.scrollToOffset({offset: -headerHeight})