[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 logiczio/stable
parent
8304ad91ac
commit
6f9993ca55
|
@ -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,49 +82,59 @@ 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={
|
||||||
data={chat.state.items}
|
chat.state.status === ConvoStatus.Ready ? chat.state.items : undefined
|
||||||
keyExtractor={item => item.key}
|
}
|
||||||
renderItem={renderItem}
|
keyExtractor={keyExtractor}
|
||||||
contentContainerStyle={{paddingHorizontal: 10}}
|
renderItem={renderItem}
|
||||||
// In the future, we might want to adjust this value. Not very concerning right now as long as we are only
|
contentContainerStyle={{paddingHorizontal: 10}}
|
||||||
// dealing with text. But whenever we have images or other media and things are taller, we will want to lower
|
// In the future, we might want to adjust this value. Not very concerning right now as long as we are only
|
||||||
// this...probably.
|
// dealing with text. But whenever we have images or other media and things are taller, we will want to lower
|
||||||
initialNumToRender={20}
|
// this...probably.
|
||||||
// Same with the max to render per batch. Let's be safe for now though.
|
initialNumToRender={20}
|
||||||
maxToRenderPerBatch={25}
|
// Same with the max to render per batch. Let's be safe for now though.
|
||||||
inverted={true}
|
maxToRenderPerBatch={25}
|
||||||
onEndReached={onEndReached}
|
inverted={true}
|
||||||
onScrollToIndexFailed={onScrollToEndFailed}
|
onEndReached={onEndReached}
|
||||||
onContentSizeChange={onContentSizeChange}
|
onScrollToIndexFailed={onScrollToEndFailed}
|
||||||
onViewableItemsChanged={onViewableItemsChanged}
|
onContentSizeChange={onContentSizeChange}
|
||||||
viewabilityConfig={viewabilityConfig}
|
onViewableItemsChanged={onViewableItemsChanged}
|
||||||
maintainVisibleContentPosition={{
|
viewabilityConfig={viewabilityConfig}
|
||||||
minIndexForVisible: 0,
|
maintainVisibleContentPosition={{
|
||||||
}}
|
minIndexForVisible: 1,
|
||||||
ListFooterComponent={
|
}}
|
||||||
<MaybeLoader isLoading={chat.state.isFetchingHistory} />
|
ListFooterComponent={
|
||||||
}
|
<MaybeLoader
|
||||||
removeClippedSubviews={true}
|
isLoading={
|
||||||
ref={flatListRef}
|
chat.state.status === ConvoStatus.Ready &&
|
||||||
keyboardDismissMode="none"
|
chat.state.isFetchingHistory
|
||||||
/>
|
}
|
||||||
)}
|
/>
|
||||||
|
}
|
||||||
|
removeClippedSubviews={true}
|
||||||
|
ref={flatListRef}
|
||||||
|
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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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 (
|
||||||
|
<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 (
|
return (
|
||||||
<CenteredView style={{flex: 1}} sideBorders>
|
<ListMaybePlaceholder
|
||||||
<ListMaybePlaceholder isLoading={true} isError={isError} />
|
isLoading={true}
|
||||||
</CenteredView>
|
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)
|
||||||
|
|
Loading…
Reference in New Issue