import LottieView from 'lottie-react-native' import React, { useState } from 'react' import { Dimensions, LayoutChangeEvent, StyleSheet, Text, TouchableWithoutFeedback, View, } from 'react-native' import { PanGestureHandler } from 'react-native-gesture-handler' import Animated, { runOnJS, useAnimatedGestureHandler, useAnimatedStyle, useSharedValue, withSpring, } from 'react-native-reanimated' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useShowOrHideAnimation } from 'src/components/useShowOrHideAnimation' import Colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import { Spacing } from 'src/styles/styles' import confettiAnimation from './confettiAnimation.json' interface Props { showAnimation: boolean title?: string | null description: string | null onAnimationFinish(): void onDismiss?(): void } const ANIMATION_DURATION = 6000 // 6 seconds const ConfettiCelebration = ({ showAnimation, title, description, onAnimationFinish, onDismiss, }: Props) => { const [isVisible, setIsVisible] = useState(showAnimation) const window = Dimensions.get('window') const safeInitialHeight = Math.max(window.width, window.height) const [notificationHeight, setNotificationHeight] = useState(safeInitialHeight) const { top } = useSafeAreaInsets() const positionStyle = { top } const slidingHeight = top + notificationHeight const progress = useSharedValue(0) const animatedTransform = useAnimatedStyle(() => { return { transform: [{ translateY: -(1 - progress.value) * slidingHeight }], } }) const animatedBackdropOpacity = useAnimatedStyle(() => ({ opacity: 0.5 * progress.value, })) const animatedConfettiOpacity = useAnimatedStyle(() => ({ opacity: progress.value, })) const handleGesture = useAnimatedGestureHandler({ onStart: (_, ctx: { initialProgress: number }) => { ctx.initialProgress = progress.value }, onActive: (event, ctx) => { if (event.translationY > 0 /* wrong direction */) { const dampedTranslation = Math.sqrt(Math.abs(event.translationY)) progress.value = ctx.initialProgress + dampedTranslation / slidingHeight } else { progress.value = ctx.initialProgress + event.translationY / slidingHeight } }, onEnd: (event) => { const dismissThreshold = 0.33 * notificationHeight const translationY = Math.abs(event.translationY) if (onDismiss && translationY > dismissThreshold) { runOnJS(onDismiss)() } else { progress.value = withSpring(1) } }, }) useShowOrHideAnimation( progress, showAnimation, () => { setIsVisible(true) }, () => { setIsVisible(false) } ) if (!isVisible) { return null } const handleLayout = (event: LayoutChangeEvent) => { setNotificationHeight(event.nativeEvent.layout.height) } return ( <> {!!title && {title}} {description} ) } const styles = StyleSheet.create({ modal: { ...StyleSheet.absoluteFillObject, }, backdrop: { backgroundColor: Colors.backgroundScrim, }, confetti: { ...StyleSheet.absoluteFillObject, pointerEvents: 'none', }, notificationContainer: { position: 'absolute', width: '100%', }, notification: { margin: Spacing.Regular16, padding: Spacing.Regular16, borderRadius: Spacing.Regular16, backgroundColor: Colors.info, }, titleText: { ...typeScale.labelSemiBoldSmall, marginBottom: Spacing.Tiny4, }, descriptionText: { ...typeScale.bodyXSmall, }, }) export default ConfettiCelebration