diff options
Diffstat (limited to 'src/view/com/notifications/FeedItem.tsx')
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 185 |
1 files changed, 91 insertions, 94 deletions
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index c38ab3fd5..aaa2ea2c6 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -1,5 +1,4 @@ -import React, {useMemo, useState, useEffect} from 'react' -import {observer} from 'mobx-react-lite' +import React, {memo, useMemo, useState, useEffect} from 'react' import { Animated, TouchableOpacity, @@ -9,6 +8,9 @@ import { } from 'react-native' import { AppBskyEmbedImages, + AppBskyFeedDefs, + AppBskyFeedPost, + ModerationOpts, ProfileModeration, moderateProfile, AppBskyEmbedRecordWithMedia, @@ -19,8 +21,7 @@ import { FontAwesomeIconStyle, Props, } from '@fortawesome/react-native-fontawesome' -import {NotificationsFeedItemModel} from 'state/models/feeds/notifications' -import {PostThreadModel} from 'state/models/content/post-thread' +import {FeedNotification} from '#/state/queries/notifications/feed' import {s, colors} from 'lib/styles' import {niceDate} from 'lib/strings/time' import {sanitizeDisplayName} from 'lib/strings/display-names' @@ -33,13 +34,14 @@ import {UserPreviewLink} from '../util/UserPreviewLink' import {ImageHorzList} from '../util/images/ImageHorzList' import {Post} from '../post/Post' import {Link, TextLink} from '../util/Link' -import {useStores} from 'state/index' import {usePalette} from 'lib/hooks/usePalette' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {formatCount} from '../util/numeric/format' import {makeProfileLink} from 'lib/routes/links' import {TimeElapsed} from '../util/TimeElapsed' import {isWeb} from 'platform/detection' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' const MAX_AUTHORS = 5 @@ -54,40 +56,34 @@ interface Author { moderation: ProfileModeration } -export const FeedItem = observer(function FeedItemImpl({ +let FeedItem = ({ item, + moderationOpts, }: { - item: NotificationsFeedItemModel -}) { - const store = useStores() + item: FeedNotification + moderationOpts: ModerationOpts +}): React.ReactNode => { const pal = usePalette('default') const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false) const itemHref = useMemo(() => { - if (item.isLike || item.isRepost) { - const urip = new AtUri(item.subjectUri) - return `/profile/${urip.host}/post/${urip.rkey}` - } else if (item.isFollow) { - return makeProfileLink(item.author) - } else if (item.isReply) { - const urip = new AtUri(item.uri) + if (item.type === 'post-like' || item.type === 'repost') { + if (item.subjectUri) { + const urip = new AtUri(item.subjectUri) + return `/profile/${urip.host}/post/${urip.rkey}` + } + } else if (item.type === 'follow') { + return makeProfileLink(item.notification.author) + } else if (item.type === 'reply') { + const urip = new AtUri(item.notification.uri) return `/profile/${urip.host}/post/${urip.rkey}` - } else if (item.isCustomFeedLike) { - const urip = new AtUri(item.subjectUri) - return `/profile/${urip.host}/feed/${urip.rkey}` + } else if (item.type === 'feedgen-like') { + if (item.subjectUri) { + const urip = new AtUri(item.subjectUri) + return `/profile/${urip.host}/feed/${urip.rkey}` + } } return '' }, [item]) - const itemTitle = useMemo(() => { - if (item.isLike || item.isRepost) { - return 'Post' - } else if (item.isFollow) { - return item.author.handle - } else if (item.isReply) { - return 'Post' - } else if (item.isCustomFeedLike) { - return 'Custom Feed' - } - }, [item]) const onToggleAuthorsExpanded = () => { setAuthorsExpanded(currentlyExpanded => !currentlyExpanded) @@ -96,15 +92,12 @@ export const FeedItem = observer(function FeedItemImpl({ const authors: Author[] = useMemo(() => { return [ { - href: makeProfileLink(item.author), - did: item.author.did, - handle: item.author.handle, - displayName: item.author.displayName, - avatar: item.author.avatar, - moderation: moderateProfile( - item.author, - store.preferences.moderationOpts, - ), + href: makeProfileLink(item.notification.author), + did: item.notification.author.did, + handle: item.notification.author.handle, + displayName: item.notification.author.displayName, + avatar: item.notification.author.avatar, + moderation: moderateProfile(item.notification.author, moderationOpts), }, ...(item.additional?.map(({author}) => { return { @@ -113,33 +106,35 @@ export const FeedItem = observer(function FeedItemImpl({ handle: author.handle, displayName: author.displayName, avatar: author.avatar, - moderation: moderateProfile(author, store.preferences.moderationOpts), + moderation: moderateProfile(author, moderationOpts), } }) || []), ] - }, [store, item.additional, item.author]) + }, [item, moderationOpts]) - if (item.additionalPost?.notFound) { + if (item.subjectUri && !item.subject) { // don't render anything if the target post was deleted or unfindable return <View /> } - if (item.isReply || item.isMention || item.isQuote) { - if (!item.additionalPost || item.additionalPost?.error) { - // hide errors - it doesnt help the user to show them - return <View /> + if ( + item.type === 'reply' || + item.type === 'mention' || + item.type === 'quote' + ) { + if (!item.subject) { + return null } return ( <Link - testID={`feedItem-by-${item.author.handle}`} + testID={`feedItem-by-${item.notification.author.handle}`} href={itemHref} - title={itemTitle} noFeedback accessible={false}> <Post - view={item.additionalPost} + post={item.subject} style={ - item.isRead + item.notification.isRead ? undefined : { backgroundColor: pal.colors.unreadNotifBg, @@ -154,23 +149,25 @@ export const FeedItem = observer(function FeedItemImpl({ let action = '' let icon: Props['icon'] | 'HeartIconSolid' let iconStyle: Props['style'] = [] - if (item.isLike) { + if (item.type === 'post-like') { action = 'liked your post' icon = 'HeartIconSolid' iconStyle = [ s.likeColor as FontAwesomeIconStyle, {position: 'relative', top: -4}, ] - } else if (item.isRepost) { + } else if (item.type === 'repost') { action = 'reposted your post' icon = 'retweet' iconStyle = [s.green3 as FontAwesomeIconStyle] - } else if (item.isFollow) { + } else if (item.type === 'follow') { action = 'followed you' icon = 'user-plus' iconStyle = [s.blue3 as FontAwesomeIconStyle] - } else if (item.isCustomFeedLike) { - action = `liked your custom feed '${new AtUri(item.subjectUri).rkey}'` + } else if (item.type === 'feedgen-like') { + action = `liked your custom feed${ + item.subjectUri ? ` '${new AtUri(item.subjectUri).rkey}}'` : '' + }` icon = 'HeartIconSolid' iconStyle = [ s.likeColor as FontAwesomeIconStyle, @@ -182,12 +179,12 @@ export const FeedItem = observer(function FeedItemImpl({ return ( <Link - testID={`feedItem-by-${item.author.handle}`} + testID={`feedItem-by-${item.notification.author.handle}`} style={[ styles.outer, pal.view, pal.border, - item.isRead + item.notification.isRead ? undefined : { backgroundColor: pal.colors.unreadNotifBg, @@ -195,9 +192,11 @@ export const FeedItem = observer(function FeedItemImpl({ }, ]} href={itemHref} - title={itemTitle} noFeedback - accessible={(item.isLike && authors.length === 1) || item.isRepost}> + accessible={ + (item.type === 'post-like' && authors.length === 1) || + item.type === 'repost' + }> <View style={styles.layoutIcon}> {/* TODO: Prevent conditional rendering and move toward composable notifications for clearer accessibility labeling */} @@ -232,7 +231,10 @@ export const FeedItem = observer(function FeedItemImpl({ /> {authors.length > 1 ? ( <> - <Text style={[pal.text]}> and </Text> + <Text style={[pal.text, s.mr5, s.ml5]}> + {' '} + <Trans>and</Trans>{' '} + </Text> <Text style={[pal.text, s.bold]}> {formatCount(authors.length - 1)}{' '} {pluralize(authors.length - 1, 'other')} @@ -240,24 +242,26 @@ export const FeedItem = observer(function FeedItemImpl({ </> ) : undefined} <Text style={[pal.text]}> {action}</Text> - <TimeElapsed timestamp={item.indexedAt}> + <TimeElapsed timestamp={item.notification.indexedAt}> {({timeElapsed}) => ( <Text style={[pal.textLight, styles.pointer]} - title={niceDate(item.indexedAt)}> + title={niceDate(item.notification.indexedAt)}> {' ' + timeElapsed} </Text> )} </TimeElapsed> </Text> </ExpandListPressable> - {item.isLike || item.isRepost || item.isQuote ? ( - <AdditionalPostText additionalPost={item.additionalPost} /> + {item.type === 'post-like' || item.type === 'repost' ? ( + <AdditionalPostText post={item.subject} /> ) : null} </View> </Link> ) -}) +} +FeedItem = memo(FeedItem) +export {FeedItem} function ExpandListPressable({ hasMultipleAuthors, @@ -292,6 +296,8 @@ function CondensedAuthorsList({ onToggleAuthorsExpanded: () => void }) { const pal = usePalette('default') + const {_} = useLingui() + if (!visible) { return ( <View style={styles.avis}> @@ -299,7 +305,7 @@ function CondensedAuthorsList({ style={styles.expandedAuthorsCloseBtn} onPress={onToggleAuthorsExpanded} accessibilityRole="button" - accessibilityLabel="Hide user list" + accessibilityLabel={_(msg`Hide user list`)} accessibilityHint="Collapses list of users for a given notification"> <FontAwesomeIcon icon="angle-up" @@ -307,7 +313,7 @@ function CondensedAuthorsList({ style={[styles.expandedAuthorsCloseBtnIcon, pal.text]} /> <Text type="sm-medium" style={pal.text}> - Hide + <Trans>Hide</Trans> </Text> </TouchableOpacity> </View> @@ -328,7 +334,7 @@ function CondensedAuthorsList({ } return ( <TouchableOpacity - accessibilityLabel="Show users" + accessibilityLabel={_(msg`Show users`)} accessibilityHint="Opens an expanded list of users in this notification" onPress={onToggleAuthorsExpanded}> <View style={styles.avis}> @@ -417,34 +423,25 @@ function ExpandedAuthorsList({ ) } -function AdditionalPostText({ - additionalPost, -}: { - additionalPost?: PostThreadModel -}) { +function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { const pal = usePalette('default') - if ( - !additionalPost || - !additionalPost.thread?.postRecord || - additionalPost.error - ) { - return <View /> + if (post && AppBskyFeedPost.isRecord(post?.record)) { + const text = post.record.text + const images = AppBskyEmbedImages.isView(post.embed) + ? post.embed.images + : AppBskyEmbedRecordWithMedia.isView(post.embed) && + AppBskyEmbedImages.isView(post.embed.media) + ? post.embed.media.images + : undefined + return ( + <> + {text?.length > 0 && <Text style={pal.textLight}>{text}</Text>} + {images && images?.length > 0 && ( + <ImageHorzList images={images} style={styles.additionalPostImages} /> + )} + </> + ) } - const text = additionalPost.thread?.postRecord.text - const images = AppBskyEmbedImages.isView(additionalPost.thread.post.embed) - ? additionalPost.thread.post.embed.images - : AppBskyEmbedRecordWithMedia.isView(additionalPost.thread.post.embed) && - AppBskyEmbedImages.isView(additionalPost.thread.post.embed.media) - ? additionalPost.thread.post.embed.media.images - : undefined - return ( - <> - {text?.length > 0 && <Text style={pal.textLight}>{text}</Text>} - {images && images?.length > 0 && ( - <ImageHorzList images={images} style={styles.additionalPostImages} /> - )} - </> - ) } const styles = StyleSheet.create({ |