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