Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 243 lines 6.0 kB view raw
1import React 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 React.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}