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,52 +28,55 @@ 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);
}
return () => {
_isMounted = true;
};
}, []);
}, [isDeleting]);
const _onItemPress = () => {
if (isSchedules) {
moveActionSheet.current.show();
return;
}
handleOnPressItem(id);
};
// consts
const scheduleStatus =
status === ScheduledPostStatus.PENDING
? intl.formatMessage({ id: 'schedules.pending' })
: status === ScheduledPostStatus.POSTPONED
? intl.formatMessage({ id: 'schedules.postponed' })
: status === ScheduledPostStatus.PUBLISHED
? intl.formatMessage({ id: 'schedules.published' })
: intl.formatMessage({ id: 'schedules.error' });
? intl.formatMessage({ id: 'schedules.postponed' })
: status === ScheduledPostStatus.PUBLISHED
? intl.formatMessage({ id: 'schedules.published' })
: intl.formatMessage({ id: 'schedules.error' });
const statusIcon =
status === ScheduledPostStatus.PENDING
? 'timer'
: status === ScheduledPostStatus.POSTPONED
? 'schedule'
: status === ScheduledPostStatus.PUBLISHED
? 'check-circle'
: 'error';
? 'schedule'
: status === ScheduledPostStatus.PUBLISHED
? 'check-circle'
: 'error';
const statusIconColor =
status === ScheduledPostStatus.PUBLISHED
? '#4FD688'
: status === ScheduledPostStatus.ERROR
? '#e63535'
: '#c1c5c7';
? '#e63535'
: '#c1c5c7';
return (
<Fragment>
@ -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,22 +5,28 @@ 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 }) => (
<PersistGate loading={null} persistor={persistor}>
<IntlProvider locale={locale} messages={flattenMessages(messages[locale])}>
<SafeAreaProvider>
<Host>
<Application />
</Host>
</SafeAreaProvider>
</IntlProvider>
</PersistGate>
<PersistQueryClientProvider {...queryClientProviderProps}>
<PersistGate loading={null} persistor={persistor}>
<IntlProvider locale={locale} messages={flattenMessages(messages[locale])}>
<SafeAreaProvider>
<Host>
<Application />
</Host>
</SafeAreaProvider>
</IntlProvider>
</PersistGate>
</PersistQueryClientProvider>
);
const mapStateToProps = (state) => ({

File diff suppressed because it is too large Load Diff

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,63 +2,70 @@ 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 { 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';
import {View as AnimatedView} from 'react-native-animatable';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
const REWARD_TYPES = [
{
key:'default',
intlId:'editor.reward_default'
key: 'default',
intlId: 'editor.reward_default',
},
{
key:'sp',
intlId:'editor.reward_power_up'
key: 'sp',
intlId: 'editor.reward_power_up',
},
{
key:'dp',
intlId:'editor.reward_decline'
key: 'dp',
intlId: 'editor.reward_decline',
},
]
];
export interface PostOptionsModalRef {
showModal:()=>void;
showModal: () => void;
}
interface PostOptionsModalProps {
body:string;
draftId:string;
thumbIndex:number,
isEdit:boolean;
isCommunityPost:boolean;
body: string;
draftId: string;
thumbUrl: string;
isEdit: boolean;
isCommunityPost: boolean;
rewardType: string;
isUploading: boolean;
handleRewardChange:(rewardType:string)=>void;
handleThumbSelection:(index:number)=>void;
handleScheduleChange:(datetime:string|null)=>void;
handleShouldReblogChange:(shouldReblog:boolean)=>void;
handleFormUpdate:()=>void;
handleRewardChange: (rewardType: string) => void;
handleThumbSelection: (url: string) => void;
handleScheduleChange: (datetime: string | null) => void;
handleShouldReblogChange: (shouldReblog: boolean) => void;
handleFormUpdate: () => void;
}
const PostOptionsModal = forwardRef(({
body,
draftId,
thumbIndex,
isEdit,
isCommunityPost,
rewardType,
isUploading,
handleRewardChange,
handleThumbSelection,
handleScheduleChange,
handleShouldReblogChange,
handleFormUpdate
}: PostOptionsModalProps, ref) => {
const PostOptionsModal = forwardRef(
(
{
body,
draftId,
thumbUrl,
isEdit,
isCommunityPost,
rewardType,
isUploading,
handleRewardChange,
handleThumbSelection,
handleScheduleChange,
handleShouldReblogChange,
handleFormUpdate,
}: PostOptionsModalProps,
ref,
) => {
const intl = useIntl();
const [showModal, setShowModal] = useState(false);
@ -70,84 +77,83 @@ const PostOptionsModal = forwardRef(({
// removed the useeffect causing index reset bug
useEffect(()=>{
if(!scheduleLater){
handleScheduleChange(null)
}else if(scheduledFor) {
handleScheduleChange(scheduledFor)
}
}, [scheduleLater, scheduledFor])
useEffect(() => {
handleShouldReblogChange(shouldReblog)
}, [shouldReblog])
if (!scheduleLater) {
handleScheduleChange(null);
} else if (scheduledFor) {
handleScheduleChange(scheduledFor);
}
}, [scheduleLater, scheduledFor]);
useEffect(() => {
if(!isCommunityPost && 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)
if (rewardType) {
let rewardTypeKey = REWARD_TYPES.findIndex((item) => item.key === rewardType);
setRewardTypeIndex(rewardTypeKey);
}
},[rewardType])
}, [rewardType]);
useImperativeHandle(ref, () => ({
show: () => {
setShowModal(true);
},
}));
show: () => {
setShowModal(true);
},
}));
const _handleRewardChange = (index:number) => {
setRewardTypeIndex(index)
const rewardTypeKey = REWARD_TYPES[index].key
const _handleRewardChange = (index: number) => {
setRewardTypeIndex(index);
const rewardTypeKey = REWARD_TYPES[index].key;
if (handleRewardChange) {
handleRewardChange(rewardTypeKey);
}
}
};
const _handleDatePickerChange = (date:string) => {
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}>
<KeyboardAwareScrollView style={styles.fillSpace} >
<KeyboardAwareScrollView style={styles.fillSpace}>
<View style={styles.container}>
{!isEdit && (
<>
<SettingsItem
title={intl.formatMessage({id:'editor.scheduled_for'}) }
title={intl.formatMessage({ id: 'editor.scheduled_for' })}
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)=> {
handleOnChange={(index) => {
setScheduleLater(index === 1);
if (index !== 1) {
handleScheduleChange(null);
}
}}
/>
{scheduleLater && (
<AnimatedView animation="flipInX" duration={700}>
<DateTimePicker
@ -156,23 +162,19 @@ const PostOptionsModal = forwardRef(({
disabled={true}
/>
</AnimatedView>
)}
<SettingsItem
title={intl.formatMessage({
id: 'editor.setting_reward',
})}
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({
@ -184,42 +186,33 @@ const PostOptionsModal = forwardRef(({
handleOnChange={setShouldReblog}
/>
)}
</>
</>
)}
<ThumbSelectionContent
<ThumbSelectionContent
body={body}
thumbIndex={thumbIndex}
thumbUrl={thumbUrl}
isUploading={isUploading}
onThumbSelection={_handleThumbIndexSelection}
/>
{!isEdit && (
<BeneficiarySelectionContent
draftId={draftId}
setDisableDone={setDisableDone}
/>
<BeneficiarySelectionContent draftId={draftId} setDisableDone={setDisableDone} />
)}
</View>
</KeyboardAwareScrollView>
<MainButton
style={{...styles.saveButton }}
style={{ ...styles.saveButton }}
isDisable={disableDone}
onPress={_onDonePress}
text={intl.formatMessage({id:"editor.done"})}
/>
text={intl.formatMessage({ id: 'editor.done' })}
/>
</View>
)
);
return (
<Modal
return (
<Modal
isOpen={showModal}
handleOnModalClose={() => {
setShowModal(false);
@ -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>
);
});
{_renderContent()}
</Modal>
);
},
);
export default PostOptionsModal
export default PostOptionsModal;

View File

@ -1,59 +1,60 @@
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
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
zIndex: 999,
},
thumbStyle: {
width: 72,
height: 72,
marginVertical: 8,
marginRight: 8,
borderRadius: 12,
backgroundColor: '$primaryLightGray',
},
checkContainer: {
position: 'absolute',
top: 12,
left: 6,
backgroundColor: '$pureWhite',
borderRadius: 12,
},
settingLabel: {
color: '$primaryDarkGray',
fontSize: 14,
fontWeight: 'bold',
flexGrow: 1,
textAlign: 'left',
},
listContainer: {
paddingBottom: getBottomSpace() + 16,
},
container: {
paddingVertical: 16,
},
bodyWrapper: { flex: 1, paddingTop: 20, paddingBottom: 20 },
inputWrapper: { flexDirection: 'row', alignItems: 'center' },
contentLabel: { color: '$iconColor', marginTop: 4, textAlign: 'left' },
weightInput: { width: 80 },
weightFormInput: { flex: 1, color: '$primaryBlack', paddingLeft: 12 },
weightFormInputWrapper: { marginTop: 8 },
usernameInput: { flex: 1, color: '$primaryBlack', marginLeft: 16 },
usernameFormInputWrapper: { marginTop: 8, marginRight: 12 },
footerWrapper: { paddingTop: 16 },
saveButton: {
width: 140,
height: 44,
alignSelf: 'flex-end',
justifyContent: 'center',
},
doneButton: { borderRadius: 16, backgroundColor: '$primaryBlue' },
thumbSelectContainer: {
marginTop: 12,
},
thumbStyle:{
width:72,
height:72,
marginVertical:8,
marginRight:8,
borderRadius:12,
backgroundColor:'$primaryLightGray'
},
selectedStyle:{
borderWidth:4,
borderColor:'$primaryBlack'
},
settingLabel:{
color: '$primaryDarkGray',
fontSize: 14,
fontWeight: 'bold',
flexGrow: 1,
textAlign:'left',
},
listContainer:{
paddingBottom:getBottomSpace() + 16,
},
container:{
paddingVertical:16
},
bodyWrapper: { flex: 1, paddingTop: 20, paddingBottom:20},
inputWrapper: { flexDirection: 'row', alignItems: 'center'},
contentLabel: { color: '$iconColor', marginTop:4, textAlign:'left' },
weightInput: {width:80},
weightFormInput: { flex:1, color: '$primaryBlack', paddingLeft: 12 },
weightFormInputWrapper: { marginTop: 8 },
usernameInput: { flex:1, color: '$primaryBlack', marginLeft: 16 },
usernameFormInputWrapper: { marginTop: 8, marginRight: 12 },
footerWrapper: { paddingTop:16 },
saveButton: {
width: 140,
height: 44,
alignSelf: 'flex-end',
justifyContent: 'center',
},
doneButton:{borderRadius:16, backgroundColor:'$primaryBlue'},
thumbSelectContainer:{
marginTop:12,
}
});

View File

@ -1,87 +1,109 @@
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;
isUploading: boolean;
onThumbSelection: (index: number) => void;
body: string;
thumbUrl: string;
isUploading: boolean;
onThumbSelection: (url: string) => void;
}
const ThumbSelectionContent = ({ body, thumbIndex, onThumbSelection, isUploading }: ThumbSelectionContentProps) => {
const intl = useIntl();
const ThumbSelectionContent = ({
body,
thumbUrl,
onThumbSelection,
isUploading,
}: ThumbSelectionContentProps) => {
const intl = useIntl();
const [imageUrls, setImageUrls] = useState<string[]>([]);
const [needMore, setNeedMore] = useState(true);
const [imageUrls, setImageUrls] = useState<string[]>([]);
const [needMore, setNeedMore] = useState(true);
const [thumbIndex, setThumbIndex] = useState(0);
useEffect(() => {
const urls = extractImageUrls({ body });
useEffect(() => {
const urls = extractImageUrls({ body });
if (urls.length < 2) {
setNeedMore(true);
onThumbSelection(0);
setImageUrls([])
} else {
setNeedMore(false);
setImageUrls(urls)
}
}, [body])
//VIEW_RENDERERS
const _renderImageItem = ({ item, index }: { item: string, index: number }) => {
const _onPress = () => {
onThumbSelection(index);
}
const selectedStyle = index === thumbIndex ? styles.selectedStyle : null
return (
<TouchableOpacity onPress={() => _onPress()} >
<FastImage
source={{ uri: item }}
style={{ ...styles.thumbStyle, ...selectedStyle }}
resizeMode='cover'
/>
</TouchableOpacity>
)
if (urls.length < 2) {
setNeedMore(true);
onThumbSelection(urls[0] || '');
setThumbIndex(0);
setImageUrls([]);
} else {
setNeedMore(false);
setImageUrls(urls);
}
const _renderHeader = () => (
isUploading &&
<View style={{flex:1, justifyContent:'center', marginRight: 16}}>
<ActivityIndicator color={ESStyleSheet.value('$primaryBlack')} />
</View>
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 _onPress = () => {
onThumbSelection(item);
setThumbIndex(index);
};
const isSelected = item === thumbUrl && index === thumbIndex;
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>
) : (
<FlatList
data={imageUrls}
renderItem={_renderImageItem}
ListHeaderComponent={_renderHeader}
keyExtractor={(item, index) => `${item}-${index}`}
horizontal={true}
contentContainerStyle={styles.listContainer}
showsHorizontalScrollIndicator={false} />
)
}
</View>
<TouchableOpacity onPress={() => _onPress()}>
<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 && (
<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>
) : (
<FlatList
data={imageUrls}
renderItem={_renderImageItem}
ListHeaderComponent={_renderHeader}
keyExtractor={(item, index) => `${item}-${index}`}
horizontal={true}
contentContainerStyle={styles.listContainer}
showsHorizontalScrollIndicator={false}
/>
)}
</View>
);
};
export default ThumbSelectionContent;

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;
onThumbSelection:(index:number)=>void;
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[]>([]);
@ -24,82 +22,74 @@ const ThumbSelectionModal = ({ onThumbSelection, thumbIndex }:ThumbSelectionModa
//CALLBACK_METHODS
useImperativeHandle(ref, () => ({
show: (postBody:string) => {
console.log("Showing action modal")
show: (postBody: string) => {
console.log('Showing action modal');
const urls = extractImageUrls({body:postBody});
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'})
)
onThumbSelection(0);
return;
}
setImageUrls(urls);
sheetModalRef.current?.setModalVisible(true);
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' }));
onThumbSelection(0);
return;
}
setImageUrls(urls);
sheetModalRef.current?.setModalVisible(true);
},
}));
const _onSelection = (index:number) => {
const _onSelection = (index: number) => {
onThumbSelection(index);
sheetModalRef.current?.setModalVisible(false);
}
};
//VIEW_RENDERERS
const _renderImageItem = ({item, index}:{item:string, index:number}) => {
const _onPress = () => {
_onSelection(index);
}
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'
/>
</TouchableOpacity>
)
}
<TouchableOpacity onPress={() => _onPress()}>
<FastImage
source={{ uri: item }}
style={{ ...styles.thumbStyle, ...selectedStyle }}
resizeMode="cover"
/>
</TouchableOpacity>
);
};
const _renderContent = () => {
return (
<View style={{alignItems:'center'}} >
<Text style={styles.title}>{intl.formatMessage({id:'editor.select_thumb'})}</Text>
<FlatList
data={imageUrls}
renderItem={_renderImageItem}
keyExtractor={(item, index)=>`${item}-${index}`}
horizontal={true}
contentContainerStyle={styles.listContainer}
showsHorizontalScrollIndicator={false}
/>
</View>
)
}
return (
<View style={{ alignItems: 'center' }}>
<Text style={styles.title}>{intl.formatMessage({ id: 'editor.select_thumb' })}</Text>
<FlatList
data={imageUrls}
renderItem={_renderImageItem}
keyExtractor={(item, index) => `${item}-${index}`}
horizontal={true}
contentContainerStyle={styles.listContainer}
showsHorizontalScrollIndicator={false}
/>
</View>
);
};
return (
<ActionSheet
ref={sheetModalRef}
gestureEnabled={false}
hideUnderlay
containerStyle={styles.sheetContent}
indicatorColor={EStyleSheet.value('$primaryWhiteLightBackground')}
>
{_renderContent()}
</ActionSheet>
<ActionSheet
ref={sheetModalRef}
gestureEnabled={false}
hideUnderlay
containerStyle={styles.sheetContent}
indicatorColor={EStyleSheet.value('$primaryWhiteLightBackground')}
>
{_renderContent()}
</ActionSheet>
);
};
export default forwardRef(ThumbSelectionModal);
export default forwardRef(ThumbSelectionModal);

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);
}
@ -124,8 +123,8 @@ class EditorContainer extends Component<any, any> {
if (navigationParams.isReply) {
({ isReply } = navigationParams);
if(post){
draftId = `${currentAccount.name}/${post.author}/${post.permlink}`
if (post) {
draftId = `${currentAccount.name}/${post.author}/${post.permlink}`;
}
this.setState({
@ -135,7 +134,7 @@ class EditorContainer extends Component<any, any> {
});
if (draftId) {
this._getStorageDraft(username, isReply, { _id: draftId });
}
}
}
if (navigationParams.isEdit) {
@ -155,12 +154,12 @@ class EditorContainer extends Component<any, any> {
}
// handle file/text shared from ReceiveSharingIntent
if(hasSharedIntent){
if (hasSharedIntent) {
const files = navigationParams.files;
console.log('files : ', files);
files.forEach((el) => {
if (el.text) {
if (el.text) {
this.setState({
sharedSnippetText: el.text,
});
@ -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],
});
}
@ -262,12 +273,14 @@ 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
if (isArray(draft.meta.beneficiaries)) {
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
@ -311,8 +324,8 @@ 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();
@ -395,14 +414,14 @@ class EditorContainer extends Component<any, any> {
tags: fields.tags.join(' '),
};
}
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);
//update draft is draftId is present
if (draftId && draftField && !saveAsNew) {
await updateDraft(draftId, draftField.title, draftField.body, draftField.tags, jsonMeta);
@ -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;
}
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) => {
_setIsUploading = (status: boolean) => {
this.setState({
isUploading:status
})
}
isUploading: status,
});
};
render() {
const { isLoggedIn, isDarkTheme, currentAccount, route } = this.props;
@ -1072,13 +1062,13 @@ class EditorContainer extends Component<any, any> {
community,
sharedSnippetText,
onLoadDraftPress,
thumbIndex,
thumbUrl,
uploadProgress,
rewardType,
} = this.state;
const tags = route.params?.tags;
const paramFiles = route.params?.files;
const paramFiles = route.params?.files;
return (
<EditorScreen
paramFiles={paramFiles}
@ -1088,7 +1078,7 @@ class EditorContainer extends Component<any, any> {
handleShouldReblogChange={this._handleShouldReblogChange}
handleSchedulePress={this._handleSchedulePress}
handleFormChanged={this._handleFormChanged}
handleOnBackPress={() => { }}
handleOnBackPress={() => {}}
handleOnSubmit={this._handleSubmit}
initialEditor={this._initialEditor}
isDarkTheme={isDarkTheme}
@ -1102,7 +1092,7 @@ class EditorContainer extends Component<any, any> {
quickReplyText={quickReplyText}
isUploading={isUploading}
post={post}
updateDraftFields = {this._updateDraftFields}
updateDraftFields={this._updateDraftFields}
saveCurrentDraft={this._saveCurrentDraft}
saveDraftToDB={this._saveDraftToDB}
uploadedImage={uploadedImage}
@ -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

@ -143,23 +143,23 @@ class EditorScreen extends Component {
};
_handleOnSaveButtonPress = () => {
const {draftId, intl} = this.props;
if(draftId){
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)
},{
text:intl.formatMessage({id:'alert.cancel'}),
onPress:()=>{},
style:'cancel'
}]
)
const { draftId, intl } = this.props;
if (draftId) {
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),
},
{
text: intl.formatMessage({ id: 'alert.cancel' }),
onPress: () => {},
style: 'cancel',
},
]);
return;
}
this._saveDraftToDB();
@ -174,7 +174,7 @@ class EditorScreen extends Component {
this.changeTimer = setTimeout(() => {
// saveCurrentDraft(fields);
updateDraftFields(fields)
updateDraftFields(fields);
}, 300);
};
@ -182,7 +182,7 @@ class EditorScreen extends Component {
const { handleOnSubmit, handleSchedulePress } = this.props;
const { fields, scheduledFor } = this.state;
if(scheduledFor && handleSchedulePress){
if (scheduledFor && handleSchedulePress) {
handleSchedulePress(scheduledFor, fields);
return;
}
@ -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) => {
_handleScheduleChange = (datetime: string | null) => {
this.setState({
scheduledFor:datetime,
})
}
scheduledFor: datetime,
});
};
_handleRewardChange = (value) => {
const { handleRewardChange } = this.props;
handleRewardChange(value);
}
};
_handleSettingsPress = () => {
if(this.postOptionsModalRef){
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,14 +247,14 @@ 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,
});
const jsonMeta = makeJsonMetadata(meta, fields.tags);
fields.meta = jsonMeta;
if (
get(fields, 'body', '').trim() !== get(_fields, 'body', '').trim() ||
get(fields, 'title', '').trim() !== get(_fields, 'title', '').trim() ||
@ -264,7 +263,7 @@ class EditorScreen extends Component {
) {
console.log('jsonMeta : ', jsonMeta);
handleFormChanged();
this._saveCurrentDraft(fields);
}
@ -338,7 +337,7 @@ class EditorScreen extends Component {
});
};
_saveDraftToDB(saveAsNew?:boolean) {
_saveDraftToDB(saveAsNew?: boolean) {
const { saveDraftToDB } = this.props;
const { fields } = this.state;
@ -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,32 +54,30 @@ 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]){
for (let i = indexChunk; i >= 0 && (!END_REGEX.test(textChunk[i]) || i === indexChunk); i--) {
if (textChunk[i]) {
word += textChunk[i];
}
}
word = word.split('').reverse().join('');
if(!END_REGEX.test(textChunk[indexChunk])){
for(let i = indexChunk + 1; i < textChunk.length && !END_REGEX.test(textChunk[i]); i++){
if(textChunk[i]){
if (!END_REGEX.test(textChunk[indexChunk])) {
for (let i = indexChunk + 1; i < textChunk.length && !END_REGEX.test(textChunk[i]); i++) {
if (textChunk[i]) {
word += textChunk[i];
}
}
}
return word;
}
};
export const generateReplyPermlink = (toAuthor) => {
if (!toAuthor) {
@ -169,52 +165,55 @@ export const makeJsonMetadataForUpdate = (oldJson, meta, tags) => {
return Object.assign({}, oldJson, mergedMeta, { tags });
};
const extractUrls = (body:string) => {
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 = [];
const mUrls = urls || extractUrls(body);
mUrls.forEach((url)=>{
mUrls.forEach((url) => {
const isImage = url.match(imgReg);
if (isImage) {
imgUrls.push(url);
}
})
});
return imgUrls;
}
};
export const extractFilenameFromPath = ({path, mimeType}:{path:string, mimeType?:string}) => {
try{
if(!path){
throw new Error("path not provided");
export const extractFilenameFromPath = ({
path,
mimeType,
}: {
path: string;
mimeType?: string;
}) => {
try {
if (!path) {
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");
if (filenameIndex < 0 || extensionIndex <= filenameIndex) {
throw new Error('file name not present with extension');
}
return path.substring(path.lastIndexOf('/') + 1);
}catch(err){
} catch (err) {
let _ext = 'jpg';
if(mimeType){
_ext = MimeTypes.extension(mimeType)
if (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 = {};
@ -227,11 +226,11 @@ export const extractMetadata = (body:string, thumbIndex?:number) => {
const matchedUsers = [];
if (mUrls) {
mUrls.forEach((url)=>{
if(matchedImages.indexOf(url) < 0){
mUrls.forEach((url) => {
if (matchedImages.indexOf(url) < 0) {
matchedLinks.push(url);
}
})
});
}
if (matchedLinks.length) {
@ -239,10 +238,10 @@ 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,8 +112,9 @@ export const groomingTransactionData = (transaction, hivePerMVests) => {
.toFixed(3)
.replace(',', '.');
result.value = `${hbdPayout > 0 ? `${hbdPayout} HBD` : ''} ${hivePayout > 0 ? `${hivePayout} HIVE` : ''
} ${vestingPayout > 0 ? `${vestingPayout} HP` : ''}`;
result.value = `${hbdPayout > 0 ? `${hbdPayout} HBD` : ''} ${
hivePayout > 0 ? `${hivePayout} HIVE` : ''
} ${vestingPayout > 0 ? `${vestingPayout} HP` : ''}`;
result.details = author && permlink ? `@${author}/${permlink}` : null;
if (result.textKey === 'comment_benefactor_reward') {
@ -117,8 +128,9 @@ 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` : ''
} ${rewardVests > 0 ? `${rewardVests} HP` : ''}`;
result.value = `${rewardHdb > 0 ? `${rewardHdb} HBD` : ''} ${
rewardHive > 0 ? `${rewardHive} HIVE` : ''
} ${rewardVests > 0 ? `${rewardVests} HP` : ''}`;
break;
case 'transfer':
case 'transfer_to_savings':
@ -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,66 +314,63 @@ 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;
}
};
/**
*
* @param username
* @param coinId
* @param coinSymbol
* @param globalProps
*
* @param username
* @param coinId
* @param coinSymbol
* @param globalProps
* @returns {Promise<CoinActivitiesCollection>}
*/
export const fetchCoinActivities = async (
@ -370,131 +379,109 @@ 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){
if (startIndex !== -1) {
return {
completed:[],
pending:[]
}
completed: [],
pending: [],
};
}
const pointActivities = await getPointsHistory(username);
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'),
})
) : [];
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, [
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);
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,
);
break;
case COIN_IDS.HBD:
history = await getAccountHistory(username, [
op.transfer, //HIVE //HBD
op.author_reward, //HBD, HP
op.transfer_to_savings, //HIVE, HBD
op.transfer_from_savings, //HIVE, HBD
op.fill_convert_request, //HBD
op.fill_order, //HIVE, HBD
op.sps_fund, //HBD
], startIndex, limit);
history = await getAccountHistory(
username,
[
op.transfer, //HIVE //HBD
op.author_reward, //HBD, HP
op.transfer_to_savings, //HIVE, HBD
op.transfer_from_savings, //HIVE, HBD
op.fill_convert_request, //HBD
op.fill_order, //HIVE, HBD
op.sps_fund, //HBD
],
startIndex,
limit,
);
break;
case COIN_IDS.HP:
history = await getAccountHistory(username, [
op.author_reward, //HBD, HP
op.curation_reward, //HP
op.transfer_to_vesting, //HIVE, HP
op.withdraw_vesting, //HIVE, HP
op.interest, //HP
op.claim_reward_balance, //HP
op.comment_benefactor_reward, //HP
op.return_vesting_delegation, //HP
], startIndex, limit);
history = await getAccountHistory(
username,
[
op.author_reward, //HBD, HP
op.curation_reward, //HP
op.transfer_to_vesting, //HIVE, HP
op.withdraw_vesting, //HIVE, HP
op.interest, //HP
op.claim_reward_balance, //HP
op.comment_benefactor_reward, //HP
op.return_vesting_delegation, //HP
],
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
@ -619,49 +595,57 @@ 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;
parseAsset(userdata.vesting_withdraw_rate).amount,
(Number(userdata.to_withdraw) - Number(userdata.withdrawn)) / 1e6,
)
: 0;
const nextVestingSharesWithdrawalHive = isPoweringDown
? vestsToHp(nextVestingSharesWithdrawal, hivePerMVests)
: 0;
const estimateVoteValueStr = '$ ' + getEstimatedAmount(userdata, globalProps);
//aaggregate extra data pairs
const extraDataPairs:DataPair[] = [];
const extraDataPairs: DataPair[] = [];
if (delegatedHP) {
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"