diff --git a/src/components/ui/ChannelsDisplayer.tsx b/src/components/ui/ChannelsDisplayer.tsx index ac92c56f8..2c04d7828 100644 --- a/src/components/ui/ChannelsDisplayer.tsx +++ b/src/components/ui/ChannelsDisplayer.tsx @@ -1,6 +1,13 @@ import '@ethersproject/shims'; -import React, {useEffect, useMemo, useRef, useState} from 'react'; -import {FlatList, Image, StyleSheet, TextInput, View} from 'react-native'; +import React, {useEffect, useMemo, useState} from 'react'; +import { + FlatList, + Image, + Platform, + StyleSheet, + TextInput, + View, +} from 'react-native'; import {useSelector} from 'react-redux'; import StylishLabel from 'src/components/labels/StylishLabel'; import EPNSActivity from 'src/components/loaders/EPNSActivity'; @@ -12,7 +19,6 @@ import useSubscriptions from 'src/hooks/channel/useSubscriptions'; import { Channel, selectChannels, - selectChannelsReachedEnd, selectIsLoadingSubscriptions, } from 'src/redux/channelSlice'; @@ -21,52 +27,25 @@ import Globals from '../../Globals'; import {ChannelCategories} from './ChannelCategories'; const ChannelsDisplayer = () => { - const [searchTimer, setSearchTimer] = useState(); - - const DEBOUNCE_TIMEOUT = 500; //time in millisecond which we want to wait for then to finish typing - const [search, setSearch] = React.useState(''); - const [showSearchResults, setShowSearchResults] = useState(false); const [selectedCategory, setSelectedCategory] = useState( Globals.CONSTANTS.ALL_CATEGORIES, ); const channelResults = useSelector(selectChannels); - const channelsReachedEnd = useSelector(selectChannelsReachedEnd); - const { - loadMoreChannels, - loadSearchResults, - resetChannelData, - isLoadingChannels, - isLoadingSearchResults, - searchResults, - } = useChannels({tag: selectedCategory, showSearchResults}); + const {isLoading, isLoadingMore, resetChannelData, loadMore} = useChannels({ + tag: selectedCategory, + searchQuery: search, + }); const isLoadingSubscriptions = useSelector(selectIsLoadingSubscriptions); const {refreshSubscriptions} = useSubscriptions(); const {userPushSDKInstance} = usePushApi(); const {openSheet} = useSheets(); - const channels = useMemo(() => { - return showSearchResults ? searchResults : channelResults; - }, [showSearchResults, searchResults, channelResults]); - - const endReached = useMemo(() => { - return showSearchResults ? true : channelsReachedEnd; - }, [showSearchResults, true, channelsReachedEnd]); - - const isLoading = useMemo(() => { - return showSearchResults ? isLoadingSearchResults : isLoadingChannels; - }, [showSearchResults, isLoadingSearchResults, isLoadingChannels]); - - const loadMore = useMemo(() => { - return showSearchResults ? () => {} : loadMoreChannels; - }, [showSearchResults]); - useEffect(() => { if (userPushSDKInstance) { refreshSubscriptions(); - loadMore(); } }, [userPushSDKInstance]); @@ -74,34 +53,15 @@ const ChannelsDisplayer = () => { openSheet({name: 'NFSettingsSheet', channel}); }; - const searchForChannel = async (channelName: string) => { - if (channelName.trim() === '') { - setShowSearchResults(false); - return; - } - setShowSearchResults(true); - setSelectedCategory(Globals.CONSTANTS.ALL_CATEGORIES); - await loadSearchResults(channelName); - }; - const handleChannelSearch = async (searchQuery: string) => { - try { - if (searchTimer) { - clearTimeout(searchTimer); - } - setSearch(searchQuery); - setSearchTimer( - setTimeout(() => { - searchForChannel(searchQuery); - }, DEBOUNCE_TIMEOUT), - ); - } catch (e) {} + setSearch(searchQuery); + resetChannelData(); + setSelectedCategory(Globals.CONSTANTS.ALL_CATEGORIES); }; const handleCategoryChange = (category: string) => { - if (search.length || showSearchResults) { + if (search.length) { setSearch(''); - setShowSearchResults(false); } resetChannelData(); setSelectedCategory(category as string); @@ -127,12 +87,12 @@ const ChannelsDisplayer = () => { - {channels.length === 0 && ( + {channelResults.length === 0 && ( {!isLoading && !isLoadingSubscriptions ? ( // Show channel not found label @@ -140,7 +100,7 @@ const ChannelsDisplayer = () => { style={styles.infoText} fontSize={16} title={ - showSearchResults + search.length ? '[dg:No channels match your query, please search for another name/address]' : '[dg:No results available.]' } @@ -159,26 +119,23 @@ const ChannelsDisplayer = () => { )} - {channels.length !== 0 && !isLoadingSubscriptions && ( + {channelResults.length !== 0 && !isLoadingSubscriptions && ( item.channel.toString()} + keyExtractor={(item, index) => `${item.name}-${index}-channel-key`} initialNumToRender={20} showsVerticalScrollIndicator={false} ItemSeparatorComponent={() => } - onEndReached={() => { - if (!endReached) { - loadMore(); - } - }} + onEndReached={loadMore} + onEndReachedThreshold={0.8} renderItem={({item: channel}) => ( )} ListFooterComponent={() => { - return isLoading ? ( - + return isLoading || isLoadingMore ? ( + ) : null; @@ -203,7 +160,7 @@ const styles = StyleSheet.create({ }, channelListContentContainerStyle: { paddingTop: 10, - paddingBottom: 80, // Add some padding to the bottom to display last item content + paddingBottom: Platform.OS === 'android' ? 100 : 140, // Add some padding to the bottom to display last item content }, infodisplay: { width: '100%', @@ -246,6 +203,7 @@ const styles = StyleSheet.create({ borderColor: '#E5E5E5', marginVertical: 24, }, + footerLoadingView: {paddingVertical: 10}, }); export default ChannelsDisplayer; diff --git a/src/hooks/channel/useChannels.tsx b/src/hooks/channel/useChannels.tsx index 81e590ec7..3de16ebd2 100644 --- a/src/hooks/channel/useChannels.tsx +++ b/src/hooks/channel/useChannels.tsx @@ -4,54 +4,62 @@ import Globals from 'src/Globals'; import GLOBALS from 'src/Globals'; import {usePushApi} from 'src/contexts/PushApiContext'; import envConfig from 'src/env.config'; -import { - addChannels, - nextChannelsPage, - resetChannels, - selectChannelsPage, - selectChannelsReachedEnd, - setChannelsPage, - setChannelsReachedEnd, -} from 'src/redux/channelSlice'; +import {addChannels, resetChannels, setChannels} from 'src/redux/channelSlice'; export type UseChannelsProps = { tag: string; - showSearchResults: boolean; + searchQuery: string; }; -const useChannels = ({tag, showSearchResults}: UseChannelsProps) => { - const [isLoadingChannels, setChannelsLoading] = useState(false); - const [isLoadingSearchResults, setSearchResultsLoading] = - useState(false); - const [searchResults, setSearchResults] = useState([]); +const DEBOUNCE_TIMEOUT = 500; //time in millisecond which we want to wait for then to finish typing + +const useChannels = ({tag, searchQuery}: UseChannelsProps) => { + const [searchTimer, setSearchTimer] = useState(); + const [page, setPage] = useState(1); + const [isLoading, setIsLoading] = useState(true); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [isEndReached, setIsEndReached] = useState(false); // this confirms that all data is loaded + const {userPushSDKInstance} = usePushApi(); const dispatch = useDispatch(); - const channelsPage = useSelector(selectChannelsPage); - const channelsReachedEnd = useSelector(selectChannelsReachedEnd); - useEffect(() => { - if ( - !channelsReachedEnd && - !isLoadingChannels && - channelsPage !== 0 && - tag && - !showSearchResults - ) { - loadChannels({page: channelsPage}); + console.log('first', {page, tag, searchQuery}); + handleChannelInterval(); + }, [page, tag, searchQuery]); + + const handleChannelInterval = () => { + if (searchTimer) { + clearTimeout(searchTimer); } - }, [channelsPage, tag]); + setSearchTimer( + setTimeout(() => { + getChannelsData(); + }, DEBOUNCE_TIMEOUT), + ); + }; - const loadMoreChannels = () => { - if (channelsReachedEnd || isLoadingChannels) return; - dispatch(nextChannelsPage()); + const getChannelsData = () => { + console.log('second', {page, tag, searchQuery}); + if (searchQuery.trim().length) { + handleSearchAPI(); + } else { + handleChannelAPI(); + } }; - const loadChannels = async ({page}: {page: number}) => { - // If we have reached the end of the channels or loading, do nothing - if (channelsReachedEnd || isLoadingChannels) return; - setChannelsLoading(true); + const loadMore = () => { + if (!isEndReached && !isLoading && !isLoadingMore) { + setIsLoadingMore(true); + setPage(page + 1); + } + }; + + /*************************************************************/ + /** This function will handle Normal API call with tags **/ + /*************************************************************/ + const handleChannelAPI = async () => { try { const apiURL = envConfig.EPNS_SERVER + envConfig.ENDPOINT_FETCH_CHANNELS; let requestURL = `${apiURL}?limit=${GLOBALS.CONSTANTS.FEED_ITEMS_TO_PULL}&page=${page}`; @@ -59,52 +67,67 @@ const useChannels = ({tag, showSearchResults}: UseChannelsProps) => { requestURL = `${requestURL}&tag=${tag}`; } const resJson = await fetch(requestURL).then(response => response.json()); - if (resJson.channels.length !== 0) { + if (page > 1) { dispatch(addChannels(resJson.channels)); - dispatch(setChannelsReachedEnd(false)); - } else if (resJson.channels.length === 0) { - dispatch(setChannelsReachedEnd(true)); + } else { + dispatch(setChannels(resJson.channels)); + } + if (resJson.channels.length < GLOBALS.CONSTANTS.FEED_ITEMS_TO_PULL) { + setIsEndReached(true); } } catch (e) { console.error(e); } finally { - setChannelsLoading(false); + setIsLoading(false); + setIsLoadingMore(false); } }; /***************************************************/ - /** This function will reset all channel data **/ - /** Currently handled for onChangeCategory **/ + /** This function will handle search API call **/ /***************************************************/ - const resetChannelData = () => { - dispatch(setChannelsPage(1)); - dispatch(setChannelsReachedEnd(false)); - setChannelsLoading(false); - dispatch(resetChannels()); - }; - - const loadSearchResults = async (query: string) => { - setSearchResultsLoading(true); + const handleSearchAPI = async () => { try { - const results = await userPushSDKInstance?.channel.search(query, { - page: 1, - limit: GLOBALS.CONSTANTS.FEED_ITEMS_TO_PULL, - }); - setSearchResults(results); + const query = searchQuery.trim(); + if (query.length) { + const results = await userPushSDKInstance?.channel.search(query, { + page: page, + limit: GLOBALS.CONSTANTS.FEED_ITEMS_TO_PULL, + }); + if (page > 1) { + dispatch(addChannels(results)); + } else { + dispatch(setChannels(results)); + } + if (results.length < GLOBALS.CONSTANTS.FEED_ITEMS_TO_PULL) { + setIsEndReached(true); + } + } } catch (e) { console.error(e); } finally { - setSearchResultsLoading(false); + setIsLoading(false); + setIsLoadingMore(false); } }; + /***************************************************/ + /** This function will reset all channel data **/ + /** Currently handled for onChangeCategory **/ + /***************************************************/ + const resetChannelData = () => { + setIsLoading(true); + setPage(1); + dispatch(resetChannels()); + setIsLoadingMore(false); + setIsEndReached(false); + }; + return { - loadMoreChannels, - loadSearchResults, + isLoading, + isLoadingMore, + loadMore, resetChannelData, - isLoadingChannels, - isLoadingSearchResults, - searchResults, }; }; diff --git a/src/redux/channelSlice.ts b/src/redux/channelSlice.ts index 56d02441f..745819661 100644 --- a/src/redux/channelSlice.ts +++ b/src/redux/channelSlice.ts @@ -28,17 +28,12 @@ export type SubscriptionsMapping = {[key: string]: ISubscription | undefined}; type ChannelSliceData = { channels: Array; - channelsPage: number; - channelsReachedEnd: boolean; - subscriptions: SubscriptionsMapping; isLoadingSubscriptions: boolean; }; const initialState: ChannelSliceData = { channels: [], - channelsPage: 0, - channelsReachedEnd: false, subscriptions: {}, isLoadingSubscriptions: false, }; @@ -50,6 +45,9 @@ const channelSlice = createSlice({ addChannels: (state, action) => { state.channels = [...state.channels, ...action.payload]; }, + setChannels: (state, action) => { + state.channels = action.payload; + }, resetChannels: state => { state.channels = []; }, @@ -68,28 +66,17 @@ const channelSlice = createSlice({ setLoadingSubscriptions: (state, action: PayloadAction) => { state.isLoadingSubscriptions = action.payload; }, - setChannelsPage: (state, action: PayloadAction) => { - state.channelsPage = action.payload; - }, - nextChannelsPage: state => { - state.channelsPage += 1; - }, - setChannelsReachedEnd: (state, action: PayloadAction) => { - state.channelsReachedEnd = action.payload; - }, }, }); export const { addChannels, + setChannels, resetChannels, - setChannelsPage, - setChannelsReachedEnd, addChannelSubscription, removeChannelSubscription, setSubscriptions, setLoadingSubscriptions, - nextChannelsPage, } = channelSlice.actions; type ReturnTypeChannel = {channel: ChannelSliceData}; @@ -99,10 +86,6 @@ export const selectChannel = (index: number) => (state: ReturnTypeChannel) => state.channel.channels[index]; export const selectSubscriptions = (state: ReturnTypeChannel) => state.channel.subscriptions; -export const selectChannelsReachedEnd = (state: ReturnTypeChannel) => - state.channel.channelsReachedEnd; -export const selectChannelsPage = (state: ReturnTypeChannel) => - state.channel.channelsPage; export const selectIsLoadingSubscriptions = (state: ReturnTypeChannel) => state.channel.isLoadingSubscriptions;