Bluesky app fork with some witchin' additions 馃挮
at main 179 lines 4.6 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import Animated, { 4 Easing, 5 LayoutAnimationConfig, 6 useReducedMotion, 7 withTiming, 8} from 'react-native-reanimated' 9 10import {decideShouldRoll} from '#/lib/custom-animations/util' 11import {s} from '#/lib/styles' 12import {Text} from '#/view/com/util/text/Text' 13import {atoms as a, useTheme} from '#/alf' 14import {useFormatPostStatCount} from '#/components/PostControls/util' 15 16const animationConfig = { 17 duration: 400, 18 easing: Easing.out(Easing.cubic), 19} 20 21function EnteringUp() { 22 'worklet' 23 const animations = { 24 opacity: withTiming(1, animationConfig), 25 transform: [{translateY: withTiming(0, animationConfig)}], 26 } 27 const initialValues = { 28 opacity: 0, 29 transform: [{translateY: 18}], 30 } 31 return { 32 animations, 33 initialValues, 34 } 35} 36 37function EnteringDown() { 38 'worklet' 39 const animations = { 40 opacity: withTiming(1, animationConfig), 41 transform: [{translateY: withTiming(0, animationConfig)}], 42 } 43 const initialValues = { 44 opacity: 0, 45 transform: [{translateY: -18}], 46 } 47 return { 48 animations, 49 initialValues, 50 } 51} 52 53function ExitingUp() { 54 'worklet' 55 const animations = { 56 opacity: withTiming(0, animationConfig), 57 transform: [ 58 { 59 translateY: withTiming(-18, animationConfig), 60 }, 61 ], 62 } 63 const initialValues = { 64 opacity: 1, 65 transform: [{translateY: 0}], 66 } 67 return { 68 animations, 69 initialValues, 70 } 71} 72 73function ExitingDown() { 74 'worklet' 75 const animations = { 76 opacity: withTiming(0, animationConfig), 77 transform: [{translateY: withTiming(18, animationConfig)}], 78 } 79 const initialValues = { 80 opacity: 1, 81 transform: [{translateY: 0}], 82 } 83 return { 84 animations, 85 initialValues, 86 } 87} 88 89export function CountWheel({ 90 likeCount, 91 big, 92 isLiked, 93 hasBeenToggled, 94}: { 95 likeCount: number 96 big?: boolean 97 isLiked: boolean 98 hasBeenToggled: boolean 99}) { 100 const t = useTheme() 101 const shouldAnimate = !useReducedMotion() && hasBeenToggled 102 const shouldRoll = decideShouldRoll(isLiked, likeCount) 103 104 // Incrementing the key will cause the `Animated.View` to re-render, with the newly selected entering/exiting 105 // animation 106 // The initial entering/exiting animations will get skipped, since these will happen on screen mounts and would 107 // be unnecessary 108 const [key, setKey] = React.useState(0) 109 const [prevCount, setPrevCount] = React.useState(likeCount) 110 const prevIsLiked = React.useRef(isLiked) 111 const formatPostStatCount = useFormatPostStatCount() 112 const formattedCount = formatPostStatCount(likeCount) 113 const formattedPrevCount = formatPostStatCount(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 : a.text_sm, 148 a.user_select_none, 149 isLiked 150 ? [a.font_semi_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 : a.text_sm, 166 a.user_select_none, 167 isLiked 168 ? [a.font_semi_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}