Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
120
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 283 lines 8.9 kB view raw
1import {View} from 'react-native' 2import {type AppBskyActorDefs} from '@atproto/api' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Trans} from '@lingui/react/macro' 6 7import {urls} from '#/lib/constants' 8import {getUserDisplayName} from '#/lib/getUserDisplayName' 9import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 10import {useModerationOpts} from '#/state/preferences/moderation-opts' 11import {useProfileQuery} from '#/state/queries/profile' 12import {useSession} from '#/state/session' 13import {atoms as a, useBreakpoints, useTheme} from '#/alf' 14import {Admonition} from '#/components/Admonition' 15import {Button, ButtonIcon, ButtonText} from '#/components/Button' 16import * as Dialog from '#/components/Dialog' 17import {useDialogControl} from '#/components/Dialog' 18import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 19import {Link} from '#/components/Link' 20import * as ProfileCard from '#/components/ProfileCard' 21import {Text} from '#/components/Typography' 22import {type FullVerificationState} from '#/components/verification' 23import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' 24import {useAnalytics} from '#/analytics' 25import type * as bsky from '#/types/bsky' 26 27export {useDialogControl} from '#/components/Dialog' 28 29export function VerificationsDialog({ 30 control, 31 profile, 32 verificationState, 33}: { 34 control: Dialog.DialogControlProps 35 profile: bsky.profile.AnyProfileView 36 verificationState: FullVerificationState 37}) { 38 return ( 39 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 40 <Dialog.Handle /> 41 <Inner 42 control={control} 43 profile={profile} 44 verificationState={verificationState} 45 /> 46 </Dialog.Outer> 47 ) 48} 49 50function Inner({ 51 profile, 52 control, 53 verificationState: state, 54}: { 55 control: Dialog.DialogControlProps 56 profile: bsky.profile.AnyProfileView 57 verificationState: FullVerificationState 58}) { 59 const t = useTheme() 60 const ax = useAnalytics() 61 const {_} = useLingui() 62 const {gtMobile} = useBreakpoints() 63 64 const userName = getUserDisplayName(profile) 65 const label = state.profile.isViewer 66 ? state.profile.isVerified 67 ? _(msg`You are verified`) 68 : _(msg`Your verifications`) 69 : state.profile.isVerified 70 ? _(msg`${userName} is verified`) 71 : _( 72 msg({ 73 message: `${userName}'s verifications`, 74 comment: `Possessive, meaning "the verifications of {userName}"`, 75 }), 76 ) 77 78 return ( 79 <Dialog.ScrollableInner 80 label={label} 81 style={[ 82 gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full, 83 ]}> 84 <View style={[a.gap_sm, a.pb_lg]}> 85 <Text style={[a.text_2xl, a.font_semi_bold, a.pr_4xl, a.leading_tight]}> 86 {label} 87 </Text> 88 <Text style={[a.text_md, a.leading_snug]}> 89 {state.profile.isVerified ? ( 90 <Trans> 91 This account has a checkmark because it's been verified by trusted 92 sources. 93 </Trans> 94 ) : ( 95 <Trans> 96 This account has one or more attempted verifications, but it is 97 not currently verified. 98 </Trans> 99 )} 100 </Text> 101 </View> 102 103 {profile.verification ? ( 104 <View style={[a.pb_xl, a.gap_md]}> 105 <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> 106 <Trans>Verified by:</Trans> 107 </Text> 108 109 <View style={[a.gap_lg]}> 110 {profile.verification.verifications.map(v => ( 111 <VerifierCard 112 key={v.uri} 113 verification={v} 114 subject={profile} 115 outerDialogControl={control} 116 /> 117 ))} 118 </View> 119 120 {profile.verification.verifications.some(v => !v.isValid) && 121 state.profile.isViewer && ( 122 <Admonition type="warning" style={[a.mt_xs]}> 123 <Trans>Some of your verifications are invalid.</Trans> 124 </Admonition> 125 )} 126 </View> 127 ) : null} 128 129 <View 130 style={[ 131 a.w_full, 132 a.gap_sm, 133 a.justify_end, 134 gtMobile 135 ? [a.flex_row, a.flex_row_reverse, a.justify_start] 136 : [a.flex_col], 137 ]}> 138 <Button 139 label={_(msg`Close dialog`)} 140 size="small" 141 variant="solid" 142 color="primary" 143 onPress={() => { 144 control.close() 145 }}> 146 <ButtonText> 147 <Trans>Close</Trans> 148 </ButtonText> 149 </Button> 150 <Link 151 overridePresentation 152 to={urls.website.blog.initialVerificationAnnouncement} 153 label={_( 154 msg({ 155 message: `Learn more about verification on Bluesky`, 156 context: `english-only-resource`, 157 }), 158 )} 159 size="small" 160 variant="solid" 161 color="secondary" 162 style={[a.justify_center]} 163 onPress={() => { 164 ax.metric('verification:learn-more', { 165 location: 'verificationsDialog', 166 }) 167 }}> 168 <ButtonText> 169 <Trans context="english-only-resource">Learn more</Trans> 170 </ButtonText> 171 </Link> 172 </View> 173 174 <Dialog.Close /> 175 </Dialog.ScrollableInner> 176 ) 177} 178 179function VerifierCard({ 180 verification, 181 subject, 182 outerDialogControl, 183}: { 184 verification: AppBskyActorDefs.VerificationView 185 subject: bsky.profile.AnyProfileView 186 outerDialogControl: Dialog.DialogControlProps 187}) { 188 const t = useTheme() 189 const {_, i18n} = useLingui() 190 const {currentAccount} = useSession() 191 const moderationOpts = useModerationOpts() 192 const {data: profile, error} = useProfileQuery({did: verification.issuer}) 193 const verificationRemovePromptControl = useDialogControl() 194 const canAdminister = verification.issuer === currentAccount?.did 195 196 const enableSquareButtons = useEnableSquareButtons() 197 198 return ( 199 <View 200 style={{ 201 opacity: verification.isValid ? 1 : 0.5, 202 }}> 203 <ProfileCard.Outer> 204 <ProfileCard.Header> 205 {error ? ( 206 <> 207 <ProfileCard.AvatarPlaceholder /> 208 <View style={[a.flex_1]}> 209 <Text 210 style={[a.text_md, a.font_semi_bold, a.leading_snug]} 211 numberOfLines={1}> 212 <Trans>Unknown verifier</Trans> 213 </Text> 214 <Text 215 emoji 216 style={[a.leading_snug, t.atoms.text_contrast_medium]} 217 numberOfLines={1}> 218 {verification.issuer} 219 </Text> 220 </View> 221 </> 222 ) : profile && moderationOpts ? ( 223 <> 224 <ProfileCard.Link 225 profile={profile} 226 style={[a.flex_row, a.align_center, a.gap_sm, a.flex_1]} 227 onPress={() => { 228 outerDialogControl.close() 229 }}> 230 <ProfileCard.Avatar 231 profile={profile} 232 moderationOpts={moderationOpts} 233 disabledPreview 234 /> 235 <View style={[a.flex_1]}> 236 <ProfileCard.Name 237 profile={profile} 238 moderationOpts={moderationOpts} 239 /> 240 <Text 241 emoji 242 style={[a.leading_snug, t.atoms.text_contrast_medium]} 243 numberOfLines={1}> 244 {i18n.date(new Date(verification.createdAt), { 245 dateStyle: 'long', 246 })} 247 </Text> 248 </View> 249 </ProfileCard.Link> 250 {canAdminister && ( 251 <View> 252 <Button 253 label={_(msg`Remove verification`)} 254 size="small" 255 variant="outline" 256 color="negative" 257 shape={enableSquareButtons ? 'square' : 'round'} 258 onPress={() => { 259 verificationRemovePromptControl.open() 260 }}> 261 <ButtonIcon icon={TrashIcon} /> 262 </Button> 263 </View> 264 )} 265 </> 266 ) : ( 267 <> 268 <ProfileCard.AvatarPlaceholder /> 269 <ProfileCard.NameAndHandlePlaceholder /> 270 </> 271 )} 272 </ProfileCard.Header> 273 </ProfileCard.Outer> 274 275 <VerificationRemovePrompt 276 control={verificationRemovePromptControl} 277 profile={subject} 278 verifications={[verification]} 279 onConfirm={() => outerDialogControl.close()} 280 /> 281 </View> 282 ) 283}