From 9ce02dff5b995d384bdc5ea43323a0d03ca754cc Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
Date: Wed, 7 Dec 2022 15:51:06 -0600
Subject: Add HorzSwipe gesture and integrate it into the ViewSelector

---
 src/view/com/util/ViewSelector.tsx | 107 +++++++++++--------------------------
 1 file changed, 31 insertions(+), 76 deletions(-)

(limited to 'src/view/com/util/ViewSelector.tsx')

diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx
index b404988f3..f7652334f 100644
--- a/src/view/com/util/ViewSelector.tsx
+++ b/src/view/com/util/ViewSelector.tsx
@@ -1,15 +1,13 @@
 import React, {useEffect, useState, useMemo} from 'react'
 import {FlatList, StyleSheet, View} from 'react-native'
-import {GestureDetector, Gesture} from 'react-native-gesture-handler'
-import {useSharedValue, withTiming, runOnJS} from 'react-native-reanimated'
 import {Selector} from './Selector'
+import {HorzSwipe} from './gestures/HorzSwipe'
+import {useAnimatedValue} from '../../lib/useAnimatedValue'
+import {useStores} from '../../../state'
 
 const HEADER_ITEM = {_reactKey: '__header__'}
 const SELECTOR_ITEM = {_reactKey: '__selector__'}
 const STICKY_HEADER_INDICES = [1]
-const SWIPE_GESTURE_MAX_DISTANCE = 200
-const SWIPE_GESTURE_VEL_TRIGGER = 2000
-const SWIPE_GESTURE_HIT_SLOP = {left: -50, top: 0, right: 0, bottom: 0} // we ignore the left 20 pixels to avoid conflicts with the page-nav gesture
 
 export function ViewSelector({
   sections,
@@ -32,72 +30,26 @@ export function ViewSelector({
   onRefresh?: () => void
   onEndReached?: (info: {distanceFromEnd: number}) => void
 }) {
+  const store = useStores()
   const [selectedIndex, setSelectedIndex] = useState<number>(0)
-  const swipeGestureInterp = useSharedValue<number>(0)
+  const panX = useAnimatedValue(0)
 
   // events
   // =
 
+  const onSwipeEnd = (dx: number) => {
+    if (dx !== 0) {
+      setSelectedIndex(selectedIndex + dx)
+    }
+  }
   const onPressSelection = (index: number) => setSelectedIndex(index)
   useEffect(() => {
+    store.shell.setViewControllingSwipes(
+      Boolean(swipeEnabled) && selectedIndex > 0,
+    )
     onSelectView?.(selectedIndex)
   }, [selectedIndex])
 
-  // gestures
-  // =
-
-  const swipeGesture = useMemo(() => {
-    if (!swipeEnabled) return undefined
-    return Gesture.Pan()
-      .hitSlop(SWIPE_GESTURE_HIT_SLOP)
-      .onUpdate(e => {
-        // calculate [-1, 1] range for the gesture
-        const clamped = Math.min(e.translationX, SWIPE_GESTURE_MAX_DISTANCE)
-        const reversed = clamped * -1
-        const scaled = reversed / SWIPE_GESTURE_MAX_DISTANCE
-        swipeGestureInterp.value = scaled
-      })
-      .onEnd(e => {
-        const vx = e.velocityX
-        if (
-          swipeGestureInterp.value >= 0.5 ||
-          (vx < 0 && Math.abs(vx) > SWIPE_GESTURE_VEL_TRIGGER)
-        ) {
-          // swiped to next
-          if (selectedIndex < sections.length - 1) {
-            // interp to the next item's position...
-            swipeGestureInterp.value = withTiming(1, {duration: 100}, () => {
-              // ...then update the index, which triggers the useEffect() below [1]
-              runOnJS(setSelectedIndex)(selectedIndex + 1)
-            })
-          } else {
-            swipeGestureInterp.value = withTiming(0, {duration: 100})
-          }
-        } else if (
-          swipeGestureInterp.value <= -0.5 ||
-          (vx > 0 && Math.abs(vx) > SWIPE_GESTURE_VEL_TRIGGER)
-        ) {
-          // swiped to prev
-          if (selectedIndex > 0) {
-            // interp to the prev item's position...
-            swipeGestureInterp.value = withTiming(-1, {duration: 100}, () => {
-              // ...then update the index, which triggers the useEffect() below [1]
-              runOnJS(setSelectedIndex)(selectedIndex - 1)
-            })
-          } else {
-            swipeGestureInterp.value = withTiming(0, {duration: 100})
-          }
-        } else {
-          swipeGestureInterp.value = withTiming(0, {duration: 100})
-        }
-      })
-  }, [swipeEnabled, swipeGestureInterp, selectedIndex, sections.length])
-  useEffect(() => {
-    // [1] completes the swipe gesture animation by resetting the interp value
-    // this has to be done as an effect so that it occurs *after* the selectedIndex has been updated
-    swipeGestureInterp.value = 0
-  }, [swipeGestureInterp, selectedIndex])
-
   // rendering
   // =
 
@@ -111,8 +63,8 @@ export function ViewSelector({
       return (
         <Selector
           items={sections}
+          panX={panX}
           selectedIndex={selectedIndex}
-          swipeGestureInterp={swipeGestureInterp}
           onSelect={onPressSelection}
         />
       )
@@ -122,21 +74,24 @@ export function ViewSelector({
   }
 
   const data = [HEADER_ITEM, SELECTOR_ITEM, ...items]
-  const listEl = (
-    <FlatList
-      data={data}
-      keyExtractor={item => item._reactKey}
-      renderItem={renderItemInternal}
-      stickyHeaderIndices={STICKY_HEADER_INDICES}
-      refreshing={refreshing}
-      onRefresh={onRefresh}
-      onEndReached={onEndReached}
-    />
+  return (
+    <HorzSwipe
+      panX={panX}
+      swipeEnabled={swipeEnabled || false}
+      canSwipeLeft={selectedIndex > 0}
+      canSwipeRight={selectedIndex < sections.length - 1}
+      onSwipeEnd={onSwipeEnd}>
+      <FlatList
+        data={data}
+        keyExtractor={item => item._reactKey}
+        renderItem={renderItemInternal}
+        stickyHeaderIndices={STICKY_HEADER_INDICES}
+        refreshing={refreshing}
+        onRefresh={onRefresh}
+        onEndReached={onEndReached}
+      />
+    </HorzSwipe>
   )
-  if (swipeEnabled) {
-    return <GestureDetector gesture={swipeGesture}>{listEl}</GestureDetector>
-  }
-  return listEl
 }
 
 const styles = StyleSheet.create({})
-- 
cgit 1.4.1

