about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-04-22 17:14:20 -0500
committerGitHub <noreply@github.com>2023-04-22 17:14:20 -0500
commitd35f7c1f1a9f35958ff3f6dacd002e31b0a824b0 (patch)
tree6d69c883ef34fb3593d42641d25e421222e21635 /src/view
parenteb6b36be61b38d2dab799d8f82b6f65645b9e3f6 (diff)
downloadvoidsky-d35f7c1f1a9f35958ff3f6dacd002e31b0a824b0.tar.zst
Android fixes (#515)
* Fix profile screen performance on android and remove dead code

* Correctly handle android hardware back btn

* Fix EditProfile modal for android

* Fix lint
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/lightbox/Lightbox.tsx3
-rw-r--r--src/view/com/modals/EditProfile.tsx193
-rw-r--r--src/view/com/modals/Modal.tsx19
-rw-r--r--src/view/com/util/UserBanner.tsx2
-rw-r--r--src/view/com/util/ViewSelector.tsx142
-rw-r--r--src/view/com/util/gestures/HorzSwipe.tsx157
-rw-r--r--src/view/com/util/gestures/SwipeAndZoom.tsx302
-rw-r--r--src/view/screens/Profile.tsx12
8 files changed, 236 insertions, 594 deletions
diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx
index d6cc8c254..06b48143b 100644
--- a/src/view/com/lightbox/Lightbox.tsx
+++ b/src/view/com/lightbox/Lightbox.tsx
@@ -1,5 +1,4 @@
 import React from 'react'
-import {View} from 'react-native'
 import {observer} from 'mobx-react-lite'
 import ImageView from './ImageViewing'
 import {useStores} from 'state/index'
@@ -48,6 +47,6 @@ export const Lightbox = observer(function Lightbox() {
       />
     )
   } else {
-    return <View />
+    return null
   }
 })
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index 0feae3a80..5a1ba3638 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -1,13 +1,15 @@
-import React, {useState} from 'react'
+import React, {useState, useCallback} from 'react'
 import * as Toast from '../util/Toast'
 import {
   ActivityIndicator,
+  KeyboardAvoidingView,
+  ScrollView,
   StyleSheet,
+  TextInput,
   TouchableOpacity,
   View,
 } from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
-import {ScrollView, TextInput} from './util'
 import {Image as RNImage} from 'react-native-image-crop-picker'
 import {Text} from '../util/text/Text'
 import {ErrorMessage} from '../util/error/ErrorMessage'
@@ -24,7 +26,7 @@ import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics'
 import {cleanError, isNetworkError} from 'lib/strings/errors'
 
-export const snapPoints = ['80%']
+export const snapPoints = ['fullscreen']
 
 export function Component({
   profileView,
@@ -61,38 +63,43 @@ export function Component({
   const onPressCancel = () => {
     store.shell.closeModal()
   }
-  const onSelectNewAvatar = async (img: RNImage | null) => {
-    track('EditProfile:AvatarSelected')
-    try {
-      // if img is null, user selected "remove avatar"
+  const onSelectNewAvatar = useCallback(
+    async (img: RNImage | null) => {
       if (!img) {
         setNewUserAvatar(null)
         setUserAvatar(null)
         return
       }
-      const finalImg = await compressIfNeeded(img, 1000000)
-      setNewUserAvatar(finalImg)
-      setUserAvatar(finalImg.path)
-    } catch (e: any) {
-      setError(cleanError(e))
-    }
-  }
-  const onSelectNewBanner = async (img: RNImage | null) => {
-    if (!img) {
-      setNewUserBanner(null)
-      setUserBanner(null)
-      return
-    }
-    track('EditProfile:BannerSelected')
-    try {
-      const finalImg = await compressIfNeeded(img, 1000000)
-      setNewUserBanner(finalImg)
-      setUserBanner(finalImg.path)
-    } catch (e: any) {
-      setError(cleanError(e))
-    }
-  }
-  const onPressSave = async () => {
+      track('EditProfile:AvatarSelected')
+      try {
+        const finalImg = await compressIfNeeded(img, 1000000)
+        setNewUserAvatar(finalImg)
+        setUserAvatar(finalImg.path)
+      } catch (e: any) {
+        setError(cleanError(e))
+      }
+    },
+    [track, setNewUserAvatar, setUserAvatar, setError],
+  )
+  const onSelectNewBanner = useCallback(
+    async (img: RNImage | null) => {
+      if (!img) {
+        setNewUserBanner(null)
+        setUserBanner(null)
+        return
+      }
+      track('EditProfile:BannerSelected')
+      try {
+        const finalImg = await compressIfNeeded(img, 1000000)
+        setNewUserBanner(finalImg)
+        setUserBanner(finalImg.path)
+      } catch (e: any) {
+        setError(cleanError(e))
+      }
+    },
+    [track, setNewUserBanner, setUserBanner, setError],
+  )
+  const onPressSave = useCallback(async () => {
     track('EditProfile:Save')
     setProcessing(true)
     if (error) {
@@ -120,11 +127,23 @@ export function Component({
       }
     }
     setProcessing(false)
-  }
+  }, [
+    track,
+    setProcessing,
+    setError,
+    error,
+    profileView,
+    onUpdate,
+    store,
+    displayName,
+    description,
+    newUserAvatar,
+    newUserBanner,
+  ])
 
   return (
-    <View style={[s.flex1, pal.view]} testID="editProfileModal">
-      <ScrollView style={styles.inner}>
+    <KeyboardAvoidingView behavior="height">
+      <ScrollView style={[pal.view]} testID="editProfileModal">
         <Text style={[styles.title, pal.text]}>Edit my profile</Text>
         <View style={styles.photos}>
           <UserBanner
@@ -144,65 +163,66 @@ export function Component({
             <ErrorMessage message={error} />
           </View>
         )}
-        <View>
-          <Text style={[styles.label, pal.text]}>Display Name</Text>
-          <TextInput
-            testID="editProfileDisplayNameInput"
-            style={[styles.textInput, pal.border, pal.text]}
-            placeholder="e.g. Alice Roberts"
-            placeholderTextColor={colors.gray4}
-            value={displayName}
-            onChangeText={v => setDisplayName(enforceLen(v, MAX_DISPLAY_NAME))}
-          />
-        </View>
-        <View style={s.pb10}>
-          <Text style={[styles.label, pal.text]}>Description</Text>
-          <TextInput
-            testID="editProfileDescriptionInput"
-            style={[styles.textArea, pal.border, pal.text]}
-            placeholder="e.g. Artist, dog-lover, and memelord."
-            placeholderTextColor={colors.gray4}
-            keyboardAppearance={theme.colorScheme}
-            multiline
-            value={description}
-            onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
-          />
-        </View>
-        {isProcessing ? (
-          <View style={[styles.btn, s.mt10, {backgroundColor: colors.gray2}]}>
-            <ActivityIndicator />
+        <View style={styles.form}>
+          <View>
+            <Text style={[styles.label, pal.text]}>Display Name</Text>
+            <TextInput
+              testID="editProfileDisplayNameInput"
+              style={[styles.textInput, pal.border, pal.text]}
+              placeholder="e.g. Alice Roberts"
+              placeholderTextColor={colors.gray4}
+              value={displayName}
+              onChangeText={v =>
+                setDisplayName(enforceLen(v, MAX_DISPLAY_NAME))
+              }
+            />
           </View>
-        ) : (
+          <View style={s.pb10}>
+            <Text style={[styles.label, pal.text]}>Description</Text>
+            <TextInput
+              testID="editProfileDescriptionInput"
+              style={[styles.textArea, pal.border, pal.text]}
+              placeholder="e.g. Artist, dog-lover, and memelord."
+              placeholderTextColor={colors.gray4}
+              keyboardAppearance={theme.colorScheme}
+              multiline
+              value={description}
+              onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
+            />
+          </View>
+          {isProcessing ? (
+            <View style={[styles.btn, s.mt10, {backgroundColor: colors.gray2}]}>
+              <ActivityIndicator />
+            </View>
+          ) : (
+            <TouchableOpacity
+              testID="editProfileSaveBtn"
+              style={s.mt10}
+              onPress={onPressSave}>
+              <LinearGradient
+                colors={[gradients.blueLight.start, gradients.blueLight.end]}
+                start={{x: 0, y: 0}}
+                end={{x: 1, y: 1}}
+                style={[styles.btn]}>
+                <Text style={[s.white, s.bold]}>Save Changes</Text>
+              </LinearGradient>
+            </TouchableOpacity>
+          )}
           <TouchableOpacity
-            testID="editProfileSaveBtn"
-            style={s.mt10}
-            onPress={onPressSave}>
-            <LinearGradient
-              colors={[gradients.blueLight.start, gradients.blueLight.end]}
-              start={{x: 0, y: 0}}
-              end={{x: 1, y: 1}}
-              style={[styles.btn]}>
-              <Text style={[s.white, s.bold]}>Save Changes</Text>
-            </LinearGradient>
+            testID="editProfileCancelBtn"
+            style={s.mt5}
+            onPress={onPressCancel}>
+            <View style={[styles.btn]}>
+              <Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
+            </View>
           </TouchableOpacity>
-        )}
-        <TouchableOpacity
-          testID="editProfileCancelBtn"
-          style={s.mt5}
-          onPress={onPressCancel}>
-          <View style={[styles.btn]}>
-            <Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
-          </View>
-        </TouchableOpacity>
+        </View>
       </ScrollView>
-    </View>
+    </KeyboardAvoidingView>
   )
 }
 
 const styles = StyleSheet.create({
-  inner: {
-    padding: 14,
-  },
   title: {
     textAlign: 'center',
     fontWeight: 'bold',
@@ -215,6 +235,9 @@ const styles = StyleSheet.create({
     paddingBottom: 4,
     marginTop: 20,
   },
+  form: {
+    paddingHorizontal: 14,
+  },
   textInput: {
     borderWidth: 1,
     borderRadius: 6,
@@ -243,7 +266,7 @@ const styles = StyleSheet.create({
   avi: {
     position: 'absolute',
     top: 80,
-    left: 10,
+    left: 24,
     width: 84,
     height: 84,
     borderWidth: 2,
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index 5d034a19d..df7d7f042 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -1,5 +1,6 @@
 import React, {useRef, useEffect} from 'react'
 import {StyleSheet} from 'react-native'
+import {SafeAreaView} from 'react-native-safe-area-context'
 import {observer} from 'mobx-react-lite'
 import BottomSheet from '@gorhom/bottom-sheet'
 import {useStores} from 'state/index'
@@ -92,13 +93,22 @@ export const ModalsContainer = observer(function ModalsContainer() {
     return null
   }
 
+  if (snapPoints[0] === 'fullscreen') {
+    return (
+      <SafeAreaView style={[styles.fullscreenContainer, pal.view]}>
+        {element}
+      </SafeAreaView>
+    )
+  }
+
   return (
     <BottomSheet
       ref={bottomSheetRef}
       snapPoints={snapPoints}
       index={store.shell.isModalActive ? 0 : -1}
       enablePanDownToClose
-      keyboardBehavior="fillParent"
+      keyboardBehavior="extend"
+      keyboardBlurBehavior="restore"
       backdropComponent={
         store.shell.isModalActive ? createCustomBackdrop(onClose) : undefined
       }
@@ -115,4 +125,11 @@ const styles = StyleSheet.create({
     borderTopLeftRadius: 10,
     borderTopRightRadius: 10,
   },
+  fullscreenContainer: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    bottom: 0,
+    right: 0,
+  },
 })
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index e58fb0ef4..fcd66ca7a 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -128,7 +128,7 @@ const styles = StyleSheet.create({
     width: 24,
     height: 24,
     bottom: 8,
-    right: 8,
+    right: 24,
     borderRadius: 12,
     alignItems: 'center',
     justifyContent: 'center',
diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx
index ba0780862..02717053d 100644
--- a/src/view/com/util/ViewSelector.tsx
+++ b/src/view/com/util/ViewSelector.tsx
@@ -1,12 +1,13 @@
 import React, {useEffect, useState} from 'react'
-import {View} from 'react-native'
-import {Selector} from './Selector'
-import {HorzSwipe} from './gestures/HorzSwipe'
+import {Pressable, StyleSheet, View} from 'react-native'
 import {FlatList} from './Views'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
+import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
+import {Text} from './text/Text'
+import {usePalette} from 'lib/hooks/usePalette'
 import {clamp} from 'lib/numbers'
-import {s} from 'lib/styles'
+import {s, colors} from 'lib/styles'
+import {isAndroid} from 'platform/detection'
 
 const HEADER_ITEM = {_reactKey: '__header__'}
 const SELECTOR_ITEM = {_reactKey: '__selector__'}
@@ -16,7 +17,6 @@ export function ViewSelector({
   sections,
   items,
   refreshing,
-  swipeEnabled,
   renderHeader,
   renderItem,
   ListFooterComponent,
@@ -42,19 +42,12 @@ export function ViewSelector({
   onEndReached?: (info: {distanceFromEnd: number}) => void
 }) {
   const [selectedIndex, setSelectedIndex] = useState<number>(0)
-  const panX = useAnimatedValue(0)
 
   // events
   // =
 
-  const onSwipeEnd = React.useCallback(
-    (dx: number) => {
-      if (dx !== 0) {
-        setSelectedIndex(clamp(selectedIndex + dx, 0, sections.length))
-      }
-    },
-    [setSelectedIndex, selectedIndex, sections],
-  )
+  const keyExtractor = React.useCallback(item => item._reactKey, [])
+
   const onPressSelection = React.useCallback(
     (index: number) => setSelectedIndex(clamp(index, 0, sections.length)),
     [setSelectedIndex, sections],
@@ -77,7 +70,6 @@ export function ViewSelector({
         return (
           <Selector
             items={sections}
-            panX={panX}
             selectedIndex={selectedIndex}
             onSelect={onPressSelection}
           />
@@ -86,7 +78,7 @@ export function ViewSelector({
         return renderItem(item)
       }
     },
-    [sections, panX, selectedIndex, onPressSelection, renderHeader, renderItem],
+    [sections, selectedIndex, onPressSelection, renderHeader, renderItem],
   )
 
   const data = React.useMemo(
@@ -94,28 +86,98 @@ export function ViewSelector({
     [items],
   )
   return (
-    <HorzSwipe
-      hasPriority
-      panX={panX}
-      swipeEnabled={swipeEnabled || false}
-      canSwipeLeft={selectedIndex > 0}
-      canSwipeRight={selectedIndex < sections.length - 1}
-      onSwipeEnd={onSwipeEnd}>
-      <FlatList
-        data={data}
-        keyExtractor={item => item._reactKey}
-        renderItem={renderItemInternal}
-        ListFooterComponent={ListFooterComponent}
-        stickyHeaderIndices={STICKY_HEADER_INDICES}
-        refreshing={refreshing}
-        onScroll={onScroll}
-        onRefresh={onRefresh}
-        onEndReached={onEndReached}
-        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
-      />
-    </HorzSwipe>
+    <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}
+      refreshing={refreshing}
+      onScroll={onScroll}
+      onRefresh={onRefresh}
+      onEndReached={onEndReached}
+      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,
+  items,
+  onSelect,
+}: {
+  selectedIndex: number
+  items: string[]
+  onSelect?: (index: number) => void
+}) {
+  const pal = usePalette('default')
+  const borderColor = useColorSchemeStyle(
+    {borderColor: colors.black},
+    {borderColor: colors.white},
+  )
+
+  const onPressItem = (index: number) => {
+    onSelect?.(index)
+  }
+
+  return (
+    <View style={[pal.view, styles.outer]}>
+      {items.map((item, i) => {
+        const selected = i === selectedIndex
+        return (
+          <Pressable
+            testID={`selector-${i}`}
+            key={item}
+            onPress={() => onPressItem(i)}>
+            <View
+              style={[
+                styles.item,
+                selected && styles.itemSelected,
+                borderColor,
+              ]}>
+              <Text
+                style={
+                  selected
+                    ? [styles.labelSelected, pal.text]
+                    : [styles.label, pal.textLight]
+                }>
+                {item}
+              </Text>
+            </View>
+          </Pressable>
+        )
+      })}
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  outer: {
+    flexDirection: 'row',
+    paddingHorizontal: 14,
+  },
+  item: {
+    marginRight: 14,
+    paddingHorizontal: 10,
+    paddingTop: 8,
+    paddingBottom: 12,
+  },
+  itemSelected: {
+    borderBottomWidth: 3,
+  },
+  label: {
+    fontWeight: '600',
+  },
+  labelSelected: {
+    fontWeight: '600',
+  },
+  underline: {
+    position: 'absolute',
+    height: 4,
+    bottom: 0,
+  },
+})
diff --git a/src/view/com/util/gestures/HorzSwipe.tsx b/src/view/com/util/gestures/HorzSwipe.tsx
deleted file mode 100644
index 09f6c345f..000000000
--- a/src/view/com/util/gestures/HorzSwipe.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import React, {useState} from 'react'
-import {
-  Animated,
-  GestureResponderEvent,
-  I18nManager,
-  PanResponder,
-  PanResponderGestureState,
-  useWindowDimensions,
-  View,
-} from 'react-native'
-import {clamp} from 'lodash'
-import {s} from 'lib/styles'
-
-interface Props {
-  panX: Animated.Value
-  canSwipeLeft?: boolean
-  canSwipeRight?: boolean
-  swipeEnabled?: boolean
-  hasPriority?: boolean // if has priority, will not release control of the gesture to another gesture
-  distThresholdDivisor?: number
-  useNativeDriver?: boolean
-  onSwipeStart?: () => void
-  onSwipeStartDirection?: (dx: number) => void
-  onSwipeEnd?: (dx: number) => void
-  children: React.ReactNode
-}
-
-export function HorzSwipe({
-  panX,
-  canSwipeLeft = false,
-  canSwipeRight = false,
-  swipeEnabled = true,
-  hasPriority = false,
-  distThresholdDivisor = 1.75,
-  useNativeDriver = false,
-  onSwipeStart,
-  onSwipeStartDirection,
-  onSwipeEnd,
-  children,
-}: Props) {
-  const winDim = useWindowDimensions()
-  const [dir, setDir] = useState<number>(0)
-
-  const swipeVelocityThreshold = 35
-  const swipeDistanceThreshold = winDim.width / distThresholdDivisor
-
-  const isMovingHorizontally = (
-    _: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    return (
-      Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 1.25) &&
-      Math.abs(gestureState.vx) > Math.abs(gestureState.vy * 1.25)
-    )
-  }
-
-  const canMoveScreen = (
-    event: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    if (swipeEnabled === false) {
-      return false
-    }
-
-    const diffX = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
-    const willHandle =
-      isMovingHorizontally(event, gestureState) &&
-      ((diffX > 0 && canSwipeLeft) || (diffX < 0 && canSwipeRight))
-    return willHandle
-  }
-
-  const startGesture = () => {
-    setDir(0)
-    onSwipeStart?.()
-
-    panX.stopAnimation()
-    // @ts-expect-error: _value is private, but docs use it as well
-    panX.setOffset(panX._value)
-  }
-
-  const respondToGesture = (
-    _: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    const diffX = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
-
-    if (
-      // swiping left
-      (diffX > 0 && !canSwipeLeft) ||
-      // swiping right
-      (diffX < 0 && !canSwipeRight)
-    ) {
-      panX.setValue(0)
-      return
-    }
-
-    panX.setValue(clamp(diffX / swipeDistanceThreshold, -1, 1) * -1)
-
-    const newDir = diffX > 0 ? -1 : diffX < 0 ? 1 : 0
-    if (newDir !== dir) {
-      setDir(newDir)
-      onSwipeStartDirection?.(newDir)
-    }
-  }
-
-  const finishGesture = (
-    _: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    if (
-      Math.abs(gestureState.dx) > Math.abs(gestureState.dy) &&
-      Math.abs(gestureState.vx) > Math.abs(gestureState.vy) &&
-      (Math.abs(gestureState.dx) > swipeDistanceThreshold / 4 ||
-        Math.abs(gestureState.vx) > swipeVelocityThreshold)
-    ) {
-      const final = Math.floor(
-        (gestureState.dx / Math.abs(gestureState.dx)) * -1,
-      )
-      Animated.timing(panX, {
-        toValue: final,
-        duration: 100,
-        useNativeDriver,
-        isInteraction: false,
-      }).start(() => {
-        onSwipeEnd?.(final)
-        panX.flattenOffset()
-        panX.setValue(0)
-      })
-    } else {
-      onSwipeEnd?.(0)
-      Animated.timing(panX, {
-        toValue: 0,
-        duration: 100,
-        useNativeDriver,
-        isInteraction: false,
-      }).start(() => {
-        panX.flattenOffset()
-        panX.setValue(0)
-      })
-    }
-  }
-
-  const panResponder = PanResponder.create({
-    onMoveShouldSetPanResponder: canMoveScreen,
-    onPanResponderGrant: startGesture,
-    onPanResponderMove: respondToGesture,
-    onPanResponderTerminate: finishGesture,
-    onPanResponderRelease: finishGesture,
-    onPanResponderTerminationRequest: () => !hasPriority,
-  })
-
-  return (
-    <View {...panResponder.panHandlers} style={s.h100pct}>
-      {children}
-    </View>
-  )
-}
diff --git a/src/view/com/util/gestures/SwipeAndZoom.tsx b/src/view/com/util/gestures/SwipeAndZoom.tsx
deleted file mode 100644
index 75c679012..000000000
--- a/src/view/com/util/gestures/SwipeAndZoom.tsx
+++ /dev/null
@@ -1,302 +0,0 @@
-import React, {useState} from 'react'
-import {
-  Animated,
-  GestureResponderEvent,
-  I18nManager,
-  PanResponder,
-  PanResponderGestureState,
-  useWindowDimensions,
-  View,
-} from 'react-native'
-import {clamp} from 'lodash'
-import {s} from 'lib/styles'
-
-export enum Dir {
-  None,
-  Up,
-  Down,
-  Left,
-  Right,
-  Zoom,
-}
-
-interface Props {
-  panX: Animated.Value
-  panY: Animated.Value
-  zoom: Animated.Value
-  canSwipeLeft?: boolean
-  canSwipeRight?: boolean
-  canSwipeUp?: boolean
-  canSwipeDown?: boolean
-  swipeEnabled?: boolean
-  zoomEnabled?: boolean
-  hasPriority?: boolean // if has priority, will not release control of the gesture to another gesture
-  horzDistThresholdDivisor?: number
-  vertDistThresholdDivisor?: number
-  useNativeDriver?: boolean
-  onSwipeStart?: () => void
-  onSwipeStartDirection?: (dir: Dir) => void
-  onSwipeEnd?: (dir: Dir) => void
-  children: React.ReactNode
-}
-
-export function SwipeAndZoom({
-  panX,
-  panY,
-  zoom,
-  canSwipeLeft = false,
-  canSwipeRight = false,
-  canSwipeUp = false,
-  canSwipeDown = false,
-  swipeEnabled = false,
-  zoomEnabled = false,
-  hasPriority = false,
-  horzDistThresholdDivisor = 1.75,
-  vertDistThresholdDivisor = 1.75,
-  useNativeDriver = false,
-  onSwipeStart,
-  onSwipeStartDirection,
-  onSwipeEnd,
-  children,
-}: Props) {
-  const winDim = useWindowDimensions()
-  const [dir, setDir] = useState<Dir>(Dir.None)
-  const [initialDistance, setInitialDistance] = useState<number | undefined>(
-    undefined,
-  )
-
-  const swipeVelocityThreshold = 35
-  const swipeHorzDistanceThreshold = winDim.width / horzDistThresholdDivisor
-  const swipeVertDistanceThreshold = winDim.height / vertDistThresholdDivisor
-
-  const isMovingHorizontally = (
-    _: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    return (
-      Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 1.25) &&
-      Math.abs(gestureState.vx) > Math.abs(gestureState.vy * 1.25)
-    )
-  }
-  const isMovingVertically = (
-    _: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    return (
-      Math.abs(gestureState.dy) > Math.abs(gestureState.dx * 1.25) &&
-      Math.abs(gestureState.vy) > Math.abs(gestureState.vx * 1.25)
-    )
-  }
-
-  const canDir = (d: Dir) => {
-    if (d === Dir.Left) {
-      return canSwipeLeft
-    }
-    if (d === Dir.Right) {
-      return canSwipeRight
-    }
-    if (d === Dir.Up) {
-      return canSwipeUp
-    }
-    if (d === Dir.Down) {
-      return canSwipeDown
-    }
-    if (d === Dir.Zoom) {
-      return zoomEnabled
-    }
-    return false
-  }
-  const isHorz = (d: Dir) => d === Dir.Left || d === Dir.Right
-  const isVert = (d: Dir) => d === Dir.Up || d === Dir.Down
-
-  const canMoveScreen = (
-    event: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    if (zoomEnabled && gestureState.numberActiveTouches === 2) {
-      return true
-    } else if (swipeEnabled && gestureState.numberActiveTouches === 1) {
-      const dx = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
-      const dy = gestureState.dy
-      const willHandle =
-        (isMovingHorizontally(event, gestureState) &&
-          ((dx > 0 && canSwipeLeft) || (dx < 0 && canSwipeRight))) ||
-        (isMovingVertically(event, gestureState) &&
-          ((dy > 0 && canSwipeUp) || (dy < 0 && canSwipeDown)))
-      return willHandle
-    }
-    return false
-  }
-
-  const startGesture = () => {
-    setDir(Dir.None)
-    onSwipeStart?.()
-
-    // reset all state
-    panX.stopAnimation()
-    // @ts-expect-error: _value is private, but docs use it as well
-    panX.setOffset(panX._value)
-    panY.stopAnimation()
-    // @ts-expect-error: _value is private, but docs use it as well
-    panY.setOffset(panY._value)
-    zoom.stopAnimation()
-    // @ts-expect-error: _value is private, but docs use it as well
-    zoom.setOffset(zoom._value)
-    setInitialDistance(undefined)
-  }
-
-  const respondToGesture = (
-    e: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    const dx = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
-    const dy = gestureState.dy
-
-    let newDir = Dir.None
-    if (dir === Dir.None) {
-      // establish if the user is swiping horz or vert, or zooming
-      if (gestureState.numberActiveTouches === 2) {
-        newDir = Dir.Zoom
-      } else if (Math.abs(dx) > Math.abs(dy)) {
-        newDir = dx > 0 ? Dir.Left : Dir.Right
-      } else {
-        newDir = dy > 0 ? Dir.Up : Dir.Down
-      }
-    } else if (isHorz(dir)) {
-      // direction update
-      newDir = dx > 0 ? Dir.Left : Dir.Right
-    } else if (isVert(dir)) {
-      // direction update
-      newDir = dy > 0 ? Dir.Up : Dir.Down
-    } else {
-      newDir = dir
-    }
-
-    if (newDir === Dir.Zoom) {
-      if (zoomEnabled) {
-        if (gestureState.numberActiveTouches === 2) {
-          // zoom in/out
-          const x0 = e.nativeEvent.touches[0].pageX
-          const x1 = e.nativeEvent.touches[1].pageX
-          const y0 = e.nativeEvent.touches[0].pageY
-          const y1 = e.nativeEvent.touches[1].pageY
-          const zoomDx = Math.abs(x0 - x1)
-          const zoomDy = Math.abs(y0 - y1)
-          const dist = Math.sqrt(zoomDx * zoomDx + zoomDy * zoomDy) / 100
-          if (
-            typeof initialDistance === 'undefined' ||
-            dist - initialDistance < 0
-          ) {
-            setInitialDistance(dist)
-          } else {
-            zoom.setValue(dist - initialDistance)
-          }
-        } else {
-          // pan around after zooming
-          panX.setValue(clamp(dx / winDim.width, -1, 1) * -1)
-          panY.setValue(clamp(dy / winDim.height, -1, 1) * -1)
-        }
-      }
-    } else if (isHorz(newDir)) {
-      // swipe left/right
-      panX.setValue(
-        clamp(
-          dx / swipeHorzDistanceThreshold,
-          canSwipeRight ? -1 : 0,
-          canSwipeLeft ? 1 : 0,
-        ) * -1,
-      )
-      panY.setValue(0)
-    } else if (isVert(newDir)) {
-      // swipe up/down
-      panY.setValue(
-        clamp(
-          dy / swipeVertDistanceThreshold,
-          canSwipeDown ? -1 : 0,
-          canSwipeUp ? 1 : 0,
-        ) * -1,
-      )
-      panX.setValue(0)
-    }
-
-    if (!canDir(newDir)) {
-      newDir = Dir.None
-    }
-    if (newDir !== dir) {
-      setDir(newDir)
-      onSwipeStartDirection?.(newDir)
-    }
-  }
-
-  const finishGesture = (
-    _: GestureResponderEvent,
-    gestureState: PanResponderGestureState,
-  ) => {
-    const finish = (finalDir: Dir) => () => {
-      if (finalDir !== Dir.None) {
-        onSwipeEnd?.(finalDir)
-      }
-      setDir(Dir.None)
-      panX.flattenOffset()
-      panX.setValue(0)
-      panY.flattenOffset()
-      panY.setValue(0)
-    }
-    if (
-      isHorz(dir) &&
-      (Math.abs(gestureState.dx) > swipeHorzDistanceThreshold / 4 ||
-        Math.abs(gestureState.vx) > swipeVelocityThreshold)
-    ) {
-      // horizontal swipe reset
-      Animated.timing(panX, {
-        toValue: dir === Dir.Left ? -1 : 1,
-        duration: 100,
-        useNativeDriver,
-      }).start(finish(dir))
-    } else if (
-      isVert(dir) &&
-      (Math.abs(gestureState.dy) > swipeVertDistanceThreshold / 8 ||
-        Math.abs(gestureState.vy) > swipeVelocityThreshold)
-    ) {
-      // vertical swipe reset
-      Animated.timing(panY, {
-        toValue: dir === Dir.Up ? -1 : 1,
-        duration: 100,
-        useNativeDriver,
-      }).start(finish(dir))
-    } else {
-      // zoom (or no direction) reset
-      onSwipeEnd?.(Dir.None)
-      Animated.timing(panX, {
-        toValue: 0,
-        duration: 100,
-        useNativeDriver,
-      }).start()
-      Animated.timing(panY, {
-        toValue: 0,
-        duration: 100,
-        useNativeDriver,
-      }).start()
-      Animated.timing(zoom, {
-        toValue: 0,
-        duration: 100,
-        useNativeDriver,
-      }).start()
-    }
-  }
-
-  const panResponder = PanResponder.create({
-    onMoveShouldSetPanResponder: canMoveScreen,
-    onPanResponderGrant: startGesture,
-    onPanResponderMove: respondToGesture,
-    onPanResponderTerminate: finishGesture,
-    onPanResponderRelease: finishGesture,
-    onPanResponderTerminationRequest: () => !hasPriority,
-  })
-
-  return (
-    <View {...panResponder.panHandlers} style={s.h100pct}>
-      {children}
-    </View>
-  )
-}
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 3b4c47ce1..cd6c72ff5 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -18,7 +18,6 @@ import {EmptyState} from '../com/util/EmptyState'
 import {Text} from '../com/util/text/Text'
 import {FAB} from '../com/util/fab/FAB'
 import {s, colors} from 'lib/styles'
-import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useAnalytics} from 'lib/analytics'
 import {ComposeIcon2} from 'lib/icons'
 
@@ -32,7 +31,6 @@ export const ProfileScreen = withAuthRequired(
       screen('Profile')
     }, [screen])
 
-    const onMainScroll = useOnMainScroll(store)
     const [hasSetup, setHasSetup] = useState<boolean>(false)
     const uiState = React.useMemo(
       () => new ProfileUiModel(store, {user: route.params.name}),
@@ -68,9 +66,12 @@ export const ProfileScreen = withAuthRequired(
       track('ProfileScreen:PressCompose')
       store.shell.openComposer({})
     }, [store, track])
-    const onSelectView = (index: number) => {
-      uiState.setSelectedViewIndex(index)
-    }
+    const onSelectView = React.useCallback(
+      (index: number) => {
+        uiState.setSelectedViewIndex(index)
+      },
+      [uiState],
+    )
     const onRefresh = React.useCallback(() => {
       uiState
         .refresh()
@@ -158,7 +159,6 @@ export const ProfileScreen = withAuthRequired(
             ListFooterComponent={Footer}
             refreshing={uiState.isRefreshing || false}
             onSelectView={onSelectView}
-            onScroll={onMainScroll}
             onRefresh={onRefresh}
             onEndReached={onEndReached}
           />