Bluesky app fork with some witchin' additions 馃挮
at main 151 lines 4.0 kB view raw
1import {memo} from 'react' 2import {type Insets} from 'react-native' 3import {type AppBskyFeedDefs} from '@atproto/api' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import type React from 'react' 7 8import {useCleanError} from '#/lib/hooks/useCleanError' 9import {logger} from '#/logger' 10import {type Shadow} from '#/state/cache/post-shadow' 11import {useFeedFeedbackContext} from '#/state/feed-feedback' 12import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' 13import {useRequireAuth} from '#/state/session' 14import {useTheme} from '#/alf' 15import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark' 16import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 17import * as toast from '#/components/Toast' 18import {PostControlButton, PostControlButtonIcon} from './PostControlButton' 19 20export const BookmarkButton = memo(function BookmarkButton({ 21 post, 22 big, 23 logContext, 24 hitSlop, 25}: { 26 post: Shadow<AppBskyFeedDefs.PostView> 27 big?: boolean 28 logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' 29 hitSlop?: Insets 30}): React.ReactNode { 31 const t = useTheme() 32 const {_} = useLingui() 33 const {mutateAsync: bookmark} = useBookmarkMutation() 34 const cleanError = useCleanError() 35 const requireAuth = useRequireAuth() 36 const {feedDescriptor} = useFeedFeedbackContext() 37 38 const {viewer} = post 39 const isBookmarked = !!viewer?.bookmarked 40 41 const undoLabel = _( 42 msg({ 43 message: `Undo`, 44 context: `Button label to undo saving/removing a post from saved posts.`, 45 }), 46 ) 47 48 const save = async ({disableUndo}: {disableUndo?: boolean} = {}) => { 49 try { 50 await bookmark({ 51 action: 'create', 52 post, 53 }) 54 55 logger.metric('post:bookmark', { 56 uri: post.uri, 57 authorDid: post.author.did, 58 logContext, 59 feedDescriptor, 60 }) 61 62 toast.show( 63 <toast.Outer> 64 <toast.Icon /> 65 <toast.Text> 66 <Trans>Skeet saved</Trans> 67 </toast.Text> 68 {!disableUndo && ( 69 <toast.Action 70 label={undoLabel} 71 onPress={() => remove({disableUndo: true})}> 72 {undoLabel} 73 </toast.Action> 74 )} 75 </toast.Outer>, 76 { 77 type: 'success', 78 }, 79 ) 80 } catch (e: any) { 81 const {raw, clean} = cleanError(e) 82 toast.show(clean || raw || e, { 83 type: 'error', 84 }) 85 } 86 } 87 88 const remove = async ({disableUndo}: {disableUndo?: boolean} = {}) => { 89 try { 90 await bookmark({ 91 action: 'delete', 92 uri: post.uri, 93 }) 94 95 logger.metric('post:unbookmark', { 96 uri: post.uri, 97 authorDid: post.author.did, 98 logContext, 99 feedDescriptor, 100 }) 101 102 toast.show( 103 <toast.Outer> 104 <toast.Icon icon={TrashIcon} /> 105 <toast.Text> 106 <Trans>Removed from saved skeets</Trans> 107 </toast.Text> 108 {!disableUndo && ( 109 <toast.Action 110 label={undoLabel} 111 onPress={() => save({disableUndo: true})}> 112 {undoLabel} 113 </toast.Action> 114 )} 115 </toast.Outer>, 116 ) 117 } catch (e: any) { 118 const {raw, clean} = cleanError(e) 119 toast.show(clean || raw || e, { 120 type: 'error', 121 }) 122 } 123 } 124 125 const onHandlePress = () => 126 requireAuth(async () => { 127 if (isBookmarked) { 128 await remove() 129 } else { 130 await save() 131 } 132 }) 133 134 return ( 135 <PostControlButton 136 testID="postBookmarkBtn" 137 big={big} 138 label={ 139 isBookmarked 140 ? _(msg`Remove from saved skeets`) 141 : _(msg`Add to saved skeets`) 142 } 143 onPress={onHandlePress} 144 hitSlop={hitSlop}> 145 <PostControlButtonIcon 146 fill={isBookmarked ? t.palette.primary_500 : undefined} 147 icon={isBookmarked ? BookmarkFilled : Bookmark} 148 /> 149 </PostControlButton> 150 ) 151})