diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-12-12 17:46:19 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-12 17:46:19 +0000 |
commit | ffc63dc85fc191a51c3dc12c1afcd250f95036d5 (patch) | |
tree | d48124c74c24662abf9ee28ff6fbbdd4b1d8ee99 /src/screens/Profile/Header/index.tsx | |
parent | 4b32b0a71be4669fa0741efc46d646093c3114f5 (diff) | |
download | voidsky-ffc63dc85fc191a51c3dc12c1afcd250f95036d5.tar.zst |
[Layout] Bleed profile banner into safe area (#6967)
* bleed profile banner into safe area (cherry picked from commit 50b3a4d0c6fd94b583ffe4efa65de35c81ae7f4e) * pointer events none when hidden (cherry picked from commit bae2c7b2dd6d7f858a98812196628308c0877755) * fix web (cherry picked from commit e3f9597170375f2903b6e567b963f008ec95aed1) * add status bar shadow * rm log * rm mini header * speed up animation * pass bool rather than int in light status bar
Diffstat (limited to 'src/screens/Profile/Header/index.tsx')
-rw-r--r-- | src/screens/Profile/Header/index.tsx | 119 |
1 files changed, 112 insertions, 7 deletions
diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx index deb8063d9..7e4b9bb31 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -1,14 +1,25 @@ -import React, {memo} from 'react' -import {StyleSheet, View} from 'react-native' +import React, {memo, useState} from 'react' +import {LayoutChangeEvent, StyleSheet, View} from 'react-native' +import Animated, { + runOnJS, + useAnimatedReaction, + useAnimatedStyle, + withTiming, +} from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import { AppBskyActorDefs, AppBskyLabelerDefs, ModerationOpts, RichText as RichTextAPI, } from '@atproto/api' +import {useIsFocused} from '@react-navigation/native' +import {isNative} from '#/platform/detection' +import {useSetLightStatusBar} from '#/state/shell/light-status-bar' +import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' -import {useTheme} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {ProfileHeaderLabeler} from './ProfileHeaderLabeler' import {ProfileHeaderStandard} from './ProfileHeaderStandard' @@ -43,20 +54,114 @@ interface Props { moderationOpts: ModerationOpts hideBackButton?: boolean isPlaceholderProfile?: boolean + setMinimumHeight: (height: number) => void } -let ProfileHeader = (props: Props): React.ReactNode => { +let ProfileHeader = ({setMinimumHeight, ...props}: Props): React.ReactNode => { + let content if (props.profile.associated?.labeler) { if (!props.labeler) { - return <ProfileHeaderLoading /> + content = <ProfileHeaderLoading /> + } else { + content = <ProfileHeaderLabeler {...props} labeler={props.labeler} /> } - return <ProfileHeaderLabeler {...props} labeler={props.labeler} /> + } else { + content = <ProfileHeaderStandard {...props} /> } - return <ProfileHeaderStandard {...props} /> + + return ( + <> + {isNative && ( + <MinimalHeader + onLayout={evt => setMinimumHeight(evt.nativeEvent.layout.height)} + profile={props.profile} + hideBackButton={props.hideBackButton} + /> + )} + {content} + </> + ) } ProfileHeader = memo(ProfileHeader) export {ProfileHeader} +const MinimalHeader = React.memo(function MinimalHeader({ + onLayout, +}: { + onLayout: (e: LayoutChangeEvent) => void + profile: AppBskyActorDefs.ProfileViewDetailed + hideBackButton?: boolean +}) { + const t = useTheme() + const insets = useSafeAreaInsets() + const ctx = usePagerHeaderContext() + const [visible, setVisible] = useState(false) + const [minimalHeaderHeight, setMinimalHeaderHeight] = React.useState(0) + const isScreenFocused = useIsFocused() + if (!ctx) throw new Error('MinimalHeader cannot be used on web') + const {scrollY, headerHeight} = ctx + + const animatedStyle = useAnimatedStyle(() => { + // if we don't yet have the min header height in JS, hide + if (!_WORKLET || minimalHeaderHeight === 0) { + return { + opacity: 0, + } + } + const pastThreshold = scrollY.get() > 100 + return { + opacity: pastThreshold + ? withTiming(1, {duration: 75}) + : withTiming(0, {duration: 75}), + transform: [ + { + translateY: Math.min( + scrollY.get(), + headerHeight - minimalHeaderHeight, + ), + }, + ], + } + }) + + useAnimatedReaction( + () => scrollY.get() > 100, + (value, prev) => { + if (prev !== value) { + runOnJS(setVisible)(value) + } + }, + ) + + useSetLightStatusBar(isScreenFocused && !visible) + + return ( + <Animated.View + pointerEvents={visible ? 'auto' : 'none'} + aria-hidden={!visible} + accessibilityElementsHidden={!visible} + importantForAccessibility={visible ? 'auto' : 'no-hide-descendants'} + onLayout={evt => { + setMinimalHeaderHeight(evt.nativeEvent.layout.height) + onLayout(evt) + }} + style={[ + a.absolute, + a.z_50, + t.atoms.bg, + { + top: 0, + left: 0, + right: 0, + paddingTop: insets.top, + }, + animatedStyle, + ]} + /> + ) +}) +MinimalHeader.displayName = 'MinimalHeader' + const styles = StyleSheet.create({ avi: { position: 'absolute', |