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