forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React from 'react'
2import {View} from 'react-native'
3import {useReducedMotion} from 'react-native-reanimated'
4
5import {s} from '#/lib/styles'
6import {useTheme} from '#/alf'
7import {
8 Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled,
9 Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
10} from '#/components/icons/Heart2'
11
12const animationConfig = {
13 duration: 600,
14 easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
15 fill: 'forwards' as FillMode,
16}
17
18const keyframe = [
19 {transform: 'scale(1)'},
20 {transform: 'scale(0.7)'},
21 {transform: 'scale(1.2)'},
22 {transform: 'scale(1)'},
23]
24
25const circle1Keyframe = [
26 {opacity: 0, transform: 'scale(0)'},
27 {opacity: 0.4},
28 {transform: 'scale(1.5)'},
29 {opacity: 0.4},
30 {opacity: 0, transform: 'scale(1.5)'},
31]
32
33const circle2Keyframe = [
34 {opacity: 0, transform: 'scale(0)'},
35 {opacity: 1},
36 {transform: 'scale(0)'},
37 {opacity: 1},
38 {opacity: 0, transform: 'scale(1.5)'},
39]
40
41export function AnimatedLikeIcon({
42 isLiked,
43 big,
44 hasBeenToggled,
45}: {
46 isLiked: boolean
47 big?: boolean
48 hasBeenToggled: boolean
49}) {
50 const t = useTheme()
51 const size = big ? 22 : 18
52 const shouldAnimate = !useReducedMotion() && hasBeenToggled
53 const prevIsLiked = React.useRef(isLiked)
54
55 const likeIconRef = React.useRef<HTMLDivElement>(null)
56 const circle1Ref = React.useRef<HTMLDivElement>(null)
57 const circle2Ref = React.useRef<HTMLDivElement>(null)
58
59 React.useEffect(() => {
60 if (prevIsLiked.current === isLiked) {
61 return
62 }
63
64 if (shouldAnimate && isLiked) {
65 likeIconRef.current?.animate?.(keyframe, animationConfig)
66 circle1Ref.current?.animate?.(circle1Keyframe, animationConfig)
67 circle2Ref.current?.animate?.(circle2Keyframe, animationConfig)
68 }
69 prevIsLiked.current = isLiked
70 }, [shouldAnimate, isLiked])
71
72 return (
73 <View>
74 {isLiked ? (
75 // @ts-expect-error is div
76 <View ref={likeIconRef}>
77 <HeartIconFilled style={s.likeColor} width={size} />
78 </View>
79 ) : (
80 <HeartIconOutline
81 style={[{color: t.palette.contrast_500}, {pointerEvents: 'none'}]}
82 width={size}
83 />
84 )}
85 <View
86 // @ts-expect-error is div
87 ref={circle1Ref}
88 style={{
89 position: 'absolute',
90 backgroundColor: s.likeColor.color,
91 top: 0,
92 left: 0,
93 width: size,
94 height: size,
95 zIndex: -1,
96 pointerEvents: 'none',
97 borderRadius: size / 2,
98 opacity: 0,
99 }}
100 />
101 <View
102 // @ts-expect-error is div
103 ref={circle2Ref}
104 style={{
105 position: 'absolute',
106 backgroundColor: t.atoms.bg.backgroundColor,
107 top: 0,
108 left: 0,
109 width: size,
110 height: size,
111 zIndex: -1,
112 pointerEvents: 'none',
113 borderRadius: size / 2,
114 opacity: 0,
115 }}
116 />
117 </View>
118 )
119}