Bluesky app fork with some witchin' additions 馃挮
at main 207 lines 4.9 kB view raw
1import {View} from 'react-native' 2import {type AppBskyLabelerDefs} from '@atproto/api' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Plural, Trans} from '@lingui/react/macro' 6import type React from 'react' 7 8import {getLabelingServiceTitle} from '#/lib/moderation' 9import {sanitizeHandle} from '#/lib/strings/handles' 10import {useLabelerInfoQuery} from '#/state/queries/labeler' 11import {UserAvatar} from '#/view/com/util/UserAvatar' 12import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 13import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 14import {Link as InternalLink, type LinkProps} from '#/components/Link' 15import {RichText} from '#/components/RichText' 16import {Text} from '#/components/Typography' 17import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' 18 19type LabelingServiceProps = { 20 labeler: AppBskyLabelerDefs.LabelerViewDetailed 21} 22 23export function Outer({ 24 children, 25 style, 26}: React.PropsWithChildren<ViewStyleProp>) { 27 return ( 28 <View 29 style={[ 30 a.flex_row, 31 a.gap_md, 32 a.w_full, 33 a.p_lg, 34 a.pr_md, 35 a.overflow_hidden, 36 style, 37 ]}> 38 {children} 39 </View> 40 ) 41} 42 43export function Avatar({avatar}: {avatar?: string}) { 44 return <UserAvatar type="labeler" size={40} avatar={avatar} /> 45} 46 47export function Title({value}: {value: string}) { 48 return ( 49 <Text emoji style={[a.text_md, a.font_semi_bold, a.leading_tight]}> 50 {value} 51 </Text> 52 ) 53} 54 55export function Description({value, handle}: {value?: string; handle: string}) { 56 const {_} = useLingui() 57 return value ? ( 58 <Text numberOfLines={2}> 59 <RichText value={value} /> 60 </Text> 61 ) : ( 62 <Text emoji style={[a.leading_snug]}> 63 {_(msg`By ${sanitizeHandle(handle, '@')}`)} 64 </Text> 65 ) 66} 67 68export function RegionalNotice() { 69 const t = useTheme() 70 return ( 71 <View 72 style={[ 73 a.flex_row, 74 a.align_center, 75 a.gap_xs, 76 a.pt_2xs, 77 {marginLeft: -2}, 78 ]}> 79 <Flag fill={t.atoms.text_contrast_low.color} size="sm" /> 80 <Text style={[a.italic, a.leading_snug]}> 81 <Trans>Required in your region</Trans> 82 </Text> 83 </View> 84 ) 85} 86 87export function LikeCount({likeCount}: {likeCount: number}) { 88 const t = useTheme() 89 return ( 90 <Text 91 style={[ 92 a.mt_sm, 93 a.text_sm, 94 t.atoms.text_contrast_medium, 95 {fontWeight: '600'}, 96 ]}> 97 <Trans> 98 Liked by <Plural value={likeCount} one="# user" other="# users" /> 99 </Trans> 100 </Text> 101 ) 102} 103 104export function Content({children}: React.PropsWithChildren<{}>) { 105 const t = useTheme() 106 107 return ( 108 <View 109 style={[ 110 a.flex_1, 111 a.flex_row, 112 a.gap_md, 113 a.align_center, 114 a.justify_between, 115 ]}> 116 <View style={[a.gap_2xs, a.flex_1]}>{children}</View> 117 118 <ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} /> 119 </View> 120 ) 121} 122 123/** 124 * The canonical view for a labeling service. Use this or compose your own. 125 */ 126export function Default({ 127 labeler, 128 style, 129}: LabelingServiceProps & ViewStyleProp) { 130 return ( 131 <Outer style={style}> 132 <Avatar avatar={labeler.creator.avatar} /> 133 <Content> 134 <Title 135 value={getLabelingServiceTitle({ 136 displayName: labeler.creator.displayName, 137 handle: labeler.creator.handle, 138 })} 139 /> 140 <Description 141 value={labeler.creator.description} 142 handle={labeler.creator.handle} 143 /> 144 {labeler.likeCount ? <LikeCount likeCount={labeler.likeCount} /> : null} 145 </Content> 146 </Outer> 147 ) 148} 149 150export function Link({ 151 children, 152 labeler, 153}: LabelingServiceProps & Pick<LinkProps, 'children'>) { 154 const {_} = useLingui() 155 156 return ( 157 <InternalLink 158 to={{ 159 screen: 'Profile', 160 params: { 161 name: labeler.creator.did, 162 }, 163 }} 164 label={_( 165 msg`View the labeling service provided by @${labeler.creator.handle}`, 166 )}> 167 {children} 168 </InternalLink> 169 ) 170} 171 172// TODO not finished yet 173export function DefaultSkeleton() { 174 return ( 175 <View> 176 <Text>Loading</Text> 177 </View> 178 ) 179} 180 181export function Loader({ 182 did, 183 loading: LoadingComponent = DefaultSkeleton, 184 error: ErrorComponent, 185 component: Component, 186}: { 187 did: string 188 loading?: React.ComponentType<{}> 189 error?: React.ComponentType<{error: string}> 190 component: React.ComponentType<{ 191 labeler: AppBskyLabelerDefs.LabelerViewDetailed 192 }> 193}) { 194 const {isLoading, data, error} = useLabelerInfoQuery({did}) 195 196 return isLoading ? ( 197 LoadingComponent ? ( 198 <LoadingComponent /> 199 ) : null 200 ) : error || !data ? ( 201 ErrorComponent ? ( 202 <ErrorComponent error={error?.message || 'Unknown error'} /> 203 ) : null 204 ) : ( 205 <Component labeler={data} /> 206 ) 207}