subtle avatar grow animation (#5480)
This commit is contained in:
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import {UserBanner} from '#/view/com/util/UserBanner'
|
|||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {LabelsOnMe} from '#/components/moderation/LabelsOnMe'
|
import {LabelsOnMe} from '#/components/moderation/LabelsOnMe'
|
||||||
import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts'
|
import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts'
|
||||||
|
import {GrowableAvatar} from './GrowableAvatar'
|
||||||
import {GrowableBanner} from './GrowableBanner'
|
import {GrowableBanner} from './GrowableBanner'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -119,6 +120,7 @@ let ProfileHeaderShell = ({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<GrowableAvatar style={styles.aviPosition}>
|
||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
testID="profileHeaderAviButton"
|
testID="profileHeaderAviButton"
|
||||||
onPress={onPressAvi}
|
onPress={onPressAvi}
|
||||||
@@ -140,6 +142,7 @@ let ProfileHeaderShell = ({
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
|
</GrowableAvatar>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -168,10 +171,12 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
avi: {
|
aviPosition: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 110,
|
top: 110,
|
||||||
left: 10,
|
left: 10,
|
||||||
|
},
|
||||||
|
avi: {
|
||||||
width: 94,
|
width: 94,
|
||||||
height: 94,
|
height: 94,
|
||||||
borderRadius: 47,
|
borderRadius: 47,
|
||||||
|
|||||||
Reference in New Issue
Block a user