Fixes and improvements to the Profile Preview modal (#992)

* Fix: use more reliable navigation method

* Fix: show lightbox over the active modal

* Fix: close the profile preview on navigation

* Factor out UserPreviewLink and add preview behavior to notifications

* Fix postmeta overflow on web

* Fix lint
This commit is contained in:
Paul Frazee 2023-07-07 12:00:17 -05:00 committed by GitHub
parent d8aded7b15
commit 237e957d16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 101 additions and 59 deletions

View file

@ -279,6 +279,24 @@ export class ShellUiModel {
return false return false
} }
/**
* used to clear out any modals, eg for a navigation
*/
closeAllActiveElements() {
if (this.isLightboxActive) {
this.closeLightbox()
}
while (this.isModalActive) {
this.closeModal()
}
if (this.isComposerActive) {
this.closeComposer()
}
if (this.isDrawerOpen) {
this.closeDrawer()
}
}
openDrawer() { openDrawer() {
this.isDrawerOpen = true this.isDrawerOpen = true
} }

View file

@ -22,7 +22,8 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {HeartIconSolid} from 'lib/icons' import {HeartIconSolid} from 'lib/icons'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar, PreviewableUserAvatar} from '../util/UserAvatar'
import {UserPreviewLink} from '../util/UserPreviewLink'
import {ImageHorzList} from '../util/images/ImageHorzList' import {ImageHorzList} from '../util/images/ImageHorzList'
import {Post} from '../post/Post' import {Post} from '../post/Post'
import {Link, TextLink} from '../util/Link' import {Link, TextLink} from '../util/Link'
@ -42,6 +43,7 @@ const EXPANDED_AUTHOR_EL_HEIGHT = 35
interface Author { interface Author {
href: string href: string
did: string
handle: string handle: string
displayName?: string displayName?: string
avatar?: string avatar?: string
@ -91,6 +93,7 @@ export const FeedItem = observer(function ({
return [ return [
{ {
href: `/profile/${item.author.handle}`, href: `/profile/${item.author.handle}`,
did: item.author.did,
handle: item.author.handle, handle: item.author.handle,
displayName: item.author.displayName, displayName: item.author.displayName,
avatar: item.author.avatar, avatar: item.author.avatar,
@ -102,6 +105,7 @@ export const FeedItem = observer(function ({
...(item.additional?.map(({author}) => { ...(item.additional?.map(({author}) => {
return { return {
href: `/profile/${author.handle}`, href: `/profile/${author.handle}`,
did: author.did,
handle: author.handle, handle: author.handle,
displayName: author.displayName, displayName: author.displayName,
avatar: author.avatar, avatar: author.avatar,
@ -282,17 +286,13 @@ function CondensedAuthorsList({
if (authors.length === 1) { if (authors.length === 1) {
return ( return (
<View style={styles.avis}> <View style={styles.avis}>
<Link <PreviewableUserAvatar
style={s.mr5} size={35}
href={authors[0].href} did={authors[0].did}
title={`@${authors[0].handle}`} handle={authors[0].handle}
asAnchor> avatar={authors[0].avatar}
<UserAvatar moderation={authors[0].moderation.avatar}
size={35} />
avatar={authors[0].avatar}
moderation={authors[0].moderation.avatar}
/>
</Link>
</View> </View>
) )
} }
@ -356,12 +356,11 @@ function ExpandedAuthorsList({
visible ? s.mb10 : undefined, visible ? s.mb10 : undefined,
]}> ]}>
{authors.map(author => ( {authors.map(author => (
<Link <UserPreviewLink
key={author.href} key={author.did}
href={author.href} did={author.did}
title={sanitizeDisplayName(author.displayName || author.handle)} handle={author.handle}
style={styles.expandedAuthor} style={styles.expandedAuthor}>
asAnchor>
<View style={styles.expandedAuthorAvi}> <View style={styles.expandedAuthorAvi}>
<UserAvatar <UserAvatar
size={35} size={35}
@ -382,7 +381,7 @@ function ExpandedAuthorsList({
</Text> </Text>
</Text> </Text>
</View> </View>
</Link> </UserPreviewLink>
))} ))}
</Animated.View> </Animated.View>
) )

View file

@ -25,7 +25,7 @@ import {ImageHider} from '../util/moderation/ImageHider'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {RichText} from '../util/text/RichText' import {RichText} from '../util/text/RichText'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar' import {PreviewableUserAvatar} from '../util/UserAvatar'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -127,8 +127,6 @@ const PostLoaded = observer(
const itemUrip = new AtUri(item.post.uri) const itemUrip = new AtUri(item.post.uri)
const itemHref = `/profile/${item.post.author.handle}/post/${itemUrip.rkey}` const itemHref = `/profile/${item.post.author.handle}/post/${itemUrip.rkey}`
const itemTitle = `Post by ${item.post.author.handle}` const itemTitle = `Post by ${item.post.author.handle}`
const authorHref = `/profile/${item.post.author.handle}`
const authorTitle = item.post.author.handle
let replyAuthorDid = '' let replyAuthorDid = ''
if (record.reply) { if (record.reply) {
const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri) const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri)
@ -214,13 +212,13 @@ const PostLoaded = observer(
{showReplyLine && <View style={styles.replyLine} />} {showReplyLine && <View style={styles.replyLine} />}
<View style={styles.layout}> <View style={styles.layout}>
<View style={styles.layoutAvi}> <View style={styles.layoutAvi}>
<Link href={authorHref} title={authorTitle} asAnchor> <PreviewableUserAvatar
<UserAvatar size={52}
size={52} did={item.post.author.did}
avatar={item.post.author.avatar} handle={item.post.author.handle}
moderation={item.moderation.avatar} avatar={item.post.author.avatar}
/> moderation={item.moderation.avatar}
</Link> />
</View> </View>
<View style={styles.layoutContent}> <View style={styles.layoutContent}>
<PostMeta <PostMeta

View file

@ -33,6 +33,7 @@ import {isDesktopWeb, isNative} from 'platform/detection'
import {FollowState} from 'state/models/cache/my-follows' import {FollowState} from 'state/models/cache/my-follows'
import {shareUrl} from 'lib/sharing' import {shareUrl} from 'lib/sharing'
import {formatCount} from '../util/numeric/format' import {formatCount} from '../util/numeric/format'
import {navigate} from '../../../Navigation'
const BACK_HITSLOP = {left: 30, top: 30, right: 30, bottom: 30} const BACK_HITSLOP = {left: 30, top: 30, right: 30, bottom: 30}
@ -143,13 +144,15 @@ const ProfileHeaderLoaded = observer(
const onPressFollowers = React.useCallback(() => { const onPressFollowers = React.useCallback(() => {
track('ProfileHeader:FollowersButtonClicked') track('ProfileHeader:FollowersButtonClicked')
navigation.push('ProfileFollowers', {name: view.handle}) navigate('ProfileFollowers', {name: view.handle})
}, [track, navigation, view]) store.shell.closeAllActiveElements() // for when used in the profile preview modal
}, [track, view, store.shell])
const onPressFollows = React.useCallback(() => { const onPressFollows = React.useCallback(() => {
track('ProfileHeader:FollowsButtonClicked') track('ProfileHeader:FollowsButtonClicked')
navigation.push('ProfileFollows', {name: view.handle}) navigate('ProfileFollows', {name: view.handle})
}, [track, navigation, view]) store.shell.closeAllActiveElements() // for when used in the profile preview modal
}, [track, view, store.shell])
const onPressShare = React.useCallback(() => { const onPressShare = React.useCallback(() => {
track('ProfileHeader:ShareButtonClicked') track('ProfileHeader:ShareButtonClicked')

View file

@ -7,7 +7,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {UserAvatar} from './UserAvatar' import {UserAvatar} from './UserAvatar'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
import {isAndroid, isIOS} from 'platform/detection' import {isAndroid} from 'platform/detection'
interface PostMetaOpts { interface PostMetaOpts {
authorAvatar?: string authorAvatar?: string
@ -88,6 +88,6 @@ const styles = StyleSheet.create({
}, },
maxWidth: { maxWidth: {
flex: isAndroid ? 1 : undefined, flex: isAndroid ? 1 : undefined,
maxWidth: isIOS ? '80%' : undefined, maxWidth: !isAndroid ? '80%' : undefined,
}, },
}) })

View file

@ -1,5 +1,5 @@
import React, {useMemo} from 'react' import React, {useMemo} from 'react'
import {Pressable, StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import Svg, {Circle, Rect, Path} from 'react-native-svg' import Svg, {Circle, Rect, Path} from 'react-native-svg'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {IconProp} from '@fortawesome/fontawesome-svg-core' import {IconProp} from '@fortawesome/fontawesome-svg-core'
@ -12,12 +12,11 @@ import {
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {colors} from 'lib/styles' import {colors} from 'lib/styles'
import {DropdownButton} from './forms/DropdownButton' import {DropdownButton} from './forms/DropdownButton'
import {Link} from './Link'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isWeb, isAndroid} from 'platform/detection' import {isWeb, isAndroid} from 'platform/detection'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
import {AvatarModeration} from 'lib/labeling/types' import {AvatarModeration} from 'lib/labeling/types'
import {isDesktopWeb} from 'platform/detection' import {UserPreviewLink} from './UserPreviewLink'
type Type = 'user' | 'algo' | 'list' type Type = 'user' | 'algo' | 'list'
@ -257,28 +256,10 @@ export function UserAvatar({
} }
export function PreviewableUserAvatar(props: PreviewableUserAvatarProps) { export function PreviewableUserAvatar(props: PreviewableUserAvatarProps) {
const store = useStores()
if (isDesktopWeb) {
return (
<Link href={`/profile/${props.handle}`} title={props.handle} asAnchor>
<UserAvatar {...props} />
</Link>
)
}
return ( return (
<Pressable <UserPreviewLink did={props.did} handle={props.handle}>
onPress={() =>
store.shell.openModal({
name: 'profile-preview',
did: props.did,
})
}
accessibilityRole="button"
accessibilityLabel={props.handle}
accessibilityHint="">
<UserAvatar {...props} /> <UserAvatar {...props} />
</Pressable> </UserPreviewLink>
) )
} }

View file

@ -0,0 +1,43 @@
import React from 'react'
import {Pressable, StyleProp, ViewStyle} from 'react-native'
import {useStores} from 'state/index'
import {Link} from './Link'
import {isDesktopWeb} from 'platform/detection'
interface UserPreviewLinkProps {
did: string
handle: string
style?: StyleProp<ViewStyle>
}
export function UserPreviewLink(
props: React.PropsWithChildren<UserPreviewLinkProps>,
) {
const store = useStores()
if (isDesktopWeb) {
return (
<Link
href={`/profile/${props.handle}`}
title={props.handle}
asAnchor
style={props.style}>
{props.children}
</Link>
)
}
return (
<Pressable
onPress={() =>
store.shell.openModal({
name: 'profile-preview',
did: props.did,
})
}
accessibilityRole="button"
accessibilityLabel={props.handle}
accessibilityHint=""
style={props.style}>
{props.children}
</Pressable>
)
}

View file

@ -61,7 +61,6 @@ const ShellInner = observer(() => {
</Drawer> </Drawer>
</ErrorBoundary> </ErrorBoundary>
</View> </View>
<Lightbox />
<Composer <Composer
active={store.shell.isComposerActive} active={store.shell.isComposerActive}
onClose={() => store.shell.closeComposer()} onClose={() => store.shell.closeComposer()}
@ -71,6 +70,7 @@ const ShellInner = observer(() => {
quote={store.shell.composerOpts?.quote} quote={store.shell.composerOpts?.quote}
/> />
<ModalsContainer /> <ModalsContainer />
<Lightbox />
</> </>
) )
}) })