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