BlueWallet/components/react-native-draggable-flatlist/procs.ts
Marcos Rodriguez Vélez 1c9fa414eb
GestureHandler
2022-01-26 10:54:32 -05:00

222 lines
8.1 KiB
TypeScript

import Animated, { clockRunning, not, startClock, stopClock } from 'react-native-reanimated';
import { isWeb } from './constants';
const { set, cond, add, sub, block, eq, neq, and, divide, greaterThan, greaterOrEq, Value, spring, lessThan, lessOrEq, multiply } =
Animated;
if (!Animated.proc) {
throw new Error('Incompatible Reanimated version (proc not found)');
}
// clock procs don't seem to work in web, not sure if there's a perf benefit to web procs anyway?
const proc = isWeb ? <T>(cb: T) => cb : Animated.proc;
export const getIsAfterActive = proc((currentIndex: Animated.Node<number>, activeIndex: Animated.Node<number>) =>
greaterThan(currentIndex, activeIndex),
);
export const hardReset = proc(
(position: Animated.Value<number>, finished: Animated.Value<number>, time: Animated.Value<number>, toValue: Animated.Value<number>) =>
block([set(position, 0), set(finished, 0), set(time, 0), set(toValue, 0)]),
);
/**
* The in react-native-reanimated.d.ts definition of `proc` only has generics
* for up to 10 arguments. We cast it to accept any params to avoid errors when
* type-checking.
*/
type RetypedProc = (cb: (...params: any) => Animated.Node<number>) => typeof cb;
export const setupCell = proc(
(
currentIndex: Animated.Value<number>,
size: Animated.Node<number>,
offset: Animated.Node<number>,
isAfterActive: Animated.Value<number>,
prevToValue: Animated.Value<number>,
prevSpacerIndex: Animated.Value<number>,
activeIndex: Animated.Node<number>,
activeCellSize: Animated.Node<number>,
hoverOffset: Animated.Node<number>,
spacerIndex: Animated.Value<number>,
toValue: Animated.Value<number>,
position: Animated.Value<number>,
time: Animated.Value<number>,
finished: Animated.Value<number>,
runSpring: Animated.Node<number>,
onFinished: Animated.Node<number>,
isDraggingCell: Animated.Node<number>,
placeholderOffset: Animated.Value<number>,
prevIsDraggingCell: Animated.Value<number>,
clock: Animated.Clock,
disabled: Animated.Node<number>,
) =>
block([
cond(
greaterThan(activeIndex, -1),
[
// Only update spacer if touch is not disabled.
// Fixes android bugs where state would update with invalid touch values on touch end.
cond(not(disabled), [
// Determine whether this cell is after the active cell in the list
set(isAfterActive, getIsAfterActive(currentIndex, activeIndex)),
// Determining spacer index is hard to visualize, see diagram: https://i.imgur.com/jRPf5t3.jpg
cond(
isAfterActive,
[
cond(
and(
greaterOrEq(add(hoverOffset, activeCellSize), offset),
lessThan(add(hoverOffset, activeCellSize), add(offset, divide(size, 2))),
),
set(spacerIndex, sub(currentIndex, 1)),
),
cond(
and(
greaterOrEq(add(hoverOffset, activeCellSize), add(offset, divide(size, 2))),
lessThan(add(hoverOffset, activeCellSize), add(offset, size)),
),
set(spacerIndex, currentIndex),
),
],
cond(lessThan(currentIndex, activeIndex), [
cond(
and(lessThan(hoverOffset, add(offset, size)), greaterOrEq(hoverOffset, add(offset, divide(size, 2)))),
set(spacerIndex, add(currentIndex, 1)),
),
cond(
and(greaterOrEq(hoverOffset, offset), lessThan(hoverOffset, add(offset, divide(size, 2)))),
set(spacerIndex, currentIndex),
),
]),
),
// Set placeholder offset
cond(eq(spacerIndex, currentIndex), [
set(placeholderOffset, cond(isAfterActive, add(sub(offset, activeCellSize), size), offset)),
]),
]),
cond(
eq(currentIndex, activeIndex),
[
// If this cell is the active cell
cond(
isDraggingCell,
[
// Set its position to the drag position
set(position, sub(hoverOffset, offset)),
],
[
// Active item, not pressed in
// Set value hovering element will snap to once released
cond(prevIsDraggingCell, [
set(toValue, sub(placeholderOffset, offset)),
// The clock starts automatically when toValue changes, however, we need to handle the
// case where the item should snap back to its original location and toValue doesn't change
cond(eq(prevToValue, toValue), [
cond(clockRunning(clock), stopClock(clock)),
set(time, 0),
set(finished, 0),
startClock(clock),
]),
]),
],
),
],
[
// Not the active item
// Translate cell down if it is before active index and active cell has passed it.
// Translate cell up if it is after the active index and active cell has passed it.
set(
toValue,
cond(
cond(isAfterActive, lessOrEq(currentIndex, spacerIndex), greaterOrEq(currentIndex, spacerIndex)),
cond(isAfterActive, multiply(activeCellSize, -1), activeCellSize),
0,
),
),
],
),
// If this cell should animate somewhere new, reset its state and start its clock
cond(neq(toValue, prevToValue), [cond(clockRunning(clock), stopClock(clock)), set(time, 0), set(finished, 0), startClock(clock)]),
cond(neq(prevSpacerIndex, spacerIndex), [
cond(eq(spacerIndex, -1), [
// Hard reset to prevent stale state bugs
cond(clockRunning(clock), stopClock(clock)),
hardReset(position, finished, time, toValue),
]),
]),
cond(finished, [onFinished, set(time, 0), set(finished, 0)]),
set(prevSpacerIndex, spacerIndex),
set(prevToValue, toValue),
set(prevIsDraggingCell, isDraggingCell),
cond(clockRunning(clock), runSpring),
],
[
// // Reset the spacer index when drag ends
cond(neq(spacerIndex, -1), set(spacerIndex, -1)),
cond(neq(position, 0), set(position, 0)),
],
),
position,
]),
);
const betterSpring = (proc as RetypedProc)(
(
finished: Animated.Value<number>,
velocity: Animated.Value<number>,
position: Animated.Value<number>,
time: Animated.Value<number>,
prevPosition: Animated.Value<number>,
toValue: Animated.Value<number>,
damping: Animated.Value<number>,
mass: Animated.Value<number>,
stiffness: Animated.Value<number>,
overshootClamping: Animated.SpringConfig['overshootClamping'],
restSpeedThreshold: Animated.Value<number>,
restDisplacementThreshold: Animated.Value<number>,
clock: Animated.Clock,
) =>
spring(
clock,
{
finished,
velocity,
position,
time,
// @ts-ignore -- https://github.com/software-mansion/react-native-reanimated/blob/master/src/animations/spring.js#L177
prevPosition,
},
{
toValue,
damping,
mass,
stiffness,
overshootClamping,
restDisplacementThreshold,
restSpeedThreshold,
},
),
);
export function springFill(clock: Animated.Clock, state: Animated.SpringState, config: Animated.SpringConfig) {
return betterSpring(
state.finished,
state.velocity,
state.position,
state.time,
new Value(0),
config.toValue,
config.damping,
config.mass,
config.stiffness,
config.overshootClamping,
config.restSpeedThreshold,
config.restDisplacementThreshold,
clock,
);
}