Bluesky app fork with some witchin' additions 馃挮
at feat/markdown-basic 148 lines 4.3 kB view raw
1import React, {useCallback} from 'react' 2import {type ListRenderItemInfo, View} from 'react-native' 3import { 4 type AppBskyActorDefs, 5 type AppBskyGraphGetList, 6 AtUri, 7 type ModerationOpts, 8} from '@atproto/api' 9import { 10 type InfiniteData, 11 type UseInfiniteQueryResult, 12} from '@tanstack/react-query' 13 14import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 15import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 16import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' 17import {useAllListMembersQuery} from '#/state/queries/list-members' 18import {useSession} from '#/state/session' 19import {List, type ListRef} from '#/view/com/util/List' 20import {type SectionRef} from '#/screens/Profile/Sections/types' 21import {atoms as a, useTheme} from '#/alf' 22import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 23import {Default as ProfileCard} from '#/components/ProfileCard' 24import {IS_NATIVE, IS_WEB} from '#/env' 25 26function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) { 27 return `${item.did}-${index}` 28} 29 30interface ProfilesListProps { 31 listUri: string 32 listMembersQuery: UseInfiniteQueryResult< 33 InfiniteData<AppBskyGraphGetList.OutputSchema> 34 > 35 moderationOpts: ModerationOpts 36 headerHeight: number 37 scrollElRef: ListRef 38} 39 40export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>( 41 function ProfilesListImpl( 42 {listUri, moderationOpts, headerHeight, scrollElRef}, 43 ref, 44 ) { 45 const t = useTheme() 46 const bottomBarOffset = useBottomBarOffset(headerHeight) 47 const initialNumToRender = useInitialNumToRender() 48 const {currentAccount} = useSession() 49 const {data, refetch, isError} = useAllListMembersQuery(listUri) 50 51 const [isPTRing, setIsPTRing] = React.useState(false) 52 53 // The server returns these sorted by descending creation date, so we want to invert 54 55 const profiles = data 56 ?.filter( 57 p => !isBlockedOrBlocking(p.subject) && !p.subject.associated?.labeler, 58 ) 59 .map(p => p.subject) 60 .reverse() 61 const isOwn = new AtUri(listUri).host === currentAccount?.did 62 63 const getSortedProfiles = () => { 64 if (!profiles) return 65 if (!isOwn) return profiles 66 67 const myIndex = profiles.findIndex(p => p.did === currentAccount?.did) 68 return myIndex !== -1 69 ? [ 70 profiles[myIndex], 71 ...profiles.slice(0, myIndex), 72 ...profiles.slice(myIndex + 1), 73 ] 74 : profiles 75 } 76 const onScrollToTop = useCallback(() => { 77 scrollElRef.current?.scrollToOffset({ 78 animated: IS_NATIVE, 79 offset: -headerHeight, 80 }) 81 }, [scrollElRef, headerHeight]) 82 83 React.useImperativeHandle(ref, () => ({ 84 scrollToTop: onScrollToTop, 85 })) 86 87 const renderItem = ({ 88 item, 89 index, 90 }: ListRenderItemInfo<AppBskyActorDefs.ProfileViewBasic>) => { 91 return ( 92 <View 93 style={[ 94 a.p_lg, 95 t.atoms.border_contrast_low, 96 (IS_WEB || index !== 0) && a.border_t, 97 ]}> 98 <ProfileCard 99 profile={item} 100 moderationOpts={moderationOpts} 101 logContext="StarterPackProfilesList" 102 /> 103 </View> 104 ) 105 } 106 107 if (!data) { 108 return ( 109 <View 110 style={[ 111 a.h_full_vh, 112 {marginTop: headerHeight, marginBottom: bottomBarOffset}, 113 ]}> 114 <ListMaybePlaceholder 115 isLoading={true} 116 isError={isError} 117 onRetry={refetch} 118 /> 119 </View> 120 ) 121 } 122 123 if (data) 124 return ( 125 <List 126 data={getSortedProfiles()} 127 renderItem={renderItem} 128 keyExtractor={keyExtractor} 129 ref={scrollElRef} 130 headerOffset={headerHeight} 131 ListFooterComponent={ 132 <ListFooter 133 style={{paddingBottom: bottomBarOffset, borderTopWidth: 0}} 134 /> 135 } 136 showsVerticalScrollIndicator={false} 137 desktopFixedHeight 138 initialNumToRender={initialNumToRender} 139 refreshing={isPTRing} 140 onRefresh={async () => { 141 setIsPTRing(true) 142 await refetch() 143 setIsPTRing(false) 144 }} 145 /> 146 ) 147 }, 148)