Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 220 lines 6.6 kB view raw
1import {memo, useState} from 'react' 2import {View} from 'react-native' 3import {type AppBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' 4import {msg} from '@lingui/core/macro' 5import {useLingui} from '@lingui/react' 6import {Trans} from '@lingui/react/macro' 7import {StackActions, useNavigation} from '@react-navigation/native' 8import type React from 'react' 9 10import {type NavigationProp} from '#/lib/routes/types' 11import {useProfileShadow} from '#/state/cache/profile-shadow' 12import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 13import { 14 useProfileBlockMutationQueue, 15 useProfileQuery, 16} from '#/state/queries/profile' 17import * as Toast from '#/view/com/util/Toast' 18import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf' 19import {Button, ButtonText} from '#/components/Button' 20import * as Dialog from '#/components/Dialog' 21import * as Toggle from '#/components/forms/Toggle' 22import {Loader} from '#/components/Loader' 23import {Text} from '#/components/Typography' 24import {IS_NATIVE} from '#/env' 25 26type ReportDialogParams = { 27 convoId: string 28 message: ChatBskyConvoDefs.MessageView 29} 30 31/** 32 * Dialog shown after a report is submitted, allowing the user to block the 33 * reporter and/or leave the conversation. 34 */ 35export const AfterReportDialog = memo(function BlockOrDeleteDialogInner({ 36 control, 37 params, 38 currentScreen, 39}: { 40 control: Dialog.DialogControlProps 41 params: ReportDialogParams 42 currentScreen: 'list' | 'conversation' 43}): React.ReactNode { 44 const {_} = useLingui() 45 return ( 46 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 47 <Dialog.Handle /> 48 <Dialog.ScrollableInner 49 label={_( 50 msg`Would you like to block this user and/or delete this conversation?`, 51 )} 52 style={[web({maxWidth: 400})]}> 53 <DialogInner params={params} currentScreen={currentScreen} /> 54 <Dialog.Close /> 55 </Dialog.ScrollableInner> 56 </Dialog.Outer> 57 ) 58}) 59 60function DialogInner({ 61 params, 62 currentScreen, 63}: { 64 params: ReportDialogParams 65 currentScreen: 'list' | 'conversation' 66}) { 67 const t = useTheme() 68 const {_} = useLingui() 69 const control = Dialog.useDialogContext() 70 const { 71 data: profile, 72 isPending, 73 isError, 74 } = useProfileQuery({ 75 did: params.message.sender.did, 76 }) 77 78 return isPending ? ( 79 <View style={[a.w_full, a.py_5xl, a.align_center]}> 80 <Loader size="lg" /> 81 </View> 82 ) : isError || !profile ? ( 83 <View style={[a.w_full, a.gap_lg]}> 84 <View style={[a.justify_center, a.gap_sm]}> 85 <Text style={[a.text_2xl, a.font_semi_bold]}> 86 <Trans>Report submitted</Trans> 87 </Text> 88 <Text style={[a.text_md, t.atoms.text_contrast_medium]}> 89 <Trans>Our moderation team has received your report.</Trans> 90 </Text> 91 </View> 92 93 <Button 94 label={_(msg`Close`)} 95 onPress={() => control.close()} 96 size={platform({native: 'small', web: 'large'})} 97 color="secondary"> 98 <ButtonText> 99 <Trans>Close</Trans> 100 </ButtonText> 101 </Button> 102 </View> 103 ) : ( 104 <DoneStep 105 convoId={params.convoId} 106 currentScreen={currentScreen} 107 profile={profile} 108 /> 109 ) 110} 111 112function DoneStep({ 113 convoId, 114 currentScreen, 115 profile, 116}: { 117 convoId: string 118 currentScreen: 'list' | 'conversation' 119 profile: AppBskyActorDefs.ProfileViewDetailed 120}) { 121 const {_} = useLingui() 122 const navigation = useNavigation<NavigationProp>() 123 const control = Dialog.useDialogContext() 124 const {gtMobile} = useBreakpoints() 125 const t = useTheme() 126 const [actions, setActions] = useState<string[]>(['block', 'leave']) 127 const shadow = useProfileShadow(profile) 128 const [queueBlock] = useProfileBlockMutationQueue(shadow) 129 130 const {mutate: leaveConvo} = useLeaveConvo(convoId, { 131 onMutate: () => { 132 if (currentScreen === 'conversation') { 133 navigation.dispatch( 134 StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}), 135 ) 136 } 137 }, 138 onError: () => { 139 Toast.show(_(msg`Could not leave chat`), 'xmark') 140 }, 141 }) 142 143 let btnText = _(msg`Done`) 144 let toastMsg: string | undefined 145 if (actions.includes('leave') && actions.includes('block')) { 146 btnText = _(msg`Block and Delete`) 147 toastMsg = _(msg({message: 'Conversation deleted', context: 'toast'})) 148 } else if (actions.includes('leave')) { 149 btnText = _(msg`Delete Conversation`) 150 toastMsg = _(msg({message: 'Conversation deleted', context: 'toast'})) 151 } else if (actions.includes('block')) { 152 btnText = _(msg`Block User`) 153 toastMsg = _(msg({message: 'User blocked', context: 'toast'})) 154 } 155 156 const onPressPrimaryAction = () => { 157 control.close(() => { 158 if (actions.includes('block')) { 159 queueBlock() 160 } 161 if (actions.includes('leave')) { 162 leaveConvo() 163 } 164 if (toastMsg) { 165 Toast.show(toastMsg, 'check') 166 } 167 }) 168 } 169 170 return ( 171 <View style={a.gap_2xl}> 172 <View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}> 173 <Text style={[a.text_2xl, a.font_semi_bold]}> 174 <Trans>Report submitted</Trans> 175 </Text> 176 <Text style={[a.text_md, t.atoms.text_contrast_medium]}> 177 <Trans>Our moderation team has received your report.</Trans> 178 </Text> 179 </View> 180 <Toggle.Group 181 label={_(msg`Block user and/or delete this conversation`)} 182 values={actions} 183 onChange={setActions}> 184 <View style={[a.gap_md]}> 185 <Toggle.Item name="block" label={_(msg`Block user`)}> 186 <Toggle.Checkbox /> 187 <Toggle.LabelText style={[a.text_md]}> 188 <Trans>Block user</Trans> 189 </Toggle.LabelText> 190 </Toggle.Item> 191 <Toggle.Item name="leave" label={_(msg`Delete conversation`)}> 192 <Toggle.Checkbox /> 193 <Toggle.LabelText style={[a.text_md]}> 194 <Trans>Delete conversation</Trans> 195 </Toggle.LabelText> 196 </Toggle.Item> 197 </View> 198 </Toggle.Group> 199 200 <View style={[a.gap_sm]}> 201 <Button 202 label={btnText} 203 onPress={onPressPrimaryAction} 204 size="large" 205 color={actions.length > 0 ? 'negative' : 'primary'}> 206 <ButtonText>{btnText}</ButtonText> 207 </Button> 208 <Button 209 label={_(msg`Close`)} 210 onPress={() => control.close()} 211 size="large" 212 color="secondary"> 213 <ButtonText> 214 <Trans>Close</Trans> 215 </ButtonText> 216 </Button> 217 </View> 218 </View> 219 ) 220}