mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at samuel/exp-cli 200 lines 5.5 kB view raw
1import {ScrollView, View} from 'react-native' 2import {moderateProfile, type ModerationOpts} from '@atproto/api' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5import {useNavigation} from '@react-navigation/native' 6 7import {type NavigationProp} from '#/lib/routes/types' 8import {sanitizeDisplayName} from '#/lib/strings/display-names' 9import {sanitizeHandle} from '#/lib/strings/handles' 10import {logger} from '#/logger' 11import {useModerationOpts} from '#/state/preferences/moderation-opts' 12import {useListConvosQuery} from '#/state/queries/messages/list-conversations' 13import {useSession} from '#/state/session' 14import {UserAvatar} from '#/view/com/util/UserAvatar' 15import {atoms as a, tokens, useTheme} from '#/alf' 16import {Button} from '#/components/Button' 17import {useDialogContext} from '#/components/Dialog' 18import {Text} from '#/components/Typography' 19import {useSimpleVerificationState} from '#/components/verification' 20import {VerificationCheck} from '#/components/verification/VerificationCheck' 21import type * as bsky from '#/types/bsky' 22 23export function RecentChats({postUri}: {postUri: string}) { 24 const control = useDialogContext() 25 const {_} = useLingui() 26 const {currentAccount} = useSession() 27 const {data} = useListConvosQuery({status: 'accepted'}) 28 const convos = data?.pages[0]?.convos?.slice(0, 10) 29 const moderationOpts = useModerationOpts() 30 const navigation = useNavigation<NavigationProp>() 31 32 const onSelectChat = (convoId: string) => { 33 control.close(() => { 34 logger.metric('share:press:recentDm', {}, {statsig: true}) 35 navigation.navigate('MessagesConversation', { 36 conversation: convoId, 37 embed: postUri, 38 }) 39 }) 40 } 41 42 if (!moderationOpts) return null 43 44 return ( 45 <View 46 style={[a.relative, a.flex_1, {marginHorizontal: tokens.space.md * -1}]}> 47 <ScrollView 48 horizontal 49 style={[a.flex_1, a.pt_2xs, {minHeight: 98}]} 50 contentContainerStyle={[a.gap_sm, a.px_md]} 51 showsHorizontalScrollIndicator={false} 52 fadingEdgeLength={64} 53 nestedScrollEnabled> 54 {convos && convos.length > 0 ? ( 55 convos.map(convo => { 56 const otherMember = convo.members.find( 57 member => member.did !== currentAccount?.did, 58 ) 59 60 if (!otherMember || otherMember.handle === 'missing.invalid') 61 return null 62 63 return ( 64 <RecentChatItem 65 key={convo.id} 66 profile={otherMember} 67 onPress={() => onSelectChat(convo.id)} 68 moderationOpts={moderationOpts} 69 /> 70 ) 71 }) 72 ) : ( 73 <> 74 <ConvoSkeleton /> 75 <ConvoSkeleton /> 76 <ConvoSkeleton /> 77 <ConvoSkeleton /> 78 <ConvoSkeleton /> 79 </> 80 )} 81 </ScrollView> 82 {convos && convos.length === 0 && <NoConvos />} 83 </View> 84 ) 85} 86 87const WIDTH = 80 88 89function RecentChatItem({ 90 profile, 91 onPress, 92 moderationOpts, 93}: { 94 profile: bsky.profile.AnyProfileView 95 onPress: () => void 96 moderationOpts: ModerationOpts 97}) { 98 const {_} = useLingui() 99 const t = useTheme() 100 101 const moderation = moderateProfile(profile, moderationOpts) 102 const name = sanitizeDisplayName( 103 profile.displayName || sanitizeHandle(profile.handle), 104 moderation.ui('displayName'), 105 ) 106 const verification = useSimpleVerificationState({profile}) 107 108 return ( 109 <Button 110 onPress={onPress} 111 label={_(msg`Send post to ${name}`)} 112 style={[ 113 a.flex_col, 114 {width: WIDTH}, 115 a.gap_sm, 116 a.justify_start, 117 a.align_center, 118 ]}> 119 <UserAvatar 120 avatar={profile.avatar} 121 size={WIDTH - 8} 122 type={profile.associated?.labeler ? 'labeler' : 'user'} 123 moderation={moderation.ui('avatar')} 124 /> 125 <View style={[a.flex_row, a.align_center, a.justify_center, a.w_full]}> 126 <Text 127 emoji 128 style={[a.text_xs, a.leading_snug, t.atoms.text_contrast_medium]} 129 numberOfLines={1}> 130 {name} 131 </Text> 132 {verification.showBadge && ( 133 <View style={[a.pl_2xs]}> 134 <VerificationCheck 135 width={10} 136 verifier={verification.role === 'verifier'} 137 /> 138 </View> 139 )} 140 </View> 141 </Button> 142 ) 143} 144 145function ConvoSkeleton() { 146 const t = useTheme() 147 return ( 148 <View 149 style={[ 150 a.flex_col, 151 {width: WIDTH, height: WIDTH + 15}, 152 a.gap_xs, 153 a.justify_start, 154 a.align_center, 155 ]}> 156 <View 157 style={[ 158 t.atoms.bg_contrast_50, 159 {width: WIDTH - 8, height: WIDTH - 8}, 160 a.rounded_full, 161 ]} 162 /> 163 <View 164 style={[ 165 t.atoms.bg_contrast_50, 166 {width: WIDTH - 8, height: 10}, 167 a.rounded_xs, 168 ]} 169 /> 170 </View> 171 ) 172} 173 174function NoConvos() { 175 const t = useTheme() 176 177 return ( 178 <View 179 style={[ 180 a.absolute, 181 a.inset_0, 182 a.justify_center, 183 a.align_center, 184 a.px_2xl, 185 ]}> 186 <View 187 style={[a.absolute, a.inset_0, t.atoms.bg_contrast_25, {opacity: 0.5}]} 188 /> 189 <Text 190 style={[ 191 a.text_sm, 192 t.atoms.text_contrast_high, 193 a.text_center, 194 a.font_bold, 195 ]}> 196 <Trans>Start a conversation, and it will appear here.</Trans> 197 </Text> 198 </View> 199 ) 200}