mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

at verify-code 243 lines 7.1 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' 4import {msg, Plural, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import {makeProfileLink} from '#/lib/routes/links' 8import {sanitizeDisplayName} from 'lib/strings/display-names' 9import {UserAvatar} from '#/view/com/util/UserAvatar' 10import {atoms as a, useTheme} from '#/alf' 11import {Link, LinkProps} from '#/components/Link' 12import {Text} from '#/components/Typography' 13 14const AVI_SIZE = 30 15const AVI_SIZE_SMALL = 20 16const AVI_BORDER = 1 17 18/** 19 * Shared logic to determine if `KnownFollowers` should be shown. 20 * 21 * Checks the # of actual returned users instead of the `count` value, because 22 * `count` includes blocked users and `followers` does not. 23 */ 24export function shouldShowKnownFollowers( 25 knownFollowers?: AppBskyActorDefs.KnownFollowers, 26) { 27 return knownFollowers && knownFollowers.followers.length > 0 28} 29 30export function KnownFollowers({ 31 profile, 32 moderationOpts, 33 onLinkPress, 34 minimal, 35}: { 36 profile: AppBskyActorDefs.ProfileViewDetailed 37 moderationOpts: ModerationOpts 38 onLinkPress?: LinkProps['onPress'] 39 minimal?: boolean 40}) { 41 const cache = React.useRef<Map<string, AppBskyActorDefs.KnownFollowers>>( 42 new Map(), 43 ) 44 45 /* 46 * Results for `knownFollowers` are not sorted consistently, so when 47 * revalidating we can see a flash of this data updating. This cache prevents 48 * this happening for screens that remain in memory. When pushing a new 49 * screen, or once this one is popped, this cache is empty, so new data is 50 * displayed. 51 */ 52 if (profile.viewer?.knownFollowers && !cache.current.has(profile.did)) { 53 cache.current.set(profile.did, profile.viewer.knownFollowers) 54 } 55 56 const cachedKnownFollowers = cache.current.get(profile.did) 57 58 if (cachedKnownFollowers && shouldShowKnownFollowers(cachedKnownFollowers)) { 59 return ( 60 <KnownFollowersInner 61 profile={profile} 62 cachedKnownFollowers={cachedKnownFollowers} 63 moderationOpts={moderationOpts} 64 onLinkPress={onLinkPress} 65 minimal={minimal} 66 /> 67 ) 68 } 69 70 return null 71} 72 73function KnownFollowersInner({ 74 profile, 75 moderationOpts, 76 cachedKnownFollowers, 77 onLinkPress, 78 minimal, 79}: { 80 profile: AppBskyActorDefs.ProfileViewDetailed 81 moderationOpts: ModerationOpts 82 cachedKnownFollowers: AppBskyActorDefs.KnownFollowers 83 onLinkPress?: LinkProps['onPress'] 84 minimal?: boolean 85}) { 86 const t = useTheme() 87 const {_} = useLingui() 88 89 const textStyle = [ 90 a.flex_1, 91 a.text_sm, 92 a.leading_snug, 93 t.atoms.text_contrast_medium, 94 ] 95 96 const slice = cachedKnownFollowers.followers.slice(0, 3).map(f => { 97 const moderation = moderateProfile(f, moderationOpts) 98 return { 99 profile: { 100 ...f, 101 displayName: sanitizeDisplayName( 102 f.displayName || f.handle, 103 moderation.ui('displayName'), 104 ), 105 }, 106 moderation, 107 } 108 }) 109 110 // Does not have blocks applied. Always >= slices.length 111 const serverCount = cachedKnownFollowers.count 112 113 /* 114 * We check above too, but here for clarity and a reminder to _check for 115 * valid indices_ 116 */ 117 if (slice.length === 0) return null 118 119 const SIZE = minimal ? AVI_SIZE_SMALL : AVI_SIZE 120 121 return ( 122 <Link 123 label={_( 124 msg`Press to view followers of this account that you also follow`, 125 )} 126 onPress={onLinkPress} 127 to={makeProfileLink(profile, 'known-followers')} 128 style={[ 129 a.flex_1, 130 a.flex_row, 131 minimal ? a.gap_sm : a.gap_md, 132 a.align_center, 133 {marginLeft: -AVI_BORDER}, 134 ]}> 135 {({hovered, pressed}) => ( 136 <> 137 <View 138 style={[ 139 { 140 height: SIZE, 141 width: SIZE + (slice.length - 1) * a.gap_md.gap, 142 }, 143 pressed && { 144 opacity: 0.5, 145 }, 146 ]}> 147 {slice.map(({profile: prof, moderation}, i) => ( 148 <View 149 key={prof.did} 150 style={[ 151 a.absolute, 152 a.rounded_full, 153 { 154 borderWidth: AVI_BORDER, 155 borderColor: t.atoms.bg.backgroundColor, 156 width: SIZE + AVI_BORDER * 2, 157 height: SIZE + AVI_BORDER * 2, 158 left: i * a.gap_md.gap, 159 zIndex: AVI_BORDER - i, 160 }, 161 ]}> 162 <UserAvatar 163 size={SIZE} 164 avatar={prof.avatar} 165 moderation={moderation.ui('avatar')} 166 /> 167 </View> 168 ))} 169 </View> 170 171 <Text 172 style={[ 173 textStyle, 174 hovered && { 175 textDecorationLine: 'underline', 176 textDecorationColor: t.atoms.text_contrast_medium.color, 177 }, 178 pressed && { 179 opacity: 0.5, 180 }, 181 ]} 182 numberOfLines={2}> 183 {slice.length >= 2 ? ( 184 // 2-n followers, including blocks 185 serverCount > 2 ? ( 186 <Trans> 187 Followed by{' '} 188 <Text key={slice[0].profile.did} style={textStyle}> 189 {slice[0].profile.displayName} 190 </Text> 191 ,{' '} 192 <Text key={slice[1].profile.did} style={textStyle}> 193 {slice[1].profile.displayName} 194 </Text> 195 , and{' '} 196 <Plural 197 value={serverCount - 2} 198 one="# other" 199 other="# others" 200 /> 201 </Trans> 202 ) : ( 203 // only 2 204 <Trans> 205 Followed by{' '} 206 <Text key={slice[0].profile.did} style={textStyle}> 207 {slice[0].profile.displayName} 208 </Text>{' '} 209 and{' '} 210 <Text key={slice[1].profile.did} style={textStyle}> 211 {slice[1].profile.displayName} 212 </Text> 213 </Trans> 214 ) 215 ) : serverCount > 1 ? ( 216 // 1-n followers, including blocks 217 <Trans> 218 Followed by{' '} 219 <Text key={slice[0].profile.did} style={textStyle}> 220 {slice[0].profile.displayName} 221 </Text>{' '} 222 and{' '} 223 <Plural 224 value={serverCount - 1} 225 one="# other" 226 other="# others" 227 /> 228 </Trans> 229 ) : ( 230 // only 1 231 <Trans> 232 Followed by{' '} 233 <Text key={slice[0].profile.did} style={textStyle}> 234 {slice[0].profile.displayName} 235 </Text> 236 </Trans> 237 )} 238 </Text> 239 </> 240 )} 241 </Link> 242 ) 243}