From 258e5d0b09b5cd4977d89f7e0b85b0f05ea95f1e Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 7 Apr 2021 23:50:00 +0500 Subject: [PATCH] Support for pagination, refresh, empty placeholders --- .../container/postsListContainer.tsx | 77 +++- .../postsList/view/postsListStyles.tsx | 14 + .../tabbedPosts/container/tabbedPosts.tsx | 45 +-- .../tabbedPosts/container/tabbedPostsProps.ts | 12 - .../tabbedPosts/services/tabbedPostsFetch.ts | 76 ++-- .../tabbedPosts/services/tabbedPostsModels.ts | 40 ++ .../services/tabbedPostsReducer.ts | 68 +++- .../tabbedPosts/view/listEmptyView.tsx | 341 ++++++++++++++++++ .../tabbedPosts/view/tabContent.tsx | 92 +++++ .../tabbedPosts/view/tabbedPostsStyles.tsx | 81 +++++ 10 files changed, 739 insertions(+), 107 deletions(-) create mode 100644 src/components/postsList/view/postsListStyles.tsx delete mode 100644 src/components/tabbedPosts/container/tabbedPostsProps.ts create mode 100644 src/components/tabbedPosts/services/tabbedPostsModels.ts create mode 100644 src/components/tabbedPosts/view/listEmptyView.tsx create mode 100644 src/components/tabbedPosts/view/tabContent.tsx create mode 100644 src/components/tabbedPosts/view/tabbedPostsStyles.tsx diff --git a/src/components/postsList/container/postsListContainer.tsx b/src/components/postsList/container/postsListContainer.tsx index 2e7119caf..e847dbce0 100644 --- a/src/components/postsList/container/postsListContainer.tsx +++ b/src/components/postsList/container/postsListContainer.tsx @@ -1,18 +1,28 @@ import React, {forwardRef, memo, useRef, useImperativeHandle, useState, useEffect} from 'react' import PostCard from '../../postCard'; import { get } from 'lodash'; -import { FlatListProps, FlatList } from 'react-native'; +import { FlatListProps, FlatList, RefreshControl, ActivityIndicator, View } from 'react-native'; import { useSelector } from 'react-redux'; +import { ThemeContainer } from '../../../containers'; +import { PostCardPlaceHolder } from '../..'; +import styles from '../view/postsListStyles'; +import { isDarkTheme } from '../../../redux/actions/applicationActions'; interface postsListContainerProps extends FlatListProps { promotedPosts:Array; isFeedScreen:boolean; + onLoadPosts?:(shouldReset:boolean)=>void; + isLoading:boolean; + isRefreshing:boolean; } const postsListContainer = ({ promotedPosts, isFeedScreen, + onLoadPosts, + isRefreshing, + isLoading, ...props }:postsListContainerProps, ref) => { const flatListRef = useRef(null); @@ -65,6 +75,23 @@ const postsListContainer = ({ } } + + + const _renderFooter = () => { + if (isLoading) { + return ( + + + + ); + } + + return null; + }; + + + + const _renderItem = ({ item, index }:{item:any, index:number}) => { const e = []; @@ -109,25 +136,39 @@ const postsListContainer = ({ }; - - return ( - content.permlink} - removeClippedSubviews - onEndReachedThreshold={1} - maxToRenderPerBatch={3} - initialNumToRender={3} - windowSize={5} - extraData={imageHeights} - {...props} - /> + + {({ isDarkTheme }) => ( + content.permlink} + removeClippedSubviews + onEndReachedThreshold={1} + maxToRenderPerBatch={3} + initialNumToRender={3} + windowSize={5} + extraData={imageHeights} + onEndReached={()=>{if(onLoadPosts){onLoadPosts(false)}}} + ListFooterComponent={_renderFooter} + refreshControl={ + {if(onLoadPosts){onLoadPosts(true)}}} + progressBackgroundColor="#357CE6" + tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'} + titleColor="#fff" + colors={['#fff']} + /> + } + {...props} + /> + )} + ) } -export default memo(forwardRef(postsListContainer)); +export default forwardRef(postsListContainer); diff --git a/src/components/postsList/view/postsListStyles.tsx b/src/components/postsList/view/postsListStyles.tsx new file mode 100644 index 000000000..1bf9fd71f --- /dev/null +++ b/src/components/postsList/view/postsListStyles.tsx @@ -0,0 +1,14 @@ +import EStyleSheet from 'react-native-extended-stylesheet'; + +export default EStyleSheet.create({ + flatlistFooter: { + alignContent: 'center', + alignItems: 'center', + marginTop: 10, + marginBottom: 60, + borderColor: '$borderColor', + }, + placeholderWrapper: { + flex: 1, + }, +}) \ No newline at end of file diff --git a/src/components/tabbedPosts/container/tabbedPosts.tsx b/src/components/tabbedPosts/container/tabbedPosts.tsx index 017b54fdf..f549a5d8c 100644 --- a/src/components/tabbedPosts/container/tabbedPosts.tsx +++ b/src/components/tabbedPosts/container/tabbedPosts.tsx @@ -3,10 +3,11 @@ import { useIntl } from 'react-intl'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import { useDispatch, useSelector } from 'react-redux'; import PostsList from '../../postsList'; -import { loadPosts, LoadPostsOptions } from '../services/tabbedPostsFetch'; +import { loadPosts } from '../services/tabbedPostsFetch'; +import { TabbedPostsProps } from '../services/tabbedPostsModels'; import { cacheReducer, initCacheState, setSelectedFilter, PostsCache } from '../services/tabbedPostsReducer'; import { StackedTabBar, TabItem } from '../view/stackedTabBar'; -import { TabbedPostsProps } from './tabbedPostsProps'; +import TabContent from '../view/tabContent'; export const TabbedPosts = ({ @@ -20,13 +21,6 @@ export const TabbedPosts = ({ ...props }:TabbedPostsProps) => { - const intl = useIntl(); - const isLoggedIn = useSelector((state) => state.application.isLoggedIn); - - //redux properties - const isAnalytics = useSelector((state) => state.application.isAnalytics); - const nsfw = useSelector((state) => state.application.nsfw); - const isConnected = useSelector((state) => state.application.isConnected); //initialize state @@ -59,45 +53,22 @@ export const TabbedPosts = ({ //initialize first set of pages const pages = combinedFilters.map((filter)=>( - )) - //side effects - useEffect(() => { - _loadPosts() - }, []) - - - - //load posts implementation - const _loadPosts = (filter?:string) => { - const options = { - passedFilter:filter, - cache, - cacheDispatch, - isLoggedIn, - isAnalytics, - nsfw, - isConnected, - feedUsername, - isFeedScreen, - ...props - } as LoadPostsOptions - loadPosts(options) - } - - //actions const _onFilterSelect = (filter:string) => { cacheDispatch(setSelectedFilter(filter)) if(cache.cachedData[filter].posts.length === 0){ - _loadPosts(filter); + // _loadPosts(filter); } } diff --git a/src/components/tabbedPosts/container/tabbedPostsProps.ts b/src/components/tabbedPosts/container/tabbedPostsProps.ts deleted file mode 100644 index 9e12019f3..000000000 --- a/src/components/tabbedPosts/container/tabbedPostsProps.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface TabbedPostsProps { - filterOptions:string[], - filterOptionsValue:string[], - isFeedScreen:boolean, - feedUsername:string, - initialFilterIndex:number, - feedSubfilterOptions:string[], - feedSubfilterOptionsValue:string[], - getFor:string, - pageType:string, - tag:string, -} \ No newline at end of file diff --git a/src/components/tabbedPosts/services/tabbedPostsFetch.ts b/src/components/tabbedPosts/services/tabbedPostsFetch.ts index 916c6a9ce..189347fef 100644 --- a/src/components/tabbedPosts/services/tabbedPostsFetch.ts +++ b/src/components/tabbedPosts/services/tabbedPostsFetch.ts @@ -1,29 +1,15 @@ -import { Dispatch, ReducerAction } from "react"; import { getAccountPosts, getRankedPosts } from "../../../providers/hive/dhive"; -import { CachedDataEntry, onLoadComplete, PostsCache, setFilterLoading, updateFilterCache } from "./tabbedPostsReducer"; +import { getUpdatedPosts } from "./tabbedPostsReducer"; import Matomo from 'react-native-matomo-sdk'; +import { LoadPostsOptions } from "./tabbedPostsModels"; - -export interface LoadPostsOptions { - cache:PostsCache, - cacheDispatch:Dispatch>, - getFor:string, - isConnected:boolean, - isLoggedIn:boolean, - feedUsername:string, - pageType:string, - tag:string, - nsfw:string, - isAnalytics:boolean, - isLatestPostCheck?:boolean, - refreshing?:boolean, - passedFilter?:string; -} +const POSTS_FETCH_COUNT = 20; export const loadPosts = async ({ - passedFilter, - cache, - cacheDispatch, + filterKey, + prevPosts, + tabMeta, + setTabMeta, isLatestPostCheck = false, getFor, isConnected, @@ -36,33 +22,41 @@ export const loadPosts = async ({ isAnalytics }:LoadPostsOptions) => { - let filter = passedFilter || cache.selectedFilter; + let filter = filterKey; //match filter with api if is friends if(filter === 'friends'){ filter = 'feed'; } - const {isLoading, startPermlink, startAuthor}:CachedDataEntry = cache.cachedData[filter]; + const {isLoading, startPermlink, startAuthor} = tabMeta; if ( isLoading || !isConnected || - (!isLoggedIn && passedFilter === 'feed') || - (!isLoggedIn && passedFilter === 'blog') + (!isLoggedIn && filterKey === 'feed') || + (!isLoggedIn && filterKey === 'blog') ) { return; } if (!isConnected && (refreshing || isLoading)) { - cacheDispatch(onLoadComplete(filter)) + setTabMeta({ + ...tabMeta, + isLoading:false, + isRefreshing:false, + }) return; } - cacheDispatch(setFilterLoading(filter, true)); + setTabMeta({ + ...tabMeta, + isLoading:true, + isRefreshing:refreshing, + }) let options = {} as any; - const limit = isLatestPostCheck ? 5 : 20; + const limit = isLatestPostCheck ? 5 : POSTS_FETCH_COUNT; let func = null; if ( @@ -122,12 +116,30 @@ export const loadPosts = async ({ if(filter === 'feed'){ filter = 'friends' } - cacheDispatch(updateFilterCache(filter, result, refreshing)) - cacheDispatch(onLoadComplete(filter)); + + // cacheDispatch(updateFilterCache(filter, result, refreshing)) + + + setTabMeta({ + ...tabMeta, + isLoading:false, + isRefreshing:false, + }) + + return getUpdatedPosts( + prevPosts, + result, + refreshing, + tabMeta, + setTabMeta + ) } catch (err) { - - cacheDispatch(onLoadComplete(filter)); + setTabMeta({ + ...tabMeta, + isLoading:false, + isRefreshing:false, + }) } // track filter and tag views diff --git a/src/components/tabbedPosts/services/tabbedPostsModels.ts b/src/components/tabbedPosts/services/tabbedPostsModels.ts new file mode 100644 index 000000000..ae4f7ebcc --- /dev/null +++ b/src/components/tabbedPosts/services/tabbedPostsModels.ts @@ -0,0 +1,40 @@ + +export interface TabbedPostsProps { + filterOptions:string[], + filterOptionsValue:string[], + isFeedScreen:boolean, + feedUsername:string, + initialFilterIndex:number, + feedSubfilterOptions:string[], + feedSubfilterOptionsValue:string[], + getFor:string, + pageType:string, + tag:string, +} + +export interface TabMeta { + startPermlink:string, + startAuthor:string, + isLoading:boolean, + isRefreshing:boolean, + isNoPost:boolean, + } + + export interface LoadPostsOptions { + + filterKey:string; + prevPosts:any[]; + tabMeta:TabMeta; + setTabMeta:(meta:TabMeta)=>void, + getFor:string, + isConnected:boolean, + isLoggedIn:boolean, + feedUsername:string, + pageType:string, + tag:string, + nsfw:string, + isAnalytics:boolean, + isLatestPostCheck?:boolean, + refreshing?:boolean, + + } \ No newline at end of file diff --git a/src/components/tabbedPosts/services/tabbedPostsReducer.ts b/src/components/tabbedPosts/services/tabbedPostsReducer.ts index 6b43a0c70..3138ccd86 100644 --- a/src/components/tabbedPosts/services/tabbedPostsReducer.ts +++ b/src/components/tabbedPosts/services/tabbedPostsReducer.ts @@ -1,5 +1,7 @@ import { TabItem } from "../view/stackedTabBar"; import unionBy from 'lodash/unionBy'; +import { TabMeta } from "./tabbedPostsModels"; +import TabBarTop from "react-navigation-tabs/lib/typescript/src/views/MaterialTopTabBar"; export const CacheActions = { SET_FILTER_LOADING:'SET_FILTER_LOADING', @@ -53,14 +55,64 @@ export const onLoadComplete = (filter:string) => ({ type:CacheActions.ON_LOAD_COMPLETE }) -export const updateFilterCache = (filter:string, posts:any[], refreshing:boolean) => ({ - payload: { - filter, - posts, - shouldReset: refreshing, - }, - type: CacheActions.UPDATE_FILTER_CACHE, -}) + +export const getUpdatedPosts = (prevPosts:any[], nextPosts:any[], shouldReset:boolean, tabMeta:TabMeta, setTabMeta:(meta:TabMeta)=>void) => { + //return state as is if component is unmounter + let _posts = nextPosts; + + + // const isFeedScreen = state.isFeedScreen + // const cachedEntry:CachedDataEntry = state.cachedData[filter]; + // if (!cachedEntry) { + // throw new Error('No cached entry available'); + // } + + if(nextPosts.length === 0){ + setTabMeta({ + ...tabMeta, + isNoPost:true + }); + return prevPosts; + } + + + const refreshing = tabMeta.isRefreshing; + + + if (prevPosts.length > 0 && !shouldReset) { + if (refreshing) { + _posts = unionBy(_posts, prevPosts, 'permlink'); + } else { + _posts = unionBy(prevPosts, _posts, 'permlink'); + } + } + //cache latest posts for main tab for returning user + // else if (isFeedScreen) { + // //schedule refetch of new posts by checking time of current post + // _scheduleLatestPostsCheck(nextPosts[0]); + + // if (filter == (get(currentAccount, 'name', null) == null ? 'hot' : 'friends')) { + // _setInitPosts(nextPosts); + // } + // } + + //update stat + + setTabMeta({ + ...tabMeta, + startAuthor:_posts[_posts.length - 1] && _posts[_posts.length - 1].author, + startPermlink: _posts[_posts.length - 1] && _posts[_posts.length - 1].permlink, + }) + + + //dispatch to redux + // if ( + // filter === (state.selectedFilter !== 'feed' ? state.selectedFilter : state.currentSubFilter) + // ) { + // _setFeedPosts(_posts); + // } + return _posts +} export const initCacheState = (filters:TabItem[], selectedFilter:string, isFeedScreen:boolean) => { diff --git a/src/components/tabbedPosts/view/listEmptyView.tsx b/src/components/tabbedPosts/view/listEmptyView.tsx new file mode 100644 index 000000000..a08c7ccdf --- /dev/null +++ b/src/components/tabbedPosts/view/listEmptyView.tsx @@ -0,0 +1,341 @@ +import React, {useEffect, useState} from 'react'; +import { useIntl } from 'react-intl'; +import { get } from 'lodash'; +import { Text, View, FlatList } from 'react-native'; +import { NoPost, PostCardPlaceHolder, UserListItem } from '../..'; +import globalStyles from '../../../globalStyles'; +import { CommunityListItem } from '../../basicUIElements'; +import styles from './tabbedPostsStyles'; +import { default as ROUTES } from '../../../constants/routeNames'; +import { withNavigation } from 'react-navigation'; +import {useSelector, useDispatch } from 'react-redux'; +import { fetchCommunities, leaveCommunity, subscribeCommunity } from '../../../redux/actions/communitiesAction'; +import { fetchLeaderboard, followUser, unfollowUser } from '../../../redux/actions/userAction'; +import { getCommunity } from '../../../providers/hive/dhive'; + +interface TabEmptyViewProps { + filterKey:string, + isNoPost:boolean, + navigation:any, +} + +const TabEmptyView = ({ + filterKey, + isNoPost, + navigation +}: TabEmptyViewProps) => { + + const intl = useIntl(); + const dispatch = useDispatch(); + //redux properties + const isLoggedIn = useSelector((state) => state.application.isLoggedIn); + const subscribingCommunities = useSelector( + (state) => state.communities.subscribingCommunitiesInFeedScreen, + ); + const [recommendedCommunities, setRecommendedCommunities] = useState([]); + const [recommendedUsers, setRecommendedUsers] = useState([]); + const followingUsers = useSelector((state) => state.user.followingUsersInFeedScreen); + const currentAccount = useSelector((state) => state.account.currentAccount); + const pinCode = useSelector((state) => state.application.pin); + + const leaderboard = useSelector((state) => state.user.leaderboard); + const communities = useSelector((state) => state.communities.communities); + + //hooks + + useEffect(()=>{ + if (isNoPost) { + if (filterKey === 'friends') { + if (recommendedUsers.length === 0) { + _getRecommendedUsers(); + } + } else if(filterKey === 'communities') { + if (recommendedCommunities.length === 0) { + _getRecommendedCommunities(); + } + } + } + }, [isNoPost]) + + + useEffect(() => { + if (!leaderboard.loading) { + if (!leaderboard.error && leaderboard.data.length > 0) { + _formatRecommendedUsers(leaderboard.data); + } + } + }, [leaderboard]); + + useEffect(() => { + if (!communities.loading) { + if (!communities.error && communities.data?.length > 0) { + _formatRecommendedCommunities(communities.data); + } + } + }, [communities]); + + + useEffect(() => { + const recommendeds = [...recommendedCommunities]; + + Object.keys(subscribingCommunities).map((communityId) => { + if (!subscribingCommunities[communityId].loading) { + if (!subscribingCommunities[communityId].error) { + if (subscribingCommunities[communityId].isSubscribed) { + recommendeds.forEach((item) => { + if (item.name === communityId) { + item.isSubscribed = true; + } + }); + } else { + recommendeds.forEach((item) => { + if (item.name === communityId) { + item.isSubscribed = false; + } + }); + } + } + } + }); + + setRecommendedCommunities(recommendeds); + }, [subscribingCommunities]); + + + + useEffect(() => { + const recommendeds = [...recommendedUsers]; + + Object.keys(followingUsers).map((following) => { + if (!followingUsers[following].loading) { + if (!followingUsers[following].error) { + if (followingUsers[following].isFollowing) { + recommendeds.forEach((item) => { + if (item._id === following) { + item.isFollowing = true; + } + }); + } else { + recommendeds.forEach((item) => { + if (item._id === following) { + item.isFollowing = false; + } + }); + } + } + } + }); + + setRecommendedUsers(recommendeds); + }, [followingUsers]); + + + //fetching + const _getRecommendedUsers = () => dispatch(fetchLeaderboard()); + const _getRecommendedCommunities = () => dispatch(fetchCommunities('', 10)); + + //formating + const _formatRecommendedCommunities = async (communitiesArray) => { + try { + const ecency = await getCommunity('hive-125125'); + + const recommendeds = [ecency, ...communitiesArray]; + recommendeds.forEach((item) => Object.assign(item, { isSubscribed: false })); + + setRecommendedCommunities(recommendeds); + } catch (err) { + console.log(err, '_getRecommendedUsers Error'); + } + }; + + const _formatRecommendedUsers = (usersArray) => { + const recommendeds = usersArray.slice(0, 10); + + recommendeds.unshift({ _id: 'good-karma' }); + recommendeds.unshift({ _id: 'ecency' }); + + recommendeds.forEach((item) => Object.assign(item, { isFollowing: false })); + + setRecommendedUsers(recommendeds); + }; + + //actions related routines + const _handleSubscribeCommunityButtonPress = (data) => { + let subscribeAction; + let successToastText = ''; + let failToastText = ''; + + if (!data.isSubscribed) { + subscribeAction = subscribeCommunity; + + successToastText = intl.formatMessage({ + id: 'alert.success_subscribe', + }); + failToastText = intl.formatMessage({ + id: 'alert.fail_subscribe', + }); + } else { + subscribeAction = leaveCommunity; + + successToastText = intl.formatMessage({ + id: 'alert.success_leave', + }); + failToastText = intl.formatMessage({ + id: 'alert.fail_leave', + }); + } + + dispatch( + subscribeAction(currentAccount, pinCode, data, successToastText, failToastText, 'feedScreen'), + ); + }; + + + const _handleFollowUserButtonPress = (data, isFollowing) => { + let followAction; + let successToastText = ''; + let failToastText = ''; + + if (!isFollowing) { + followAction = followUser; + + successToastText = intl.formatMessage({ + id: 'alert.success_follow', + }); + failToastText = intl.formatMessage({ + id: 'alert.fail_follow', + }); + } else { + followAction = unfollowUser; + + successToastText = intl.formatMessage({ + id: 'alert.success_unfollow', + }); + failToastText = intl.formatMessage({ + id: 'alert.fail_unfollow', + }); + } + + data.follower = get(currentAccount, 'name', ''); + + dispatch(followAction(currentAccount, pinCode, data, successToastText, failToastText)); + }; + + + const _handleOnPressLogin = () => { + navigation.navigate(ROUTES.SCREENS.LOGIN); + }; + + +//render related operations + if ((filterKey === 'feed' || filterKey === 'blog') && !isLoggedIn) { + return ( + + ); + } + + if (isNoPost) { + if (filterKey === 'friends') { + + return ( + <> + + {intl.formatMessage({ id: 'profile.follow_people' })} + + `${item._id || item.id}${index}`} + renderItem={({ item, index }) => ( + + navigation.navigate({ + routeName: ROUTES.SCREENS.PROFILE, + params: { + username, + }, + key: username, + }) + } + /> + )} + /> + + ); + } else if (filterKey === 'communities') { + return ( + <> + + {intl.formatMessage({ id: 'profile.follow_communities' })} + + `${item.id || item.title}${index}`} + renderItem={({ item, index }) => ( + + navigation.navigate({ + routeName: ROUTES.SCREENS.COMMUNITY, + params: { + tag: name, + }, + }) + } + handleSubscribeButtonPress={_handleSubscribeCommunityButtonPress} + isSubscribed={item.isSubscribed} + isLoadingRightAction={ + subscribingCommunities.hasOwnProperty(item.name) && + subscribingCommunities[item.name].loading + } + isLoggedIn={isLoggedIn} + /> + )} + /> + + ); + } else { + return {intl.formatMessage({ id: 'profile.havent_posted' })}; + } + } + + return ( + + + + ); +}; + +export default withNavigation(TabEmptyView); + diff --git a/src/components/tabbedPosts/view/tabContent.tsx b/src/components/tabbedPosts/view/tabContent.tsx new file mode 100644 index 000000000..a02a6d23b --- /dev/null +++ b/src/components/tabbedPosts/view/tabContent.tsx @@ -0,0 +1,92 @@ +import React, {useState, useEffect} from 'react'; +import PostsList from '../../postsList'; +import { loadPosts } from '../services/tabbedPostsFetch'; +import { LoadPostsOptions, TabMeta } from '../services/tabbedPostsModels'; +import {useSelector } from 'react-redux'; +import TabEmptyView from './listEmptyView'; + +interface TabContentProps { + filterKey:string, + tabLabel:string, + isFeedScreen:boolean, + promotedPosts:any[], + getFor:string, + pageType:string, + feedUsername:string, + tag:string, +} + +const TabContent = ({ + filterKey, + isFeedScreen, + promotedPosts, + ...props +}: TabContentProps) => { + + //redux properties + const isLoggedIn = useSelector((state) => state.application.isLoggedIn); + const isAnalytics = useSelector((state) => state.application.isAnalytics); + const nsfw = useSelector((state) => state.application.nsfw); + const isConnected = useSelector((state) => state.application.isConnected); + + //state + const [posts, setPosts] = useState([]); + const [tabMeta, setTabMeta] = useState({ + startAuthor:'', + startPermlink:'', + isLoading:false, + isRefreshing:false, + } as TabMeta) + + + useEffect(()=>{ + _loadPosts(); + },[]) + + //load posts implementation + const _loadPosts = async (shouldReset:boolean = false) => { + const options = { + filterKey, + prevPosts:posts, + tabMeta, + setTabMeta, + isLoggedIn, + isAnalytics, + nsfw, + isConnected, + isFeedScreen, + refreshing:shouldReset, + ...props + } as LoadPostsOptions + + const updatedPosts = await loadPosts(options) + if(updatedPosts){ + setPosts(updatedPosts); + } + + } + + + const _renderEmptyContent = () => { + return + } + + + return ( + + + + ); +}; + +export default TabContent; + + diff --git a/src/components/tabbedPosts/view/tabbedPostsStyles.tsx b/src/components/tabbedPosts/view/tabbedPostsStyles.tsx new file mode 100644 index 000000000..f2a21e166 --- /dev/null +++ b/src/components/tabbedPosts/view/tabbedPostsStyles.tsx @@ -0,0 +1,81 @@ +import EStyleSheet from 'react-native-extended-stylesheet'; + +export default EStyleSheet.create({ + container: { + backgroundColor: '$primaryLightBackground', + }, + placeholder: { + backgroundColor: '$primaryBackgroundColor', + padding: 20, + borderStyle: 'solid', + borderWidth: 1, + borderTopWidth: 1, + borderColor: '#e2e5e8', + borderRadius: 5, + marginRight: 0, + marginLeft: 0, + marginTop: 10, + }, + tabs: { + position: 'absolute', + top: '$deviceWidth / 30', + alignItems: 'center', + }, + flatlistFooter: { + alignContent: 'center', + alignItems: 'center', + marginTop: 10, + marginBottom: 60, + borderColor: '$borderColor', + }, + noImage: { + width: 193, + height: 189, + }, + placeholderWrapper: { + flex: 1, + }, + noPostTitle: { + textAlign: 'center', + marginVertical: 16, + color: '$primaryBlack', + }, + popupContainer: { + position: 'absolute', + top: 80, + left: 0, + right: 0, + alignItems: 'center', + }, + popupContentContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '$primaryBlue', + paddingHorizontal: 0, + paddingVertical: 2, + borderRadius: 32, + }, + popupText: { + fontWeight: '500', + color: '$white', + marginLeft: 6, + }, + closeIcon: { + color: '$white', + margin: 0, + padding: 6, + }, + arrowUpIcon: { + color: '$white', + margin: 0, + marginHorizontal: 4, + }, + popupImage: { + height: 24, + width: 24, + borderRadius: 12, + borderWidth: 2, + marginLeft: -8, + borderColor: '$primaryBlue', + }, +});