Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

subtle avatar grow animation (#5480)

authored by

Samuel Newman and committed by
GitHub
2296ea33 f7a23681

+88 -22
+61
src/screens/Profile/Header/GrowableAvatar.tsx
··· 1 + import React from 'react' 2 + import {StyleProp, View, ViewStyle} from 'react-native' 3 + import Animated, { 4 + Extrapolation, 5 + interpolate, 6 + SharedValue, 7 + useAnimatedStyle, 8 + } from 'react-native-reanimated' 9 + 10 + import {isIOS} from '#/platform/detection' 11 + import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 12 + 13 + export function GrowableAvatar({ 14 + children, 15 + style, 16 + }: { 17 + children: React.ReactNode 18 + style?: StyleProp<ViewStyle> 19 + }) { 20 + const pagerContext = usePagerHeaderContext() 21 + 22 + // pagerContext should only be present on iOS, but better safe than sorry 23 + if (!pagerContext || !isIOS) { 24 + return <View style={style}>{children}</View> 25 + } 26 + 27 + const {scrollY} = pagerContext 28 + 29 + return ( 30 + <GrowableAvatarInner scrollY={scrollY} style={style}> 31 + {children} 32 + </GrowableAvatarInner> 33 + ) 34 + } 35 + 36 + function GrowableAvatarInner({ 37 + scrollY, 38 + children, 39 + style, 40 + }: { 41 + scrollY: SharedValue<number> 42 + children: React.ReactNode 43 + style?: StyleProp<ViewStyle> 44 + }) { 45 + const animatedStyle = useAnimatedStyle(() => ({ 46 + transform: [ 47 + { 48 + scale: interpolate(scrollY.value, [-150, 0], [1.2, 1], { 49 + extrapolateRight: Extrapolation.CLAMP, 50 + }), 51 + }, 52 + ], 53 + })) 54 + 55 + return ( 56 + <Animated.View 57 + style={[style, {transformOrigin: 'bottom left'}, animatedStyle]}> 58 + {children} 59 + </Animated.View> 60 + ) 61 + }
+27 -22
src/screens/Profile/Header/Shell.tsx
··· 19 19 import {atoms as a, useTheme} from '#/alf' 20 20 import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' 21 21 import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' 22 + import {GrowableAvatar} from './GrowableAvatar' 22 23 import {GrowableBanner} from './GrowableBanner' 23 24 24 25 interface Props { ··· 119 120 </View> 120 121 )} 121 122 122 - <TouchableWithoutFeedback 123 - testID="profileHeaderAviButton" 124 - onPress={onPressAvi} 125 - accessibilityRole="image" 126 - accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} 127 - accessibilityHint=""> 128 - <View 129 - style={[ 130 - t.atoms.bg, 131 - {borderColor: t.atoms.bg.backgroundColor}, 132 - styles.avi, 133 - profile.associated?.labeler && styles.aviLabeler, 134 - ]}> 135 - <UserAvatar 136 - type={profile.associated?.labeler ? 'labeler' : 'user'} 137 - size={90} 138 - avatar={profile.avatar} 139 - moderation={moderation.ui('avatar')} 140 - /> 141 - </View> 142 - </TouchableWithoutFeedback> 123 + <GrowableAvatar style={styles.aviPosition}> 124 + <TouchableWithoutFeedback 125 + testID="profileHeaderAviButton" 126 + onPress={onPressAvi} 127 + accessibilityRole="image" 128 + accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} 129 + accessibilityHint=""> 130 + <View 131 + style={[ 132 + t.atoms.bg, 133 + {borderColor: t.atoms.bg.backgroundColor}, 134 + styles.avi, 135 + profile.associated?.labeler && styles.aviLabeler, 136 + ]}> 137 + <UserAvatar 138 + type={profile.associated?.labeler ? 'labeler' : 'user'} 139 + size={90} 140 + avatar={profile.avatar} 141 + moderation={moderation.ui('avatar')} 142 + /> 143 + </View> 144 + </TouchableWithoutFeedback> 145 + </GrowableAvatar> 143 146 </View> 144 147 ) 145 148 } ··· 168 171 alignItems: 'center', 169 172 justifyContent: 'center', 170 173 }, 171 - avi: { 174 + aviPosition: { 172 175 position: 'absolute', 173 176 top: 110, 174 177 left: 10, 178 + }, 179 + avi: { 175 180 width: 94, 176 181 height: 94, 177 182 borderRadius: 47,