From f912ebf5eb9267c87b9afe320bfe0725fe9aa838 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 20 Aug 2023 20:30:30 +0500 Subject: [PATCH 01/40] added waves screen entry in bottom tab bar --- .../bottomTabBar/view/bottomTabBarView.tsx | 2 + src/constants/routeNames.js | 2 + src/navigation/botomTabNavigator.tsx | 16 ++++- src/screens/waves/index.ts | 3 + src/screens/waves/screen/wavesScreen.tsx | 60 +++++++++++++++++++ 5 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 src/screens/waves/index.ts create mode 100644 src/screens/waves/screen/wavesScreen.tsx diff --git a/src/components/bottomTabBar/view/bottomTabBarView.tsx b/src/components/bottomTabBar/view/bottomTabBarView.tsx index 683a9591ba..b12c640d79 100644 --- a/src/components/bottomTabBar/view/bottomTabBarView.tsx +++ b/src/components/bottomTabBar/view/bottomTabBarView.tsx @@ -63,9 +63,11 @@ const BottomTabBarView = ({ _tabBarIcon = ; break; case ROUTES.TABBAR.POST_BUTTON: + case ROUTES.TABBAR.WAVES: _iconProps.iconType = 'MaterialCommunityIcons'; _tabBarIcon = ; break; + } return ( diff --git a/src/constants/routeNames.js b/src/constants/routeNames.js index 68db7dad1a..c80fc340d3 100644 --- a/src/constants/routeNames.js +++ b/src/constants/routeNames.js @@ -39,6 +39,7 @@ const ROUTES = { WELCOME: `Welcome${SCREEN_SUFFIX}`, BACKUP_KEYS: `BackupKeys${SCREEN_SUFFIX}`, TRADE: `Trade${SCREEN_SUFFIX}`, + }, MODALS: { ASSETS_SELECT: `AssetsSelect${MODAL_SUFFIX}`, @@ -52,6 +53,7 @@ const ROUTES = { WALLET: `Wallet${TABBAR_SUFFIX}`, POST_BUTTON: `PostButton${TABBAR_SUFFIX}`, PROFILE: `Profile${TABBAR_SUFFIX}`, + WAVES: `Waves${TABBAR_SUFFIX}`, }, STACK: { MAIN: `Main${STACK_SUFFIX}`, diff --git a/src/navigation/botomTabNavigator.tsx b/src/navigation/botomTabNavigator.tsx index 548c9e29d4..897068269d 100644 --- a/src/navigation/botomTabNavigator.tsx +++ b/src/navigation/botomTabNavigator.tsx @@ -4,6 +4,7 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import ROUTES from '../constants/routeNames'; import { BottomTabBar } from '../components'; import { Feed, Notification, Profile, Wallet } from '../screens'; +import Waves from '../screens/waves'; const Tab = createBottomTabNavigator(); @@ -12,6 +13,7 @@ export const BottomTabNavigator = () => { } backBehavior="initialRoute" + initialRouteName={ROUTES.TABBAR.WAVES} screenOptions={{ headerShown: false, tabBarShowLabel: false, @@ -28,10 +30,10 @@ export const BottomTabNavigator = () => { /> @@ -51,6 +53,14 @@ export const BottomTabNavigator = () => { }} /> + + { + const renderItem = ({ item }: { item: Item }) => ( + + {item.title} + + ); + + return ( + +
+ + Header Title + + item.id.toString()} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#fff', + }, + header: { + padding: 20, + borderBottomWidth: 1, + borderBottomColor: '#ccc', + alignItems: 'center', + }, + headerText: { + fontSize: 20, + fontWeight: 'bold', + }, + item: { + padding: 20, + borderBottomWidth: 1, + borderBottomColor: '#ccc', + }, +}); + +export default WavesScreen; \ No newline at end of file From c026970dba750ffcb7317e36ae9adbd57a76bdeb Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 20 Aug 2023 23:17:03 +0500 Subject: [PATCH 02/40] rendering first set of ecency waves --- src/screens/waves/screen/wavesScreen.tsx | 89 +++++++------------ .../waves/styles/wavesScreen.styles.ts | 11 +++ 2 files changed, 44 insertions(+), 56 deletions(-) create mode 100644 src/screens/waves/styles/wavesScreen.styles.ts diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index e279e64bcc..8d69ec7ae8 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -1,60 +1,37 @@ -import React from 'react'; -import { View, Text, FlatList, StyleSheet } from 'react-native'; -import { Header } from '../../../components'; - -interface Item { - id: number; - title: string; -} - -const DATA: Item[] = [ - { id: 1, title: 'Item 1' }, - { id: 2, title: 'Item 2' }, - { id: 3, title: 'Item 3' }, -]; - -const WavesScreen: React.FC = () => { - const renderItem = ({ item }: { item: Item }) => ( - - {item.title} - - ); - - return ( - -
- - Header Title - - item.id.toString()} - /> - - ); +import React, { useEffect, useState } from 'react'; +import { View } from 'react-native'; +import { Comments, Header } from '../../../components'; +import { getComments } from '../../../providers/hive/dhive'; +import styles from '../styles/wavesScreen.styles'; + + +const WavesScreen = () => { + + + const [waves, setWaves] = useState([]); + + useEffect(() => { + _fetchWaves() + }, []) + + const _fetchWaves = async () => { + const data = await getComments('ecency.waves', 'waves-2023-08-19'); + setWaves(data) + } + + return ( + +
+ + + + + + + ); }; -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - }, - header: { - padding: 20, - borderBottomWidth: 1, - borderBottomColor: '#ccc', - alignItems: 'center', - }, - headerText: { - fontSize: 20, - fontWeight: 'bold', - }, - item: { - padding: 20, - borderBottomWidth: 1, - borderBottomColor: '#ccc', - }, -}); export default WavesScreen; \ No newline at end of file diff --git a/src/screens/waves/styles/wavesScreen.styles.ts b/src/screens/waves/styles/wavesScreen.styles.ts new file mode 100644 index 0000000000..d6d415ea75 --- /dev/null +++ b/src/screens/waves/styles/wavesScreen.styles.ts @@ -0,0 +1,11 @@ +import { ViewStyle } from 'react-native'; +import EStyleSheet from 'react-native-extended-stylesheet'; + +export default EStyleSheet.create({ + container: { + flex:1, + backgroundColor: '$primaryBackgroundColor', + + } as ViewStyle, + +}) \ No newline at end of file From db64f30ce38e75ba28389399f274d7eb19fb83d8 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 21 Aug 2023 18:26:59 +0500 Subject: [PATCH 03/40] enabled pagination with react query --- src/providers/queries/index.ts | 3 +- src/providers/queries/queryKeys.ts | 3 + src/providers/queries/wavesQueries/index.ts | 3 + .../queries/wavesQueries/wavesQueries.ts | 175 ++++++++++++++++++ src/screens/waves/screen/wavesScreen.tsx | 39 ++-- 5 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 src/providers/queries/wavesQueries/index.ts create mode 100644 src/providers/queries/wavesQueries/wavesQueries.ts diff --git a/src/providers/queries/index.ts b/src/providers/queries/index.ts index a27f8021dc..2e072a0740 100644 --- a/src/providers/queries/index.ts +++ b/src/providers/queries/index.ts @@ -29,4 +29,5 @@ export * from './editorQueries'; export * from './pointQueries'; export * from './postQueries'; export * from './walletQueries'; -export * from './leaderboardQueries'; \ No newline at end of file +export * from './leaderboardQueries'; +export * from './wavesQueries'; \ No newline at end of file diff --git a/src/providers/queries/queryKeys.ts b/src/providers/queries/queryKeys.ts index 17c6a97b7f..ed8a4c9d30 100644 --- a/src/providers/queries/queryKeys.ts +++ b/src/providers/queries/queryKeys.ts @@ -27,6 +27,9 @@ const QUERIES = { LEADERBOARD: { GET: 'QUERY_GET_LEADERBOARD', }, + WAVES: { + GET: 'QUERY_GET_WAVES', + } }; export default QUERIES; diff --git a/src/providers/queries/wavesQueries/index.ts b/src/providers/queries/wavesQueries/index.ts new file mode 100644 index 0000000000..1690c4dfe0 --- /dev/null +++ b/src/providers/queries/wavesQueries/index.ts @@ -0,0 +1,3 @@ +import * as wavesQueries from './wavesQueries'; + +export { wavesQueries }; diff --git a/src/providers/queries/wavesQueries/wavesQueries.ts b/src/providers/queries/wavesQueries/wavesQueries.ts new file mode 100644 index 0000000000..5bc56066c7 --- /dev/null +++ b/src/providers/queries/wavesQueries/wavesQueries.ts @@ -0,0 +1,175 @@ + + import { + useQueries, + } from '@tanstack/react-query'; + import { useEffect, useState } from 'react'; + + import { unionBy } from 'lodash'; + import { getComments } from '../../hive/dhive'; + +import { getAccountPosts } from '../../hive/dhive'; +import QUERIES from '../queryKeys'; + + + + export const useWavesQuery = (host: string) => { + const [isRefreshing, setIsRefreshing] = useState(false); + const [activePermlinks, setActivePermlinks] = useState([]); + const [permlinksBucket, setPermlinksBucket] = useState([]); + + + useEffect(() => { + _fetchPermlinks() + }, []) + + + useEffect(() => { + if(!!permlinksBucket.length){ + activePermlinks.push(permlinksBucket[activePermlinks.length]); + setActivePermlinks([...activePermlinks]); + } + },[permlinksBucket]) + + + const _fetchPermlinks = async (startPermlink?:string) => { + const query: any = { + account: host, + start_author: '',//,'',//refresh ? '' : lastAuthor, + start_permlink: startPermlink || '',//refresh ? '' : lastPermlink, + limit: 10, + observer: '', + sort: 'posts', + }; + + const result = await getAccountPosts(query); + + const _permlinksBucket = result.map(post => post.permlink); + console.log('permlinks bucket filled', _permlinksBucket); + setPermlinksBucket(_permlinksBucket); + + } + + const _fetchWaves = async (pagePermlink: string) => { + console.log('fetching waves from:', host, pagePermlink); + const response = await getComments(host, pagePermlink); + response.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); + console.log('new waves fetched', response); + return response || []; + }; + + // const _getNextPagePermlink = (lastPage: any[]) => { + // const lastId = lastPage && lastPage.length ? lastPage.lastItem.id : undefined; + // console.log('extracting next page parameter', lastId); + // return lastId; + // }; + + // query initialization + const wavesQueries = useQueries({ + queries: activePermlinks.map((pagePermlink) => ({ + queryKey: [QUERIES.WAVES.GET, host, pagePermlink], + queryFn: () => _fetchWaves(pagePermlink), + initialData: [], + })), + }); + + const _refresh = async () => { + setIsRefreshing(true); + setActivePermlinks([permlinksBucket[0]]); + await wavesQueries[0].refetch(); + setIsRefreshing(false); + }; + + const _fetchNextPage = () => { + const lastPage = wavesQueries.lastItem; + + if (!lastPage || lastPage.isFetching) { + return; + } + + const _nextPagePermlink = permlinksBucket[activePermlinks.length]; + + //TODO: find a way to proactively refill active permlinks here. + + if (_nextPagePermlink && !activePermlinks.includes(_nextPagePermlink)) { + activePermlinks.push(_nextPagePermlink); + setActivePermlinks([...activePermlinks]); + } + }; + + const _dataArrs = wavesQueries.map((query) => query.data); + + return { + data: unionBy(..._dataArrs, 'url'), + isRefreshing, + isLoading: wavesQueries.lastItem?.isLoading || wavesQueries.lastItem?.isFetching, + fetchNextPage: _fetchNextPage, + refresh: _refresh, + }; + }; + + + + + // export const useNotificationReadMutation = () => { + // const intl = useIntl(); + // const dispatch = useAppDispatch(); + // const queryClient = useQueryClient(); + + // const currentAccount = useAppSelector((state) => state.account.currentAccount); + // const pinCode = useAppSelector((state) => state.application.pin); + + // // id is options, if no id is provided program marks all notifications as read; + // const _mutationFn = async (id?: string) => { + // try { + // const response = await markNotifications(id); + // console.log('Ecency notifications marked as Read', response); + // if (!id) { + // await markHiveNotifications(currentAccount, pinCode); + // console.log('Hive notifications marked as Read'); + // } + + // return response.unread || 0; + // } catch (err) { + // bugsnapInstance.notify(err); + // } + // }; + + // const _options: UseMutationOptions = { + // onMutate: async (notificationId) => { + // // TODO: find a way to optimise mutations by avoiding too many loops + // console.log('on mutate data', notificationId); + + // // update query data + // const queriesData: [QueryKey, any[] | undefined][] = queryClient.getQueriesData([ + // QUERIES.NOTIFICATIONS.GET, + // ]); + // console.log('query data', queriesData); + + // queriesData.forEach(([queryKey, data]) => { + // if (data) { + // console.log('mutating data', queryKey); + // const _mutatedData = data.map((item) => ({ + // ...item, + // read: !notificationId || notificationId === item.id ? 1 : item.read, + // })); + // queryClient.setQueryData(queryKey, _mutatedData); + // } + // }); + // }, + + // onSuccess: async (unreadCount, notificationId) => { + // console.log('on success data', unreadCount); + + // dispatch(updateUnreadActivityCount(unreadCount)); + // if (!notificationId) { + // queryClient.invalidateQueries([QUERIES.NOTIFICATIONS.GET]); + // } + // }, + // onError: () => { + // dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); + // }, + // }; + + // return useMutation(_mutationFn, _options); + // }; + \ No newline at end of file diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index 8d69ec7ae8..c6bc915c72 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -1,31 +1,46 @@ -import React, { useEffect, useState } from 'react'; -import { View } from 'react-native'; +import React from 'react'; +import { RefreshControl, View } from 'react-native'; import { Comments, Header } from '../../../components'; -import { getComments } from '../../../providers/hive/dhive'; import styles from '../styles/wavesScreen.styles'; +import { useWavesQuery } from '../../../providers/queries/wavesQueries/wavesQueries'; const WavesScreen = () => { + const wavesQuery = useWavesQuery('ecency.waves'); - const [waves, setWaves] = useState([]); + const _fetchData = ({refresh}:{refresh?:boolean}) => { - useEffect(() => { - _fetchWaves() - }, []) - - const _fetchWaves = async () => { - const data = await getComments('ecency.waves', 'waves-2023-08-19'); - setWaves(data) + if(refresh){ + wavesQuery.refresh(); + } else { + wavesQuery.fetchNextPage(); + } + } + + const _data = wavesQuery.data.slice(); return (
{}, + // ListEmptyComponent: _renderListEmpty, + // ListFooterComponent: _renderListFooter, + onEndReachedThreshold: 1, + refreshControl: ( + _fetchData({ refresh: true })} + /> + ), + }} /> From 99624bd11f87765cf7e53055343bda6b850c36a2 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 21 Aug 2023 19:16:03 +0500 Subject: [PATCH 04/40] refilling permlinks bucket --- .../queries/wavesQueries/wavesQueries.ts | 33 +++++++++++-------- src/screens/waves/screen/wavesScreen.tsx | 7 ++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/providers/queries/wavesQueries/wavesQueries.ts b/src/providers/queries/wavesQueries/wavesQueries.ts index 5bc56066c7..74bb62d7ac 100644 --- a/src/providers/queries/wavesQueries/wavesQueries.ts +++ b/src/providers/queries/wavesQueries/wavesQueries.ts @@ -9,6 +9,7 @@ import { getAccountPosts } from '../../hive/dhive'; import QUERIES from '../queryKeys'; +import { delay } from '../../../utils/editor'; @@ -31,22 +32,29 @@ import QUERIES from '../queryKeys'; },[permlinksBucket]) - const _fetchPermlinks = async (startPermlink?:string) => { + const _fetchPermlinks = async (startPermlink = '', refresh = false) => { const query: any = { account: host, - start_author: '',//,'',//refresh ? '' : lastAuthor, - start_permlink: startPermlink || '',//refresh ? '' : lastPermlink, - limit: 10, + start_author: !!startPermlink ? host : '', + start_permlink: startPermlink, + limit: 2, observer: '', sort: 'posts', }; const result = await getAccountPosts(query); - const _permlinksBucket = result.map(post => post.permlink); - console.log('permlinks bucket filled', _permlinksBucket); + const _fetchedPermlinks = result.map(post => post.permlink); + console.log('permlinks fetched', _fetchedPermlinks); + + const _permlinksBucket = refresh ? _fetchedPermlinks : [...permlinksBucket, ..._fetchedPermlinks]; setPermlinksBucket(_permlinksBucket); - + + //precautionary delay of 200ms to let state update before concluding promise, + //it is effective for waves refresh routine. + if(refresh){ + await delay(200) + } } const _fetchWaves = async (pagePermlink: string) => { @@ -57,11 +65,6 @@ import QUERIES from '../queryKeys'; return response || []; }; - // const _getNextPagePermlink = (lastPage: any[]) => { - // const lastId = lastPage && lastPage.length ? lastPage.lastItem.id : undefined; - // console.log('extracting next page parameter', lastId); - // return lastId; - // }; // query initialization const wavesQueries = useQueries({ @@ -74,7 +77,9 @@ import QUERIES from '../queryKeys'; const _refresh = async () => { setIsRefreshing(true); - setActivePermlinks([permlinksBucket[0]]); + setPermlinksBucket([]); + setActivePermlinks([]); + await _fetchPermlinks('', true); await wavesQueries[0].refetch(); setIsRefreshing(false); }; @@ -93,6 +98,8 @@ import QUERIES from '../queryKeys'; if (_nextPagePermlink && !activePermlinks.includes(_nextPagePermlink)) { activePermlinks.push(_nextPagePermlink); setActivePermlinks([...activePermlinks]); + } else { + _fetchPermlinks(permlinksBucket.lastItem) } }; diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index c6bc915c72..4fda7e8129 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { RefreshControl, View } from 'react-native'; +import { ActivityIndicator, RefreshControl, View } from 'react-native'; import { Comments, Header } from '../../../components'; import styles from '../styles/wavesScreen.styles'; import { useWavesQuery } from '../../../providers/queries/wavesQueries/wavesQueries'; @@ -21,6 +21,9 @@ const WavesScreen = () => { const _data = wavesQuery.data.slice(); + + const _renderListFooter = () => wavesQuery.isLoading ? : ; + return (
@@ -32,7 +35,7 @@ const WavesScreen = () => { onEndReached: _fetchData, onScroll: () => {}, // ListEmptyComponent: _renderListEmpty, - // ListFooterComponent: _renderListFooter, + ListFooterComponent: _renderListFooter, onEndReachedThreshold: 1, refreshControl: ( Date: Mon, 21 Aug 2023 19:52:37 +0500 Subject: [PATCH 05/40] better handling initial loading indicator --- .../queries/wavesQueries/wavesQueries.ts | 52 +++++++++++-------- src/screens/waves/screen/wavesScreen.tsx | 11 ++-- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/providers/queries/wavesQueries/wavesQueries.ts b/src/providers/queries/wavesQueries/wavesQueries.ts index 74bb62d7ac..c36b1ce1bf 100644 --- a/src/providers/queries/wavesQueries/wavesQueries.ts +++ b/src/providers/queries/wavesQueries/wavesQueries.ts @@ -15,6 +15,7 @@ import { delay } from '../../../utils/editor'; export const useWavesQuery = (host: string) => { const [isRefreshing, setIsRefreshing] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [activePermlinks, setActivePermlinks] = useState([]); const [permlinksBucket, setPermlinksBucket] = useState([]); @@ -33,28 +34,35 @@ import { delay } from '../../../utils/editor'; const _fetchPermlinks = async (startPermlink = '', refresh = false) => { - const query: any = { - account: host, - start_author: !!startPermlink ? host : '', - start_permlink: startPermlink, - limit: 2, - observer: '', - sort: 'posts', - }; - - const result = await getAccountPosts(query); - - const _fetchedPermlinks = result.map(post => post.permlink); - console.log('permlinks fetched', _fetchedPermlinks); - - const _permlinksBucket = refresh ? _fetchedPermlinks : [...permlinksBucket, ..._fetchedPermlinks]; - setPermlinksBucket(_permlinksBucket); + setIsLoading(true); + try{ + const query: any = { + account: host, + start_author: !!startPermlink ? host : '', + start_permlink: startPermlink, + limit: 10, + observer: '', + sort: 'posts', + }; + + const result = await getAccountPosts(query); + + const _fetchedPermlinks = result.map(post => post.permlink); + console.log('permlinks fetched', _fetchedPermlinks); + + const _permlinksBucket = refresh ? _fetchedPermlinks : [...permlinksBucket, ..._fetchedPermlinks]; + setPermlinksBucket(_permlinksBucket); + + if(refresh){ + //precautionary delay of 200ms to let state update before concluding promise, + //it is effective for waves refresh routine. + await delay(200) + } + } catch(err){ + console.warn("failed to fetch waves permlinks"); + } - //precautionary delay of 200ms to let state update before concluding promise, - //it is effective for waves refresh routine. - if(refresh){ - await delay(200) - } + setIsLoading(false) } const _fetchWaves = async (pagePermlink: string) => { @@ -108,7 +116,7 @@ import { delay } from '../../../utils/editor'; return { data: unionBy(..._dataArrs, 'url'), isRefreshing, - isLoading: wavesQueries.lastItem?.isLoading || wavesQueries.lastItem?.isFetching, + isLoading: isLoading || wavesQueries.lastItem?.isLoading || wavesQueries.lastItem?.isFetching, fetchNextPage: _fetchNextPage, refresh: _refresh, }; diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index 4fda7e8129..aca77eb545 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ActivityIndicator, RefreshControl, View } from 'react-native'; -import { Comments, Header } from '../../../components'; +import { Comments, EmptyScreen, Header } from '../../../components'; import styles from '../styles/wavesScreen.styles'; import { useWavesQuery } from '../../../providers/queries/wavesQueries/wavesQueries'; @@ -10,19 +10,20 @@ const WavesScreen = () => { const wavesQuery = useWavesQuery('ecency.waves'); const _fetchData = ({refresh}:{refresh?:boolean}) => { - if(refresh){ wavesQuery.refresh(); } else { wavesQuery.fetchNextPage(); } - } const _data = wavesQuery.data.slice(); - const _renderListFooter = () => wavesQuery.isLoading ? : ; + const _renderListFooter = () => wavesQuery.isLoading && !wavesQuery.isRefreshing + ? : ; + const _renderListEmpty = () => wavesQuery.isRefreshing || wavesQuery.isLoading + ? : ; return ( @@ -34,7 +35,7 @@ const WavesScreen = () => { flatListProps={{ onEndReached: _fetchData, onScroll: () => {}, - // ListEmptyComponent: _renderListEmpty, + ListEmptyComponent: _renderListEmpty, ListFooterComponent: _renderListFooter, onEndReachedThreshold: 1, refreshControl: ( From c0c8e4fa35056cdf2491a41165ab957f181b44ee Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 21 Aug 2023 20:09:27 +0500 Subject: [PATCH 06/40] navigating to reply thread on content press --- src/components/comment/view/commentView.tsx | 5 +++++ src/components/postElements/body/view/commentBodyView.tsx | 3 +++ src/components/postHtmlRenderer/postHtmlRenderer.tsx | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/components/comment/view/commentView.tsx b/src/components/comment/view/commentView.tsx index f11cdb0330..10e4317877 100644 --- a/src/components/comment/view/commentView.tsx +++ b/src/components/comment/view/commentView.tsx @@ -71,6 +71,10 @@ const CommentView = ({ } }; + const _handleOnContentPress = () => { + openReplyThread(comment); + } + const _handleOnReplyPress = () => { if (isLoggedIn) { dispatch(showReplyModal(comment)); @@ -98,6 +102,7 @@ const CommentView = ({ handleOnLongPress(comment)} handleLinkPress={handleLinkPress} diff --git a/src/components/postElements/body/view/commentBodyView.tsx b/src/components/postElements/body/view/commentBodyView.tsx index 595d24bebf..c256f7ed20 100644 --- a/src/components/postElements/body/view/commentBodyView.tsx +++ b/src/components/postElements/body/view/commentBodyView.tsx @@ -24,6 +24,7 @@ const WIDTH = getWindowDimensions().width; const CommentBody = ({ body, + handleOnContentPress, handleOnUserPress, handleOnPostPress, handleOnLongPress, @@ -85,6 +86,7 @@ const CommentBody = ({ } }; + const _handleOnUserPress = (username) => { if (handleOnUserPress) { handleOnUserPress(username); @@ -125,6 +127,7 @@ const CommentBody = ({ handleTagPress={_handleTagPress} handleVideoPress={handleVideoPress} handleYoutubePress={handleYoutubePress} + handleOnContentPress={handleOnContentPress} /> diff --git a/src/components/postHtmlRenderer/postHtmlRenderer.tsx b/src/components/postHtmlRenderer/postHtmlRenderer.tsx index 1ff4bf0f9a..f486829469 100644 --- a/src/components/postHtmlRenderer/postHtmlRenderer.tsx +++ b/src/components/postHtmlRenderer/postHtmlRenderer.tsx @@ -23,6 +23,7 @@ interface PostHtmlRendererProps { handleTagPress: (tag: string, filter?: string) => void; handleVideoPress: (videoUrl: string) => void; handleYoutubePress: (videoId: string, startTime: number) => void; + handleOnContentPress?: () => void; } export const PostHtmlRenderer = memo( @@ -34,6 +35,7 @@ export const PostHtmlRenderer = memo( onLoaded, setSelectedImage, setSelectedLink, + handleOnContentPress, handleOnPostPress, handleOnUserPress, handleTagPress, @@ -274,6 +276,7 @@ export const PostHtmlRenderer = memo( */ const _paraRenderer = ({ TDefaultRenderer, ...props }: CustomRendererProps) => { props.style = props.tnode.parent.tagName === 'li' ? styles.pLi : styles.p; + props.onPress = !props.onPress && handleOnContentPress ? handleOnContentPress : props.onPress; return ; }; From e4438e48cafd220d82e14825ef55dd20646e3ad2 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 21 Aug 2023 20:14:56 +0500 Subject: [PATCH 07/40] temporary modified post details to handle waves --- src/components/postView/view/postDisplayView.tsx | 4 +++- src/screens/post/screen/postScreen.tsx | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/postView/view/postDisplayView.tsx b/src/components/postView/view/postDisplayView.tsx index 24f8b65006..da0592007b 100644 --- a/src/components/postView/view/postDisplayView.tsx +++ b/src/components/postView/view/postDisplayView.tsx @@ -249,6 +249,8 @@ const PostDisplayView = ({ setIsLoadedComments(true); }; + const _renderTitle = !!post.title && post.parent_author !== 'ecency.waves' //TODO: implemnent a better way to avoid rendering title for waves + const _postContentView = ( <> {parentPost && } @@ -262,7 +264,7 @@ const PostDisplayView = ({ setPostBodyHeight(event.nativeEvent.layout.height); }} > - {!!post.title && {post.title}} + {_renderTitle && {post.title}} { useEffect(() => { const post = getPostQuery.data; if (post) { - if (post && post.depth > 0 && post.parent_author && post.parent_permlink) { + const _fetchParent = post && post.depth > 0 + && post.parent_author && post.parent_permlink + && post.parent_author !== 'ecency.waves'; //TODO: implement a better generic way to avoid parent fetching for waves + + if (_fetchParent) { getParentPostQuery.setAuthor(post.parent_author); getParentPostQuery.setPermlink(post.parent_permlink); } From 3d5b7ce1f2cdd37afcd930179d5b050538a61e3b Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 22 Aug 2023 11:47:39 +0500 Subject: [PATCH 08/40] updated pressable highlight color --- src/components/postHtmlRenderer/postHtmlRenderer.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/postHtmlRenderer/postHtmlRenderer.tsx b/src/components/postHtmlRenderer/postHtmlRenderer.tsx index f486829469..361aa23fb4 100644 --- a/src/components/postHtmlRenderer/postHtmlRenderer.tsx +++ b/src/components/postHtmlRenderer/postHtmlRenderer.tsx @@ -277,6 +277,7 @@ export const PostHtmlRenderer = memo( const _paraRenderer = ({ TDefaultRenderer, ...props }: CustomRendererProps) => { props.style = props.tnode.parent.tagName === 'li' ? styles.pLi : styles.p; props.onPress = !props.onPress && handleOnContentPress ? handleOnContentPress : props.onPress; + return ; }; @@ -398,6 +399,7 @@ export const PostHtmlRenderer = memo( customHTMLElementModels={customHTMLElementModels} renderersProps={renderersProps} WebView={WebView} + pressableHightlightColor={'transparent'} /> ); }, From cfa3b2c32a6af44b991e7f9461ae28b67edd1ff4 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 22 Aug 2023 11:58:59 +0500 Subject: [PATCH 09/40] fine tuned post details screen header for waves --- .../postView/container/postDisplayContainer.tsx | 4 +++- src/components/postView/view/postDisplayStyles.js | 3 +++ src/components/postView/view/postDisplayView.tsx | 8 +++++--- src/screens/post/screen/postScreen.tsx | 15 +++++++++------ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/postView/container/postDisplayContainer.tsx b/src/components/postView/container/postDisplayContainer.tsx index 594a768e92..e2e4e523b5 100644 --- a/src/components/postView/container/postDisplayContainer.tsx +++ b/src/components/postView/container/postDisplayContainer.tsx @@ -25,6 +25,7 @@ const PostDisplayContainer = ({ isNewPost, parentPost, isPostUnavailable, + isWavePost, author, permlink, }) => { @@ -145,6 +146,7 @@ const PostDisplayContainer = ({ activeVotes={activeVotes} activeVotesCount={activeVotesCount} reblogs={reblogs} + isWavePost={isWavePost} fetchPost={_fetchPost} handleOnEditPress={_handleOnEditPress} handleOnRemovePress={_handleDeleteComment} @@ -157,4 +159,4 @@ const PostDisplayContainer = ({ }; -export default injectIntl(PostDisplayContainer); +export default PostDisplayContainer; diff --git a/src/components/postView/view/postDisplayStyles.js b/src/components/postView/view/postDisplayStyles.js index 8e4aa9da41..2858cbab1a 100644 --- a/src/components/postView/view/postDisplayStyles.js +++ b/src/components/postView/view/postDisplayStyles.js @@ -15,6 +15,9 @@ export default EStyleSheet.create({ marginTop: -4, marginBottom: 4, }, + titlePlaceholder:{ + marginBottom: 4, + }, title: { fontSize: 24, color: '$primaryBlack', diff --git a/src/components/postView/view/postDisplayView.tsx b/src/components/postView/view/postDisplayView.tsx index da0592007b..d46890b854 100644 --- a/src/components/postView/view/postDisplayView.tsx +++ b/src/components/postView/view/postDisplayView.tsx @@ -49,6 +49,7 @@ const PostDisplayView = ({ handleOnRemovePress, activeVotes, reblogs, + isWavePost, activeVotesCount, }) => { const dispatch = useAppDispatch(); @@ -249,8 +250,9 @@ const PostDisplayView = ({ setIsLoadedComments(true); }; - const _renderTitle = !!post.title && post.parent_author !== 'ecency.waves' //TODO: implemnent a better way to avoid rendering title for waves - + const _renderTitle = !!post.title && !isWavePost + ? {post.title} : + const _postContentView = ( <> {parentPost && } @@ -264,7 +266,7 @@ const PostDisplayView = ({ setPostBodyHeight(event.nativeEvent.layout.height); }} > - {_renderTitle && {post.title}} + {_renderTitle} { const getPostQuery = postQueries.useGetPostQuery(author, permlink, params.content); const getParentPostQuery = postQueries.useGetPostQuery(); - useEffect(()=>{ + const isWavePost = useMemo(() => getPostQuery.data?.parent_author === 'ecency.waves', [getPostQuery.data]) //TODO: implement a better generic way to avoid parent fetching for waves + + useEffect(() => { return () => { //clears FastImage RAM, not disk usage; FastImage.clearMemoryCache(); } - },[]) + }, []) useEffect(() => { const post = getPostQuery.data; if (post) { - const _fetchParent = post && post.depth > 0 - && post.parent_author && post.parent_permlink - && post.parent_author !== 'ecency.waves'; //TODO: implement a better generic way to avoid parent fetching for waves + const _fetchParent = post && post.depth > 0 + && post.parent_author && post.parent_permlink + && !isWavePost; if (_fetchParent) { getParentPostQuery.setAuthor(post.parent_author); @@ -88,6 +90,7 @@ const PostScreen = ({ route }) => { isNewPost={isNewPost} parentPost={getParentPostQuery.data} post={getPostQuery.data} + isWavePost={isWavePost} /> From e92c87aa3103f0e1c63397f0e1f2a2b644d535b6 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 22 Aug 2023 12:29:43 +0500 Subject: [PATCH 10/40] rearrange title rendering to avoid post screen crash --- src/components/postView/view/postDisplayView.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/postView/view/postDisplayView.tsx b/src/components/postView/view/postDisplayView.tsx index d46890b854..5a40d8028d 100644 --- a/src/components/postView/view/postDisplayView.tsx +++ b/src/components/postView/view/postDisplayView.tsx @@ -250,9 +250,7 @@ const PostDisplayView = ({ setIsLoadedComments(true); }; - const _renderTitle = !!post.title && !isWavePost - ? {post.title} : - + const _postContentView = ( <> {parentPost && } @@ -266,7 +264,13 @@ const PostDisplayView = ({ setPostBodyHeight(event.nativeEvent.layout.height); }} > - {_renderTitle} + + { + !!post.title && !isWavePost + ? {post.title} + : + } + Date: Tue, 22 Aug 2023 12:43:33 +0500 Subject: [PATCH 11/40] enabled replies button to open post screen from waves --- src/components/comment/view/commentView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/comment/view/commentView.tsx b/src/components/comment/view/commentView.tsx index 10e4317877..e8e52d408b 100644 --- a/src/components/comment/view/commentView.tsx +++ b/src/components/comment/view/commentView.tsx @@ -61,7 +61,7 @@ const CommentView = ({ const _currentUsername = currentAccountUsername || currentAccount?.username; const _showSubCommentsToggle = async (force = false) => { - if ((replies && replies.length > 0) || force) { + if (!!comment.commentKey && ((replies && replies.length > 0) || force)) { setIsOpeningReplies(true); await delay(10); // hack to rendering inidcator first before start loading comments handleOnToggleReplies(comment.commentKey); From 91ddadaff3f48f1c1e76dff431a24a9b9b7b7e4a Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 22 Aug 2023 17:12:36 +0500 Subject: [PATCH 12/40] separated post submitter as a hook --- .../quickReplyModalContent.tsx | 140 +++++------------ .../quickReplyModal/usePostSubmitter.ts | 146 ++++++++++++++++++ 2 files changed, 182 insertions(+), 104 deletions(-) create mode 100644 src/components/quickReplyModal/usePostSubmitter.ts diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index cca1c13777..dbaedfa820 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -38,6 +38,7 @@ import { RootState } from '../../redux/store/store'; import { PointActivityIds } from '../../providers/ecency/ecency.types'; import { useUserActivityMutation } from '../../providers/queries/pointQueries'; import { postQueries } from '../../providers/queries'; +import { usePostSubmitter } from './usePostSubmitter'; export interface QuickReplyModalContentProps { selectedPost?: any; @@ -52,6 +53,8 @@ export const QuickReplyModalContent = forwardRef( const userActivityMutation = useUserActivityMutation(); const postsCachePrimer = postQueries.usePostsCachePrimer(); + const postSubmitter = usePostSubmitter(); + // const inputRef = useRef(null); const currentAccount = useSelector((state: RootState) => state.account.currentAccount); @@ -89,11 +92,7 @@ export const QuickReplyModalContent = forwardRef( } setCommentValue(_value); - // if (inputRef.current) { - // inputRef.current.setNativeProps({ - // text: _value, - // }); - // } + }, [selectedPost]); // add quick comment value into cache @@ -127,110 +126,31 @@ export const QuickReplyModalContent = forwardRef( }); }; + + + // handle submit reply const _submitReply = async () => { - if (!commentValue) { - return; - } - if (isSending) { - return; - } - if (currentAccount) { - setIsSending(true); - - const permlink = generateReplyPermlink(selectedPost.author); - const author = currentAccount.name; - const parentAuthor = selectedPost.author; - const parentPermlink = selectedPost.permlink; - const parentTags = selectedPost.json_metadata.tags; - const category = get(selectedPost, 'category', ''); - const url = `/${category}/@${parentAuthor}/${parentPermlink}#@${author}/${permlink}`; - - console.log( - currentAccount, - pinCode, - parentAuthor, - parentPermlink, - permlink, - commentValue, - parentTags, - ); - - const status = await postComment( - currentAccount, - pinCode, - parentAuthor, - parentPermlink, - permlink, - commentValue, - parentTags, - ) - .then((response) => { - userActivityMutation.mutate({ - pointsTy: PointActivityIds.COMMENT, - transactionId: response.id, - }); - setIsSending(false); - setCommentValue(''); - - // if (inputRef.current) { - // inputRef.current.setNativeProps({ - // text: '', - // }); - // } - - dispatch( - toastNotification( - intl.formatMessage({ - id: 'alert.success', - }), - ), - ); - - // add comment cache entry - - dispatch( - updateCommentCache( - `${author}/${permlink}`, - { - author, - permlink, - url, - parent_author: parentAuthor, - parent_permlink: parentPermlink, - markdownBody: commentValue, - }, - { - parentTags: parentTags || ['ecency'], - }, - ), - ); - - // delete quick comment draft cache if it exist - if (draftsCollection && draftsCollection[draftId]) { - dispatch(deleteDraftCacheEntry(draftId)); - } - - // close should alwasy be called at method end - onClose(); - }) - .catch((error) => { - console.log(error); - Alert.alert( - intl.formatMessage({ - id: 'alert.something_wrong', - }), - error.message || JSON.stringify(error), - ); - - setIsSending(false); - _addQuickCommentIntoCache(); // add comment value into cache if there is error while posting comment - }); - console.log('status : ', status); + const isSuccess = await postSubmitter.submitReply(commentValue, selectedPost) + + if(isSuccess){ + + // delete quick comment draft cache if it exist + if (draftsCollection && draftsCollection[draftId]) { + dispatch(deleteDraftCacheEntry(draftId)); + } + setCommentValue(''); + onClose(); + } else { + _addQuickCommentIntoCache(); // add comment value into cache if there is error while posting comment } + }; + + + const _handleExpandBtn = async () => { if (selectedPost) { Keyboard.dismiss(); @@ -254,6 +174,9 @@ export const QuickReplyModalContent = forwardRef( _deboucedCacheUpdate(value); }; + + + // VIEW_RENDERERS const _renderSummary = () => ( @@ -264,6 +187,8 @@ export const QuickReplyModalContent = forwardRef( ); + + const _renderAvatar = () => ( @@ -273,6 +198,8 @@ export const QuickReplyModalContent = forwardRef( ); + + const _renderExpandBtn = () => ( ); + + + const _renderReplyBtn = () => ( ); + + const _renderContent = ( {_renderSummary()} diff --git a/src/components/quickReplyModal/usePostSubmitter.ts b/src/components/quickReplyModal/usePostSubmitter.ts new file mode 100644 index 0000000000..59ea8d9cc7 --- /dev/null +++ b/src/components/quickReplyModal/usePostSubmitter.ts @@ -0,0 +1,146 @@ +import { useDispatch } from "react-redux"; +import { useAppSelector } from "../../hooks"; +import { postComment } from "../../providers/hive/dhive"; +import { generateReplyPermlink } from "../../utils/editor"; +import { Alert } from "react-native"; +import { updateCommentCache } from "../../redux/actions/cacheActions"; +import { toastNotification } from "../../redux/actions/uiAction"; +import { useIntl } from "react-intl"; +import { useState } from "react"; +import { useUserActivityMutation, wavesQueries } from "../../providers/queries"; +import { PointActivityIds } from "../../providers/ecency/ecency.types"; + + +export const usePostSubmitter = () => { + + const dispatch = useDispatch(); + const intl = useIntl(); + + + const currentAccount = useAppSelector((state) => state.account.currentAccount); + const pinCode = useAppSelector(state => state.application.pin); + const userActivityMutation = useUserActivityMutation(); + const [isSending, setIsSending] = useState(false); + + + // handle submit reply + const _submitReply = async (commentBody: string, parentPost: any) => { + if (!commentBody) { + return; + } + if (isSending) { + return; + } + + if (currentAccount) { + setIsSending(true); + + const permlink = generateReplyPermlink(parentPost.author); + const author = currentAccount.name; + const parentAuthor = parentPost.author; + const parentPermlink = parentPost.permlink; + const parentTags = parentPost.json_metadata.tags; + const category = parentPost.category || ''; + const url = `/${category}/@${parentAuthor}/${parentPermlink}#@${author}/${permlink}`; + + console.log( + currentAccount, + pinCode, + parentAuthor, + parentPermlink, + permlink, + commentBody, + parentTags, + ); + + + try { + const response = await postComment( + currentAccount, + pinCode, + parentAuthor, + parentPermlink, + permlink, + commentBody, + parentTags, + ) + + userActivityMutation.mutate({ + pointsTy: PointActivityIds.COMMENT, + transactionId: response.id, + }); + setIsSending(false); + + dispatch( + toastNotification( + intl.formatMessage({ + id: 'alert.success', + }), + ), + ); + + // add comment cache entry + + dispatch( + updateCommentCache( + `${author}/${permlink}`, + { + author, + permlink, + url, + parent_author: parentAuthor, + parent_permlink: parentPermlink, + markdownBody: commentBody, + }, + { + parentTags: parentTags || ['ecency'], + }, + ), + ); + + return true; + + + } catch (error) { + console.log(error); + Alert.alert( + intl.formatMessage({ + id: 'alert.something_wrong', + }), + error.message || JSON.stringify(error), + ); + + setIsSending(false); + return false; + + } + + + } + }; + + + + //feteced lates wafves container and post wave to that container + const _submitWave = async (body:string) => { + + try { + const _wavesHost = 'ecency.waves' //TODO: make waves host selection dynamic + const latestWavesPost = await wavesQueries.fetchLatestWavesContainer(_wavesHost); + + return _submitReply(body, latestWavesPost) + } catch (err) { + Alert.alert("Fail", err.message) + return false; + } + } + + + + return { + submitReply: _submitReply, + submitWave: _submitWave, + isSending + } + +} \ No newline at end of file From 7823f2116ec30dd64a61b7275101dec6bb45dfc7 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 22 Aug 2023 17:13:05 +0500 Subject: [PATCH 13/40] method for fetching latest waves container for selected host --- .../queries/wavesQueries/wavesQueries.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/providers/queries/wavesQueries/wavesQueries.ts b/src/providers/queries/wavesQueries/wavesQueries.ts index c36b1ce1bf..7ed926b22c 100644 --- a/src/providers/queries/wavesQueries/wavesQueries.ts +++ b/src/providers/queries/wavesQueries/wavesQueries.ts @@ -121,6 +121,29 @@ import { delay } from '../../../utils/editor'; refresh: _refresh, }; }; + + + export const fetchLatestWavesContainer = async (host) => { + const query: any = { + account: host, + start_author: '', + start_permlink: '', + limit: 1, + observer: '', + sort: 'posts', + }; + + const result = await getAccountPosts(query); + + const _latestPost = result[0]; + console.log('lates waves post', host, _latestPost); + + if(!_latestPost){ + throw new Error("Lates waves container could be not fetched"); + } + + return _latestPost; + } From 1f6d7a36b8efc9b76564664006c34451addda599 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 22 Aug 2023 18:21:54 +0500 Subject: [PATCH 14/40] updated quick reply modal payload to support varioud modes --- .../bottomTabBar/view/bottomTabBarView.tsx | 19 ++++++++++++++++--- src/components/comment/view/commentView.tsx | 2 +- .../postView/view/postDisplayView.tsx | 2 +- .../tabbedPosts/view/tabContent.tsx | 2 +- src/redux/actions/uiAction.ts | 8 ++++++-- src/redux/reducers/uiReducer.ts | 18 ++++++++++++++---- 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/components/bottomTabBar/view/bottomTabBarView.tsx b/src/components/bottomTabBar/view/bottomTabBarView.tsx index b12c640d79..45689d2ffd 100644 --- a/src/components/bottomTabBar/view/bottomTabBarView.tsx +++ b/src/components/bottomTabBar/view/bottomTabBarView.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { SafeAreaView, View, TouchableOpacity } from 'react-native'; +import { SafeAreaView, View, TouchableOpacity, Alert } from 'react-native'; // Components // import TabBar from './tabbar'; @@ -13,7 +13,7 @@ import ROUTES from '../../../constants/routeNames'; import styles from './bottomTabBarStyles'; import Icon, { IconContainer } from '../../icon'; import scalePx from '../../../utils/scalePx'; -import { updateActiveBottomTab } from '../../../redux/actions/uiAction'; +import { showReplyModal, updateActiveBottomTab } from '../../../redux/actions/uiAction'; const BottomTabBarView = ({ state: { routes, index }, @@ -26,9 +26,18 @@ const BottomTabBarView = ({ dispatch(updateActiveBottomTab(routes[index].name)); }, [index]); + + const _jumpTo = (route, isFocused) => { + if (route.name === ROUTES.TABBAR.POST_BUTTON) { - navigation.navigate(ROUTES.SCREENS.EDITOR, { key: 'editor_post' }); + + if (routes[index].name === ROUTES.TABBAR.WAVES) { + dispatch(showReplyModal({mode:'wave'})); + } else { + navigation.navigate(ROUTES.SCREENS.EDITOR, { key: 'editor_post' }); + } + return; } @@ -44,6 +53,8 @@ const BottomTabBarView = ({ } }; + + const _tabButtons = routes.map((route, idx) => { const { tabBarActiveTintColor, tabBarInactiveTintColor } = descriptors[route.key].options; const isFocused = index == idx; @@ -77,6 +88,8 @@ const BottomTabBarView = ({ ); }); + + return {_tabButtons}; }; diff --git a/src/components/comment/view/commentView.tsx b/src/components/comment/view/commentView.tsx index e8e52d408b..301d5e2013 100644 --- a/src/components/comment/view/commentView.tsx +++ b/src/components/comment/view/commentView.tsx @@ -77,7 +77,7 @@ const CommentView = ({ const _handleOnReplyPress = () => { if (isLoggedIn) { - dispatch(showReplyModal(comment)); + dispatch(showReplyModal({mode:'comment', parentPost:comment})); } else { console.log('Not LoggedIn'); } diff --git a/src/components/postView/view/postDisplayView.tsx b/src/components/postView/view/postDisplayView.tsx index 5a40d8028d..8186a15e7b 100644 --- a/src/components/postView/view/postDisplayView.tsx +++ b/src/components/postView/view/postDisplayView.tsx @@ -233,7 +233,7 @@ const PostDisplayView = ({ // show quick reply modal const _showQuickReplyModal = (_post = post) => { if (isLoggedIn) { - dispatch(showReplyModal(_post)); + dispatch(showReplyModal({mode:'comment', parentPost:_post})); } else { console.log('Not LoggedIn'); } diff --git a/src/components/tabbedPosts/view/tabContent.tsx b/src/components/tabbedPosts/view/tabContent.tsx index a31312842d..4c3f79903f 100644 --- a/src/components/tabbedPosts/view/tabContent.tsx +++ b/src/components/tabbedPosts/view/tabContent.tsx @@ -329,7 +329,7 @@ const TabContent = ({ // show quick reply modal const _showQuickReplyModal = (post: any) => { if (isLoggedIn) { - dispatch(showReplyModal(post)); + dispatch(showReplyModal({mode:'comment', parentPost:post})); } else { //TODO: show proper alert message console.log('Not LoggedIn'); diff --git a/src/redux/actions/uiAction.ts b/src/redux/actions/uiAction.ts index 014e9ca9ce..01cca10160 100644 --- a/src/redux/actions/uiAction.ts +++ b/src/redux/actions/uiAction.ts @@ -16,6 +16,7 @@ import { LOGOUT, LOGOUT_DONE, } from '../constants/constants'; +import { PostEditorModalData } from '../reducers/uiReducer'; export const updateActiveBottomTab = (payload: string) => ({ payload, @@ -82,8 +83,11 @@ export const setLockedOrientation = (payload: string) => ({ type: SET_LOCKED_ORIENTATION, }); -export const showReplyModal = (selectionPost: any) => ({ - payload: selectionPost, +export const showReplyModal = ({mode, parentPost}:PostEditorModalData) => ({ + payload: { + mode: mode || 'comment', + parentPost + } as PostEditorModalData, type: SHOW_REPLY_MODAL, }); diff --git a/src/redux/reducers/uiReducer.ts b/src/redux/reducers/uiReducer.ts index b7cbd765b2..f005cf1265 100644 --- a/src/redux/reducers/uiReducer.ts +++ b/src/redux/reducers/uiReducer.ts @@ -18,6 +18,12 @@ import { } from '../constants/constants'; import { orientations } from '../constants/orientationsConstants'; + +export interface PostEditorModalData { + mode:'wave'|'comment'|'post', + parentPost?:any +} + interface UiState { activeBottomTab: string; toastNotification: string; @@ -31,7 +37,7 @@ interface UiState { deviceOrientation: string; lockedOrientation: string; replyModalVisible: boolean; - replyModalPost: any; + replyModalData?: PostEditorModalData | null; isLogingOut: boolean; } @@ -47,7 +53,7 @@ const initialState: UiState = { isVisibleQRModal: false, deviceOrientation: orientations.PORTRAIT, lockedOrientation: orientations.PORTRAIT, - replyModalPost: null, + replyModalData: null, replyModalVisible: false, isLogingOut: false, }; @@ -128,16 +134,20 @@ export default function (state = initialState, action): UiState { lockedOrientation: action.payload, }; case SHOW_REPLY_MODAL: + const _payload = action.payload as PostEditorModalData; + if(_payload.mode === 'comment' && !_payload.parentPost){ + throw new Error("parent post missing for showing post editor modal with comment mode") + } return { ...state, replyModalVisible: true, - replyModalPost: action.payload, + replyModalData: action.payload, }; case HIDE_REPLY_MODAL: return { ...state, replyModalVisible: false, - replyModalPost: null, + replyModalData: null, }; case LOGOUT: return { From 3608875cf49937410c4d58c309753a6e26de0106 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 22 Aug 2023 18:22:16 +0500 Subject: [PATCH 15/40] support for publishing waves from mobile app --- .../quickReplyModalContent.tsx | 118 ++++++++++-------- .../quickReplyModal/quickReplyModalView.tsx | 8 +- .../quickReplyModal/usePostSubmitter.ts | 6 +- src/config/locales/en-US.json | 2 + 4 files changed, 79 insertions(+), 55 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index dbaedfa820..f7bd5b4f95 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -10,11 +10,9 @@ import EStyleSheet from 'react-native-extended-stylesheet'; import { View, Text, - Alert, TouchableOpacity, Keyboard, Platform, - TextInput as RNTextInput, } from 'react-native'; import { useIntl } from 'react-intl'; import { useSelector, useDispatch } from 'react-redux'; @@ -22,12 +20,9 @@ import { get, debounce } from 'lodash'; import { postBodySummary } from '@ecency/render-helper'; import styles from './quickReplyModalStyles'; import { IconButton, MainButton, TextButton, TextInput, UserAvatar } from '..'; -import { delay, generateReplyPermlink } from '../../utils/editor'; -import { postComment } from '../../providers/hive/dhive'; -import { toastNotification } from '../../redux/actions/uiAction'; +import { delay } from '../../utils/editor'; import { deleteDraftCacheEntry, - updateCommentCache, updateDraftCache, } from '../../redux/actions/cacheActions'; import { default as ROUTES } from '../../constants/routeNames'; @@ -35,22 +30,24 @@ import RootNavigation from '../../navigation/rootNavigation'; import { Draft } from '../../redux/reducers/cacheReducer'; import { RootState } from '../../redux/store/store'; -import { PointActivityIds } from '../../providers/ecency/ecency.types'; -import { useUserActivityMutation } from '../../providers/queries/pointQueries'; import { postQueries } from '../../providers/queries'; import { usePostSubmitter } from './usePostSubmitter'; export interface QuickReplyModalContentProps { + mode: 'comment' | 'wave' | 'post', selectedPost?: any; handleCloseRef?: any; onClose: () => void; } export const QuickReplyModalContent = forwardRef( - ({ selectedPost, onClose }: QuickReplyModalContentProps, ref) => { + ({ + mode, + selectedPost, + onClose + }: QuickReplyModalContentProps, ref) => { const intl = useIntl(); const dispatch = useDispatch(); - const userActivityMutation = useUserActivityMutation(); const postsCachePrimer = postQueries.usePostsCachePrimer(); const postSubmitter = usePostSubmitter(); @@ -58,17 +55,19 @@ export const QuickReplyModalContent = forwardRef( // const inputRef = useRef(null); const currentAccount = useSelector((state: RootState) => state.account.currentAccount); - const pinCode = useSelector((state: RootState) => state.application.pin); const draftsCollection = useSelector((state: RootState) => state.cache.draftsCollection); const [commentValue, setCommentValue] = useState(''); - const [isSending, setIsSending] = useState(false); const headerText = selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS)); const parentAuthor = selectedPost ? selectedPost.author : ''; const parentPermlink = selectedPost ? selectedPost.permlink : ''; - const draftId = `${currentAccount.name}/${parentAuthor}/${parentPermlink}`; // different draftId for each user acount + + + const draftId = mode === 'wave' + ? `${currentAccount.name}/ecency.waves` //TODO: update author based on selected host + : `${currentAccount.name}/${parentAuthor}/${parentPermlink}`; // different draftId for each user acount useImperativeHandle(ref, () => ({ handleSheetClose() { @@ -130,12 +129,24 @@ export const QuickReplyModalContent = forwardRef( // handle submit reply - const _submitReply = async () => { + const _submitPost = async () => { + + let _isSuccess = false; + + switch (mode) { + case 'comment': + _isSuccess = await postSubmitter.submitReply(commentValue, selectedPost); + break;; + case 'wave': + _isSuccess = await postSubmitter.submitWave(commentValue); + break; + default: + throw new Error("mode needs implementing") + } - const isSuccess = await postSubmitter.submitReply(commentValue, selectedPost) - if(isSuccess){ - + if (_isSuccess) { + // delete quick comment draft cache if it exist if (draftsCollection && draftsCollection[draftId]) { dispatch(deleteDraftCacheEntry(draftId)); @@ -179,7 +190,7 @@ export const QuickReplyModalContent = forwardRef( // VIEW_RENDERERS - const _renderSummary = () => ( + const _renderSummary = () => mode !== 'wave' && ( _handleOnSummaryPress()}> {headerText} @@ -202,42 +213,51 @@ export const QuickReplyModalContent = forwardRef( const _renderExpandBtn = () => ( - + {mode !== 'wave' && ( + + )} + ); - const _renderReplyBtn = () => ( - - - _submitReply()} - text={intl.formatMessage({ - id: 'quick_reply.reply', - })} - isLoading={postSubmitter.isSending} - /> - - ); + const _renderReplyBtn = () => { + const _titleId = mode !== 'comment' ? 'quick_reply.publish' : 'quick_reply.reply'; + return ( + + + _submitPost()} + text={intl.formatMessage({ + id: _titleId, + })} + isLoading={postSubmitter.isSending} + /> + + ) + }; - const _renderContent = ( + + const _placeholderId = mode === 'comment' ? 'quick_reply.placeholder' : 'quick_reply.placeholder_wave' + + return ( {_renderSummary()} {_renderAvatar()} @@ -248,7 +268,7 @@ export const QuickReplyModalContent = forwardRef( onChangeText={_onChangeText} autoFocus={true} placeholder={intl.formatMessage({ - id: 'quick_reply.placeholder', + id: _placeholderId, })} placeholderTextColor="#c1c5c7" style={styles.textInput} @@ -262,8 +282,6 @@ export const QuickReplyModalContent = forwardRef( {_renderReplyBtn()} - ); - - return _renderContent; + ) }, ); diff --git a/src/components/quickReplyModal/quickReplyModalView.tsx b/src/components/quickReplyModal/quickReplyModalView.tsx index c4082407be..61e45f7a95 100644 --- a/src/components/quickReplyModal/quickReplyModalView.tsx +++ b/src/components/quickReplyModal/quickReplyModalView.tsx @@ -3,12 +3,13 @@ import { QuickReplyModalContent } from './quickReplyModalContent'; import { InputSupportModal } from '../organisms'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { hideReplyModal } from '../../redux/actions/uiAction'; +import { PostEditorModalData } from '../../redux/reducers/uiReducer'; const QuickReplyModal = () => { const dispatch = useAppDispatch(); const replyModalVisible = useAppSelector((state) => state.ui.replyModalVisible); - const replyModalPost = useAppSelector((state) => state.ui.replyModalPost); + const replyModalData:PostEditorModalData = useAppSelector((state) => state.ui.replyModalData); const modalContentRef = useRef(null); const _onClose = () => { @@ -19,10 +20,11 @@ const QuickReplyModal = () => { }; return ( - + diff --git a/src/components/quickReplyModal/usePostSubmitter.ts b/src/components/quickReplyModal/usePostSubmitter.ts index 59ea8d9cc7..d43a67a5dc 100644 --- a/src/components/quickReplyModal/usePostSubmitter.ts +++ b/src/components/quickReplyModal/usePostSubmitter.ts @@ -26,10 +26,10 @@ export const usePostSubmitter = () => { // handle submit reply const _submitReply = async (commentBody: string, parentPost: any) => { if (!commentBody) { - return; + return false ; } if (isSending) { - return; + return false; } if (currentAccount) { @@ -117,6 +117,8 @@ export const usePostSubmitter = () => { } + + return false; }; diff --git a/src/config/locales/en-US.json b/src/config/locales/en-US.json index 8b7c40fa0c..556749f7f8 100644 --- a/src/config/locales/en-US.json +++ b/src/config/locales/en-US.json @@ -1009,8 +1009,10 @@ }, "quick_reply":{ "placeholder":"Add a comment...", + "placeholder_wave": "What's happening?", "comment": "Comment", "reply": "REPLY", + "publish": "PUBLISH", "close":"CLOSE" }, "walkthrough":{ From 9f1f2fb39635fcbd6f8f4c97f551bc3d2007e2df Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Thu, 24 Aug 2023 14:50:25 +0500 Subject: [PATCH 16/40] moved inject vote cache to post parser --- .../queries/postQueries/postQueries.ts | 102 +----------------- src/utils/postParser.tsx | 98 +++++++++++++++++ 2 files changed, 103 insertions(+), 97 deletions(-) diff --git a/src/providers/queries/postQueries/postQueries.ts b/src/providers/queries/postQueries/postQueries.ts index a4ac5ac473..2bbbb5b94a 100644 --- a/src/providers/queries/postQueries/postQueries.ts +++ b/src/providers/queries/postQueries/postQueries.ts @@ -7,6 +7,7 @@ import { useAppSelector } from '../../../hooks'; import { getDiscussionCollection, getPost } from '../../hive/dhive'; import QUERIES from '../queryKeys'; import { Comment, CacheStatus, LastUpdateMeta } from '../../../redux/reducers/cacheReducer'; +import { injectPostCache, injectVoteCache } from '../../../utils/postParser'; /** hook used to return user drafts */ export const useGetPostQuery = (_author?: string, _permlink?: string, initialPost?: any) => { @@ -121,83 +122,14 @@ export const useDiscussionQuery = (_author?: string, _permlink?: string) => { ); useEffect(() => { - _injectCache(); + const _data = injectPostCache(query.data, cachedComments, cachedVotes, lastCacheUpdate); + setData(_data); }, [query.data, cachedComments, cachedVotes]); useEffect(() => { restructureData(); }, [data]); - // inject cached comments here - const _injectCache = async () => { - let shouldClone = false; - const _comments = query.data || {}; - console.log('updating with cache', _comments, cachedComments); - if (!cachedComments || !_comments) { - console.log('Skipping cache injection'); - return _comments; - } - - // process votes cache - for (const path in cachedVotes) { - const cachedVote = cachedVotes[path]; - if (_comments[path]) { - console.log('injection vote cache'); - _comments[path] = _injectVoteFunc(_comments[path], cachedVote); - } - } - - // process comments cache - for (const path in cachedComments) { - const currentTime = new Date().getTime(); - const cachedComment = cachedComments[path]; - const _parentPath = `${cachedComment.parent_author}/${cachedComment.parent_permlink}`; - const cacheUpdateTimestamp = new Date(cachedComment.updated || 0).getTime(); - - switch (cachedComment.status) { - case CacheStatus.DELETED: - if (_comments && _comments[path]) { - delete _comments[path]; - shouldClone = true; - } - break; - case CacheStatus.UPDATED: - case CacheStatus.PENDING: - // check if commentKey already exist in comments map, - if (_comments[path]) { - shouldClone = true; - // check if we should update comments map with cached map based on updat timestamp - const remoteUpdateTimestamp = new Date(_comments[path].updated).getTime(); - - if (cacheUpdateTimestamp > remoteUpdateTimestamp) { - _comments[path].body = cachedComment.body; - } - } - - // if comment key do not exist, possiblky comment is a new comment, in this case, check if parent of comment exist in map - else if (_comments[_parentPath]) { - shouldClone = true; - // in this case add comment key in childern and inject cachedComment in commentsMap - _comments[path] = cachedComment; - _comments[_parentPath].replies.push(path); - _comments[_parentPath].children = _comments[_parentPath].children + 1; - - // if comment was created very recently enable auto reveal - if ( - lastCacheUpdate.postPath === path && - currentTime - lastCacheUpdate.updatedAt < 5000 - ) { - console.log('setting show replies flag'); - _comments[_parentPath].expandedReplies = true; - _comments[path].renderOnTop = true; - } - } - break; - } - } - - setData(shouldClone ? { ..._comments } : _comments); - }; // traverse discussion collection to curate sections const restructureData = async () => { @@ -297,7 +229,7 @@ export const useInjectVotesCache = (_data: any | any[]) => { // if post available, inject cache and update state if (_postData) { - _postData = _injectVoteFunc(_postData, _voteCache); + _postData = injectVoteCache(_postData, _voteCache); if (_postIndex < 0) { console.log('updating data', _postData); @@ -321,7 +253,7 @@ export const useInjectVotesCache = (_data: any | any[]) => { const _path = `${item.author}/${item.permlink}`; const voteCache = votesCollection[_path]; - item = _injectVoteFunc(item, voteCache); + item = injectVoteCache(item, voteCache); } return item; }; @@ -333,27 +265,3 @@ export const useInjectVotesCache = (_data: any | any[]) => { return retData || _data; }; - -const _injectVoteFunc = (post, voteCache) => { - if ( - voteCache && - (voteCache.status !== CacheStatus.FAILED || voteCache.status !== CacheStatus.DELETED) - ) { - const _voteIndex = post.active_votes.findIndex((i) => i.voter === voteCache.voter); - if (_voteIndex < 0) { - post.total_payout += voteCache.amount * (voteCache.isDownvote ? -1 : 1); - post.active_votes = [ - ...post.active_votes, - { - voter: voteCache.voter, - rshares: voteCache.isDownvote ? -1000 : 1000, - }, - ]; - } else { - post.active_votes[_voteIndex].rshares = voteCache.isDownvote ? -1000 : 1000; - post.active_votes = [...post.active_votes]; - } - } - - return post; -}; diff --git a/src/utils/postParser.tsx b/src/utils/postParser.tsx index 22112dcd50..ee1ec0ad34 100644 --- a/src/utils/postParser.tsx +++ b/src/utils/postParser.tsx @@ -9,6 +9,7 @@ import FastImage from 'react-native-fast-image'; import parseAsset from './parseAsset'; import { getResizedAvatar } from './image'; import { parseReputation } from './user'; +import { CacheStatus } from '../redux/reducers/cacheReducer'; const webp = Platform.OS !== 'ios'; @@ -201,6 +202,103 @@ export const parseComment = (comment: any) => { return comment; }; + +export const injectPostCache = (commentsMap, cachedComments, cachedVotes, lastCacheUpdate) => { + let shouldClone = false; + const _comments = commentsMap || {}; + console.log('updating with cache', _comments, cachedComments); + if (!cachedComments || !_comments) { + console.log('Skipping cache injection'); + return _comments; + } + + // process votes cache + for (const path in cachedVotes) { + const cachedVote = cachedVotes[path]; + if (_comments[path]) { + console.log('injection vote cache'); + _comments[path] = injectVoteCache(_comments[path], cachedVote); + } + } + + // process comments cache + for (const path in cachedComments) { + const currentTime = new Date().getTime(); + const cachedComment = cachedComments[path]; + const _parentPath = `${cachedComment.parent_author}/${cachedComment.parent_permlink}`; + const cacheUpdateTimestamp = new Date(cachedComment.updated || 0).getTime(); + + switch (cachedComment.status) { + case CacheStatus.DELETED: + if (_comments && _comments[path]) { + delete _comments[path]; + shouldClone = true; + } + break; + case CacheStatus.UPDATED: + case CacheStatus.PENDING: + // check if commentKey already exist in comments map, + if (_comments[path]) { + shouldClone = true; + // check if we should update comments map with cached map based on updat timestamp + const remoteUpdateTimestamp = new Date(_comments[path].updated).getTime(); + + if (cacheUpdateTimestamp > remoteUpdateTimestamp) { + _comments[path].body = cachedComment.body; + } + } + + // if comment key do not exist, possiblky comment is a new comment, in this case, check if parent of comment exist in map + else if (_comments[_parentPath]) { + shouldClone = true; + // in this case add comment key in childern and inject cachedComment in commentsMap + _comments[path] = cachedComment; + _comments[_parentPath].replies.push(path); + _comments[_parentPath].children = _comments[_parentPath].children + 1; + + // if comment was created very recently enable auto reveal + if ( + lastCacheUpdate.postPath === path && + currentTime - lastCacheUpdate.updatedAt < 5000 + ) { + console.log('setting show replies flag'); + _comments[_parentPath].expandedReplies = true; + _comments[path].renderOnTop = true; + } + } + break; + } + } + + return shouldClone ? { ..._comments } : _comments; +} + + +export const injectVoteCache = (post, voteCache) => { + if ( + voteCache && + (voteCache.status !== CacheStatus.FAILED || voteCache.status !== CacheStatus.DELETED) + ) { + const _voteIndex = post.active_votes.findIndex((i) => i.voter === voteCache.voter); + if (_voteIndex < 0) { + post.total_payout += voteCache.amount * (voteCache.isDownvote ? -1 : 1); + post.active_votes = [ + ...post.active_votes, + { + voter: voteCache.voter, + rshares: voteCache.isDownvote ? -1000 : 1000, + }, + ]; + } else { + post.active_votes[_voteIndex].rshares = voteCache.isDownvote ? -1000 : 1000; + post.active_votes = [...post.active_votes]; + } + } + + return post; +}; + + export const isVoted = async (activeVotes, currentUserName) => { if (!currentUserName) { return false; From a926631accfbaad32cb4213d09a0a94bb6170f0c Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Thu, 24 Aug 2023 19:35:26 +0500 Subject: [PATCH 17/40] introduced mutation to update waves query data with cached wave --- .../quickReplyModalContent.tsx | 2 +- .../quickReplyModal/usePostSubmitter.ts | 37 +-- src/providers/queries/index.ts | 3 +- src/providers/queries/postQueries/index.ts | 3 +- .../queries/postQueries/postQueries.ts | 2 +- .../queries/postQueries/wavesQueries.ts | 208 +++++++++++++++++ src/providers/queries/wavesQueries/index.ts | 3 - .../queries/wavesQueries/wavesQueries.ts | 213 ------------------ src/screens/post/screen/postScreen.tsx | 2 +- src/screens/waves/screen/wavesScreen.tsx | 4 +- 10 files changed, 240 insertions(+), 237 deletions(-) create mode 100644 src/providers/queries/postQueries/wavesQueries.ts delete mode 100644 src/providers/queries/wavesQueries/index.ts delete mode 100644 src/providers/queries/wavesQueries/wavesQueries.ts diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index f7bd5b4f95..424abfa30e 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -66,7 +66,7 @@ export const QuickReplyModalContent = forwardRef( const draftId = mode === 'wave' - ? `${currentAccount.name}/ecency.waves` //TODO: update author based on selected host + ? `${currentAccount.name}/demo.com` //TODO: update author based on selected host : `${currentAccount.name}/${parentAuthor}/${parentPermlink}`; // different draftId for each user acount useImperativeHandle(ref, () => ({ diff --git a/src/components/quickReplyModal/usePostSubmitter.ts b/src/components/quickReplyModal/usePostSubmitter.ts index d43a67a5dc..0a43184f89 100644 --- a/src/components/quickReplyModal/usePostSubmitter.ts +++ b/src/components/quickReplyModal/usePostSubmitter.ts @@ -1,7 +1,7 @@ import { useDispatch } from "react-redux"; import { useAppSelector } from "../../hooks"; import { postComment } from "../../providers/hive/dhive"; -import { generateReplyPermlink } from "../../utils/editor"; +import { generateReplyPermlink, makeJsonMetadataReply } from "../../utils/editor"; import { Alert } from "react-native"; import { updateCommentCache } from "../../redux/actions/cacheActions"; import { toastNotification } from "../../redux/actions/uiAction"; @@ -9,6 +9,7 @@ import { useIntl } from "react-intl"; import { useState } from "react"; import { useUserActivityMutation, wavesQueries } from "../../providers/queries"; import { PointActivityIds } from "../../providers/ecency/ecency.types"; +import { usePublishWaveMutation } from "../../providers/queries/postQueries/wavesQueries"; export const usePostSubmitter = () => { @@ -16,6 +17,8 @@ export const usePostSubmitter = () => { const dispatch = useDispatch(); const intl = useIntl(); + const pusblishWaveMutation = usePublishWaveMutation(); + const currentAccount = useAppSelector((state) => state.account.currentAccount); const pinCode = useAppSelector(state => state.application.pin); @@ -80,25 +83,27 @@ export const usePostSubmitter = () => { ); // add comment cache entry - + const _cacheCommentData = { + author, + permlink, + url, + parent_author: parentAuthor, + parent_permlink: parentPermlink, + markdownBody: commentBody, + json_metadata: makeJsonMetadataReply(parentTags || ['ecency']) + } + dispatch( updateCommentCache( `${author}/${permlink}`, - { - author, - permlink, - url, - parent_author: parentAuthor, - parent_permlink: parentPermlink, - markdownBody: commentBody, - }, + _cacheCommentData, { parentTags: parentTags || ['ecency'], }, ), ); - return true; + return _cacheCommentData; } catch (error) { @@ -127,10 +132,16 @@ export const usePostSubmitter = () => { const _submitWave = async (body:string) => { try { - const _wavesHost = 'ecency.waves' //TODO: make waves host selection dynamic + const _wavesHost = 'demo.com' //TODO: make waves host selection dynamic const latestWavesPost = await wavesQueries.fetchLatestWavesContainer(_wavesHost); - return _submitReply(body, latestWavesPost) + const _cacheCommentData = await _submitReply(body, latestWavesPost) + + if(_cacheCommentData){ + pusblishWaveMutation.mutate(_cacheCommentData) + } + + return _cacheCommentData } catch (err) { Alert.alert("Fail", err.message) return false; diff --git a/src/providers/queries/index.ts b/src/providers/queries/index.ts index 2e072a0740..a27f8021dc 100644 --- a/src/providers/queries/index.ts +++ b/src/providers/queries/index.ts @@ -29,5 +29,4 @@ export * from './editorQueries'; export * from './pointQueries'; export * from './postQueries'; export * from './walletQueries'; -export * from './leaderboardQueries'; -export * from './wavesQueries'; \ No newline at end of file +export * from './leaderboardQueries'; \ No newline at end of file diff --git a/src/providers/queries/postQueries/index.ts b/src/providers/queries/postQueries/index.ts index 4998af6119..87a1f768b5 100644 --- a/src/providers/queries/postQueries/index.ts +++ b/src/providers/queries/postQueries/index.ts @@ -1,3 +1,4 @@ import * as postQueries from './postQueries'; +import * as wavesQueries from './wavesQueries'; -export { postQueries }; +export { postQueries, wavesQueries }; diff --git a/src/providers/queries/postQueries/postQueries.ts b/src/providers/queries/postQueries/postQueries.ts index 2bbbb5b94a..f6eed34a5a 100644 --- a/src/providers/queries/postQueries/postQueries.ts +++ b/src/providers/queries/postQueries/postQueries.ts @@ -6,7 +6,7 @@ import { isArray } from 'lodash'; import { useAppSelector } from '../../../hooks'; import { getDiscussionCollection, getPost } from '../../hive/dhive'; import QUERIES from '../queryKeys'; -import { Comment, CacheStatus, LastUpdateMeta } from '../../../redux/reducers/cacheReducer'; +import { Comment, LastUpdateMeta } from '../../../redux/reducers/cacheReducer'; import { injectPostCache, injectVoteCache } from '../../../utils/postParser'; /** hook used to return user drafts */ diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts new file mode 100644 index 0000000000..7d1c0b9b9d --- /dev/null +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -0,0 +1,208 @@ + +import { + UseMutationOptions, + useMutation, + useQueries, useQueryClient, +} from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; + +import { unionBy } from 'lodash'; +import { getComments } from '../../hive/dhive'; + +import { getAccountPosts } from '../../hive/dhive'; +import QUERIES from '../queryKeys'; +import { delay } from '../../../utils/editor'; +import { useAppSelector } from '../../../hooks'; + + + +export const useWavesQuery = (host: string) => { + + const [isRefreshing, setIsRefreshing] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [activePermlinks, setActivePermlinks] = useState([]); + const [permlinksBucket, setPermlinksBucket] = useState([]); + + + + + // query initialization + const wavesQueries = useQueries({ + queries: activePermlinks.map((pagePermlink, index) => ({ + queryKey: [QUERIES.WAVES.GET, host, index], + queryFn: () => _fetchWaves(pagePermlink), + initialData: [], + })), + }); + + + + + useEffect(() => { + _fetchPermlinks() + }, []) + + + useEffect(() => { + if (!!permlinksBucket.length) { + activePermlinks.push(permlinksBucket[activePermlinks.length]); + setActivePermlinks([...activePermlinks]); + } + }, [permlinksBucket]) + + + + const _fetchPermlinks = async (startPermlink = '', refresh = false) => { + setIsLoading(true); + try { + const query: any = { + account: host, + start_author: !!startPermlink ? host : '', + start_permlink: startPermlink, + limit: 10, + observer: '', + sort: 'posts', + }; + + const result = await getAccountPosts(query); + + const _fetchedPermlinks = result.map(post => post.permlink); + console.log('permlinks fetched', _fetchedPermlinks); + + const _permlinksBucket = refresh ? _fetchedPermlinks : [...permlinksBucket, ..._fetchedPermlinks]; + setPermlinksBucket(_permlinksBucket); + + if (refresh) { + //precautionary delay of 200ms to let state update before concluding promise, + //it is effective for waves refresh routine. + await delay(200) + } + } catch (err) { + console.warn("failed to fetch waves permlinks"); + } + + setIsLoading(false) + } + + const _fetchWaves = async (pagePermlink: string) => { + console.log('fetching waves from:', host, pagePermlink); + const response = await getComments(host, pagePermlink); + response.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); + console.log('new waves fetched', response); + return response || []; + }; + + + const _refresh = async () => { + setIsRefreshing(true); + setPermlinksBucket([]); + setActivePermlinks([]); + await _fetchPermlinks('', true); + await wavesQueries[0].refetch(); + setIsRefreshing(false); + }; + + const _fetchNextPage = () => { + const lastPage = wavesQueries.lastItem; + + if (!lastPage || lastPage.isFetching) { + return; + } + + const _nextPagePermlink = permlinksBucket[activePermlinks.length]; + + //TODO: find a way to proactively refill active permlinks here. + + if (_nextPagePermlink && !activePermlinks.includes(_nextPagePermlink)) { + activePermlinks.push(_nextPagePermlink); + setActivePermlinks([...activePermlinks]); + } else { + _fetchPermlinks(permlinksBucket.lastItem) + } + }; + + const _dataArrs = wavesQueries.map((query) => query.data); + + return { + data: unionBy(..._dataArrs, 'url'), + isRefreshing, + isLoading: isLoading || wavesQueries.lastItem?.isLoading || wavesQueries.lastItem?.isFetching, + fetchNextPage: _fetchNextPage, + refresh: _refresh, + }; +}; + + +export const fetchLatestWavesContainer = async (host) => { + const query: any = { + account: host, + start_author: '', + start_permlink: '', + limit: 1, + observer: '', + sort: 'posts', + }; + + const result = await getAccountPosts(query); + + const _latestPost = result[0]; + console.log('lates waves post', host, _latestPost); + + if (!_latestPost) { + throw new Error("Lates waves container could be not fetched"); + } + + return _latestPost; +} + + + + +export const usePublishWaveMutation = () => { + + const queryClient = useQueryClient(); + + // const cachedComments = useAppSelector(state => state.cache.commentsCollection); + + // id is options, if no id is provided program marks all notifications as read; + const _mutationFn = async (cachePostData: any) => { + //TODO: lates port wave publishing here or introduce post publishing mutation; + if(cachePostData){ //TODO: expand to check multiple wave hosts;{ + const _host = cachePostData.parent_author; + + console.log('returning waves host', _host); + return _host; + } + + throw new Error("invalid mutations data") + + }; + + const _options: UseMutationOptions = { + onMutate: async (cacheCommentData:any) => { + // TODO: find a way to optimise mutations by avoiding too many loops + console.log('on mutate data', cacheCommentData); + + const _host = cacheCommentData.parent_author; + + // update query data + const _queryKey = [ QUERIES.WAVES.GET, _host, 0]; + const queryData: any[] | undefined = queryClient.getQueryData(_queryKey); + + console.log('query data', queryData); + + if (queryData && cacheCommentData) { + queryData.splice(0, 0, cacheCommentData); + queryClient.setQueryData(_queryKey, queryData); + } + + }, + + onSuccess: async (host) => { + await delay(5000); + queryClient.invalidateQueries([QUERIES.WAVES.GET, host, 0]); + }, + }; + + return useMutation(_mutationFn, _options); +}; diff --git a/src/providers/queries/wavesQueries/index.ts b/src/providers/queries/wavesQueries/index.ts deleted file mode 100644 index 1690c4dfe0..0000000000 --- a/src/providers/queries/wavesQueries/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as wavesQueries from './wavesQueries'; - -export { wavesQueries }; diff --git a/src/providers/queries/wavesQueries/wavesQueries.ts b/src/providers/queries/wavesQueries/wavesQueries.ts deleted file mode 100644 index 7ed926b22c..0000000000 --- a/src/providers/queries/wavesQueries/wavesQueries.ts +++ /dev/null @@ -1,213 +0,0 @@ - - import { - useQueries, - } from '@tanstack/react-query'; - import { useEffect, useState } from 'react'; - - import { unionBy } from 'lodash'; - import { getComments } from '../../hive/dhive'; - -import { getAccountPosts } from '../../hive/dhive'; -import QUERIES from '../queryKeys'; -import { delay } from '../../../utils/editor'; - - - - export const useWavesQuery = (host: string) => { - const [isRefreshing, setIsRefreshing] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [activePermlinks, setActivePermlinks] = useState([]); - const [permlinksBucket, setPermlinksBucket] = useState([]); - - - useEffect(() => { - _fetchPermlinks() - }, []) - - - useEffect(() => { - if(!!permlinksBucket.length){ - activePermlinks.push(permlinksBucket[activePermlinks.length]); - setActivePermlinks([...activePermlinks]); - } - },[permlinksBucket]) - - - const _fetchPermlinks = async (startPermlink = '', refresh = false) => { - setIsLoading(true); - try{ - const query: any = { - account: host, - start_author: !!startPermlink ? host : '', - start_permlink: startPermlink, - limit: 10, - observer: '', - sort: 'posts', - }; - - const result = await getAccountPosts(query); - - const _fetchedPermlinks = result.map(post => post.permlink); - console.log('permlinks fetched', _fetchedPermlinks); - - const _permlinksBucket = refresh ? _fetchedPermlinks : [...permlinksBucket, ..._fetchedPermlinks]; - setPermlinksBucket(_permlinksBucket); - - if(refresh){ - //precautionary delay of 200ms to let state update before concluding promise, - //it is effective for waves refresh routine. - await delay(200) - } - } catch(err){ - console.warn("failed to fetch waves permlinks"); - } - - setIsLoading(false) - } - - const _fetchWaves = async (pagePermlink: string) => { - console.log('fetching waves from:', host, pagePermlink); - const response = await getComments(host, pagePermlink); - response.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); - console.log('new waves fetched', response); - return response || []; - }; - - - // query initialization - const wavesQueries = useQueries({ - queries: activePermlinks.map((pagePermlink) => ({ - queryKey: [QUERIES.WAVES.GET, host, pagePermlink], - queryFn: () => _fetchWaves(pagePermlink), - initialData: [], - })), - }); - - const _refresh = async () => { - setIsRefreshing(true); - setPermlinksBucket([]); - setActivePermlinks([]); - await _fetchPermlinks('', true); - await wavesQueries[0].refetch(); - setIsRefreshing(false); - }; - - const _fetchNextPage = () => { - const lastPage = wavesQueries.lastItem; - - if (!lastPage || lastPage.isFetching) { - return; - } - - const _nextPagePermlink = permlinksBucket[activePermlinks.length]; - - //TODO: find a way to proactively refill active permlinks here. - - if (_nextPagePermlink && !activePermlinks.includes(_nextPagePermlink)) { - activePermlinks.push(_nextPagePermlink); - setActivePermlinks([...activePermlinks]); - } else { - _fetchPermlinks(permlinksBucket.lastItem) - } - }; - - const _dataArrs = wavesQueries.map((query) => query.data); - - return { - data: unionBy(..._dataArrs, 'url'), - isRefreshing, - isLoading: isLoading || wavesQueries.lastItem?.isLoading || wavesQueries.lastItem?.isFetching, - fetchNextPage: _fetchNextPage, - refresh: _refresh, - }; - }; - - - export const fetchLatestWavesContainer = async (host) => { - const query: any = { - account: host, - start_author: '', - start_permlink: '', - limit: 1, - observer: '', - sort: 'posts', - }; - - const result = await getAccountPosts(query); - - const _latestPost = result[0]; - console.log('lates waves post', host, _latestPost); - - if(!_latestPost){ - throw new Error("Lates waves container could be not fetched"); - } - - return _latestPost; - } - - - - - // export const useNotificationReadMutation = () => { - // const intl = useIntl(); - // const dispatch = useAppDispatch(); - // const queryClient = useQueryClient(); - - // const currentAccount = useAppSelector((state) => state.account.currentAccount); - // const pinCode = useAppSelector((state) => state.application.pin); - - // // id is options, if no id is provided program marks all notifications as read; - // const _mutationFn = async (id?: string) => { - // try { - // const response = await markNotifications(id); - // console.log('Ecency notifications marked as Read', response); - // if (!id) { - // await markHiveNotifications(currentAccount, pinCode); - // console.log('Hive notifications marked as Read'); - // } - - // return response.unread || 0; - // } catch (err) { - // bugsnapInstance.notify(err); - // } - // }; - - // const _options: UseMutationOptions = { - // onMutate: async (notificationId) => { - // // TODO: find a way to optimise mutations by avoiding too many loops - // console.log('on mutate data', notificationId); - - // // update query data - // const queriesData: [QueryKey, any[] | undefined][] = queryClient.getQueriesData([ - // QUERIES.NOTIFICATIONS.GET, - // ]); - // console.log('query data', queriesData); - - // queriesData.forEach(([queryKey, data]) => { - // if (data) { - // console.log('mutating data', queryKey); - // const _mutatedData = data.map((item) => ({ - // ...item, - // read: !notificationId || notificationId === item.id ? 1 : item.read, - // })); - // queryClient.setQueryData(queryKey, _mutatedData); - // } - // }); - // }, - - // onSuccess: async (unreadCount, notificationId) => { - // console.log('on success data', unreadCount); - - // dispatch(updateUnreadActivityCount(unreadCount)); - // if (!notificationId) { - // queryClient.invalidateQueries([QUERIES.NOTIFICATIONS.GET]); - // } - // }, - // onError: () => { - // dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); - // }, - // }; - - // return useMutation(_mutationFn, _options); - // }; - \ No newline at end of file diff --git a/src/screens/post/screen/postScreen.tsx b/src/screens/post/screen/postScreen.tsx index ac45e25578..2974ad302a 100644 --- a/src/screens/post/screen/postScreen.tsx +++ b/src/screens/post/screen/postScreen.tsx @@ -22,7 +22,7 @@ const PostScreen = ({ route }) => { const getPostQuery = postQueries.useGetPostQuery(author, permlink, params.content); const getParentPostQuery = postQueries.useGetPostQuery(); - const isWavePost = useMemo(() => getPostQuery.data?.parent_author === 'ecency.waves', [getPostQuery.data]) //TODO: implement a better generic way to avoid parent fetching for waves + const isWavePost = useMemo(() => getPostQuery.data?.parent_author === 'demo.com', [getPostQuery.data]) //TODO: implement a better generic way to avoid parent fetching for waves useEffect(() => { return () => { diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index aca77eb545..dcbeb6c1f1 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -2,12 +2,12 @@ import React from 'react'; import { ActivityIndicator, RefreshControl, View } from 'react-native'; import { Comments, EmptyScreen, Header } from '../../../components'; import styles from '../styles/wavesScreen.styles'; -import { useWavesQuery } from '../../../providers/queries/wavesQueries/wavesQueries'; +import { wavesQueries } from '../../../providers/queries'; const WavesScreen = () => { - const wavesQuery = useWavesQuery('ecency.waves'); + const wavesQuery = wavesQueries.useWavesQuery('demo.com'); const _fetchData = ({refresh}:{refresh?:boolean}) => { if(refresh){ From 79e281dae2ad94e8664ce3068200b9847d65d4f9 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Fri, 25 Aug 2023 21:06:43 +0500 Subject: [PATCH 18/40] attempt to better handle cache --- .../quickReplyModalContent.tsx | 2 +- .../quickReplyModal/usePostSubmitter.ts | 2 +- .../queries/postQueries/wavesQueries.ts | 26 ++++++++--- src/screens/post/screen/postScreen.tsx | 2 +- src/screens/waves/screen/wavesScreen.tsx | 6 ++- src/utils/parsePurchaseUrl.ts | 2 +- src/utils/postParser.tsx | 43 +++++++++++++++++++ 7 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index 424abfa30e..f7bd5b4f95 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -66,7 +66,7 @@ export const QuickReplyModalContent = forwardRef( const draftId = mode === 'wave' - ? `${currentAccount.name}/demo.com` //TODO: update author based on selected host + ? `${currentAccount.name}/ecency.waves` //TODO: update author based on selected host : `${currentAccount.name}/${parentAuthor}/${parentPermlink}`; // different draftId for each user acount useImperativeHandle(ref, () => ({ diff --git a/src/components/quickReplyModal/usePostSubmitter.ts b/src/components/quickReplyModal/usePostSubmitter.ts index 0a43184f89..f24ffbf43b 100644 --- a/src/components/quickReplyModal/usePostSubmitter.ts +++ b/src/components/quickReplyModal/usePostSubmitter.ts @@ -132,7 +132,7 @@ export const usePostSubmitter = () => { const _submitWave = async (body:string) => { try { - const _wavesHost = 'demo.com' //TODO: make waves host selection dynamic + const _wavesHost = 'ecency.waves' //TODO: make waves host selection dynamic const latestWavesPost = await wavesQueries.fetchLatestWavesContainer(_wavesHost); const _cacheCommentData = await _submitReply(body, latestWavesPost) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 7d1c0b9b9d..3087af15e3 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -7,17 +7,23 @@ import { import { useEffect, useState } from 'react'; import { unionBy } from 'lodash'; -import { getComments } from '../../hive/dhive'; +import { getDiscussionCollection } from '../../hive/dhive'; import { getAccountPosts } from '../../hive/dhive'; import QUERIES from '../queryKeys'; import { delay } from '../../../utils/editor'; +import { injectPostCache, mapDiscussionToThreads } from '../../../utils/postParser'; import { useAppSelector } from '../../../hooks'; export const useWavesQuery = (host: string) => { + + const cachedComments = useAppSelector(state=>state.cache.commentsCollection); + const cachedVotes = useAppSelector(state=>state.cache.votesCollection); + const lastCacheUpdate = useAppSelector(state=>state.cache.lastUpdate); + const [isRefreshing, setIsRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); const [activePermlinks, setActivePermlinks] = useState([]); @@ -52,6 +58,7 @@ export const useWavesQuery = (host: string) => { + const _fetchPermlinks = async (startPermlink = '', refresh = false) => { setIsLoading(true); try { @@ -86,10 +93,19 @@ export const useWavesQuery = (host: string) => { const _fetchWaves = async (pagePermlink: string) => { console.log('fetching waves from:', host, pagePermlink); - const response = await getComments(host, pagePermlink); - response.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); - console.log('new waves fetched', response); - return response || []; + const response = await getDiscussionCollection(host, pagePermlink); + + //TODO: inject comment cache here... + const _cResponse = injectPostCache(response, cachedComments, cachedVotes, lastCacheUpdate); + // const _threadedComments = await mapDiscussionToThreads(_cResponse, host, pagePermlink, 1); + + if(!_cResponse){ + throw new Error("Failed to parse waves"); + } + + // _threadedComments.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); + console.log('new waves fetched', _cResponse); + return _cResponse || {}; }; diff --git a/src/screens/post/screen/postScreen.tsx b/src/screens/post/screen/postScreen.tsx index 2974ad302a..ac45e25578 100644 --- a/src/screens/post/screen/postScreen.tsx +++ b/src/screens/post/screen/postScreen.tsx @@ -22,7 +22,7 @@ const PostScreen = ({ route }) => { const getPostQuery = postQueries.useGetPostQuery(author, permlink, params.content); const getParentPostQuery = postQueries.useGetPostQuery(); - const isWavePost = useMemo(() => getPostQuery.data?.parent_author === 'demo.com', [getPostQuery.data]) //TODO: implement a better generic way to avoid parent fetching for waves + const isWavePost = useMemo(() => getPostQuery.data?.parent_author === 'ecency.waves', [getPostQuery.data]) //TODO: implement a better generic way to avoid parent fetching for waves useEffect(() => { return () => { diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index dcbeb6c1f1..9a6698be31 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -3,11 +3,12 @@ import { ActivityIndicator, RefreshControl, View } from 'react-native'; import { Comments, EmptyScreen, Header } from '../../../components'; import styles from '../styles/wavesScreen.styles'; import { wavesQueries } from '../../../providers/queries'; +import { useInjectVotesCache } from '../../../providers/queries/postQueries/postQueries'; const WavesScreen = () => { - const wavesQuery = wavesQueries.useWavesQuery('demo.com'); + const wavesQuery = wavesQueries.useWavesQuery('ecency.waves'); const _fetchData = ({refresh}:{refresh?:boolean}) => { if(refresh){ @@ -18,7 +19,8 @@ const WavesScreen = () => { } - const _data = wavesQuery.data.slice(); + // const _data = useInjectVotesCache(wavesQuery.data.slice()); + const _data = wavesQuery.data; const _renderListFooter = () => wavesQuery.isLoading && !wavesQuery.isRefreshing ? : ; diff --git a/src/utils/parsePurchaseUrl.ts b/src/utils/parsePurchaseUrl.ts index 9350edde7d..9875a1b087 100644 --- a/src/utils/parsePurchaseUrl.ts +++ b/src/utils/parsePurchaseUrl.ts @@ -1,5 +1,5 @@ /** - * extracts purchase information from deep link url i-e https://ecency.com/purchase?type=boost&username=demo.com + * extracts purchase information from deep link url i-e https://ecency.com/purchase?type=boost&username=ecency.waves * */ diff --git a/src/utils/postParser.tsx b/src/utils/postParser.tsx index ee1ec0ad34..c438d68763 100644 --- a/src/utils/postParser.tsx +++ b/src/utils/postParser.tsx @@ -110,6 +110,8 @@ export const parseDiscussionCollection = async (commentsMap: { [key: string]: an return commentsMap; }; + +//TODO: discard/deprecate method after porting getComments in commentsContainer to getDiscussionCollection export const parseCommentThreads = async (commentsMap: any, author: string, permlink: string) => { const MAX_THREAD_LEVEL = 3; const comments = []; @@ -151,6 +153,47 @@ export const parseCommentThreads = async (commentsMap: any, author: string, perm return comments; }; + +export const mapDiscussionToThreads = async (commentsMap: any, author: string, permlink: string, maxLevel:number = 3) => { + const comments = []; + + if (!commentsMap) { + return null; + } + + // traverse map to curate threads + const parseReplies = (commentsMap: any, replies: any[], level: number) => { + if (replies && replies.length > 0 && maxLevel > level) { + return replies.map((pathKey) => { + const comment = commentsMap[pathKey]; + if (comment) { + comment.replies = parseReplies(commentsMap, comment.replies, level + 1); + return comment; + } else { + return null; + } + }); + } + return []; + }; + + for (const key in commentsMap) { + if (commentsMap.hasOwnProperty(key)) { + const comment = commentsMap[key]; + + // prcoess first level comment + if (comment && comment.parent_author === author && comment.parent_permlink === permlink) { + + comment.replies = parseReplies(commentsMap, comment.replies, 1); + comments.push(comment); + } + } + } + + return comments; +}; + + export const parseComments = (comments: any[]) => { if (!comments) { return null; From e2d288260395d2cb9f605de6667526904dad164a Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Fri, 25 Aug 2023 21:07:35 +0500 Subject: [PATCH 19/40] added notes to update cache strategy --- .../queries/postQueries/wavesQueries.ts | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 3087af15e3..08959e6fba 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -4,7 +4,7 @@ import { useMutation, useQueries, useQueryClient, } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { unionBy } from 'lodash'; import { getDiscussionCollection } from '../../hive/dhive'; @@ -12,7 +12,7 @@ import { getDiscussionCollection } from '../../hive/dhive'; import { getAccountPosts } from '../../hive/dhive'; import QUERIES from '../queryKeys'; import { delay } from '../../../utils/editor'; -import { injectPostCache, mapDiscussionToThreads } from '../../../utils/postParser'; +import { injectPostCache, injectVoteCache, mapDiscussionToThreads } from '../../../utils/postParser'; import { useAppSelector } from '../../../hooks'; @@ -57,6 +57,21 @@ export const useWavesQuery = (host: string) => { }, [permlinksBucket]) + useEffect(()=>{ + //TODO: checkd cache is recently updated and take post patch + + //using post path get index of query key where that post exists + + //get query data, update query data by finding post and injecting cache + + //set query data + + //inject cache in that particular post + // const post = injectVoteCache(post, cachedVotes) + + //update state and data + }, [cachedComments, cachedVotes]) + const _fetchPermlinks = async (startPermlink = '', refresh = false) => { @@ -97,15 +112,15 @@ export const useWavesQuery = (host: string) => { //TODO: inject comment cache here... const _cResponse = injectPostCache(response, cachedComments, cachedVotes, lastCacheUpdate); - // const _threadedComments = await mapDiscussionToThreads(_cResponse, host, pagePermlink, 1); + const _threadedComments = await mapDiscussionToThreads(_cResponse, host, pagePermlink, 1); - if(!_cResponse){ + if(!_threadedComments){ throw new Error("Failed to parse waves"); } - // _threadedComments.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); - console.log('new waves fetched', _cResponse); - return _cResponse || {}; + _threadedComments.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); + console.log('new waves fetched', _threadedComments); + return _threadedComments || {}; }; From 1fb684aebeb4c84dce5617b677173e22629f7454 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 27 Aug 2023 15:41:47 +0500 Subject: [PATCH 20/40] enabled post actions for waves --- .../comments/container/commentsContainer.tsx | 2 ++ src/components/comments/view/commentsView.tsx | 28 +++++++++++-------- src/screens/waves/screen/wavesScreen.tsx | 17 +++++++++-- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/components/comments/container/commentsContainer.tsx b/src/components/comments/container/commentsContainer.tsx index bb26ef3f8b..01d6265d02 100644 --- a/src/components/comments/container/commentsContainer.tsx +++ b/src/components/comments/container/commentsContainer.tsx @@ -38,6 +38,7 @@ const CommentsContainer = ({ isLoggedIn, commentNumber, mainAuthor, + handleOnOptionsPress, selectedPermlink, isHideImage, isShowSubComments, @@ -296,6 +297,7 @@ const CommentsContainer = ({ fetchPost={fetchPost} handleDeleteComment={_handleDeleteComment} handleOnPressCommentMenu={_handleOnPressCommentMenu} + handleOnOptionsPress={handleOnOptionsPress} isOwnProfile={isOwnProfile} isHideImage={isHideImage} handleOnVotersPress={_handleOnVotersPress} diff --git a/src/components/comments/view/commentsView.tsx b/src/components/comments/view/commentsView.tsx index 70bd8ee265..78d175afc3 100644 --- a/src/components/comments/view/commentsView.tsx +++ b/src/components/comments/view/commentsView.tsx @@ -31,6 +31,7 @@ const CommentsView = ({ isLoggedIn, isShowSubComments, mainAuthor, + handleOnOptionsPress, marginLeft, showAllComments, hideManyCommentsButton, @@ -48,7 +49,10 @@ const CommentsView = ({ const postInteractionRef = useRef(null); const _openCommentMenu = (item) => { - if (commentMenu.current) { + + if (handleOnOptionsPress) { + handleOnOptionsPress(item); + } else if (commentMenu.current) { setSelectedComment(item); commentMenu.current.show(); } @@ -139,9 +143,9 @@ const CommentsView = ({ const styleOerride = commentNumber > 1 ? { - backgroundColor: EStyleSheet.value('$primaryLightBackground'), - marginTop: 8, - } + backgroundColor: EStyleSheet.value('$primaryLightBackground'), + marginTop: 8, + } : null; const _renderEmptyContent = () => { @@ -171,13 +175,15 @@ const CommentsView = ({ overScrollMode="never" {...flatListProps} /> - + {!handleOnOptionsPress && ( + + )} diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index 9a6698be31..5a099b3a09 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -1,13 +1,14 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { ActivityIndicator, RefreshControl, View } from 'react-native'; -import { Comments, EmptyScreen, Header } from '../../../components'; +import { Comments, EmptyScreen, Header, PostOptionsModal } from '../../../components'; import styles from '../styles/wavesScreen.styles'; import { wavesQueries } from '../../../providers/queries'; -import { useInjectVotesCache } from '../../../providers/queries/postQueries/postQueries'; const WavesScreen = () => { + const postOptionsModalRef = useRef(null); + const wavesQuery = wavesQueries.useWavesQuery('ecency.waves'); const _fetchData = ({refresh}:{refresh?:boolean}) => { @@ -19,6 +20,13 @@ const WavesScreen = () => { } + const _handleOnOptionsPress = (content:any) => { + if(postOptionsModalRef.current){ + postOptionsModalRef.current.show(content); + } + } + + // const _data = useInjectVotesCache(wavesQuery.data.slice()); const _data = wavesQuery.data; @@ -34,6 +42,7 @@ const WavesScreen = () => { {}, @@ -50,6 +59,8 @@ const WavesScreen = () => { /> + + ); }; From ffaf90f522618ad1405aa5db8f98c9453403081e Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 27 Aug 2023 17:00:48 +0500 Subject: [PATCH 21/40] safely unlinked profile tab --- .../children/quickProfileContent.tsx | 154 +++++++++--------- .../container/searchModalContainer.js | 2 +- .../userAvatar/view/userAvatarView.tsx | 2 +- src/constants/routeNames.js | 1 - src/constants/sideMenuItems.js | 2 +- src/containers/profileContainer.js | 23 +-- src/navigation/botomTabNavigator.tsx | 7 - .../application/hook/useInitApplication.tsx | 2 +- .../container/peopleResultsContainer.js | 2 +- 9 files changed, 84 insertions(+), 111 deletions(-) diff --git a/src/components/organisms/quickProfileModal/children/quickProfileContent.tsx b/src/components/organisms/quickProfileModal/children/quickProfileContent.tsx index e4fd16078f..e6ec0b5e63 100644 --- a/src/components/organisms/quickProfileModal/children/quickProfileContent.tsx +++ b/src/components/organisms/quickProfileModal/children/quickProfileContent.tsx @@ -18,20 +18,20 @@ import bugsnapInstance from '../../../../config/bugsnag' import RootNavigation from '../../../../navigation/rootNavigation' interface QuickProfileContentProps { - username:string, - onClose:()=>void; + username: string, + onClose: () => void; } export const QuickProfileContent = ({ username, onClose -}:QuickProfileContentProps) => { +}: QuickProfileContentProps) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const currentAccount = useAppSelector((state)=>state.account.currentAccount); - const pinCode = useAppSelector((state)=>state.application.pin); - const isLoggedIn = useAppSelector((state)=>state.application.isLoggedIn); + const currentAccount = useAppSelector((state) => state.account.currentAccount); + const pinCode = useAppSelector((state) => state.application.pin); + const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn); const [isLoading, setIsLoading] = useState(false); const [user, setUser] = useState(null); @@ -45,7 +45,7 @@ export const QuickProfileContent = ({ const isProfileLoaded = (user && follows) ? true : false; useEffect(() => { - if(username) { + if (username) { _fetchUser(); _fetchExtraUserData(); } else { @@ -58,8 +58,8 @@ export const QuickProfileContent = ({ const _fetchUser = async () => { setIsLoading(true); try { - const _user = await getUser(username, isOwnProfile); - setUser(_user) + const _user = await getUser(username, isOwnProfile); + setUser(_user) } catch (error) { setIsLoading(false); } @@ -73,35 +73,35 @@ export const QuickProfileContent = ({ let _isMuted; let _isFavourite; let follows; - + if (!isOwnProfile) { const res = await getRelationship(currentAccountName, username); _isFollowing = res && res.follows; _isMuted = res && res.ignores; _isFavourite = await checkFavorite(username); } - + try { follows = await getFollows(username); } catch (err) { follows = null; } - - + + setFollows(follows); setIsFollowing(_isFollowing); setIsMuted(_isMuted) setIsFavourite(_isFavourite) setIsLoading(false); - + } } catch (error) { console.warn('Failed to fetch complete profile data', error); Alert.alert( - intl.formatMessage({ - id: 'alert.fail', - }), - error.message || error.toString(), + intl.formatMessage({ + id: 'alert.fail', + }), + error.message || error.toString(), ); setIsLoading(false); } @@ -109,90 +109,88 @@ export const QuickProfileContent = ({ const _onFollowPress = async () => { - try{ + try { const follower = currentAccountName const following = username; - + setIsLoading(true); await followUser(currentAccount, pinCode, { - follower, - following, + follower, + following, }) - + setIsLoading(false); setIsFollowing(true) dispatch( toastNotification( - intl.formatMessage({ - id: isFollowing ? 'alert.success_unfollow' : 'alert.success_follow', - }), + intl.formatMessage({ + id: isFollowing ? 'alert.success_unfollow' : 'alert.success_follow', + }), ), ); } - catch(err){ + catch (err) { setIsLoading(false); console.warn("Failed to follow user", err) bugsnapInstance.notify(err); - Alert.alert(intl.formatMessage({id:'alert.fail'}), err.message) + Alert.alert(intl.formatMessage({ id: 'alert.fail' }), err.message) } } const _onFavouritePress = async () => { - try{ + try { setIsLoading(true); let favoriteAction; - + if (isFavourite) { - favoriteAction = deleteFavorite; + favoriteAction = deleteFavorite; } else { - favoriteAction = addFavorite; + favoriteAction = addFavorite; } - + await favoriteAction(username) - + dispatch( toastNotification( - intl.formatMessage({ - id: isFavourite ? 'alert.success_unfavorite' : 'alert.success_favorite', - }), + intl.formatMessage({ + id: isFavourite ? 'alert.success_unfavorite' : 'alert.success_favorite', + }), ), ); setIsFavourite(!isFavourite); setIsLoading(false); } - catch(error){ + catch (error) { console.warn('Failed to perform favorite action'); setIsLoading(false); Alert.alert( - intl.formatMessage({ - id: 'alert.fail', - }), - error.message || error.toString(), + intl.formatMessage({ + id: 'alert.fail', + }), + error.message || error.toString(), ); } } - + //UI CALLBACKS const _openFullProfile = () => { let params = { - username, - reputation: user ? user.reputation : null + username, + reputation: user ? user.reputation : null }; - - if (isOwnProfile) { - RootNavigation.navigate(ROUTES.TABBAR.PROFILE); - } else { - RootNavigation.navigate({ + + + RootNavigation.navigate({ name: ROUTES.SCREENS.PROFILE, params, key: username, - }); - } - if(onClose){ + }); + + if (onClose) { onClose(); } } @@ -208,58 +206,58 @@ export const QuickProfileContent = ({ let _createdData = null; if (isProfileLoaded) { - _votingPower = getVotingPower(user).toFixed(1); - _resourceCredits = getRcPower(user).toFixed(0); - _postCount = user.post_count || 0; - _about = user.about?.profile?.about || ''; - _reputation = parseReputation(user.reputation); - _createdData = getTimeFromNowNative(user.created) - - if(follows){ - _followerCount = follows.follower_count || 0; - _followingCount = follows.following_count || 0 - } + _votingPower = getVotingPower(user).toFixed(1); + _resourceCredits = getRcPower(user).toFixed(0); + _postCount = user.post_count || 0; + _about = user.about?.profile?.about || ''; + _reputation = parseReputation(user.reputation); + _createdData = getTimeFromNowNative(user.created) + + if (follows) { + _followerCount = follows.follower_count || 0; + _followingCount = follows.following_count || 0 + } } - + const statsData1 = [ - {label:intl.formatMessage({id:'profile.follower'}), value:_followerCount}, - {label:intl.formatMessage({id:'profile.following'}), value:_followingCount}, - {label:intl.formatMessage({id:'profile.post'}), value:_postCount}, + { label: intl.formatMessage({ id: 'profile.follower' }), value: _followerCount }, + { label: intl.formatMessage({ id: 'profile.following' }), value: _followingCount }, + { label: intl.formatMessage({ id: 'profile.post' }), value: _postCount }, ] as StatsData[] const statsData2 = [ - {label:intl.formatMessage({id:'profile.resource_credits'}), value:_resourceCredits, suffix:'%'}, - {label:intl.formatMessage({id:'profile.reputation'}), value:_reputation}, + { label: intl.formatMessage({ id: 'profile.resource_credits' }), value: _resourceCredits, suffix: '%' }, + { label: intl.formatMessage({ id: 'profile.reputation' }), value: _reputation }, ] as StatsData[] return ( - - - {isLoggedIn && ( - )} - + ) }; diff --git a/src/components/searchModal/container/searchModalContainer.js b/src/components/searchModal/container/searchModalContainer.js index 612d55ce7f..31e6b10495 100644 --- a/src/components/searchModal/container/searchModalContainer.js +++ b/src/components/searchModal/container/searchModalContainer.js @@ -172,7 +172,7 @@ const SearchModalContainer = ({ isConnected, handleOnClose, username, isOpen, pl switch (type) { case 'user': - name = get(item, 'text') === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE; + name = ROUTES.SCREENS.PROFILE; params = { username: get(item, 'text'), }; diff --git a/src/components/userAvatar/view/userAvatarView.tsx b/src/components/userAvatar/view/userAvatarView.tsx index 429e60497d..618d8a8eda 100644 --- a/src/components/userAvatar/view/userAvatarView.tsx +++ b/src/components/userAvatar/view/userAvatarView.tsx @@ -45,7 +45,7 @@ const UserAvatarView = ({ // Component Functions const _handleOnAvatarPress = (username: string) => { - const name = curUsername === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE; + const name = ROUTES.SCREENS.PROFILE; RootNavigation.navigate(name, { username }); }; diff --git a/src/constants/routeNames.js b/src/constants/routeNames.js index c80fc340d3..062e9273bb 100644 --- a/src/constants/routeNames.js +++ b/src/constants/routeNames.js @@ -52,7 +52,6 @@ const ROUTES = { NOTIFICATION: `Notification${TABBAR_SUFFIX}`, WALLET: `Wallet${TABBAR_SUFFIX}`, POST_BUTTON: `PostButton${TABBAR_SUFFIX}`, - PROFILE: `Profile${TABBAR_SUFFIX}`, WAVES: `Waves${TABBAR_SUFFIX}`, }, STACK: { diff --git a/src/constants/sideMenuItems.js b/src/constants/sideMenuItems.js index fb1742bc59..f2781b01bf 100644 --- a/src/constants/sideMenuItems.js +++ b/src/constants/sideMenuItems.js @@ -3,7 +3,7 @@ import { default as ROUTES } from './routeNames'; const authMenuItems = [ { name: 'Profile', - route: ROUTES.TABBAR.PROFILE, + route: ROUTES.SCREENS.PROFILE, icon: 'user', id: 'profile', }, diff --git a/src/containers/profileContainer.js b/src/containers/profileContainer.js index 0fae2a8eda..8e9e896785 100644 --- a/src/containers/profileContainer.js +++ b/src/containers/profileContainer.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars */ import React, { Component } from 'react'; import { connect } from 'react-redux'; -import { get, has, unionBy, update } from 'lodash'; +import { get, unionBy } from 'lodash'; import { Alert } from 'react-native'; import { injectIntl } from 'react-intl'; @@ -12,7 +12,6 @@ import { unfollowUser, ignoreUser, getFollows, - getRepliesByLastUpdate, getUser, getRelationship, getAccountPosts, @@ -53,9 +52,9 @@ class ProfileContainer extends Component { user: null, quickProfile: { reputation: get(props, 'route.params.reputation', ''), - name: get(props, 'route.params.username', ''), + name: isOwnProfile ? currentAccountUsername : username }, - reverseHeader: !!username, + reverseHeader: true, deepLinkFilter: get(props, 'route.params.deepLinkFilter'), }; } @@ -479,28 +478,12 @@ class ProfileContainer extends Component { } const { isLoggedIn, navigation } = this.props; - const { isOwnProfile } = this.state; if (isLoggedIn && !nextProps.isLoggedIn) { navigation.navigate(ROUTES.SCREENS.LOGIN); return; } - if (isOwnProfile) { - const { user } = this.state; - const { activeBottomTab, currentAccount } = this.props; - - const currentUsername = - get(currentAccount, 'name') !== get(nextProps, 'currentAccount.name') && - get(nextProps, 'currentAccount.name'); - const isActiveTabChanged = - activeBottomTab !== get(nextProps, 'activeBottomTab') && - get(nextProps, 'activeBottomTab') === ROUTES.TABBAR.PROFILE; - - if ((isActiveTabChanged && user) || currentUsername) { - this._loadProfile(get(nextProps, 'currentAccount.name')); - } - } } render() { diff --git a/src/navigation/botomTabNavigator.tsx b/src/navigation/botomTabNavigator.tsx index 897068269d..625cc93269 100644 --- a/src/navigation/botomTabNavigator.tsx +++ b/src/navigation/botomTabNavigator.tsx @@ -61,13 +61,6 @@ export const BottomTabNavigator = () => { }} /> - ); }; diff --git a/src/screens/application/hook/useInitApplication.tsx b/src/screens/application/hook/useInitApplication.tsx index f9d5145f13..c0bb7ff10b 100644 --- a/src/screens/application/hook/useInitApplication.tsx +++ b/src/screens/application/hook/useInitApplication.tsx @@ -221,7 +221,7 @@ export const useInitApplication = () => { break; case 'transfer': - routeName = ROUTES.TABBAR.PROFILE; + routeName = ROUTES.TABBAR.WALLET; params = { activePage: 2, }; diff --git a/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js b/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js index 9b30b4d4a5..02b476b87c 100644 --- a/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js +++ b/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js @@ -33,7 +33,7 @@ const PeopleResultsContainer = ({ children, searchValue, username }) => { const _handleOnPress = (item) => { navigation.navigate({ - name: item.name === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE, + name: ROUTES.SCREENS.PROFILE, params: { username: item.name, }, From be9280d674f64bec4c203a6a6431dac148601de2 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 27 Aug 2023 18:17:17 +0500 Subject: [PATCH 22/40] media insertion in progress --- .../quickReplyModalContent.tsx | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index f7bd5b4f95..4561d735a3 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -13,13 +13,14 @@ import { TouchableOpacity, Keyboard, Platform, + Alert, } from 'react-native'; import { useIntl } from 'react-intl'; import { useSelector, useDispatch } from 'react-redux'; import { get, debounce } from 'lodash'; import { postBodySummary } from '@ecency/render-helper'; import styles from './quickReplyModalStyles'; -import { IconButton, MainButton, TextButton, TextInput, UserAvatar } from '..'; +import { IconButton, MainButton, TextButton, TextInput, UploadsGalleryModal, UserAvatar } from '..'; import { delay } from '../../utils/editor'; import { deleteDraftCacheEntry, @@ -58,6 +59,7 @@ export const QuickReplyModalContent = forwardRef( const draftsCollection = useSelector((state: RootState) => state.cache.draftsCollection); const [commentValue, setCommentValue] = useState(''); + const [mediaUrl, setMediaUrl] = useState(''); const headerText = selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS)); @@ -133,6 +135,8 @@ export const QuickReplyModalContent = forwardRef( let _isSuccess = false; + //TODO: insert image url at the of comment wiht markdown format + switch (mode) { case 'comment': _isSuccess = await postSubmitter.submitReply(commentValue, selectedPost); @@ -178,6 +182,10 @@ export const QuickReplyModalContent = forwardRef( } }; + const _handleMediaBtn = () => { + Alert.alert("show media selection modal"); + } + const _deboucedCacheUpdate = useCallback(debounce(_addQuickCommentIntoCache, 500), []); const _onChangeText = (value) => { @@ -213,6 +221,14 @@ export const QuickReplyModalContent = forwardRef( const _renderExpandBtn = () => ( + {mode !== 'wave' && ( + + { + //TODO: link galley + // + } + {_renderExpandBtn()} {_renderReplyBtn()} From 1e16fedfdc9702077c9a64ef5618cd16fe3c863e Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 27 Aug 2023 21:03:06 +0500 Subject: [PATCH 23/40] enabled media attachment from pre uploaded images --- .../quickReplyModalContent.tsx | 61 +++++++++++++------ 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index 4561d735a3..ad6a5661bf 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -33,6 +33,7 @@ import { RootState } from '../../redux/store/store'; import { postQueries } from '../../providers/queries'; import { usePostSubmitter } from './usePostSubmitter'; +import { MediaInsertData, MediaInsertStatus } from '../uploadsGalleryModal/container/uploadsGalleryModal'; export interface QuickReplyModalContentProps { mode: 'comment' | 'wave' | 'post', @@ -49,6 +50,10 @@ export const QuickReplyModalContent = forwardRef( }: QuickReplyModalContentProps, ref) => { const intl = useIntl(); const dispatch = useDispatch(); + + const uploadsGalleryModalRef = useRef(null); + + const postsCachePrimer = postQueries.usePostsCachePrimer(); const postSubmitter = usePostSubmitter(); @@ -59,7 +64,8 @@ export const QuickReplyModalContent = forwardRef( const draftsCollection = useSelector((state: RootState) => state.cache.draftsCollection); const [commentValue, setCommentValue] = useState(''); - const [mediaUrl, setMediaUrl] = useState(''); + const [mediaUrls, setMediaUrls] = useState([]); + const [isUploading, setIsUploading] = useState(false); const headerText = selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS)); @@ -134,15 +140,14 @@ export const QuickReplyModalContent = forwardRef( const _submitPost = async () => { let _isSuccess = false; - - //TODO: insert image url at the of comment wiht markdown format + let _body = mediaUrls.length > 0 ? commentValue + `\n\n ![Wave Media](${mediaUrls[0]})` : commentValue; switch (mode) { case 'comment': - _isSuccess = await postSubmitter.submitReply(commentValue, selectedPost); + _isSuccess = await postSubmitter.submitReply(_body, selectedPost); break;; case 'wave': - _isSuccess = await postSubmitter.submitWave(commentValue); + _isSuccess = await postSubmitter.submitWave(_body); break; default: throw new Error("mode needs implementing") @@ -165,6 +170,20 @@ export const QuickReplyModalContent = forwardRef( + const _handleMediaInsert = (data:MediaInsertData[]) => { + const _insertUrls:string[] = [] + data.forEach((item)=>{ + if(item.url && item.status === MediaInsertStatus.READY){ + _insertUrls.push(item.url) + } + }) + + setMediaUrls(_insertUrls); + uploadsGalleryModalRef.current?.toggleModal(false); + + } + + const _handleExpandBtn = async () => { if (selectedPost) { @@ -183,7 +202,9 @@ export const QuickReplyModalContent = forwardRef( }; const _handleMediaBtn = () => { - Alert.alert("show media selection modal"); + if(uploadsGalleryModalRef.current){ + uploadsGalleryModalRef.current.toggleModal(true) + } } const _deboucedCacheUpdate = useCallback(debounce(_addQuickCommentIntoCache, 500), []); @@ -262,6 +283,7 @@ export const QuickReplyModalContent = forwardRef( text={intl.formatMessage({ id: _titleId, })} + isDisable={isUploading} isLoading={postSubmitter.isSending} /> @@ -294,20 +316,19 @@ export const QuickReplyModalContent = forwardRef( /> - { - //TODO: link galley - // - } + + {}} + handleMediaInsert={_handleMediaInsert} + setIsUploading={setIsUploading} + /> + {_renderExpandBtn()} From 8e9254022e0d174b96ea371bfbe579a7f52d2b9c Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 27 Aug 2023 23:19:50 +0500 Subject: [PATCH 24/40] enabled selected media preview --- .../quickReplyModalContent.tsx | 99 +++++++++++++------ .../quickReplyModal/quickReplyModalStyles.ts | 16 +++ 2 files changed, 87 insertions(+), 28 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index ad6a5661bf..f9a472bd29 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -5,6 +5,7 @@ import React, { useImperativeHandle, forwardRef, useCallback, + Fragment, } from 'react'; import EStyleSheet from 'react-native-extended-stylesheet'; import { @@ -20,7 +21,7 @@ import { useSelector, useDispatch } from 'react-redux'; import { get, debounce } from 'lodash'; import { postBodySummary } from '@ecency/render-helper'; import styles from './quickReplyModalStyles'; -import { IconButton, MainButton, TextButton, TextInput, UploadsGalleryModal, UserAvatar } from '..'; +import { Icon, IconButton, MainButton, TextButton, TextInput, UploadsGalleryModal, UserAvatar } from '..'; import { delay } from '../../utils/editor'; import { deleteDraftCacheEntry, @@ -34,6 +35,7 @@ import { RootState } from '../../redux/store/store'; import { postQueries } from '../../providers/queries'; import { usePostSubmitter } from './usePostSubmitter'; import { MediaInsertData, MediaInsertStatus } from '../uploadsGalleryModal/container/uploadsGalleryModal'; +import FastImage from 'react-native-fast-image'; export interface QuickReplyModalContentProps { mode: 'comment' | 'wave' | 'post', @@ -66,6 +68,7 @@ export const QuickReplyModalContent = forwardRef( const [commentValue, setCommentValue] = useState(''); const [mediaUrls, setMediaUrls] = useState([]); const [isUploading, setIsUploading] = useState(false); + const [mediaModalVisible, setMediaModalVisible] = useState(true); const headerText = selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS)); @@ -170,17 +173,18 @@ export const QuickReplyModalContent = forwardRef( - const _handleMediaInsert = (data:MediaInsertData[]) => { - const _insertUrls:string[] = [] - data.forEach((item)=>{ - if(item.url && item.status === MediaInsertStatus.READY){ + const _handleMediaInsert = (data: MediaInsertData[]) => { + const _insertUrls: string[] = [] + data.forEach((item) => { + if (item.url && item.status === MediaInsertStatus.READY) { _insertUrls.push(item.url) } }) setMediaUrls(_insertUrls); + setMediaModalVisible(false); uploadsGalleryModalRef.current?.toggleModal(false); - + } @@ -202,8 +206,9 @@ export const QuickReplyModalContent = forwardRef( }; const _handleMediaBtn = () => { - if(uploadsGalleryModalRef.current){ - uploadsGalleryModalRef.current.toggleModal(true) + if (uploadsGalleryModalRef.current) { + uploadsGalleryModalRef.current.toggleModal(!mediaModalVisible) + setMediaModalVisible(!mediaModalVisible) } } @@ -239,17 +244,64 @@ export const QuickReplyModalContent = forwardRef( ); + const _renderMediaPanel = () => { + const _onPress = () => { + setMediaUrls([]) + } + + const _minusIcon = ( + !isUploading && + + + + ) + + + const _mediaThumb = ( + !mediaModalVisible && mediaUrls.length > 0 && ( + + + {_minusIcon} + + ) + ) + + return + {_mediaThumb} + + { }} + handleMediaInsert={_handleMediaInsert} + setIsUploading={setIsUploading} + /> + + } + const _renderExpandBtn = () => ( + iconStyle={styles.backIcon} + iconType="MaterialsIcons" + name="image-outline" + onPress={_handleMediaBtn} + size={24} + color={EStyleSheet.value('$primaryBlack')} + /> {mode !== 'wave' && ( - - {}} - handleMediaInsert={_handleMediaInsert} - setIsUploading={setIsUploading} - /> - + {_renderMediaPanel()} + + + {_renderExpandBtn()} diff --git a/src/components/quickReplyModal/quickReplyModalStyles.ts b/src/components/quickReplyModal/quickReplyModalStyles.ts index f76c0ec9ee..b925826186 100644 --- a/src/components/quickReplyModal/quickReplyModalStyles.ts +++ b/src/components/quickReplyModal/quickReplyModalStyles.ts @@ -1,3 +1,4 @@ +import { ImageStyle, ViewStyle } from 'react-native'; import EStyleSheet from 'react-native-extended-stylesheet'; export default EStyleSheet.create({ @@ -101,4 +102,19 @@ export default EStyleSheet.create({ fontWeight: 'bold', fontSize: 10, }, + mediaItem: { + marginLeft: 8, + height: 96, + width: 96, + borderRadius: 16, + backgroundColor: '$primaryLightBackground', + } as ImageStyle, + minusContainer: { + position: 'absolute', + top: 8, + left: 14, + backgroundColor: '$primaryRed', + borderRadius: 16, + padding: 2, + } as ViewStyle, }); From d894642cd1c519c7369654bd24ad280764977ea2 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Sun, 27 Aug 2023 23:43:10 +0500 Subject: [PATCH 25/40] showing upload status for new media items --- .../quickReplyModalContent.tsx | 44 +++++++++++++------ .../quickReplyModal/quickReplyModalStyles.ts | 8 ++++ .../children/uploadsGalleryContent.tsx | 2 +- .../container/uploadsGalleryModal.tsx | 4 +- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index f9a472bd29..922b9a6b8b 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -15,6 +15,7 @@ import { Keyboard, Platform, Alert, + ActivityIndicator, } from 'react-native'; import { useIntl } from 'react-intl'; import { useSelector, useDispatch } from 'react-redux'; @@ -175,15 +176,26 @@ export const QuickReplyModalContent = forwardRef( const _handleMediaInsert = (data: MediaInsertData[]) => { const _insertUrls: string[] = [] - data.forEach((item) => { - if (item.url && item.status === MediaInsertStatus.READY) { - _insertUrls.push(item.url) + + const _item = data[0]; + + if(_item){ + switch(_item.status){ + case MediaInsertStatus.READY: + if(_item.url){ + _insertUrls.push(_item.url) + } + break; + case MediaInsertStatus.FAILED: + setIsUploading(false); + break; } - }) + } + - setMediaUrls(_insertUrls); setMediaModalVisible(false); uploadsGalleryModalRef.current?.toggleModal(false); + setMediaUrls(_insertUrls); } @@ -274,17 +286,24 @@ export const QuickReplyModalContent = forwardRef( ) ) + const _uploadingPlaceholder = ( + isUploading && + + + ) + return {_mediaThumb} + {_uploadingPlaceholder} { }} + allowMultiple={false} + hideToolbarExtension={() => { + setMediaModalVisible(false); + }} handleMediaInsert={_handleMediaInsert} setIsUploading={setIsUploading} /> @@ -293,9 +312,8 @@ export const QuickReplyModalContent = forwardRef( const _renderExpandBtn = () => ( - + {mode !== 'wave' && ( )} diff --git a/src/components/quickReplyModal/quickReplyModalStyles.ts b/src/components/quickReplyModal/quickReplyModalStyles.ts index b925826186..3c73636290 100644 --- a/src/components/quickReplyModal/quickReplyModalStyles.ts +++ b/src/components/quickReplyModal/quickReplyModalStyles.ts @@ -108,6 +108,8 @@ export default EStyleSheet.create({ width: 96, borderRadius: 16, backgroundColor: '$primaryLightBackground', + justifyContent:'center', + alignItems:'center' } as ImageStyle, minusContainer: { position: 'absolute', @@ -117,4 +119,10 @@ export default EStyleSheet.create({ borderRadius: 16, padding: 2, } as ViewStyle, + toolbarContainer:{ + flexDirection:'row', + } as ViewStyle, + expandIcon:{ + marginLeft:8 + } as ViewStyle }); diff --git a/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx b/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx index a749e27a0d..c84acad5f2 100644 --- a/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx +++ b/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx @@ -118,7 +118,7 @@ const UploadsGalleryContent = ({ const thumbUrl = proxifyImageSrc(item.url, 600, 500, Platform.OS === 'ios' ? 'match' : 'webp'); let isInsertedTimes = 0; - insertedMediaUrls.forEach((url) => (isInsertedTimes += url === item.url ? 1 : 0)); + insertedMediaUrls?.forEach((url) => (isInsertedTimes += url === item.url ? 1 : 0)); const isToBeDeleted = deleteIds.indexOf(item._id) >= 0; const transformStyle = { transform: isToBeDeleted ? [{ scaleX: 0.7 }, { scaleY: 0.7 }] : [], diff --git a/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx b/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx index ca1cffe270..7ae1422e2c 100644 --- a/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx +++ b/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx @@ -37,6 +37,7 @@ interface UploadsGalleryModalProps { username: string; isEditing: boolean; isPreviewActive: boolean; + allowMultiple?: boolean; hideToolbarExtension: () => void; handleMediaInsert: (data: Array) => void; setIsUploading: (status: boolean) => void; @@ -50,6 +51,7 @@ export const UploadsGalleryModal = forwardRef( username, isEditing, isPreviewActive, + allowMultiple, hideToolbarExtension, handleMediaInsert, setIsUploading, @@ -128,7 +130,7 @@ export const UploadsGalleryModal = forwardRef( const _handleOpenImagePicker = (addToUploads?: boolean) => { ImagePicker.openPicker({ includeBase64: true, - multiple: true, + multiple: allowMultiple || true, mediaType: 'photo', smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'], }) From 049e19d501129fac430e4bd920963eb1687cfb08 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 28 Aug 2023 17:00:06 +0500 Subject: [PATCH 26/40] updated flatlist props --- src/components/comments/view/commentsView.tsx | 4 ++++ src/screens/waves/screen/wavesScreen.tsx | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/comments/view/commentsView.tsx b/src/components/comments/view/commentsView.tsx index 78d175afc3..a7464410fd 100644 --- a/src/components/comments/view/commentsView.tsx +++ b/src/components/comments/view/commentsView.tsx @@ -173,6 +173,10 @@ const CommentsView = ({ ListEmptyComponent={_renderEmptyContent()} ListHeaderComponent={postContentView} overScrollMode="never" + onEndReachedThreshold={1} + maxToRenderPerBatch={7} + initialNumToRender={5} + windowSize={10} {...flatListProps} /> {!handleOnOptionsPress && ( diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index 5a099b3a09..0553c063b0 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -3,6 +3,7 @@ import { ActivityIndicator, RefreshControl, View } from 'react-native'; import { Comments, EmptyScreen, Header, PostOptionsModal } from '../../../components'; import styles from '../styles/wavesScreen.styles'; import { wavesQueries } from '../../../providers/queries'; +import { useAppSelector } from '../../../hooks'; const WavesScreen = () => { @@ -11,6 +12,9 @@ const WavesScreen = () => { const wavesQuery = wavesQueries.useWavesQuery('ecency.waves'); + + const isDarkTheme = useAppSelector(state=>state.application.isDarkTheme) + const _fetchData = ({refresh}:{refresh?:boolean}) => { if(refresh){ wavesQuery.refresh(); @@ -48,11 +52,14 @@ const WavesScreen = () => { onScroll: () => {}, ListEmptyComponent: _renderListEmpty, ListFooterComponent: _renderListFooter, - onEndReachedThreshold: 1, refreshControl: ( _fetchData({ refresh: true })} + progressBackgroundColor="#357CE6" + tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'} + titleColor="#fff" + colors={['#fff']} /> ), }} From 784358ebd10b66ecced9272ae45685862a54acfd Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 28 Aug 2023 17:00:29 +0500 Subject: [PATCH 27/40] set default modal value to false --- src/components/quickReplyModal/quickReplyModalContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index 922b9a6b8b..4977253c06 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -69,7 +69,7 @@ export const QuickReplyModalContent = forwardRef( const [commentValue, setCommentValue] = useState(''); const [mediaUrls, setMediaUrls] = useState([]); const [isUploading, setIsUploading] = useState(false); - const [mediaModalVisible, setMediaModalVisible] = useState(true); + const [mediaModalVisible, setMediaModalVisible] = useState(false); const headerText = selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS)); From e43756322d21d2744333c599ef1d75cdf19ceb45 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 28 Aug 2023 17:19:46 +0500 Subject: [PATCH 28/40] introduced reusable writePostButton in place hoder writeCommentButton --- src/components/atoms/index.ts | 1 + .../container/writePostButton.tsx} | 16 +++++++++------- src/components/atoms/writePostButton/index.ts | 1 + .../styles/writePostButton.styles.ts} | 0 src/components/postView/view/postDisplayView.tsx | 5 ++--- 5 files changed, 13 insertions(+), 10 deletions(-) rename src/components/{postView/children/writeCommentButton.tsx => atoms/writePostButton/container/writePostButton.tsx} (72%) create mode 100644 src/components/atoms/writePostButton/index.ts rename src/components/{postView/styles/writeCommentButton.styles.ts => atoms/writePostButton/styles/writePostButton.styles.ts} (100%) diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index b19c6e4dc0..46d4a25276 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -1,3 +1,4 @@ export * from './optionsModal'; export * from './progressBar'; export * from './assetIcon'; +export * from './writePostButton'; diff --git a/src/components/postView/children/writeCommentButton.tsx b/src/components/atoms/writePostButton/container/writePostButton.tsx similarity index 72% rename from src/components/postView/children/writeCommentButton.tsx rename to src/components/atoms/writePostButton/container/writePostButton.tsx index afad052758..7cbd4cc3ef 100644 --- a/src/components/postView/children/writeCommentButton.tsx +++ b/src/components/atoms/writePostButton/container/writePostButton.tsx @@ -2,16 +2,18 @@ import { View, Text, TouchableOpacity } from 'react-native'; import React, { forwardRef, useImperativeHandle, useRef } from 'react'; import { View as AnimatedView } from 'react-native-animatable'; import { useIntl } from 'react-intl'; -import UserAvatar from '../../userAvatar'; -import styles from '../styles/writeCommentButton.styles'; -import { useAppSelector } from '../../../hooks'; -import showLoginAlert from '../../../utils/showLoginAlert'; +import UserAvatar from '../../../userAvatar'; +import styles from '../styles/writePostButton.styles'; +import { useAppSelector } from '../../../../hooks'; +import showLoginAlert from '../../../../utils/showLoginAlert'; -interface WriteCommentButtonProps { +interface WritePostButtonProps { + title?: string; + placeholderId:string; onPress: () => void; } -export const WriteCommentButton = forwardRef(({ onPress }: WriteCommentButtonProps, ref) => { +export const WritePostButton = forwardRef(({title, placeholderId: placeholder, onPress }: WritePostButtonProps, ref) => { const intl = useIntl(); const animatedContainer = useRef(); @@ -45,7 +47,7 @@ export const WriteCommentButton = forwardRef(({ onPress }: WriteCommentButtonPro - {intl.formatMessage({ id: 'quick_reply.placeholder' })} + {intl.formatMessage({ id: placeholder })} diff --git a/src/components/atoms/writePostButton/index.ts b/src/components/atoms/writePostButton/index.ts new file mode 100644 index 0000000000..c0314a4615 --- /dev/null +++ b/src/components/atoms/writePostButton/index.ts @@ -0,0 +1 @@ +export * from './container/writePostButton'; \ No newline at end of file diff --git a/src/components/postView/styles/writeCommentButton.styles.ts b/src/components/atoms/writePostButton/styles/writePostButton.styles.ts similarity index 100% rename from src/components/postView/styles/writeCommentButton.styles.ts rename to src/components/atoms/writePostButton/styles/writePostButton.styles.ts diff --git a/src/components/postView/view/postDisplayView.tsx b/src/components/postView/view/postDisplayView.tsx index 8186a15e7b..5db5c17c85 100644 --- a/src/components/postView/view/postDisplayView.tsx +++ b/src/components/postView/view/postDisplayView.tsx @@ -17,7 +17,7 @@ import { ParentPost } from '../../parentPost'; // Styles import styles from './postDisplayStyles'; -import { OptionsModal } from '../../atoms'; +import { OptionsModal, WritePostButton } from '../../atoms'; import getWindowDimensions from '../../../utils/getWindowDimensions'; import { useAppDispatch } from '../../../hooks'; import { showProfileModal, showReplyModal } from '../../../redux/actions/uiAction'; @@ -56,7 +56,6 @@ const PostDisplayView = ({ const insets = useSafeAreaInsets(); const userActivityMutation = useUserActivityMutation(); - const writeCommentRef = useRef(); const postCommentsRef = useRef(null); const upvotePopoverRef = useRef(null); @@ -301,7 +300,7 @@ const PostDisplayView = ({ )} {formatedTime} - + )} From 3d690e5316fcdff484f2a28543f1e6679d283200 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 28 Aug 2023 17:40:03 +0500 Subject: [PATCH 29/40] easy access waves create button --- src/config/locales/en-US.json | 3 +- src/screens/waves/children/index.ts | 0 src/screens/waves/children/wavesHeader.tsx | 32 +++++++++++++++++++++ src/screens/waves/screen/wavesScreen.tsx | 6 ++++ src/screens/waves/styles/children.styles.ts | 18 ++++++++++++ 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/screens/waves/children/index.ts create mode 100644 src/screens/waves/children/wavesHeader.tsx create mode 100644 src/screens/waves/styles/children.styles.ts diff --git a/src/config/locales/en-US.json b/src/config/locales/en-US.json index 556749f7f8..8af7aeef14 100644 --- a/src/config/locales/en-US.json +++ b/src/config/locales/en-US.json @@ -665,7 +665,8 @@ "wrong_link": "Wrong link", "in": "in", "reveal_muted":"MUTED\nTap to reveal content", - "posted_by":"Posted by {username} via {appname} " + "posted_by":"Posted by {username} via {appname} ", + "ecency_waves":"Ecency Waves" }, "drafts": { "title": "Drafts", diff --git a/src/screens/waves/children/index.ts b/src/screens/waves/children/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/screens/waves/children/wavesHeader.tsx b/src/screens/waves/children/wavesHeader.tsx new file mode 100644 index 0000000000..67367826ab --- /dev/null +++ b/src/screens/waves/children/wavesHeader.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { WritePostButton } from '../../../components/atoms'; +import { showReplyModal } from '../../../redux/actions/uiAction'; +import { useDispatch } from 'react-redux'; +import styles from '../styles/children.styles'; +import { useIntl } from 'react-intl'; + +interface WavesHeaderProps { + +} + +export const WavesHeader: React.FC = ({ }) => { + + const dispatch = useDispatch(); + const intl = useIntl(); + + const _onPress = () => { + dispatch(showReplyModal({mode:'wave'})); + } + + return ( + + {intl.formatMessage({id:'post.ecency_waves'})} + + + + ); +}; + + +export default WavesHeader; \ No newline at end of file diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index 0553c063b0..2dc811e8e0 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -4,6 +4,8 @@ import { Comments, EmptyScreen, Header, PostOptionsModal } from '../../../compon import styles from '../styles/wavesScreen.styles'; import { wavesQueries } from '../../../providers/queries'; import { useAppSelector } from '../../../hooks'; +import { WritePostButton } from '../../../components/atoms'; +import WavesHeader from '../children/wavesHeader'; const WavesScreen = () => { @@ -34,6 +36,9 @@ const WavesScreen = () => { // const _data = useInjectVotesCache(wavesQuery.data.slice()); const _data = wavesQuery.data; + const _renderListHeader = ( + + ) const _renderListFooter = () => wavesQuery.isLoading && !wavesQuery.isRefreshing ? : ; const _renderListEmpty = () => wavesQuery.isRefreshing || wavesQuery.isLoading @@ -52,6 +57,7 @@ const WavesScreen = () => { onScroll: () => {}, ListEmptyComponent: _renderListEmpty, ListFooterComponent: _renderListFooter, + ListHeaderComponent: _renderListHeader, refreshControl: ( Date: Mon, 28 Aug 2023 19:32:03 +0500 Subject: [PATCH 30/40] code for handling vote cache in waves --- .../queries/postQueries/wavesQueries.ts | 66 ++++++++++++------- src/screens/waves/screen/wavesScreen.tsx | 3 - 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 08959e6fba..6803211e78 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -4,7 +4,7 @@ import { useMutation, useQueries, useQueryClient, } from '@tanstack/react-query'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { unionBy } from 'lodash'; import { getDiscussionCollection } from '../../hive/dhive'; @@ -19,16 +19,19 @@ import { useAppSelector } from '../../../hooks'; export const useWavesQuery = (host: string) => { + const queryClient = useQueryClient(); - const cachedComments = useAppSelector(state=>state.cache.commentsCollection); - const cachedVotes = useAppSelector(state=>state.cache.votesCollection); - const lastCacheUpdate = useAppSelector(state=>state.cache.lastUpdate); + const cachedComments = useAppSelector(state => state.cache.commentsCollection); + const cachedVotes = useAppSelector(state => state.cache.votesCollection); + const lastCacheUpdate = useAppSelector(state => state.cache.lastUpdate); const [isRefreshing, setIsRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); const [activePermlinks, setActivePermlinks] = useState([]); const [permlinksBucket, setPermlinksBucket] = useState([]); + const wavesIndexCollection = useRef<{ [key: string]: string }>({}); + @@ -57,20 +60,34 @@ export const useWavesQuery = (host: string) => { }, [permlinksBucket]) - useEffect(()=>{ - //TODO: checkd cache is recently updated and take post patch - - //using post path get index of query key where that post exists - - //get query data, update query data by finding post and injecting cache - - //set query data - - //inject cache in that particular post - // const post = injectVoteCache(post, cachedVotes) + useEffect(() => { + //check cache is recently updated and take post path + if (lastCacheUpdate) { + const _timeElapsed = new Date().getTime() - lastCacheUpdate.updateAt + if (lastCacheUpdate.type === 'vote' && _timeElapsed < 5000) { + //using post path get index of query key where that post exists + const _path = lastCacheUpdate.postPath; + const _containerPermlink = wavesIndexCollection.current[_path]; + const _containerIndex = activePermlinks.indexOf[_containerPermlink] + if (_containerIndex >= 0) { + //mean data exist, get query data, update query data by finding post and injecting cache + const _qData: any[] | undefined = wavesQueries[_containerIndex].data; + if (_qData) { + const _postIndex = _qData.findIndex((item) => lastCacheUpdate.postPath === `${item.author}/${item.permlink}`); + const _post = _qData[_postIndex]; + if (_post) { + const _cPost = injectVoteCache(_post, cachedVotes); + + //set query data + _qData.splice(_postIndex, 1, _cPost); + queryClient.setQueryData([QUERIES.WAVES.GET, host, _containerIndex], [..._qData]); + } + } + } + } + } - //update state and data - }, [cachedComments, cachedVotes]) + }, [lastCacheUpdate]) @@ -114,11 +131,14 @@ export const useWavesQuery = (host: string) => { const _cResponse = injectPostCache(response, cachedComments, cachedVotes, lastCacheUpdate); const _threadedComments = await mapDiscussionToThreads(_cResponse, host, pagePermlink, 1); - if(!_threadedComments){ + if (!_threadedComments) { throw new Error("Failed to parse waves"); } _threadedComments.sort((a, b) => new Date(a.created) > new Date(b.created) ? -1 : 1); + _threadedComments.forEach((item) => { + wavesIndexCollection.current[`${item.author}/${item.permlink}`] = pagePermlink + }) console.log('new waves fetched', _threadedComments); return _threadedComments || {}; }; @@ -198,26 +218,26 @@ export const usePublishWaveMutation = () => { // id is options, if no id is provided program marks all notifications as read; const _mutationFn = async (cachePostData: any) => { //TODO: lates port wave publishing here or introduce post publishing mutation; - if(cachePostData){ //TODO: expand to check multiple wave hosts;{ + if (cachePostData) { //TODO: expand to check multiple wave hosts;{ const _host = cachePostData.parent_author; - + console.log('returning waves host', _host); return _host; } throw new Error("invalid mutations data") - + }; const _options: UseMutationOptions = { - onMutate: async (cacheCommentData:any) => { + onMutate: async (cacheCommentData: any) => { // TODO: find a way to optimise mutations by avoiding too many loops console.log('on mutate data', cacheCommentData); const _host = cacheCommentData.parent_author; // update query data - const _queryKey = [ QUERIES.WAVES.GET, _host, 0]; + const _queryKey = [QUERIES.WAVES.GET, _host, 0]; const queryData: any[] | undefined = queryClient.getQueryData(_queryKey); console.log('query data', queryData); diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index 2dc811e8e0..918da5cdba 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -4,7 +4,6 @@ import { Comments, EmptyScreen, Header, PostOptionsModal } from '../../../compon import styles from '../styles/wavesScreen.styles'; import { wavesQueries } from '../../../providers/queries'; import { useAppSelector } from '../../../hooks'; -import { WritePostButton } from '../../../components/atoms'; import WavesHeader from '../children/wavesHeader'; @@ -32,8 +31,6 @@ const WavesScreen = () => { } } - - // const _data = useInjectVotesCache(wavesQuery.data.slice()); const _data = wavesQuery.data; const _renderListHeader = ( From 1569ea6735473e7b31fed29aa0afc6752d9f0365 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 11:35:40 +0500 Subject: [PATCH 31/40] added quick post wave summary --- .../quickReplyModal/quickReplyModalContent.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index 4977253c06..79daa3feba 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -71,12 +71,14 @@ export const QuickReplyModalContent = forwardRef( const [isUploading, setIsUploading] = useState(false); const [mediaModalVisible, setMediaModalVisible] = useState(false); - const headerText = - selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS)); const parentAuthor = selectedPost ? selectedPost.author : ''; const parentPermlink = selectedPost ? selectedPost.permlink : ''; + const headerText = mode === 'wave' + ? intl.formatMessage({ id: 'quick_reply.summary_wave' }, { host: 'ecency.waves' }) //TODO: update based on selected host + : selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS)); + const draftId = mode === 'wave' ? `${currentAccount.name}/ecency.waves` //TODO: update author based on selected host : `${currentAccount.name}/${parentAuthor}/${parentPermlink}`; // different draftId for each user acount @@ -179,10 +181,10 @@ export const QuickReplyModalContent = forwardRef( const _item = data[0]; - if(_item){ - switch(_item.status){ + if (_item) { + switch (_item.status) { case MediaInsertStatus.READY: - if(_item.url){ + if (_item.url) { _insertUrls.push(_item.url) } break; @@ -236,7 +238,7 @@ export const QuickReplyModalContent = forwardRef( // VIEW_RENDERERS - const _renderSummary = () => mode !== 'wave' && ( + const _renderSummary = () => ( _handleOnSummaryPress()}> {headerText} @@ -301,7 +303,7 @@ export const QuickReplyModalContent = forwardRef( isPreviewActive={false} username={currentAccount.username} allowMultiple={false} - hideToolbarExtension={() => { + hideToolbarExtension={() => { setMediaModalVisible(false); }} handleMediaInsert={_handleMediaInsert} From 86f79eb0e5c96b37e79ecfa83064366404614878 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 12:33:10 +0500 Subject: [PATCH 32/40] translations update --- src/config/locales/en-US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/locales/en-US.json b/src/config/locales/en-US.json index 8af7aeef14..5b25bc6735 100644 --- a/src/config/locales/en-US.json +++ b/src/config/locales/en-US.json @@ -1011,6 +1011,7 @@ "quick_reply":{ "placeholder":"Add a comment...", "placeholder_wave": "What's happening?", + "summary_wave":"Will be published in: {host}", "comment": "Comment", "reply": "REPLY", "publish": "PUBLISH", From 8b030f3218c94c88311e12e9e0e32af41834b785 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 12:43:25 +0500 Subject: [PATCH 33/40] throwing login alert of editor button press if not logged in --- .../writePostButton/container/writePostButton.tsx | 5 ++--- src/components/bottomTabBar/view/bottomTabBarView.tsx | 10 ++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/atoms/writePostButton/container/writePostButton.tsx b/src/components/atoms/writePostButton/container/writePostButton.tsx index 7cbd4cc3ef..d7edfc85b8 100644 --- a/src/components/atoms/writePostButton/container/writePostButton.tsx +++ b/src/components/atoms/writePostButton/container/writePostButton.tsx @@ -8,12 +8,11 @@ import { useAppSelector } from '../../../../hooks'; import showLoginAlert from '../../../../utils/showLoginAlert'; interface WritePostButtonProps { - title?: string; - placeholderId:string; + placeholderId: string; onPress: () => void; } -export const WritePostButton = forwardRef(({title, placeholderId: placeholder, onPress }: WritePostButtonProps, ref) => { +export const WritePostButton = forwardRef(({ placeholderId: placeholder, onPress }: WritePostButtonProps, ref) => { const intl = useIntl(); const animatedContainer = useRef(); diff --git a/src/components/bottomTabBar/view/bottomTabBarView.tsx b/src/components/bottomTabBar/view/bottomTabBarView.tsx index 45689d2ffd..7fcafbf9eb 100644 --- a/src/components/bottomTabBar/view/bottomTabBarView.tsx +++ b/src/components/bottomTabBar/view/bottomTabBarView.tsx @@ -14,13 +14,18 @@ import styles from './bottomTabBarStyles'; import Icon, { IconContainer } from '../../icon'; import scalePx from '../../../utils/scalePx'; import { showReplyModal, updateActiveBottomTab } from '../../../redux/actions/uiAction'; +import { useAppSelector } from '../../../hooks'; +import showLoginAlert from '../../../utils/showLoginAlert'; +import { useIntl } from 'react-intl'; const BottomTabBarView = ({ state: { routes, index }, navigation, descriptors, }: BottomTabBarProps) => { + const intl = useIntl(); const dispatch = useDispatch(); + const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn); useEffect(() => { dispatch(updateActiveBottomTab(routes[index].name)); @@ -32,6 +37,11 @@ const BottomTabBarView = ({ if (route.name === ROUTES.TABBAR.POST_BUTTON) { + if(!isLoggedIn){ + showLoginAlert({intl}) + return; + } + if (routes[index].name === ROUTES.TABBAR.WAVES) { dispatch(showReplyModal({mode:'wave'})); } else { From 6f6a70777a2fdafc3b5232541cd96f8e3d1eabcb Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 13:15:22 +0500 Subject: [PATCH 34/40] vote cache injection working in waves --- .../queries/postQueries/wavesQueries.ts | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 6803211e78..92e18759f4 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -63,33 +63,40 @@ export const useWavesQuery = (host: string) => { useEffect(() => { //check cache is recently updated and take post path if (lastCacheUpdate) { - const _timeElapsed = new Date().getTime() - lastCacheUpdate.updateAt + const _timeElapsed = new Date().getTime() - lastCacheUpdate.updatedAt if (lastCacheUpdate.type === 'vote' && _timeElapsed < 5000) { - //using post path get index of query key where that post exists - const _path = lastCacheUpdate.postPath; - const _containerPermlink = wavesIndexCollection.current[_path]; - const _containerIndex = activePermlinks.indexOf[_containerPermlink] - if (_containerIndex >= 0) { - //mean data exist, get query data, update query data by finding post and injecting cache - const _qData: any[] | undefined = wavesQueries[_containerIndex].data; - if (_qData) { - const _postIndex = _qData.findIndex((item) => lastCacheUpdate.postPath === `${item.author}/${item.permlink}`); - const _post = _qData[_postIndex]; - if (_post) { - const _cPost = injectVoteCache(_post, cachedVotes); - - //set query data - _qData.splice(_postIndex, 1, _cPost); - queryClient.setQueryData([QUERIES.WAVES.GET, host, _containerIndex], [..._qData]); - } - } - } + _injectPostCache(lastCacheUpdate.postPath) } } }, [lastCacheUpdate]) + const _injectPostCache = async (postPath:string) => { + //using post path get index of query key where that post exists + const _containerPermlink = wavesIndexCollection.current[postPath]; + const _containerIndex = activePermlinks.indexOf(_containerPermlink) + const _voteCache = cachedVotes[postPath]; + + if (_containerIndex >= 0 && _voteCache) { + //mean data exist, get query data, update query data by finding post and injecting cache + const _qData: any[] | undefined = wavesQueries[_containerIndex].data; + + if (_qData) { + const _postIndex = _qData.findIndex((item) => lastCacheUpdate.postPath === `${item.author}/${item.permlink}`); + const _post = _qData[_postIndex]; + + if (_post) { + //inject cache and set query data + const _cPost = injectVoteCache(_post, _voteCache); + _qData.splice(_postIndex, 1, _cPost); + queryClient.setQueryData([QUERIES.WAVES.GET, host, _containerIndex], [..._qData]); + } + } + } + } + + const _fetchPermlinks = async (startPermlink = '', refresh = false) => { setIsLoading(true); From f4c82013411591894c7c8b2543b04041ade4c26b Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 14:29:37 +0500 Subject: [PATCH 35/40] cherry pick quries for persistrance --- src/providers/queries/index.ts | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/providers/queries/index.ts b/src/providers/queries/index.ts index a27f8021dc..7ece8ad705 100644 --- a/src/providers/queries/index.ts +++ b/src/providers/queries/index.ts @@ -1,7 +1,8 @@ -import { QueryClient } from '@tanstack/react-query'; +import { Query, QueryClient } from '@tanstack/react-query'; import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { PersistQueryClientProviderProps } from '@tanstack/react-query-persist-client'; +import QUERIES from './queryKeys'; export const initQueryClient = () => { const asyncStoragePersister = createAsyncStoragePersister({ @@ -12,14 +13,40 @@ export const initQueryClient = () => { //Query client configurations go here... defaultOptions: { queries: { - cacheTime: 1000 * 60 * 60 * 24 * 6 , // 7 days cache timer + cacheTime: 1000 * 60 * 60 * 24 * 6, // 7 days cache timer }, }, }); + + + + const _shouldDehdrateQuery = (query:Query) => { + const _isSuccess = query.state.status === 'success'; + + if(_isSuccess){ + //Cherry pick whihc queries to dehydrate for persistance + switch(query.queryKey[0]){ + case QUERIES.WAVES.GET: + return query.queryKey[2] === 0 //only dehydrate first page of waves + case QUERIES.NOTIFICATIONS.GET: + return query.queryKey[2] === '' //only dehydrate first page of notifications + default: + return true; + } + } + + console.log("status error for dehydration", query.queryKey) + return false; + } + return { client, - persistOptions: { persister: asyncStoragePersister }, + persistOptions: { + persister: asyncStoragePersister, dehydrateOptions: { + shouldDehydrateQuery: _shouldDehdrateQuery + } + }, } as PersistQueryClientProviderProps; }; From 9304f76f88c83dc7aa681b2695461f1ee7df33ef Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 16:48:55 +0500 Subject: [PATCH 36/40] handling empty waves container --- .../queries/postQueries/wavesQueries.ts | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 92e18759f4..0e60b669f5 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -4,7 +4,7 @@ import { useMutation, useQueries, useQueryClient, } from '@tanstack/react-query'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { unionBy } from 'lodash'; import { getDiscussionCollection } from '../../hive/dhive'; @@ -60,6 +60,14 @@ export const useWavesQuery = (host: string) => { }, [permlinksBucket]) + useEffect(()=>{ + const _latestData = wavesQueries.lastItem?.data; + if(_latestData?.length === 0){ + _fetchNextPage(); + } + }, [wavesQueries.lastItem?.data]) + + useEffect(() => { //check cache is recently updated and take post path if (lastCacheUpdate) { @@ -146,8 +154,11 @@ export const useWavesQuery = (host: string) => { _threadedComments.forEach((item) => { wavesIndexCollection.current[`${item.author}/${item.permlink}`] = pagePermlink }) + + console.log('new waves fetched', _threadedComments); - return _threadedComments || {}; + + return _threadedComments || []; }; @@ -191,28 +202,6 @@ export const useWavesQuery = (host: string) => { }; -export const fetchLatestWavesContainer = async (host) => { - const query: any = { - account: host, - start_author: '', - start_permlink: '', - limit: 1, - observer: '', - sort: 'posts', - }; - - const result = await getAccountPosts(query); - - const _latestPost = result[0]; - console.log('lates waves post', host, _latestPost); - - if (!_latestPost) { - throw new Error("Lates waves container could be not fetched"); - } - - return _latestPost; -} - @@ -220,8 +209,6 @@ export const usePublishWaveMutation = () => { const queryClient = useQueryClient(); - // const cachedComments = useAppSelector(state => state.cache.commentsCollection); - // id is options, if no id is provided program marks all notifications as read; const _mutationFn = async (cachePostData: any) => { //TODO: lates port wave publishing here or introduce post publishing mutation; @@ -264,3 +251,27 @@ export const usePublishWaveMutation = () => { return useMutation(_mutationFn, _options); }; + + + +export const fetchLatestWavesContainer = async (host) => { + const query: any = { + account: host, + start_author: '', + start_permlink: '', + limit: 1, + observer: '', + sort: 'posts', + }; + + const result = await getAccountPosts(query); + + const _latestPost = result[0]; + console.log('lates waves post', host, _latestPost); + + if (!_latestPost) { + throw new Error("Lates waves container could be not fetched"); + } + + return _latestPost; +} From 347b1688f61e56daaef7b2315733a363a3a7df0b Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 17:52:36 +0500 Subject: [PATCH 37/40] test value reset --- .../quickReplyModalContent.tsx | 1 - src/navigation/botomTabNavigator.tsx | 2 +- .../queries/postQueries/wavesQueries.ts | 32 ++++++++------- src/providers/queries/queryKeys.ts | 1 + src/screens/waves/screen/wavesScreen.tsx | 40 +++++++++---------- src/utils/parsePurchaseUrl.ts | 2 +- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index 79daa3feba..470f074fff 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -14,7 +14,6 @@ import { TouchableOpacity, Keyboard, Platform, - Alert, ActivityIndicator, } from 'react-native'; import { useIntl } from 'react-intl'; diff --git a/src/navigation/botomTabNavigator.tsx b/src/navigation/botomTabNavigator.tsx index 625cc93269..82ea69055c 100644 --- a/src/navigation/botomTabNavigator.tsx +++ b/src/navigation/botomTabNavigator.tsx @@ -13,7 +13,7 @@ export const BottomTabNavigator = () => { } backBehavior="initialRoute" - initialRouteName={ROUTES.TABBAR.WAVES} + initialRouteName={ROUTES.TABBAR.FEED} screenOptions={{ headerShown: false, tabBarShowLabel: false, diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 0e60b669f5..f80b552b39 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -4,7 +4,7 @@ import { useMutation, useQueries, useQueryClient, } from '@tanstack/react-query'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { unionBy } from 'lodash'; import { getDiscussionCollection } from '../../hive/dhive'; @@ -28,11 +28,12 @@ export const useWavesQuery = (host: string) => { const [isRefreshing, setIsRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); const [activePermlinks, setActivePermlinks] = useState([]); - const [permlinksBucket, setPermlinksBucket] = useState([]); const wavesIndexCollection = useRef<{ [key: string]: string }>({}); - + const _initialContainerPermlinks = useMemo(() => + queryClient.getQueryData([QUERIES.WAVES.INITIAL_CONTAINERS, host]) || [], []); + const [permlinksBucket, setPermlinksBucket] = useState(_initialContainerPermlinks); // query initialization @@ -45,24 +46,24 @@ export const useWavesQuery = (host: string) => { }); - - useEffect(() => { - _fetchPermlinks() + _fetchPermlinks('', true); }, []) useEffect(() => { if (!!permlinksBucket.length) { + console.log("incrementing active permlinks bucket", permlinksBucket[activePermlinks.length]); activePermlinks.push(permlinksBucket[activePermlinks.length]); setActivePermlinks([...activePermlinks]); } }, [permlinksBucket]) - useEffect(()=>{ + + useEffect(() => { const _latestData = wavesQueries.lastItem?.data; - if(_latestData?.length === 0){ + if (!_latestData || _latestData.length < 10) { _fetchNextPage(); } }, [wavesQueries.lastItem?.data]) @@ -80,7 +81,7 @@ export const useWavesQuery = (host: string) => { }, [lastCacheUpdate]) - const _injectPostCache = async (postPath:string) => { + const _injectPostCache = async (postPath: string) => { //using post path get index of query key where that post exists const _containerPermlink = wavesIndexCollection.current[postPath]; const _containerIndex = activePermlinks.indexOf(_containerPermlink) @@ -113,7 +114,7 @@ export const useWavesQuery = (host: string) => { account: host, start_author: !!startPermlink ? host : '', start_permlink: startPermlink, - limit: 10, + limit: 3, observer: '', sort: 'posts', }; @@ -127,15 +128,19 @@ export const useWavesQuery = (host: string) => { setPermlinksBucket(_permlinksBucket); if (refresh) { + queryClient.setQueryData([QUERIES.WAVES.INITIAL_CONTAINERS, host], _permlinksBucket); //precautionary delay of 200ms to let state update before concluding promise, //it is effective for waves refresh routine. await delay(200) } + + } catch (err) { console.warn("failed to fetch waves permlinks"); } setIsLoading(false) + } const _fetchWaves = async (pagePermlink: string) => { @@ -155,9 +160,8 @@ export const useWavesQuery = (host: string) => { wavesIndexCollection.current[`${item.author}/${item.permlink}`] = pagePermlink }) - console.log('new waves fetched', _threadedComments); - + return _threadedComments || []; }; @@ -180,12 +184,12 @@ export const useWavesQuery = (host: string) => { const _nextPagePermlink = permlinksBucket[activePermlinks.length]; - //TODO: find a way to proactively refill active permlinks here. - if (_nextPagePermlink && !activePermlinks.includes(_nextPagePermlink)) { + console.log("updating next page permlink", _nextPagePermlink) activePermlinks.push(_nextPagePermlink); setActivePermlinks([...activePermlinks]); } else { + console.log("fetching new containers", permlinksBucket.lastItem) _fetchPermlinks(permlinksBucket.lastItem) } }; diff --git a/src/providers/queries/queryKeys.ts b/src/providers/queries/queryKeys.ts index ed8a4c9d30..925c229c31 100644 --- a/src/providers/queries/queryKeys.ts +++ b/src/providers/queries/queryKeys.ts @@ -29,6 +29,7 @@ const QUERIES = { }, WAVES: { GET: 'QUERY_GET_WAVES', + INITIAL_CONTAINERS: 'QUERY_DATA_INITIAL_CONTAINERS' } }; diff --git a/src/screens/waves/screen/wavesScreen.tsx b/src/screens/waves/screen/wavesScreen.tsx index 918da5cdba..d15605c6f9 100644 --- a/src/screens/waves/screen/wavesScreen.tsx +++ b/src/screens/waves/screen/wavesScreen.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { ActivityIndicator, RefreshControl, View } from 'react-native'; import { Comments, EmptyScreen, Header, PostOptionsModal } from '../../../components'; import styles from '../styles/wavesScreen.styles'; @@ -13,11 +13,11 @@ const WavesScreen = () => { const wavesQuery = wavesQueries.useWavesQuery('ecency.waves'); + const isDarkTheme = useAppSelector(state => state.application.isDarkTheme) - const isDarkTheme = useAppSelector(state=>state.application.isDarkTheme) - const _fetchData = ({refresh}:{refresh?:boolean}) => { - if(refresh){ + const _fetchData = ({ refresh }: { refresh?: boolean }) => { + if (refresh) { wavesQuery.refresh(); } else { wavesQuery.fetchNextPage(); @@ -25,8 +25,8 @@ const WavesScreen = () => { } - const _handleOnOptionsPress = (content:any) => { - if(postOptionsModalRef.current){ + const _handleOnOptionsPress = (content: any) => { + if (postOptionsModalRef.current) { postOptionsModalRef.current.show(content); } } @@ -36,10 +36,10 @@ const WavesScreen = () => { const _renderListHeader = ( ) - const _renderListFooter = () => wavesQuery.isLoading && !wavesQuery.isRefreshing - ? : ; - const _renderListEmpty = () => wavesQuery.isRefreshing || wavesQuery.isLoading - ? : ; + const _renderListFooter = () => wavesQuery.isLoading && !wavesQuery.isRefreshing + ? : ; + const _renderListEmpty = () => wavesQuery.isRefreshing || wavesQuery.isLoading + ? : ; return ( @@ -51,21 +51,21 @@ const WavesScreen = () => { handleOnOptionsPress={_handleOnOptionsPress} flatListProps={{ onEndReached: _fetchData, - onScroll: () => {}, + onScroll: () => { }, ListEmptyComponent: _renderListEmpty, ListFooterComponent: _renderListFooter, ListHeaderComponent: _renderListHeader, refreshControl: ( - _fetchData({ refresh: true })} - progressBackgroundColor="#357CE6" - tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'} - titleColor="#fff" - colors={['#fff']} - /> + _fetchData({ refresh: true })} + progressBackgroundColor="#357CE6" + tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'} + titleColor="#fff" + colors={['#fff']} + /> ), - }} + }} /> diff --git a/src/utils/parsePurchaseUrl.ts b/src/utils/parsePurchaseUrl.ts index 9875a1b087..9350edde7d 100644 --- a/src/utils/parsePurchaseUrl.ts +++ b/src/utils/parsePurchaseUrl.ts @@ -1,5 +1,5 @@ /** - * extracts purchase information from deep link url i-e https://ecency.com/purchase?type=boost&username=ecency.waves + * extracts purchase information from deep link url i-e https://ecency.com/purchase?type=boost&username=demo.com * */ From 64a31d832992d53dbc1c3d21374310e4d2e8d319 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 29 Aug 2023 17:55:08 +0500 Subject: [PATCH 38/40] updated minimum base container posts fetch --- src/providers/queries/postQueries/wavesQueries.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index f80b552b39..7c7d7680d9 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -114,7 +114,7 @@ export const useWavesQuery = (host: string) => { account: host, start_author: !!startPermlink ? host : '', start_permlink: startPermlink, - limit: 3, + limit: 5, observer: '', sort: 'posts', }; From 8f86b1daee6d261ec1367423b6d51bdf2cf1f880 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 30 Aug 2023 13:35:43 +0500 Subject: [PATCH 39/40] better handling cache injection on refresh --- .../queries/postQueries/wavesQueries.ts | 65 +++++++++++++------ 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 7c7d7680d9..672185db5c 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -2,7 +2,7 @@ import { UseMutationOptions, useMutation, - useQueries, useQueryClient, + useQueries, useQuery, useQueryClient, } from '@tanstack/react-query'; import { useEffect, useMemo, useRef, useState } from 'react'; @@ -21,9 +21,12 @@ export const useWavesQuery = (host: string) => { const queryClient = useQueryClient(); - const cachedComments = useAppSelector(state => state.cache.commentsCollection); - const cachedVotes = useAppSelector(state => state.cache.votesCollection); - const lastCacheUpdate = useAppSelector(state => state.cache.lastUpdate); + const cache = useAppSelector(state => state.cache); + const cacheRef = useRef(cache); + + const cachedVotes = cache.votesCollection + const lastCacheUpdate = cache.lastCacheUpdate + const [isRefreshing, setIsRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); @@ -39,13 +42,21 @@ export const useWavesQuery = (host: string) => { // query initialization const wavesQueries = useQueries({ queries: activePermlinks.map((pagePermlink, index) => ({ - queryKey: [QUERIES.WAVES.GET, host, index], - queryFn: () => _fetchWaves(pagePermlink), - initialData: [], + queryKey: [QUERIES.WAVES.GET, host, index], + queryFn: () => _fetchWaves(pagePermlink), + initialData: [], })), }); + //hook to update cache reference, + //workaround required since query fucntion do get passed an + //updated copy for states that are not part of query key and contexet while conext is not + //supported by useQueries + useEffect(() => { + cacheRef.current = cache; + }, [cache]) + useEffect(() => { _fetchPermlinks('', true); }, []) @@ -53,8 +64,14 @@ export const useWavesQuery = (host: string) => { useEffect(() => { if (!!permlinksBucket.length) { - console.log("incrementing active permlinks bucket", permlinksBucket[activePermlinks.length]); - activePermlinks.push(permlinksBucket[activePermlinks.length]); + //if first elements permlinks do not match, means there is a new container, push at first + if (permlinksBucket[0] !== activePermlinks[0]) { + activePermlinks.splice(0, 0, permlinksBucket[0]); + } + //permlinks bucket is updated, it needs to be connect with active one to start chain again + else { + activePermlinks.push(permlinksBucket[activePermlinks.length]); + } setActivePermlinks([...activePermlinks]); } }, [permlinksBucket]) @@ -147,8 +164,12 @@ export const useWavesQuery = (host: string) => { console.log('fetching waves from:', host, pagePermlink); const response = await getDiscussionCollection(host, pagePermlink); - //TODO: inject comment cache here... - const _cResponse = injectPostCache(response, cachedComments, cachedVotes, lastCacheUpdate); + //inject cache here... + const _cachedComments = cacheRef.current.commentsCollection; + const _cachedVotes = cacheRef.current.votesCollection; + const _lastCacheUpdate = cacheRef.current.lastCacheUpdate + const _cResponse = injectPostCache(response, _cachedComments, _cachedVotes, _lastCacheUpdate); + const _threadedComments = await mapDiscussionToThreads(_cResponse, host, pagePermlink, 1); if (!_threadedComments) { @@ -166,14 +187,6 @@ export const useWavesQuery = (host: string) => { }; - const _refresh = async () => { - setIsRefreshing(true); - setPermlinksBucket([]); - setActivePermlinks([]); - await _fetchPermlinks('', true); - await wavesQueries[0].refetch(); - setIsRefreshing(false); - }; const _fetchNextPage = () => { const lastPage = wavesQueries.lastItem; @@ -194,6 +207,19 @@ export const useWavesQuery = (host: string) => { } }; + + + const _refresh = async () => { + setIsRefreshing(true); + setPermlinksBucket([]); + setActivePermlinks([]); + await _fetchPermlinks('', true); + await wavesQueries[0].refetch(); + setIsRefreshing(false); + }; + + + const _dataArrs = wavesQueries.map((query) => query.data); return { @@ -248,7 +274,6 @@ export const usePublishWaveMutation = () => { }, onSuccess: async (host) => { - await delay(5000); queryClient.invalidateQueries([QUERIES.WAVES.GET, host, 0]); }, }; From 496dedb82d2ba826efb43bab94245b16c6140f2b Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 30 Aug 2023 17:01:56 +0500 Subject: [PATCH 40/40] fixed last cache update typo --- src/providers/queries/postQueries/wavesQueries.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/queries/postQueries/wavesQueries.ts b/src/providers/queries/postQueries/wavesQueries.ts index 672185db5c..a477c72b27 100644 --- a/src/providers/queries/postQueries/wavesQueries.ts +++ b/src/providers/queries/postQueries/wavesQueries.ts @@ -2,7 +2,7 @@ import { UseMutationOptions, useMutation, - useQueries, useQuery, useQueryClient, + useQueries, useQueryClient, } from '@tanstack/react-query'; import { useEffect, useMemo, useRef, useState } from 'react'; @@ -25,7 +25,7 @@ export const useWavesQuery = (host: string) => { const cacheRef = useRef(cache); const cachedVotes = cache.votesCollection - const lastCacheUpdate = cache.lastCacheUpdate + const lastCacheUpdate = cache.lastUpdate const [isRefreshing, setIsRefreshing] = useState(false);