Merge branch 'development' into sa/limit-meta-imgs

This commit is contained in:
Sadaqat Ali 2022-09-24 15:30:22 +05:00 committed by GitHub
commit ff87e77b38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1344 additions and 1274 deletions

View File

@ -54,6 +54,9 @@
"@react-navigation/native": "^6.0.11",
"@react-navigation/native-stack": "^6.7.0",
"@react-navigation/stack": "^6.2.2",
"@tanstack/query-async-storage-persister": "^4.3.9",
"@tanstack/react-query": "^4.3.9",
"@tanstack/react-query-persist-client": "^4.3.9",
"@tradle/react-native-http": "^2.0.0",
"appcenter": "^4.1.0",
"appcenter-analytics": "^4.1.0",
@ -104,7 +107,6 @@
"react-native-highlight-words": "^1.0.1",
"react-native-iap": "^7.5.6",
"react-native-image-crop-picker": "^0.35.2",
"react-native-image-size": "^1.1.3",
"react-native-image-zoom-viewer": "^2.2.27",
"react-native-iphone-x-helper": "^1.3.1",
"react-native-keyboard-aware-scroll-view": "^0.9.1",

View File

@ -14,16 +14,9 @@ export default EStyleSheet.create({
body: {
marginHorizontal: 9,
},
image: {
margin: 0,
alignItems: 'center',
alignSelf: 'center',
//height: 200,
//width: '$deviceWidth - 16',
borderRadius: 8,
backgroundColor: '$primaryLightGray',
// paddingVertical: 10,
marginVertical: 5,
thumbnail: {
width: '$deviceWidth - 16',
height: 300
},
postDescripton: {
flexDirection: 'column',

View File

@ -1,7 +1,6 @@
import React, { useRef, useState, useEffect, Fragment } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { injectIntl } from 'react-intl';
import ImageSize from 'react-native-image-size';
// Utils
import { getTimeFromNow } from '../../../utils/time';
@ -9,20 +8,14 @@ import { getTimeFromNow } from '../../../utils/time';
// Components
import { PostHeaderDescription } from '../../postElements';
import { IconButton } from '../../iconButton';
import ProgressiveImage from '../../progressiveImage';
import { OptionsModal } from '../../atoms';
// Styles
import styles from './draftListItemStyles';
import { ScheduledPostStatus } from '../../../providers/ecency/ecency.types';
import { PopoverWrapper } from '../../popoverWrapper/popoverWrapperView';
import getWindowDimensions from '../../../utils/getWindowDimensions';
import FastImage from 'react-native-fast-image';
// Defaults
const DEFAULT_IMAGE =
'https://images.ecency.com/DQmT8R33geccEjJfzZEdsRHpP3VE8pu3peRCnQa1qukU4KR/no_image_3x.png';
const dim = getWindowDimensions();
const DraftListItemView = ({
title,
@ -35,28 +28,31 @@ const DraftListItemView = ({
thumbnail,
handleOnPressItem,
handleOnRemoveItem,
handleOnMovePress,
id,
intl,
isFormatedDate,
status,
isSchedules,
isDeleting,
}) => {
const actionSheet = useRef(null);
const [calcImgHeight, setCalcImgHeight] = useState(300);
// Component Life Cycles
const moveActionSheet = useRef(null);
const [deleteRequested, setIsDeleteRequested] = useState(false);
useEffect(() => {
let _isMounted = false;
if (image) {
if (!_isMounted) {
ImageSize.getSize(thumbnail.uri).then((size) => {
setCalcImgHeight(Math.floor((size.height / size.width) * dim.width));
});
if (deleteRequested && !isDeleting) {
setIsDeleteRequested(false);
}
}, [isDeleting]);
const _onItemPress = () => {
if (isSchedules) {
moveActionSheet.current.show();
return;
}
return () => {
_isMounted = true;
handleOnPressItem(id);
};
}, []);
// consts
const scheduleStatus =
@ -116,19 +112,17 @@ const DraftListItemView = ({
onPress={() => actionSheet.current.show()}
style={[styles.rightItem]}
color="#c1c5c7"
isLoading={isDeleting && deleteRequested}
/>
</View>
</View>
<View style={styles.body}>
<TouchableOpacity onPress={() => handleOnPressItem(id)}>
<TouchableOpacity onPress={_onItemPress}>
{image !== null && (
<ProgressiveImage
<FastImage
source={image}
thumbnailSource={thumbnail}
style={[
styles.thumbnail,
{ width: dim.width - 16, height: Math.min(calcImgHeight, dim.height) },
]}
style={styles.thumbnail}
resizeMode={FastImage.resizeMode.cover}
/>
)}
<View style={[styles.postDescripton]}>
@ -151,6 +145,29 @@ const DraftListItemView = ({
onPress={(index) => {
if (index === 0) {
handleOnRemoveItem(id);
setIsDeleteRequested(true);
}
}}
/>
<OptionsModal
ref={moveActionSheet}
title={intl.formatMessage({
id: 'alert.move_question',
})}
options={[
intl.formatMessage({
id: 'alert.move',
}),
intl.formatMessage({
id: 'alert.cancel',
}),
]}
cancelButtonIndex={1}
onPress={(index) => {
if (index === 0) {
handleOnMovePress(id);
setIsDeleteRequested(true);
}
}}
/>

View File

@ -2,9 +2,7 @@ import EStyleSheet from 'react-native-extended-stylesheet';
import isAndroidOreo from '../../../../utils/isAndroidOreo';
export default EStyleSheet.create({
container: {
},
container: {},
textInput: {
color: '$primaryBlack',
fontSize: 15,
@ -14,7 +12,7 @@ export default EStyleSheet.create({
borderTopColor: '$primaryLightGray',
borderBottomWidth: 1,
borderBottomColor: '$primaryLightGray',
height: isAndroidOreo() ? 36: 40
height: isAndroidOreo() ? 36 : 40,
},
warning: {
color: '$primaryRed',

View File

@ -17,7 +17,7 @@ export default EStyleSheet.create({
color: '$primaryBlack',
backgroundColor: '$primaryBackgroundColor',
textAlignVertical: 'top',
maxHeight: isAndroidOreo() ? '$deviceHeight':undefined
maxHeight: isAndroidOreo() ? '$deviceHeight' : undefined,
},
previewContainer: {
flex: 1,

View File

@ -5,13 +5,18 @@ import { PersistGate } from 'redux-persist/integration/react';
import { IntlProvider } from 'react-intl';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Host } from 'react-native-portalize';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import { flattenMessages } from './utils/flattenMessages';
import messages from './config/locales';
import Application from './screens/application';
import { store, persistor } from './redux/store/store';
import { initQueryClient } from './providers/queries';
const queryClientProviderProps = initQueryClient();
const _renderApp = ({ locale }) => (
<PersistQueryClientProvider {...queryClientProviderProps}>
<PersistGate loading={null} persistor={persistor}>
<IntlProvider locale={locale} messages={flattenMessages(messages[locale])}>
<SafeAreaProvider>
@ -21,6 +26,7 @@ const _renderApp = ({ locale }) => (
</SafeAreaProvider>
</IntlProvider>
</PersistGate>
</PersistQueryClientProvider>
);
const mapStateToProps = (state) => ({

View File

@ -6,10 +6,19 @@ import { upload } from '../../config/imageApi';
import serverList from '../../config/serverListApi';
import { SERVER_LIST } from '../../constants/options/api';
import { parsePost } from '../../utils/postParser';
import { convertCommentHistory, convertLatestQuotes, convertReferral, convertReferralStat } from './converters';
import { CommentHistoryItem, LatestMarketPrices, ReceivedVestingShare, Referral, ReferralStat } from './ecency.types';
import {
convertCommentHistory,
convertLatestQuotes,
convertReferral,
convertReferralStat,
} from './converters';
import {
CommentHistoryItem,
LatestMarketPrices,
ReceivedVestingShare,
Referral,
ReferralStat,
} from './ecency.types';
/**
* ************************************
@ -30,10 +39,10 @@ export const getCurrencyRate = (currency) =>
export const getLatestQuotes = async (currencyRate: number): Promise<LatestMarketPrices> => {
try {
console.log('using currency rate', currencyRate);
const res = await ecencyApi.get(`/private-api/market-data/latest`);
const res = await ecencyApi.get('/private-api/market-data/latest');
if (!res.data) {
throw new Error("No quote data returned");
throw new Error('No quote data returned');
}
const data = convertLatestQuotes(res.data, currencyRate);
@ -43,10 +52,9 @@ export const getLatestQuotes = async (currencyRate: number): Promise<LatestMarke
} catch (error) {
bugsnagInstance.notify(error);
console.warn(error);
throw error
throw error;
}
}
};
export const getCurrencyTokenRate = (currency, token) =>
ecencyApi
@ -57,27 +65,22 @@ export const getCurrencyTokenRate = (currency, token) =>
return 0;
});
export const getReceivedVestingShares = async (username: string): Promise<ReceivedVestingShare[]> => {
export const getReceivedVestingShares = async (
username: string,
): Promise<ReceivedVestingShare[]> => {
try {
const res = await ecencyApi.get(`/private-api/received-vesting/${username}`);
console.log("Vesting Shares User", username, res.data);
console.log('Vesting Shares User', username, res.data);
if (!res.data || !res.data.list) {
throw new Error("No vesting shares for user")
throw new Error('No vesting shares for user');
}
return res.data.list;
} catch (error) {
bugsnagInstance.notify(error);
console.warn(error);
throw error
throw error;
}
}
};
/**
* returns list of saved drafts on ecency server
@ -85,29 +88,26 @@ export const getReceivedVestingShares = async (username: string): Promise<Receiv
export const getDrafts = async () => {
try {
const res = await ecencyApi.post('/private-api/drafts');
return res.data;
return res.data || [];
} catch (error) {
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* @params draftId
*/
export const deleteDraft = async (draftId: string) => {
try {
const data = { id: draftId }
const res = await ecencyApi.post(`/private-api/drafts-delete`, data);
return res.data
const data = { id: draftId };
const res = await ecencyApi.post('/private-api/drafts-delete', data);
return res.data || [];
} catch (error) {
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* @params title
@ -117,8 +117,8 @@ export const deleteDraft = async (draftId: string) => {
*/
export const addDraft = async (title: string, body: string, tags: string, meta: Object) => {
try {
const data = { title, body, tags, meta }
const res = await ecencyApi.post('/private-api/drafts-add', data)
const data = { title, body, tags, meta };
const res = await ecencyApi.post('/private-api/drafts-add', data);
const { drafts } = res.data;
if (drafts) {
return drafts.pop(); //return recently saved last draft in the list
@ -129,8 +129,7 @@ export const addDraft = async (title: string, body: string, tags: string, meta:
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* @params draftId
@ -139,14 +138,20 @@ export const addDraft = async (title: string, body: string, tags: string, meta:
* @params tags
* @params meta
*/
export const updateDraft = async (draftId: string, title: string, body: string, tags: string, meta: Object) => {
export const updateDraft = async (
draftId: string,
title: string,
body: string,
tags: string,
meta: Object,
) => {
try {
const data = { id: draftId, title, body, tags, meta }
const res = await ecencyApi.post(`/private-api/drafts-update`, data)
const data = { id: draftId, title, body, tags, meta };
const res = await ecencyApi.post('/private-api/drafts-update', data);
if (res.data) {
return res.data
return res.data;
} else {
throw new Error("No data returned in response")
throw new Error('No data returned in response');
}
} catch (error) {
bugsnagInstance.notify(error);
@ -154,8 +159,6 @@ export const updateDraft = async (draftId: string, title: string, body: string,
}
};
/**
* ************************************
* BOOKMARKS ECENCY APIS IMPLEMENTATION
@ -171,14 +174,14 @@ export const updateDraft = async (draftId: string, title: string, body: string,
export const addBookmark = async (author: string, permlink: string) => {
try {
const data = { author, permlink };
const response = await ecencyApi.post(`/private-api/bookmarks-add`, data);
const response = await ecencyApi.post('/private-api/bookmarks-add', data);
return response.data;
} catch (error) {
console.warn("Failed to add bookmark", error)
bugsnagInstance.notify(error)
throw error
}
console.warn('Failed to add bookmark', error);
bugsnagInstance.notify(error);
throw error;
}
};
/**
* fetches saved bookmarks of user
@ -186,15 +189,14 @@ export const addBookmark = async (author: string, permlink: string) => {
*/
export const getBookmarks = async () => {
try {
const response = await ecencyApi.post(`/private-api/bookmarks`);
const response = await ecencyApi.post('/private-api/bookmarks');
return response.data;
} catch (error) {
console.warn("Failed to get saved bookmarks", error)
bugsnagInstance.notify(error)
throw error
console.warn('Failed to get saved bookmarks', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* Deletes bookmark from user's saved bookmarks
@ -203,46 +205,42 @@ export const getBookmarks = async () => {
*/
export const deleteBookmark = async (bookmarkId: string) => {
try {
const data = { id: bookmarkId }
const response = await ecencyApi.post(`/private-api/bookmarks-delete`, data);
const data = { id: bookmarkId };
const response = await ecencyApi.post('/private-api/bookmarks-delete', data);
return response.data;
} catch (error) {
console.warn("Failed to delete bookmark", error)
bugsnagInstance.notify(error)
throw error
console.warn('Failed to delete bookmark', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
export const addReport = async (type: 'content' | 'user', data: string) => {
try {
const response = await api
.post('/report', {
const response = await api.post('/report', {
type,
data
})
return response.data
data,
});
return response.data;
} catch (err) {
console.warn("Failed to report to ecency")
console.warn('Failed to report to ecency');
bugsnagInstance.notify(err);
throw err;
}
}
};
export const deleteAccount = async (username: string) => {
try {
const response = await api
.post('/request-delete', {
const response = await api.post('/request-delete', {
username,
})
return response.data
});
return response.data;
} catch (err) {
console.warn("Failed to report to ecency")
console.warn('Failed to report to ecency');
bugsnagInstance.notify(err);
throw err;
}
}
};
/**
* ************************************
@ -256,14 +254,14 @@ export const deleteAccount = async (username: string) => {
*/
export const getFavorites = async () => {
try {
const response = await ecencyApi.post(`/private-api/favorites`)
const response = await ecencyApi.post('/private-api/favorites');
return response.data;
} catch (error) {
console.warn("Failed to get favorites", error);
console.warn('Failed to get favorites', error);
bugsnagInstance.notify(error);
throw error
}
throw error;
}
};
/**
* Checks if user is precent in current user's favourites
@ -273,13 +271,13 @@ export const getFavorites = async () => {
export const checkFavorite = async (targetUsername: string) => {
try {
const data = { account: targetUsername };
const response = await ecencyApi.post(`/private-api/favorites-check`, data);
const response = await ecencyApi.post('/private-api/favorites-check', data);
return response.data || false;
} catch (error) {
console.warn("Failed to check favorite", error);
console.warn('Failed to check favorite', error);
bugsnagInstance.notify(error);
}
}
};
/**
* Adds taget user to current user's favourites
@ -289,15 +287,14 @@ export const checkFavorite = async (targetUsername: string) => {
export const addFavorite = async (targetUsername: string) => {
try {
const data = { account: targetUsername };
const response = await ecencyApi.post(`/private-api/favorites-add`, data);
const response = await ecencyApi.post('/private-api/favorites-add', data);
return response.data;
} catch (error) {
console.warn("Failed to add user favorites", error);
console.warn('Failed to add user favorites', error);
bugsnagInstance.notify(error);
throw error
throw error;
}
}
};
/**
* Removes taget user to current user's favourites
@ -307,15 +304,14 @@ export const addFavorite = async (targetUsername: string) => {
export const deleteFavorite = async (targetUsername: string) => {
try {
const data = { account: targetUsername };
const response = await ecencyApi.post(`/private-api/favorites-delete`, data);
const response = await ecencyApi.post('/private-api/favorites-delete', data);
return response.data;
} catch (error) {
console.warn("Failed to add user favorites", error);
console.warn('Failed to add user favorites', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* ************************************
@ -323,22 +319,20 @@ export const deleteFavorite = async (targetUsername: string) => {
* ************************************
*/
/**
* Fetches all saved user fragments/snippets from ecency
* @returns array of fragments
*/
export const getFragments = async () => {
try {
const response = await ecencyApi.post(`/private-api/fragments`);
const response = await ecencyApi.post('/private-api/fragments');
return response.data;
} catch (error) {
console.warn("Failed to get fragments", error);
bugsnagInstance.notify(error)
console.warn('Failed to get fragments', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* Adds new fragment/snippets to user's saved fragments/snippets
@ -350,14 +344,14 @@ export const getFragments = async () => {
export const addFragment = async (title: string, body: string) => {
try {
const data = { title, body };
const response = await ecencyApi.post(`/private-api/fragments-add`, data);
const response = await ecencyApi.post('/private-api/fragments-add', data);
return response.data;
} catch (error) {
console.warn("Failed to add fragment", error);
bugsnagInstance.notify(error)
console.warn('Failed to add fragment', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* Updates a fragment content using fragment id
@ -369,14 +363,14 @@ export const addFragment = async (title: string, body: string) => {
export const updateFragment = async (fragmentId: string, title: string, body: string) => {
try {
const data = { id: fragmentId, title, body };
const response = await ecencyApi.post(`/private-api/fragments-update`, data);
const response = await ecencyApi.post('/private-api/fragments-update', data);
return response.data;
} catch (error) {
console.warn("Failed to update fragment", error);
bugsnagInstance.notify(error)
console.warn('Failed to update fragment', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* Deletes user saved fragment using specified fragment id
@ -386,16 +380,14 @@ export const updateFragment = async (fragmentId: string, title: string, body: st
export const deleteFragment = async (fragmentId: string) => {
try {
const data = { id: fragmentId };
const response = await ecencyApi.post(`/private-api/fragments-delete`, data);
const response = await ecencyApi.post('/private-api/fragments-delete', data);
return response.data;
} catch (error) {
console.warn("Failed to delete fragment", error);
bugsnagInstance.notify(error)
console.warn('Failed to delete fragment', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* ************************************
@ -405,7 +397,7 @@ export const deleteFragment = async (fragmentId: string) => {
export const getLeaderboard = async (duration: 'day' | 'week' | 'month') => {
try {
const response = await ecencyApi.get(`private-api/leaderboard/${duration}`)
const response = await ecencyApi.get(`private-api/leaderboard/${duration}`);
const rawData = response.data;
if (!rawData || !isArray(rawData)) {
@ -413,10 +405,10 @@ export const getLeaderboard = async (duration: 'day' | 'week' | 'month') => {
}
return rawData;
} catch (error) {
bugsnagInstance.notify(error)
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* fetches notifications from ecency server using filter and since props
@ -424,62 +416,59 @@ export const getLeaderboard = async (duration: 'day' | 'week' | 'month') => {
* @returns array of notifications
*/
export const getNotifications = async (data: {
filter?: "rvotes" | "mentions" | "follows" | "replies" | "reblogs" | "transfers" | "delegations",
since?: string
filter?: 'rvotes' | 'mentions' | 'follows' | 'replies' | 'reblogs' | 'transfers' | 'delegations';
since?: string;
}) => {
try {
const response = await ecencyApi.post(`/private-api/notifications`, data);
const response = await ecencyApi.post('/private-api/notifications', data);
return response.data;
} catch (error) {
console.warn("Failed to get notifications", error)
bugsnagInstance.notify(error)
console.warn('Failed to get notifications', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
export const getUnreadNotificationCount = async (accessToken?: string) => {
try {
const data = accessToken ? { code: accessToken } : {}
const response = await ecencyApi.post(`/private-api/notifications/unread`, data)
const data = accessToken ? { code: accessToken } : {};
const response = await ecencyApi.post('/private-api/notifications/unread', data);
return response.data ? response.data.count : 0;
} catch (error) {
bugsnagInstance.notify(error);
return 0;
}
}
};
export const markNotifications = async (id: string | null = null) => {
try {
const data = id ? { id } : {};
const response = await ecencyApi.post((`/private-api/notifications/mark`), data);
return response.data
const response = await ecencyApi.post('/private-api/notifications/mark', data);
return response.data;
} catch (error) {
bugsnagInstance.notify(error);
throw error
throw error;
}
};
export const setPushToken = async (data, accessToken = null) => {
try {
if (!data.username) {
console.log("skipping push token setting, as no user is provided")
console.log('skipping push token setting, as no user is provided');
return;
}
if (accessToken) {
data.code = accessToken
data.code = accessToken;
}
const res = await await ecencyApi.post((`/private-api/register-device`), data);
const res = await await ecencyApi.post('/private-api/register-device', data);
return res.data;
} catch (error) {
console.warn("Failed to set push token on server")
console.warn('Failed to set push token on server');
bugsnagInstance.notify(error);
}
}
};
/**
* ************************************
@ -488,22 +477,21 @@ export const setPushToken = async (data, accessToken = null) => {
*/
export const search = async (data: {
q: string,
sort: string,
hideLow: string,
since?: string,
scroll_id?: string
q: string;
sort: string;
hideLow: string;
since?: string;
scroll_id?: string;
}) => {
try {
const response = await ecencyApi.post('/search-api/search', data);
return response.data;
} catch (error) {
console.warn("Search failed", error);
console.warn('Search failed', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
*
@ -516,12 +504,11 @@ export const searchPath = async (q: string) => {
const response = await ecencyApi.post('/search-api/search-path', data);
return response.data;
} catch (error) {
console.warn("path search failed", error)
console.warn('path search failed', error);
bugsnagInstance.notify(error);
throw error
throw error;
}
}
};
/**
*
@ -536,16 +523,15 @@ export const searchAccount = async (q: string = '', limit: number = 20, random:
q,
limit,
random,
}
const response = await ecencyApi.post(`/search-api/search-account`, data)
};
const response = await ecencyApi.post('/search-api/search-account', data);
return response.data;
} catch (error) {
console.warn("account search failed", error)
console.warn('account search failed', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
*
@ -560,17 +546,15 @@ export const searchTag = async (q: string = '', limit: number = 20, random: numb
q,
limit,
random,
}
const response = await ecencyApi.post(`/search-api/search-tag`, data);
};
const response = await ecencyApi.post('/search-api/search-tag', data);
return response.data;
} catch (error) {
console.warn("tag search failed", error)
console.warn('tag search failed', error);
bugsnagInstance.notify(error);
throw error
throw error;
}
}
};
/**
* ************************************
@ -594,7 +578,7 @@ export const addSchedule = async (
body: string,
meta: any,
options: any,
scheduleDate: string
scheduleDate: string,
) => {
try {
const data = {
@ -605,16 +589,15 @@ export const addSchedule = async (
schedule: scheduleDate,
options,
reblog: 0,
}
const response = await ecencyApi
.post('/private-api/schedules-add', data)
};
const response = await ecencyApi.post('/private-api/schedules-add', data);
return response.data;
} catch (error) {
console.warn("Failed to add post to schedule", error)
console.warn('Failed to add post to schedule', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* Fetches all scheduled posts against current user
@ -622,14 +605,14 @@ export const addSchedule = async (
*/
export const getSchedules = async () => {
try {
const response = await ecencyApi.post(`/private-api/schedules`)
return response.data;
const response = await ecencyApi.post('/private-api/schedules');
return response.data || [];
} catch (error) {
console.warn("Failed to get schedules")
bugsnagInstance.notify(error)
console.warn('Failed to get schedules');
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* Removes post from scheduled posts using post id;
@ -639,14 +622,14 @@ export const getSchedules = async () => {
export const deleteScheduledPost = async (id: string) => {
try {
const data = { id };
const response = await ecencyApi.post(`/private-api/schedules-delete`, data);
return response;
const response = await ecencyApi.post('/private-api/schedules-delete', data);
return response.data || [];
} catch (error) {
console.warn("Failed to delete scheduled post")
bugsnagInstance.notify(error)
console.warn('Failed to delete scheduled post');
bugsnagInstance.notify(error);
throw error;
}
}
};
/**
* Moves scheduled post to draft using schedule id
@ -655,15 +638,15 @@ export const deleteScheduledPost = async (id: string) => {
*/
export const moveScheduledToDraft = async (id: string) => {
try {
const data = { id }
const response = await ecencyApi.post(`/private-api/schedules-move`, data);
const data = { id };
const response = await ecencyApi.post('/private-api/schedules-move', data);
return response.data;
} catch (error) {
console.warn("Failed to move scheduled post to drafts")
bugsnagInstance.notify(error)
console.warn('Failed to move scheduled post to drafts');
bugsnagInstance.notify(error);
throw error;
}
}
};
// Old image service
/**
@ -672,40 +655,39 @@ export const moveScheduledToDraft = async (id: string) => {
* ************************************
*/
export const getImages = async () => {
try {
const response = await ecencyApi.post('/private-api/images')
const response = await ecencyApi.post('/private-api/images');
return response.data;
} catch (error) {
console.warn('Failed to get images', error);
bugsnagInstance.notify(error);
}
}
};
export const addImage = async (url: string) => {
try {
const data = { url };
const response = await ecencyApi.post(`/private-api/images-add`, data);
const response = await ecencyApi.post('/private-api/images-add', data);
return response.data;
} catch (error) {
console.warn('Failed to add image', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
export const deleteImage = async (id: string) => {
try {
const data = { id };
const response = await ecencyApi.post(`/private-api/images-delete`, data);
const response = await ecencyApi.post('/private-api/images-delete', data);
return response.data;
} catch (error) {
console.warn('Failed to delete image', error);
bugsnagInstance.notify(error);
throw error;
}
}
};
export const uploadImage = async (media, username, sign, uploadProgress = null) => {
try {
@ -719,15 +701,14 @@ export const uploadImage = async (media, username, sign, uploadProgress = null)
const fData = new FormData();
fData.append('file', file);
const res = await upload(fData, username, sign, uploadProgress)
const res = await upload(fData, username, sign, uploadProgress);
if (!res || !res.data) {
throw new Error("Returning response missing media data");
throw new Error('Returning response missing media data');
}
return res;
} catch (error) {
console.warn("Image upload failed", error)
return { error }
console.warn('Image upload failed', error);
return { error };
}
};
@ -735,8 +716,6 @@ export const uploadImage = async (media, username, sign, uploadProgress = null)
export const getNodes = () => serverList.get().then((resp) => resp.data.hived || SERVER_LIST);
/**
* refreshes access token using refresh token
* @param code refresh token
@ -746,16 +725,14 @@ export const getSCAccessToken = async (code: string) => {
try {
const response = await ecencyApi.post('/auth-api/hs-token-refresh', {
code,
})
});
return response.data;
} catch (error) {
console.warn("failed to refresh token")
console.warn('failed to refresh token');
bugsnagInstance.notify(error);
throw error
throw error;
}
}
};
/**
* fetches promoted posts for tab content
@ -771,29 +748,24 @@ export const getPromotedEntries = async (username: string) => {
);
});
} catch (error) {
console.warn("Failed to get promoted enties")
console.warn('Failed to get promoted enties');
bugsnagInstance.notify(error);
return error;
}
};
export const purchaseOrder = (data) =>
api
.post('/purchase-order', data)
.then((resp) => resp.data)
.catch((error) => bugsnagInstance.notify(error));
export const getPostReblogs = (data) =>
api
.get(`/post-reblogs/${data.author}/${data.permlink}`)
.then((resp) => resp.data)
.catch((error) => bugsnagInstance.notify(error));
/**
* Registers new user with ecency and hive, on confirmation sends
* details to user email
@ -807,8 +779,8 @@ export const signUp = async (username: string, email: string, referral?: string)
const data = {
username,
email,
referral
}
referral,
};
const response = await ecencyApi.post('/private-api/account-create', data);
return response.status === 202;
} catch (error) {
@ -823,25 +795,29 @@ export const signUp = async (username: string, email: string, referral?: string)
* ************************************
*/
export const getReferralsList = async (username: string, maxId: number | undefined): Promise<Referral[]> => {
export const getReferralsList = async (
username: string,
maxId: number | undefined,
): Promise<Referral[]> => {
try {
const res = await ecencyApi.get(`/private-api/referrals/${username}`, {
params: {
max_id: maxId
}
max_id: maxId,
},
});
console.log('Referrals List', username, res.data);
if (!res.data) {
throw new Error('No Referrals for this user!');
}
const referralsList = res.data.length > 0 ? res.data.map((referralItem: any) => convertReferral(referralItem)) : [];
const referralsList =
res.data.length > 0 ? res.data.map((referralItem: any) => convertReferral(referralItem)) : [];
return referralsList;
} catch (error) {
bugsnagInstance.notify(error);
console.warn(error);
throw error;
}
}
};
export const getReferralsStats = async (username: string): Promise<ReferralStat> => {
try {
@ -856,7 +832,7 @@ export const getReferralsStats = async (username: string): Promise<ReferralStat>
console.warn(error);
throw error;
}
}
};
/**
* ************************************
@ -864,12 +840,15 @@ export const getReferralsStats = async (username: string): Promise<ReferralStat>
* ************************************
*/
export const getCommentHistory = async (author: string, permlink: string): Promise<CommentHistoryItem[]> => {
export const getCommentHistory = async (
author: string,
permlink: string,
): Promise<CommentHistoryItem[]> => {
try {
const data = {
author,
permlink
}
permlink,
};
const res = await ecencyApi.post('/private-api/comment-history', data);
console.log('comment history', res.data);
if (!res.data) {
@ -880,5 +859,4 @@ export const getCommentHistory = async (author: string, permlink: string): Promi
bugsnagInstance.notify(error);
throw error;
}
}
};

View File

@ -0,0 +1,106 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useIntl } from 'react-intl';
import { useAppDispatch } from '../../hooks';
import { toastNotification } from '../../redux/actions/uiAction';
import {
deleteDraft,
deleteScheduledPost,
getDrafts,
getSchedules,
moveScheduledToDraft,
} from '../ecency/ecency';
import QUERIES from './queryKeys';
/** hook used to return user drafts */
export const useGetDraftsQuery = () => {
return useQuery([QUERIES.DRAFTS.GET], _getDrafts);
};
/** used to return user schedules */
export const useGetSchedulesQuery = () => {
return useQuery([QUERIES.SCHEDULES.GET], _getSchedules);
};
export const useDraftDeleteMutation = () => {
const queryClient = useQueryClient();
const dispatch = useAppDispatch();
const intl = useIntl();
return useMutation(deleteDraft, {
retry: 3,
onSuccess: (data) => {
console.log('Success draft delete', data);
queryClient.setQueryData([QUERIES.DRAFTS.GET], _sortData(data));
},
onError: () => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
},
});
};
export const useScheduleDeleteMutation = () => {
const queryClient = useQueryClient();
const dispatch = useAppDispatch();
const intl = useIntl();
return useMutation(deleteScheduledPost, {
retry: 3,
onSuccess: (data) => {
console.log('Success scheduled post delete', data);
queryClient.setQueryData([QUERIES.SCHEDULES.GET], _sortData(data));
},
onError: () => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
},
});
};
export const useMoveScheduleToDraftsMutation = () => {
const queryClient = useQueryClient();
const dispatch = useAppDispatch();
const intl = useIntl();
return useMutation(moveScheduledToDraft, {
retry: 3,
onSuccess: (data) => {
console.log('Moved to drafts data', data);
queryClient.setQueryData([QUERIES.SCHEDULES.GET], _sortData(data));
queryClient.invalidateQueries([QUERIES.DRAFTS.GET]);
dispatch(toastNotification(intl.formatMessage({ id: 'alert.success_moved' })));
},
onError: () => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
},
});
};
const _getDrafts = async () => {
try {
const data = await getDrafts();
return _sortData(data || []);
} catch (err) {
throw new Error('draft.load_error');
}
};
const _getSchedules = async () => {
try {
const data = await getSchedules();
return _sortDataS(data);
} catch (err) {
throw new Error('drafts.load_error');
}
};
const _sortDataS = (data) =>
data.sort((a, b) => {
const dateA = new Date(a.schedule).getTime();
const dateB = new Date(b.schedule).getTime();
return dateB > dateA ? 1 : -1;
});
const _sortData = (data) =>
data.sort((a, b) => {
const dateA = new Date(a.created).getTime();
const dateB = new Date(b.created).getTime();
return dateB > dateA ? 1 : -1;
});

View File

@ -0,0 +1,24 @@
import { QueryClient } from '@tanstack/react-query';
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import AsyncStorage from '@react-native-community/async-storage';
import { PersistQueryClientProviderProps } from '@tanstack/react-query-persist-client';
export const initQueryClient = () => {
const asyncStoragePersister = createAsyncStoragePersister({
storage: AsyncStorage,
});
const client = new QueryClient({
//Query client configurations go here...
defaultOptions: {
queries: {
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
});
return {
client,
persistOptions: { persister: asyncStoragePersister },
} as PersistQueryClientProviderProps;
};

View File

@ -0,0 +1,10 @@
const QUERIES = {
DRAFTS: {
GET: 'QUERY_GET_DRAFTS',
},
SCHEDULES: {
GET: 'QUERY_GET_SCHEDULES',
},
};
export default QUERIES;

View File

@ -1,159 +0,0 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Alert } from 'react-native';
import { injectIntl } from 'react-intl';
// Services and Actions
import {
getDrafts,
deleteDraft,
getSchedules,
moveScheduledToDraft,
deleteScheduledPost,
} from '../../../providers/ecency/ecency';
import { toastNotification } from '../../../redux/actions/uiAction';
// Middleware
// Constants
import { default as ROUTES } from '../../../constants/routeNames';
// Utilities
// Component
import DraftsScreen from '../screen/draftsScreen';
const DraftsContainer = ({ currentAccount, intl, navigation, dispatch, route }) => {
const [drafts, setDrafts] = useState([]);
const [schedules, setSchedules] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [initialTabIndex] = useState(route.params?.showSchedules ? 1 : 0);
useEffect(() => {
_getDrafts();
_getSchedules();
}, []);
// Component Functions
const _getSchedules = () => {
setIsLoading(true);
getSchedules()
.then((data) => {
setSchedules(_sortDataS(data));
setIsLoading(false);
})
.catch(() => {
Alert.alert(intl.formatMessage({ id: 'drafts.load_error' }));
setIsLoading(false);
});
};
const _getDrafts = () => {
setIsLoading(true);
getDrafts()
.then((data) => {
setDrafts(_sortData(data));
setIsLoading(false);
})
.catch(() => {
Alert.alert(intl.formatMessage({ id: 'drafts.load_error' }));
setIsLoading(false);
});
};
const _removeDraft = (id) => {
deleteDraft(id)
.then(() => {
const newDrafts = [...drafts].filter((draft) => draft._id !== id);
setDrafts(_sortData(newDrafts));
})
.catch(() => {
Alert.alert(intl.formatMessage({ id: 'alert.fail' }));
});
};
const _removeSchedule = (id) => {
deleteScheduledPost(id)
.then((res) => {
const newSchedules = [...schedules].filter((schedule) => schedule._id !== id);
setSchedules(_sortDataS(newSchedules));
})
.catch(() => {
Alert.alert(intl.formatMessage({ id: 'alert.fail' }));
});
};
const _moveScheduleToDraft = (id) => {
moveScheduledToDraft(id)
.then((res) => {
dispatch(
toastNotification(
intl.formatMessage({
id: 'alert.success_moved',
}),
),
);
_getDrafts();
_getSchedules();
})
.catch((error) => {
console.warn('Failed to move scheduled post to drafts');
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
});
};
const _editDraft = (id) => {
const selectedDraft = drafts.find((draft) => draft._id === id);
navigation.navigate({
name: ROUTES.SCREENS.EDITOR,
key: `editor_draft_${id}`,
params: {
draft: selectedDraft,
fetchPost: _getDrafts,
},
});
};
const _sortData = (data) =>
data.sort((a, b) => {
const dateA = new Date(a.created).getTime();
const dateB = new Date(b.created).getTime();
return dateB > dateA ? 1 : -1;
});
const _sortDataS = (data) =>
data.sort((a, b) => {
const dateA = new Date(a.schedule).getTime();
const dateB = new Date(b.schedule).getTime();
return dateB > dateA ? 1 : -1;
});
return (
<DraftsScreen
isLoading={isLoading}
editDraft={_editDraft}
currentAccount={currentAccount}
drafts={drafts}
schedules={schedules}
removeDraft={_removeDraft}
moveScheduleToDraft={_moveScheduleToDraft}
removeSchedule={_removeSchedule}
initialTabIndex={initialTabIndex}
/>
);
};
const mapStateToProps = (state) => ({
currentAccount: state.account.currentAccount,
});
export default injectIntl(connect(mapStateToProps)(DraftsContainer));

View File

@ -0,0 +1,93 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
// Services and Actions
import {
useDraftDeleteMutation,
useGetDraftsQuery,
useGetSchedulesQuery,
useMoveScheduleToDraftsMutation,
useScheduleDeleteMutation,
} from '../../../providers/queries/draftQueries';
// Middleware
// Constants
import { default as ROUTES } from '../../../constants/routeNames';
// Utilities
// Component
import DraftsScreen from '../screen/draftsScreen';
const DraftsContainer = ({ currentAccount, navigation, route }) => {
const { mutate: deleteDraft, isLoading: isDeletingDraft } = useDraftDeleteMutation();
const { mutate: deleteSchedule, isLoading: isDeletingSchedule } = useScheduleDeleteMutation();
const {
mutate: moveScheduleToDrafts,
isLoading: isMovingToDrafts,
} = useMoveScheduleToDraftsMutation();
const {
isLoading: isLoadingDrafts,
data: drafts = [],
isFetching: isFetchingDrafts,
refetch: refetchDrafts,
} = useGetDraftsQuery();
const {
isLoading: isLoadingSchedules,
data: schedules = [],
isFetching: isFetchingSchedules,
refetch: refetchSchedules,
} = useGetSchedulesQuery();
const [initialTabIndex] = useState(route.params?.showSchedules ? 1 : 0);
// Component Functions
const _onRefresh = () => {
refetchDrafts();
refetchSchedules();
};
const _editDraft = (id: string) => {
const selectedDraft = drafts.find((draft) => draft._id === id);
navigation.navigate({
name: ROUTES.SCREENS.EDITOR,
key: `editor_draft_${id}`,
params: {
draft: selectedDraft,
fetchPost: refetchDrafts,
},
});
};
const _isLoading =
isLoadingDrafts || isLoadingSchedules || isFetchingDrafts || isFetchingSchedules;
const _isDeleting = isDeletingDraft || isDeletingSchedule || isMovingToDrafts;
return (
<DraftsScreen
isLoading={_isLoading}
isDeleting={_isDeleting}
editDraft={_editDraft}
currentAccount={currentAccount}
drafts={drafts}
schedules={schedules}
removeDraft={deleteDraft}
moveScheduleToDraft={moveScheduleToDrafts}
removeSchedule={deleteSchedule}
onRefresh={_onRefresh}
initialTabIndex={initialTabIndex}
/>
);
};
const mapStateToProps = (state) => ({
currentAccount: state.account.currentAccount,
});
export default injectIntl(connect(mapStateToProps)(DraftsContainer));

View File

@ -1,6 +1,6 @@
import React, { useState, useRef } from 'react';
import React from 'react';
import { injectIntl } from 'react-intl';
import { View, FlatList, Text, Platform } from 'react-native';
import { View, FlatList, Text, Platform, RefreshControl } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view';
// Utils
@ -14,7 +14,7 @@ import { BasicHeader, TabBar, DraftListItem, PostCardPlaceHolder } from '../../.
// Styles
import globalStyles from '../../../globalStyles';
import styles from './draftStyles';
import { OptionsModal } from '../../../components/atoms';
import { useAppSelector } from '../../../hooks';
const DraftsScreen = ({
currentAccount,
@ -22,20 +22,15 @@ const DraftsScreen = ({
editDraft,
removeSchedule,
isLoading,
isDeleting,
onRefresh,
intl,
drafts,
schedules,
moveScheduleToDraft,
initialTabIndex,
}) => {
const [selectedId, setSelectedId] = useState(null);
const ActionSheetRef = useRef(null);
const _onActionPress = (index) => {
if (index === 0) {
moveScheduleToDraft(selectedId);
}
};
const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme);
// Component Functions
const _renderItem = (item, type) => {
@ -53,12 +48,7 @@ const DraftsScreen = ({
const isSchedules = type === 'schedules';
const _onItemPress = () => {
if (isSchedules) {
setSelectedId(item._id);
if (ActionSheetRef.current) {
ActionSheetRef.current.show();
}
} else {
if (!isSchedules) {
editDraft(item._id);
}
};
@ -75,11 +65,13 @@ const DraftsScreen = ({
username={currentAccount.name}
reputation={currentAccount.reputation}
handleOnPressItem={_onItemPress}
handleOnMovePress={moveScheduleToDraft}
handleOnRemoveItem={isSchedules ? removeSchedule : removeDraft}
id={item._id}
key={item._id}
status={item.status}
isSchedules={isSchedules}
isDeleting={isDeleting}
/>
);
};
@ -111,6 +103,16 @@ const DraftsScreen = ({
removeClippedSubviews={false}
renderItem={({ item }) => _renderItem(item, type)}
ListEmptyComponent={_renderEmptyContent()}
refreshControl={
<RefreshControl
refreshing={isLoading}
onRefresh={onRefresh}
progressBackgroundColor="#357CE6"
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
titleColor="#fff"
colors={['#fff']}
/>
}
/>
</View>
);
@ -152,22 +154,6 @@ const DraftsScreen = ({
{_getTabItem(schedules, 'schedules')}
</View>
</ScrollableTabView>
<OptionsModal
ref={ActionSheetRef}
title={intl.formatMessage({
id: 'alert.move_question',
})}
options={[
intl.formatMessage({
id: 'alert.move',
}),
intl.formatMessage({
id: 'alert.cancel',
}),
]}
cancelButtonIndex={1}
onPress={_onActionPress}
/>
</View>
);
};

View File

@ -2,53 +2,58 @@ import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'rea
import { useIntl } from 'react-intl';
import { View } from 'react-native';
import { BeneficiarySelectionContent, DateTimePicker, MainButton, Modal, SettingsItem } from '../../../components';
import styles from './postOptionsModalStyles';
import ThumbSelectionContent from './thumbSelectionContent';
import { View as AnimatedView } from 'react-native-animatable';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import {
BeneficiarySelectionContent,
DateTimePicker,
MainButton,
Modal,
SettingsItem,
} from '../../../components';
import styles from './postOptionsModalStyles';
import ThumbSelectionContent from './thumbSelectionContent';
const REWARD_TYPES = [
{
key: 'default',
intlId:'editor.reward_default'
intlId: 'editor.reward_default',
},
{
key: 'sp',
intlId:'editor.reward_power_up'
intlId: 'editor.reward_power_up',
},
{
key: 'dp',
intlId:'editor.reward_decline'
intlId: 'editor.reward_decline',
},
]
];
export interface PostOptionsModalRef {
showModal: () => void;
}
interface PostOptionsModalProps {
body: string;
draftId: string;
thumbIndex:number,
thumbUrl: string;
isEdit: boolean;
isCommunityPost: boolean;
rewardType: string;
isUploading: boolean;
handleRewardChange: (rewardType: string) => void;
handleThumbSelection:(index:number)=>void;
handleThumbSelection: (url: string) => void;
handleScheduleChange: (datetime: string | null) => void;
handleShouldReblogChange: (shouldReblog: boolean) => void;
handleFormUpdate: () => void;
}
const PostOptionsModal = forwardRef(({
const PostOptionsModal = forwardRef(
(
{
body,
draftId,
thumbIndex,
thumbUrl,
isEdit,
isCommunityPost,
rewardType,
@ -57,8 +62,10 @@ const PostOptionsModal = forwardRef(({
handleThumbSelection,
handleScheduleChange,
handleShouldReblogChange,
handleFormUpdate
}: PostOptionsModalProps, ref) => {
handleFormUpdate,
}: PostOptionsModalProps,
ref,
) => {
const intl = useIntl();
const [showModal, setShowModal] = useState(false);
@ -72,29 +79,29 @@ const PostOptionsModal = forwardRef(({
useEffect(() => {
if (!scheduleLater) {
handleScheduleChange(null)
handleScheduleChange(null);
} else if (scheduledFor) {
handleScheduleChange(scheduledFor)
handleScheduleChange(scheduledFor);
}
}, [scheduleLater, scheduledFor])
}, [scheduleLater, scheduledFor]);
useEffect(() => {
handleShouldReblogChange(shouldReblog)
}, [shouldReblog])
handleShouldReblogChange(shouldReblog);
}, [shouldReblog]);
useEffect(() => {
if (!isCommunityPost && shouldReblog) {
setShouldReblog(false);
}
}, [isCommunityPost])
}, [isCommunityPost]);
// load rewardtype from props if it is already saved in drafts
useEffect(() => {
if (rewardType) {
let rewardTypeKey = REWARD_TYPES.findIndex((item) => item.key === rewardType)
let rewardTypeKey = REWARD_TYPES.findIndex((item) => item.key === rewardType);
setRewardTypeIndex(rewardTypeKey);
}
},[rewardType])
}, [rewardType]);
useImperativeHandle(ref, () => ({
show: () => {
@ -102,28 +109,27 @@ const PostOptionsModal = forwardRef(({
},
}));
const _handleRewardChange = (index: number) => {
setRewardTypeIndex(index)
const rewardTypeKey = REWARD_TYPES[index].key
setRewardTypeIndex(index);
const rewardTypeKey = REWARD_TYPES[index].key;
if (handleRewardChange) {
handleRewardChange(rewardTypeKey);
}
}
};
const _handleDatePickerChange = (date: string) => {
setScheduledFor(date);
}
};
const _onDonePress = () => {
setShowModal(false);
handleFormUpdate();
}
};
// handle index change here instead of useeffetc
const _handleThumbIndexSelection = (index:number) => {
handleThumbSelection(index)
}
const _handleThumbIndexSelection = (url: string) => {
handleThumbSelection(url);
};
const _renderContent = () => (
<View style={styles.fillSpace}>
@ -136,8 +142,8 @@ const PostOptionsModal = forwardRef(({
type="dropdown"
actionType="reward"
options={[
intl.formatMessage({id:"editor.scheduled_immediate"}),
intl.formatMessage({id:"editor.scheduled_later"}),
intl.formatMessage({ id: 'editor.scheduled_immediate' }),
intl.formatMessage({ id: 'editor.scheduled_later' }),
]}
selectedOptionIndex={scheduleLater ? 1 : 0}
handleOnChange={(index) => {
@ -156,7 +162,6 @@ const PostOptionsModal = forwardRef(({
disabled={true}
/>
</AnimatedView>
)}
<SettingsItem
@ -165,14 +170,11 @@ const PostOptionsModal = forwardRef(({
})}
type="dropdown"
actionType="reward"
options={
REWARD_TYPES.map((type)=>intl.formatMessage({ id: type.intlId}))
}
options={REWARD_TYPES.map((type) => intl.formatMessage({ id: type.intlId }))}
selectedOptionIndex={rewardTypeIndex}
handleOnChange={_handleRewardChange}
/>
{isCommunityPost && (
<SettingsItem
title={intl.formatMessage({
@ -187,22 +189,16 @@ const PostOptionsModal = forwardRef(({
</>
)}
<ThumbSelectionContent
body={body}
thumbIndex={thumbIndex}
thumbUrl={thumbUrl}
isUploading={isUploading}
onThumbSelection={_handleThumbIndexSelection}
/>
{!isEdit && (
<BeneficiarySelectionContent
draftId={draftId}
setDisableDone={setDisableDone}
/>
<BeneficiarySelectionContent draftId={draftId} setDisableDone={setDisableDone} />
)}
</View>
</KeyboardAwareScrollView>
@ -210,13 +206,10 @@ const PostOptionsModal = forwardRef(({
style={{ ...styles.saveButton }}
isDisable={disableDone}
onPress={_onDonePress}
text={intl.formatMessage({id:"editor.done"})}
text={intl.formatMessage({ id: 'editor.done' })}
/>
</View>
)
);
return (
<Modal
@ -228,14 +221,14 @@ const PostOptionsModal = forwardRef(({
isFullScreen
isCloseButton
presentationStyle="formSheet"
title={intl.formatMessage({id:"editor.settings_title"})}
title={intl.formatMessage({ id: 'editor.settings_title' })}
animationType="slide"
style={styles.modalStyle}
>
{_renderContent()}
</Modal>
);
});
},
);
export default PostOptionsModal
export default PostOptionsModal;

View File

@ -1,16 +1,14 @@
import { ViewStyle } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import { getBottomSpace } from 'react-native-iphone-x-helper';
export default EStyleSheet.create({
sheetContent: {
backgroundColor: '$primaryBackgroundColor',
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
zIndex:999
zIndex: 999,
},
thumbStyle: {
width: 72,
@ -18,11 +16,14 @@ export default EStyleSheet.create({
marginVertical: 8,
marginRight: 8,
borderRadius: 12,
backgroundColor:'$primaryLightGray'
backgroundColor: '$primaryLightGray',
},
selectedStyle:{
borderWidth:4,
borderColor:'$primaryBlack'
checkContainer: {
position: 'absolute',
top: 12,
left: 6,
backgroundColor: '$pureWhite',
borderRadius: 12,
},
settingLabel: {
color: '$primaryDarkGray',
@ -35,7 +36,7 @@ export default EStyleSheet.create({
paddingBottom: getBottomSpace() + 16,
},
container: {
paddingVertical:16
paddingVertical: 16,
},
bodyWrapper: { flex: 1, paddingTop: 20, paddingBottom: 20 },
inputWrapper: { flexDirection: 'row', alignItems: 'center' },
@ -55,5 +56,5 @@ export default EStyleSheet.create({
doneButton: { borderRadius: 16, backgroundColor: '$primaryBlue' },
thumbSelectContainer: {
marginTop: 12,
}
},
});

View File

@ -1,73 +1,96 @@
import React, { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
import { ActivityIndicator, Alert, Text, TouchableOpacity, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import { FlatList } from 'react-native-gesture-handler';
import ESStyleSheet from 'react-native-extended-stylesheet';
import EStyleSheet from 'react-native-extended-stylesheet';
import { View as AnimatedView } from 'react-native-animatable';
import { extractImageUrls } from '../../../utils/editor';
import styles from './styles';
import ESStyleSheet from 'react-native-extended-stylesheet';
import { Icon } from '../../../components';
interface ThumbSelectionContentProps {
body: string;
thumbIndex: number;
thumbUrl: string;
isUploading: boolean;
onThumbSelection: (index: number) => void;
onThumbSelection: (url: string) => void;
}
const ThumbSelectionContent = ({ body, thumbIndex, onThumbSelection, isUploading }: ThumbSelectionContentProps) => {
const ThumbSelectionContent = ({
body,
thumbUrl,
onThumbSelection,
isUploading,
}: ThumbSelectionContentProps) => {
const intl = useIntl();
const [imageUrls, setImageUrls] = useState<string[]>([]);
const [needMore, setNeedMore] = useState(true);
const [thumbIndex, setThumbIndex] = useState(0);
useEffect(() => {
const urls = extractImageUrls({ body });
if (urls.length < 2) {
setNeedMore(true);
onThumbSelection(0);
setImageUrls([])
onThumbSelection(urls[0] || '');
setThumbIndex(0);
setImageUrls([]);
} else {
setNeedMore(false);
setImageUrls(urls)
setImageUrls(urls);
}
}, [body])
const _urlIndex = urls.indexOf(thumbUrl);
if (_urlIndex < 0) {
onThumbSelection(urls[0] || '');
setThumbIndex(0);
} else {
setThumbIndex(_urlIndex);
}
}, [body]);
//VIEW_RENDERERS
const _renderImageItem = ({ item, index }: { item: string, index: number }) => {
const _renderImageItem = ({ item, index }: { item: string; index: number }) => {
const _onPress = () => {
onThumbSelection(index);
}
onThumbSelection(item);
setThumbIndex(index);
};
const selectedStyle = index === thumbIndex ? styles.selectedStyle : null
const isSelected = item === thumbUrl && index === thumbIndex;
return (
<TouchableOpacity onPress={() => _onPress()}>
<FastImage
source={{ uri: item }}
style={{ ...styles.thumbStyle, ...selectedStyle }}
resizeMode='cover'
<FastImage source={{ uri: item }} style={styles.thumbStyle} resizeMode="cover" />
{isSelected && (
<AnimatedView duration={300} animation="zoomIn" style={styles.checkContainer}>
<Icon
color={EStyleSheet.value('$primaryBlue')}
iconType="MaterialCommunityIcons"
name="checkbox-marked-circle"
size={20}
/>
</AnimatedView>
)}
</TouchableOpacity>
)
}
);
};
const _renderHeader = () => (
isUploading &&
const _renderHeader = () =>
isUploading && (
<View style={{ flex: 1, justifyContent: 'center', marginRight: 16 }}>
<ActivityIndicator color={ESStyleSheet.value('$primaryBlack')} />
</View>
)
);
return (
<View style={styles.thumbSelectContainer}>
<Text style={styles.settingLabel}>{intl.formatMessage({ id: 'editor.select_thumb' })}</Text>
{
needMore ? (
<Text style={styles.contentLabel}>{intl.formatMessage({ id: 'editor.add_more_imgs' })}</Text>
{needMore ? (
<Text style={styles.contentLabel}>
{intl.formatMessage({ id: 'editor.add_more_imgs' })}
</Text>
) : (
<FlatList
data={imageUrls}
@ -76,10 +99,9 @@ const ThumbSelectionContent = ({ body, thumbIndex, onThumbSelection, isUploading
keyExtractor={(item, index) => `${item}-${index}`}
horizontal={true}
contentContainerStyle={styles.listContainer}
showsHorizontalScrollIndicator={false} />
)
}
showsHorizontalScrollIndicator={false}
/>
)}
</View>
);
};

View File

@ -1,22 +1,20 @@
import React, { useImperativeHandle, useRef, useState } from 'react';
import React, { useImperativeHandle, useRef, useState, forwardRef } from 'react';
import { FlatList } from 'react-native-gesture-handler';
import ActionSheet from 'react-native-actions-sheet';
import EStyleSheet from 'react-native-extended-stylesheet';
import styles from './styles';
import { extractImageUrls } from '../../../utils/editor';
import FastImage from 'react-native-fast-image';
import { forwardRef } from 'react';
import { View, Text, Alert, TouchableOpacity } from 'react-native';
import { useIntl } from 'react-intl';
import { extractImageUrls } from '../../../utils/editor';
import styles from './styles';
export interface ThumbSelectionModalProps {
thumbIndex:number;
thumbUrl: string;
onThumbSelection: (index: number) => void;
}
const ThumbSelectionModal = ({ onThumbSelection, thumbIndex }:ThumbSelectionModalProps, ref) => {
const ThumbSelectionModal = ({ onThumbSelection, thumbUrl }: ThumbSelectionModalProps, ref) => {
const intl = useIntl();
const [imageUrls, setImageUrls] = useState<string[]>([]);
@ -25,51 +23,45 @@ const ThumbSelectionModal = ({ onThumbSelection, thumbIndex }:ThumbSelectionModa
//CALLBACK_METHODS
useImperativeHandle(ref, () => ({
show: (postBody: string) => {
console.log("Showing action modal")
console.log('Showing action modal');
const urls = extractImageUrls({ body: postBody });
if (urls.length < 2) {
console.log("Skipping modal show as post images are less than 2");
Alert.alert(
intl.formatMessage({id:'editor.two_thumbs_required'})
)
console.log('Skipping modal show as post images are less than 2');
Alert.alert(intl.formatMessage({ id: 'editor.two_thumbs_required' }));
onThumbSelection(0);
return;
}
setImageUrls(urls);
sheetModalRef.current?.setModalVisible(true);
}
},
}));
const _onSelection = (index: number) => {
onThumbSelection(index);
sheetModalRef.current?.setModalVisible(false);
}
};
//VIEW_RENDERERS
const _renderImageItem = ({item, index}:{item:string, index:number}) => {
const _renderImageItem = ({ item, index }: { item: string; index: number }) => {
const _onPress = () => {
_onSelection(index);
}
};
const selectedStyle = index === thumbIndex ? styles.selectedStyle : null
const selectedStyle = item === thumbUrl ? styles.selectedStyle : null;
return (
<TouchableOpacity onPress={() => _onPress()}>
<FastImage
source={{ uri: item }}
style={{ ...styles.thumbStyle, ...selectedStyle }}
resizeMode='cover'
resizeMode="cover"
/>
</TouchableOpacity>
)
}
);
};
const _renderContent = () => {
return (
@ -84,10 +76,8 @@ const ThumbSelectionModal = ({ onThumbSelection, thumbIndex }:ThumbSelectionModa
showsHorizontalScrollIndicator={false}
/>
</View>
)
}
);
};
return (
<ActionSheet

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import { Alert } from 'react-native';
import { Alert, AppState, AppStateStatus } from 'react-native';
import get from 'lodash/get';
import AsyncStorage from '@react-native-community/async-storage';
import { isArray } from 'lodash';
@ -9,13 +9,8 @@ import { isArray } from 'lodash';
// Services and Actions
import { Buffer } from 'buffer';
import {
addDraft,
updateDraft,
getDrafts,
addSchedule,
} from '../../../providers/ecency/ecency';
import { useQueryClient } from '@tanstack/react-query';
import { addDraft, updateDraft, getDrafts, addSchedule } from '../../../providers/ecency/ecency';
import { toastNotification, setRcOffer } from '../../../redux/actions/uiAction';
import {
postContent,
@ -43,7 +38,13 @@ import {
import EditorScreen from '../screen/editorScreen';
import { removeBeneficiaries, setBeneficiaries } from '../../../redux/actions/editorActions';
import { DEFAULT_USER_DRAFT_ID, TEMP_BENEFICIARIES_ID } from '../../../redux/constants/constants';
import { deleteDraftCacheEntry, updateCommentCache, updateDraftCache } from '../../../redux/actions/cacheActions';
import {
deleteDraftCacheEntry,
updateCommentCache,
updateDraftCache,
} from '../../../redux/actions/cacheActions';
import QUERIES from '../../../providers/queries/queryKeys';
import bugsnapInstance from '../../../config/bugsnag';
/*
* Props Name Description Value
@ -54,6 +55,7 @@ import { deleteDraftCacheEntry, updateCommentCache, updateDraftCache } from '../
class EditorContainer extends Component<any, any> {
_isMounted = false;
_updatedDraftFields = null;
_appState = AppState.currentState;
constructor(props) {
super(props);
@ -71,14 +73,12 @@ class EditorContainer extends Component<any, any> {
uploadProgress: 0,
post: null,
uploadedImage: null,
isDraft: false,
community: [],
rewardType: 'default',
sharedSnippetText: null,
onLoadDraftPress: false,
thumbIndex: 0,
thumbUrl: '',
shouldReblog: false,
failedImageUploads: 0,
};
}
@ -105,7 +105,6 @@ class EditorContainer extends Component<any, any> {
this.setState({
draftId: _draft._id,
isDraft: true,
});
this._getStorageDraft(username, isReply, _draft);
}
@ -125,7 +124,7 @@ class EditorContainer extends Component<any, any> {
if (navigationParams.isReply) {
({ isReply } = navigationParams);
if (post) {
draftId = `${currentAccount.name}/${post.author}/${post.permlink}`
draftId = `${currentAccount.name}/${post.author}/${post.permlink}`;
}
this.setState({
@ -173,20 +172,34 @@ class EditorContainer extends Component<any, any> {
this._fetchDraftsForComparison(isReply);
}
this._requestKeyboardFocus();
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
this._isMounted = false;
}
componentDidUpdate(prevProps: Readonly<any>, prevState: Readonly<any>, snapshot?: any): void {
if(prevState.rewardType !== this.state.rewardType || prevProps.beneficiariesMap !== this.props.beneficiariesMap){
componentDidUpdate(prevProps: Readonly<any>, prevState: Readonly<any>): void {
if (
prevState.rewardType !== this.state.rewardType ||
prevProps.beneficiariesMap !== this.props.beneficiariesMap
) {
// update isDraftSaved when reward type or beneficiaries are changed in post options
this._handleFormChanged();
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
this._isMounted = false;
}
_handleAppStateChange = (nextAppState: AppStateStatus) => {
if (this._appState.match(/active|forground/) && nextAppState === 'inactive') {
this._saveCurrentDraft(this._updatedDraftFields);
}
this._appState = nextAppState;
};
_getStorageDraft = async (username, isReply, paramDraft) => {
const { drafts, dispatch } = this.props;
const { drafts } = this.props;
if (isReply) {
const _draft = drafts.get(paramDraft._id);
if (_draft && _draft.body) {
@ -204,15 +217,16 @@ class EditorContainer extends Component<any, any> {
//if _draft is returned and param draft is available, compare timestamp, use latest
//if no draft, use result anayways
if (_localDraft && (!paramDraft || paramDraft.timestamp < _localDraft.updated)) {
const _remoteDraftModifiedAt = paramDraft ? new Date(paramDraft.modified).getTime() : 0;
const _useLocalDraft = _localDraft && _remoteDraftModifiedAt < _localDraft.updated;
if (_useLocalDraft) {
this.setState({
draftPost: {
body: get(_localDraft, 'body', ''),
title: get(_localDraft, 'title', ''),
tags: get(_localDraft, 'tags', '').split(','),
isDraft: paramDraft ? true : false,
draftId: paramDraft ? paramDraft._id : null,
meta: _localDraft.meta ? _localDraft.meta : null
meta: _localDraft.meta ? _localDraft.meta : null,
},
});
this._loadMeta(_localDraft); //load meta from local draft
@ -229,15 +243,13 @@ class EditorContainer extends Component<any, any> {
title: paramDraft.title,
body: paramDraft.body,
tags: _tags,
meta: paramDraft.meta ? paramDraft.meta : null
meta: paramDraft.meta ? paramDraft.meta : null,
},
isDraft: true,
draftId: paramDraft._id,
});
this._loadMeta(paramDraft); //load meta from param draft
}
}
};
@ -248,9 +260,8 @@ class EditorContainer extends Component<any, any> {
const body = draft.body;
if (draft.meta && draft.meta.image) {
const urls = extractImageUrls({ body });
const draftThumbIndex = urls.indexOf(draft.meta.image[0]);
this.setState({
thumbIndex: draftThumbIndex,
thumbUrl: draft.meta.image[0],
});
}
@ -263,11 +274,13 @@ class EditorContainer extends Component<any, any> {
if (draft._id && draft.meta && draft.meta.beneficiaries) {
if (isArray(draft.meta.beneficiaries)) {
const filteredBeneficiaries = draft.meta.beneficiaries.filter((item) => item.account !== currentAccount.username); //remove default beneficiary from array while saving
const filteredBeneficiaries = draft.meta.beneficiaries.filter(
(item) => item.account !== currentAccount.username,
); //remove default beneficiary from array while saving
dispatch(setBeneficiaries(draft._id || TEMP_BENEFICIARIES_ID, filteredBeneficiaries));
}
}
}
};
_requestKeyboardFocus = () => {
//50 ms timeout is added to avoid keyboard not showing up on android
setTimeout(() => {
@ -286,7 +299,7 @@ class EditorContainer extends Component<any, any> {
* @param isReply
**/
_fetchDraftsForComparison = async (isReply) => {
const { currentAccount, isLoggedIn, intl, dispatch, drafts } = this.props;
const { currentAccount, isLoggedIn, drafts } = this.props;
const username = get(currentAccount, 'name', '');
//initilizes editor with reply or non remote id less draft
@ -312,7 +325,7 @@ class EditorContainer extends Component<any, any> {
const remoteDrafts = await getDrafts(username);
const idLessDraft = drafts.get(DEFAULT_USER_DRAFT_ID + username)
const idLessDraft = drafts.get(DEFAULT_USER_DRAFT_ID + username);
const loadRecentDraft = () => {
//if no draft available means local draft is recent
@ -341,7 +354,6 @@ class EditorContainer extends Component<any, any> {
//initilize editor as draft
this.setState({
draftId: _draft._id,
isDraft: true,
});
this._getStorageDraft(username, isReply, _draft);
};
@ -361,21 +373,28 @@ class EditorContainer extends Component<any, any> {
const { draftId } = this.state;
const { beneficiariesMap, currentAccount } = this.props;
return beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID]
|| [{ account: currentAccount.name, weight: 10000 }];
}
return (
beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID] || [
{ account: currentAccount.name, weight: 10000 },
]
);
};
_saveDraftToDB = async (fields, saveAsNew = false) => {
const { isDraftSaved, draftId, thumbIndex, isReply, rewardType } = this.state;
const { currentAccount, dispatch, intl } = this.props;
const { isDraftSaved, draftId, thumbUrl, isReply, rewardType } = this.state;
const { currentAccount, dispatch, intl, queryClient } = this.props;
if (isReply) {
this._saveCurrentDraft(this._updatedDraftFields)
return;
try {
//saves draft locallly
this._saveCurrentDraft(this._updatedDraftFields);
} catch (err) {
console.warn('local draft safe failed, skipping for remote only', err);
bugsnapInstance.notify(err);
}
if (isReply) {
return;
}
const beneficiaries = this._extractBeneficiaries();
@ -396,10 +415,10 @@ class EditorContainer extends Component<any, any> {
};
}
const meta = Object.assign({}, extractMetadata(draftField.body, thumbIndex), {
const meta = Object.assign({}, extractMetadata(draftField.body, thumbUrl), {
tags: draftField.tags,
beneficiaries,
rewardType
rewardType,
});
const jsonMeta = makeJsonMetadata(meta, draftField.tags);
@ -417,7 +436,12 @@ class EditorContainer extends Component<any, any> {
//create new darft otherwise
else if (draftField) {
const response = await addDraft(draftField.title, draftField.body, draftField.tags, jsonMeta);
const response = await addDraft(
draftField.title,
draftField.body,
draftField.tags,
jsonMeta,
);
if (this._isMounted) {
this.setState({
@ -426,17 +450,18 @@ class EditorContainer extends Component<any, any> {
draftId: response._id,
});
}
const filteredBeneficiaries = beneficiaries.filter((item) => item.account !== currentAccount.username); //remove default beneficiary from array while saving
const filteredBeneficiaries = beneficiaries.filter(
(item) => item.account !== currentAccount.username,
); //remove default beneficiary from array while saving
dispatch(setBeneficiaries(response._id, filteredBeneficiaries));
dispatch(removeBeneficiaries(TEMP_BENEFICIARIES_ID));
//clear local copy if darft save is successful
const username = get(currentAccount, 'name', '');
dispatch(deleteDraftCacheEntry(draftId || (DEFAULT_USER_DRAFT_ID + username)))
dispatch(deleteDraftCacheEntry(draftId || DEFAULT_USER_DRAFT_ID + username));
}
dispatch(
toastNotification(
intl.formatMessage({
@ -445,9 +470,10 @@ class EditorContainer extends Component<any, any> {
),
);
//call fetch post to drafts screen
this._navigationBackFetchDrafts();
if (queryClient) {
queryClient.invalidateQueries([QUERIES.DRAFTS.GET]);
}
}
} catch (err) {
console.warn('Failed to save draft to DB: ', err);
@ -456,9 +482,6 @@ class EditorContainer extends Component<any, any> {
isDraftSaving: false,
isDraftSaved: false,
});
//saves draft locally if remote draft save fails
this._saveCurrentDraft(this._updatedDraftFields)
}
dispatch(
@ -471,11 +494,9 @@ class EditorContainer extends Component<any, any> {
}
};
_updateDraftFields = (fields) => {
this._updatedDraftFields = fields;
}
};
_saveCurrentDraft = async (fields) => {
const { draftId, isReply, isEdit, isPostSending } = this.state;
@ -494,15 +515,15 @@ class EditorContainer extends Component<any, any> {
tags: fields.tags && fields.tags.length > 0 ? fields.tags.toString() : '',
author: username,
meta: fields.meta && fields.meta,
}
};
//save reply data
if (isReply && draftField.body !== null) {
dispatch(updateDraftCache(draftId, draftField))
dispatch(updateDraftCache(draftId, draftField));
//save existing draft data locally
} else if (draftId) {
dispatch(updateDraftCache(draftId, draftField))
dispatch(updateDraftCache(draftId, draftField));
}
//update editor data locally
@ -511,12 +532,7 @@ class EditorContainer extends Component<any, any> {
}
};
_submitPost = async ({ fields, scheduleDate }: { fields: any, scheduleDate?: string }) => {
_submitPost = async ({ fields, scheduleDate }: { fields: any; scheduleDate?: string }) => {
const {
currentAccount,
dispatch,
@ -525,11 +541,10 @@ class EditorContainer extends Component<any, any> {
pinCode,
// isDefaultFooter,
} = this.props;
const { rewardType, isPostSending, thumbIndex, draftId, shouldReblog } = this.state;
const { rewardType, isPostSending, thumbUrl, draftId, shouldReblog } = this.state;
const beneficiaries = this._extractBeneficiaries();
if (isPostSending) {
return;
}
@ -539,7 +554,7 @@ class EditorContainer extends Component<any, any> {
isPostSending: true,
});
const meta = extractMetadata(fields.body, thumbIndex);
const meta = extractMetadata(fields.body, thumbUrl);
const _tags = fields.tags.filter((tag) => tag && tag !== ' ');
const jsonMeta = makeJsonMetadata(meta, _tags);
@ -553,7 +568,7 @@ class EditorContainer extends Component<any, any> {
dublicatePost = null;
}
if (dublicatePost && (dublicatePost.permlink === permlink)) {
if (dublicatePost && dublicatePost.permlink === permlink) {
permlink = generatePermlink(fields.title, true);
}
@ -592,29 +607,25 @@ class EditorContainer extends Component<any, any> {
voteWeight,
)
.then((response) => {
console.log(response);
//reblog if flag is active
if (shouldReblog) {
reblog(
currentAccount,
pinCode,
author,
permlink
).then((resp) => {
console.log("Successfully reblogged post", resp)
}).catch((err) => {
console.warn("Failed to reblog post", err)
reblog(currentAccount, pinCode, author, permlink)
.then((resp) => {
console.log('Successfully reblogged post', resp);
})
.catch((err) => {
console.warn('Failed to reblog post', err);
});
}
//post publish updates
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name))
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name));
dispatch(removeBeneficiaries(TEMP_BENEFICIARIES_ID))
dispatch(removeBeneficiaries(TEMP_BENEFICIARIES_ID));
if (draftId) {
dispatch(removeBeneficiaries(draftId))
dispatch(removeBeneficiaries(draftId));
}
dispatch(
@ -632,9 +643,10 @@ class EditorContainer extends Component<any, any> {
ROUTES.SCREENS.PROFILE,
{
username: get(currentAccount, 'name'),
}, {
key: get(currentAccount, 'name')
}
},
{
key: get(currentAccount, 'name'),
},
);
}, 3000);
})
@ -665,7 +677,6 @@ class EditorContainer extends Component<any, any> {
const parentPermlink = post.permlink;
const parentTags = post.json_metadata.tags;
await postComment(
currentAccount,
pinCode,
@ -691,11 +702,10 @@ class EditorContainer extends Component<any, any> {
markdownBody: fields.body,
},
{
parentTags: parentTags || ['ecency']
}
)
)
parentTags: parentTags || ['ecency'],
},
),
);
})
.catch((error) => {
this._handleSubmitFailure(error);
@ -705,7 +715,7 @@ class EditorContainer extends Component<any, any> {
_submitEdit = async (fields) => {
const { currentAccount, pinCode, dispatch } = this.props;
const { post, isEdit, isPostSending, thumbIndex, isReply } = this.state;
const { post, isEdit, isPostSending, thumbUrl, isReply } = this.state;
if (isPostSending) {
return;
@ -731,7 +741,7 @@ class EditorContainer extends Component<any, any> {
newBody = patch;
}
const meta = extractMetadata(fields.body, thumbIndex);
const meta = extractMetadata(fields.body, thumbUrl);
let jsonMeta = {};
@ -773,13 +783,13 @@ class EditorContainer extends Component<any, any> {
author_reputation: post.author_reputation,
total_payout: post.total_payout,
created: post.created,
json_metadata: jsonMeta
json_metadata: jsonMeta,
},
{
isUpdate: true
}
)
)
isUpdate: true,
},
),
);
}
})
.catch((error) => {
@ -799,10 +809,7 @@ class EditorContainer extends Component<any, any> {
) {
//when RC is not enough, offer boosting account
dispatch(setRcOffer(true));
} else if (
error &&
error.jse_shortmsg &&
error.jse_shortmsg.includes('wait to transact')) {
} else if (error && error.jse_shortmsg && error.jse_shortmsg.includes('wait to transact')) {
//when RC is not enough, offer boosting account
dispatch(setRcOffer(true));
} else {
@ -840,20 +847,10 @@ class EditorContainer extends Component<any, any> {
}, 3000);
};
_navigationBackFetchDrafts = () => {
const { route } = this.props;
const { isDraft } = this.state;
if (isDraft && route.params?.fetchPost) {
route.params.fetchPost
}
};
_handleSubmit = (form: any) => {
const { isReply, isEdit } = this.state;
const { intl } = this.props;
if (isReply && !isEdit) {
this._submitReply(form.fields);
} else if (isEdit) {
@ -909,8 +906,6 @@ class EditorContainer extends Component<any, any> {
}
};
_handleFormChanged = () => {
const { isDraftSaved } = this.state;
@ -921,7 +916,6 @@ class EditorContainer extends Component<any, any> {
}
};
_handleSchedulePress = async (datePickerValue, fields) => {
const { currentAccount, pinCode, intl } = this.props;
@ -998,13 +992,12 @@ class EditorContainer extends Component<any, any> {
),
);
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name))
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name));
setTimeout(() => {
navigation.replace(ROUTES.SCREENS.DRAFTS,
{
showSchedules: true
})
navigation.replace(ROUTES.SCREENS.DRAFTS, {
showSchedules: true,
});
}, 3000);
})
.catch((error) => {
@ -1018,10 +1011,10 @@ class EditorContainer extends Component<any, any> {
_initialEditor = () => {
const {
currentAccount: { name },
dispatch
dispatch,
} = this.props;
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + name))
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + name));
this.setState({
uploadedImage: null,
@ -1032,26 +1025,23 @@ class EditorContainer extends Component<any, any> {
this.setState({ rewardType: value });
};
_handleShouldReblogChange = (value: boolean) => {
this.setState({
shouldReblog: value
})
}
shouldReblog: value,
});
};
_handleSetThumbIndex = (index: number) => {
_handleSetThumbUrl = (url: string) => {
this.setState({
thumbIndex: index
})
}
thumbUrl: url,
});
};
_setIsUploading = (status: boolean) => {
this.setState({
isUploading:status
})
}
isUploading: status,
});
};
render() {
const { isLoggedIn, isDarkTheme, currentAccount, route } = this.props;
@ -1072,7 +1062,7 @@ class EditorContainer extends Component<any, any> {
community,
sharedSnippetText,
onLoadDraftPress,
thumbIndex,
thumbUrl,
uploadProgress,
rewardType,
} = this.state;
@ -1112,8 +1102,8 @@ class EditorContainer extends Component<any, any> {
draftId={draftId}
sharedSnippetText={sharedSnippetText}
onLoadDraftPress={onLoadDraftPress}
thumbIndex={thumbIndex}
setThumbIndex={this._handleSetThumbIndex}
thumbUrl={thumbUrl}
setThumbUrl={this._handleSetThumbUrl}
uploadProgress={uploadProgress}
rewardType={rewardType}
getBeneficiaries={this._extractBeneficiaries}
@ -1132,4 +1122,10 @@ const mapStateToProps = (state) => ({
drafts: state.cache.drafts,
});
export default connect(mapStateToProps)(injectIntl(EditorContainer));
export default connect(mapStateToProps)(
injectIntl(
//NOTE: remove extra integration step once compoent converted to functional component
//TOOD: inject add and update draft mutation hooks as well
(props) => <EditorContainer {...props} queryClient={useQueryClient()} />,
),
);

View File

@ -145,21 +145,21 @@ class EditorScreen extends Component {
_handleOnSaveButtonPress = () => {
const { draftId, intl } = this.props;
if (draftId) {
Alert.alert(
intl.formatMessage({id:'editor.draft_save_title'}),
"",
[{
Alert.alert(intl.formatMessage({ id: 'editor.draft_save_title' }), '', [
{
text: intl.formatMessage({ id: 'editor.draft_update' }),
onPress: () => this._saveDraftToDB(),
},{
},
{
text: intl.formatMessage({ id: 'editor.draft_save_new' }),
onPress:()=>this._saveDraftToDB(true)
},{
onPress: () => this._saveDraftToDB(true),
},
{
text: intl.formatMessage({ id: 'alert.cancel' }),
onPress: () => {},
style:'cancel'
}]
)
style: 'cancel',
},
]);
return;
}
this._saveDraftToDB();
@ -174,7 +174,7 @@ class EditorScreen extends Component {
this.changeTimer = setTimeout(() => {
// saveCurrentDraft(fields);
updateDraftFields(fields)
updateDraftFields(fields);
}, 300);
};
@ -192,29 +192,28 @@ class EditorScreen extends Component {
}
};
_handleOnThumbSelection = (index) => {
const { setThumbIndex } = this.props;
if (setThumbIndex) {
setThumbIndex(index);
_handleOnThumbSelection = (url: string) => {
const { setThumbUrl } = this.props;
if (setThumbUrl) {
setThumbUrl(url);
}
};
_handleScheduleChange = (datetime: string | null) => {
this.setState({
scheduledFor: datetime,
})
}
});
};
_handleRewardChange = (value) => {
const { handleRewardChange } = this.props;
handleRewardChange(value);
}
};
_handleSettingsPress = () => {
if (this.postOptionsModalRef) {
this.postOptionsModalRef.show();
}
}
};
_handleIsFormValid = (bodyText) => {
const { fields } = this.state;
@ -236,7 +235,7 @@ class EditorScreen extends Component {
};
_handleFormUpdate = (componentID, content) => {
const { handleFormChanged, thumbIndex, rewardType, getBeneficiaries } = this.props;
const { handleFormChanged, thumbUrl, rewardType, getBeneficiaries } = this.props;
const { fields: _fields } = this.state;
const fields = { ..._fields };
@ -248,7 +247,7 @@ class EditorScreen extends Component {
fields.tags = content;
}
const meta = Object.assign({}, extractMetadata(fields.body, thumbIndex), {
const meta = Object.assign({}, extractMetadata(fields.body, thumbUrl), {
tags: fields.tags,
beneficiaries: getBeneficiaries(),
rewardType,
@ -381,17 +380,22 @@ class EditorScreen extends Component {
autoFocusText,
sharedSnippetText,
onLoadDraftPress,
thumbIndex,
thumbUrl,
uploadProgress,
rewardType,
setIsUploading,
} = this.props;
const rightButtonText = intl.formatMessage({
id: isEdit ? 'basic_header.update' : isReply ? 'basic_header.reply' : scheduledFor ? 'basic_header.schedule' : 'basic_header.publish',
id: isEdit
? 'basic_header.update'
: isReply
? 'basic_header.reply'
: scheduledFor
? 'basic_header.schedule'
: 'basic_header.publish',
});
const _renderCommunityModal = () => {
return (
<Modal
@ -484,7 +488,7 @@ class EditorScreen extends Component {
ref={(componentRef) => (this.postOptionsModalRef = componentRef)}
body={fields.body}
draftId={draftId}
thumbIndex={thumbIndex}
thumbUrl={thumbUrl}
isEdit={isEdit}
isCommunityPost={selectedCommunity !== null}
rewardType={rewardType}

View File

@ -1,7 +1,6 @@
import getSlug from 'speakingurl';
import { diff_match_patch as diffMatchPatch } from 'diff-match-patch';
import VersionNumber from 'react-native-version-number';
import { PanGestureHandler } from 'react-native-gesture-handler';
import MimeTypes from 'mime-types';
export const getWordsCount = (text) =>
@ -46,8 +45,7 @@ export const generatePermlink = (title, random = false) => {
return perm;
};
;export const extractWordAtIndex = (text:string, index:number) => {
export const extractWordAtIndex = (text: string, index: number) => {
const RANGE = 50;
const _start = index - RANGE;
@ -56,14 +54,12 @@ export const generatePermlink = (title, random = false) => {
const _length = text.length;
const textChunk = text.substring(_start > 0 ? _start : 0, _end < _length ? _end : _length);
const indexChunk = index < 50 ? index : (
_length - index < 50 ? textChunk.length - (_length - index) :
RANGE
);
const indexChunk =
index < 50 ? index : _length - index < 50 ? textChunk.length - (_length - index) : RANGE;
console.log('char at index: ', textChunk[indexChunk]);
const END_REGEX = /[\s,]/
const END_REGEX = /[\s,]/;
let word = '';
for (let i = indexChunk; i >= 0 && (!END_REGEX.test(textChunk[i]) || i === indexChunk); i--) {
if (textChunk[i]) {
@ -81,7 +77,7 @@ export const generatePermlink = (title, random = false) => {
}
return word;
}
};
export const generateReplyPermlink = (toAuthor) => {
if (!toAuthor) {
@ -169,15 +165,13 @@ export const makeJsonMetadataForUpdate = (oldJson, meta, tags) => {
return Object.assign({}, oldJson, mergedMeta, { tags });
};
const extractUrls = (body: string) => {
const urlReg = /(\b(https?|ftp):\/\/[A-Z0-9+&@#/%?=~_|!:,.;-]*[-A-Z0-9+&@#/%=~_|])/gim;
const mUrls = body && body.match(urlReg);
return mUrls || [];
}
};
export const extractImageUrls = ({body, urls}:{body?:string, urls?:string[]}) => {
export const extractImageUrls = ({ body, urls }: { body?: string; urls?: string[] }) => {
const imgReg = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif|heic|webp))/gim;
let imgUrls = [];
@ -188,33 +182,38 @@ export const extractImageUrls = ({body, urls}:{body?:string, urls?:string[]}) =>
if (isImage) {
imgUrls.push(url);
}
})
});
return imgUrls;
}
};
export const extractFilenameFromPath = ({path, mimeType}:{path:string, mimeType?:string}) => {
export const extractFilenameFromPath = ({
path,
mimeType,
}: {
path: string;
mimeType?: string;
}) => {
try {
if (!path) {
throw new Error("path not provided");
throw new Error('path not provided');
}
const filenameIndex = path.lastIndexOf('/') + 1;
const extensionIndex = path.lastIndexOf('.');
if (filenameIndex < 0 || extensionIndex <= filenameIndex) {
throw new Error("file name not present with extension");
throw new Error('file name not present with extension');
}
return path.substring(path.lastIndexOf('/') + 1);
} catch (err) {
let _ext = 'jpg';
if (mimeType) {
_ext = MimeTypes.extension(mimeType)
_ext = MimeTypes.extension(mimeType);
}
return `${generateRndStr()}.${_ext}`;
}
}
};
export const extractMetadata = (body:string, thumbIndex?:number) => {
export const extractMetadata = (body: string, thumbUrl?: string) => {
const userReg = /(^|\s)(@[a-z][-.a-z\d]+[a-z\d])/gim;
const out = {};
@ -231,7 +230,7 @@ export const extractMetadata = (body:string, thumbIndex?:number) => {
if (matchedImages.indexOf(url) < 0) {
matchedLinks.push(url);
}
})
});
}
if (matchedLinks.length) {
@ -239,8 +238,8 @@ export const extractMetadata = (body:string, thumbIndex?:number) => {
}
if (matchedImages.length) {
if(thumbIndex){
matchedImages.splice(0, 0, matchedImages.splice(thumbIndex, 1)[0]);
if (thumbUrl) {
matchedImages.sort((item) => (item === thumbUrl ? -1 : 1));
}
out.image = matchedImages;
@ -271,4 +270,4 @@ export const createPatch = (text1, text2) => {
return patch;
};
export const delay = ms => new Promise(res => setTimeout(res, ms));
export const delay = (ms) => new Promise((res) => setTimeout(res, ms));

View File

@ -1,21 +1,39 @@
import get from 'lodash/get';
import { operationOrders } from '@hiveio/dhive/lib/utils';
import { utils } from '@hiveio/dhive';
import parseDate from './parseDate';
import parseToken from './parseToken';
import { vestsToHp } from './conversions';
import { getAccount, getAccountHistory, getConversionRequests, getFeedHistory, getOpenOrders, getSavingsWithdrawFrom } from '../providers/hive/dhive';
import {
fetchGlobalProps,
getAccount,
getAccountHistory,
getConversionRequests,
getFeedHistory,
getOpenOrders,
getSavingsWithdrawFrom,
} from '../providers/hive/dhive';
import { getCurrencyTokenRate, getLatestQuotes } from '../providers/ecency/ecency';
import { CoinActivitiesCollection, CoinActivity, CoinBase, CoinData, DataPair, QuoteItem } from '../redux/reducers/walletReducer';
import {
CoinActivitiesCollection,
CoinActivity,
CoinBase,
CoinData,
DataPair,
QuoteItem,
} from '../redux/reducers/walletReducer';
import { GlobalProps } from '../redux/reducers/accountReducer';
import { getEstimatedAmount } from './vote';
import { getPointsSummary, getPointsHistory } from '../providers/ecency/ePoint';
// Constant
import POINTS from '../constants/options/points';
import { COIN_IDS } from '../constants/defaultCoins';
import { operationOrders } from '@hiveio/dhive/lib/utils';
import { ConversionRequest, OpenOrderItem, OrdersData, SavingsWithdrawRequest } from '../providers/hive/hive.types';
import {
ConversionRequest,
OpenOrderItem,
SavingsWithdrawRequest,
} from '../providers/hive/hive.types';
import parseAsset from './parseAsset';
import { utils } from '@hiveio/dhive';
export const transferTypes = [
'curation_reward',
@ -39,24 +57,16 @@ export const transferTypes = [
'fill_vesting_withdraw',
];
const ECENCY_ACTIONS = [
'dropdown_transfer', 'dropdown_promote', 'dropdown_boost'
];
const ECENCY_ACTIONS = ['dropdown_transfer', 'dropdown_promote', 'dropdown_boost'];
const HIVE_ACTIONS = [
'transfer_token',
'transfer_to_savings',
'transfer_to_vesting',
'withdraw_hive'
];
const HBD_ACTIONS = [
'transfer_token',
'transfer_to_savings',
'convert',
'withdraw_hbd'
'withdraw_hive',
];
const HBD_ACTIONS = ['transfer_token', 'transfer_to_savings', 'convert', 'withdraw_hbd'];
const HIVE_POWER_ACTIONS = ['delegate', 'power_down'];
export const groomingTransactionData = (transaction, hivePerMVests) => {
if (!transaction || !hivePerMVests) {
return [];
@ -64,7 +74,7 @@ export const groomingTransactionData = (transaction, hivePerMVests) => {
const result = {
iconType: 'MaterialIcons',
trxIndex:transaction[0]
trxIndex: transaction[0],
};
[result.textKey] = transaction[1].op;
@ -102,7 +112,8 @@ export const groomingTransactionData = (transaction, hivePerMVests) => {
.toFixed(3)
.replace(',', '.');
result.value = `${hbdPayout > 0 ? `${hbdPayout} HBD` : ''} ${hivePayout > 0 ? `${hivePayout} HIVE` : ''
result.value = `${hbdPayout > 0 ? `${hbdPayout} HBD` : ''} ${
hivePayout > 0 ? `${hivePayout} HIVE` : ''
} ${vestingPayout > 0 ? `${vestingPayout} HP` : ''}`;
result.details = author && permlink ? `@${author}/${permlink}` : null;
@ -117,7 +128,8 @@ export const groomingTransactionData = (transaction, hivePerMVests) => {
rewardHive = parseToken(rewardHive).toFixed(3).replace(',', '.');
rewardVests = vestsToHp(parseToken(rewardVests), hivePerMVests).toFixed(3).replace(',', '.');
result.value = `${rewardHdb > 0 ? `${rewardHdb} HBD` : ''} ${rewardHive > 0 ? `${rewardHive} HIVE` : ''
result.value = `${rewardHdb > 0 ? `${rewardHdb} HBD` : ''} ${
rewardHive > 0 ? `${rewardHive} HIVE` : ''
} ${rewardVests > 0 ? `${rewardVests} HP` : ''}`;
break;
case 'transfer':
@ -263,7 +275,7 @@ export const groomingWalletData = async (user, globalProps, userCurrency) => {
walletData.nextVestingWithdrawal = Math.round(timeDiff / (1000 * 3600));
//TOOD: transfer history can be separated from here
const op = utils.operationOrders
const op = utils.operationOrders;
const ops = [
op.transfer, //HIVE
op.author_reward, //HBD, HP
@ -279,7 +291,7 @@ export const groomingWalletData = async (user, globalProps, userCurrency) => {
op.sps_fund, //HBD
op.comment_benefactor_reward, //HP
op.return_vesting_delegation, //HP
]
];
const history = await getAccountHistory(get(user, 'name'), ops);
@ -291,10 +303,10 @@ export const groomingWalletData = async (user, globalProps, userCurrency) => {
return walletData;
};
const fetchPendingRequests = async (username: string, coinSymbol: string): Promise<CoinActivity[]> => {
const fetchPendingRequests = async (
username: string,
coinSymbol: string,
): Promise<CoinActivity[]> => {
const _rawConversions = await getConversionRequests(username);
const _rawOpenOrdres = await getOpenOrders(username);
const _rawWithdrawRequests = await getSavingsWithdrawFrom(username);
@ -302,59 +314,56 @@ const fetchPendingRequests = async (username: string, coinSymbol: string): Promi
console.log('fetched pending requests', _rawConversions, _rawOpenOrdres, _rawWithdrawRequests);
const openOrderRequests = _rawOpenOrdres
.filter(request => request.sell_price.base.includes(coinSymbol))
.filter((request) => request.sell_price.base.includes(coinSymbol))
.map((request) => {
const { base, quote } = request?.sell_price || {};
return ({
iconType: "MaterialIcons",
return {
iconType: 'MaterialIcons',
textKey: 'open_order',
expires: request.expiration,
created: request.created,
icon: 'reorder',
value: base || '-- --',
details: base && quote ? `@ ${base} = ${quote}` : '',
} as CoinActivity)
})
} as CoinActivity;
});
const withdrawRequests = _rawWithdrawRequests
.filter(request => request.amount.includes(coinSymbol))
.filter((request) => request.amount.includes(coinSymbol))
.map((request) => {
return ({
iconType: "MaterialIcons",
textKey: "withdraw_savings",
return {
iconType: 'MaterialIcons',
textKey: 'withdraw_savings',
created: request.complete,
icon: "compare-arrows",
icon: 'compare-arrows',
value: request.amount,
details: request.from && request.to ? `@${request.from} to @${request.to}` : null,
memo: request.memo || null
} as CoinActivity)
})
memo: request.memo || null,
} as CoinActivity;
});
const conversionRequests = _rawConversions
.filter(request => request.amount.includes(coinSymbol))
.filter((request) => request.amount.includes(coinSymbol))
.map((request) => {
return ({
iconType: "MaterialIcons",
textKey: "convert_request",
return {
iconType: 'MaterialIcons',
textKey: 'convert_request',
created: request.conversion_date,
icon: "hourglass-full",
value: request.amount
} as CoinActivity)
})
icon: 'hourglass-full',
value: request.amount,
} as CoinActivity;
});
const pendingRequests = [
...openOrderRequests,
...withdrawRequests,
...conversionRequests
];
const pendingRequests = [...openOrderRequests, ...withdrawRequests, ...conversionRequests];
pendingRequests.sort((a, b) => (
new Date(a.expires || a.created).getTime() > new Date(b.expires || b.created).getTime() ? 1 : -1
))
pendingRequests.sort((a, b) =>
new Date(a.expires || a.created).getTime() > new Date(b.expires || b.created).getTime()
? 1
: -1,
);
return pendingRequests;
}
};
/**
*
@ -370,52 +379,58 @@ export const fetchCoinActivities = async (
coinSymbol: string,
globalProps: GlobalProps,
startIndex: number,
limit:number
limit: number,
): Promise<CoinActivitiesCollection> => {
const op = operationOrders;
let history = [];
switch (coinId) {
case COIN_IDS.ECENCY: {
//TODO: remove condition when we have a way to fetch paginated points data
if (startIndex !== -1) {
return {
completed: [],
pending:[]
}
pending: [],
};
}
const pointActivities = await getPointsHistory(username);
console.log("Points Activities", pointActivities);
const completed = pointActivities && pointActivities.length ?
pointActivities.map((item) =>
console.log('Points Activities', pointActivities);
const completed =
pointActivities && pointActivities.length
? pointActivities.map((item) =>
groomingPointsTransactionData({
...item,
icon: get(POINTS[get(item, 'type')], 'icon'),
iconType: get(POINTS[get(item, 'type')], 'iconType'),
textKey: get(POINTS[get(item, 'type')], 'textKey'),
})
) : [];
}),
)
: [];
return {
completed,
pending: [] as CoinActivity[]
}
pending: [] as CoinActivity[],
};
}
case COIN_IDS.HIVE:
history = await getAccountHistory(username, [
history = await getAccountHistory(
username,
[
op.transfer, //HIVE
op.transfer_to_vesting, //HIVE, HP
op.withdraw_vesting, //HIVE, HP
op.transfer_to_savings, //HIVE, HBD
op.transfer_from_savings, //HIVE, HBD
op.fill_order, //HIVE, HBD
], startIndex, limit);
],
startIndex,
limit,
);
break;
case COIN_IDS.HBD:
history = await getAccountHistory(username, [
history = await getAccountHistory(
username,
[
op.transfer, //HIVE //HBD
op.author_reward, //HBD, HP
op.transfer_to_savings, //HIVE, HBD
@ -423,10 +438,15 @@ export const fetchCoinActivities = async (
op.fill_convert_request, //HBD
op.fill_order, //HIVE, HBD
op.sps_fund, //HBD
], startIndex, limit);
],
startIndex,
limit,
);
break;
case COIN_IDS.HP:
history = await getAccountHistory(username, [
history = await getAccountHistory(
username,
[
op.author_reward, //HBD, HP
op.curation_reward, //HP
op.transfer_to_vesting, //HIVE, HP
@ -435,66 +455,33 @@ export const fetchCoinActivities = async (
op.claim_reward_balance, //HP
op.comment_benefactor_reward, //HP
op.return_vesting_delegation, //HP
], startIndex, limit);
],
startIndex,
limit,
);
break;
}
const transfers = history.filter((tx) => transferTypes.includes(get(tx[1], 'op[0]', false)));
transfers.sort(compare);
const activities = transfers.map(item => groomingTransactionData(item, globalProps.hivePerMVests));
const activities = transfers.map((item) =>
groomingTransactionData(item, globalProps.hivePerMVests),
);
const filterdActivities: CoinActivity[] = activities
? activities.filter((item) => {
return (
item &&
item.value &&
item.value.includes(coinSymbol)
);
return item && item.value && item.value.includes(coinSymbol);
})
: [];
console.log('FILTERED comap', activities.length, filterdActivities.length)
console.log('FILTERED comap', activities.length, filterdActivities.length);
const pendingRequests = await fetchPendingRequests(username, coinSymbol);
return {
completed: filterdActivities,
pending: pendingRequests,
}
}
const calculateConvertingAmount = (requests: ConversionRequest[]): number => {
if (!requests || !requests.length) {
return 0;
}
//TODO: add method body
// ecency-vision -> src/common/components/wallet-hive/index.tsx#fetchConvertingAmount
throw new Error("calculateConvertingAmount method body not implemented yet");
}
const calculateSavingsWithdrawalAmount = (requests: SavingsWithdrawRequest[], coinSymbol: string): number => {
return requests.reduce((prevVal, curRequest) => {
const _amount = curRequest.amount;
return _amount.includes(coinSymbol)
? prevVal + parseAsset(_amount).amount
: prevVal
}, 0);
}
const calculateOpenOrdersAmount = (requests: OpenOrderItem[], coinSymbol: string): number => {
return requests.reduce((prevVal, curRequest) => {
const _basePrice = curRequest.sell_price.base;
return _basePrice.includes(coinSymbol)
? prevVal + parseAsset(_basePrice).amount
: prevVal
}, 0);
}
};
};
export const fetchCoinsData = async ({
coins,
@ -505,36 +492,32 @@ export const fetchCoinsData = async ({
refresh,
quotes,
}: {
coins: CoinBase[],
currentAccount: any,
vsCurrency: string,
currencyRate: number,
globalProps: GlobalProps,
quotes: { [key: string]: QuoteItem }
refresh: boolean,
})
: Promise<{ [key: string]: CoinData }> => {
coins: CoinBase[];
currentAccount: any;
vsCurrency: string;
currencyRate: number;
globalProps: GlobalProps;
quotes: { [key: string]: QuoteItem };
refresh: boolean;
}): Promise<{ [key: string]: CoinData }> => {
const username = currentAccount.username;
const { base, quote, hivePerMVests } = globalProps
const coinData = {} as { [key: string]: CoinData };
const walletData = {} as any;
if (!username) {
return walletData;
}
//fetch latest global props if refresh or data not available
const { base, quote, hivePerMVests } =
refresh || !globalProps || !globalProps.hivePerMVests ? await fetchGlobalProps() : globalProps;
//TODO: Use already available accoutn for frist wallet start
const userdata = refresh ? await getAccount(username) : currentAccount;
const _pointsSummary = refresh ? await getPointsSummary(username) : currentAccount.pointsSummary
const _pointsSummary = refresh ? await getPointsSummary(username) : currentAccount.pointsSummary;
//TODO: cache data in redux or fetch once on wallet startup
const _prices = !refresh && quotes ? quotes : await getLatestQuotes(currencyRate); //TODO: figure out a way to handle other currencies
coins.forEach((coinBase) => {
switch (coinBase.id) {
case COIN_IDS.ECENCY: {
const balance = _pointsSummary.points ? parseFloat(_pointsSummary.points) : 0;
@ -549,7 +532,7 @@ export const fetchCoinsData = async ({
currentPrice: ppEstm,
unclaimedBalance: unclaimedBalance,
actions: ECENCY_ACTIONS,
}
};
break;
}
case COIN_IDS.HIVE: {
@ -557,7 +540,6 @@ export const fetchCoinsData = async ({
const savings = parseToken(userdata.savings_balance);
const ppHive = _prices[coinBase.id].price;
coinData[coinBase.id] = {
balance: Math.round(balance * 1000) / 1000,
estimateValue: (balance + savings) * ppHive,
@ -566,7 +548,7 @@ export const fetchCoinsData = async ({
currentPrice: ppHive,
unclaimedBalance: '',
actions: HIVE_ACTIONS,
}
};
break;
}
@ -583,33 +565,27 @@ export const fetchCoinsData = async ({
currentPrice: ppHbd,
unclaimedBalance: '',
actions: HBD_ACTIONS,
}
};
break;
}
case COIN_IDS.HP: {
const _getBalanceStr = (val: number, cur: string) => (val ? Math.round(val * 1000) / 1000 + cur : '');
const balance = Math.round(
vestsToHp(parseToken(userdata.vesting_shares), hivePerMVests) * 1000,
) / 1000;
const _getBalanceStr = (val: number, cur: string) =>
val ? Math.round(val * 1000) / 1000 + cur : '';
const balance =
Math.round(vestsToHp(parseToken(userdata.vesting_shares), hivePerMVests) * 1000) / 1000;
const receivedHP = vestsToHp(
parseToken(userdata.received_vesting_shares),
hivePerMVests,
)
const receivedHP = vestsToHp(parseToken(userdata.received_vesting_shares), hivePerMVests);
const delegatedHP = vestsToHp(
parseToken(userdata.delegated_vesting_shares),
hivePerMVests,
)
const delegatedHP = vestsToHp(parseToken(userdata.delegated_vesting_shares), hivePerMVests);
//agggregate claim button text
const unclaimedBalance = [
_getBalanceStr(parseToken(userdata.reward_hive_balance), ' HIVE'),
_getBalanceStr(parseToken(userdata.reward_hbd_balance), ' HBD'),
_getBalanceStr(parseToken(userdata.reward_vesting_hive), ' HP')
_getBalanceStr(parseToken(userdata.reward_vesting_hive), ' HP'),
].reduce(
(prevVal, bal) => prevVal + (!bal ? '' : (`${prevVal !== '' ? ' ' : ''}${bal}`)),
''
(prevVal, bal) => prevVal + (!bal ? '' : `${prevVal !== '' ? ' ' : ''}${bal}`),
'',
);
//calculate power down
@ -620,9 +596,12 @@ export const fetchCoinsData = async ({
const nextVestingSharesWithdrawal = isPoweringDown
? Math.min(
parseAsset(userdata.vesting_withdraw_rate).amount,
(Number(userdata.to_withdraw) - Number(userdata.withdrawn)) / 1e6
) : 0;
const nextVestingSharesWithdrawalHive = isPoweringDown ? vestsToHp(nextVestingSharesWithdrawal, hivePerMVests) : 0;
(Number(userdata.to_withdraw) - Number(userdata.withdrawn)) / 1e6,
)
: 0;
const nextVestingSharesWithdrawalHive = isPoweringDown
? vestsToHp(nextVestingSharesWithdrawal, hivePerMVests)
: 0;
const estimateVoteValueStr = '$ ' + getEstimatedAmount(userdata, globalProps);
@ -633,35 +612,40 @@ export const fetchCoinsData = async ({
extraDataPairs.push({
dataKey: 'delegated_hive_power',
value: `- ${delegatedHP.toFixed(3)} HP`,
isClickable: true
})
isClickable: true,
});
}
if (receivedHP) {
extraDataPairs.push({
dataKey: 'received_hive_power',
value: `+ ${receivedHP.toFixed(3)} HP`,
isClickable: true
})
isClickable: true,
});
}
if (nextVestingSharesWithdrawalHive) {
extraDataPairs.push({
dataKey: 'powering_down_hive_power',
value: `- ${nextVestingSharesWithdrawalHive.toFixed(3)} HP`
})
value: `- ${nextVestingSharesWithdrawalHive.toFixed(3)} HP`,
});
}
extraDataPairs.concat([
{
dataKey: 'total_hive_power',
value: `${(balance - delegatedHP + receivedHP - nextVestingSharesWithdrawalHive).toFixed(3)} HP`
}, {
value: `${(
balance -
delegatedHP +
receivedHP -
nextVestingSharesWithdrawalHive
).toFixed(3)} HP`,
},
{
dataKey: 'vote_value',
value: estimateVoteValueStr
}
])
value: estimateVoteValueStr,
},
]);
const ppHive = _prices[COIN_IDS.HIVE].price;
coinData[coinBase.id] = {
@ -672,22 +656,29 @@ export const fetchCoinsData = async ({
currentPrice: ppHive,
actions: HIVE_POWER_ACTIONS,
extraDataPairs: [
...extraDataPairs, {
...extraDataPairs,
{
dataKey: 'total_hive_power',
value: `${(balance - delegatedHP + receivedHP - nextVestingSharesWithdrawalHive).toFixed(3)} HP`
}, {
value: `${(
balance -
delegatedHP +
receivedHP -
nextVestingSharesWithdrawalHive
).toFixed(3)} HP`,
},
{
dataKey: 'vote_value',
value: estimateVoteValueStr
}
]
}
value: estimateVoteValueStr,
},
],
};
break;
}
default:
break;
}
})
});
//TODO:discard unnessacry data processings towards the end of PR
walletData.rewardHiveBalance = parseToken(userdata.reward_hive_balance);
@ -709,8 +700,6 @@ export const fetchCoinsData = async ({
walletData.savingBalance = parseToken(userdata.savings_balance);
walletData.savingBalanceHbd = parseToken(userdata.savings_hbd_balance);
walletData.hivePerMVests = hivePerMVests;
const pricePerHive = base / quote;
@ -723,15 +712,12 @@ export const fetchCoinsData = async ({
walletData.estimatedValue = totalHive * pricePerHive + totalHbd;
walletData.showPowerDown = userdata.next_vesting_withdrawal !== '1969-12-31T23:59:59';
const timeDiff = Math.abs(parseDate(userdata.next_vesting_withdrawal) - new Date());
walletData.nextVestingWithdrawal = Math.round(timeDiff / (1000 * 3600));
return coinData;
}
};
function compare(a, b) {
if (a[1].block < b[1].block) {

View File

@ -1846,6 +1846,31 @@
dependencies:
type-detect "4.0.8"
"@tanstack/query-async-storage-persister@^4.3.9":
version "4.3.9"
resolved "https://registry.yarnpkg.com/@tanstack/query-async-storage-persister/-/query-async-storage-persister-4.3.9.tgz#d9954a19f41450152daf4a84c357284b36391a8a"
integrity sha512-Xn6UbUfXIpSdEMYnhgY22eYPPzNBfAGiN8WYQV/UD7lJ0iPtcX93576QON/gsqQl0oN1mwO8k38Eg1ZW+kwacA==
dependencies:
"@tanstack/react-query-persist-client" "4.3.9"
"@tanstack/query-core@4.3.8":
version "4.3.8"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.3.8.tgz#d5f07c1d9d4f83f16f0bed7f3b245fa0e557b037"
integrity sha512-AEUWtCNBIImFZ9tMt/P8V86kIhMHpfoJqAI1auGOLR8Wzeq7Ymiue789PJG0rKYcyViUicBZeHjggMqyEQVMfQ==
"@tanstack/react-query-persist-client@4.3.9", "@tanstack/react-query-persist-client@^4.3.9":
version "4.3.9"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-4.3.9.tgz#246acb070b8083078b6cdbf813bd6dfa2f6596e3"
integrity sha512-oFZA8bo6BQHoQqJSHXTtIDaIAxbF46cQHwhF72FwiMvBhm6eEbySUIPhGAWah7Jys2t2RJIhJ1T+q9P0RRIjwg==
"@tanstack/react-query@^4.3.9":
version "4.3.9"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.3.9.tgz#13332a1d4dd404baec24c2853883bcb3cc61ea92"
integrity sha512-odfDW6WiSntCsCh+HFeJtUys3UnVOjfJMhykAtGtYvcklMyyDmCv9BVBt5KlSpbk/qW3kURPFCDapO+BFUlCwg==
dependencies:
"@tanstack/query-core" "4.3.8"
use-sync-external-store "^1.2.0"
"@tradle/react-native-http@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@tradle/react-native-http/-/react-native-http-2.0.1.tgz#af19e240e1e580bfa249563924d1be472686f48b"
@ -8901,11 +8926,6 @@ react-native-image-pan-zoom@^2.1.9:
resolved "https://registry.yarnpkg.com/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz#eb98bf56fb5610379bdbfdb63219cc1baca98fd2"
integrity sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==
react-native-image-size@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/react-native-image-size/-/react-native-image-size-1.1.3.tgz#7d69c2cd4e1d1632947867e47643ed8cabb9de27"
integrity sha512-jJvN6CjXVAm69LAVZNV7m7r50Qk9vuPZwLyrbs/k31/3Xs8bZyVCdvfP44FuBisITn/yFsiOo6i8NPrFBPH20w==
react-native-image-zoom-viewer@^2.2.27:
version "2.2.27"
resolved "https://registry.yarnpkg.com/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-2.2.27.tgz#fb3314c5dc86ac33da48cb31bf4920d97eecb6eb"
@ -10965,6 +10985,11 @@ use-subscription@^1.0.0:
dependencies:
object-assign "^4.1.1"
use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"