From 2808f8b73da4791e0f3cb85b03403738e5bf6b63 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 12 Dec 2024 11:48:26 -0600 Subject: New profile feed header (#7056) * Init hacking * Lil baby button checkpoint * Playing around * Revert "Playing around" This reverts commit f58a7fafa12269035d440cfa2d8cb1dbd562305f. * Mostly there * Cleanups * Cleanup * Fix report dialog nesting * Remove transform on native * Rename header * Fix layout, overflowing FAB buttons * Remove hack * Couple of fixes * Keep Pin primary CTA (#7061) * Update src/screens/Profile/components/ProfileFeedHeader.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Simplify, use old string * Wrap Trans better --------- Co-authored-by: dan Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> --- .../Profile/components/ProfileFeedHeader.tsx | 534 +++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 src/screens/Profile/components/ProfileFeedHeader.tsx (limited to 'src/screens/Profile/components/ProfileFeedHeader.tsx') diff --git a/src/screens/Profile/components/ProfileFeedHeader.tsx b/src/screens/Profile/components/ProfileFeedHeader.tsx new file mode 100644 index 000000000..0154d535c --- /dev/null +++ b/src/screens/Profile/components/ProfileFeedHeader.tsx @@ -0,0 +1,534 @@ +import React from 'react' +import {View} from 'react-native' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {AtUri} from '@atproto/api' +import {msg, Plural, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useHaptics} from '#/lib/haptics' +import {makeProfileLink} from '#/lib/routes/links' +import {makeCustomFeedLink} from '#/lib/routes/links' +import {shareUrl} from '#/lib/sharing' +import {sanitizeHandle} from '#/lib/strings/handles' +import {toShareUrl} from '#/lib/strings/url-helpers' +import {logger} from '#/logger' +import {FeedSourceFeedInfo} from '#/state/queries/feed' +import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' +import { + useAddSavedFeedsMutation, + usePreferencesQuery, + useRemoveFeedMutation, + useUpdateSavedFeedsMutation, +} from '#/state/queries/preferences' +import {useSession} from '#/state/session' +import {formatCount} from '#/view/com/util/numeric/format' +import * as Toast from '#/view/com/util/Toast' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {Divider} from '#/components/Divider' +import {useRichText} from '#/components/hooks/useRichText' +import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' +import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import { + Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled, + Heart2_Stroke2_Corner0_Rounded as Heart, +} from '#/components/icons/Heart2' +import { + Pin_Filled_Corner0_Rounded as PinFilled, + Pin_Stroke2_Corner0_Rounded as Pin, +} from '#/components/icons/Pin' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' +import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' +import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' +import * as Layout from '#/components/Layout' +import {InlineLinkText} from '#/components/Link' +import {Loader} from '#/components/Loader' +import * as Menu from '#/components/Menu' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' +import {RichText} from '#/components/RichText' +import {Text} from '#/components/Typography' + +export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) { + const t = useTheme() + const {_, i18n} = useLingui() + const {hasSession} = useSession() + const {gtPhone, gtMobile} = useBreakpoints() + const {top} = useSafeAreaInsets() + const infoControl = Dialog.useDialogControl() + const playHaptic = useHaptics() + + const {data: preferences} = usePreferencesQuery() + + const [likeUri, setLikeUri] = React.useState(info.likeUri || '') + const isLiked = !!likeUri + const likeCount = + isLiked && likeUri ? (info.likeCount || 0) + 1 : info.likeCount || 0 + + const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} = + useAddSavedFeedsMutation() + const {mutateAsync: removeFeed, isPending: isRemovePending} = + useRemoveFeedMutation() + const {mutateAsync: updateSavedFeeds, isPending: isUpdateFeedPending} = + useUpdateSavedFeedsMutation() + + const isFeedStateChangePending = + isAddSavedFeedPending || isRemovePending || isUpdateFeedPending + const savedFeedConfig = preferences?.savedFeeds?.find( + f => f.value === info.uri, + ) + const isSaved = Boolean(savedFeedConfig) + const isPinned = Boolean(savedFeedConfig?.pinned) + + const onToggleSaved = React.useCallback(async () => { + try { + playHaptic() + + if (savedFeedConfig) { + await removeFeed(savedFeedConfig) + Toast.show(_(msg`Removed from your feeds`)) + } else { + await addSavedFeeds([ + { + type: 'feed', + value: info.uri, + pinned: false, + }, + ]) + Toast.show(_(msg`Saved to your feeds`)) + } + } catch (err) { + Toast.show( + _( + msg`There was an issue updating your feeds, please check your internet connection and try again.`, + ), + 'xmark', + ) + logger.error('Failed to update feeds', {message: err}) + } + }, [_, playHaptic, info, removeFeed, addSavedFeeds, savedFeedConfig]) + + const onTogglePinned = React.useCallback(async () => { + try { + playHaptic() + + if (savedFeedConfig) { + const pinned = !savedFeedConfig.pinned + await updateSavedFeeds([ + { + ...savedFeedConfig, + pinned, + }, + ]) + + if (pinned) { + Toast.show(_(msg`Pinned ${info.displayName} to Home`)) + } else { + Toast.show(_(msg`Unpinned ${info.displayName} from Home`)) + } + } else { + await addSavedFeeds([ + { + type: 'feed', + value: info.uri, + pinned: true, + }, + ]) + Toast.show(_(msg`Pinned ${info.displayName} to Home`)) + } + } catch (e) { + Toast.show(_(msg`There was an issue contacting the server`), 'xmark') + logger.error('Failed to toggle pinned feed', {message: e}) + } + }, [playHaptic, info, _, savedFeedConfig, updateSavedFeeds, addSavedFeeds]) + + return ( + <> + + + + + + + + {hasSession && ( + + {isPinned ? ( + + + {({props}) => { + return ( + + ) + }} + + + + + {_(msg`Unpin from home`)} + + + + + {isSaved + ? _(msg`Remove from my feeds`) + : _(msg`Save to my feeds`)} + + + + + + ) : ( + + )} + + )} + + + + + + + + + + + ) +} + +function DialogInner({ + info, + likeUri, + setLikeUri, + likeCount, + isPinned, + onTogglePinned, + isFeedStateChangePending, +}: { + info: FeedSourceFeedInfo + likeUri: string + setLikeUri: (uri: string) => void + likeCount: number + isPinned: boolean + onTogglePinned: () => void + isFeedStateChangePending: boolean +}) { + const t = useTheme() + const {_} = useLingui() + const {hasSession} = useSession() + const playHaptic = useHaptics() + const control = Dialog.useDialogContext() + const reportDialogControl = useReportDialogControl() + const [rt, loading] = useRichText(info.description.text) + const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation() + const {mutateAsync: unlikeFeed, isPending: isUnlikePending} = + useUnlikeMutation() + + const isLiked = !!likeUri + const feedRkey = React.useMemo(() => new AtUri(info.uri).rkey, [info.uri]) + + const onToggleLiked = React.useCallback(async () => { + try { + playHaptic() + + if (isLiked && likeUri) { + await unlikeFeed({uri: likeUri}) + setLikeUri('') + } else { + const res = await likeFeed({uri: info.uri, cid: info.cid}) + setLikeUri(res.uri) + } + } catch (err) { + Toast.show( + _( + msg`There was an issue contacting the server, please check your internet connection and try again.`, + ), + 'xmark', + ) + logger.error('Failed to toggle like', {message: err}) + } + }, [playHaptic, isLiked, likeUri, unlikeFeed, setLikeUri, likeFeed, info, _]) + + const onPressShare = React.useCallback(() => { + playHaptic() + const url = toShareUrl(info.route.href) + shareUrl(url) + }, [info, playHaptic]) + + const onPressReport = React.useCallback(() => { + reportDialogControl.open() + }, [reportDialogControl]) + + return loading ? ( + + ) : ( + + + + + + + {info.displayName} + + + + By{' '} + control.close()}> + {sanitizeHandle(info.creatorHandle, '@')} + + + + + + + + + + + + {typeof likeCount === 'number' && ( + control.close()}> + + Liked by + + + )} + + + {hasSession && ( + <> + + + + + + + + + + + Something wrong? Let us know. + + + + + + + + + )} + + ) +} -- cgit 1.4.1