Bluesky app fork with some witchin' additions 馃挮
at main 220 lines 5.5 kB view raw
1import {type StyleProp, View, type ViewStyle} from 'react-native' 2import { 3 type $Typed, 4 AppBskyFeedDefs, 5 type AppBskyGraphDefs, 6 AtUri, 7} from '@atproto/api' 8import {msg, Plural, Trans} from '@lingui/macro' 9import {useLingui} from '@lingui/react' 10 11import {sanitizeHandle} from '#/lib/strings/handles' 12import { 13 type FeedSourceInfo, 14 hydrateFeedGenerator, 15 hydrateList, 16 useFeedSourceInfoQuery, 17} from '#/state/queries/feed' 18import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 19import {UserAvatar} from '#/view/com/util/UserAvatar' 20import {atoms as a, useTheme} from '#/alf' 21import {Link} from '#/components/Link' 22import {RichText} from '#/components/RichText' 23import {Text} from '#/components/Typography' 24import {MissingFeed} from './MissingFeed' 25 26type FeedSourceCardProps = { 27 feedUri: string 28 feedData?: 29 | $Typed<AppBskyFeedDefs.GeneratorView> 30 | $Typed<AppBskyGraphDefs.ListView> 31 style?: StyleProp<ViewStyle> 32 showSaveBtn?: boolean 33 showDescription?: boolean 34 showLikes?: boolean 35 pinOnSave?: boolean 36 showMinimalPlaceholder?: boolean 37 hideTopBorder?: boolean 38 link?: boolean 39} 40 41export function FeedSourceCard({ 42 feedUri, 43 feedData, 44 ...props 45}: FeedSourceCardProps) { 46 if (feedData) { 47 let feed: FeedSourceInfo 48 if (AppBskyFeedDefs.isGeneratorView(feedData)) { 49 feed = hydrateFeedGenerator(feedData) 50 } else { 51 feed = hydrateList(feedData) 52 } 53 return <FeedSourceCardLoaded feedUri={feedUri} feed={feed} {...props} /> 54 } else { 55 return <FeedSourceCardWithoutData feedUri={feedUri} {...props} /> 56 } 57} 58 59export function FeedSourceCardWithoutData({ 60 feedUri, 61 ...props 62}: Omit<FeedSourceCardProps, 'feedData'>) { 63 const {data: feed, error} = useFeedSourceInfoQuery({ 64 uri: feedUri, 65 }) 66 67 return ( 68 <FeedSourceCardLoaded 69 feedUri={feedUri} 70 feed={feed} 71 error={error} 72 {...props} 73 /> 74 ) 75} 76 77export function FeedSourceCardLoaded({ 78 feedUri, 79 feed, 80 style, 81 showDescription = false, 82 showLikes = false, 83 showMinimalPlaceholder, 84 hideTopBorder, 85 link = true, 86 error, 87}: { 88 feedUri: string 89 feed?: FeedSourceInfo 90 style?: StyleProp<ViewStyle> 91 showDescription?: boolean 92 showLikes?: boolean 93 showMinimalPlaceholder?: boolean 94 hideTopBorder?: boolean 95 link?: boolean 96 error?: unknown 97}) { 98 const t = useTheme() 99 const {_} = useLingui() 100 101 /* 102 * LOAD STATE 103 * 104 * This state also captures the scenario where a feed can't load for whatever 105 * reason. 106 */ 107 if (!feed) { 108 if (error) { 109 return ( 110 <MissingFeed 111 uri={feedUri} 112 style={style} 113 hideTopBorder={hideTopBorder} 114 error={error} 115 /> 116 ) 117 } 118 119 return ( 120 <FeedLoadingPlaceholder 121 style={[ 122 t.atoms.border_contrast_low, 123 !(showMinimalPlaceholder || hideTopBorder) && a.border_t, 124 a.flex_1, 125 style, 126 ]} 127 showTopBorder={false} 128 showLowerPlaceholder={!showMinimalPlaceholder} 129 /> 130 ) 131 } 132 133 const inner = ( 134 <> 135 <View style={[a.flex_row, a.align_center]}> 136 <View style={[a.mr_md]}> 137 <UserAvatar type="algo" size={36} avatar={feed.avatar} /> 138 </View> 139 <View style={[a.flex_1]}> 140 <Text 141 emoji 142 style={[a.text_sm, a.font_semi_bold, a.leading_snug]} 143 numberOfLines={1}> 144 {feed.displayName} 145 </Text> 146 <Text 147 style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]} 148 numberOfLines={1}> 149 {feed.type === 'feed' ? ( 150 <Trans>Feed by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 151 ) : ( 152 <Trans>List by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 153 )} 154 </Text> 155 </View> 156 </View> 157 {showDescription && feed.description ? ( 158 <RichText 159 style={[t.atoms.text_contrast_high, a.flex_1, a.flex_wrap]} 160 value={feed.description} 161 numberOfLines={3} 162 /> 163 ) : null} 164 {showLikes && feed.type === 'feed' ? ( 165 <Text 166 style={[ 167 a.text_sm, 168 a.font_semi_bold, 169 t.atoms.text_contrast_medium, 170 a.leading_snug, 171 ]}> 172 <Trans> 173 Liked by{' '} 174 <Plural value={feed.likeCount || 0} one="# user" other="# users" /> 175 </Trans> 176 </Text> 177 ) : null} 178 </> 179 ) 180 181 if (link) { 182 return ( 183 <Link 184 testID={`feed-${feed.displayName}`} 185 label={_( 186 feed.type === 'feed' 187 ? msg`${feed.displayName}, a feed by ${sanitizeHandle(feed.creatorHandle, '@')}, liked by ${feed.likeCount || 0}` 188 : msg`${feed.displayName}, a list by ${sanitizeHandle(feed.creatorHandle, '@')}`, 189 )} 190 to={{ 191 screen: feed.type === 'feed' ? 'ProfileFeed' : 'ProfileList', 192 params: {name: feed.creatorDid, rkey: new AtUri(feed.uri).rkey}, 193 }} 194 style={[ 195 a.flex_1, 196 a.p_lg, 197 a.gap_md, 198 !hideTopBorder && !a.border_t, 199 t.atoms.border_contrast_low, 200 style, 201 ]}> 202 {inner} 203 </Link> 204 ) 205 } else { 206 return ( 207 <View 208 style={[ 209 a.flex_1, 210 a.p_lg, 211 a.gap_md, 212 !hideTopBorder && !a.border_t, 213 t.atoms.border_contrast_low, 214 style, 215 ]}> 216 {inner} 217 </View> 218 ) 219 } 220}