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 choicezio/stable
parent
df7552135a
commit
6f69157269
|
@ -129,6 +129,7 @@ interface ScreenPropertiesMap {
|
|||
Feed: {}
|
||||
Notifications: {}
|
||||
Profile: {}
|
||||
'Profile:Preview': {}
|
||||
Settings: {}
|
||||
AppPasswords: {}
|
||||
Moderation: {}
|
||||
|
|
|
@ -31,6 +31,11 @@ export interface EditProfileModal {
|
|||
onUpdate?: () => void
|
||||
}
|
||||
|
||||
export interface ProfilePreviewModal {
|
||||
name: 'profile-preview'
|
||||
did: string
|
||||
}
|
||||
|
||||
export interface ServerInputModal {
|
||||
name: 'server-input'
|
||||
initialService: string
|
||||
|
@ -128,6 +133,7 @@ export type Modal =
|
|||
| ChangeHandleModal
|
||||
| DeleteAccountModal
|
||||
| EditProfileModal
|
||||
| ProfilePreviewModal
|
||||
|
||||
// Curation
|
||||
| ContentFilteringSettingsModal
|
||||
|
|
|
@ -9,6 +9,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
|
||||
import * as ConfirmModal from './Confirm'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
import * as ProfilePreviewModal from './ProfilePreview'
|
||||
import * as ServerInputModal from './ServerInput'
|
||||
import * as ReportPostModal from './report/ReportPost'
|
||||
import * as RepostModal from './Repost'
|
||||
|
@ -62,6 +63,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
|||
} else if (activeModal?.name === 'edit-profile') {
|
||||
snapPoints = EditProfileModal.snapPoints
|
||||
element = <EditProfileModal.Component {...activeModal} />
|
||||
} else if (activeModal?.name === 'profile-preview') {
|
||||
snapPoints = ProfilePreviewModal.snapPoints
|
||||
element = <ProfilePreviewModal.Component {...activeModal} />
|
||||
} else if (activeModal?.name === 'server-input') {
|
||||
snapPoints = ServerInputModal.snapPoints
|
||||
element = <ServerInputModal.Component {...activeModal} />
|
||||
|
|
|
@ -8,6 +8,7 @@ import {isMobileWeb} from 'platform/detection'
|
|||
|
||||
import * as ConfirmModal from './Confirm'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
import * as ProfilePreviewModal from './ProfilePreview'
|
||||
import * as ServerInputModal from './ServerInput'
|
||||
import * as ReportPostModal from './report/ReportPost'
|
||||
import * as ReportAccountModal from './report/ReportAccount'
|
||||
|
@ -68,6 +69,8 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||
element = <ConfirmModal.Component {...modal} />
|
||||
} else if (modal.name === 'edit-profile') {
|
||||
element = <EditProfileModal.Component {...modal} />
|
||||
} else if (modal.name === 'profile-preview') {
|
||||
element = <ProfilePreviewModal.Component {...modal} />
|
||||
} else if (modal.name === 'server-input') {
|
||||
element = <ServerInputModal.Component {...modal} />
|
||||
} else if (modal.name === 'report-post') {
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import React, {useState, useEffect, useCallback} from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {useNavigation, StackActions} from '@react-navigation/native'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {useStores} from 'state/index'
|
||||
import {ProfileModel} from 'state/models/content/profile'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {ProfileHeader} from '../profile/ProfileHeader'
|
||||
import {Button} from '../util/forms/Button'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
|
||||
export const snapPoints = [560]
|
||||
|
||||
export const Component = observer(({did}: {did: string}) => {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const palInverted = usePalette('inverted')
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const [model] = useState(new ProfileModel(store, {actor: did}))
|
||||
const {screen} = useAnalytics()
|
||||
|
||||
useEffect(() => {
|
||||
screen('Profile:Preview')
|
||||
model.setup()
|
||||
}, [model, screen])
|
||||
|
||||
const onPressViewProfile = useCallback(() => {
|
||||
navigation.dispatch(StackActions.push('Profile', {name: model.handle}))
|
||||
store.shell.closeModal()
|
||||
}, [navigation, store, model])
|
||||
|
||||
return (
|
||||
<View style={pal.view}>
|
||||
<View style={styles.headerWrapper}>
|
||||
<ProfileHeader view={model} hideBackButton onRefreshAll={() => {}} />
|
||||
</View>
|
||||
<View style={[styles.buttonsContainer, pal.view]}>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
type="inverted"
|
||||
style={[styles.button, styles.buttonWide]}
|
||||
onPress={onPressViewProfile}
|
||||
accessibilityLabel="View profile"
|
||||
accessibilityHint="">
|
||||
<Text type="button-lg" style={palInverted.text}>
|
||||
View Profile
|
||||
</Text>
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
style={styles.button}
|
||||
onPress={() => store.shell.closeModal()}
|
||||
accessibilityLabel="Close this preview"
|
||||
accessibilityHint="">
|
||||
<Text type="button-lg" style={pal.text}>
|
||||
Close
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerWrapper: {
|
||||
height: 440,
|
||||
},
|
||||
buttonsContainer: {
|
||||
height: 120,
|
||||
},
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
paddingHorizontal: 14,
|
||||
paddingTop: 16,
|
||||
},
|
||||
button: {
|
||||
flex: 2,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 12,
|
||||
},
|
||||
buttonWide: {
|
||||
flex: 3,
|
||||
},
|
||||
})
|
|
@ -13,7 +13,7 @@ import {RichText} from '../util/text/RichText'
|
|||
import {Text} from '../util/text/Text'
|
||||
import {PostDropdownBtn} from '../util/forms/DropdownButton'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||
import {s} from 'lib/styles'
|
||||
import {ago, niceDate} from 'lib/strings/time'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
|
@ -163,22 +163,17 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
<PostSandboxWarning />
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
<Link
|
||||
href={authorHref}
|
||||
title={authorTitle}
|
||||
asAnchor
|
||||
accessibilityLabel={`${item.post.author.handle}'s avatar`}
|
||||
accessibilityHint="">
|
||||
<UserAvatar
|
||||
size={52}
|
||||
avatar={item.post.author.avatar}
|
||||
moderation={item.moderation.avatar}
|
||||
/>
|
||||
</Link>
|
||||
<PreviewableUserAvatar
|
||||
size={52}
|
||||
did={item.post.author.did}
|
||||
handle={item.post.author.handle}
|
||||
avatar={item.post.author.avatar}
|
||||
moderation={item.moderation.avatar}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
<View style={[styles.meta, styles.metaExpandedLine1]}>
|
||||
<View style={[s.flexRow, s.alignBaseline]}>
|
||||
<View style={[s.flexRow]}>
|
||||
<Link
|
||||
style={styles.metaItem}
|
||||
href={authorHref}
|
||||
|
@ -353,13 +348,13 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
<PostSandboxWarning />
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
<Link href={authorHref} title={authorTitle} asAnchor>
|
||||
<UserAvatar
|
||||
size={52}
|
||||
avatar={item.post.author.avatar}
|
||||
moderation={item.moderation.avatar}
|
||||
/>
|
||||
</Link>
|
||||
<PreviewableUserAvatar
|
||||
size={52}
|
||||
did={item.post.author.did}
|
||||
handle={item.post.author.handle}
|
||||
avatar={item.post.author.avatar}
|
||||
moderation={item.moderation.avatar}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
<PostMeta
|
||||
|
@ -368,7 +363,6 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
authorHasWarning={!!item.post.author.labels?.length}
|
||||
timestamp={item.post.indexedAt}
|
||||
postHref={itemHref}
|
||||
did={item.post.author.did}
|
||||
/>
|
||||
<ContentHider
|
||||
moderation={item.moderation.thread}
|
||||
|
|
|
@ -229,7 +229,6 @@ const PostLoaded = observer(
|
|||
authorHasWarning={!!item.post.author.labels?.length}
|
||||
timestamp={item.post.indexedAt}
|
||||
postHref={itemHref}
|
||||
did={item.post.author.did}
|
||||
/>
|
||||
{replyAuthorDid !== '' && (
|
||||
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
|
||||
|
|
|
@ -28,7 +28,6 @@ const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
|||
export const Feed = observer(function Feed({
|
||||
feed,
|
||||
style,
|
||||
showPostFollowBtn,
|
||||
scrollElRef,
|
||||
onPressTryAgain,
|
||||
onScroll,
|
||||
|
@ -41,7 +40,6 @@ export const Feed = observer(function Feed({
|
|||
}: {
|
||||
feed: PostsFeedModel
|
||||
style?: StyleProp<ViewStyle>
|
||||
showPostFollowBtn?: boolean
|
||||
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
||||
onPressTryAgain?: () => void
|
||||
onScroll?: OnScrollCb
|
||||
|
@ -138,15 +136,9 @@ export const Feed = observer(function Feed({
|
|||
} else if (item === LOADING_ITEM) {
|
||||
return <PostFeedLoadingPlaceholder />
|
||||
}
|
||||
return <FeedSlice slice={item} showFollowBtn={showPostFollowBtn} />
|
||||
return <FeedSlice slice={item} />
|
||||
},
|
||||
[
|
||||
feed,
|
||||
onPressTryAgain,
|
||||
onPressRetryLoadMore,
|
||||
showPostFollowBtn,
|
||||
renderEmptyState,
|
||||
],
|
||||
[feed, onPressTryAgain, onPressRetryLoadMore, renderEmptyState],
|
||||
)
|
||||
|
||||
const FeedFooter = React.useCallback(
|
||||
|
|
|
@ -21,7 +21,7 @@ import {ImageHider} from '../util/moderation/ImageHider'
|
|||
import {RichText} from '../util/text/RichText'
|
||||
import {PostSandboxWarning} from '../util/PostSandboxWarning'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||
import {s} from 'lib/styles'
|
||||
import {useStores} from 'state/index'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
|
@ -33,14 +33,12 @@ export const FeedItem = observer(function ({
|
|||
item,
|
||||
isThreadChild,
|
||||
isThreadParent,
|
||||
showFollowBtn,
|
||||
ignoreMuteFor,
|
||||
}: {
|
||||
item: PostsFeedItemModel
|
||||
isThreadChild?: boolean
|
||||
isThreadParent?: boolean
|
||||
showReplyLine?: boolean
|
||||
showFollowBtn?: boolean
|
||||
ignoreMuteFor?: string
|
||||
}) {
|
||||
const store = useStores()
|
||||
|
@ -55,7 +53,6 @@ export const FeedItem = observer(function ({
|
|||
return `/profile/${item.post.author.handle}/post/${urip.rkey}`
|
||||
}, [item.post.uri, item.post.author.handle])
|
||||
const itemTitle = `Post by ${item.post.author.handle}`
|
||||
const authorHref = `/profile/${item.post.author.handle}`
|
||||
const replyAuthorDid = useMemo(() => {
|
||||
if (!record?.reply) {
|
||||
return ''
|
||||
|
@ -214,13 +211,13 @@ export const FeedItem = observer(function ({
|
|||
<PostSandboxWarning />
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
<Link href={authorHref} title={item.post.author.handle} asAnchor>
|
||||
<UserAvatar
|
||||
size={52}
|
||||
avatar={item.post.author.avatar}
|
||||
moderation={item.moderation.avatar}
|
||||
/>
|
||||
</Link>
|
||||
<PreviewableUserAvatar
|
||||
size={52}
|
||||
did={item.post.author.did}
|
||||
handle={item.post.author.handle}
|
||||
avatar={item.post.author.avatar}
|
||||
moderation={item.moderation.avatar}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
<PostMeta
|
||||
|
@ -229,8 +226,6 @@ export const FeedItem = observer(function ({
|
|||
authorHasWarning={!!item.post.author.labels?.length}
|
||||
timestamp={item.post.indexedAt}
|
||||
postHref={itemHref}
|
||||
did={item.post.author.did}
|
||||
showFollowBtn={showFollowBtn}
|
||||
/>
|
||||
{!isThreadChild && replyAuthorDid !== '' && (
|
||||
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
|
||||
|
@ -357,9 +352,9 @@ const styles = StyleSheet.create({
|
|||
layout: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 1,
|
||||
gap: 10,
|
||||
},
|
||||
layoutAvi: {
|
||||
width: 70,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
layoutContent: {
|
||||
|
|
|
@ -11,11 +11,9 @@ import {ModerationBehaviorCode} from 'lib/labeling/types'
|
|||
|
||||
export function FeedSlice({
|
||||
slice,
|
||||
showFollowBtn,
|
||||
ignoreMuteFor,
|
||||
}: {
|
||||
slice: PostsFeedSliceModel
|
||||
showFollowBtn?: boolean
|
||||
ignoreMuteFor?: string
|
||||
}) {
|
||||
if (slice.moderation.list.behavior === ModerationBehaviorCode.Hide) {
|
||||
|
@ -32,7 +30,6 @@ export function FeedSlice({
|
|||
item={slice.items[0]}
|
||||
isThreadParent={slice.isThreadParentAt(0)}
|
||||
isThreadChild={slice.isThreadChildAt(0)}
|
||||
showFollowBtn={showFollowBtn}
|
||||
ignoreMuteFor={ignoreMuteFor}
|
||||
/>
|
||||
<FeedItem
|
||||
|
@ -40,7 +37,6 @@ export function FeedSlice({
|
|||
item={slice.items[1]}
|
||||
isThreadParent={slice.isThreadParentAt(1)}
|
||||
isThreadChild={slice.isThreadChildAt(1)}
|
||||
showFollowBtn={showFollowBtn}
|
||||
ignoreMuteFor={ignoreMuteFor}
|
||||
/>
|
||||
<ViewFullThread slice={slice} />
|
||||
|
@ -49,7 +45,6 @@ export function FeedSlice({
|
|||
item={slice.items[last]}
|
||||
isThreadParent={slice.isThreadParentAt(last)}
|
||||
isThreadChild={slice.isThreadChildAt(last)}
|
||||
showFollowBtn={showFollowBtn}
|
||||
ignoreMuteFor={ignoreMuteFor}
|
||||
/>
|
||||
</>
|
||||
|
@ -64,7 +59,6 @@ export function FeedSlice({
|
|||
item={item}
|
||||
isThreadParent={slice.isThreadParentAt(i)}
|
||||
isThreadChild={slice.isThreadChildAt(i)}
|
||||
showFollowBtn={showFollowBtn}
|
||||
ignoreMuteFor={ignoreMuteFor}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -28,7 +28,6 @@ import {CogIcon} from 'lib/icons'
|
|||
export const MultiFeed = observer(function Feed({
|
||||
multifeed,
|
||||
style,
|
||||
showPostFollowBtn,
|
||||
scrollElRef,
|
||||
onScroll,
|
||||
scrollEventThrottle,
|
||||
|
@ -38,7 +37,6 @@ export const MultiFeed = observer(function Feed({
|
|||
}: {
|
||||
multifeed: PostsMultiFeedModel
|
||||
style?: StyleProp<ViewStyle>
|
||||
showPostFollowBtn?: boolean
|
||||
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
||||
onPressTryAgain?: () => void
|
||||
onScroll?: OnScrollCb
|
||||
|
@ -105,9 +103,7 @@ export const MultiFeed = observer(function Feed({
|
|||
</View>
|
||||
)
|
||||
} else if (item.type === 'feed-slice') {
|
||||
return (
|
||||
<FeedSlice slice={item.slice} showFollowBtn={showPostFollowBtn} />
|
||||
)
|
||||
return <FeedSlice slice={item.slice} />
|
||||
} else if (item.type === 'feed-loading') {
|
||||
return <PostFeedLoadingPlaceholder />
|
||||
} else if (item.type === 'feed-error') {
|
||||
|
@ -139,7 +135,7 @@ export const MultiFeed = observer(function Feed({
|
|||
}
|
||||
return null
|
||||
},
|
||||
[showPostFollowBtn, pal],
|
||||
[pal],
|
||||
)
|
||||
|
||||
const ListFooter = React.useCallback(
|
||||
|
|
|
@ -6,10 +6,7 @@ import {
|
|||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {BlurView} from '../util/BlurView'
|
||||
import {ProfileModel} from 'state/models/content/profile'
|
||||
|
@ -102,6 +99,7 @@ export const ProfileHeader = observer(
|
|||
const ProfileHeaderLoaded = observer(
|
||||
({view, onRefreshAll, hideBackButton = false}: Props) => {
|
||||
const pal = usePalette('default')
|
||||
const palInverted = usePalette('inverted')
|
||||
const store = useStores()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {track} = useAnalytics()
|
||||
|
@ -351,15 +349,15 @@ const ProfileHeaderLoaded = observer(
|
|||
<TouchableOpacity
|
||||
testID="followBtn"
|
||||
onPress={onPressToggleFollow}
|
||||
style={[styles.btn, styles.primaryBtn]}
|
||||
style={[styles.btn, styles.mainBtn, palInverted.view]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Follow ${view.handle}`}
|
||||
accessibilityHint={`Shows direct posts from ${view.handle} in your feed`}>
|
||||
<FontAwesomeIcon
|
||||
icon="plus"
|
||||
style={[s.white as FontAwesomeIconStyle, s.mr5]}
|
||||
style={[palInverted.text, s.mr5]}
|
||||
/>
|
||||
<Text type="button" style={[s.white, s.bold]}>
|
||||
<Text type="button" style={[palInverted.text, s.bold]}>
|
||||
Follow
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
@ -609,7 +607,6 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
|
||||
description: {
|
||||
flex: 1,
|
||||
marginBottom: 8,
|
||||
},
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -106,7 +106,6 @@ export const FeedsScreen = withAuthRequired(
|
|||
onScroll={onMainScroll}
|
||||
scrollEventThrottle={100}
|
||||
headerOffset={HEADER_OFFSET}
|
||||
showPostFollowBtn
|
||||
/>
|
||||
<ViewHeader
|
||||
title="My Feeds"
|
||||
|
|
|
@ -266,7 +266,6 @@ const FeedPage = observer(
|
|||
key="default"
|
||||
feed={feed}
|
||||
scrollElRef={scrollElRef}
|
||||
showPostFollowBtn
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
onScroll={onMainScroll}
|
||||
scrollEventThrottle={100}
|
||||
|
|
Loading…
Reference in New Issue