import React, {useCallback, useMemo, useRef, useState} from 'react'
import {FlatList, View, ViewToken} from 'react-native'
import {Alert} from 'react-native'
import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
import {isWeb} from '#/platform/detection'
import {MessageInput} from '#/screens/Messages/Conversation/MessageInput'
import {MessageItem} from '#/screens/Messages/Conversation/MessageItem'
import {
useChat,
useChatLogQuery,
useSendMessageMutation,
} from '#/screens/Messages/Temp/query/query'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import * as TempDmChatDefs from '#/temp/dm/defs'
type MessageWithNext = {
message: TempDmChatDefs.MessageView | TempDmChatDefs.DeletedMessage
next: TempDmChatDefs.MessageView | TempDmChatDefs.DeletedMessage | null
}
function MaybeLoader({isLoading}: {isLoading: boolean}) {
return (
{isLoading && }
)
}
function renderItem({item}: {item: MessageWithNext}) {
if (TempDmChatDefs.isMessageView(item.message))
return
if (TempDmChatDefs.isDeletedMessage(item)) return Deleted message
return null
}
// TODO rm
// TEMP: This is a temporary function to generate unique keys for mutation placeholders
const generateUniqueKey = () => `_${Math.random().toString(36).substr(2, 9)}`
function onScrollToEndFailed() {
// Placeholder function. You have to give FlatList something or else it will error.
}
export function MessagesList({chatId}: {chatId: string}) {
const flatListRef = useRef(null)
// Whenever we reach the end (visually the top), we don't want to keep calling it. We will set `isFetching` to true
// once the request for new posts starts. Then, we will change it back to false after the content size changes.
const isFetching = useRef(false)
// 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 [_, setShowSpinner] = useState(false)
// Query Data
const {data: chat} = useChat(chatId)
const {mutate: sendMessage} = useSendMessageMutation(chatId)
useChatLogQuery()
const [onViewableItemsChanged, viewabilityConfig] = useMemo(() => {
return [
(info: {viewableItems: Array; changed: Array}) => {
const firstVisibleIndex = info.viewableItems[0]?.index
isAtBottom.current = Number(firstVisibleIndex) < 2
},
{
itemVisiblePercentThreshold: 50,
minimumViewTime: 10,
},
]
}, [])
const onContentSizeChange = useCallback(() => {
if (isAtBottom.current) {
flatListRef.current?.scrollToOffset({offset: 0, animated: true})
}
isFetching.current = false
setShowSpinner(false)
}, [])
const onEndReached = useCallback(() => {
if (isFetching.current) return
isFetching.current = true
setShowSpinner(true)
// Eventually we will add more here when we hit the top through RQuery
// We wouldn't actually use a timeout, but there would be a delay while loading
setTimeout(() => {
// Do something
setShowSpinner(false)
}, 1000)
}, [])
const onInputFocus = useCallback(() => {
if (!isAtBottom.current) {
flatListRef.current?.scrollToOffset({offset: 0, animated: true})
}
}, [])
const onSendMessage = useCallback(
async (message: string) => {
if (!message) return
try {
sendMessage({
message,
tempId: generateUniqueKey(),
})
} catch (e: any) {
Alert.alert(e.toString())
}
},
[sendMessage],
)
const onInputBlur = useCallback(() => {}, [])
const messages = useMemo(() => {
if (!chat) return []
const filtered = chat.messages
.filter(
(
message,
): message is
| TempDmChatDefs.MessageView
| TempDmChatDefs.DeletedMessage => {
return (
TempDmChatDefs.isMessageView(message) ||
TempDmChatDefs.isDeletedMessage(message)
)
},
)
.reduce((acc, message) => {
// convert [n1, n2, n3, ...] to [{message: n1, next: n2}, {message: n2, next: n3}, {message: n3, next: n4}, ...]
return [...acc, {message, next: acc.at(-1)?.message ?? null}]
}, [] as MessageWithNext[])
totalMessages.current = filtered.length
return filtered
}, [chat])
return (
item.message.id}
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={}
removeClippedSubviews={true}
ref={flatListRef}
keyboardDismissMode="none"
/>
)
}