import {memo, useCallback, useEffect, useMemo, useRef} from 'react' import {Pressable, View} from 'react-native' import Animated, { measure, type MeasuredDimensions, runOnJS, runOnUI, useAnimatedRef, } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' import {utils} from '@bsky.app/alf' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {BACK_HITSLOP} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {type NavigationProp} from '#/lib/routes/types' import {type Shadow} from '#/state/cache/types' import {useLightboxControls} from '#/state/lightbox' import {useEnableSquareAvatars} from '#/state/preferences/enable-square-avatars' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' import {useHighQualityImages} from '#/state/preferences/high-quality-images' import { applyImageTransforms, useImageCdnHost, } from '#/state/preferences/image-cdn-host' import {useSession} from '#/state/session' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserBanner} from '#/view/com/util/UserBanner' import {atoms as a, platform, useTheme} from '#/alf' import {Button} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' import {useActorStatus} from '#/features/liveNow' import {EditLiveDialog} from '#/features/liveNow/components/EditLiveDialog' import {LiveIndicator} from '#/features/liveNow/components/LiveIndicator' import {LiveStatusDialog} from '#/features/liveNow/components/LiveStatusDialog' import {GrowableAvatar} from './GrowableAvatar' import {GrowableBanner} from './GrowableBanner' import {StatusBarShadow} from './StatusBarShadow' interface Props { profile: Shadow moderation: ModerationDecision hideBackButton?: boolean isPlaceholderProfile?: boolean } let ProfileHeaderShell = ({ children, profile, moderation, hideBackButton = false, isPlaceholderProfile, }: React.PropsWithChildren): React.ReactNode => { const t = useTheme() const ax = useAnalytics() const {currentAccount} = useSession() const {t: l} = useLingui() const {openLightbox} = useLightboxControls() const navigation = useNavigation() const {top: topInset} = useSafeAreaInsets() const playHaptic = useHaptics() const liveStatusControl = useDialogControl() const highQualityImages = useHighQualityImages() const imageCdnHost = useImageCdnHost() const enableSquareAvatars = useEnableSquareAvatars() const enableSquareButtons = useEnableSquareButtons() const aviRef = useAnimatedRef() const bannerRef = useAnimatedRef() const containerRef = useRef(null) // Apply safe-area CSS on web useEffect(() => { if (containerRef.current && typeof window !== 'undefined') { const element = containerRef.current as any if (element.style) { element.style.paddingTop = 'env(safe-area-inset-top)' } } }, []) const onPressBack = useCallback(() => { if (navigation.canGoBack()) { navigation.goBack() } else { navigation.navigate('Home') } }, [navigation]) const _openLightbox = useCallback( ( uri: string, thumbRect: MeasuredDimensions | null, type: 'circle-avi' | 'rect-avi' | 'image' = 'circle-avi', ) => { openLightbox({ images: [ { uri: applyImageTransforms(uri, {imageCdnHost, highQualityImages}), thumbUri: applyImageTransforms(uri, { imageCdnHost, highQualityImages, }), thumbRect, dimensions: type === 'circle-avi' || type === 'rect-avi' ? { // It's fine if it's actually smaller but we know it's 1:1. height: 1000, width: 1000, } : { // Banner aspect ratio is 3:1 width: 3000, height: 1000, }, thumbDimensions: null, type: enableSquareAvatars ? 'rect-avi' : 'circle-avi', }, ], index: 0, }) }, [openLightbox, imageCdnHost, highQualityImages, enableSquareAvatars], ) // theres probs a better way instead of just making a separate one but this works:tm: so its whatever const _openLightboxBanner = useCallback( (uri: string, thumbRect: MeasuredDimensions | null) => { openLightbox({ images: [ { uri: applyImageTransforms(uri, {imageCdnHost, highQualityImages}), thumbUri: applyImageTransforms(uri, { imageCdnHost, highQualityImages, }), thumbRect, dimensions: thumbRect, thumbDimensions: null, type: 'image', }, ], index: 0, }) }, [openLightbox, imageCdnHost, highQualityImages], ) const isMe = useMemo( () => currentAccount?.did === profile.did, [currentAccount, profile], ) const live = useActorStatus(profile) useEffect(() => { if (live.isActive) { ax.metric('live:view:profile', {subject: profile.did}) } }, [ax, live.isActive, profile.did]) const onPressAvi = useCallback(() => { if (live.isActive) { playHaptic('Light') ax.metric('live:card:open', {subject: profile.did, from: 'profile'}) liveStatusControl.open() } else { const modui = moderation.ui('avatar') const avatar = profile.avatar const type = profile.associated?.labeler ? 'rect-avi' : 'circle-avi' if (avatar && !(modui.blur && modui.noOverride)) { runOnUI(() => { 'worklet' const rect = measure(aviRef) runOnJS(_openLightbox)(avatar, rect, type) })() } } }, [ ax, profile, moderation, _openLightbox, aviRef, liveStatusControl, live, playHaptic, ]) const onPressBanner = useCallback(() => { const modui = moderation.ui('banner') const banner = profile.banner if (banner && !(modui.blur && modui.noOverride)) { runOnUI(() => { 'worklet' const rect = measure(bannerRef) runOnJS(_openLightboxBanner)(banner, rect) })() } }, [profile.banner, moderation, _openLightboxBanner, bannerRef]) return ( {({hovered}) => ( )} ) }> {isPlaceholderProfile ? ( ) : ( )} {children} {!isPlaceholderProfile && (isMe ? ( ) : ( ))} {live.isActive && } {live.isActive && (isMe ? ( ) : ( { const modui = moderation.ui('avatar') const avatar = profile.avatar if (avatar && !(modui.blur && modui.noOverride)) { runOnUI(() => { 'worklet' const rect = measure(aviRef) runOnJS(_openLightbox)(avatar, rect) })() } }} /> ))} ) } ProfileHeaderShell = memo(ProfileHeaderShell) export {ProfileHeaderShell}