Merge branch 'development' of https://github.com/ecency/ecency-mobile into sa/fix-usenativedriver-warnings

This commit is contained in:
Sadaqat Ali 2022-09-24 19:13:49 +05:00
commit 752de13223
56 changed files with 1633 additions and 1591 deletions

View File

@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch
versionName "3.0.34"
versionName "3.0.35"
resValue "string", "build_config_package", "app.esteem.mobile.android"
multiDexEnabled true
// react-native-image-crop-picker

View File

@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.34</string>
<string>3.0.35</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2813</string>
<string>2814</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>NSAppTransportSecurity</key>

View File

@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.34</string>
<string>3.0.35</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2813</string>
<string>2814</string>
</dict>
</plist>

View File

@ -1132,7 +1132,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2813;
CURRENT_PROJECT_VERSION = 2814;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = 75B6RXTKGT;
EXCLUDED_ARCHS = "";
@ -1211,7 +1211,7 @@
CODE_SIGN_ENTITLEMENTS = Ecency/Ecency.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2813;
CURRENT_PROJECT_VERSION = 2814;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = 75B6RXTKGT;
EXCLUDED_ARCHS = "";

View File

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.34</string>
<string>3.0.35</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.34</string>
<string>3.0.35</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2813</string>
<string>2814</string>
</dict>
</plist>

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>3.0.34</string>
<string>3.0.35</string>
<key>CFBundleVersion</key>
<string>2813</string>
<string>2814</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@ -1,6 +1,6 @@
{
"name": "ecency",
"version": "3.0.34",
"version": "3.0.35",
"displayName": "Ecency",
"private": true,
"rnpm": {
@ -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",
@ -201,7 +203,7 @@
"rn-nodeify": "^10.3.0"
},
"lint-staged": {
"*.js": [
"*.{js,jsx,ts,tsx}": [
"yarn run lint:fix",
"git add"
]

View File

@ -25,16 +25,8 @@ class ContainerHeaderView extends PureComponent {
// Component Functions
render() {
const {
color,
defaultTitle,
fontSize,
hasSeperator,
iconName,
isBoldTitle,
title,
isCenter,
} = this.props;
const { color, defaultTitle, fontSize, hasSeperator, iconName, isBoldTitle, title, isCenter } =
this.props;
return (
<View style={[styles.wrapper, hasSeperator && styles.hasTopBorder]}>

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: {
// height: isAndroidOreo() ? 28 : 40,
},
container: {},
textInput: {
color: '$primaryBlack',
fontSize: 15,
@ -14,8 +12,7 @@ export default EStyleSheet.create({
borderTopColor: '$primaryLightGray',
borderBottomWidth: 1,
borderBottomColor: '$primaryLightGray',
flex: 1,
height: isAndroidOreo() ? 28 : 40,
height: isAndroidOreo() ? 36 : 40,
},
warning: {
color: '$primaryRed',

View File

@ -37,16 +37,8 @@ class MainButton extends Component {
};
_getBody = () => {
const {
isLoading,
text,
secondText,
iconColor,
iconName,
source,
iconType,
textStyle,
} = this.props;
const { isLoading, text, secondText, iconColor, iconName, source, iconType, textStyle } =
this.props;
if (isLoading) {
this._getIndicator();

View File

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

View File

@ -7,6 +7,7 @@ import {
Platform,
ScrollView,
TouchableOpacity,
TextStyle,
} from 'react-native';
import { renderPostBody, postBodySummary } from '@ecency/render-helper';
import { useDispatch, useSelector } from 'react-redux';
@ -22,9 +23,7 @@ import { toggleAccountsBottomSheet } from '../../../redux/actions/uiAction';
// Components
import {
IconButton,
PostBody,
StickyBar,
TextInput,
UserAvatar,
TitleArea,
@ -33,13 +32,10 @@ import {
SummaryArea,
Modal,
SnippetsModal,
UploadsGalleryModal,
Tooltip,
InsertLinkModal,
} from '../../index';
import { ThemeContainer } from '../../../containers';
// Styles
import styles from '../styles/markdownEditorStyles';
import applySnippet from '../children/formats/applySnippet';
@ -52,6 +48,7 @@ import { walkthrough } from '../../../redux/constants/walkthroughConstants';
import { MediaInsertData } from '../../uploadsGalleryModal/container/uploadsGalleryModal';
import { EditorToolbar } from '../children/editorToolbar';
import { extractImageUrls } from '../../../utils/editor';
import { useAppSelector } from '../../../hooks';
const MIN_BODY_INPUT_HEIGHT = 300;
@ -63,15 +60,12 @@ var bodySelection = { start: 0, end: 0 };
const MarkdownEditorView = ({
paramFiles,
draftBody,
handleOpenImagePicker,
intl,
isPreviewActive,
isReply,
isLoading,
isUploading,
initialFields,
onChange,
uploadedImage,
isEdit,
post,
fields,
@ -86,6 +80,8 @@ const MarkdownEditorView = ({
}) => {
const dispatch = useDispatch();
const isDarkTheme = useAppSelector(state => state.application.isDarkTheme);
const [editable, setEditable] = useState(true);
const [bodyInputHeight, setBodyInputHeight] = useState(MIN_BODY_INPUT_HEIGHT);
const [isSnippetsOpen, setIsSnippetsOpen] = useState(false);
@ -93,6 +89,7 @@ const MarkdownEditorView = ({
const [isEditing, setIsEditing] = useState(false);
const [insertedMediaUrls, setInsertedMediaUrls] = useState([]);
const inputRef = useRef(null);
const clearRef = useRef(null);
const insertLinkModalRef = useRef(null);
@ -199,20 +196,20 @@ const MarkdownEditorView = ({
};
const _debouncedOnTextChange = useCallback(debounce(()=>{
const _debouncedOnTextChange = useCallback(debounce(() => {
console.log("setting is editing to", false)
setIsEditing(false)
const urls = extractImageUrls({body:bodyText})
if(urls.length !== insertedMediaUrls.length){
const urls = extractImageUrls({ body: bodyText })
if (urls.length !== insertedMediaUrls.length) {
setInsertedMediaUrls(urls);
}
}, 500),[])
}, 500), [])
// eslint-disable-next-line react-hooks/exhaustive-deps
const _changeText = useCallback((input) => {
bodyText = input;
if(!isEditing){
if (!isEditing) {
console.log('force setting is editing to true', true)
setIsEditing(true)
}
@ -364,7 +361,7 @@ const MarkdownEditorView = ({
_setTextAndSelection({ text: '', selection: { start: 0, end: 0 } });
}
};
const _renderEditor = () => (
const _renderEditor = (bodyInputStyle:TextStyle, editorScrollEnabled:boolean) => (
<>
{isReply && !isEdit && <SummaryArea summary={headerText} />}
{!isReply && (
@ -407,8 +404,6 @@ const MarkdownEditorView = ({
</View>
)}
{!isPreviewActive ? (
<ThemeContainer>
{({ isDarkTheme }) => (
<TextInput
multiline
autoCorrect={true}
@ -420,55 +415,49 @@ const MarkdownEditorView = ({
})}
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
selectionColor="#357ce6"
style={{ ...styles.textWrapper, height: bodyInputHeight }}
style={{...styles.textWrapper, ...bodyInputStyle}}
underlineColorAndroid="transparent"
innerRef={inputRef}
editable={editable}
contextMenuHidden={false}
autoGrow={false}
scrollEnabled={false}
scrollEnabled={editorScrollEnabled}
onContentSizeChange={_handleOnContentSizeChange}
/>
)}
</ThemeContainer>
) : (
_renderPreview()
)}
</>
);
const _renderEditorWithScroll = () => (
<ScrollView style={styles.container}>{_renderEditor()}</ScrollView>
);
const _renderEditorWithoutScroll = () => <View style={styles.container}>{_renderEditor()}</View>;
const _editorWithScroll = <ScrollView style={styles.container}>{_renderEditor({height:bodyInputHeight}, false)}</ScrollView>;
const _editorWithoutScroll = <View style={styles.container}>{_renderEditor({flex:1}, true)}</View>;
const _renderContent = () => {
const _innerContent = (
<>
{isAndroidOreo() ? _renderEditorWithoutScroll() : _renderEditorWithScroll()}
{isAndroidOreo() ? _editorWithoutScroll : _editorWithScroll}
<UsernameAutofillBar text={bodyText} selection={bodySelection} onApplyUsername={_onApplyUsername} />
{_renderFloatingDraftButton()}
<EditorToolbar
insertedMediaUrls={insertedMediaUrls}
isPreviewActive={isPreviewActive}
paramFiles={paramFiles}
setIsUploading={setIsUploading}
handleMediaInsert={_handleMediaInsert}
handleOnAddLinkPress={_handleOnAddLinkPress}
handleShowSnippets={() => setIsSnippetsOpen(true)}
handleOnClearPress={() => clearRef.current.show()}
handleOnMarkupButtonPress={(item) => {
item.onPress({
text: bodyText,
selection: bodySelection,
setTextAndSelection: _setTextAndSelection,
item
})
}}
/>
<EditorToolbar
insertedMediaUrls={insertedMediaUrls}
isPreviewActive={isPreviewActive}
paramFiles={paramFiles}
setIsUploading={setIsUploading}
handleMediaInsert={_handleMediaInsert}
handleOnAddLinkPress={_handleOnAddLinkPress}
handleShowSnippets={() => setIsSnippetsOpen(true)}
handleOnClearPress={() => clearRef.current.show()}
handleOnMarkupButtonPress={(item) => {
item.onPress({
text: bodyText,
selection: bodySelection,
setTextAndSelection: _setTextAndSelection,
item
})
}}
/>
</>
);

View File

@ -56,6 +56,7 @@ const NotificationLineView = ({
if (
notification.type === 'vote' ||
notification.type === 'reblog' ||
notification.type === 'favorites' ||
(notification.type === 'mention' && notification.post)
) {
_moreinfo = notification.title || notification.permlink;

View File

@ -61,7 +61,6 @@ const PostsContainer = ({
const isHideImages = useSelector((state) => state.application.hidePostsThumbnails);
const username = useSelector((state) => state.account.currentAccount.name);
const isLoggedIn = useSelector((state) => state.application.isLoggedIn);
const isAnalytics = useSelector((state) => state.application.isAnalytics);
const currentAccount = useSelector((state) => state.account.currentAccount);
const pinCode = useSelector((state) => state.application.pin);
const leaderboard = useSelector((state) => state.user.leaderboard);
@ -813,7 +812,6 @@ const PostsContainer = ({
handleOnScroll={_handleOnScroll}
isHideImage={isHideImages}
isLoggedIn={isLoggedIn}
isAnalytics={isAnalytics}
selectedOptionIndex={selectedOptionIndex}
tag={tag}
filterOptionsValue={filterOptionsValue}

View File

@ -20,7 +20,6 @@ const CommentsTabContent = ({isOwnProfile, username, type, onScroll, selectedUse
const intl = useIntl();
const isHideImage = useAppSelector(state => state.application.hidePostsThumbnails);
const isAnalytics = useAppSelector(state => state.application.isAnalytics);
const [data, setData] = useState([]);
const [lastAuthor, setLastAuthor] = useState('');

View File

@ -56,13 +56,8 @@ class TabBar extends PureComponent {
};
_renderUnderline = () => {
const {
tabs,
tabUnderlineDefaultWidth,
tabUnderlineScaleX,
scrollValue,
underlineStyle,
} = this.props;
const { tabs, tabUnderlineDefaultWidth, tabUnderlineScaleX, scrollValue, underlineStyle } =
this.props;
const { activeColor } = this.state;
const containerWidth = getWindowDimensions().nativeWidth;

View File

@ -20,7 +20,6 @@ export const loadPosts = async ({
pageType,
tag,
nsfw,
isAnalytics
}:LoadPostsOptions) => {
let filter = filterKey;

View File

@ -41,7 +41,6 @@ export interface TabMeta {
pageType:string,
tag:string,
nsfw:string,
isAnalytics:boolean,
isLatestPostsCheck?:boolean,
refreshing?:boolean,

View File

@ -43,7 +43,6 @@ const TabContent = ({
//redux properties
const dispatch = useDispatch();
const isLoggedIn = useSelector((state) => state.application.isLoggedIn);
const isAnalytics = useSelector((state) => state.application.isAnalytics);
const nsfw = useSelector((state) => state.application.nsfw);
const isConnected = useSelector((state) => state.application.isConnected);
const currentAccount = useSelector((state) => state.account.currentAccount);
@ -195,7 +194,6 @@ const TabContent = ({
prevPosts:_posts,
tabMeta:_tabMeta,
isLoggedIn,
isAnalytics,
nsfw,
isConnected,
isFeedScreen,

View File

@ -1,23 +0,0 @@
import React from 'react';
import { TextInput, NativeModules } from 'react-native';
import { ThemeContainer } from '../../../containers';
// Styles
import styles from './textInputStyles';
const TextInputView = ({ innerRef, height, style, ...props }) => (
<ThemeContainer>
{({ isDarkTheme }) => (
<TextInput
ref={innerRef}
keyboardAppearance={isDarkTheme ? 'dark' : 'light'}
textAlign={NativeModules.I18nManager.isRTL ? 'right' : 'left'}
{...props}
style={[styles.input, { minHeight: height }, style]}
/>
)}
</ThemeContainer>
);
export default TextInputView;

View File

@ -0,0 +1,28 @@
import React, { Ref } from 'react';
import { TextInput, NativeModules, TextInputProps, TextStyle } from 'react-native';
import { useAppSelector } from '../../../hooks';
// Styles
import styles from './textInputStyles';
interface Props extends TextInputProps {
innerRef: Ref<TextInput>,
height: number,
style: TextStyle
}
const TextInputView = ({ innerRef, height, style, ...props }: Props) => {
const isDarkTheme = useAppSelector(state => state.application.isDarkTheme);
return (
<TextInput
ref={innerRef}
keyboardAppearance={isDarkTheme ? 'dark' : 'light'}
textAlign={NativeModules.I18nManager.isRTL ? 'right' : 'left'}
{...props}
style={[styles.input, { minHeight: height }, style]}
/>
)
};
export default TextInputView;

View File

@ -141,6 +141,7 @@
"unvote": "downvoted",
"reply": "replied to",
"mention": "mentioned in",
"favorites": "made new post",
"follow": "followed you",
"unfollow": "unfollowed you",
"ignore": "ignored you",
@ -227,6 +228,7 @@
"vote": "Vote",
"comment": "Comment",
"mention": "Mention",
"favorite": "Favorites",
"reblog": "Reblog",
"transfers": "Transfers",
"delegations": "Delegations"

View File

@ -65,7 +65,6 @@ class ProfileContainer extends Component {
navigation,
isConnected,
isLoggedIn,
isAnalytics,
currentAccount: { name: currentAccountUsername },
} = this.props;
const username = get(navigation, 'state.params.username');
@ -93,7 +92,6 @@ class ProfileContainer extends Component {
const { isOwnProfile, comments, user } = this.state;
const {
currentAccount: { name: currentAccountUsername },
isAnalytics,
} = this.props;
this.setState({ isProfileLoading: true });
let repliesAction;
@ -585,7 +583,6 @@ const mapStateToProps = (state) => ({
isDarkTheme: state.application.isDarkTheme,
isLoggedIn: state.application.isLoggedIn,
pinCode: state.application.pin,
isAnalytics: state.application.isAnalytics,
activeBottomTab: state.ui.activeBottomTab,
currentAccount: state.account.currentAccount,
isHideImage: state.application.hidePostsThumbnails,

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

@ -416,6 +416,9 @@ export const setNotificationSettings = async ({ type, action }) => {
case 'notification.mention':
setting.mentionNotification = action;
break;
case 'notification.favorite':
setting.favoriteNotification = action;
break;
case 'notification.reblog':
setting.reblogNotification = action;
break;
@ -524,6 +527,7 @@ export const getSettings = async () => {
voteNotification: true,
commentNotification: true,
mentionNotification: true,
favoriteNotification: true,
reblogNotification: true,
transfersNotification: true,
isPinCodeOpen: false,

View File

@ -4,12 +4,12 @@ import {
CHANGE_COMMENT_NOTIFICATION,
CHANGE_FOLLOW_NOTIFICATION,
CHANGE_MENTION_NOTIFICATION,
CHANGE_FAVORITE_NOTIFICATION,
CHANGE_REBLOG_NOTIFICATION,
CHANGE_TRANSFERS_NOTIFICATION,
CHANGE_ALL_NOTIFICATION_SETTINGS,
CHANGE_VOTE_NOTIFICATION,
IS_CONNECTED,
IS_ANALYTICS,
IS_DARK_THEME,
IS_DEFAULT_FOOTER,
IS_LOGIN_DONE,
@ -32,7 +32,7 @@ import {
SET_IS_BIOMETRIC_ENABLED,
SET_ENC_UNLOCK_PIN,
SET_POST_UPVOTE_PERCENT,
SET_COMMENT_UPVOTE_PERCENT
SET_COMMENT_UPVOTE_PERCENT,
} from '../constants/constants';
export const login = (payload) => ({
@ -52,9 +52,6 @@ export const isLoginDone = () => ({
type: IS_LOGIN_DONE,
});
// Settings actions
export const setLanguage = (payload) => ({
payload,
@ -66,7 +63,6 @@ export const setApi = (payload) => ({
type: SET_API,
});
export const setPostUpvotePercent = (payload) => ({
payload,
type: SET_POST_UPVOTE_PERCENT,
@ -108,6 +104,12 @@ export const changeNotificationSettings = (payload) => {
type: CHANGE_MENTION_NOTIFICATION,
};
case 'notification.favorite':
return {
payload: payload.action,
type: CHANGE_FAVORITE_NOTIFICATION,
};
case 'notification.reblog':
return {
payload: payload.action,
@ -136,10 +138,10 @@ export const isDarkTheme = (payload) => ({
type: IS_DARK_THEME,
});
export const setColorTheme = (payload:number) => ({
export const setColorTheme = (payload: number) => ({
payload,
type: SET_COLOR_THEME
})
type: SET_COLOR_THEME,
});
export const isPinCodeOpen = (payload) => ({
payload,
@ -151,11 +153,6 @@ export const setConnectivityStatus = (payload) => ({
type: IS_CONNECTED,
});
export const setAnalyticsStatus = (payload) => ({
payload,
type: IS_ANALYTICS,
});
export const setNsfw = (payload) => ({
payload,
type: SET_NSFW,
@ -172,11 +169,11 @@ export const isDefaultFooter = (payload) => ({
export const setCurrency = (currency) => async (dispatch) => {
const currencySymbol = getSymbolFromCurrency(currency);
const currencyRate = await getCurrencyRate(currency)
const currencyRate = await getCurrencyRate(currency);
dispatch({
type: SET_CURRENCY,
payload: { currency, currencyRate, currencySymbol },
})
});
};
export const setPinCode = (data) => ({
@ -189,34 +186,32 @@ export const isRenderRequired = (payload) => ({
type: IS_RENDER_REQUIRED,
});
export const setLastAppVersion = (versionNumber:string) => ({
payload:versionNumber,
type: SET_LAST_APP_VERSION
})
export const setLastAppVersion = (versionNumber: string) => ({
payload: versionNumber,
type: SET_LAST_APP_VERSION,
});
export const setSettingsMigrated = (isMigrated:boolean) => ({
payload:isMigrated,
type: SET_SETTINGS_MIGRATED
})
export const setSettingsMigrated = (isMigrated: boolean) => ({
payload: isMigrated,
type: SET_SETTINGS_MIGRATED,
});
export const setHidePostsThumbnails = (shouldHide:boolean) => ({
payload:shouldHide,
export const setHidePostsThumbnails = (shouldHide: boolean) => ({
payload: shouldHide,
type: HIDE_POSTS_THUMBNAILS,
});
export const setIsTermsAccepted = (isTermsAccepted:boolean) => ({
payload:isTermsAccepted,
type: SET_TERMS_ACCEPTED
})
export const setIsBiometricEnabled = (enabled:boolean) => ({
payload:enabled,
type: SET_IS_BIOMETRIC_ENABLED
})
export const setEncryptedUnlockPin = (encryptedUnlockPin:string) => ({
payload:encryptedUnlockPin,
type: SET_ENC_UNLOCK_PIN
})
export const setIsTermsAccepted = (isTermsAccepted: boolean) => ({
payload: isTermsAccepted,
type: SET_TERMS_ACCEPTED,
});
export const setIsBiometricEnabled = (enabled: boolean) => ({
payload: enabled,
type: SET_IS_BIOMETRIC_ENABLED,
});
export const setEncryptedUnlockPin = (encryptedUnlockPin: string) => ({
payload: encryptedUnlockPin,
type: SET_ENC_UNLOCK_PIN,
});

View File

@ -5,7 +5,6 @@ export const SET_USER_DATA = 'SET_USER_DATA';
// Applicaiton
export const IS_CONNECTED = 'IS_CONNECTED';
export const IS_ANALYTICS = 'IS_ANALYTICS';
export const IS_DARK_THEME = 'IS_DARK_THEME';
export const IS_PIN_CODE_OPEN = 'IS_PIN_CODE_OPEN';
export const IS_LOGGED_IN = 'IS_LOGGED_IN';
@ -28,6 +27,7 @@ export const CHANGE_FOLLOW_NOTIFICATION = 'CHANGE_FOLLOW_NOTIFICATION';
export const CHANGE_VOTE_NOTIFICATION = 'CHANGE_VOTE_NOTIFICATION';
export const CHANGE_COMMENT_NOTIFICATION = 'CHANGE_COMMENT_NOTIFICATION';
export const CHANGE_MENTION_NOTIFICATION = 'CHANGE_MENTION_NOTIFICATION';
export const CHANGE_FAVORITE_NOTIFICATION = 'CHANGE_FAVORITE_NOTIFICATION';
export const CHANGE_REBLOG_NOTIFICATION = 'CHANGE_REBLOG_NOTIFICATION';
export const CHANGE_TRANSFERS_NOTIFICATION = 'CHANGE_TRANSFERS_NOTIFICATION';
export const CHANGE_ALL_NOTIFICATION_SETTINGS = 'CHANGE_ALL_NOTIFICATION_SETTINGS';

View File

@ -2,12 +2,12 @@ import {
CHANGE_COMMENT_NOTIFICATION,
CHANGE_FOLLOW_NOTIFICATION,
CHANGE_MENTION_NOTIFICATION,
CHANGE_FAVORITE_NOTIFICATION,
CHANGE_REBLOG_NOTIFICATION,
CHANGE_TRANSFERS_NOTIFICATION,
CHANGE_VOTE_NOTIFICATION,
CHANGE_ALL_NOTIFICATION_SETTINGS,
IS_CONNECTED,
IS_ANALYTICS,
IS_DARK_THEME,
IS_DEFAULT_FOOTER,
IS_LOGIN_DONE,
@ -30,50 +30,50 @@ import {
HIDE_POSTS_THUMBNAILS,
SET_TERMS_ACCEPTED,
SET_IS_BIOMETRIC_ENABLED,
SET_ENC_UNLOCK_PIN
SET_ENC_UNLOCK_PIN,
} from '../constants/constants';
interface State {
api: string;
currency: {
currency: string,
currencyRate: number,
currencySymbol: string,
},
isConnected: boolean|null, // internet connectivity
currency: string;
currencyRate: number;
currencySymbol: string;
};
isConnected: boolean | null; // internet connectivity
isDarkTheme: boolean;
colorTheme: number;
isDefaultFooter: boolean; //TODO: remove present of isDefaultFooter as it's no longer in use
isLoggedIn: boolean; // Has any logged in user.
isAnalytics: boolean;
isLoginDone: boolean;
isLogingOut: boolean;
isNotificationOpen: boolean;
language: string,
language: string;
loading: boolean; // It is lock to all screen and shows loading animation.
notificationDetails: {
commentNotification: boolean,
followNotification: boolean,
mentionNotification: boolean,
reblogNotification: boolean,
transfersNotification: boolean,
voteNotification: boolean,
},
commentNotification: boolean;
followNotification: boolean;
mentionNotification: boolean;
favoriteNotification: boolean;
reblogNotification: boolean;
transfersNotification: boolean;
voteNotification: boolean;
};
postUpvotePercent: number;
commentUpvotePercent: number;
nsfw: string;
pin: string|null; //encrypted pin used for encrypting sensitive user data
pin: string | null; //encrypted pin used for encrypting sensitive user data
isPinCodeOpen: boolean;
isRenderRequired: boolean;
encUnlockPin: string; //ecryped pin used for user defined lock screen pass code
lastAppVersion:string;
lastAppVersion: string;
settingsMigratedV2: boolean;
hidePostsThumbnails: boolean;
isTermsAccepted: boolean;
isBiometricEnabled: boolean;
}
const initialState:State = {
const initialState: State = {
api: 'rpc.ecency.com',
currency: {
currency: 'usd',
@ -85,7 +85,6 @@ const initialState:State = {
colorTheme: 0, //values mapped from => src/constants/options/theme.ts
isDefaultFooter: true, //TODO: remove present of isDefaultFooter as it's no longer in use
isLoggedIn: false, // Has any logged in user.
isAnalytics: false,
isLoginDone: false,
isLogingOut: false,
isNotificationOpen: true,
@ -95,6 +94,7 @@ const initialState:State = {
commentNotification: true,
followNotification: true,
mentionNotification: true,
favoriteNotification: true,
reblogNotification: true,
transfersNotification: true,
voteNotification: true,
@ -106,14 +106,14 @@ const initialState:State = {
isPinCodeOpen: false,
isRenderRequired: false,
encUnlockPin: '',
lastAppVersion:'',
lastAppVersion: '',
settingsMigratedV2: false,
hidePostsThumbnails: false,
isTermsAccepted: false,
isBiometricEnabled: false
isBiometricEnabled: false,
};
export default function (state = initialState, action):State {
export default function (state = initialState, action): State {
switch (action.type) {
case LOGIN:
return {
@ -130,11 +130,7 @@ export default function (state = initialState, action):State {
...state,
isConnected: action.payload,
};
case IS_ANALYTICS:
return {
...state,
isAnalytics: action.payload,
};
case LOGOUT:
return {
...state,
@ -183,6 +179,13 @@ export default function (state = initialState, action):State {
mentionNotification: action.payload,
},
});
case CHANGE_FAVORITE_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
favoriteNotification: action.payload,
},
});
case CHANGE_REBLOG_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
@ -210,6 +213,7 @@ export default function (state = initialState, action):State {
notificationDetails: {
...state.notificationDetails,
mentionNotification: action.payload.mentionNotification,
favoriteNotification: action.payload.favoriteNotification,
reblogNotification: action.payload.reblogNotification,
transfersNotification: action.payload.transfersNotification,
voteNotification: action.payload.voteNotification,
@ -224,7 +228,7 @@ export default function (state = initialState, action):State {
case SET_COLOR_THEME:
return {
...state,
colorTheme:action.payload
colorTheme: action.payload,
};
case IS_PIN_CODE_OPEN:
return Object.assign({}, state, {
@ -256,42 +260,42 @@ export default function (state = initialState, action):State {
return Object.assign({}, state, {
isRenderRequired: action.payload,
});
case SET_LAST_APP_VERSION:
return {
...state,
lastAppVersion:action.payload
}
lastAppVersion: action.payload,
};
case SET_SETTINGS_MIGRATED:
return {
...state,
settingsMigratedV2:action.payload
}
settingsMigratedV2: action.payload,
};
case HIDE_POSTS_THUMBNAILS:
return {
...state,
hidePostsThumbnails:action.payload
}
hidePostsThumbnails: action.payload,
};
case SET_TERMS_ACCEPTED:
return {
...state,
isTermsAccepted:action.payload
}
isTermsAccepted: action.payload,
};
case SET_IS_BIOMETRIC_ENABLED:
return {
...state,
isBiometricEnabled:action.payload
}
isBiometricEnabled: action.payload,
};
case SET_ENC_UNLOCK_PIN:
return {
...state,
encUnlockPin:action.payload
}
encUnlockPin: action.payload,
};
default:
return state;

View File

@ -40,7 +40,7 @@ const persistConfig = {
key: 'root',
// Storage Method (React Native)
storage: AsyncStorage,
version: 0, // New version 0, default or previous version -1, versions are useful migrations
version: 1, // New version 0, default or previous version -1, versions are useful migrations
// Blacklist (Don't Save Specific Reducers)
blacklist: ['communities', 'user', 'ui'],
timeout: 0,

View File

@ -13,7 +13,7 @@ import messaging from '@react-native-firebase/messaging';
import PushNotification from 'react-native-push-notification';
import VersionNumber from 'react-native-version-number';
import ReceiveSharingIntent from 'react-native-receive-sharing-intent';
import SplashScreen from 'react-native-splash-screen'
import SplashScreen from 'react-native-splash-screen';
// Constants
import AUTH_TYPE from '../../../constants/authType';
@ -89,14 +89,12 @@ import MigrationHelpers from '../../../utils/migrationHelpers';
import { deepLinkParser } from '../../../utils/deepLinkParser';
import bugsnapInstance from '../../../config/bugsnag';
let firebaseOnNotificationOpenedAppListener = null;
let firebaseOnMessageListener = null;
let removeAppearanceListener = null;
class ApplicationContainer extends Component {
_pinCodeTimer:any = null
_pinCodeTimer: any = null;
constructor(props) {
super(props);
@ -109,8 +107,7 @@ class ApplicationContainer extends Component {
}
componentDidMount = () => {
const { dispatch, isAnalytics } = this.props;
const { dispatch } = this.props;
this._setNetworkListener();
@ -133,20 +130,19 @@ class ApplicationContainer extends Component {
this._fetchApp();
ReceiveSharingIntent.getReceivedFiles(
files => {
(files) => {
navigate({
routeName: ROUTES.SCREENS.EDITOR,
params: { hasSharedIntent: true, files },
});
// files returns as JSON Array example
//[{ filePath: null, text: null, weblink: null, mimeType: null, contentUri: null, fileName: null, extension: null }]
ReceiveSharingIntent.clearReceivedFiles(); // clear Intents
ReceiveSharingIntent.clearReceivedFiles(); // clear Intents
},
(error) => {
console.log('error :>> ', error);
},
);
};
componentDidUpdate(prevProps, prevState) {
@ -167,7 +163,6 @@ class ApplicationContainer extends Component {
}
componentWillUnmount() {
const { isPinCodeOpen: _isPinCodeOpen } = this.props;
//TOOD: listen for back press and cancel all pending api requests;
@ -225,15 +220,14 @@ class ApplicationContainer extends Component {
_handleDeepLink = async (url = '') => {
const { currentAccount, intl } = this.props;
if(!url){
if (!url) {
return;
}
try{
try {
const deepLinkData = await deepLinkParser(url, currentAccount);
const { routeName, params, key } = deepLinkData || {};
if (routeName && key) {
navigate({
routeName,
@ -241,10 +235,9 @@ class ApplicationContainer extends Component {
key: key,
});
}
} catch(err){
this._handleAlert(err.message)
} catch (err) {
this._handleAlert(err.message);
}
};
_compareAndPromptForUpdate = async () => {
@ -309,16 +302,15 @@ class ApplicationContainer extends Component {
);
};
_handleAppStateChange = (nextAppState) => {
const { isPinCodeOpen:_isPinCodeOpen } = this.props;
const { isPinCodeOpen: _isPinCodeOpen } = this.props;
const { appState } = this.state;
if (appState.match(/inactive|background/) && nextAppState === 'active') {
this._refreshGlobalProps();
if (_isPinCodeOpen && this._pinCodeTimer) {
clearTimeout(this._pinCodeTimer);
}
}
}
if (appState.match(/active|forground/) && nextAppState === 'inactive') {
@ -330,12 +322,10 @@ class ApplicationContainer extends Component {
});
};
_fetchApp = async () => {
const {dispatch, settingsMigratedV2} = this.props;
const { dispatch, settingsMigratedV2 } = this.props;
await MigrationHelpers.migrateSettings(dispatch, settingsMigratedV2)
await MigrationHelpers.migrateSettings(dispatch, settingsMigratedV2);
this._refreshGlobalProps();
await this._getUserDataFromRealm();
@ -345,15 +335,15 @@ class ApplicationContainer extends Component {
};
_startPinCodeTimer = () => {
const {isPinCodeOpen:_isPinCodeOpen} = this.props;
const { isPinCodeOpen: _isPinCodeOpen } = this.props;
if (_isPinCodeOpen) {
this._pinCodeTimer = setTimeout(() => {
navigate({
routeName:ROUTES.SCREENS.PINCODE
})
}, 1 * 60 * 1000);
this._pinCodeTimer = setTimeout(() => {
navigate({
routeName: ROUTES.SCREENS.PINCODE,
});
}, 1 * 60 * 1000);
}
};
};
_pushNavigate = (notification) => {
const { dispatch } = this.props;
@ -361,7 +351,7 @@ class ApplicationContainer extends Component {
let key = null;
let routeName = null;
if (!!notification) {
if (notification) {
const push = get(notification, 'data');
const type = get(push, 'type', '');
const fullPermlink =
@ -467,7 +457,6 @@ class ApplicationContainer extends Component {
this.setState({
foregroundNotificationData: remoteMessage,
});
});
firebaseOnNotificationOpenedAppListener = messaging().onNotificationOpenedApp(
@ -492,7 +481,6 @@ class ApplicationContainer extends Component {
}
};
_refreshGlobalProps = () => {
const { actions } = this.props;
actions.fetchGlobalProperties();
@ -556,11 +544,10 @@ class ApplicationContainer extends Component {
await switchAccount(realmObject[0].username);
}
realmObject[0].name = currentUsername;
// If in dev mode pin code does not show
if (_isPinCodeOpen) {
navigate({routeName:ROUTES.SCREENS.PINCODE})
navigate({ routeName: ROUTES.SCREENS.PINCODE });
} else if (!_isPinCodeOpen) {
const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY);
dispatch(savePinCode(encryptedPin));
@ -568,7 +555,6 @@ class ApplicationContainer extends Component {
if (isConnected) {
this._fetchUserDataFromDsteem(realmObject[0]);
}
return realmObject[0];
@ -655,11 +641,10 @@ class ApplicationContainer extends Component {
}
};
//update notification settings and update push token for each signed accoutn useing access tokens
_registerDeviceForNotifications = (settings?: any) => {
const { currentAccount, otherAccounts, notificationDetails, isNotificationsEnabled } = this.props;
const { currentAccount, otherAccounts, notificationDetails, isNotificationsEnabled } =
this.props;
const isEnabled = settings ? !!settings.notification : isNotificationsEnabled;
settings = settings || notificationDetails;
@ -675,28 +660,31 @@ class ApplicationContainer extends Component {
}
this._enableNotification(account.name, isEnabled, settings, accessToken);
}
};
//updateing fcm token with settings;
otherAccounts.forEach((account) => {
//since there can be more than one accounts, process access tokens separate
if (account?.local?.accessToken) {
_enabledNotificationForAccount(account)
_enabledNotificationForAccount(account);
} else {
console.warn("access token not present, reporting to bugsnag")
bugsnapInstance.notify(new Error(`Reporting missing access token in other accounts section: account:${account.name} with local data ${JSON.stringify(account?.local)}`))
console.warn('access token not present, reporting to bugsnag');
bugsnapInstance.notify(
new Error(
`Reporting missing access token in other accounts section: account:${
account.name
} with local data ${JSON.stringify(account?.local)}`,
),
);
//fallback to current account access token to register atleast logged in account
if (currentAccount.name === account.name) {
_enabledNotificationForAccount(currentAccount)
_enabledNotificationForAccount(currentAccount);
}
}
});
};
_connectNotificationServer = (username) => {
/* eslint no-undef: "warn" */
const ws = new WebSocket(`${Config.ACTIVITY_WEBSOCKET_URL}?user=${username}`);
@ -720,7 +708,6 @@ class ApplicationContainer extends Component {
currentAccount: { name, local },
dispatch,
intl,
} = this.props;
removeUserData(name)
@ -742,7 +729,7 @@ class ApplicationContainer extends Component {
});
setExistUser(false);
dispatch(isPinCodeOpen(false));
dispatch(setEncryptedUnlockPin(encryptKey(Config.DEFAULT_KEU, Config.PIN_KEY)))
dispatch(setEncryptedUnlockPin(encryptKey(Config.DEFAULT_KEU, Config.PIN_KEY)));
if (local.authType === AUTH_TYPE.STEEM_CONNECT) {
removeSCAccount(name);
}
@ -771,6 +758,7 @@ class ApplicationContainer extends Component {
commentNotification: 4,
reblogNotification: 5,
transfersNotification: 6,
favoriteNotification: 13,
};
Object.keys(settings).map((item) => {
@ -836,7 +824,6 @@ class ApplicationContainer extends Component {
dispatch(fetchSubscribedCommunities(_currentAccount.username));
};
UNSAFE_componentWillReceiveProps(nextProps) {
const {
isDarkTheme: _isDarkTheme,
@ -919,7 +906,6 @@ export default connect(
isConnected: state.application.isConnected,
api: state.application.api,
isGlobalRenderRequired: state.application.isRenderRequired,
isAnalytics: state.application.isAnalytics,
lastUpdateCheck: state.application.lastUpdateCheck,
settingsMigratedV2: state.application.settingsMigratedV2,
isNotificationsEnabled: state.application.isNotificationOpen,

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

@ -13,7 +13,7 @@ import PostScreen from '../screen/postScreen';
*@props --> content which is include all post data Object
*
*/
const PostContainer = ({ currentAccount, isLoggedIn, isAnalytics, route }) => {
const PostContainer = ({ currentAccount, isLoggedIn, route }) => {
const [post, setPost] = useState(null);
const [error, setError] = useState(null);
const [isNewPost, setIsNewPost] = useState(false);
@ -119,7 +119,6 @@ const PostContainer = ({ currentAccount, isLoggedIn, isAnalytics, route }) => {
const mapStateToProps = (state) => ({
currentAccount: state.account.currentAccount,
isLoggedIn: state.application.isLoggedIn,
isAnalytics: state.application.isAnalytics,
});
export default connect(mapStateToProps)(PostContainer);

View File

@ -50,7 +50,8 @@ const RegisterScreen = ({ navigation, route }) => {
}, []);
const _handleEmailChange = (value) => {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const re =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
setIsEmailValid(re.test(value));
setEmail(value);
};

View File

@ -39,9 +39,8 @@ const CommunitiesResultsContainer = ({ children, searchValue }) => {
// handle cache when searchResultsScreen data updates in communities reducer
useEffect(() => {
if (subscribingCommunitiesInSearchResultsScreen && selectedCommunityItem) {
const { status } = subscribingCommunitiesInSearchResultsScreen[
selectedCommunityItem.communityId
];
const { status } =
subscribingCommunitiesInSearchResultsScreen[selectedCommunityItem.communityId];
if (status === statusMessage.SUCCESS) {
dispatch(updateSubscribedCommunitiesCache(selectedCommunityItem));
}

View File

@ -6,6 +6,7 @@ import VersionNumber from 'react-native-version-number';
import Config from 'react-native-config';
import { injectIntl } from 'react-intl';
import messaging from '@react-native-firebase/messaging';
import { withNavigation } from '@react-navigation/compat';
import { languageRestart } from '../../../utils/I18nUtils';
import THEME_OPTIONS from '../../../constants/options/theme';
@ -53,7 +54,6 @@ import { VALUE as CURRENCY_VALUE } from '../../../constants/options/currency';
import { VALUE as LANGUAGE_VALUE } from '../../../constants/options/language';
import settingsTypes from '../../../constants/settingsTypes';
// Utilities
import { sendEmail } from '../../../utils/sendEmail';
import { encryptKey, decryptKey } from '../../../utils/crypto';
@ -62,7 +62,6 @@ import { encryptKey, decryptKey } from '../../../utils/crypto';
import SettingsScreen from '../screen/settingsScreen';
import { SERVER_LIST } from '../../../constants/options/api';
import ROUTES from '../../../constants/routeNames';
import { withNavigation } from '@react-navigation/compat';
/*
* Props Name Description Value
@ -212,6 +211,7 @@ class SettingsContainer extends Component {
case 'notification.vote':
case 'notification.comment':
case 'notification.mention':
case 'notification.favorite':
case 'notification.reblog':
case 'notification.transfers':
this._handleNotification(action, actionType);
@ -229,12 +229,11 @@ class SettingsContainer extends Component {
isReset: true,
isOldPinVerified: true,
oldPinCode: Config.DEFAULT_PIN,
})
});
} else {
navigation.navigate(ROUTES.SCREENS.PINCODE, {
callback: () => this._enableDefaultUnlockPin(action),
})
});
}
break;
@ -242,7 +241,7 @@ class SettingsContainer extends Component {
navigation.navigate(ROUTES.SCREENS.PINCODE, {
callback: () => dispatch(setIsBiometricEnabled(action)),
});
break;
case settingsTypes.SHOW_HIDE_IMGS:
dispatch(setHidePostsThumbnails(!isHideImages));
@ -261,6 +260,7 @@ class SettingsContainer extends Component {
comment: 4,
reblog: 5,
transfers: 6,
favorite: 13,
};
const notifyTypes = [];
@ -301,14 +301,14 @@ class SettingsContainer extends Component {
switch (actionType) {
case 'reset_pin':
navigation.navigate(ROUTES.SCREENS.PINCODE, {
isReset:true
})
isReset: true,
});
break;
case 'feedback':
this._handleSendFeedback();
break;
case settingsTypes.DELETE_ACCOUNT:
this._handleDeleteAccount();
break;
@ -433,8 +433,7 @@ class SettingsContainer extends Component {
],
}),
);
}
};
_clearUserData = async () => {
const { otherAccounts, dispatch } = this.props;
@ -474,7 +473,6 @@ class SettingsContainer extends Component {
}, 500);
};
_enableDefaultUnlockPin = (isEnabled) => {
const { dispatch, encUnlockPin } = this.props;
@ -524,6 +522,7 @@ const mapStateToProps = (state) => ({
commentNotification: state.application.notificationDetails.commentNotification,
followNotification: state.application.notificationDetails.followNotification,
mentionNotification: state.application.notificationDetails.mentionNotification,
favoriteNotification: state.application.notificationDetails.favoriteNotification,
reblogNotification: state.application.notificationDetails.reblogNotification,
transfersNotification: state.application.notificationDetails.transfersNotification,
voteNotification: state.application.notificationDetails.voteNotification,

View File

@ -37,6 +37,7 @@ const SettingsScreen = ({
commentNotification,
followNotification,
mentionNotification,
favoriteNotification,
reblogNotification,
transfersNotification,
voteNotification,
@ -247,6 +248,15 @@ const SettingsScreen = ({
isOn={mentionNotification}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.favorite',
})}
type="toggle"
actionType="notification.favorite"
isOn={favoriteNotification}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.reblog',

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 = {};
@ -222,16 +221,16 @@ export const extractMetadata = (body:string, thumbIndex?:number) => {
const mUrls = extractUrls(body);
const mUsers = body && body.match(userReg);
const matchedImages = extractImageUrls({urls:mUrls});
const matchedImages = extractImageUrls({ urls: mUrls });
const matchedLinks = [];
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

@ -104,7 +104,7 @@ export const migrateUserEncryption = async (dispatch, currentAccount, encUserPin
}
try{
try {
const pinData = {
pinCode: Config.DEFAULT_PIN,
username: currentAccount.username,
@ -112,7 +112,7 @@ export const migrateUserEncryption = async (dispatch, currentAccount, encUserPin
};
const response = updatePinCode(pinData)
const _currentAccount = currentAccount;
_currentAccount.local = response;
@ -125,10 +125,10 @@ export const migrateUserEncryption = async (dispatch, currentAccount, encUserPin
const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY);
dispatch(setPinCode(encryptedPin));
} catch(err){
} catch (err) {
console.warn('pin update failure: ', err);
}
dispatch(setEncryptedUnlockPin(encUserPin))
@ -139,54 +139,58 @@ export const migrateUserEncryption = async (dispatch, currentAccount, encUserPin
_currentAccount.local = realmData[0];
try {
const pinHash = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY);
//migration script for previously mast key based logged in user not having access token
if (
realmData[0].authType !== AUTH_TYPE.STEEM_CONNECT &&
realmData[0].accessToken === ''
) {
_currentAccount = await migrateToMasterKeyWithAccessToken(
_currentAccount,
realmData[0],
pinHash,
);
}
const pinHash = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY);
//migration script for previously mast key based logged in user not having access token
if (
realmData[0].authType !== AUTH_TYPE.STEEM_CONNECT &&
realmData[0].accessToken === ''
) {
_currentAccount = await migrateToMasterKeyWithAccessToken(
_currentAccount,
realmData[0],
pinHash,
);
}
//refresh access token
const encryptedAccessToken = await refreshSCToken(_currentAccount.local, Config.DEFAULT_PIN);
_currentAccount.local.accessToken = encryptedAccessToken;
//refresh access token
const encryptedAccessToken = await refreshSCToken(_currentAccount.local, Config.DEFAULT_PIN);
_currentAccount.local.accessToken = encryptedAccessToken;
} catch (error) {
onFailure(error)
}
//get unread notifications
try {
_currentAccount.unread_activity_count = await getUnreadNotificationCount();
_currentAccount.pointsSummary = await getPointsSummary(_currentAccount.username);
_currentAccount.mutes = await getMutes(_currentAccount.username);
_currentAccount.unread_activity_count = await getUnreadNotificationCount();
_currentAccount.pointsSummary = await getPointsSummary(_currentAccount.username);
_currentAccount.mutes = await getMutes(_currentAccount.username);
} catch (err) {
console.warn(
'Optional user data fetch failed, account can still function without them',
err,
);
console.warn(
'Optional user data fetch failed, account can still function without them',
err,
);
}
dispatch(updateCurrentAccount({ ..._currentAccount }));
dispatch(fetchSubscribedCommunities(_currentAccount.username));
}
const reduxMigrations = {
0: (state) => {
const upvotePercent = state.application.upvotePercent;
state.application.postUpvotePercent = upvotePercent;
state.application.commentUpvotePercent = upvotePercent
state.application.upvotePercent = undefined;
return state
const upvotePercent = state.application.upvotePercent;
state.application.postUpvotePercent = upvotePercent;
state.application.commentUpvotePercent = upvotePercent
state.application.upvotePercent = undefined;
return state
},
1: (state) => {
state.application.notificationDetails.favoriteNotification = true
return state;
}
}
}
export default {
migrateSettings,

View File

@ -125,12 +125,9 @@ export default (url) => {
}
if (
[
'https://ecency.com',
'https://hive.blog',
'https://peakd.com',
'https://leofinance.io',
].some((x) => url.startsWith(x))
['https://ecency.com', 'https://hive.blog', 'https://peakd.com', 'https://leofinance.io'].some(
(x) => url.startsWith(x),
)
) {
return parseAuthorPermlink(url);
}

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"