Bluesky app fork with some witchin' additions 💫

[Clipclops] Fix list, rework structure (#3799)

* proper min index

* move keyextractor out of react

* move onSendMessage out

* don't render the flatlist conditionally

* add loader

* rework structure

* remove some unneeded logic

authored by hailey.at and committed by

GitHub 6f9993ca 8304ad91

+80 -63
+49 -51
src/screens/Messages/Conversation/MessagesList.tsx
··· 3 3 import {KeyboardAvoidingView} from 'react-native-keyboard-controller' 4 4 5 5 import {useChat} from '#/state/messages' 6 - import {ChatProvider} from '#/state/messages' 7 6 import {ConvoItem, ConvoStatus} from '#/state/messages/convo' 8 7 import {isWeb} from 'platform/detection' 9 8 import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' ··· 37 36 return null 38 37 } 39 38 39 + function keyExtractor(item: ConvoItem) { 40 + return item.key 41 + } 42 + 40 43 function onScrollToEndFailed() { 41 44 // Placeholder function. You have to give FlatList something or else it will error. 42 45 } 43 46 44 - export function MessagesList({convoId}: {convoId: string}) { 45 - return ( 46 - <ChatProvider convoId={convoId}> 47 - <MessagesListInner /> 48 - </ChatProvider> 49 - ) 50 - } 51 - 52 - export function MessagesListInner() { 47 + export function MessagesList() { 53 48 const chat = useChat() 54 49 const flatListRef = useRef<FlatList>(null) 55 50 // We use this to know if we should scroll after a new clop is added to the list 56 51 const isAtBottom = useRef(false) 57 - 58 - // Because the viewableItemsChanged callback won't have access to the updated state, we use a ref to store the 59 - // total number of clops 60 - // TODO this needs to be set to whatever the initial number of messages is 61 - // const totalMessages = useRef(10) 62 - 63 - // TODO later 64 52 65 53 const [onViewableItemsChanged, viewabilityConfig] = useMemo(() => { 66 54 return [ ··· 94 82 95 83 const onInputBlur = useCallback(() => {}, []) 96 84 85 + const onSendMessage = useCallback( 86 + (text: string) => { 87 + chat.service.sendMessage({ 88 + text, 89 + }) 90 + }, 91 + [chat.service], 92 + ) 93 + 97 94 return ( 98 95 <KeyboardAvoidingView 99 96 style={{flex: 1, marginBottom: isWeb ? 20 : 85}} 100 97 behavior="padding" 101 98 keyboardVerticalOffset={70} 102 99 contentContainerStyle={{flex: 1}}> 103 - {chat.state.status === ConvoStatus.Ready && ( 104 - <FlatList 105 - data={chat.state.items} 106 - keyExtractor={item => item.key} 107 - renderItem={renderItem} 108 - contentContainerStyle={{paddingHorizontal: 10}} 109 - // In the future, we might want to adjust this value. Not very concerning right now as long as we are only 110 - // dealing with text. But whenever we have images or other media and things are taller, we will want to lower 111 - // this...probably. 112 - initialNumToRender={20} 113 - // Same with the max to render per batch. Let's be safe for now though. 114 - maxToRenderPerBatch={25} 115 - inverted={true} 116 - onEndReached={onEndReached} 117 - onScrollToIndexFailed={onScrollToEndFailed} 118 - onContentSizeChange={onContentSizeChange} 119 - onViewableItemsChanged={onViewableItemsChanged} 120 - viewabilityConfig={viewabilityConfig} 121 - maintainVisibleContentPosition={{ 122 - minIndexForVisible: 0, 123 - }} 124 - ListFooterComponent={ 125 - <MaybeLoader isLoading={chat.state.isFetchingHistory} /> 126 - } 127 - removeClippedSubviews={true} 128 - ref={flatListRef} 129 - keyboardDismissMode="none" 130 - /> 131 - )} 100 + <FlatList 101 + data={ 102 + chat.state.status === ConvoStatus.Ready ? chat.state.items : undefined 103 + } 104 + keyExtractor={keyExtractor} 105 + renderItem={renderItem} 106 + contentContainerStyle={{paddingHorizontal: 10}} 107 + // In the future, we might want to adjust this value. Not very concerning right now as long as we are only 108 + // dealing with text. But whenever we have images or other media and things are taller, we will want to lower 109 + // this...probably. 110 + initialNumToRender={20} 111 + // Same with the max to render per batch. Let's be safe for now though. 112 + maxToRenderPerBatch={25} 113 + inverted={true} 114 + onEndReached={onEndReached} 115 + onScrollToIndexFailed={onScrollToEndFailed} 116 + onContentSizeChange={onContentSizeChange} 117 + onViewableItemsChanged={onViewableItemsChanged} 118 + viewabilityConfig={viewabilityConfig} 119 + maintainVisibleContentPosition={{ 120 + minIndexForVisible: 1, 121 + }} 122 + ListFooterComponent={ 123 + <MaybeLoader 124 + isLoading={ 125 + chat.state.status === ConvoStatus.Ready && 126 + chat.state.isFetchingHistory 127 + } 128 + /> 129 + } 130 + removeClippedSubviews={true} 131 + ref={flatListRef} 132 + keyboardDismissMode="none" 133 + /> 132 134 133 135 <View style={{paddingHorizontal: 10}}> 134 136 <MessageInput 135 - onSendMessage={text => { 136 - chat.service.sendMessage({ 137 - text, 138 - }) 139 - }} 137 + onSendMessage={onSendMessage} 140 138 onFocus={onInputFocus} 141 139 onBlur={onInputBlur} 142 140 />
+31 -12
src/screens/Messages/Conversation/index.tsx
··· 9 9 10 10 import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' 11 11 import {useGate} from '#/lib/statsig/statsig' 12 - import {useConvoQuery} from '#/state/queries/messages/conversation' 13 12 import {BACK_HITSLOP} from 'lib/constants' 14 13 import {isWeb} from 'platform/detection' 14 + import {ChatProvider, useChat} from 'state/messages' 15 + import {ConvoStatus} from 'state/messages/convo' 15 16 import {useSession} from 'state/session' 16 17 import {UserAvatar} from 'view/com/util/UserAvatar' 17 18 import {CenteredView} from 'view/com/util/Views' ··· 30 31 export function MessagesConversationScreen({route}: Props) { 31 32 const gate = useGate() 32 33 const convoId = route.params.conversation 34 + 35 + if (!gate('dms')) return <ClipClopGate /> 36 + 37 + return ( 38 + <ChatProvider convoId={convoId}> 39 + <Inner /> 40 + </ChatProvider> 41 + ) 42 + } 43 + 44 + function Inner() { 45 + const chat = useChat() 33 46 const {currentAccount} = useSession() 34 47 const myDid = currentAccount?.did 35 48 36 - const {data: chat, isError: isError} = useConvoQuery(convoId) 37 49 const otherProfile = React.useMemo(() => { 38 - return chat?.members?.find(m => m.did !== myDid) 39 - }, [chat?.members, myDid]) 50 + if (chat.state.status !== ConvoStatus.Ready) return 51 + return chat.state.convo.members.find(m => m.did !== myDid) 52 + }, [chat.state, myDid]) 40 53 41 - if (!gate('dms')) return <ClipClopGate /> 42 - 43 - if (!chat || !otherProfile) { 54 + // TODO whenever we have error messages, we should use them in here -hailey 55 + if (chat.state.status !== ConvoStatus.Ready || !otherProfile) { 44 56 return ( 45 - <CenteredView style={{flex: 1}} sideBorders> 46 - <ListMaybePlaceholder isLoading={true} isError={isError} /> 47 - </CenteredView> 57 + <ListMaybePlaceholder 58 + isLoading={true} 59 + isError={chat.state.status === ConvoStatus.Error} 60 + /> 48 61 ) 49 62 } 50 63 51 64 return ( 52 65 <CenteredView style={{flex: 1}} sideBorders> 53 66 <Header profile={otherProfile} /> 54 - <MessagesList convoId={convoId} /> 67 + <MessagesList /> 55 68 </CenteredView> 56 69 ) 57 70 } 58 71 59 - function Header({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) { 72 + let Header = ({ 73 + profile, 74 + }: { 75 + profile: AppBskyActorDefs.ProfileViewBasic 76 + }): React.ReactNode => { 60 77 const t = useTheme() 61 78 const {_} = useLingui() 62 79 const {gtTablet} = useBreakpoints() ··· 126 143 </View> 127 144 ) 128 145 } 146 + 147 + Header = React.memo(Header)