mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View} from 'react-native'
3import Animated, {
4 Easing,
5 LayoutAnimationConfig,
6 useReducedMotion,
7 withTiming,
8} from 'react-native-reanimated'
9import {i18n} from '@lingui/core'
10
11import {decideShouldRoll} from '#/lib/custom-animations/util'
12import {s} from '#/lib/styles'
13import {formatCount} from '#/view/com/util/numeric/format'
14import {Text} from '#/view/com/util/text/Text'
15import {atoms as a, useTheme} from '#/alf'
16
17const animationConfig = {
18 duration: 400,
19 easing: Easing.out(Easing.cubic),
20}
21
22function EnteringUp() {
23 'worklet'
24 const animations = {
25 opacity: withTiming(1, animationConfig),
26 transform: [{translateY: withTiming(0, animationConfig)}],
27 }
28 const initialValues = {
29 opacity: 0,
30 transform: [{translateY: 18}],
31 }
32 return {
33 animations,
34 initialValues,
35 }
36}
37
38function EnteringDown() {
39 'worklet'
40 const animations = {
41 opacity: withTiming(1, animationConfig),
42 transform: [{translateY: withTiming(0, animationConfig)}],
43 }
44 const initialValues = {
45 opacity: 0,
46 transform: [{translateY: -18}],
47 }
48 return {
49 animations,
50 initialValues,
51 }
52}
53
54function ExitingUp() {
55 'worklet'
56 const animations = {
57 opacity: withTiming(0, animationConfig),
58 transform: [
59 {
60 translateY: withTiming(-18, animationConfig),
61 },
62 ],
63 }
64 const initialValues = {
65 opacity: 1,
66 transform: [{translateY: 0}],
67 }
68 return {
69 animations,
70 initialValues,
71 }
72}
73
74function ExitingDown() {
75 'worklet'
76 const animations = {
77 opacity: withTiming(0, animationConfig),
78 transform: [{translateY: withTiming(18, animationConfig)}],
79 }
80 const initialValues = {
81 opacity: 1,
82 transform: [{translateY: 0}],
83 }
84 return {
85 animations,
86 initialValues,
87 }
88}
89
90export function CountWheel({
91 likeCount,
92 big,
93 isLiked,
94 hasBeenToggled,
95}: {
96 likeCount: number
97 big?: boolean
98 isLiked: boolean
99 hasBeenToggled: boolean
100}) {
101 const t = useTheme()
102 const shouldAnimate = !useReducedMotion() && hasBeenToggled
103 const shouldRoll = decideShouldRoll(isLiked, likeCount)
104
105 // Incrementing the key will cause the `Animated.View` to re-render, with the newly selected entering/exiting
106 // animation
107 // The initial entering/exiting animations will get skipped, since these will happen on screen mounts and would
108 // be unnecessary
109 const [key, setKey] = React.useState(0)
110 const [prevCount, setPrevCount] = React.useState(likeCount)
111 const prevIsLiked = React.useRef(isLiked)
112 const formattedCount = formatCount(i18n, likeCount)
113 const formattedPrevCount = formatCount(i18n, prevCount)
114
115 React.useEffect(() => {
116 if (isLiked === prevIsLiked.current) {
117 return
118 }
119
120 const newPrevCount = isLiked ? likeCount - 1 : likeCount + 1
121 setKey(prev => prev + 1)
122 setPrevCount(newPrevCount)
123 prevIsLiked.current = isLiked
124 }, [isLiked, likeCount])
125
126 const enteringAnimation =
127 shouldAnimate && shouldRoll
128 ? isLiked
129 ? EnteringUp
130 : EnteringDown
131 : undefined
132 const exitingAnimation =
133 shouldAnimate && shouldRoll
134 ? isLiked
135 ? ExitingUp
136 : ExitingDown
137 : undefined
138
139 return (
140 <LayoutAnimationConfig skipEntering skipExiting>
141 {likeCount > 0 ? (
142 <View style={[a.justify_center]}>
143 <Animated.View entering={enteringAnimation} key={key}>
144 <Text
145 testID="likeCount"
146 style={[
147 big ? a.text_md : {fontSize: 15},
148 a.user_select_none,
149 isLiked
150 ? [a.font_bold, s.likeColor]
151 : {color: t.palette.contrast_500},
152 ]}>
153 {formattedCount}
154 </Text>
155 </Animated.View>
156 {shouldAnimate && (likeCount > 1 || !isLiked) ? (
157 <Animated.View
158 entering={exitingAnimation}
159 // Add 2 to the key so there are never duplicates
160 key={key + 2}
161 style={[a.absolute, {width: 50, opacity: 0}]}
162 aria-disabled={true}>
163 <Text
164 style={[
165 big ? a.text_md : {fontSize: 15},
166 a.user_select_none,
167 isLiked
168 ? [a.font_bold, s.likeColor]
169 : {color: t.palette.contrast_500},
170 ]}>
171 {formattedPrevCount}
172 </Text>
173 </Animated.View>
174 ) : null}
175 </View>
176 ) : null}
177 </LayoutAnimationConfig>
178 )
179}