Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 63fbf577d994ae0fd0e0ea9be75dfdfff7b0ddfb 239 lines 5.9 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import {type AtUri} from '@atproto/api' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import {PressableScale} from '#/lib/custom-animations/PressableScale' 8// import {makeProfileLink} from '#/lib/routes/links' 9// import {feedUriToHref} from '#/lib/strings/url-helpers' 10// import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' 11// import {CloseQuote_Filled_Stroke2_Corner0_Rounded as Quote} from '#/components/icons/Quote' 12// import {UserAvatar} from '#/view/com/util/UserAvatar' 13import {type TrendingTopic} from '#/state/queries/trending/useTrendingTopics' 14import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf' 15import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack' 16import {Link as InternalLink, type LinkProps} from '#/components/Link' 17import {Text} from '#/components/Typography' 18 19export function TrendingTopic({ 20 topic: raw, 21 size, 22 style, 23 hovered, 24}: { 25 topic: TrendingTopic 26 size?: 'large' | 'small' 27 hovered?: boolean 28} & ViewStyleProp) { 29 const topic = useTopic(raw) 30 31 const isSmall = size === 'small' 32 const hasIcon = topic.type === 'starter-pack' && !isSmall 33 const iconSize = 20 34 35 return ( 36 <View 37 style={[ 38 a.flex_row, 39 a.align_center, 40 isSmall 41 ? [ 42 { 43 paddingVertical: 2, 44 paddingHorizontal: 4, 45 }, 46 ] 47 : [a.py_xs, a.px_sm], 48 hasIcon && {gap: 6}, 49 style, 50 ]}> 51 {hasIcon && topic.type === 'starter-pack' && ( 52 <StarterPackIcon 53 gradient="sky" 54 width={iconSize} 55 style={{marginLeft: -3, marginVertical: -1}} 56 /> 57 )} 58 59 {/* 60 <View 61 style={[ 62 a.align_center, 63 a.justify_center, 64 a.rounded_full, 65 a.overflow_hidden, 66 { 67 width: iconSize, 68 height: iconSize, 69 }, 70 ]}> 71 {topic.type === 'tag' ? ( 72 <Hashtag width={iconSize} /> 73 ) : topic.type === 'topic' ? ( 74 <Quote width={iconSize - 2} /> 75 ) : topic.type === 'feed' ? ( 76 <UserAvatar 77 type="user" 78 size={aviSize} 79 avatar="" 80 /> 81 ) : ( 82 <UserAvatar 83 type="user" 84 size={aviSize} 85 avatar="" 86 /> 87 )} 88 </View> 89 */} 90 91 <Text 92 style={[ 93 a.font_semi_bold, 94 a.leading_tight, 95 isSmall ? [a.text_sm] : [a.text_md, {paddingBottom: 1}], 96 hovered && {textDecorationLine: 'underline'}, 97 ]} 98 numberOfLines={1}> 99 {topic.displayName} 100 </Text> 101 </View> 102 ) 103} 104 105export function TrendingTopicSkeleton({ 106 size = 'large', 107 index = 0, 108}: { 109 size?: 'large' | 'small' 110 index?: number 111}) { 112 const t = useTheme() 113 const isSmall = size === 'small' 114 return ( 115 <View 116 style={[ 117 a.rounded_full, 118 a.border, 119 t.atoms.border_contrast_medium, 120 t.atoms.bg_contrast_25, 121 isSmall 122 ? { 123 width: index % 2 === 0 ? 75 : 90, 124 height: 27, 125 } 126 : { 127 width: index % 2 === 0 ? 90 : 110, 128 height: 36, 129 }, 130 ]} 131 /> 132 ) 133} 134 135export function TrendingTopicLink({ 136 topic: raw, 137 children, 138 ...rest 139}: { 140 topic: TrendingTopic 141} & Omit<LinkProps, 'to' | 'label'>) { 142 const topic = useTopic(raw) 143 144 return ( 145 <InternalLink 146 label={topic.label} 147 to={topic.url} 148 PressableComponent={native(PressableScale)} 149 {...rest}> 150 {children} 151 </InternalLink> 152 ) 153} 154 155type ParsedTrendingTopic = 156 | { 157 type: 'topic' | 'tag' | 'starter-pack' | 'unknown' 158 label: string 159 displayName: string 160 url: string 161 uri: undefined 162 } 163 | { 164 type: 'profile' | 'feed' 165 label: string 166 displayName: string 167 url: string 168 uri: AtUri 169 } 170 171export function useTopic(raw: TrendingTopic): ParsedTrendingTopic { 172 const {_} = useLingui() 173 return React.useMemo(() => { 174 const {topic: displayName, link} = raw 175 176 if (link.startsWith('/search')) { 177 return { 178 type: 'topic', 179 label: _(msg`Browse posts about ${displayName}`), 180 displayName, 181 uri: undefined, 182 url: link, 183 } 184 } else if (link.startsWith('/hashtag')) { 185 return { 186 type: 'tag', 187 label: _(msg`Browse posts tagged with ${displayName}`), 188 displayName, 189 // displayName: displayName.replace(/^#/, ''), 190 uri: undefined, 191 url: link, 192 } 193 } else if (link.startsWith('/starter-pack')) { 194 return { 195 type: 'starter-pack', 196 label: _(msg`Browse starter pack ${displayName}`), 197 displayName, 198 uri: undefined, 199 url: link, 200 } 201 } 202 203 /* 204 if (!link.startsWith('at://')) { 205 // above logic 206 } else { 207 const urip = new AtUri(link) 208 switch (urip.collection) { 209 case 'app.bsky.actor.profile': { 210 return { 211 type: 'profile', 212 label: _(msg`View ${displayName}'s profile`), 213 displayName, 214 uri: urip, 215 url: makeProfileLink({did: urip.host, handle: urip.host}), 216 } 217 } 218 case 'app.bsky.feed.generator': { 219 return { 220 type: 'feed', 221 label: _(msg`Browse the ${displayName} feed`), 222 displayName, 223 uri: urip, 224 url: feedUriToHref(link), 225 } 226 } 227 } 228 } 229 */ 230 231 return { 232 type: 'unknown', 233 label: _(msg`Browse topic ${displayName}`), 234 displayName, 235 uri: undefined, 236 url: link, 237 } 238 }, [_, raw]) 239}