Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
119
fork

Configure Feed

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

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