From fb8ab9f26d9d6b24fa40ce2f54057470fe3ff1de Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 16 Apr 2025 22:14:33 +0300 Subject: ALF Notifications screen (#8208) * alf notifs page * Stop propagation * Ok safari wants preventDefault too * Fix lint --------- Co-authored-by: Eric Bailey --- .../com/notifications/NotificationFeedItem.tsx | 392 ++++++++++----------- 1 file changed, 190 insertions(+), 202 deletions(-) (limited to 'src/view/com/notifications/NotificationFeedItem.tsx') diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index 84694fe3b..8875ec02e 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -1,25 +1,27 @@ -import React, { +import { memo, type ReactElement, + useCallback, useEffect, useMemo, useState, } from 'react' import { Animated, + type GestureResponderEvent, Pressable, StyleSheet, TouchableOpacity, View, } from 'react-native' import { - AppBskyActorDefs, - AppBskyFeedDefs, + type AppBskyActorDefs, + type AppBskyFeedDefs, AppBskyFeedPost, AppBskyGraphFollow, moderateProfile, - ModerationDecision, - ModerationOpts, + type ModerationDecision, + type ModerationOpts, } from '@atproto/api' import {AtUri} from '@atproto/api' import {TID} from '@atproto/common-web' @@ -31,18 +33,22 @@ import {useQueryClient} from '@tanstack/react-query' import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue' import {usePalette} from '#/lib/hooks/usePalette' import {makeProfileLink} from '#/lib/routes/links' -import {NavigationProp} from '#/lib/routes/types' +import {type NavigationProp} from '#/lib/routes/types' import {forceLTR} from '#/lib/strings/bidi' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {niceDate} from '#/lib/strings/time' -import {colors, s} from '#/lib/styles' +import {s} from '#/lib/styles' import {logger} from '#/logger' -import {isWeb} from '#/platform/detection' import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' -import {FeedNotification} from '#/state/queries/notifications/feed' -import {precacheProfile} from '#/state/queries/profile' +import {type FeedNotification} from '#/state/queries/notifications/feed' +import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' import {useAgent} from '#/state/session' +import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard' +import {Post} from '#/view/com/post/Post' +import {formatCount} from '#/view/com/util/numeric/format' +import {TimeElapsed} from '#/view/com/util/TimeElapsed' +import {PreviewableUserAvatar, UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import { @@ -53,19 +59,13 @@ import {Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled} from '#/compon import {PersonPlus_Filled_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost' import {StarterPack} from '#/components/icons/StarterPack' -import {Link as NewLink} from '#/components/Link' +import {InlineLinkText, Link} from '#/components/Link' import * as MediaPreview from '#/components/MediaPreview' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard' import {SubtleWebHover} from '#/components/SubtleWebHover' +import {Text} from '#/components/Typography' import * as bsky from '#/types/bsky' -import {FeedSourceCard} from '../feeds/FeedSourceCard' -import {Post} from '../post/Post' -import {Link, TextLink} from '../util/Link' -import {formatCount} from '../util/numeric/format' -import {Text} from '../util/text/Text' -import {TimeElapsed} from '../util/TimeElapsed' -import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar' const MAX_AUTHORS = 5 @@ -90,8 +90,8 @@ let NotificationFeedItem = ({ }): React.ReactNode => { const queryClient = useQueryClient() const pal = usePalette('default') - const {_, i18n} = useLingui() const t = useTheme() + const {_, i18n} = useLingui() const [isAuthorsExpanded, setAuthorsExpanded] = useState(false) const itemHref = useMemo(() => { if (item.type === 'post-like' || item.type === 'repost') { @@ -116,12 +116,16 @@ let NotificationFeedItem = ({ return '' }, [item]) - const onToggleAuthorsExpanded = () => { + const onToggleAuthorsExpanded = (e?: GestureResponderEvent) => { + if (e) { + e.preventDefault() + e.stopPropagation() + } setAuthorsExpanded(currentlyExpanded => !currentlyExpanded) } - const onBeforePress = React.useCallback(() => { - precacheProfile(queryClient, item.notification.author) + const onBeforePress = useCallback(() => { + unstableCacheProfileView(queryClient, item.notification.author) }, [queryClient, item.notification.author]) const authors: Author[] = useMemo(() => { @@ -139,7 +143,11 @@ let NotificationFeedItem = ({ ] }, [item, moderationOpts]) - const [hover, setHover] = React.useState(false) + const niceTimestamp = niceDate(i18n, item.notification.indexedAt) + const firstAuthor = authors[0] + const firstAuthorName = sanitizeDisplayName( + firstAuthor.profile.displayName || firstAuthor.profile.handle, + ) if (item.subjectUri && !item.subject && item.type !== 'feedgen-like') { // don't render anything if the target post was deleted or unfindable @@ -156,42 +164,29 @@ let NotificationFeedItem = ({ } const isHighlighted = highlightUnread && !item.notification.isRead return ( - - - + } + hideTopBorder={hideTopBorder} + /> ) } - const niceTimestamp = niceDate(i18n, item.notification.indexedAt) - const firstAuthor = authors[0] - const firstAuthorName = sanitizeDisplayName( - firstAuthor.profile.displayName || firstAuthor.profile.handle, - ) const firstAuthorLink = ( - - {forceLTR(firstAuthorName)} - - } + style={[t.atoms.text, a.font_bold, a.text_md, a.leading_tight]} + to={firstAuthor.href} disableMismatchWarning - /> + emoji + label={_(msg`Go to ${firstAuthorName}'s profile`)}> + {forceLTR(firstAuthorName)} + ) const additionalAuthorsCount = authors.length - 1 const hasMultipleAuthors = additionalAuthorsCount > 0 @@ -223,7 +218,7 @@ let NotificationFeedItem = ({ notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} - + {firstAuthorLink} and{' '} - + {firstAuthorLink} and{' '} - + {firstAuthorLink} and{' '} - + {firstAuthorLink} and{' '} - + { - setHover(true) - }} - onPointerLeave={() => { - setHover(false) }}> - - - {/* TODO: Prevent conditional rendering and move toward composable - notifications for clearer accessibility labeling */} - {icon} - - - - - - - {notificationContent} - - {({timeElapsed}) => ( - <> - {/* make sure there's whitespace around the middot -sfn */} - · - - {timeElapsed} - - - )} - - - - {item.type === 'post-like' || item.type === 'repost' ? ( - - ) : null} - {item.type === 'feedgen-like' && item.subjectUri ? ( - - ) : null} - {item.type === 'starterpack-joined' ? ( - - - - + {({hovered}) => ( + <> + + + {/* TODO: Prevent conditional rendering and move toward composable + notifications for clearer accessibility labeling */} + {icon} - ) : null} - + + + + + + {notificationContent} + + {({timeElapsed}) => ( + <> + {/* make sure there's whitespace around the middot -sfn */} + + {' '} + ·{' '} + + + {timeElapsed} + + + )} + + + + {item.type === 'post-like' || item.type === 'repost' ? ( + + ) : null} + {item.type === 'feedgen-like' && item.subjectUri ? ( + + ) : null} + {item.type === 'starterpack-joined' ? ( + + + + + + ) : null} + + + )} ) } @@ -509,7 +519,7 @@ function ExpandListPressable({ }: { hasMultipleAuthors: boolean children: React.ReactNode - onToggleAuthorsExpanded: () => void + onToggleAuthorsExpanded: (e: GestureResponderEvent) => void }) { if (hasMultipleAuthors) { return ( @@ -529,7 +539,7 @@ function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) { const {_} = useLingui() const agent = useAgent() const navigation = useNavigation() - const [isLoading, setIsLoading] = React.useState(false) + const [isLoading, setIsLoading] = useState(false) if ( profile.associated?.chat?.allowIncoming === 'none' || @@ -580,15 +590,15 @@ function CondensedAuthorsList({ }: { visible: boolean authors: Author[] - onToggleAuthorsExpanded: () => void + onToggleAuthorsExpanded: (e: GestureResponderEvent) => void showDmButton?: boolean }) { - const pal = usePalette('default') + const t = useTheme() const {_} = useLingui() if (!visible) { return ( - + - + Hide @@ -610,7 +620,7 @@ function CondensedAuthorsList({ } if (authors.length === 1) { return ( - + - + {authors.slice(0, MAX_AUTHORS).map(author => ( ))} {authors.length > MAX_AUTHORS ? ( - + +{authors.length - MAX_AUTHORS} ) : undefined} @@ -658,7 +673,7 @@ function ExpandedAuthorsList({ authors: Author[] }) { const {_} = useLingui() - const pal = usePalette('default') + const t = useTheme() const heightInterp = useAnimatedValue(visible ? 1 : 0) const targetHeight = authors.length * (EXPANDED_AUTHOR_EL_HEIGHT + 10) /*10=margin*/ @@ -677,7 +692,7 @@ function ExpandedAuthorsList({ {visible && authors.map(author => ( - - + - - - + + + {sanitizeDisplayName( author.profile.displayName || author.profile.handle, )} - {' '} - + + {sanitizeHandle(author.profile.handle, '@')} - + - + ))} ) } function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { - const pal = usePalette('default') + const t = useTheme() if ( post && bsky.dangerousIsType( @@ -732,7 +759,9 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { return ( <> {text?.length > 0 && ( - + {text} )} @@ -746,18 +775,6 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { } const styles = StyleSheet.create({ - pointer: isWeb - ? { - // @ts-ignore web only - cursor: 'pointer', - } - : {}, - - outer: { - padding: 10, - paddingRight: 15, - flexDirection: 'row', - }, layoutIcon: { width: 60, alignItems: 'flex-end', @@ -767,27 +784,6 @@ const styles = StyleSheet.create({ marginRight: 10, marginTop: 4, }, - layoutContent: { - flex: 1, - }, - avis: { - flexDirection: 'row', - alignItems: 'center', - }, - aviExtraCount: { - fontWeight: '600', - paddingLeft: 6, - }, - meta: { - flexDirection: 'row', - flexWrap: 'wrap', - paddingTop: 6, - paddingBottom: 2, - }, - postText: { - paddingBottom: 5, - color: colors.black, - }, additionalPostImages: { marginTop: 5, marginLeft: 2, @@ -798,7 +794,6 @@ const styles = StyleSheet.create({ paddingVertical: 12, marginTop: 6, }, - addedContainer: { paddingTop: 4, paddingLeft: 36, @@ -812,17 +807,10 @@ const styles = StyleSheet.create({ paddingTop: 10, paddingBottom: 6, }, - expandedAuthorsCloseBtnIcon: { - marginLeft: 4, - marginRight: 4, - }, expandedAuthor: { flexDirection: 'row', alignItems: 'center', marginTop: 10, height: EXPANDED_AUTHOR_EL_HEIGHT, }, - expandedAuthorAvi: { - marginRight: 5, - }, }) -- cgit 1.4.1