Bluesky app fork with some witchin' additions 馃挮
at main 206 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} 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}