Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 3x 10x 10x 10x 4x 10x 4x 3x 3x 3x 1x 10x 10x 10x 9x 2x 10x 10x 10x 6x | import {
SharedValue,
useAnimatedReaction,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { ANIMATION_DURATION_SHORT, EASING_OUT_CUBIC } from '@/constants/animation';
import { MASCOT_DRAG_FADE_FULL, MASCOT_DRAG_FADE_START } from '../constants';
interface UseImageAnimationProps {
imageAnimationProgress?: SharedValue<number>;
translateX: SharedValue<number>;
translateY: SharedValue<number>;
currentIndex: number;
index: number;
isCurrentItemNewRequest?: boolean;
}
export const useImageAnimation = ({
imageAnimationProgress,
translateX,
translateY,
currentIndex,
index,
isCurrentItemNewRequest,
}: UseImageAnimationProps) => {
const wasDragging = useSharedValue(false);
// Real-time drag: fade mascot out proportional to drag distance
// On snap-back (return to rest after drag): spring mascot back in
useAnimatedReaction(
() => {
if (!imageAnimationProgress || currentIndex !== index || isCurrentItemNewRequest) return null;
return Math.max(Math.abs(translateX.value), Math.abs(translateY.value));
},
dragDist => {
if (dragDist === null || !imageAnimationProgress) return;
if (dragDist > MASCOT_DRAG_FADE_START) {
wasDragging.value = true;
const t = Math.min(
1,
(dragDist - MASCOT_DRAG_FADE_START) / (MASCOT_DRAG_FADE_FULL - MASCOT_DRAG_FADE_START),
);
imageAnimationProgress.value = 1 - t;
} else Iif (dragDist <= 2 && wasDragging.value) {
// Card returned to rest after a drag (snap-back complete)
wasDragging.value = false;
imageAnimationProgress.value = withTiming(1, {
duration: ANIMATION_DURATION_SHORT,
easing: EASING_OUT_CUBIC,
});
}
},
[currentIndex, index, isCurrentItemNewRequest],
);
// Loop card on top: hide mascot
useAnimatedReaction(
() => currentIndex === index && !!isCurrentItemNewRequest,
(isLoopTop, wasLoopTop) => {
if (!imageAnimationProgress) return;
if (isLoopTop && !wasLoopTop) {
imageAnimationProgress.value = withTiming(0, {
duration: ANIMATION_DURATION_SHORT,
easing: EASING_OUT_CUBIC,
});
}
},
[currentIndex, index, isCurrentItemNewRequest],
);
// Re-show mascot when returning to a regular card after a loop card
useAnimatedReaction(
() => currentIndex === index && !isCurrentItemNewRequest,
(isRegularTop, wasRegularTop) => {
if (!imageAnimationProgress || wasDragging.value) return;
Iif (isRegularTop && !wasRegularTop && imageAnimationProgress.value < 1) {
imageAnimationProgress.value = withTiming(1, {
duration: ANIMATION_DURATION_SHORT,
easing: EASING_OUT_CUBIC,
});
}
},
[currentIndex, index, isCurrentItemNewRequest],
);
};
|