forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useMemo} from 'react'
2import {View} from 'react-native'
3import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
4import {msg} from '@lingui/core/macro'
5import {useLingui} from '@lingui/react'
6import {Trans} from '@lingui/react/macro'
7
8import {useHaptics} from '#/lib/haptics'
9import {makeListLink} from '#/lib/routes/links'
10import {logger} from '#/logger'
11import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
12import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
13import {
14 useAddSavedFeedsMutation,
15 type UsePreferencesQueryResponse,
16 useUpdateSavedFeedsMutation,
17} from '#/state/queries/preferences'
18import {useSession} from '#/state/session'
19import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader'
20import {atoms as a} from '#/alf'
21import {Button, ButtonIcon, ButtonText} from '#/components/Button'
22import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
23import {Loader} from '#/components/Loader'
24import {RichText} from '#/components/RichText'
25import * as Toast from '#/components/Toast'
26import {useAnalytics} from '#/analytics'
27import {MoreOptionsMenu} from './MoreOptionsMenu'
28import {SubscribeMenu} from './SubscribeMenu'
29
30export function Header({
31 rkey,
32 list,
33 preferences,
34}: {
35 rkey: string
36 list: AppBskyGraphDefs.ListView
37 preferences: UsePreferencesQueryResponse
38}) {
39 const {_} = useLingui()
40 const ax = useAnalytics()
41 const {currentAccount} = useSession()
42 const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST
43 const isModList = list.purpose === AppBskyGraphDefs.MODLIST
44 const isBlocking = !!list.viewer?.blocked
45 const isMuting = !!list.viewer?.muted
46 const playHaptic = useHaptics()
47
48 const enableSquareButtons = useEnableSquareButtons()
49
50 const {mutateAsync: muteList, isPending: isMutePending} =
51 useListMuteMutation()
52 const {mutateAsync: blockList, isPending: isBlockPending} =
53 useListBlockMutation()
54 const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} =
55 useAddSavedFeedsMutation()
56 const {mutateAsync: updateSavedFeeds, isPending: isUpdatingSavedFeeds} =
57 useUpdateSavedFeedsMutation()
58
59 const isPending = isAddSavedFeedPending || isUpdatingSavedFeeds
60
61 const savedFeedConfig = preferences?.savedFeeds?.find(
62 f => f.value === list.uri,
63 )
64 const isPinned = Boolean(savedFeedConfig?.pinned)
65
66 const onTogglePinned = async () => {
67 playHaptic()
68
69 try {
70 if (savedFeedConfig) {
71 const pinned = !savedFeedConfig.pinned
72 await updateSavedFeeds([
73 {
74 ...savedFeedConfig,
75 pinned,
76 },
77 ])
78 Toast.show(
79 pinned
80 ? _(msg`Pinned to your feeds`)
81 : _(msg`Unpinned from your feeds`),
82 )
83 } else {
84 await addSavedFeeds([
85 {
86 type: 'list',
87 value: list.uri,
88 pinned: true,
89 },
90 ])
91 Toast.show(_(msg`Saved to your feeds`))
92 }
93 } catch (e) {
94 Toast.show(_(msg`There was an issue contacting the server`), {
95 type: 'error',
96 })
97 logger.error('Failed to toggle pinned feed', {message: e})
98 }
99 }
100
101 const onUnsubscribeMute = async () => {
102 try {
103 await muteList({uri: list.uri, mute: false})
104 Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
105 ax.metric('moderation:unsubscribedFromList', {listType: 'mute'})
106 } catch {
107 Toast.show(
108 _(
109 msg`There was an issue. Please check your internet connection and try again.`,
110 ),
111 )
112 }
113 }
114
115 const onUnsubscribeBlock = async () => {
116 try {
117 await blockList({uri: list.uri, block: false})
118 Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
119 ax.metric('moderation:unsubscribedFromList', {listType: 'block'})
120 } catch {
121 Toast.show(
122 _(
123 msg`There was an issue. Please check your internet connection and try again.`,
124 ),
125 )
126 }
127 }
128
129 const descriptionRT = useMemo(
130 () =>
131 list.description
132 ? new RichTextAPI({
133 text: list.description,
134 facets: list.descriptionFacets,
135 })
136 : undefined,
137 [list],
138 )
139
140 return (
141 <>
142 <ProfileSubpageHeader
143 href={makeListLink(list.creator.handle || list.creator.did || '', rkey)}
144 title={list.name}
145 avatar={list.avatar}
146 isOwner={list.creator.did === currentAccount?.did}
147 creator={list.creator}
148 purpose={list.purpose}
149 avatarType="list">
150 {isCurateList ? (
151 <Button
152 testID={isPinned ? 'unpinBtn' : 'pinBtn'}
153 color={isPinned ? 'secondary' : 'primary_subtle'}
154 label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
155 onPress={onTogglePinned}
156 disabled={isPending}
157 size="small"
158 style={[enableSquareButtons ? a.rounded_sm : a.rounded_full]}>
159 {!isPinned && <ButtonIcon icon={isPending ? Loader : PinIcon} />}
160 <ButtonText>
161 {isPinned ? <Trans>Unpin</Trans> : <Trans>Pin to home</Trans>}
162 </ButtonText>
163 </Button>
164 ) : isModList ? (
165 isBlocking ? (
166 <Button
167 testID="unblockBtn"
168 color="secondary"
169 label={_(msg`Unblock`)}
170 onPress={onUnsubscribeBlock}
171 size="small"
172 style={[enableSquareButtons ? a.rounded_sm : a.rounded_full]}
173 disabled={isBlockPending}>
174 {isBlockPending && <ButtonIcon icon={Loader} />}
175 <ButtonText>
176 <Trans>Unblock</Trans>
177 </ButtonText>
178 </Button>
179 ) : isMuting ? (
180 <Button
181 testID="unmuteBtn"
182 color="secondary"
183 label={_(msg`Unmute`)}
184 onPress={onUnsubscribeMute}
185 size="small"
186 style={[enableSquareButtons ? a.rounded_sm : a.rounded_full]}
187 disabled={isMutePending}>
188 {isMutePending && <ButtonIcon icon={Loader} />}
189 <ButtonText>
190 <Trans>Unmute</Trans>
191 </ButtonText>
192 </Button>
193 ) : (
194 <SubscribeMenu list={list} />
195 )
196 ) : null}
197 <MoreOptionsMenu list={list} />
198 </ProfileSubpageHeader>
199 {descriptionRT ? (
200 <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}>
201 <RichText value={descriptionRT} style={[a.text_md]} />
202 </View>
203 ) : null}
204 </>
205 )
206}