import React, {useMemo, useState} from 'react'
import {observer} from 'mobx-react-lite'
import {Linking, StyleSheet, View} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import {AtUri} from '@atproto/api'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {PostsFeedItemModel} from 'state/models/feeds/post'
import {FeedSourceInfo} from 'lib/api/feed/types'
import {Link, TextLinkOnWebOnly} from '../util/Link'
import {Text} from '../util/text/Text'
import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta'
import {PostCtrls} from '../util/post-ctrls/PostCtrls'
import {PostEmbeds} from '../util/post-embeds'
import {ContentHider} from '../util/moderation/ContentHider'
import {PostAlerts} from '../util/moderation/PostAlerts'
import {RichText} from '../util/text/RichText'
import {PostSandboxWarning} from '../util/PostSandboxWarning'
import * as Toast from '../util/Toast'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {s} from 'lib/styles'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {getTranslatorLink} from '../../../locale/helpers'
import {makeProfileLink} from 'lib/routes/links'
import {isEmbedByEmbedder} from 'lib/embeds'
export const FeedItem = observer(function FeedItemImpl({
item,
source,
isThreadChild,
isThreadLastChild,
isThreadParent,
}: {
item: PostsFeedItemModel
source?: FeedSourceInfo
isThreadChild?: boolean
isThreadLastChild?: boolean
isThreadParent?: boolean
showReplyLine?: boolean
}) {
const store = useStores()
const pal = usePalette('default')
const {track} = useAnalytics()
const [deleted, setDeleted] = useState(false)
const record = item.postRecord
const itemUri = item.post.uri
const itemCid = item.post.cid
const itemHref = useMemo(() => {
const urip = new AtUri(item.post.uri)
return makeProfileLink(item.post.author, 'post', urip.rkey)
}, [item.post.uri, item.post.author])
const itemTitle = `Post by ${item.post.author.handle}`
const replyAuthorDid = useMemo(() => {
if (!record?.reply) {
return ''
}
const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri)
return urip.hostname
}, [record?.reply])
const translatorUrl = getTranslatorLink(
record?.text || '',
store.preferences.primaryLanguage,
)
const onPressReply = React.useCallback(() => {
track('FeedItem:PostReply')
store.shell.openComposer({
replyTo: {
uri: item.post.uri,
cid: item.post.cid,
text: record?.text || '',
author: {
handle: item.post.author.handle,
displayName: item.post.author.displayName,
avatar: item.post.author.avatar,
},
},
})
}, [item, track, record, store])
const onPressToggleRepost = React.useCallback(() => {
track('FeedItem:PostRepost')
return item
.toggleRepost()
.catch(e => store.log.error('Failed to toggle repost', e))
}, [track, item, store])
const onPressToggleLike = React.useCallback(() => {
track('FeedItem:PostLike')
return item
.toggleLike()
.catch(e => store.log.error('Failed to toggle like', e))
}, [track, item, store])
const onCopyPostText = React.useCallback(() => {
Clipboard.setString(record?.text || '')
Toast.show('Copied to clipboard')
}, [record])
const onOpenTranslate = React.useCallback(() => {
Linking.openURL(translatorUrl)
}, [translatorUrl])
const onToggleThreadMute = React.useCallback(async () => {
track('FeedItem:ThreadMute')
try {
await item.toggleThreadMute()
if (item.isThreadMuted) {
Toast.show('You will no longer receive notifications for this thread')
} else {
Toast.show('You will now receive notifications for this thread')
}
} catch (e) {
store.log.error('Failed to toggle thread mute', e)
}
}, [track, item, store])
const onDeletePost = React.useCallback(() => {
track('FeedItem:PostDelete')
item.delete().then(
() => {
setDeleted(true)
Toast.show('Post deleted')
},
e => {
store.log.error('Failed to delete post', e)
Toast.show('Failed to delete post, please try again')
},
)
}, [track, item, setDeleted, store])
const outerStyles = [
styles.outer,
pal.view,
{
borderColor: pal.colors.border,
paddingBottom:
isThreadLastChild || (!isThreadChild && !isThreadParent)
? 6
: undefined,
},
isThreadChild ? styles.outerSmallTop : undefined,
]
if (!record || deleted) {
return
}
return (
{isThreadChild && (
)}
{source ? (
From{' '}
) : item.reasonRepost ? (
Reposted by{' '}
) : null}
{isThreadParent && (
)}
{!isThreadChild && replyAuthorDid !== '' && (
Reply to{' '}
)}
{item.richText?.text ? (
) : undefined}
{item.post.embed ? (
) : null}
)
})
const styles = StyleSheet.create({
outer: {
borderTopWidth: 1,
paddingLeft: 10,
paddingRight: 15,
cursor: 'pointer',
overflow: 'hidden',
},
outerSmallTop: {
borderTopWidth: 0,
},
replyLine: {
width: 2,
marginLeft: 'auto',
marginRight: 'auto',
},
includeReason: {
flexDirection: 'row',
marginTop: 2,
marginBottom: 2,
marginLeft: -20,
},
layout: {
flexDirection: 'row',
marginTop: 1,
gap: 10,
},
layoutAvi: {
paddingLeft: 8,
},
layoutContent: {
flex: 1,
},
alert: {
marginTop: 6,
marginBottom: 6,
},
postTextContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
paddingBottom: 4,
},
contentHiderChild: {
marginTop: 6,
},
embed: {
marginBottom: 6,
},
translateLink: {
marginBottom: 6,
},
})