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 {decideShouldRoll} from '#/lib/custom-animations/util'
6import {s} from '#/lib/styles'
7import {Text} from '#/view/com/util/text/Text'
8import {atoms as a, useTheme} from '#/alf'
9import {useFormatPostStatCount} from '#/components/PostControls/util'
10
11const animationConfig = {
12 duration: 400,
13 easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
14 fill: 'forwards' as FillMode,
15}
16
17const enteringUpKeyframe = [
18 {opacity: 0, transform: 'translateY(18px)'},
19 {opacity: 1, transform: 'translateY(0)'},
20]
21
22const enteringDownKeyframe = [
23 {opacity: 0, transform: 'translateY(-18px)'},
24 {opacity: 1, transform: 'translateY(0)'},
25]
26
27const exitingUpKeyframe = [
28 {opacity: 1, transform: 'translateY(0)'},
29 {opacity: 0, transform: 'translateY(-18px)'},
30]
31
32const exitingDownKeyframe = [
33 {opacity: 1, transform: 'translateY(0)'},
34 {opacity: 0, transform: 'translateY(18px)'},
35]
36
37export function CountWheel({
38 likeCount,
39 big,
40 isLiked,
41 hasBeenToggled,
42}: {
43 likeCount: number
44 big?: boolean
45 isLiked: boolean
46 hasBeenToggled: boolean
47}) {
48 const t = useTheme()
49 const shouldAnimate = !useReducedMotion() && hasBeenToggled
50 const shouldRoll = decideShouldRoll(isLiked, likeCount)
51
52 const countView = React.useRef<HTMLDivElement>(null)
53 const prevCountView = React.useRef<HTMLDivElement>(null)
54
55 const [prevCount, setPrevCount] = React.useState(likeCount)
56 const prevIsLiked = React.useRef(isLiked)
57 const formatPostStatCount = useFormatPostStatCount()
58 const formattedCount = formatPostStatCount(likeCount)
59 const formattedPrevCount = formatPostStatCount(prevCount)
60
61 React.useEffect(() => {
62 if (isLiked === prevIsLiked.current) {
63 return
64 }
65
66 const newPrevCount = isLiked ? likeCount - 1 : likeCount + 1
67 if (shouldAnimate && shouldRoll) {
68 countView.current?.animate?.(
69 isLiked ? enteringUpKeyframe : enteringDownKeyframe,
70 animationConfig,
71 )
72 prevCountView.current?.animate?.(
73 isLiked ? exitingUpKeyframe : exitingDownKeyframe,
74 animationConfig,
75 )
76 setPrevCount(newPrevCount)
77 }
78 prevIsLiked.current = isLiked
79 }, [isLiked, likeCount, shouldAnimate, shouldRoll])
80
81 if (likeCount < 1) {
82 return null
83 }
84
85 return (
86 <View>
87 <View
88 // @ts-expect-error is div
89 ref={countView}>
90 <Text
91 testID="likeCount"
92 style={[
93 big ? a.text_md : a.text_sm,
94 a.user_select_none,
95 isLiked
96 ? [a.font_semi_bold, s.likeColor]
97 : {color: t.palette.contrast_500},
98 ]}>
99 {formattedCount}
100 </Text>
101 </View>
102 {shouldAnimate && (likeCount > 1 || !isLiked) ? (
103 <View
104 style={{position: 'absolute', opacity: 0}}
105 aria-disabled={true}
106 // @ts-expect-error is div
107 ref={prevCountView}>
108 <Text
109 style={[
110 big ? a.text_md : a.text_sm,
111 a.user_select_none,
112 isLiked
113 ? [a.font_semi_bold, s.likeColor]
114 : {color: t.palette.contrast_500},
115 ]}>
116 {formattedPrevCount}
117 </Text>
118 </View>
119 ) : null}
120 </View>
121 )
122}