about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/lib/ScrollContext.tsx5
-rw-r--r--src/screens/Profile/Sections/Labels.tsx3
-rw-r--r--src/view/com/util/List.tsx5
-rw-r--r--src/view/com/util/MainScrollProvider.tsx59
4 files changed, 58 insertions, 14 deletions
diff --git a/src/lib/ScrollContext.tsx b/src/lib/ScrollContext.tsx
index 00b197bed..d55b8cdab 100644
--- a/src/lib/ScrollContext.tsx
+++ b/src/lib/ScrollContext.tsx
@@ -5,6 +5,7 @@ const ScrollContext = createContext<ScrollHandlers<any>>({
   onBeginDrag: undefined,
   onEndDrag: undefined,
   onScroll: undefined,
+  onMomentumEnd: undefined,
 })
 
 export function useScrollHandlers(): ScrollHandlers<any> {
@@ -20,14 +21,16 @@ export function ScrollProvider({
   onBeginDrag,
   onEndDrag,
   onScroll,
+  onMomentumEnd,
 }: ProviderProps) {
   const handlers = useMemo(
     () => ({
       onBeginDrag,
       onEndDrag,
       onScroll,
+      onMomentumEnd,
     }),
-    [onBeginDrag, onEndDrag, onScroll],
+    [onBeginDrag, onEndDrag, onScroll, onMomentumEnd],
   )
   return (
     <ScrollContext.Provider value={handlers}>{children}</ScrollContext.Provider>
diff --git a/src/screens/Profile/Sections/Labels.tsx b/src/screens/Profile/Sections/Labels.tsx
index f43e3633d..553d94d2e 100644
--- a/src/screens/Profile/Sections/Labels.tsx
+++ b/src/screens/Profile/Sections/Labels.tsx
@@ -123,6 +123,9 @@ export function ProfileLabelsSectionInner({
     onScroll(e, ctx) {
       contextScrollHandlers.onScroll?.(e, ctx)
     },
+    onMomentumEnd(e, ctx) {
+      contextScrollHandlers.onMomentumEnd?.(e, ctx)
+    },
   })
 
   const {labelValues} = labelerInfo.policies
diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx
index 5729a43a5..d96a634ef 100644
--- a/src/view/com/util/List.tsx
+++ b/src/view/com/util/List.tsx
@@ -64,6 +64,11 @@ function ListImpl<ItemT>(
         }
       }
     },
+    // Note: adding onMomentumBegin here makes simulator scroll
+    // lag on Android. So either don't add it, or figure out why.
+    onMomentumEnd(e, ctx) {
+      contextScrollHandlers.onMomentumEnd?.(e, ctx)
+    },
   })
 
   let refreshControl
diff --git a/src/view/com/util/MainScrollProvider.tsx b/src/view/com/util/MainScrollProvider.tsx
index 01b8a954d..f45229dc4 100644
--- a/src/view/com/util/MainScrollProvider.tsx
+++ b/src/view/com/util/MainScrollProvider.tsx
@@ -1,11 +1,12 @@
 import React, {useCallback, useEffect} from 'react'
+import {NativeScrollEvent} from 'react-native'
+import {interpolate, useSharedValue} from 'react-native-reanimated'
 import EventEmitter from 'eventemitter3'
+
 import {ScrollProvider} from '#/lib/ScrollContext'
-import {NativeScrollEvent} from 'react-native'
-import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
+import {useMinimalShellMode, useSetMinimalShellMode} from '#/state/shell'
 import {useShellLayout} from '#/state/shell/shell-layout'
 import {isNative, isWeb} from 'platform/detection'
-import {useSharedValue, interpolate} from 'react-native-reanimated'
 
 const WEB_HIDE_SHELL_THRESHOLD = 200
 
@@ -32,6 +33,31 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
     }
   })
 
+  const snapToClosestState = useCallback(
+    (e: NativeScrollEvent) => {
+      'worklet'
+      if (isNative) {
+        if (startDragOffset.value === null) {
+          return
+        }
+        const didScrollDown = e.contentOffset.y > startDragOffset.value
+        startDragOffset.value = null
+        startMode.value = null
+        if (e.contentOffset.y < headerHeight.value) {
+          // If we're close to the top, show the shell.
+          setMode(false)
+        } else if (didScrollDown) {
+          // Showing the bar again on scroll down feels annoying, so don't.
+          setMode(true)
+        } else {
+          // Snap to whichever state is the closest.
+          setMode(Math.round(mode.value) === 1)
+        }
+      }
+    },
+    [startDragOffset, startMode, setMode, mode, headerHeight],
+  )
+
   const onBeginDrag = useCallback(
     (e: NativeScrollEvent) => {
       'worklet'
@@ -47,18 +73,24 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
     (e: NativeScrollEvent) => {
       'worklet'
       if (isNative) {
-        startDragOffset.value = null
-        startMode.value = null
-        if (e.contentOffset.y < headerHeight.value / 2) {
-          // If we're close to the top, show the shell.
-          setMode(false)
-        } else {
-          // Snap to whichever state is the closest.
-          setMode(Math.round(mode.value) === 1)
+        if (e.velocity && e.velocity.y !== 0) {
+          // If we detect a velocity, wait for onMomentumEnd to snap.
+          return
         }
+        snapToClosestState(e)
       }
     },
-    [startDragOffset, startMode, setMode, mode, headerHeight],
+    [snapToClosestState],
+  )
+
+  const onMomentumEnd = useCallback(
+    (e: NativeScrollEvent) => {
+      'worklet'
+      if (isNative) {
+        snapToClosestState(e)
+      }
+    },
+    [snapToClosestState],
   )
 
   const onScroll = useCallback(
@@ -119,7 +151,8 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
     <ScrollProvider
       onBeginDrag={onBeginDrag}
       onEndDrag={onEndDrag}
-      onScroll={onScroll}>
+      onScroll={onScroll}
+      onMomentumEnd={onMomentumEnd}>
       {children}
     </ScrollProvider>
   )