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