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:
parent
d8aded7b15
commit
237e957d16
8 changed files with 101 additions and 59 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
43
src/view/com/util/UserPreviewLink.tsx
Normal file
43
src/view/com/util/UserPreviewLink.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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 />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue