mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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})