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