[APP-782] Support invalid handles correctly (#1049)

* Update profile link construction to support handle.invalid

* Update list links  to support using handles

* Use did for isMe check to ensure invalid handles dont distort the check

* Shift the red (error) colors away from the pink spectrum

* Add ThemedText helper component

* Add sanitizedHandle() helper to render invalid handles well

* Fix regression: only show avatar in PostMeta when needed

* Restore the color of likes

* Remove users with invalid handles from default autosuggests
This commit is contained in:
Paul Frazee 2023-07-27 10:50:12 -05:00 committed by GitHub
parent 5a0899b989
commit 49356700c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 291 additions and 117 deletions

View file

@ -10,11 +10,13 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {FollowButton} from './FollowButton'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {
getProfileViewBasicLabelInfo,
getProfileModeration,
} from 'lib/labeling/helpers'
import {ModerationBehaviorCode} from 'lib/labeling/types'
import {makeProfileLink} from 'lib/routes/links'
export const ProfileCard = observer(
({
@ -60,7 +62,7 @@ export const ProfileCard = observer(
noBorder && styles.outerNoBorder,
!noBg && pal.view,
]}
href={`/profile/${profile.handle}`}
href={makeProfileLink(profile)}
title={profile.handle}
asAnchor
anchorNoUnderline>
@ -78,10 +80,12 @@ export const ProfileCard = observer(
style={[s.bold, pal.text]}
numberOfLines={1}
lineHeight={1.2}>
{sanitizeDisplayName(profile.displayName || profile.handle)}
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
)}
</Text>
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
@{profile.handle}
{sanitizeHandle(profile.handle, '@')}
</Text>
{!!profile.viewer?.followedBy && (
<View style={s.flexRow}>
@ -160,7 +164,7 @@ export const ProfileCardWithFollowBtn = observer(
followers?: AppBskyActorDefs.ProfileView[] | undefined
}) => {
const store = useStores()
const isMe = store.me.handle === profile.handle
const isMe = store.me.did === profile.did
return (
<ProfileCard

View file

@ -15,11 +15,13 @@ import {ProfileImageLightbox} from 'state/models/ui/shell'
import {pluralize} from 'lib/strings/helpers'
import {toShareUrl} from 'lib/strings/url-helpers'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {s, colors} from 'lib/styles'
import {DropdownButton, DropdownItem} from '../util/forms/DropdownButton'
import * as Toast from '../util/Toast'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {Text} from '../util/text/Text'
import {ThemedText} from '../util/text/ThemedText'
import {TextLink} from '../util/Link'
import {RichText} from '../util/text/RichText'
import {UserAvatar} from '../util/UserAvatar'
@ -34,6 +36,8 @@ import {FollowState} from 'state/models/cache/my-follows'
import {shareUrl} from 'lib/sharing'
import {formatCount} from '../util/numeric/format'
import {navigate} from '../../../Navigation'
import {isInvalidHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links'
const BACK_HITSLOP = {left: 30, top: 30, right: 30, bottom: 30}
@ -67,7 +71,9 @@ export const ProfileHeader = observer(
</View>
<View>
<Text type="title-2xl" style={[pal.text, styles.title]}>
{sanitizeDisplayName(view.displayName || view.handle)}
{sanitizeDisplayName(
view.displayName || sanitizeHandle(view.handle),
)}
</Text>
</View>
</View>
@ -104,6 +110,7 @@ const ProfileHeaderLoaded = observer(
const store = useStores()
const navigation = useNavigation<NavigationProp>()
const {track} = useAnalytics()
const invalidHandle = isInvalidHandle(view.handle)
const onPressBack = React.useCallback(() => {
navigation.goBack()
@ -144,19 +151,23 @@ const ProfileHeaderLoaded = observer(
const onPressFollowers = React.useCallback(() => {
track('ProfileHeader:FollowersButtonClicked')
navigate('ProfileFollowers', {name: view.handle})
navigate('ProfileFollowers', {
name: isInvalidHandle(view.handle) ? view.did : view.handle,
})
store.shell.closeAllActiveElements() // for when used in the profile preview modal
}, [track, view, store.shell])
const onPressFollows = React.useCallback(() => {
track('ProfileHeader:FollowsButtonClicked')
navigate('ProfileFollows', {name: view.handle})
navigate('ProfileFollows', {
name: isInvalidHandle(view.handle) ? view.did : view.handle,
})
store.shell.closeAllActiveElements() // for when used in the profile preview modal
}, [track, view, store.shell])
const onPressShare = React.useCallback(() => {
track('ProfileHeader:ShareButtonClicked')
const url = toShareUrl(`/profile/${view.handle}`)
const url = toShareUrl(makeProfileLink(view))
shareUrl(url)
}, [track, view])
@ -338,7 +349,7 @@ const ProfileHeaderLoaded = observer(
style={[styles.btn, styles.mainBtn, pal.btn]}
accessibilityRole="button"
accessibilityLabel={`Unfollow ${view.handle}`}
accessibilityHint={`Hides direct posts from ${view.handle} in your feed`}>
accessibilityHint={`Hides posts from ${view.handle} in your feed`}>
<FontAwesomeIcon
icon="check"
style={[pal.text, s.mr5]}
@ -355,7 +366,7 @@ const ProfileHeaderLoaded = observer(
style={[styles.btn, styles.mainBtn, palInverted.view]}
accessibilityRole="button"
accessibilityLabel={`Follow ${view.handle}`}
accessibilityHint={`Shows direct posts from ${view.handle} in your feed`}>
accessibilityHint={`Shows posts from ${view.handle} in your feed`}>
<FontAwesomeIcon
icon="plus"
style={[palInverted.text, s.mr5]}
@ -382,7 +393,9 @@ const ProfileHeaderLoaded = observer(
testID="profileHeaderDisplayName"
type="title-2xl"
style={[pal.text, styles.title]}>
{sanitizeDisplayName(view.displayName || view.handle)}
{sanitizeDisplayName(
view.displayName || sanitizeHandle(view.handle),
)}
</Text>
</View>
<View style={styles.handleLine}>
@ -393,7 +406,16 @@ const ProfileHeaderLoaded = observer(
</Text>
</View>
) : undefined}
<Text style={[pal.textLight, styles.handle]}>@{view.handle}</Text>
<ThemedText
type={invalidHandle ? 'xs' : 'md'}
fg={invalidHandle ? 'error' : 'light'}
border={invalidHandle ? 'error' : undefined}
style={[
invalidHandle ? styles.invalidHandle : undefined,
styles.handle,
]}>
{invalidHandle ? '⚠Invalid Handle' : `@${view.handle}`}
</ThemedText>
</View>
{!blockHide && (
<>
@ -600,6 +622,11 @@ const styles = StyleSheet.create({
// @ts-ignore web only -prf
wordBreak: 'break-all',
},
invalidHandle: {
borderWidth: 1,
borderRadius: 4,
paddingHorizontal: 4,
},
handleLine: {
flexDirection: 'row',