Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 268 lines 8.4 kB view raw
1import React, {useCallback, useEffect} from 'react' 2import {View} from 'react-native' 3import { 4 type AppBskyActorDefs, 5 moderateProfile, 6 type ModerationDecision, 7} from '@atproto/api' 8import {msg} from '@lingui/core/macro' 9import {useLingui} from '@lingui/react' 10import {Trans} from '@lingui/react/macro' 11import { 12 type RouteProp, 13 useFocusEffect, 14 useNavigation, 15 useRoute, 16} from '@react-navigation/native' 17import {type NativeStackScreenProps} from '@react-navigation/native-stack' 18 19import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 20import { 21 type CommonNavigatorParams, 22 type NavigationProp, 23} from '#/lib/routes/types' 24import {type Shadow, useMaybeProfileShadow} from '#/state/cache/profile-shadow' 25import {useEmail} from '#/state/email-verification' 26import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' 27import {ConvoStatus} from '#/state/messages/convo/types' 28import {useCurrentConvoId} from '#/state/messages/current-convo-id' 29import {useModerationOpts} from '#/state/preferences/moderation-opts' 30import {useProfileQuery} from '#/state/queries/profile' 31import {useSetMinimalShellMode} from '#/state/shell' 32import {MessagesList} from '#/screens/Messages/components/MessagesList' 33import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 34import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen' 35import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' 36import { 37 EmailDialogScreenID, 38 useEmailDialogControl, 39} from '#/components/dialogs/EmailDialog' 40import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter' 41import {MessagesListHeader} from '#/components/dms/MessagesListHeader' 42import {Error} from '#/components/Error' 43import * as Layout from '#/components/Layout' 44import {Loader} from '#/components/Loader' 45import {IS_WEB} from '#/env' 46 47type Props = NativeStackScreenProps< 48 CommonNavigatorParams, 49 'MessagesConversation' 50> 51 52export function MessagesConversationScreen(props: Props) { 53 const {_} = useLingui() 54 const aaCopy = useAgeAssuranceCopy() 55 return ( 56 <AgeRestrictedScreen 57 screenTitle={_(msg`Conversation`)} 58 infoText={aaCopy.chatsInfoText}> 59 <MessagesConversationScreenInner {...props} /> 60 </AgeRestrictedScreen> 61 ) 62} 63 64export function MessagesConversationScreenInner({route}: Props) { 65 const {gtMobile} = useBreakpoints() 66 const setMinimalShellMode = useSetMinimalShellMode() 67 68 const convoId = route.params.conversation 69 const {setCurrentConvoId} = useCurrentConvoId() 70 71 useFocusEffect( 72 useCallback(() => { 73 setCurrentConvoId(convoId) 74 75 if (IS_WEB && !gtMobile) { 76 setMinimalShellMode(true) 77 } else { 78 setMinimalShellMode(false) 79 } 80 81 return () => { 82 setCurrentConvoId(undefined) 83 setMinimalShellMode(false) 84 } 85 }, [gtMobile, convoId, setCurrentConvoId, setMinimalShellMode]), 86 ) 87 88 return ( 89 <Layout.Screen testID="convoScreen" style={web([{minHeight: 0}, a.flex_1])}> 90 <ConvoProvider key={convoId} convoId={convoId}> 91 <Inner /> 92 </ConvoProvider> 93 </Layout.Screen> 94 ) 95} 96 97function Inner() { 98 const t = useTheme() 99 const convoState = useConvo() 100 const {_} = useLingui() 101 102 const moderationOpts = useModerationOpts() 103 const {data: recipientUnshadowed} = useProfileQuery({ 104 did: convoState.recipients?.[0].did, 105 }) 106 const recipient = useMaybeProfileShadow(recipientUnshadowed) 107 108 const moderation = React.useMemo(() => { 109 if (!recipient || !moderationOpts) return null 110 return moderateProfile(recipient, moderationOpts) 111 }, [recipient, moderationOpts]) 112 113 // Because we want to give the list a chance to asynchronously scroll to the end before it is visible to the user, 114 // we use `hasScrolled` to determine when to render. With that said however, there is a chance that the chat will be 115 // empty. So, we also check for that possible state as well and render once we can. 116 const [hasScrolled, setHasScrolled] = React.useState(false) 117 const readyToShow = 118 hasScrolled || 119 (isConvoActive(convoState) && 120 !convoState.isFetchingHistory && 121 convoState.items.length === 0) 122 123 // Any time that we re-render the `Initializing` state, we have to reset `hasScrolled` to false. After entering this 124 // state, we know that we're resetting the list of messages and need to re-scroll to the bottom when they get added. 125 React.useEffect(() => { 126 if (convoState.status === ConvoStatus.Initializing) { 127 setHasScrolled(false) 128 } 129 }, [convoState.status]) 130 131 if (convoState.status === ConvoStatus.Error) { 132 return ( 133 <> 134 <Layout.Center style={[a.flex_1]}> 135 {moderation ? ( 136 <MessagesListHeader moderation={moderation} profile={recipient} /> 137 ) : ( 138 <MessagesListHeader /> 139 )} 140 </Layout.Center> 141 <Error 142 title={_(msg`Something went wrong`)} 143 message={_(msg`We couldn't load this conversation`)} 144 onRetry={() => convoState.error.retry()} 145 sideBorders={false} 146 /> 147 </> 148 ) 149 } 150 151 return ( 152 <Layout.Center style={[a.flex_1]}> 153 {!readyToShow && 154 (moderation ? ( 155 <MessagesListHeader moderation={moderation} profile={recipient} /> 156 ) : ( 157 <MessagesListHeader /> 158 ))} 159 <View style={[a.flex_1]}> 160 {moderation && recipient ? ( 161 <InnerReady 162 moderation={moderation} 163 recipient={recipient} 164 hasScrolled={hasScrolled} 165 setHasScrolled={setHasScrolled} 166 /> 167 ) : ( 168 <View style={[a.align_center, a.gap_sm, a.flex_1]} /> 169 )} 170 {!readyToShow && ( 171 <View 172 style={[ 173 a.absolute, 174 a.z_10, 175 a.w_full, 176 a.h_full, 177 a.justify_center, 178 a.align_center, 179 t.atoms.bg, 180 ]}> 181 <View style={[{marginBottom: 75}]}> 182 <Loader size="xl" /> 183 </View> 184 </View> 185 )} 186 </View> 187 </Layout.Center> 188 ) 189} 190 191function InnerReady({ 192 moderation, 193 recipient, 194 hasScrolled, 195 setHasScrolled, 196}: { 197 moderation: ModerationDecision 198 recipient: Shadow<AppBskyActorDefs.ProfileViewDetailed> 199 hasScrolled: boolean 200 setHasScrolled: React.Dispatch<React.SetStateAction<boolean>> 201}) { 202 const convoState = useConvo() 203 const navigation = useNavigation<NavigationProp>() 204 const {params} = 205 useRoute<RouteProp<CommonNavigatorParams, 'MessagesConversation'>>() 206 const {needsEmailVerification} = useEmail() 207 const emailDialogControl = useEmailDialogControl() 208 209 /** 210 * Must be non-reactive, otherwise the update to open the global dialog will 211 * cause a re-render loop. 212 */ 213 const maybeBlockForEmailVerification = useNonReactiveCallback(() => { 214 if (needsEmailVerification) { 215 /* 216 * HACKFIX 217 * 218 * Load bearing timeout, to bump this state update until the after the 219 * `navigator.addListener('state')` handler closes elements from 220 * `shell/index.*.tsx` - sfn & esb 221 */ 222 setTimeout(() => 223 emailDialogControl.open({ 224 id: EmailDialogScreenID.Verify, 225 instructions: [ 226 <Trans key="pre-compose"> 227 Before you can message another user, you must first verify your 228 email. 229 </Trans>, 230 ], 231 onCloseWithoutVerifying: () => { 232 if (navigation.canGoBack()) { 233 navigation.goBack() 234 } else { 235 navigation.navigate('Messages', {animation: 'pop'}) 236 } 237 }, 238 }), 239 ) 240 } 241 }) 242 243 useEffect(() => { 244 maybeBlockForEmailVerification() 245 }, [maybeBlockForEmailVerification]) 246 247 return ( 248 <> 249 <MessagesListHeader profile={recipient} moderation={moderation} /> 250 {isConvoActive(convoState) && ( 251 <MessagesList 252 hasScrolled={hasScrolled} 253 setHasScrolled={setHasScrolled} 254 blocked={moderation?.blocked} 255 hasAcceptOverride={!!params.accept} 256 footer={ 257 <MessagesListBlockedFooter 258 recipient={recipient} 259 convoId={convoState.convo.id} 260 hasMessages={convoState.items.length > 0} 261 moderation={moderation} 262 /> 263 } 264 /> 265 )} 266 </> 267 ) 268}