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