import React, { useEffect, useMemo, useRef, useState } from 'react'; // RNGR's FlatList ist currently breaking the pull-to-refresh behaviour on Android // See https://github.com/software-mansion/react-native-gesture-handler/issues/598 import { FlatList, StyleSheet, View } from 'react-native'; import type { Channel } from 'stream-chat'; import { ChannelsContextValue, useChannelsContext, } from '../../contexts/channelsContext/ChannelsContext'; import { useChatContext } from '../../contexts/chatContext/ChatContext'; import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useDebugContext } from '../../contexts/debugContext/DebugContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStableCallback } from '../../hooks'; import { ChannelPreview } from '../ChannelPreview/ChannelPreview'; export type ChannelListViewPropsWithContext = Omit< ChannelsContextValue, 'maxUnreadCount' | 'numberOfSkeletons' | 'onSelect' >; const StatusIndicator = () => { const { isOnline } = useChatContext(); const styles = useStyles(); const { error, loadingChannels, refreshList } = useChannelsContext(); const { ChannelListHeaderErrorIndicator, ChannelListHeaderNetworkDownIndicator } = useComponentsContext(); if (loadingChannels) { return null; } if (!isOnline) { return ( ); } else if (error) { return ( ); } return null; }; const renderItem = ({ item }: { item: Channel }) => ; const keyExtractor = (item: Channel) => item.cid; const ChannelListViewWithContext = (props: ChannelListViewPropsWithContext) => { const onEndReachedCalledDuringCurrentScrollRef = useRef(false); const { additionalFlatListProps, channelListInitialized, channels, error, forceUpdate, hasNextPage, loadingChannels, loadingNextPage, loadMoreThreshold, loadNextPage, refreshing, refreshList, reloadList, setFlatListRef, } = props; const { EmptyStateIndicator, ChannelListFooterLoadingIndicator, ListHeaderComponent, LoadingErrorIndicator, ChannelListLoadingIndicator: LoadingIndicator, } = useComponentsContext(); /** * In order to prevent the EmptyStateIndicator component from showing up briefly on mount, * we set the loading state one cycle behind to ensure the channels are set before the * change to loadingChannels is registered. */ const [loading, setLoading] = useState(true); const debugRef = useDebugContext(); const styles = useStyles(); useEffect(() => { if (!!loadingChannels !== loading) { setLoading(!!loadingChannels); } }, [loading, loadingChannels]); const isDebugModeEnabled = __DEV__ && debugRef && debugRef.current; if (isDebugModeEnabled) { if (debugRef.current.setEventType) { debugRef.current.setEventType('send'); } if (debugRef.current.setSendEventParams) { debugRef.current.setSendEventParams({ action: 'Channels', data: channels?.map((channel) => ({ data: channel.data, members: channel.state.members, })), }); } } const onEndReached = useStableCallback(() => { if (!onEndReachedCalledDuringCurrentScrollRef.current && hasNextPage) { loadNextPage(); onEndReachedCalledDuringCurrentScrollRef.current = true; } }); if (error && !refreshing && !loadingChannels && (channels === null || !channelListInitialized)) { return ( ); } return ( <> : } ListFooterComponent={loadingNextPage ? : undefined} ListHeaderComponent={ListHeaderComponent} onEndReached={onEndReached} onEndReachedThreshold={loadMoreThreshold} onMomentumScrollBegin={() => (onEndReachedCalledDuringCurrentScrollRef.current = false)} onRefresh={refreshList} ref={setFlatListRef} refreshing={refreshing} renderItem={renderItem} style={styles.flatList} testID='channel-list-view' {...additionalFlatListProps} /> ); }; export type ChannelListViewProps = Partial; /** * This UI component displays the preview list of channels and handles Channel navigation. It * receives all props from the ChannelList component. * * @example ./ChannelListView.md */ export const ChannelListView = (props: ChannelListViewProps) => { const { additionalFlatListProps, channelListInitialized, channels, error, forceUpdate, hasNextPage, loadingChannels, loadingNextPage, loadMoreThreshold, loadNextPage, refreshing, refreshList, reloadList, setFlatListRef, } = useChannelsContext(); return ( ); }; ChannelListView.displayName = 'ChannelListView{channelListView}'; const useStyles = () => { const { theme: { semantics, channelListView: { flatList, flatListContent }, }, } = useTheme(); return useMemo(() => { return StyleSheet.create({ flatList: { flex: 1, backgroundColor: semantics.backgroundCoreApp, ...flatList, }, flatListContentContainer: { flexGrow: 1, backgroundColor: semantics.backgroundCoreApp, ...flatListContent, }, statusIndicator: { left: 0, position: 'absolute', right: 0, top: 0 }, }); }, [flatList, flatListContent, semantics]); };