[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:
		
							parent
							
								
									5a0899b989
								
							
						
					
					
						commit
						49356700c3
					
				
					 33 changed files with 291 additions and 117 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue