From 07845189caabeb7f7fdffb4ef1a8411e18ba8d4e Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 7 Apr 2026 10:52:29 +0300 Subject: [PATCH] Fix layout shift when liking a post on Android, make countwheel generic --- src/analytics/index.tsx | 1 + .../PostControls/PostControlButton.tsx | 3 + src/components/PostControls/index.tsx | 15 +++-- src/lib/custom-animations/CountWheel.tsx | 66 ++++++------------- src/lib/custom-animations/CountWheel.web.tsx | 65 ++++++------------ 5 files changed, 54 insertions(+), 96 deletions(-) diff --git a/src/analytics/index.tsx b/src/analytics/index.tsx index 3d39f464cd..d6b1f55c81 100644 --- a/src/analytics/index.tsx +++ b/src/analytics/index.tsx @@ -110,6 +110,7 @@ const Context = createContext({ }, }, }) +Context.displayName = 'AnalyticsContext' /** * Ensures that deviceId is set and migrated from legacy storage. Handled on diff --git a/src/components/PostControls/PostControlButton.tsx b/src/components/PostControls/PostControlButton.tsx index 9a24eb9173..3ea85e2811 100644 --- a/src/components/PostControls/PostControlButton.tsx +++ b/src/components/PostControls/PostControlButton.tsx @@ -130,8 +130,11 @@ export function PostControlButtonText({style, ...props}: TextProps) { { const ax = useAnalytics() + const t = useTheme() const {t: l} = useLingui() const {openComposer} = useOpenComposer() const {feedDescriptor} = useFeedFeedbackContext() @@ -270,6 +271,8 @@ let PostControls = ({ requireAuth(() => onPressToggleLike())} label={ post.viewer?.like @@ -296,10 +299,14 @@ let PostControls = ({ hasBeenToggled={hasLikeIconBeenToggled} /> ( + + {formatPostStatCount(count)} + + )} /> diff --git a/src/lib/custom-animations/CountWheel.tsx b/src/lib/custom-animations/CountWheel.tsx index e2c206078c..698df0e49d 100644 --- a/src/lib/custom-animations/CountWheel.tsx +++ b/src/lib/custom-animations/CountWheel.tsx @@ -8,10 +8,7 @@ import Animated, { } from 'react-native-reanimated' import {decideShouldRoll} from '#/lib/custom-animations/util' -import {s} from '#/lib/styles' -import {Text} from '#/view/com/util/text/Text' -import {atoms as a, useTheme} from '#/alf' -import {useFormatPostStatCount} from '#/components/PostControls/util' +import {atoms as a} from '#/alf' const animationConfig = { duration: 400, @@ -87,89 +84,66 @@ function ExitingDown() { } export function CountWheel({ - likeCount, - big, - isLiked, + count, + isToggled, hasBeenToggled, + renderCount, }: { - likeCount: number - big?: boolean - isLiked: boolean + count: number + isToggled: boolean hasBeenToggled: boolean + renderCount: (props: {count: number}) => React.ReactNode }) { - const t = useTheme() const shouldAnimate = !useReducedMotion() && hasBeenToggled - const shouldRoll = decideShouldRoll(isLiked, likeCount) + const shouldRoll = decideShouldRoll(isToggled, count) // Incrementing the key will cause the `Animated.View` to re-render, with the newly selected entering/exiting // animation // The initial entering/exiting animations will get skipped, since these will happen on screen mounts and would // be unnecessary const [key, setKey] = useState(0) - const [prevCount, setPrevCount] = useState(likeCount) - const prevIsLiked = useRef(isLiked) - const formatPostStatCount = useFormatPostStatCount() - const formattedCount = formatPostStatCount(likeCount) - const formattedPrevCount = formatPostStatCount(prevCount) + const [prevCount, setPrevCount] = useState(count) + const prevIsToggled = useRef(isToggled) useEffect(() => { - if (isLiked === prevIsLiked.current) { + if (isToggled === prevIsToggled.current) { return } - const newPrevCount = isLiked ? likeCount - 1 : likeCount + 1 + const newPrevCount = isToggled ? count - 1 : count + 1 setKey(prev => prev + 1) setPrevCount(newPrevCount) - prevIsLiked.current = isLiked - }, [isLiked, likeCount]) + prevIsToggled.current = isToggled + }, [isToggled, count]) const enteringAnimation = shouldAnimate && shouldRoll - ? isLiked + ? isToggled ? EnteringUp : EnteringDown : undefined const exitingAnimation = shouldAnimate && shouldRoll - ? isLiked + ? isToggled ? ExitingUp : ExitingDown : undefined return ( - {likeCount > 0 ? ( + {count > 0 ? ( - - {formattedCount} - + {renderCount({count})} - {shouldAnimate && (likeCount > 1 || !isLiked) ? ( + {shouldAnimate && (count > 1 || !isToggled) ? ( - - {formattedPrevCount} - + {renderCount({count: prevCount})} ) : null} diff --git a/src/lib/custom-animations/CountWheel.web.tsx b/src/lib/custom-animations/CountWheel.web.tsx index c5ca71e9bd..446dab1dd8 100644 --- a/src/lib/custom-animations/CountWheel.web.tsx +++ b/src/lib/custom-animations/CountWheel.web.tsx @@ -3,10 +3,6 @@ import {View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' import {decideShouldRoll} from '#/lib/custom-animations/util' -import {s} from '#/lib/styles' -import {Text} from '#/view/com/util/text/Text' -import {atoms as a, useTheme} from '#/alf' -import {useFormatPostStatCount} from '#/components/PostControls/util' const animationConfig = { duration: 400, @@ -35,50 +31,46 @@ const exitingDownKeyframe = [ ] export function CountWheel({ - likeCount, - big, - isLiked, + count, + isToggled, hasBeenToggled, + renderCount, }: { - likeCount: number - big?: boolean - isLiked: boolean + count: number + isToggled: boolean hasBeenToggled: boolean + renderCount: (props: {count: number}) => React.ReactNode }) { - const t = useTheme() const shouldAnimate = !useReducedMotion() && hasBeenToggled - const shouldRoll = decideShouldRoll(isLiked, likeCount) + const shouldRoll = decideShouldRoll(isToggled, count) const countView = useRef(null) const prevCountView = useRef(null) - const [prevCount, setPrevCount] = useState(likeCount) - const prevIsLiked = useRef(isLiked) - const formatPostStatCount = useFormatPostStatCount() - const formattedCount = formatPostStatCount(likeCount) - const formattedPrevCount = formatPostStatCount(prevCount) + const [prevCount, setPrevCount] = useState(count) + const prevIsToggled = useRef(isToggled) useEffect(() => { - if (isLiked === prevIsLiked.current) { + if (isToggled === prevIsToggled.current) { return } - const newPrevCount = isLiked ? likeCount - 1 : likeCount + 1 + const newPrevCount = isToggled ? count - 1 : count + 1 if (shouldAnimate && shouldRoll) { countView.current?.animate?.( - isLiked ? enteringUpKeyframe : enteringDownKeyframe, + isToggled ? enteringUpKeyframe : enteringDownKeyframe, animationConfig, ) prevCountView.current?.animate?.( - isLiked ? exitingUpKeyframe : exitingDownKeyframe, + isToggled ? exitingUpKeyframe : exitingDownKeyframe, animationConfig, ) setPrevCount(newPrevCount) } - prevIsLiked.current = isLiked - }, [isLiked, likeCount, shouldAnimate, shouldRoll]) + prevIsToggled.current = isToggled + }, [isToggled, count, shouldAnimate, shouldRoll]) - if (likeCount < 1) { + if (count < 1) { return null } @@ -87,34 +79,15 @@ export function CountWheel({ - - {formattedCount} - + {renderCount({count})} - {shouldAnimate && (likeCount > 1 || !isLiked) ? ( + {shouldAnimate && (count > 1 || !isToggled) ? ( - - {formattedPrevCount} - + {renderCount({count: prevCount})} ) : null}