diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-09-25 15:02:29 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-25 15:02:29 +0100 |
commit | 2296ea338e8f7b4906a928e802267837c06754cc (patch) | |
tree | 4d9c4546f52cc6fa3d1916aaa1a2978301eff35e | |
parent | f7a2368100d293c7ddc65bf27ade9fda66ecda95 (diff) | |
download | voidsky-2296ea338e8f7b4906a928e802267837c06754cc.tar.zst |
subtle avatar grow animation (#5480)
-rw-r--r-- | src/screens/Profile/Header/GrowableAvatar.tsx | 61 | ||||
-rw-r--r-- | src/screens/Profile/Header/Shell.tsx | 49 |
2 files changed, 88 insertions, 22 deletions
diff --git a/src/screens/Profile/Header/GrowableAvatar.tsx b/src/screens/Profile/Header/GrowableAvatar.tsx new file mode 100644 index 000000000..20ac14892 --- /dev/null +++ b/src/screens/Profile/Header/GrowableAvatar.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import {StyleProp, View, ViewStyle} from 'react-native' +import Animated, { + Extrapolation, + interpolate, + SharedValue, + useAnimatedStyle, +} from 'react-native-reanimated' + +import {isIOS} from '#/platform/detection' +import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' + +export function GrowableAvatar({ + children, + style, +}: { + children: React.ReactNode + style?: StyleProp<ViewStyle> +}) { + const pagerContext = usePagerHeaderContext() + + // pagerContext should only be present on iOS, but better safe than sorry + if (!pagerContext || !isIOS) { + return <View style={style}>{children}</View> + } + + const {scrollY} = pagerContext + + return ( + <GrowableAvatarInner scrollY={scrollY} style={style}> + {children} + </GrowableAvatarInner> + ) +} + +function GrowableAvatarInner({ + scrollY, + children, + style, +}: { + scrollY: SharedValue<number> + children: React.ReactNode + style?: StyleProp<ViewStyle> +}) { + const animatedStyle = useAnimatedStyle(() => ({ + transform: [ + { + scale: interpolate(scrollY.value, [-150, 0], [1.2, 1], { + extrapolateRight: Extrapolation.CLAMP, + }), + }, + ], + })) + + return ( + <Animated.View + style={[style, {transformOrigin: 'bottom left'}, animatedStyle]}> + {children} + </Animated.View> + ) +} diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index d31912dda..f7011fd35 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -19,6 +19,7 @@ import {UserBanner} from '#/view/com/util/UserBanner' import {atoms as a, useTheme} from '#/alf' import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' +import {GrowableAvatar} from './GrowableAvatar' import {GrowableBanner} from './GrowableBanner' interface Props { @@ -119,27 +120,29 @@ let ProfileHeaderShell = ({ </View> )} - <TouchableWithoutFeedback - testID="profileHeaderAviButton" - onPress={onPressAvi} - accessibilityRole="image" - accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} - accessibilityHint=""> - <View - style={[ - t.atoms.bg, - {borderColor: t.atoms.bg.backgroundColor}, - styles.avi, - profile.associated?.labeler && styles.aviLabeler, - ]}> - <UserAvatar - type={profile.associated?.labeler ? 'labeler' : 'user'} - size={90} - avatar={profile.avatar} - moderation={moderation.ui('avatar')} - /> - </View> - </TouchableWithoutFeedback> + <GrowableAvatar style={styles.aviPosition}> + <TouchableWithoutFeedback + testID="profileHeaderAviButton" + onPress={onPressAvi} + accessibilityRole="image" + accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} + accessibilityHint=""> + <View + style={[ + t.atoms.bg, + {borderColor: t.atoms.bg.backgroundColor}, + styles.avi, + profile.associated?.labeler && styles.aviLabeler, + ]}> + <UserAvatar + type={profile.associated?.labeler ? 'labeler' : 'user'} + size={90} + avatar={profile.avatar} + moderation={moderation.ui('avatar')} + /> + </View> + </TouchableWithoutFeedback> + </GrowableAvatar> </View> ) } @@ -168,10 +171,12 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - avi: { + aviPosition: { position: 'absolute', top: 110, left: 10, + }, + avi: { width: 94, height: 94, borderRadius: 47, |