Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
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})