forked from
jollywhoppers.com/witchsky.app
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, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6import type React from 'react'
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}: {
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 ax = useAnalytics()
33 const {_} = useLingui()
34 const {mutateAsync: bookmark} = useBookmarkMutation()
35 const cleanError = useCleanError()
36 const requireAuth = useRequireAuth()
37 const {feedDescriptor} = useFeedFeedbackContext()
38
39 const {viewer} = post
40 const isBookmarked = !!viewer?.bookmarked
41
42 const undoLabel = _(
43 msg({
44 message: `Undo`,
45 context: `Button label to undo saving/removing a post from saved posts.`,
46 }),
47 )
48
49 const save = async ({disableUndo}: {disableUndo?: boolean} = {}) => {
50 try {
51 await bookmark({
52 action: 'create',
53 post,
54 })
55
56 ax.metric('post:bookmark', {
57 uri: post.uri,
58 authorDid: post.author.did,
59 logContext,
60 feedDescriptor,
61 })
62
63 toast.show(
64 <toast.Outer>
65 <toast.Icon />
66 <toast.Text>
67 <Trans>Post saved</Trans>
68 </toast.Text>
69 {!disableUndo && (
70 <toast.Action
71 label={undoLabel}
72 onPress={() => remove({disableUndo: true})}>
73 {undoLabel}
74 </toast.Action>
75 )}
76 </toast.Outer>,
77 {
78 type: 'success',
79 },
80 )
81 } catch (e: any) {
82 const {raw, clean} = cleanError(e)
83 toast.show(clean || raw || e, {
84 type: 'error',
85 })
86 }
87 }
88
89 const remove = async ({disableUndo}: {disableUndo?: boolean} = {}) => {
90 try {
91 await bookmark({
92 action: 'delete',
93 uri: post.uri,
94 })
95
96 ax.metric('post:unbookmark', {
97 uri: post.uri,
98 authorDid: post.author.did,
99 logContext,
100 feedDescriptor,
101 })
102
103 toast.show(
104 <toast.Outer>
105 <toast.Icon icon={TrashIcon} />
106 <toast.Text>
107 <Trans>Removed from saved posts</Trans>
108 </toast.Text>
109 {!disableUndo && (
110 <toast.Action
111 label={undoLabel}
112 onPress={() => save({disableUndo: true})}>
113 {undoLabel}
114 </toast.Action>
115 )}
116 </toast.Outer>,
117 )
118 } catch (e: any) {
119 const {raw, clean} = cleanError(e)
120 toast.show(clean || raw || e, {
121 type: 'error',
122 })
123 }
124 }
125
126 const onHandlePress = () =>
127 requireAuth(async () => {
128 if (isBookmarked) {
129 await remove()
130 } else {
131 await save()
132 }
133 })
134
135 return (
136 <PostControlButton
137 testID="postBookmarkBtn"
138 big={big}
139 label={
140 isBookmarked
141 ? _(msg`Remove from saved posts`)
142 : _(msg`Add to saved posts`)
143 }
144 onPress={onHandlePress}
145 hitSlop={hitSlop}>
146 <PostControlButtonIcon
147 fill={isBookmarked ? t.palette.primary_500 : undefined}
148 icon={isBookmarked ? BookmarkFilled : Bookmark}
149 />
150 </PostControlButton>
151 )
152})