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