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