Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {useCallback} from 'react'
2import {Pressable, View} from 'react-native'
3import Animated, {useAnimatedRef} from 'react-native-reanimated'
4import {type AppBskyGraphDefs} from '@atproto/api'
5import {msg} from '@lingui/core/macro'
6import {useLingui} from '@lingui/react'
7import {Trans} from '@lingui/react/macro'
8import {useNavigation} from '@react-navigation/native'
9
10import {usePalette} from '#/lib/hooks/usePalette'
11import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
12import {makeProfileLink} from '#/lib/routes/links'
13import {type NavigationProp} from '#/lib/routes/types'
14import {sanitizeHandle} from '#/lib/strings/handles'
15import {emitSoftReset} from '#/state/events'
16import {useLightboxControls} from '#/state/lightbox'
17import {TextLink} from '#/view/com/util/Link'
18import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
19import {Text} from '#/view/com/util/text/Text'
20import {UserAvatar, type UserAvatarType} from '#/view/com/util/UserAvatar'
21import {StarterPack} from '#/components/icons/StarterPack'
22import * as Layout from '#/components/Layout'
23
24export function ProfileSubpageHeader({
25 isLoading,
26 href,
27 title,
28 avatar,
29 isOwner,
30 purpose,
31 creator,
32 avatarType,
33 children,
34}: React.PropsWithChildren<{
35 isLoading?: boolean
36 href: string
37 title: string | undefined
38 avatar: string | undefined
39 isOwner: boolean | undefined
40 purpose: AppBskyGraphDefs.ListPurpose | undefined
41 creator:
42 | {
43 did: string
44 handle: string
45 }
46 | undefined
47 avatarType: UserAvatarType | 'starter-pack'
48}>) {
49 const navigation = useNavigation<NavigationProp>()
50 const {_} = useLingui()
51 const {isMobile} = useWebMediaQueries()
52 const {openLightbox} = useLightboxControls()
53 const pal = usePalette('default')
54 const canGoBack = navigation.canGoBack()
55 const aviRef = useAnimatedRef()
56
57 const onPressAvi = useCallback(() => {
58 if (
59 avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
60 ) {
61 openLightbox({
62 images: [
63 {
64 uri: avatar,
65 thumbUri: avatar,
66 thumbRect: null,
67 thumbRef: aviRef,
68 dimensions: {
69 // It's fine if it's actually smaller but we know it's 1:1.
70 height: 1000,
71 width: 1000,
72 },
73 thumbDimensions: null,
74 type: 'rect-avi',
75 },
76 ],
77 index: 0,
78 })
79 }
80 }, [openLightbox, avatar, aviRef])
81
82 return (
83 <>
84 <Layout.Header.Outer>
85 {canGoBack ? (
86 <Layout.Header.BackButton />
87 ) : (
88 <Layout.Header.MenuButton />
89 )}
90 <Layout.Header.Content />
91 {children}
92 </Layout.Header.Outer>
93
94 <View
95 style={{
96 flexDirection: 'row',
97 alignItems: 'flex-start',
98 gap: 10,
99 paddingTop: 14,
100 paddingBottom: 14,
101 paddingHorizontal: isMobile ? 12 : 14,
102 }}>
103 <Animated.View ref={aviRef} collapsable={false}>
104 <Pressable
105 testID="headerAviButton"
106 onPress={onPressAvi}
107 accessibilityRole="image"
108 accessibilityLabel={_(msg`View the avatar`)}
109 accessibilityHint=""
110 style={{width: 58}}>
111 {avatarType === 'starter-pack' ? (
112 <StarterPack width={58} gradient="sky" />
113 ) : (
114 <UserAvatar type={avatarType} size={58} avatar={avatar} />
115 )}
116 </Pressable>
117 </Animated.View>
118 <View style={{flex: 1, gap: 4}}>
119 {isLoading ? (
120 <LoadingPlaceholder
121 width={200}
122 height={32}
123 style={{marginVertical: 6}}
124 />
125 ) : (
126 <TextLink
127 testID="headerTitle"
128 type="title-xl"
129 href={href}
130 style={[pal.text, {fontWeight: '600'}]}
131 text={title || ''}
132 onPress={emitSoftReset}
133 numberOfLines={4}
134 />
135 )}
136
137 {isLoading || !creator ? (
138 <LoadingPlaceholder width={50} height={8} />
139 ) : (
140 <Text type="lg" style={[pal.textLight]} numberOfLines={1}>
141 {purpose === 'app.bsky.graph.defs#curatelist' ? (
142 isOwner ? (
143 <Trans>List by you</Trans>
144 ) : (
145 <Trans>
146 List by{' '}
147 <TextLink
148 text={sanitizeHandle(creator.handle || '', '@')}
149 href={makeProfileLink(creator)}
150 style={pal.textLight}
151 />
152 </Trans>
153 )
154 ) : purpose === 'app.bsky.graph.defs#modlist' ? (
155 isOwner ? (
156 <Trans>Moderation list by you</Trans>
157 ) : (
158 <Trans>
159 Moderation list by{' '}
160 <TextLink
161 text={sanitizeHandle(creator.handle || '', '@')}
162 href={makeProfileLink(creator)}
163 style={pal.textLight}
164 />
165 </Trans>
166 )
167 ) : purpose === 'app.bsky.graph.defs#referencelist' ? (
168 isOwner ? (
169 <Trans>Starter pack by you</Trans>
170 ) : (
171 <Trans>
172 Starter pack by{' '}
173 <TextLink
174 text={sanitizeHandle(creator.handle || '', '@')}
175 href={makeProfileLink(creator)}
176 style={pal.textLight}
177 />
178 </Trans>
179 )
180 ) : null}
181 </Text>
182 )}
183 </View>
184 </View>
185 </>
186 )
187}