Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at feat/custom-appview 183 lines 5.3 kB view raw
1import {useMemo, useState} from 'react' 2import {View} from 'react-native' 3import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' 4import {msg} from '@lingui/core/macro' 5import {useLingui} from '@lingui/react' 6import {Trans} from '@lingui/react/macro' 7import {differenceInSeconds} from 'date-fns' 8 9import {HITSLOP_10} from '#/lib/constants' 10import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 11import {sanitizeDisplayName} from '#/lib/strings/display-names' 12import {useModerationOpts} from '#/state/preferences/moderation-opts' 13import {useSession} from '#/state/session' 14import {atoms as a, useTheme, web} from '#/alf' 15import {Button, ButtonText} from '#/components/Button' 16import * as Dialog from '#/components/Dialog' 17import {useDialogControl} from '#/components/Dialog' 18import {Newskie} from '#/components/icons/Newskie' 19import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' 20import {Text} from '#/components/Typography' 21import {IS_NATIVE} from '#/env' 22 23export function NewskieDialog({ 24 profile, 25 disabled, 26}: { 27 profile: AppBskyActorDefs.ProfileViewDetailed 28 disabled?: boolean 29}) { 30 const {_} = useLingui() 31 const control = useDialogControl() 32 33 const createdAt = profile.createdAt 34 35 const [now] = useState(() => Date.now()) 36 const daysOld = useMemo(() => { 37 if (!createdAt) return Infinity 38 return differenceInSeconds(now, new Date(createdAt)) / 86400 39 }, [createdAt, now]) 40 41 if (!createdAt || daysOld > 7) return null 42 43 return ( 44 <View style={[a.pr_2xs]}> 45 <Button 46 disabled={disabled} 47 label={_( 48 msg`This user is new here. Press for more info about when they joined.`, 49 )} 50 hitSlop={HITSLOP_10} 51 onPress={control.open}> 52 {({hovered, pressed}) => ( 53 <Newskie 54 size="lg" 55 fill="#FFC404" 56 style={{ 57 opacity: hovered || pressed ? 0.5 : 1, 58 }} 59 /> 60 )} 61 </Button> 62 63 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 64 <Dialog.Handle /> 65 <DialogInner profile={profile} createdAt={createdAt} now={now} /> 66 </Dialog.Outer> 67 </View> 68 ) 69} 70 71function DialogInner({ 72 profile, 73 createdAt, 74 now, 75}: { 76 profile: AppBskyActorDefs.ProfileViewDetailed 77 createdAt: string 78 now: number 79}) { 80 const control = Dialog.useDialogContext() 81 const {_} = useLingui() 82 const t = useTheme() 83 const moderationOpts = useModerationOpts() 84 const {currentAccount} = useSession() 85 const timeAgo = useGetTimeAgo() 86 const isMe = profile.did === currentAccount?.did 87 88 const profileName = useMemo(() => { 89 if (!moderationOpts) return profile.displayName || profile.handle 90 const moderation = moderateProfile(profile, moderationOpts) 91 return sanitizeDisplayName( 92 profile.displayName || profile.handle, 93 moderation.ui('displayName'), 94 ) 95 }, [moderationOpts, profile]) 96 97 const getJoinMessage = () => { 98 const timeAgoString = timeAgo(createdAt, now, {format: 'long'}) 99 100 if (isMe) { 101 if (profile.joinedViaStarterPack) { 102 return _( 103 msg`You joined Bluesky using a starter pack ${timeAgoString} ago`, 104 ) 105 } else { 106 return _(msg`You joined Bluesky ${timeAgoString} ago`) 107 } 108 } else { 109 if (profile.joinedViaStarterPack) { 110 return _( 111 msg`${profileName} joined Bluesky using a starter pack ${timeAgoString} ago`, 112 ) 113 } else { 114 return _(msg`${profileName} joined Bluesky ${timeAgoString} ago`) 115 } 116 } 117 } 118 119 return ( 120 <Dialog.ScrollableInner 121 label={_(msg`New user info dialog`)} 122 style={web({maxWidth: 400})}> 123 <View style={[a.gap_md]}> 124 <View style={[a.align_center]}> 125 <View 126 style={[ 127 { 128 height: 60, 129 width: 64, 130 }, 131 ]}> 132 <Newskie 133 width={64} 134 height={64} 135 fill="#FFC404" 136 style={[a.absolute, a.inset_0]} 137 /> 138 </View> 139 <Text style={[a.font_semi_bold, a.text_xl]}> 140 {isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>} 141 </Text> 142 </View> 143 <Text style={[a.text_md, a.text_center, a.leading_snug]}> 144 {getJoinMessage()} 145 </Text> 146 {profile.joinedViaStarterPack ? ( 147 <StarterPackCard.Link 148 starterPack={profile.joinedViaStarterPack} 149 onPress={() => control.close()}> 150 <View 151 style={[ 152 a.w_full, 153 a.mt_sm, 154 a.p_lg, 155 a.border, 156 a.rounded_sm, 157 t.atoms.border_contrast_low, 158 ]}> 159 <StarterPackCard.Card 160 starterPack={profile.joinedViaStarterPack} 161 /> 162 </View> 163 </StarterPackCard.Link> 164 ) : null} 165 166 {IS_NATIVE && ( 167 <Button 168 label={_(msg`Close`)} 169 color="secondary" 170 size="small" 171 style={[a.mt_sm]} 172 onPress={() => control.close()}> 173 <ButtonText> 174 <Trans>Close</Trans> 175 </ButtonText> 176 </Button> 177 )} 178 </View> 179 180 <Dialog.Close /> 181 </Dialog.ScrollableInner> 182 ) 183}