[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 {useChat} from '#/state/messages'
import {ChatProvider} from '#/state/messages'
import {ConvoItem, ConvoStatus} from '#/state/messages/convo'
import {isWeb} from 'platform/detection'
import {MessageInput} from '#/screens/Messages/Conversation/MessageInput'
@ -37,31 +36,20 @@ function renderItem({item}: {item: ConvoItem}) {
return null
}
function keyExtractor(item: ConvoItem) {
return item.key
}
function onScrollToEndFailed() {
// Placeholder function. You have to give FlatList something or else it will error.
}
export function MessagesList({convoId}: {convoId: string}) {
return (
<ChatProvider convoId={convoId}>
<MessagesListInner />
</ChatProvider>
)
}
export function MessagesListInner() {
export function MessagesList() {
const chat = useChat()
const flatListRef = useRef<FlatList>(null)
// We use this to know if we should scroll after a new clop is added to the list
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(() => {
return [
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
@ -94,49 +82,59 @@ export function MessagesListInner() {
const onInputBlur = useCallback(() => {}, [])
const onSendMessage = useCallback(
(text: string) => {
chat.service.sendMessage({
text,
})
},
[chat.service],
)
return (
<KeyboardAvoidingView
style={{flex: 1, marginBottom: isWeb ? 20 : 85}}
behavior="padding"
keyboardVerticalOffset={70}
contentContainerStyle={{flex: 1}}>
{chat.state.status === ConvoStatus.Ready && (
<FlatList
data={chat.state.items}
keyExtractor={item => item.key}
renderItem={renderItem}
contentContainerStyle={{paddingHorizontal: 10}}
// In the future, we might want to adjust this value. Not very concerning right now as long as we are only
// dealing with text. But whenever we have images or other media and things are taller, we will want to lower
// this...probably.
initialNumToRender={20}
// Same with the max to render per batch. Let's be safe for now though.
maxToRenderPerBatch={25}
inverted={true}
onEndReached={onEndReached}
onScrollToIndexFailed={onScrollToEndFailed}
onContentSizeChange={onContentSizeChange}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
}}
ListFooterComponent={
<MaybeLoader isLoading={chat.state.isFetchingHistory} />
}
removeClippedSubviews={true}
ref={flatListRef}
keyboardDismissMode="none"
/>
)}
<FlatList
data={
chat.state.status === ConvoStatus.Ready ? chat.state.items : undefined
}
keyExtractor={keyExtractor}
renderItem={renderItem}
contentContainerStyle={{paddingHorizontal: 10}}
// In the future, we might want to adjust this value. Not very concerning right now as long as we are only
// dealing with text. But whenever we have images or other media and things are taller, we will want to lower
// this...probably.
initialNumToRender={20}
// Same with the max to render per batch. Let's be safe for now though.
maxToRenderPerBatch={25}
inverted={true}
onEndReached={onEndReached}
onScrollToIndexFailed={onScrollToEndFailed}
onContentSizeChange={onContentSizeChange}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
maintainVisibleContentPosition={{
minIndexForVisible: 1,
}}
ListFooterComponent={
<MaybeLoader
isLoading={
chat.state.status === ConvoStatus.Ready &&
chat.state.isFetchingHistory
}
/>
}
removeClippedSubviews={true}
ref={flatListRef}
keyboardDismissMode="none"
/>
<View style={{paddingHorizontal: 10}}>
<MessageInput
onSendMessage={text => {
chat.service.sendMessage({
text,
})
}}
onSendMessage={onSendMessage}
onFocus={onInputFocus}
onBlur={onInputBlur}
/>

View File

@ -9,9 +9,10 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {useGate} from '#/lib/statsig/statsig'
import {useConvoQuery} from '#/state/queries/messages/conversation'
import {BACK_HITSLOP} from 'lib/constants'
import {isWeb} from 'platform/detection'
import {ChatProvider, useChat} from 'state/messages'
import {ConvoStatus} from 'state/messages/convo'
import {useSession} from 'state/session'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {CenteredView} from 'view/com/util/Views'
@ -30,33 +31,49 @@ type Props = NativeStackScreenProps<
export function MessagesConversationScreen({route}: Props) {
const gate = useGate()
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 (!chat || !otherProfile) {
return (
<ChatProvider convoId={convoId}>
<Inner />
</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 (
<CenteredView style={{flex: 1}} sideBorders>
<ListMaybePlaceholder isLoading={true} isError={isError} />
</CenteredView>
<ListMaybePlaceholder
isLoading={true}
isError={chat.state.status === ConvoStatus.Error}
/>
)
}
return (
<CenteredView style={{flex: 1}} sideBorders>
<Header profile={otherProfile} />
<MessagesList convoId={convoId} />
<MessagesList />
</CenteredView>
)
}
function Header({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) {
let Header = ({
profile,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
}): React.ReactNode => {
const t = useTheme()
const {_} = useLingui()
const {gtTablet} = useBreakpoints()
@ -126,3 +143,5 @@ function Header({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) {
</View>
)
}
Header = React.memo(Header)