forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useMemo} from 'react'
2import {
3 type DimensionValue,
4 type StyleProp,
5 StyleSheet,
6 View,
7 type ViewStyle,
8} from 'react-native'
9
10import {s} from '#/lib/styles'
11import {atoms as a, useTheme} from '#/alf'
12import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble'
13import {
14 Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled,
15 Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
16} from '#/components/icons/Heart2'
17import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
18
19export function LoadingPlaceholder({
20 width,
21 height,
22 style,
23}: {
24 width: DimensionValue
25 height: DimensionValue | undefined
26 style?: StyleProp<ViewStyle>
27}) {
28 const t = useTheme()
29 return (
30 <View
31 style={[
32 styles.loadingPlaceholder,
33 {
34 width,
35 height,
36 backgroundColor: t.palette.contrast_50,
37 },
38 style,
39 ]}
40 />
41 )
42}
43
44export function PostLoadingPlaceholder({
45 style,
46}: {
47 style?: StyleProp<ViewStyle>
48}) {
49 const t = useTheme()
50 return (
51 <View style={[styles.post, style]}>
52 <LoadingPlaceholder
53 width={42}
54 height={42}
55 style={[
56 styles.avatar,
57 {
58 position: 'relative',
59 top: -6,
60 },
61 ]}
62 />
63 <View style={[s.flex1]}>
64 <LoadingPlaceholder width={100} height={6} style={{marginBottom: 10}} />
65 <LoadingPlaceholder width="95%" height={6} style={{marginBottom: 8}} />
66 <LoadingPlaceholder width="95%" height={6} style={{marginBottom: 8}} />
67 <LoadingPlaceholder width="80%" height={6} style={{marginBottom: 11}} />
68 <View style={styles.postCtrls}>
69 <View style={[styles.postCtrl, {marginLeft: -6}]}>
70 <View style={styles.postBtn}>
71 <Bubble
72 style={[
73 {
74 color: t.palette.contrast_500,
75 },
76 {pointerEvents: 'none'},
77 ]}
78 width={18}
79 />
80 </View>
81 </View>
82 <View style={styles.postCtrl}>
83 <View style={styles.postBtn}>
84 <Repost
85 style={[
86 {
87 color: t.palette.contrast_500,
88 },
89 {pointerEvents: 'none'},
90 ]}
91 width={18}
92 />
93 </View>
94 </View>
95 <View style={styles.postCtrl}>
96 <View style={styles.postBtn}>
97 <HeartIconOutline
98 style={[
99 {
100 color: t.palette.contrast_500,
101 },
102 {pointerEvents: 'none'},
103 ]}
104 width={18}
105 />
106 </View>
107 </View>
108 <View style={styles.postCtrl}>
109 <View style={[styles.postBtn, {minHeight: 30}]} />
110 </View>
111 </View>
112 </View>
113 </View>
114 )
115}
116
117export function PostFeedLoadingPlaceholder() {
118 return (
119 <View>
120 <PostLoadingPlaceholder />
121 <PostLoadingPlaceholder />
122 <PostLoadingPlaceholder />
123 <PostLoadingPlaceholder />
124 <PostLoadingPlaceholder />
125 <PostLoadingPlaceholder />
126 <PostLoadingPlaceholder />
127 <PostLoadingPlaceholder />
128 </View>
129 )
130}
131
132export function NotificationLoadingPlaceholder({
133 style,
134}: {
135 style?: StyleProp<ViewStyle>
136}) {
137 const t = useTheme()
138 return (
139 <View style={[styles.notification, style]}>
140 <View style={[{width: 60}, a.align_end, a.pr_sm, a.pt_2xs]}>
141 <HeartIconFilled size="xl" style={{color: t.palette.contrast_50}} />
142 </View>
143 <View style={{flex: 1}}>
144 <View style={[a.flex_row, s.mb10]}>
145 <LoadingPlaceholder
146 width={35}
147 height={35}
148 style={styles.smallAvatar}
149 />
150 </View>
151 <LoadingPlaceholder width="90%" height={6} style={[s.mb5]} />
152 <LoadingPlaceholder width="70%" height={6} style={[s.mb5]} />
153 </View>
154 </View>
155 )
156}
157
158export function NotificationFeedLoadingPlaceholder() {
159 return (
160 <>
161 <NotificationLoadingPlaceholder />
162 <NotificationLoadingPlaceholder />
163 <NotificationLoadingPlaceholder />
164 <NotificationLoadingPlaceholder />
165 <NotificationLoadingPlaceholder />
166 <NotificationLoadingPlaceholder />
167 <NotificationLoadingPlaceholder />
168 <NotificationLoadingPlaceholder />
169 <NotificationLoadingPlaceholder />
170 <NotificationLoadingPlaceholder />
171 <NotificationLoadingPlaceholder />
172 </>
173 )
174}
175
176export function ProfileCardLoadingPlaceholder({
177 style,
178}: {
179 style?: StyleProp<ViewStyle>
180}) {
181 return (
182 <View style={[styles.profileCard, style]}>
183 <LoadingPlaceholder
184 width={40}
185 height={40}
186 style={styles.profileCardAvi}
187 />
188 <View>
189 <LoadingPlaceholder width={140} height={8} style={[s.mb5]} />
190 <LoadingPlaceholder width={120} height={8} style={[s.mb10]} />
191 <LoadingPlaceholder width={220} height={8} style={[s.mb5]} />
192 </View>
193 </View>
194 )
195}
196
197export function ProfileCardFeedLoadingPlaceholder() {
198 return (
199 <>
200 <ProfileCardLoadingPlaceholder />
201 <ProfileCardLoadingPlaceholder />
202 <ProfileCardLoadingPlaceholder />
203 <ProfileCardLoadingPlaceholder />
204 <ProfileCardLoadingPlaceholder />
205 <ProfileCardLoadingPlaceholder />
206 <ProfileCardLoadingPlaceholder />
207 <ProfileCardLoadingPlaceholder />
208 <ProfileCardLoadingPlaceholder />
209 <ProfileCardLoadingPlaceholder />
210 <ProfileCardLoadingPlaceholder />
211 </>
212 )
213}
214
215export function FeedLoadingPlaceholder({
216 style,
217 showLowerPlaceholder = true,
218 showTopBorder = true,
219}: {
220 style?: StyleProp<ViewStyle>
221 showTopBorder?: boolean
222 showLowerPlaceholder?: boolean
223}) {
224 const t = useTheme()
225 return (
226 <View
227 style={[
228 {
229 padding: 16,
230 borderTopWidth: showTopBorder ? StyleSheet.hairlineWidth : 0,
231 },
232 t.atoms.border_contrast_low,
233 style,
234 ]}>
235 <View style={[{flexDirection: 'row'}]}>
236 <LoadingPlaceholder
237 width={36}
238 height={36}
239 style={[styles.avatar, {borderRadius: 8}]}
240 />
241 <View style={[s.flex1]}>
242 <LoadingPlaceholder width={100} height={8} style={[s.mt5, s.mb10]} />
243 <LoadingPlaceholder width={120} height={8} />
244 </View>
245 </View>
246 {showLowerPlaceholder && (
247 <View style={{marginTop: 12}}>
248 <LoadingPlaceholder width={120} height={8} />
249 </View>
250 )}
251 </View>
252 )
253}
254
255export function FeedFeedLoadingPlaceholder() {
256 return (
257 <>
258 <FeedLoadingPlaceholder />
259 <FeedLoadingPlaceholder />
260 <FeedLoadingPlaceholder />
261 <FeedLoadingPlaceholder />
262 <FeedLoadingPlaceholder />
263 <FeedLoadingPlaceholder />
264 <FeedLoadingPlaceholder />
265 <FeedLoadingPlaceholder />
266 <FeedLoadingPlaceholder />
267 <FeedLoadingPlaceholder />
268 <FeedLoadingPlaceholder />
269 </>
270 )
271}
272
273export function ChatListItemLoadingPlaceholder({
274 style,
275}: {
276 style?: StyleProp<ViewStyle>
277}) {
278 const t = useTheme()
279 const random = useMemo(() => Math.random(), [])
280 return (
281 <View style={[a.flex_row, a.gap_md, a.px_lg, a.mt_lg, t.atoms.bg, style]}>
282 <LoadingPlaceholder width={52} height={52} style={a.rounded_full} />
283 <View>
284 <LoadingPlaceholder width={140} height={12} style={a.mt_xs} />
285 <LoadingPlaceholder width={120} height={8} style={a.mt_sm} />
286 <LoadingPlaceholder
287 width={80 + random * 100}
288 height={8}
289 style={a.mt_sm}
290 />
291 </View>
292 </View>
293 )
294}
295
296export function ChatListLoadingPlaceholder() {
297 return (
298 <>
299 <ChatListItemLoadingPlaceholder />
300 <ChatListItemLoadingPlaceholder />
301 <ChatListItemLoadingPlaceholder />
302 <ChatListItemLoadingPlaceholder />
303 <ChatListItemLoadingPlaceholder />
304 <ChatListItemLoadingPlaceholder />
305 <ChatListItemLoadingPlaceholder />
306 <ChatListItemLoadingPlaceholder />
307 <ChatListItemLoadingPlaceholder />
308 <ChatListItemLoadingPlaceholder />
309 <ChatListItemLoadingPlaceholder />
310 </>
311 )
312}
313
314const styles = StyleSheet.create({
315 loadingPlaceholder: {
316 borderRadius: 6,
317 },
318 post: {
319 flexDirection: 'row',
320 alignItems: 'flex-start',
321 paddingHorizontal: 10,
322 paddingTop: 20,
323 paddingBottom: 5,
324 paddingRight: 15,
325 },
326 postCtrls: {
327 opacity: 0.5,
328 flexDirection: 'row',
329 justifyContent: 'space-between',
330 },
331 postCtrl: {
332 flex: 1,
333 },
334 postBtn: {
335 flex: 1,
336 flexDirection: 'row',
337 alignItems: 'center',
338 padding: 5,
339 },
340 avatar: {
341 borderRadius: 999,
342 marginRight: 12,
343 },
344 notification: {
345 flexDirection: 'row',
346 padding: 10,
347 },
348 profileCard: {
349 flexDirection: 'row',
350 padding: 10,
351 margin: 1,
352 },
353 profileCardAvi: {
354 borderRadius: 999,
355 marginRight: 10,
356 },
357 smallAvatar: {
358 borderRadius: 999,
359 marginRight: 10,
360 },
361})