forked from
jollywhoppers.com/witchsky.app
fork
Configure Feed
Select the types of activity you want to include in your feed.
Bluesky app fork with some witchin' additions 馃挮
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}