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 {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})