Bluesky app fork with some witchin' additions 馃挮
at readme-update 367 lines 9.3 kB view raw
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})