Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 153 lines 3.6 kB view raw
1import {useEffect, useRef, useState} 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 {atoms as a} from '#/alf' 12 13const animationConfig = { 14 duration: 400, 15 easing: Easing.out(Easing.cubic), 16} 17 18function EnteringUp() { 19 'worklet' 20 const animations = { 21 opacity: withTiming(1, animationConfig), 22 transform: [{translateY: withTiming(0, animationConfig)}], 23 } 24 const initialValues = { 25 opacity: 0, 26 transform: [{translateY: 18}], 27 } 28 return { 29 animations, 30 initialValues, 31 } 32} 33 34function EnteringDown() { 35 'worklet' 36 const animations = { 37 opacity: withTiming(1, animationConfig), 38 transform: [{translateY: withTiming(0, animationConfig)}], 39 } 40 const initialValues = { 41 opacity: 0, 42 transform: [{translateY: -18}], 43 } 44 return { 45 animations, 46 initialValues, 47 } 48} 49 50function ExitingUp() { 51 'worklet' 52 const animations = { 53 opacity: withTiming(0, animationConfig), 54 transform: [ 55 { 56 translateY: withTiming(-18, animationConfig), 57 }, 58 ], 59 } 60 const initialValues = { 61 opacity: 1, 62 transform: [{translateY: 0}], 63 } 64 return { 65 animations, 66 initialValues, 67 } 68} 69 70function ExitingDown() { 71 'worklet' 72 const animations = { 73 opacity: withTiming(0, animationConfig), 74 transform: [{translateY: withTiming(18, animationConfig)}], 75 } 76 const initialValues = { 77 opacity: 1, 78 transform: [{translateY: 0}], 79 } 80 return { 81 animations, 82 initialValues, 83 } 84} 85 86export function CountWheel({ 87 count, 88 isToggled, 89 hasBeenToggled, 90 renderCount, 91}: { 92 count: number 93 isToggled: boolean 94 hasBeenToggled: boolean 95 renderCount: (props: {count: number}) => React.ReactNode 96}) { 97 const shouldAnimate = !useReducedMotion() && hasBeenToggled 98 const shouldRoll = decideShouldRoll(isToggled, count) 99 100 // Incrementing the key will cause the `Animated.View` to re-render, with the newly selected entering/exiting 101 // animation 102 // The initial entering/exiting animations will get skipped, since these will happen on screen mounts and would 103 // be unnecessary 104 const [key, setKey] = useState(0) 105 const [prevCount, setPrevCount] = useState(count) 106 const prevIsToggled = useRef(isToggled) 107 108 useEffect(() => { 109 if (isToggled === prevIsToggled.current) { 110 return 111 } 112 113 const newPrevCount = isToggled ? count - 1 : count + 1 114 setKey(prev => prev + 1) 115 setPrevCount(newPrevCount) 116 prevIsToggled.current = isToggled 117 }, [isToggled, count]) 118 119 const enteringAnimation = 120 shouldAnimate && shouldRoll 121 ? isToggled 122 ? EnteringUp 123 : EnteringDown 124 : undefined 125 const exitingAnimation = 126 shouldAnimate && shouldRoll 127 ? isToggled 128 ? ExitingUp 129 : ExitingDown 130 : undefined 131 132 return ( 133 <LayoutAnimationConfig skipEntering skipExiting> 134 {count > 0 ? ( 135 <View style={[a.justify_center]}> 136 <Animated.View entering={enteringAnimation} key={key}> 137 {renderCount({count})} 138 </Animated.View> 139 {shouldAnimate && (count > 1 || !isToggled) ? ( 140 <Animated.View 141 entering={exitingAnimation} 142 // Add 2 to the key so there are never duplicates 143 key={key + 2} 144 style={[a.absolute, {width: 50, opacity: 0}]} 145 aria-disabled={true}> 146 {renderCount({count: prevCount})} 147 </Animated.View> 148 ) : null} 149 </View> 150 ) : null} 151 </LayoutAnimationConfig> 152 ) 153}