[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
zio/stable
Hailey 2024-05-01 11:48:19 -07:00 committed by GitHub
parent 8304ad91ac
commit 6f9993ca55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 82 additions and 65 deletions

View File

@ -3,7 +3,6 @@ import {FlatList, View, ViewToken} from 'react-native'
import {KeyboardAvoidingView} from 'react-native-keyboard-controller' import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
import {useChat} from '#/state/messages' import {useChat} from '#/state/messages'
import {ChatProvider} from '#/state/messages'
import {ConvoItem, ConvoStatus} from '#/state/messages/convo' import {ConvoItem, ConvoStatus} from '#/state/messages/convo'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' import {MessageInput} from '#/screens/Messages/Conversation/MessageInput'
@ -37,31 +36,20 @@ function renderItem({item}: {item: ConvoItem}) {
return null return null
} }
function keyExtractor(item: ConvoItem) {
return item.key
}
function onScrollToEndFailed() { function onScrollToEndFailed() {
// Placeholder function. You have to give FlatList something or else it will error. // Placeholder function. You have to give FlatList something or else it will error.
} }
export function MessagesList({convoId}: {convoId: string}) { export function MessagesList() {
return (
<ChatProvider convoId={convoId}>
<MessagesListInner />
</ChatProvider>
)
}
export function MessagesListInner() {
const chat = useChat() const chat = useChat()
const flatListRef = useRef<FlatList>(null) const flatListRef = useRef<FlatList>(null)
// We use this to know if we should scroll after a new clop is added to the list // We use this to know if we should scroll after a new clop is added to the list
const isAtBottom = useRef(false) const isAtBottom = useRef(false)
// Because the viewableItemsChanged callback won't have access to the updated state, we use a ref to store the
// total number of clops
// TODO this needs to be set to whatever the initial number of messages is
// const totalMessages = useRef(10)
// TODO later
const [onViewableItemsChanged, viewabilityConfig] = useMemo(() => { const [onViewableItemsChanged, viewabilityConfig] = useMemo(() => {
return [ return [
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => { (info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
@ -94,16 +82,26 @@ export function MessagesListInner() {
const onInputBlur = useCallback(() => {}, []) const onInputBlur = useCallback(() => {}, [])
const onSendMessage = useCallback(
(text: string) => {
chat.service.sendMessage({
text,
})
},
[chat.service],
)
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
style={{flex: 1, marginBottom: isWeb ? 20 : 85}} style={{flex: 1, marginBottom: isWeb ? 20 : 85}}
behavior="padding" behavior="padding"
keyboardVerticalOffset={70} keyboardVerticalOffset={70}
contentContainerStyle={{flex: 1}}> contentContainerStyle={{flex: 1}}>
{chat.state.status === ConvoStatus.Ready && (
<FlatList <FlatList
data={chat.state.items} data={
keyExtractor={item => item.key} chat.state.status === ConvoStatus.Ready ? chat.state.items : undefined
}
keyExtractor={keyExtractor}
renderItem={renderItem} renderItem={renderItem}
contentContainerStyle={{paddingHorizontal: 10}} contentContainerStyle={{paddingHorizontal: 10}}
// In the future, we might want to adjust this value. Not very concerning right now as long as we are only // In the future, we might want to adjust this value. Not very concerning right now as long as we are only
@ -119,24 +117,24 @@ export function MessagesListInner() {
onViewableItemsChanged={onViewableItemsChanged} onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig} viewabilityConfig={viewabilityConfig}
maintainVisibleContentPosition={{ maintainVisibleContentPosition={{
minIndexForVisible: 0, minIndexForVisible: 1,
}} }}
ListFooterComponent={ ListFooterComponent={
<MaybeLoader isLoading={chat.state.isFetchingHistory} /> <MaybeLoader
isLoading={
chat.state.status === ConvoStatus.Ready &&
chat.state.isFetchingHistory
}
/>
} }
removeClippedSubviews={true} removeClippedSubviews={true}
ref={flatListRef} ref={flatListRef}
keyboardDismissMode="none" keyboardDismissMode="none"
/> />
)}
<View style={{paddingHorizontal: 10}}> <View style={{paddingHorizontal: 10}}>
<MessageInput <MessageInput
onSendMessage={text => { onSendMessage={onSendMessage}
chat.service.sendMessage({
text,
})
}}
onFocus={onInputFocus} onFocus={onInputFocus}
onBlur={onInputBlur} onBlur={onInputBlur}
/> />

View File

@ -9,9 +9,10 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {useGate} from '#/lib/statsig/statsig' import {useGate} from '#/lib/statsig/statsig'
import {useConvoQuery} from '#/state/queries/messages/conversation'
import {BACK_HITSLOP} from 'lib/constants' import {BACK_HITSLOP} from 'lib/constants'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {ChatProvider, useChat} from 'state/messages'
import {ConvoStatus} from 'state/messages/convo'
import {useSession} from 'state/session' import {useSession} from 'state/session'
import {UserAvatar} from 'view/com/util/UserAvatar' import {UserAvatar} from 'view/com/util/UserAvatar'
import {CenteredView} from 'view/com/util/Views' import {CenteredView} from 'view/com/util/Views'
@ -30,33 +31,49 @@ type Props = NativeStackScreenProps<
export function MessagesConversationScreen({route}: Props) { export function MessagesConversationScreen({route}: Props) {
const gate = useGate() const gate = useGate()
const convoId = route.params.conversation const convoId = route.params.conversation
const {currentAccount} = useSession()
const myDid = currentAccount?.did
const {data: chat, isError: isError} = useConvoQuery(convoId)
const otherProfile = React.useMemo(() => {
return chat?.members?.find(m => m.did !== myDid)
}, [chat?.members, myDid])
if (!gate('dms')) return <ClipClopGate /> if (!gate('dms')) return <ClipClopGate />
if (!chat || !otherProfile) {
return ( return (
<CenteredView style={{flex: 1}} sideBorders> <ChatProvider convoId={convoId}>
<ListMaybePlaceholder isLoading={true} isError={isError} /> <Inner />
</CenteredView> </ChatProvider>
)
}
function Inner() {
const chat = useChat()
const {currentAccount} = useSession()
const myDid = currentAccount?.did
const otherProfile = React.useMemo(() => {
if (chat.state.status !== ConvoStatus.Ready) return
return chat.state.convo.members.find(m => m.did !== myDid)
}, [chat.state, myDid])
// TODO whenever we have error messages, we should use them in here -hailey
if (chat.state.status !== ConvoStatus.Ready || !otherProfile) {
return (
<ListMaybePlaceholder
isLoading={true}
isError={chat.state.status === ConvoStatus.Error}
/>
) )
} }
return ( return (
<CenteredView style={{flex: 1}} sideBorders> <CenteredView style={{flex: 1}} sideBorders>
<Header profile={otherProfile} /> <Header profile={otherProfile} />
<MessagesList convoId={convoId} /> <MessagesList />
</CenteredView> </CenteredView>
) )
} }
function Header({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) { let Header = ({
profile,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
}): React.ReactNode => {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {gtTablet} = useBreakpoints() const {gtTablet} = useBreakpoints()
@ -126,3 +143,5 @@ function Header({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) {
</View> </View>
) )
} }
Header = React.memo(Header)