import React from 'react' import {observer} from 'mobx-react-lite' import { Animated, TouchableOpacity, TouchableWithoutFeedback, StyleSheet, View, } from 'react-native' import {AppBskyEmbedImages} from '@atproto/api' import {AtUri} from '../../../third-party/uri' import { FontAwesomeIcon, FontAwesomeIconStyle, Props, } from '@fortawesome/react-native-fontawesome' import {NotificationsViewItemModel} from '../../../state/models/notifications-view' import {PostThreadViewModel} from '../../../state/models/post-thread-view' import {s, colors} from '../../lib/styles' import {ago, pluralize} from '../../../lib/strings' import {HeartIconSolid} from '../../lib/icons' import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' import {ImageHorzList} from '../util/images/ImageHorzList' import {ErrorMessage} from '../util/error/ErrorMessage' import {Post} from '../post/Post' import {Link} from '../util/Link' import {usePalette} from '../../lib/hooks/usePalette' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' const MAX_AUTHORS = 5 const EXPANDED_AUTHOR_EL_HEIGHT = 35 interface Author { href: string handle: string displayName?: string avatar?: string } export const FeedItem = observer(function FeedItem({ item, }: { item: NotificationsViewItemModel }) { const pal = usePalette('default') const [isAuthorsExpanded, setAuthorsExpanded] = React.useState(false) const itemHref = React.useMemo(() => { if (item.isUpvote || item.isRepost) { const urip = new AtUri(item.subjectUri) return `/profile/${urip.host}/post/${urip.rkey}` } else if (item.isFollow || item.isAssertion) { return `/profile/${item.author.handle}` } else if (item.isReply) { const urip = new AtUri(item.uri) return `/profile/${urip.host}/post/${urip.rkey}` } return '' }, [item]) const itemTitle = React.useMemo(() => { if (item.isUpvote || item.isRepost) { return 'Post' } else if (item.isFollow || item.isAssertion) { return item.author.handle } else if (item.isReply) { return 'Post' } }, [item]) const onToggleAuthorsExpanded = () => { setAuthorsExpanded(!isAuthorsExpanded) } if (item.additionalPost?.notFound) { // don't render anything if the target post was deleted or unfindable return } if (item.isReply || item.isMention) { return ( ) } let action = '' let icon: Props['icon'] | 'HeartIconSolid' let iconStyle: Props['style'] = [] if (item.isUpvote) { action = 'liked your post' icon = 'HeartIconSolid' iconStyle = [ s.red3 as FontAwesomeIconStyle, {position: 'relative', top: -4}, ] } else if (item.isRepost) { action = 'reposted your post' icon = 'retweet' iconStyle = [s.green3 as FontAwesomeIconStyle] } else if (item.isReply) { action = 'replied to your post' icon = ['far', 'comment'] } else if (item.isFollow) { action = 'followed you' icon = 'user-plus' iconStyle = [s.blue3 as FontAwesomeIconStyle] } else { return <> } let authors: Author[] = [ { href: `/profile/${item.author.handle}`, handle: item.author.handle, displayName: item.author.displayName, avatar: item.author.avatar, }, ] if (item.additional?.length) { authors = authors.concat( item.additional.map(item2 => ({ href: `/profile/${item2.author.handle}`, handle: item2.author.handle, displayName: item2.author.displayName, avatar: item2.author.avatar, })), ) } return ( {icon === 'HeartIconSolid' ? ( ) : ( )} 1 ? onToggleAuthorsExpanded : () => {}}> {authors[0].displayName || authors[0].handle} {authors.length > 1 ? ( <> and {authors.length - 1}{' '} {pluralize(authors.length - 1, 'other')} ) : undefined} {action} {ago(item.indexedAt)} {item.isUpvote || item.isRepost ? ( ) : ( <> )} ) }) function CondensedAuthorsList({ visible, authors, onToggleAuthorsExpanded, }: { visible: boolean authors: Author[] onToggleAuthorsExpanded: () => void }) { const pal = usePalette('default') if (!visible) { return ( Hide ) } if (authors.length === 1) { return ( ) } return ( {authors.slice(0, MAX_AUTHORS).map(author => ( ))} {authors.length > MAX_AUTHORS ? ( +{authors.length - MAX_AUTHORS} ) : undefined} ) } function ExpandedAuthorsList({ visible, authors, }: { visible: boolean authors: Author[] }) { const pal = usePalette('default') const heightInterp = useAnimatedValue(visible ? 1 : 0) const targetHeight = authors.length * (EXPANDED_AUTHOR_EL_HEIGHT + 10) /*10=margin*/ const heightStyle = { height: Animated.multiply(heightInterp, targetHeight), } React.useEffect(() => { Animated.timing(heightInterp, { toValue: visible ? 1 : 0, duration: 200, useNativeDriver: false, }).start() }, [heightInterp, visible]) return ( {authors.map(author => ( {author.displayName || author.handle}   {author.handle} ))} ) } function AdditionalPostText({ additionalPost, }: { additionalPost?: PostThreadViewModel }) { const pal = usePalette('default') if (!additionalPost || !additionalPost.thread?.postRecord) { return } if (additionalPost.error) { return } const text = additionalPost.thread?.postRecord.text const images = ( additionalPost.thread.post.embed as AppBskyEmbedImages.Presented )?.images return ( <> {text?.length > 0 && {text}} {images && images?.length > 0 && ( img.thumb)} style={styles.additionalPostImages} /> )} ) } const styles = StyleSheet.create({ overflowHidden: { overflow: 'hidden', }, outer: { padding: 10, paddingRight: 15, borderTopWidth: 1, }, outerUnread: { borderColor: colors.blue1, }, layout: { flexDirection: 'row', }, layoutIcon: { width: 70, alignItems: 'flex-end', paddingTop: 2, }, icon: { marginRight: 10, marginTop: 4, }, avis: { flexDirection: 'row', alignItems: 'center', }, aviExtraCount: { fontWeight: 'bold', paddingLeft: 6, }, layoutContent: { flex: 1, }, meta: { flexDirection: 'row', flexWrap: 'wrap', paddingTop: 6, paddingBottom: 2, }, metaItem: { paddingRight: 3, }, postText: { paddingBottom: 5, color: colors.black, }, additionalPostImages: { marginTop: 5, marginLeft: 2, opacity: 0.8, }, addedContainer: { paddingTop: 4, paddingLeft: 36, }, expandedAuthorsCloseBtn: { flexDirection: 'row', alignItems: 'center', paddingTop: 10, paddingBottom: 6, }, expandedAuthorsCloseBtnIcon: { marginLeft: 4, marginRight: 4, }, expandedAuthor: { flexDirection: 'row', alignItems: 'center', marginTop: 10, height: EXPANDED_AUTHOR_EL_HEIGHT, }, expandedAuthorAvi: { marginRight: 5, }, })