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