mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at ruby-v 6.6 kB view raw
1import React, {memo} from 'react' 2import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' 3import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' 4import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' 5import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 6import {msg} from '@lingui/macro' 7import {useLingui} from '@lingui/react' 8import {useNavigation} from '@react-navigation/native' 9 10import {BACK_HITSLOP} from '#/lib/constants' 11import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef' 12import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 13import {NavigationProp} from '#/lib/routes/types' 14import {isIOS} from '#/platform/detection' 15import {Shadow} from '#/state/cache/types' 16import {useLightboxControls} from '#/state/lightbox' 17import {useSession} from '#/state/session' 18import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 19import {UserAvatar} from '#/view/com/util/UserAvatar' 20import {UserBanner} from '#/view/com/util/UserBanner' 21import {atoms as a, useTheme} from '#/alf' 22import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' 23import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' 24import {GrowableAvatar} from './GrowableAvatar' 25import {GrowableBanner} from './GrowableBanner' 26 27interface Props { 28 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> 29 moderation: ModerationDecision 30 hideBackButton?: boolean 31 isPlaceholderProfile?: boolean 32} 33 34let ProfileHeaderShell = ({ 35 children, 36 profile, 37 moderation, 38 hideBackButton = false, 39 isPlaceholderProfile, 40}: React.PropsWithChildren<Props>): React.ReactNode => { 41 const t = useTheme() 42 const {currentAccount} = useSession() 43 const {_} = useLingui() 44 const {openLightbox} = useLightboxControls() 45 const navigation = useNavigation<NavigationProp>() 46 const {isDesktop} = useWebMediaQueries() 47 const aviRef = useHandleRef() 48 49 const onPressBack = React.useCallback(() => { 50 if (navigation.canGoBack()) { 51 navigation.goBack() 52 } else { 53 navigation.navigate('Home') 54 } 55 }, [navigation]) 56 57 const _openLightbox = React.useCallback( 58 (uri: string, thumbRect: MeasuredDimensions | null) => { 59 openLightbox({ 60 images: [ 61 { 62 uri, 63 thumbUri: uri, 64 thumbRect, 65 dimensions: { 66 // It's fine if it's actually smaller but we know it's 1:1. 67 height: 1000, 68 width: 1000, 69 }, 70 thumbDimensions: null, 71 type: 'circle-avi', 72 }, 73 ], 74 index: 0, 75 }) 76 }, 77 [openLightbox], 78 ) 79 80 const onPressAvi = React.useCallback(() => { 81 const modui = moderation.ui('avatar') 82 const avatar = profile.avatar 83 if (avatar && !(modui.blur && modui.noOverride)) { 84 const aviHandle = aviRef.current 85 runOnUI(() => { 86 'worklet' 87 const rect = measureHandle(aviHandle) 88 runOnJS(_openLightbox)(avatar, rect) 89 })() 90 } 91 }, [profile, moderation, _openLightbox, aviRef]) 92 93 const isMe = React.useMemo( 94 () => currentAccount?.did === profile.did, 95 [currentAccount, profile], 96 ) 97 98 return ( 99 <View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}> 100 <View 101 pointerEvents={isIOS ? 'auto' : 'box-none'} 102 style={[a.relative, {height: 150}]}> 103 <GrowableBanner 104 backButton={ 105 <> 106 {!isDesktop && !hideBackButton && ( 107 <TouchableWithoutFeedback 108 testID="profileHeaderBackBtn" 109 onPress={onPressBack} 110 hitSlop={BACK_HITSLOP} 111 accessibilityRole="button" 112 accessibilityLabel={_(msg`Back`)} 113 accessibilityHint=""> 114 <View style={styles.backBtnWrapper}> 115 <FontAwesomeIcon 116 size={18} 117 icon="angle-left" 118 color="white" 119 /> 120 </View> 121 </TouchableWithoutFeedback> 122 )} 123 </> 124 }> 125 {isPlaceholderProfile ? ( 126 <LoadingPlaceholder 127 width="100%" 128 height="100%" 129 style={{borderRadius: 0}} 130 /> 131 ) : ( 132 <UserBanner 133 type={profile.associated?.labeler ? 'labeler' : 'default'} 134 banner={profile.banner} 135 moderation={moderation.ui('banner')} 136 /> 137 )} 138 </GrowableBanner> 139 </View> 140 141 {children} 142 143 {!isPlaceholderProfile && ( 144 <View 145 style={[a.px_lg, a.py_xs]} 146 pointerEvents={isIOS ? 'auto' : 'box-none'}> 147 {isMe ? ( 148 <LabelsOnMe type="account" labels={profile.labels} /> 149 ) : ( 150 <ProfileHeaderAlerts moderation={moderation} /> 151 )} 152 </View> 153 )} 154 155 <GrowableAvatar style={styles.aviPosition}> 156 <TouchableWithoutFeedback 157 testID="profileHeaderAviButton" 158 onPress={onPressAvi} 159 accessibilityRole="image" 160 accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} 161 accessibilityHint=""> 162 <View 163 style={[ 164 t.atoms.bg, 165 {borderColor: t.atoms.bg.backgroundColor}, 166 styles.avi, 167 profile.associated?.labeler && styles.aviLabeler, 168 ]}> 169 <View ref={aviRef} collapsable={false}> 170 <UserAvatar 171 type={profile.associated?.labeler ? 'labeler' : 'user'} 172 size={90} 173 avatar={profile.avatar} 174 moderation={moderation.ui('avatar')} 175 /> 176 </View> 177 </View> 178 </TouchableWithoutFeedback> 179 </GrowableAvatar> 180 </View> 181 ) 182} 183ProfileHeaderShell = memo(ProfileHeaderShell) 184export {ProfileHeaderShell} 185 186const styles = StyleSheet.create({ 187 backBtnWrapper: { 188 position: 'absolute', 189 top: 10, 190 left: 10, 191 width: 30, 192 height: 30, 193 overflow: 'hidden', 194 borderRadius: 15, 195 // @ts-ignore web only 196 cursor: 'pointer', 197 backgroundColor: 'rgba(0, 0, 0, 0.5)', 198 alignItems: 'center', 199 justifyContent: 'center', 200 }, 201 backBtn: { 202 width: 30, 203 height: 30, 204 borderRadius: 15, 205 alignItems: 'center', 206 justifyContent: 'center', 207 }, 208 aviPosition: { 209 position: 'absolute', 210 top: 110, 211 left: 10, 212 }, 213 avi: { 214 width: 94, 215 height: 94, 216 borderRadius: 47, 217 borderWidth: 2, 218 }, 219 aviLabeler: { 220 borderRadius: 10, 221 }, 222})