diff --git a/src/components/AvatarBubbles.tsx b/src/components/AvatarBubbles.tsx index f7ebdc3d69..2dd2f3b203 100644 --- a/src/components/AvatarBubbles.tsx +++ b/src/components/AvatarBubbles.tsx @@ -1,4 +1,13 @@ +import {useCallback, useEffect} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' +import Animated, { + Easing, + interpolate, + useAnimatedStyle, + useSharedValue, + withDelay, + withTiming, +} from 'react-native-reanimated' import {useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' @@ -7,21 +16,58 @@ import {Person_Filled_Corner2_Rounded as PersonIcon} from '#/components/icons/Pe import type * as bsky from '#/types/bsky' type Props = { + animate?: boolean profiles: bsky.profile.AnyProfileView[] size?: 'small' | 'medium' | 'large' } -export function AvatarBubbles({profiles: allProfiles, size = 'large'}: Props) { +export function AvatarBubbles({ + animate = false, + profiles: allProfiles, + size = 'large', +}: Props) { const {currentAccount} = useSession() const profiles = allProfiles.filter(p => p.did !== currentAccount?.did) const containerSize = size === 'small' ? 40 : size === 'medium' ? 56 : 120 const scale = size === 'small' ? 40 / 120 : size === 'medium' ? 56 / 120 : 1 const marginOffset = size === 'small' || size === 'medium' ? -2 : 0 + const initialValue = animate ? 0 : 1 + const p0 = useSharedValue(initialValue) + const p1 = useSharedValue(initialValue) + const p2 = useSharedValue(initialValue) + const p3 = useSharedValue(initialValue) + + const animateScale = (p: Animated.SharedValue, index: number) => { + p.set(0) + p.set(() => + withDelay( + 500 + index * 100, + withTiming(1, { + duration: 250, + easing: Easing.out(Easing.back(1.75)), + }), + ), + ) + } + + const playScaleAnimation = useCallback(() => { + animateScale(p0, 0) + animateScale(p1, 1) + animateScale(p2, 2) + animateScale(p3, 3) + }, [p0, p1, p2, p3]) + + useEffect(() => { + if (!animate) return + playScaleAnimation() + }, [animate, playScaleAnimation]) + let avatars = ( <> - - - + + + ) } @@ -52,16 +117,34 @@ export function AvatarBubbles({profiles: allProfiles, size = 'large'}: Props) { if (profiles.length >= 4) { avatars = ( <> - - - - + + + + ) } return ( - {avatars} - + ) } function AvatarBubble({ profile, + scale, size, style, x, @@ -93,6 +177,7 @@ function AvatarBubble({ includeProfileBorder, }: { profile?: bsky.profile.AnyProfileView + scale: Animated.SharedValue size: number style?: StyleProp x: number @@ -101,8 +186,16 @@ function AvatarBubble({ }) { const t = useTheme() + const animatedStyle = useAnimatedStyle(() => ({ + transform: [ + {translateX: x}, + {translateY: y}, + {scale: interpolate(scale.get(), [0, 1], [0, 1])}, + ], + })) + return ( - {profile ? ( ) : ( )} - + ) }