Post UI updates (Profile Preview on mobile) (#990)
* Update postmeta to put the timestamp on the right side on mobile * Drop the two-line PostMeta mode * Add ProfilePreview modal * Tune PostMeta to give the best behavior possible for a given platform * Remove old showFollowBtn attributes * Fix style issue * Switch the follow button in the profile header to use the inverted color for consistency with the rest of the app * Fix lint * Fix darkmode * Tune the profile preview footer * Better analytics choice
This commit is contained in:
parent
df7552135a
commit
6f69157269
17 changed files with 215 additions and 190 deletions
|
@ -6,6 +6,7 @@ import {
|
|||
Platform,
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
TextProps,
|
||||
View,
|
||||
ViewStyle,
|
||||
TouchableOpacity,
|
||||
|
@ -144,7 +145,7 @@ export const TextLink = observer(function TextLink({
|
|||
numberOfLines?: number
|
||||
lineHeight?: number
|
||||
dataSet?: any
|
||||
}) {
|
||||
} & TextProps) {
|
||||
const {...props} = useLinkProps({to: sanitizeUrl(href)})
|
||||
const store = useStores()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
@ -186,16 +187,7 @@ export const TextLink = observer(function TextLink({
|
|||
/**
|
||||
* Only acts as a link on desktop web
|
||||
*/
|
||||
export const DesktopWebTextLink = observer(function DesktopWebTextLink({
|
||||
testID,
|
||||
type = 'md',
|
||||
style,
|
||||
href,
|
||||
text,
|
||||
numberOfLines,
|
||||
lineHeight,
|
||||
...props
|
||||
}: {
|
||||
interface DesktopWebTextLinkProps extends TextProps {
|
||||
testID?: string
|
||||
type?: TypographyVariant
|
||||
style?: StyleProp<TextStyle>
|
||||
|
@ -206,7 +198,17 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
|
|||
accessible?: boolean
|
||||
accessibilityLabel?: string
|
||||
accessibilityHint?: string
|
||||
}) {
|
||||
}
|
||||
export const DesktopWebTextLink = observer(function DesktopWebTextLink({
|
||||
testID,
|
||||
type = 'md',
|
||||
style,
|
||||
href,
|
||||
text,
|
||||
numberOfLines,
|
||||
lineHeight,
|
||||
...props
|
||||
}: DesktopWebTextLinkProps) {
|
||||
if (isDesktopWeb) {
|
||||
return (
|
||||
<TextLink
|
||||
|
|
|
@ -4,12 +4,10 @@ import {Text} from './text/Text'
|
|||
import {DesktopWebTextLink} from './Link'
|
||||
import {ago, niceDate} from 'lib/strings/time'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useStores} from 'state/index'
|
||||
import {UserAvatar} from './UserAvatar'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {FollowButton} from '../profile/FollowButton'
|
||||
import {FollowState} from 'state/models/cache/my-follows'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {isAndroid, isIOS} from 'platform/detection'
|
||||
|
||||
interface PostMetaOpts {
|
||||
authorAvatar?: string
|
||||
|
@ -18,88 +16,17 @@ interface PostMetaOpts {
|
|||
authorHasWarning: boolean
|
||||
postHref: string
|
||||
timestamp: string
|
||||
did?: string
|
||||
showFollowBtn?: boolean
|
||||
}
|
||||
|
||||
export const PostMeta = observer(function (opts: PostMetaOpts) {
|
||||
const pal = usePalette('default')
|
||||
const displayName = opts.authorDisplayName || opts.authorHandle
|
||||
const handle = opts.authorHandle
|
||||
const store = useStores()
|
||||
const isMe = opts.did === store.me.did
|
||||
const followState =
|
||||
typeof opts.did === 'string'
|
||||
? store.me.follows.getFollowState(opts.did)
|
||||
: FollowState.Unknown
|
||||
|
||||
const [didFollow, setDidFollow] = React.useState(false)
|
||||
const onToggleFollow = React.useCallback(() => {
|
||||
setDidFollow(true)
|
||||
}, [setDidFollow])
|
||||
|
||||
if (
|
||||
opts.showFollowBtn &&
|
||||
!isMe &&
|
||||
(followState === FollowState.NotFollowing || didFollow) &&
|
||||
opts.did
|
||||
) {
|
||||
// two-liner with follow button
|
||||
return (
|
||||
<View style={styles.metaTwoLine}>
|
||||
<View style={styles.metaTwoLineLeft}>
|
||||
<View style={styles.metaTwoLineTop}>
|
||||
<DesktopWebTextLink
|
||||
type="lg-bold"
|
||||
style={pal.text}
|
||||
numberOfLines={1}
|
||||
lineHeight={1.2}
|
||||
text={sanitizeDisplayName(displayName)}
|
||||
href={`/profile/${opts.authorHandle}`}
|
||||
/>
|
||||
<Text
|
||||
type="md"
|
||||
style={pal.textLight}
|
||||
lineHeight={1.2}
|
||||
accessible={false}>
|
||||
·
|
||||
</Text>
|
||||
<DesktopWebTextLink
|
||||
type="md"
|
||||
style={[styles.metaItem, pal.textLight]}
|
||||
lineHeight={1.2}
|
||||
text={ago(opts.timestamp)}
|
||||
accessibilityLabel={niceDate(opts.timestamp)}
|
||||
accessibilityHint=""
|
||||
href={opts.postHref}
|
||||
/>
|
||||
</View>
|
||||
<DesktopWebTextLink
|
||||
type="md"
|
||||
style={[styles.metaItem, pal.textLight]}
|
||||
lineHeight={1.2}
|
||||
numberOfLines={1}
|
||||
text={`@${handle}`}
|
||||
href={`/profile/${opts.authorHandle}`}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FollowButton
|
||||
unfollowedType="default"
|
||||
did={opts.did}
|
||||
onToggleFollow={onToggleFollow}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
// one-liner
|
||||
return (
|
||||
<View style={styles.meta}>
|
||||
<View style={styles.metaOneLine}>
|
||||
{typeof opts.authorAvatar !== 'undefined' && (
|
||||
<View style={[styles.metaItem, styles.avatar]}>
|
||||
<View style={styles.avatar}>
|
||||
<UserAvatar
|
||||
avatar={opts.authorAvatar}
|
||||
size={16}
|
||||
|
@ -107,7 +34,7 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
|
|||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={[styles.metaItem, styles.maxWidth]}>
|
||||
<View style={styles.maxWidth}>
|
||||
<DesktopWebTextLink
|
||||
type="lg-bold"
|
||||
style={pal.text}
|
||||
|
@ -128,12 +55,18 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
|
|||
href={`/profile/${opts.authorHandle}`}
|
||||
/>
|
||||
</View>
|
||||
<Text type="md" style={pal.textLight} lineHeight={1.2} accessible={false}>
|
||||
·
|
||||
</Text>
|
||||
{!isAndroid && (
|
||||
<Text
|
||||
type="md"
|
||||
style={pal.textLight}
|
||||
lineHeight={1.2}
|
||||
accessible={false}>
|
||||
·
|
||||
</Text>
|
||||
)}
|
||||
<DesktopWebTextLink
|
||||
type="md"
|
||||
style={[styles.metaItem, pal.textLight]}
|
||||
style={pal.textLight}
|
||||
lineHeight={1.2}
|
||||
text={ago(opts.timestamp)}
|
||||
accessibilityLabel={niceDate(opts.timestamp)}
|
||||
|
@ -145,32 +78,16 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
|
|||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
meta: {
|
||||
metaOneLine: {
|
||||
flexDirection: 'row',
|
||||
paddingBottom: 2,
|
||||
},
|
||||
metaTwoLine: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
paddingBottom: 4,
|
||||
},
|
||||
metaTwoLineLeft: {
|
||||
flex: 1,
|
||||
paddingRight: 40,
|
||||
},
|
||||
metaTwoLineTop: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
metaItem: {
|
||||
paddingRight: 5,
|
||||
gap: 4,
|
||||
},
|
||||
avatar: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
maxWidth: {
|
||||
maxWidth: '80%',
|
||||
flex: isAndroid ? 1 : undefined,
|
||||
maxWidth: isIOS ? '80%' : undefined,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, {useMemo} from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {Pressable, StyleSheet, View} from 'react-native'
|
||||
import Svg, {Circle, Rect, Path} from 'react-native-svg'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||
|
@ -12,13 +12,31 @@ import {
|
|||
import {useStores} from 'state/index'
|
||||
import {colors} from 'lib/styles'
|
||||
import {DropdownButton} from './forms/DropdownButton'
|
||||
import {Link} from './Link'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {isWeb, isAndroid} from 'platform/detection'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {AvatarModeration} from 'lib/labeling/types'
|
||||
import {isDesktopWeb} from 'platform/detection'
|
||||
|
||||
type Type = 'user' | 'algo' | 'list'
|
||||
|
||||
interface BaseUserAvatarProps {
|
||||
type?: Type
|
||||
size: number
|
||||
avatar?: string | null
|
||||
moderation?: AvatarModeration
|
||||
}
|
||||
|
||||
interface UserAvatarProps extends BaseUserAvatarProps {
|
||||
onSelectNewAvatar?: (img: RNImage | null) => void
|
||||
}
|
||||
|
||||
interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
|
||||
did: string
|
||||
handle: string
|
||||
}
|
||||
|
||||
const BLUR_AMOUNT = isWeb ? 5 : 100
|
||||
|
||||
function DefaultAvatar({type, size}: {type: Type; size: number}) {
|
||||
|
@ -91,13 +109,7 @@ export function UserAvatar({
|
|||
avatar,
|
||||
moderation,
|
||||
onSelectNewAvatar,
|
||||
}: {
|
||||
type?: Type
|
||||
size: number
|
||||
avatar?: string | null
|
||||
moderation?: AvatarModeration
|
||||
onSelectNewAvatar?: (img: RNImage | null) => void
|
||||
}) {
|
||||
}: UserAvatarProps) {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
||||
|
@ -244,6 +256,32 @@ export function UserAvatar({
|
|||
)
|
||||
}
|
||||
|
||||
export function PreviewableUserAvatar(props: PreviewableUserAvatarProps) {
|
||||
const store = useStores()
|
||||
|
||||
if (isDesktopWeb) {
|
||||
return (
|
||||
<Link href={`/profile/${props.handle}`} title={props.handle} asAnchor>
|
||||
<UserAvatar {...props} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
store.shell.openModal({
|
||||
name: 'profile-preview',
|
||||
did: props.did,
|
||||
})
|
||||
}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={props.handle}
|
||||
accessibilityHint="">
|
||||
<UserAvatar {...props} />
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
editButtonContainer: {
|
||||
position: 'absolute',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue