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:
Paul Frazee 2023-07-06 21:12:54 -05:00 committed by GitHub
parent df7552135a
commit 6f69157269
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 215 additions and 190 deletions

View file

@ -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

View file

@ -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}>
&nbsp;&middot;&nbsp;
</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}>
&middot;&nbsp;
</Text>
{!isAndroid && (
<Text
type="md"
style={pal.textLight}
lineHeight={1.2}
accessible={false}>
&middot;
</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,
},
})

View file

@ -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',