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
This commit is contained in:
parent
eb6b36be61
commit
d35f7c1f1a
11 changed files with 273 additions and 594 deletions
|
@ -128,7 +128,7 @@ const styles = StyleSheet.create({
|
|||
width: 24,
|
||||
height: 24,
|
||||
bottom: 8,
|
||||
right: 8,
|
||||
right: 24,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue