mirror of
https://github.com/ecency/ecency-mobile.git
synced 2025-01-04 03:56:54 +03:00
Merge remote-tracking branch 'origin/development' into nt/ui-fixes
This commit is contained in:
commit
d285675d6d
@ -828,4 +828,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 0282022703ad578ab2d9afbf3147ba3b373b4311
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.11.3
|
||||
|
@ -108,7 +108,7 @@
|
||||
"react-native-iap": "^7.5.6",
|
||||
"react-native-image-crop-picker": "^0.35.2",
|
||||
"react-native-image-zoom-viewer": "^2.2.27",
|
||||
"react-native-iphone-x-helper": "^1.3.1",
|
||||
"react-native-iphone-x-helper": "Norcy/react-native-iphone-x-helper",
|
||||
"react-native-keyboard-aware-scroll-view": "^0.9.1",
|
||||
"react-native-level-fs": "^3.0.0",
|
||||
"react-native-linear-gradient": "^2.4.2",
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View, TouchableHighlight, Animated } from 'react-native';
|
||||
import { View, TouchableHighlight } from 'react-native';
|
||||
import Animated, { Easing } from 'react-native-reanimated';
|
||||
|
||||
// Constants
|
||||
|
||||
@ -50,6 +51,7 @@ class CollapsibleCardView extends PureComponent {
|
||||
Animated.timing(this.anime.height, {
|
||||
toValue: this.anime.expanded ? this._getMinValue() : this._getMaxValue() + (moreHeight || 0),
|
||||
duration: 200,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
}).start();
|
||||
this.anime.expanded = !this.anime.expanded;
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { View, Text, TouchableOpacity } from 'react-native';
|
||||
import { injectIntl } from 'react-intl';
|
||||
|
||||
// Utils
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { getTimeFromNow } from '../../../utils/time';
|
||||
|
||||
// Components
|
||||
@ -14,8 +15,6 @@ import { OptionsModal } from '../../atoms';
|
||||
import styles from './draftListItemStyles';
|
||||
import { ScheduledPostStatus } from '../../../providers/ecency/ecency.types';
|
||||
import { PopoverWrapper } from '../../popoverWrapper/popoverWrapperView';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
|
||||
const DraftListItemView = ({
|
||||
title,
|
||||
@ -59,24 +58,24 @@ const DraftListItemView = ({
|
||||
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>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
import { Icon } from '../../icon';
|
||||
|
||||
import styles from './iconButtonStyles';
|
||||
@ -47,7 +48,10 @@ const IconButton = ({
|
||||
badgeCount={badgeCount}
|
||||
/>
|
||||
) : (
|
||||
<ActivityIndicator color="white" style={styles.activityIndicator} />
|
||||
<ActivityIndicator
|
||||
color={color || EStyleSheet.value('$primaryBlack')}
|
||||
style={styles.activityIndicator}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</Fragment>
|
||||
|
@ -47,14 +47,13 @@ class NotificationView extends PureComponent {
|
||||
// Component Functions
|
||||
|
||||
_handleOnDropdownSelect = async (index) => {
|
||||
const { getActivities, changeSelectedFilter, } = this.props;
|
||||
const { changeSelectedFilter } = this.props;
|
||||
const { filters, contentOffset } = this.state;
|
||||
|
||||
const _selectedFilter = filters[index].key;
|
||||
|
||||
this.setState({ selectedFilter: _selectedFilter, selectedIndex: index, contentOffset });
|
||||
await changeSelectedFilter(_selectedFilter, index);
|
||||
getActivities(_selectedFilter, false);
|
||||
this.listRef.current?.scrollToOffset({ x: 0, y: 0, animated: false });
|
||||
};
|
||||
|
||||
@ -176,34 +175,40 @@ class NotificationView extends PureComponent {
|
||||
return 5;
|
||||
};
|
||||
|
||||
|
||||
_getActivityIndicator = () => (
|
||||
<View style={styles.loading}>
|
||||
<ActivityIndicator animating size="large" />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
_renderSectionHeader = ({ section: { title, index } }) => (
|
||||
<ContainerHeader hasSeperator={index !== 0} isBoldTitle title={title} key={title} />
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
_renderItem = ({ item }) => (
|
||||
<>
|
||||
{item.sectionTitle && <ContainerHeader hasSeperator={!item.firstSection} isBoldTitle title={item.sectionTitle} />}
|
||||
{item.sectionTitle && (
|
||||
<ContainerHeader hasSeperator={!item.firstSection} isBoldTitle title={item.sectionTitle} />
|
||||
)}
|
||||
<NotificationLine
|
||||
notification={item}
|
||||
handleOnPressNotification={this.props.navigateToNotificationRoute}
|
||||
handleOnUserPress={() => { this.props.handleOnUserPress(item.source) }}
|
||||
handleOnUserPress={() => {
|
||||
this.props.handleOnUserPress(item.source);
|
||||
}}
|
||||
globalProps={this.props.globalProps}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
render() {
|
||||
const { readAllNotification, getActivities, isNotificationRefreshing, intl, isLoading } = this.props;
|
||||
const {
|
||||
readAllNotification,
|
||||
getActivities,
|
||||
isNotificationRefreshing,
|
||||
intl,
|
||||
isLoading,
|
||||
} = this.props;
|
||||
const { filters, selectedFilter, selectedIndex } = this.state;
|
||||
const _notifications = this._getNotificationsArrays();
|
||||
|
||||
@ -222,27 +227,28 @@ class NotificationView extends PureComponent {
|
||||
onRightIconPress={readAllNotification}
|
||||
/>
|
||||
<ThemeContainer>
|
||||
{({ isDarkTheme }) =>
|
||||
|
||||
{({ isDarkTheme }) => (
|
||||
<FlatList
|
||||
ref={this.listRef}
|
||||
data={_notifications}
|
||||
keyExtractor={(item, index) => `${item.id}-${index}`}
|
||||
onEndReached={() => getActivities(selectedFilter, true)}
|
||||
onEndReached={() => getActivities(true)}
|
||||
onEndReachedThreshold={0.3}
|
||||
ListFooterComponent={this._renderFooterLoading}
|
||||
ListEmptyComponent={
|
||||
isLoading ? <ListPlaceHolder/> : (
|
||||
<Text style={globalStyles.hintText}>
|
||||
{intl.formatMessage({ id: 'notification.noactivity' })}
|
||||
</Text>
|
||||
isNotificationRefreshing ? (
|
||||
<ListPlaceHolder />
|
||||
) : (
|
||||
<Text style={globalStyles.hintText}>
|
||||
{intl.formatMessage({ id: 'notification.noactivity' })}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}
|
||||
contentContainerStyle={styles.listContentContainer}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isNotificationRefreshing}
|
||||
onRefresh={() => getActivities(selectedFilter)}
|
||||
onRefresh={() => getActivities()}
|
||||
progressBackgroundColor="#357CE6"
|
||||
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
|
||||
titleColor="#fff"
|
||||
@ -251,8 +257,7 @@ class NotificationView extends PureComponent {
|
||||
}
|
||||
renderItem={this._renderItem}
|
||||
/>
|
||||
|
||||
}
|
||||
)}
|
||||
</ThemeContainer>
|
||||
</View>
|
||||
);
|
||||
|
@ -33,6 +33,7 @@ class PinAnimatedInput extends Component {
|
||||
toValue: 1,
|
||||
duration: 250,
|
||||
easing: Easing.linear,
|
||||
useNativeDriver: false, //setting it to false as animation is not being used
|
||||
}),
|
||||
),
|
||||
]).start((o) => {
|
||||
@ -87,6 +88,5 @@ class PinAnimatedInput extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PinAnimatedInput;
|
||||
/* eslint-enable */
|
||||
|
@ -44,7 +44,7 @@ export default EStyleSheet.create({
|
||||
borderColor: '$borderColor',
|
||||
borderRadius: 8,
|
||||
padding: 2,
|
||||
color: '$primaryBlack',
|
||||
// color: '$primaryBlack',
|
||||
width: 172,
|
||||
marginRight: 33,
|
||||
},
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import RenderHTML, { CustomRendererProps, Element, TNode } from 'react-native-render-html';
|
||||
import { useHtmlIframeProps, iframeModel } from '@native-html/iframe-plugin';
|
||||
import styles from './postHtmlRendererStyles';
|
||||
import { LinkData, parseLinkData } from './linkDataParser';
|
||||
import VideoThumb from './videoThumb';
|
||||
import { AutoHeightImage } from '../autoHeightImage/autoHeightImage';
|
||||
import { useHtmlIframeProps, iframeModel } from '@native-html/iframe-plugin';
|
||||
import WebView from 'react-native-webview';
|
||||
import { VideoPlayer } from '..';
|
||||
import { useHtmlTableProps } from '@native-html/table-plugin';
|
||||
@ -46,7 +46,7 @@ export const PostHtmlRenderer = memo(
|
||||
|
||||
console.log('Comment body:', body);
|
||||
|
||||
const _minTableColWidth = (contentWidth / 3) - 12;
|
||||
const _minTableColWidth = contentWidth / 3 - 12;
|
||||
|
||||
const _handleOnLinkPress = (data: LinkData) => {
|
||||
if (!data) {
|
||||
@ -119,10 +119,9 @@ export const PostHtmlRenderer = memo(
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
|
||||
//this method checks if image is a child of table column
|
||||
//and calculates img width accordingly,
|
||||
//returns full width if img is not part of table
|
||||
@ -142,8 +141,6 @@ export const PostHtmlRenderer = memo(
|
||||
return getMaxImageWidth(tnode.parent);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//Does some needed dom modifications for proper rendering
|
||||
const _onElement = (element: Element) => {
|
||||
if (element.tagName === 'img' && element.attribs.src) {
|
||||
@ -152,10 +149,9 @@ export const PostHtmlRenderer = memo(
|
||||
onElementIsImage(imgUrl);
|
||||
}
|
||||
|
||||
|
||||
//this avoids invalid rendering of first element of table pushing rest of columsn to extreme right.
|
||||
if (element.tagName === 'table') {
|
||||
console.log('table detected')
|
||||
console.log('table detected');
|
||||
|
||||
element.children.forEach((child) => {
|
||||
if (child.name === 'tr') {
|
||||
@ -168,15 +164,15 @@ export const PostHtmlRenderer = memo(
|
||||
if (gChild.name !== 'td' && headerIndex === -1) {
|
||||
headerIndex = index;
|
||||
} else if (colIndex === -1) {
|
||||
colIndex = index
|
||||
colIndex = index;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//if row contans a header with column siblings
|
||||
//remove first child and place it as first separate row in table
|
||||
if (headerIndex !== -1 && colIndex !== -1 && headerIndex < colIndex) {
|
||||
console.log("time to do some switching", headerIndex, colIndex);
|
||||
console.log('time to do some switching', headerIndex, colIndex);
|
||||
const header = child.children[headerIndex];
|
||||
const headerRow = new Element('tr', {}, [header]);
|
||||
|
||||
@ -184,13 +180,10 @@ export const PostHtmlRenderer = memo(
|
||||
prependChild(element, headerRow);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const _anchorRenderer = ({ InternalRenderer, tnode, ...props }: CustomRendererProps<TNode>) => {
|
||||
const parsedTnode = parseLinkData(tnode);
|
||||
const _onPress = () => {
|
||||
@ -199,10 +192,8 @@ export const PostHtmlRenderer = memo(
|
||||
_handleOnLinkPress(data);
|
||||
};
|
||||
|
||||
|
||||
//process video link
|
||||
if (tnode.classes?.indexOf('markdown-video-link') >= 0) {
|
||||
|
||||
if (isComment) {
|
||||
const imgElement = tnode.children.find((child) => {
|
||||
return child.classes.indexOf('video-thumbnail') > 0 ? true : false;
|
||||
@ -226,23 +217,20 @@ export const PostHtmlRenderer = memo(
|
||||
|
||||
if (tnode.children.length === 1 && tnode.children[0].tagName === 'img') {
|
||||
const maxImgWidth = getMaxImageWidth(tnode);
|
||||
return <AutoHeightImage
|
||||
contentWidth={maxImgWidth}
|
||||
imgUrl={tnode.children[0].attributes.src}
|
||||
isAnchored={false}
|
||||
activeOpacity={0.8}
|
||||
onPress={_onPress}
|
||||
/>
|
||||
return (
|
||||
<AutoHeightImage
|
||||
contentWidth={maxImgWidth}
|
||||
imgUrl={tnode.children[0].attributes.src}
|
||||
isAnchored={false}
|
||||
activeOpacity={0.8}
|
||||
onPress={_onPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return <InternalRenderer tnode={tnode} onPress={_onPress} {...props} />;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const _imageRenderer = ({ tnode }: CustomRendererProps<TNode>) => {
|
||||
const imgUrl = tnode.attributes.src;
|
||||
const _onPress = () => {
|
||||
@ -280,15 +268,15 @@ export const PostHtmlRenderer = memo(
|
||||
return <TDefaultRenderer {...props} />;
|
||||
};
|
||||
|
||||
|
||||
//based on number of columns a table have, sets scroll enabled or disable, also adjust table full width
|
||||
const _tableRenderer = ({ InternalRenderer, ...props }: CustomRendererProps<TNode>) => {
|
||||
// const tableProps = useHtmlTableProps(props);
|
||||
|
||||
let maxColumns = 0;
|
||||
props.tnode.children.forEach((child) =>
|
||||
maxColumns = child.children.length > maxColumns ? child.children.length : maxColumns
|
||||
)
|
||||
props.tnode.children.forEach(
|
||||
(child) =>
|
||||
(maxColumns = child.children.length > maxColumns ? child.children.length : maxColumns),
|
||||
);
|
||||
|
||||
const isScrollable = maxColumns > 3;
|
||||
const _tableWidth = isScrollable ? maxColumns * _minTableColWidth : contentWidth;
|
||||
@ -298,8 +286,8 @@ export const PostHtmlRenderer = memo(
|
||||
<ScrollView horizontal={true} scrollEnabled={isScrollable}>
|
||||
<InternalRenderer {...props} />
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// iframe renderer for rendering iframes in body
|
||||
@ -313,19 +301,10 @@ export const PostHtmlRenderer = memo(
|
||||
handleVideoPress(iframeProps.source.uri);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<VideoThumb contentWidth={contentWidth} onPress={_onPress} />
|
||||
)
|
||||
return <VideoThumb contentWidth={contentWidth} onPress={_onPress} />;
|
||||
} else {
|
||||
return (
|
||||
<VideoPlayer
|
||||
mode='uri'
|
||||
uri={iframeProps.source.uri}
|
||||
contentWidth={contentWidth}
|
||||
/>
|
||||
);
|
||||
return <VideoPlayer mode="uri" uri={iframeProps.source.uri} contentWidth={contentWidth} />;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const tagsStyles = useMemo(
|
||||
@ -341,68 +320,54 @@ export const PostHtmlRenderer = memo(
|
||||
code: styles.code,
|
||||
li: styles.li,
|
||||
p: styles.p,
|
||||
h6: styles.h6
|
||||
h6: styles.h6,
|
||||
}),
|
||||
[contentWidth]
|
||||
[contentWidth],
|
||||
);
|
||||
|
||||
const baseStyle = useMemo(
|
||||
() => (
|
||||
{ ...styles.baseStyle, width: contentWidth }
|
||||
),
|
||||
[contentWidth]
|
||||
);
|
||||
const baseStyle = useMemo(() => ({ ...styles.baseStyle, width: contentWidth }), [contentWidth]);
|
||||
|
||||
const classesStyles = useMemo(
|
||||
() => (
|
||||
{
|
||||
phishy: styles.phishy,
|
||||
'text-justify': styles.textJustify,
|
||||
'text-center': styles.textCenter,
|
||||
}
|
||||
),
|
||||
() => ({
|
||||
phishy: styles.phishy,
|
||||
'text-justify': styles.textJustify,
|
||||
'text-center': styles.textCenter,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const renderers = useMemo(
|
||||
() => (
|
||||
{
|
||||
() =>
|
||||
({
|
||||
img: _imageRenderer,
|
||||
a: _anchorRenderer,
|
||||
p: _paraRenderer,
|
||||
iframe: _iframeRenderer,
|
||||
table: _tableRenderer
|
||||
} as any
|
||||
),
|
||||
table: _tableRenderer,
|
||||
} as any),
|
||||
[],
|
||||
);
|
||||
|
||||
const domVisitors = useMemo(
|
||||
() => (
|
||||
{
|
||||
onElement: _onElement,
|
||||
}
|
||||
),
|
||||
() => ({
|
||||
onElement: _onElement,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const customHTMLElementModels = useMemo(
|
||||
() => (
|
||||
{
|
||||
iframe: iframeModel,
|
||||
}
|
||||
),
|
||||
() => ({
|
||||
iframe: iframeModel,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const renderersProps = useMemo(
|
||||
() => (
|
||||
{
|
||||
iframe: {
|
||||
scalesPageToFit: true
|
||||
},
|
||||
}
|
||||
),
|
||||
() => ({
|
||||
iframe: {
|
||||
scalesPageToFit: true,
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, StyleSheet, Animated } from 'react-native';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import Animated, { Easing } from 'react-native-reanimated';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
imageOverlay: {
|
||||
@ -31,12 +32,14 @@ const ProgressiveImage = ({ thumbnailSource, source, style, ...props }) => {
|
||||
}*/
|
||||
Animated.timing(thumbnailAnimated, {
|
||||
toValue: 1,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
}).start();
|
||||
};
|
||||
|
||||
const onImageLoad = () => {
|
||||
Animated.timing(imageAnimated, {
|
||||
toValue: 1,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
}).start();
|
||||
};
|
||||
|
||||
|
@ -4,166 +4,143 @@ import { Alert, KeyboardAvoidingView, Platform, View } from 'react-native';
|
||||
import { TextInput } from '..';
|
||||
import { ThemeContainer } from '../../containers';
|
||||
import { Snippet } from '../../models';
|
||||
import { addFragment, updateFragment} from '../../providers/ecency/ecency';
|
||||
import { useSnippetsMutation } from '../../providers/queries';
|
||||
import { TextButton } from '../buttons';
|
||||
import Modal from '../modal';
|
||||
import styles from './snippetEditorModalStyles';
|
||||
|
||||
|
||||
export interface SnippetEditorModalRef {
|
||||
showNewModal:()=>void;
|
||||
showEditModal:(snippet:Snippet)=>void;
|
||||
showNewModal: () => void;
|
||||
showEditModal: (snippet: Snippet) => void;
|
||||
}
|
||||
|
||||
interface SnippetEditorModalProps {
|
||||
onSnippetsUpdated:(snips:Array<Snippet>)=>void;
|
||||
}
|
||||
const SnippetEditorModal = ({}, ref) => {
|
||||
const intl = useIntl();
|
||||
const titleInputRef = useRef(null);
|
||||
const bodyInputRef = useRef(null);
|
||||
|
||||
const SnippetEditorModal = ({onSnippetsUpdated}: SnippetEditorModalProps, ref) => {
|
||||
const intl = useIntl();
|
||||
const titleInputRef = useRef(null);
|
||||
const bodyInputRef = useRef(null);
|
||||
const snippetsMutation = useSnippetsMutation();
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [body, setBody] = useState('');
|
||||
const [snippetId, setSnippetId] = useState<string|null>(null);
|
||||
const [isNewSnippet, setIsNewSnippet] = useState(true);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [titleHeight, setTitleHeight] = useState(0)
|
||||
const [title, setTitle] = useState('');
|
||||
const [body, setBody] = useState('');
|
||||
const [snippetId, setSnippetId] = useState<string | null>(null);
|
||||
const [isNewSnippet, setIsNewSnippet] = useState(true);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [titleHeight, setTitleHeight] = useState(0);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
showNewModal: () => {
|
||||
setTitle('');
|
||||
setBody('');
|
||||
setIsNewSnippet(true);
|
||||
setShowModal(true);
|
||||
},
|
||||
showEditModal:(snippet:Snippet)=>{
|
||||
setSnippetId(snippet.id);
|
||||
setTitle(snippet.title);
|
||||
setBody(snippet.body);
|
||||
setIsNewSnippet(false);
|
||||
setShowModal(true);
|
||||
}
|
||||
}));
|
||||
useImperativeHandle(ref, () => ({
|
||||
showNewModal: () => {
|
||||
setTitle('');
|
||||
setBody('');
|
||||
setIsNewSnippet(true);
|
||||
setShowModal(true);
|
||||
},
|
||||
showEditModal: (snippet: Snippet) => {
|
||||
setSnippetId(snippet.id);
|
||||
setTitle(snippet.title);
|
||||
setBody(snippet.body);
|
||||
setIsNewSnippet(false);
|
||||
setShowModal(true);
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
//save snippet based on editor type
|
||||
const _saveSnippet = async () => {
|
||||
try{
|
||||
if(!title || !body){
|
||||
Alert.alert(intl.formatMessage({id:'snippets.message_incomplete'}));
|
||||
return;
|
||||
}
|
||||
|
||||
let response = [];
|
||||
if(!isNewSnippet){
|
||||
console.log("Updating snippet:", snippetId, title, body)
|
||||
response = await updateFragment(snippetId, title, body);
|
||||
console.log("Response from add snippet: ", response)
|
||||
}else{
|
||||
console.log("Saving snippet:", title, body)
|
||||
const res = await addFragment(title, body)
|
||||
response = res && res.fragments
|
||||
console.log("Response from add snippet: ", response)
|
||||
}
|
||||
setShowModal(false);
|
||||
onSnippetsUpdated(response);
|
||||
|
||||
}catch(err){
|
||||
Alert.alert(intl.formatMessage({id:'snippets.message_failed'}))
|
||||
console.warn("Failed to save snippet", err)
|
||||
}
|
||||
|
||||
//save snippet based on editor type
|
||||
const _saveSnippet = async () => {
|
||||
if (!title || !body) {
|
||||
Alert.alert(intl.formatMessage({ id: 'snippets.message_incomplete' }));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Saving snippet:', title, body);
|
||||
|
||||
const _renderContent = (
|
||||
<ThemeContainer>
|
||||
{({isDarkTheme})=>(
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
keyboardVerticalOffset={Platform.OS == 'ios' ? 64 : null}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : null}
|
||||
>
|
||||
<View style={styles.inputContainer}>
|
||||
snippetsMutation.mutate({
|
||||
id: isNewSnippet ? null : snippetId,
|
||||
title,
|
||||
body,
|
||||
});
|
||||
|
||||
|
||||
<View style={{height:Math.max(35, titleHeight)}}>
|
||||
<TextInput
|
||||
autoFocus={true}
|
||||
innerRef={titleInputRef}
|
||||
style={styles.titleInput}
|
||||
height={Math.max(35, titleHeight)}
|
||||
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
|
||||
maxLength={250}
|
||||
placeholder={intl.formatMessage({id:'snippets.placeholder_title'})}
|
||||
multiline
|
||||
numberOfLines={2}
|
||||
onContentSizeChange={(event) => {
|
||||
setTitleHeight(event.nativeEvent.contentSize.height);
|
||||
}}
|
||||
onChangeText={setTitle}
|
||||
value={title}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<TextInput
|
||||
multiline
|
||||
autoCorrect={true}
|
||||
value={body}
|
||||
onChangeText={setBody}
|
||||
placeholder={intl.formatMessage({id:'snippets.placeholder_body'})}
|
||||
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
|
||||
selectionColor="#357ce6"
|
||||
style={styles.bodyWrapper}
|
||||
underlineColorAndroid="transparent"
|
||||
innerRef={bodyInputRef}
|
||||
autoGrow={false}
|
||||
scrollEnabled={false}
|
||||
height={100}
|
||||
/>
|
||||
</View>
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
|
||||
<View style={styles.actionPanel}>
|
||||
<TextButton
|
||||
text={intl.formatMessage({id:'snippets.btn_close'})}
|
||||
onPress={()=>setShowModal(false)}
|
||||
style={styles.closeButton}
|
||||
/>
|
||||
<TextButton
|
||||
text={intl.formatMessage({id:'snippets.btn_save'})}
|
||||
onPress={_saveSnippet}
|
||||
textStyle={styles.btnText}
|
||||
style={styles.saveButton}
|
||||
/>
|
||||
</View>
|
||||
|
||||
</KeyboardAvoidingView>
|
||||
)}
|
||||
</ThemeContainer>
|
||||
)
|
||||
const _renderContent = (
|
||||
<ThemeContainer>
|
||||
{({ isDarkTheme }) => (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
keyboardVerticalOffset={Platform.OS == 'ios' ? 64 : null}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : null}
|
||||
>
|
||||
<View style={styles.inputContainer}>
|
||||
<View style={{ height: Math.max(35, titleHeight) }}>
|
||||
<TextInput
|
||||
autoFocus={true}
|
||||
innerRef={titleInputRef}
|
||||
style={styles.titleInput}
|
||||
height={Math.max(35, titleHeight)}
|
||||
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
|
||||
maxLength={250}
|
||||
placeholder={intl.formatMessage({ id: 'snippets.placeholder_title' })}
|
||||
multiline
|
||||
numberOfLines={2}
|
||||
onContentSizeChange={(event) => {
|
||||
setTitleHeight(event.nativeEvent.contentSize.height);
|
||||
}}
|
||||
onChangeText={setTitle}
|
||||
value={title}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<TextInput
|
||||
multiline
|
||||
autoCorrect={true}
|
||||
value={body}
|
||||
onChangeText={setBody}
|
||||
placeholder={intl.formatMessage({ id: 'snippets.placeholder_body' })}
|
||||
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
|
||||
selectionColor="#357ce6"
|
||||
style={styles.bodyWrapper}
|
||||
underlineColorAndroid="transparent"
|
||||
innerRef={bodyInputRef}
|
||||
autoGrow={false}
|
||||
scrollEnabled={false}
|
||||
height={100}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.actionPanel}>
|
||||
<TextButton
|
||||
text={intl.formatMessage({ id: 'snippets.btn_close' })}
|
||||
onPress={() => setShowModal(false)}
|
||||
style={styles.closeButton}
|
||||
/>
|
||||
<TextButton
|
||||
text={intl.formatMessage({ id: 'snippets.btn_save' })}
|
||||
onPress={_saveSnippet}
|
||||
textStyle={styles.btnText}
|
||||
style={styles.saveButton}
|
||||
/>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
)}
|
||||
</ThemeContainer>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
handleOnModalClose={()=>{setShowModal(false)}}
|
||||
presentationStyle="formSheet"
|
||||
title={intl.formatMessage({
|
||||
id:isNewSnippet
|
||||
? 'snippets.title_add_snippet'
|
||||
: 'snippets.title_edit_snippet'
|
||||
})}
|
||||
animationType="slide"
|
||||
style={styles.modalStyle}
|
||||
>
|
||||
{_renderContent}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
handleOnModalClose={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
presentationStyle="formSheet"
|
||||
title={intl.formatMessage({
|
||||
id: isNewSnippet ? 'snippets.title_add_snippet' : 'snippets.title_edit_snippet',
|
||||
})}
|
||||
animationType="slide"
|
||||
style={styles.modalStyle}
|
||||
>
|
||||
{_renderContent}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(SnippetEditorModal);
|
||||
|
||||
|
||||
|
@ -1,43 +1,72 @@
|
||||
import * as React from 'react';
|
||||
import { Text, View, Button } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Alert, Text, View } from 'react-native';
|
||||
import { useSnippetDeleteMutation } from '../../providers/queries';
|
||||
import IconButton from '../iconButton';
|
||||
import styles from './snippetsModalStyles';
|
||||
|
||||
interface SnippetItemProps {
|
||||
title:string;
|
||||
body:string;
|
||||
index:number;
|
||||
onEditPress:()=>void;
|
||||
onRemovePress:()=>void;
|
||||
id: string | null;
|
||||
title: string;
|
||||
body: string;
|
||||
index: number;
|
||||
onEditPress: () => void;
|
||||
}
|
||||
|
||||
const SnippetItem = ({title, body, index, onEditPress, onRemovePress}: SnippetItemProps) => {
|
||||
const SnippetItem = ({ id, title, body, index, onEditPress }: SnippetItemProps) => {
|
||||
const intl = useIntl();
|
||||
const snippetsDeleteMutation = useSnippetDeleteMutation();
|
||||
|
||||
const _onRemovePress = () => {
|
||||
//asks for remvoe confirmation and run remove routing upon confirming
|
||||
if (id) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({ id: 'snippets.title_remove_confirmation' }),
|
||||
intl.formatMessage({ id: 'snippets.message_remove_confirmation' }),
|
||||
[
|
||||
{
|
||||
text: intl.formatMessage({ id: 'snippets.btn_cancel' }),
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: 'snippets.btn_confirm' }),
|
||||
onPress: () => snippetsDeleteMutation.mutate(id),
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.itemWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
|
||||
<View style={styles.itemHeader}>
|
||||
<Text style={styles.title} numberOfLines={1} >{`${title}`}</Text>
|
||||
<IconButton
|
||||
iconStyle={styles.itemIcon}
|
||||
style={styles.itemIconWrapper}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="pencil"
|
||||
onPress={onEditPress}
|
||||
size={20}
|
||||
/>
|
||||
<IconButton
|
||||
iconStyle={styles.itemIcon}
|
||||
style={styles.itemIconWrapper}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="delete"
|
||||
onPress={onRemovePress}
|
||||
size={20}
|
||||
/>
|
||||
<Text style={styles.title} numberOfLines={1}>{`${title}`}</Text>
|
||||
{id && (
|
||||
<>
|
||||
<IconButton
|
||||
iconStyle={styles.itemIcon}
|
||||
style={styles.itemIconWrapper}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="pencil"
|
||||
onPress={onEditPress}
|
||||
size={20}
|
||||
/>
|
||||
<IconButton
|
||||
iconStyle={styles.itemIcon}
|
||||
style={styles.itemIconWrapper}
|
||||
isLoading={snippetsDeleteMutation.isLoading}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="delete"
|
||||
onPress={_onRemovePress}
|
||||
size={20}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
|
||||
<Text style={styles.body} numberOfLines={2} ellipsizeMode="tail">{`${body}`}</Text>
|
||||
|
||||
</View>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default SnippetItem;
|
||||
export default SnippetItem;
|
||||
|
@ -1,172 +1,106 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { View, FlatList, Text, TouchableOpacity, Alert } from 'react-native';
|
||||
import React, { useRef } from 'react';
|
||||
import { View, FlatList, Text, TouchableOpacity, Alert, RefreshControl } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { getFragments, deleteFragment } from '../../providers/ecency/ecency';
|
||||
import { deleteFragment } from '../../providers/ecency/ecency';
|
||||
import { MainButton } from '..';
|
||||
import styles from './snippetsModalStyles';
|
||||
import { RefreshControl } from 'react-native';
|
||||
import SnippetEditorModal, { SnippetEditorModalRef } from '../snippetEditorModal/snippetEditorModal';
|
||||
|
||||
import SnippetEditorModal, {
|
||||
SnippetEditorModalRef,
|
||||
} from '../snippetEditorModal/snippetEditorModal';
|
||||
import SnippetItem from './snippetItem';
|
||||
import { Snippet } from '../../models';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { useSnippetDeleteMutation, useSnippetsQuery } from '../../providers/queries';
|
||||
|
||||
interface SnippetsModalProps {
|
||||
handleOnSelect:(snippetText:string)=>void,
|
||||
handleOnSelect: (snippetText: string) => void;
|
||||
}
|
||||
|
||||
const SnippetsModal = ({ handleOnSelect }:SnippetsModalProps) => {
|
||||
const SnippetsModal = ({ handleOnSelect }: SnippetsModalProps) => {
|
||||
const editorRef = useRef<SnippetEditorModalRef>(null);
|
||||
const intl = useIntl();
|
||||
|
||||
const isLoggedIn = useAppSelector(state => state.application.isLoggedIn)
|
||||
|
||||
const [snippets, setSnippets] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
_getSnippets();
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
//fetch snippets from server
|
||||
const _getSnippets = async () => {
|
||||
try{
|
||||
|
||||
setIsLoading(true);
|
||||
const snips = await getFragments()
|
||||
console.log("snips received", snips)
|
||||
setSnippets(snips);
|
||||
setIsLoading(false);
|
||||
|
||||
}catch(err){
|
||||
console.warn("Failed to get snippets")
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
//removes snippet from users snippet collection on user confirmation
|
||||
const _removeSnippet = async (id:string) => {
|
||||
try{
|
||||
|
||||
setIsLoading(true);
|
||||
const snips = await deleteFragment(id)
|
||||
setSnippets(snips);
|
||||
setIsLoading(false);
|
||||
|
||||
}catch(err){
|
||||
console.warn("Failed to get snippets")
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
||||
|
||||
const snippetsQuery = useSnippetsQuery();
|
||||
|
||||
//render list item for snippet and handle actions;
|
||||
const _renderItem = ({ item, index }:{item:Snippet, index:number}) => {
|
||||
|
||||
const _onPress = () => handleOnSelect(item.body)
|
||||
|
||||
//asks for remvoe confirmation and run remove routing upon confirming
|
||||
const _onRemovePress = () => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({id:'snippets.title_remove_confirmation'}),
|
||||
intl.formatMessage({id:'snippets.message_remove_confirmation'}),
|
||||
[
|
||||
{
|
||||
text:intl.formatMessage({id:'snippets.btn_cancel'}),
|
||||
style:'cancel'
|
||||
},
|
||||
{
|
||||
text:intl.formatMessage({id:'snippets.btn_confirm'}),
|
||||
onPress:()=>_removeSnippet(item.id)
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
const _renderItem = ({ item, index }: { item: Snippet; index: number }) => {
|
||||
const _onPress = () => handleOnSelect(item.body);
|
||||
|
||||
const _onEditPress = () => {
|
||||
if(editorRef.current){
|
||||
if (editorRef.current) {
|
||||
editorRef.current.showEditModal(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={_onPress}>
|
||||
<SnippetItem
|
||||
title={item.title}
|
||||
body={item.body}
|
||||
index={index}
|
||||
onEditPress={_onEditPress}
|
||||
onRemovePress={_onRemovePress}
|
||||
/>
|
||||
<SnippetItem
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
body={item.body}
|
||||
index={index}
|
||||
onEditPress={_onEditPress}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//render empty list placeholder
|
||||
const _renderEmptyContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Text style={styles.title}>{intl.formatMessage({id:'snippets.label_no_snippets'})}</Text>
|
||||
<Text style={styles.title}>{intl.formatMessage({ id: 'snippets.label_no_snippets' })}</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//renders footer with add snipept button and shows new snippet modal
|
||||
const _renderFloatingButton = () => {
|
||||
if(!isLoggedIn){
|
||||
if (!isLoggedIn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const _onPress = () => {
|
||||
if(editorRef.current){
|
||||
if (editorRef.current) {
|
||||
editorRef.current.showNewModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<View style={styles.floatingContainer}>
|
||||
<MainButton
|
||||
style={{ width: 150}}
|
||||
style={{ width: 150 }}
|
||||
onPress={_onPress}
|
||||
iconName="plus"
|
||||
iconType="MaterialCommunityIcons"
|
||||
iconColor="white"
|
||||
text={intl.formatMessage({id:'snippets.btn_add'})}
|
||||
text={intl.formatMessage({ id: 'snippets.btn_add' })}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.bodyWrapper}>
|
||||
<FlatList
|
||||
data={snippets}
|
||||
data={snippetsQuery.data}
|
||||
keyExtractor={(item, index) => index.toString()}
|
||||
renderItem={_renderItem}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isLoading}
|
||||
onRefresh={_getSnippets}
|
||||
<RefreshControl
|
||||
refreshing={snippetsQuery.isFetching}
|
||||
onRefresh={snippetsQuery.refetch}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{_renderFloatingButton()}
|
||||
|
||||
</View>
|
||||
|
||||
<SnippetEditorModal
|
||||
ref={editorRef}
|
||||
onSnippetsUpdated={setSnippets}
|
||||
/>
|
||||
<SnippetEditorModal ref={editorRef} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Animated, TouchableOpacity, Text } from 'react-native';
|
||||
import { TouchableOpacity, Text } from 'react-native';
|
||||
import { View as AnimatedView } from 'react-native-animatable';
|
||||
|
||||
// Styles
|
||||
import styles from './toastNotificationStyles';
|
||||
@ -12,70 +13,60 @@ class ToastNotification extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
animatedValue: new Animated.Value(0),
|
||||
};
|
||||
}
|
||||
|
||||
// Component Functions
|
||||
_showToast() {
|
||||
const { duration } = this.props;
|
||||
const animatedValue = new Animated.Value(0);
|
||||
|
||||
this.setState({ animatedValue });
|
||||
|
||||
Animated.timing(animatedValue, { toValue: 1, duration: 350 }).start();
|
||||
|
||||
if (duration) {
|
||||
this.closeTimer = setTimeout(() => {
|
||||
this._hideToast();
|
||||
}, duration);
|
||||
}
|
||||
}
|
||||
|
||||
_hideToast() {
|
||||
const { animatedValue } = this.state;
|
||||
const { onHide } = this.props;
|
||||
|
||||
Animated.timing(animatedValue, { toValue: 0.0, duration: 350 }).start(() => {
|
||||
if (onHide) {
|
||||
onHide();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.closeTimer) {
|
||||
clearTimeout(this.closeTimer);
|
||||
}
|
||||
}
|
||||
|
||||
// Component Life Cycles
|
||||
UNSAFE_componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._showToast();
|
||||
}
|
||||
|
||||
handleViewRef = (ref) => (this.view = ref);
|
||||
|
||||
// Component Functions
|
||||
_showToast = () => {
|
||||
const { duration, isTop } = this.props;
|
||||
const initialPosition = isTop ? { top: 0 } : { bottom: 0 };
|
||||
const finalPosition = isTop ? { top: 100 } : { bottom: 100 };
|
||||
this.view
|
||||
.animate({ 0: { opacity: 0, ...initialPosition }, 1: { opacity: 1, ...finalPosition } })
|
||||
.then((endState) => {
|
||||
if (duration) {
|
||||
this.closeTimer = setTimeout(() => {
|
||||
this._hideToast();
|
||||
}, duration);
|
||||
}
|
||||
});
|
||||
};
|
||||
_hideToast = () => {
|
||||
const { isTop } = this.props;
|
||||
const finalPosition = isTop ? { top: 0 } : { bottom: 0 };
|
||||
const initialPosition = isTop ? { top: 100 } : { bottom: 100 };
|
||||
this.view
|
||||
.animate({ 0: { opacity: 1, ...initialPosition }, 1: { opacity: 0, ...finalPosition } })
|
||||
.then((endState) => {
|
||||
const { onHide } = this.props;
|
||||
if (onHide) {
|
||||
onHide();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { text, textStyle, style, onPress, isTop } = this.props;
|
||||
const { animatedValue } = this.state;
|
||||
const outputRange = isTop ? [-50, 0] : [50, 0];
|
||||
const y = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange,
|
||||
});
|
||||
const position = isTop ? { top: 100 } : { bottom: 100 };
|
||||
const { text, textStyle, style, onPress } = this.props;
|
||||
|
||||
return (
|
||||
<TouchableOpacity disabled={!onPress} onPress={() => onPress && onPress()}>
|
||||
<Animated.View
|
||||
<AnimatedView
|
||||
style={{
|
||||
...styles.container,
|
||||
...style,
|
||||
...position,
|
||||
opacity: animatedValue,
|
||||
transform: [{ translateY: y }],
|
||||
}}
|
||||
easing="ease-in-out"
|
||||
duration={500}
|
||||
ref={this.handleViewRef}
|
||||
>
|
||||
<Text style={[styles.text, textStyle]}>{text}</Text>
|
||||
</Animated.View>
|
||||
</AnimatedView>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View, TouchableOpacity, Animated, NativeModules } from 'react-native';
|
||||
import { View, TouchableOpacity, NativeModules } from 'react-native';
|
||||
import Animated, { Easing } from 'react-native-reanimated';
|
||||
|
||||
// Constants
|
||||
|
||||
@ -97,6 +98,7 @@ class ToggleSwitchView extends PureComponent {
|
||||
Animated.timing(this.offsetX, {
|
||||
toValue,
|
||||
duration,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
}).start();
|
||||
};
|
||||
|
||||
|
@ -15,9 +15,11 @@ import {
|
||||
import {
|
||||
CommentHistoryItem,
|
||||
LatestMarketPrices,
|
||||
NotificationFilters,
|
||||
ReceivedVestingShare,
|
||||
Referral,
|
||||
ReferralStat,
|
||||
Snippet,
|
||||
} from './ecency.types';
|
||||
|
||||
/**
|
||||
@ -326,7 +328,7 @@ export const deleteFavorite = async (targetUsername: string) => {
|
||||
export const getFragments = async () => {
|
||||
try {
|
||||
const response = await ecencyApi.post('/private-api/fragments');
|
||||
return response.data;
|
||||
return response.data as Snippet[];
|
||||
} catch (error) {
|
||||
console.warn('Failed to get fragments', error);
|
||||
bugsnagInstance.notify(error);
|
||||
@ -416,16 +418,9 @@ export const getLeaderboard = async (duration: 'day' | 'week' | 'month') => {
|
||||
* @returns array of notifications
|
||||
*/
|
||||
export const getNotifications = async (data: {
|
||||
filter?:
|
||||
| 'rvotes'
|
||||
| 'mentions'
|
||||
| 'follows'
|
||||
| 'replies'
|
||||
| 'reblogs'
|
||||
| 'transfers'
|
||||
| 'delegations'
|
||||
| 'nfavorites';
|
||||
filter?: NotificationFilters;
|
||||
since?: string;
|
||||
limit?: number;
|
||||
}) => {
|
||||
try {
|
||||
const response = await ecencyApi.post('/private-api/notifications', data);
|
||||
|
@ -1,54 +1,61 @@
|
||||
import { QuoteItem } from "../../redux/reducers/walletReducer";
|
||||
import { QuoteItem } from '../../redux/reducers/walletReducer';
|
||||
|
||||
export interface ReceivedVestingShare {
|
||||
delegator:string;
|
||||
delegatee:string;
|
||||
vesting_shares:string;
|
||||
timestamp:string;
|
||||
delegator: string;
|
||||
delegatee: string;
|
||||
vesting_shares: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface Snippet {
|
||||
id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
created: string;
|
||||
modified: string;
|
||||
}
|
||||
|
||||
export interface EcencyUser {
|
||||
username:string;
|
||||
points:string;
|
||||
unclaimed_points:string;
|
||||
points_by_type:{[key:string]:string};
|
||||
unclaimed_points_by_type:{[key:string]:string};
|
||||
username: string;
|
||||
points: string;
|
||||
unclaimed_points: string;
|
||||
points_by_type: { [key: string]: string };
|
||||
unclaimed_points_by_type: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface Referral {
|
||||
id:number;
|
||||
referral:string;
|
||||
rewarded:boolean;
|
||||
username:string;
|
||||
created:string
|
||||
id: number;
|
||||
referral: string;
|
||||
rewarded: boolean;
|
||||
username: string;
|
||||
created: string;
|
||||
}
|
||||
|
||||
export interface ReferralStat {
|
||||
total: number;
|
||||
rewarded: number;
|
||||
total: number;
|
||||
rewarded: number;
|
||||
}
|
||||
|
||||
export interface UserPoint {
|
||||
id: number;
|
||||
type: number;
|
||||
amount: string;
|
||||
created:string;
|
||||
memo?: string;
|
||||
receiver?: string;
|
||||
sender?: string;
|
||||
|
||||
id: number;
|
||||
type: number;
|
||||
amount: string;
|
||||
created: string;
|
||||
memo?: string;
|
||||
receiver?: string;
|
||||
sender?: string;
|
||||
}
|
||||
|
||||
export interface LatestQuotes {
|
||||
[key:string]:QuoteItem
|
||||
[key: string]: QuoteItem;
|
||||
}
|
||||
|
||||
export interface CommentHistoryItem {
|
||||
body: string;
|
||||
tags: [string];
|
||||
title: string;
|
||||
timestamp:string;
|
||||
v: number;
|
||||
body: string;
|
||||
tags: [string];
|
||||
title: string;
|
||||
timestamp: string;
|
||||
v: number;
|
||||
}
|
||||
|
||||
export enum ScheduledPostStatus {
|
||||
@ -56,4 +63,16 @@ export enum ScheduledPostStatus {
|
||||
POSTPONED = 2,
|
||||
PUBLISHED = 3,
|
||||
ERROR = 4,
|
||||
}
|
||||
}
|
||||
|
||||
export enum NotificationFilters {
|
||||
ACTIVITIES = "activities",
|
||||
RVOTES = "rvotes",
|
||||
MENTIONS = "mentions",
|
||||
FOLLOWS = "follows",
|
||||
REPLIES = "replies",
|
||||
REBLOGS = "reblogs",
|
||||
TRANFERS = "transfers",
|
||||
DELEGATIONS = "delegations",
|
||||
FAVOURITES = "nfavorites"
|
||||
}
|
||||
|
90
src/providers/queries/editorQueries.ts
Normal file
90
src/providers/queries/editorQueries.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useAppDispatch } from '../../hooks';
|
||||
import { toastNotification } from '../../redux/actions/uiAction';
|
||||
import { addFragment, deleteFragment, getFragments, updateFragment } from '../ecency/ecency';
|
||||
import { Snippet } from '../ecency/ecency.types';
|
||||
import QUERIES from './queryKeys';
|
||||
|
||||
interface SnippetMutationVars {
|
||||
id: string | null;
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export const useSnippetsQuery = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
return useQuery<Snippet[]>([QUERIES.SNIPPETS.GET], getFragments, {
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSnippetsMutation = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Snippet[], undefined, SnippetMutationVars>(
|
||||
async (vars) => {
|
||||
console.log('going to add/update snippet', vars);
|
||||
if (vars.id) {
|
||||
const response = await updateFragment(vars.id, vars.title, vars.body);
|
||||
return response;
|
||||
} else {
|
||||
const response = await addFragment(vars.title, vars.body);
|
||||
return response;
|
||||
}
|
||||
},
|
||||
{
|
||||
onMutate: (vars) => {
|
||||
console.log('mutate snippets for add/update', vars);
|
||||
|
||||
const _newItem = {
|
||||
id: vars.id,
|
||||
title: vars.title,
|
||||
body: vars.body,
|
||||
created: new Date().toDateString(),
|
||||
modified: new Date().toDateString(),
|
||||
} as Snippet;
|
||||
|
||||
const data = queryClient.getQueryData<Snippet[]>([QUERIES.SNIPPETS.GET]);
|
||||
|
||||
let _newData: Snippet[] = data ? [...data] : [];
|
||||
if (vars.id) {
|
||||
const snipIndex = _newData.findIndex((item) => vars.id === item.id);
|
||||
_newData[snipIndex] = _newItem;
|
||||
} else {
|
||||
_newData = [_newItem, ..._newData];
|
||||
}
|
||||
|
||||
queryClient.setQueryData([QUERIES.SNIPPETS.GET], _newData);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
console.log('added/updated snippet', data);
|
||||
queryClient.invalidateQueries([QUERIES.SNIPPETS.GET]);
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'snippets.message_failed' })));
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useSnippetDeleteMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
return useMutation<Snippet[], undefined, string>(deleteFragment, {
|
||||
retry: 3,
|
||||
onSuccess: (data) => {
|
||||
console.log('Success scheduled post delete', data);
|
||||
queryClient.setQueryData([QUERIES.SNIPPETS.GET], data);
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
};
|
@ -22,3 +22,7 @@ export const initQueryClient = () => {
|
||||
persistOptions: { persister: asyncStoragePersister },
|
||||
} as PersistQueryClientProviderProps;
|
||||
};
|
||||
|
||||
export * from './notificationQueries';
|
||||
export * from './draftQueries';
|
||||
export * from './editorQueries';
|
||||
|
132
src/providers/queries/notificationQueries.ts
Normal file
132
src/providers/queries/notificationQueries.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import {
|
||||
QueryKey,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQueries,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import bugsnapInstance from '../../config/bugsnag';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { updateUnreadActivityCount } from '../../redux/actions/accountAction';
|
||||
import { toastNotification } from '../../redux/actions/uiAction';
|
||||
import { getNotifications, markNotifications } from '../ecency/ecency';
|
||||
import { NotificationFilters } from '../ecency/ecency.types';
|
||||
import { markHiveNotifications } from '../hive/dhive';
|
||||
import QUERIES from './queryKeys';
|
||||
|
||||
const FETCH_LIMIT = 20;
|
||||
|
||||
export const useNotificationsQuery = (filter: NotificationFilters) => {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [pageParams, setPageParams] = useState(['']);
|
||||
|
||||
const _fetchNotifications = async (pageParam: string) => {
|
||||
console.log('fetching page since:', pageParam);
|
||||
const response = await getNotifications({ filter, since: pageParam, limit: FETCH_LIMIT });
|
||||
console.log('new page fetched', response);
|
||||
return response || [];
|
||||
};
|
||||
|
||||
const _getNextPageParam = (lastPage: any[]) => {
|
||||
const lastId = lastPage && lastPage.length ? lastPage.lastItem.id : undefined;
|
||||
console.log('extracting next page parameter', lastId);
|
||||
return lastId;
|
||||
};
|
||||
|
||||
//query initialization
|
||||
const notificationQueries = useQueries({
|
||||
queries: pageParams.map((pageParam) => ({
|
||||
queryKey: [QUERIES.NOTIFICATIONS.GET, filter, pageParam],
|
||||
queryFn: () => _fetchNotifications(pageParam),
|
||||
initialData: [],
|
||||
})),
|
||||
});
|
||||
|
||||
const _refresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
setPageParams(['']);
|
||||
await notificationQueries[0].refetch();
|
||||
setIsRefreshing(false);
|
||||
};
|
||||
|
||||
const _fetchNextPage = () => {
|
||||
const lastId = _getNextPageParam(notificationQueries.lastItem.data);
|
||||
if (!pageParams.includes(lastId)) {
|
||||
pageParams.push(lastId);
|
||||
setPageParams([...pageParams]);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
data: notificationQueries.flatMap((query) => query.data),
|
||||
isRefreshing,
|
||||
isLoading: notificationQueries.lastItem.isLoading || notificationQueries.lastItem.isFetching,
|
||||
fetchNextPage: _fetchNextPage,
|
||||
refresh: _refresh,
|
||||
};
|
||||
};
|
||||
|
||||
export const useNotificationReadMutation = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||
const pinCode = useAppSelector((state) => state.application.pin);
|
||||
|
||||
//id is options, if no id is provided program marks all notifications as read;
|
||||
const _mutationFn = async (id?: string) => {
|
||||
try {
|
||||
const response = await markNotifications(id);
|
||||
console.log('Ecency notifications marked as Read', response);
|
||||
if (!id) {
|
||||
await markHiveNotifications(currentAccount, pinCode);
|
||||
console.log('Hive notifications marked as Read');
|
||||
}
|
||||
|
||||
return response.unread || 0;
|
||||
} catch (err) {
|
||||
bugsnapInstance.notify(err);
|
||||
}
|
||||
};
|
||||
|
||||
const _options: UseMutationOptions<number, unknown, string | undefined, void> = {
|
||||
onMutate: async (notificationId) => {
|
||||
//TODO: find a way to optimise mutations by avoiding too many loops
|
||||
console.log('on mutate data', notificationId);
|
||||
|
||||
//update query data
|
||||
const queriesData: [QueryKey, any[] | undefined][] = queryClient.getQueriesData([
|
||||
QUERIES.NOTIFICATIONS.GET,
|
||||
]);
|
||||
console.log('query data', queriesData);
|
||||
|
||||
queriesData.forEach(([queryKey, data]) => {
|
||||
if (data) {
|
||||
console.log('mutating data', queryKey);
|
||||
const _mutatedData = data.map((item) => ({
|
||||
...item,
|
||||
read: !notificationId || notificationId === item.id ? 1 : item.read,
|
||||
}));
|
||||
queryClient.setQueryData(queryKey, _mutatedData);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onSuccess: async (unreadCount, notificationId) => {
|
||||
console.log('on success data', unreadCount);
|
||||
|
||||
dispatch(updateUnreadActivityCount(unreadCount));
|
||||
if (!notificationId) {
|
||||
queryClient.invalidateQueries([QUERIES.NOTIFICATIONS.GET]);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
};
|
||||
|
||||
return useMutation(_mutationFn, _options);
|
||||
};
|
@ -5,6 +5,12 @@ const QUERIES = {
|
||||
SCHEDULES: {
|
||||
GET: 'QUERY_GET_SCHEDULES',
|
||||
},
|
||||
NOTIFICATIONS:{
|
||||
GET: 'QERUY_GET_NOTIFICATIONS'
|
||||
},
|
||||
SNIPPETS: {
|
||||
GET: 'QUERY_GET_SNIPPETS',
|
||||
}
|
||||
};
|
||||
|
||||
export default QUERIES;
|
||||
|
@ -2,38 +2,39 @@ import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { persistStore, persistReducer, createTransform } from 'redux-persist';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import createMigrate from 'redux-persist/es/createMigrate';
|
||||
import Reactotron from '../../../reactotron-config';
|
||||
|
||||
import reducer from '../reducers';
|
||||
import createMigrate from 'redux-persist/es/createMigrate';
|
||||
import MigrationHelpers from '../../utils/migrationHelpers';
|
||||
|
||||
const transformCacheVoteMap = createTransform(
|
||||
(inboundState:any) => ({
|
||||
...inboundState,
|
||||
votes : Array.from(inboundState.votes),
|
||||
comments : Array.from(inboundState.comments),
|
||||
drafts : Array.from(inboundState.drafts),
|
||||
subscribedCommunities: Array.from(inboundState.subscribedCommunities)
|
||||
(inboundState: any) => ({
|
||||
...inboundState,
|
||||
votes: Array.from(inboundState.votes),
|
||||
comments: Array.from(inboundState.comments),
|
||||
drafts: Array.from(inboundState.drafts),
|
||||
subscribedCommunities: Array.from(inboundState.subscribedCommunities),
|
||||
}),
|
||||
(outboundState) => ({
|
||||
...outboundState,
|
||||
votes:new Map(outboundState.votes),
|
||||
comments:new Map(outboundState.comments),
|
||||
(outboundState) => ({
|
||||
...outboundState,
|
||||
votes: new Map(outboundState.votes),
|
||||
comments: new Map(outboundState.comments),
|
||||
drafts: new Map(outboundState.drafts),
|
||||
subscribedCommunities: new Map(outboundState.subscribedCommunities)
|
||||
subscribedCommunities: new Map(outboundState.subscribedCommunities),
|
||||
}),
|
||||
{whitelist:['cache']}
|
||||
{ whitelist: ['cache'] },
|
||||
);
|
||||
|
||||
const transformWalkthroughMap = createTransform(
|
||||
(inboundState:any) => ({ ...inboundState, walkthroughMap : Array.from(inboundState.walkthroughMap)}),
|
||||
(outboundState) => ({ ...outboundState, walkthroughMap:new Map(outboundState.walkthroughMap)}),
|
||||
{whitelist:['walkthrough']}
|
||||
(inboundState: any) => ({
|
||||
...inboundState,
|
||||
walkthroughMap: Array.from(inboundState.walkthroughMap),
|
||||
}),
|
||||
(outboundState) => ({ ...outboundState, walkthroughMap: new Map(outboundState.walkthroughMap) }),
|
||||
{ whitelist: ['walkthrough'] },
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Middleware: Redux Persist Config
|
||||
const persistConfig = {
|
||||
// Root
|
||||
@ -44,11 +45,8 @@ const persistConfig = {
|
||||
// Blacklist (Don't Save Specific Reducers)
|
||||
blacklist: ['communities', 'user', 'ui'],
|
||||
timeout: 0,
|
||||
transforms:[
|
||||
transformCacheVoteMap,
|
||||
transformWalkthroughMap
|
||||
],
|
||||
migrate: createMigrate(MigrationHelpers.reduxMigrations, {debug:false})
|
||||
transforms: [transformCacheVoteMap, transformWalkthroughMap],
|
||||
migrate: createMigrate(MigrationHelpers.reduxMigrations, { debug: false }),
|
||||
};
|
||||
|
||||
// Middleware: Redux Persist Persisted Reducer
|
||||
@ -66,8 +64,7 @@ const persistor = persistStore(store);
|
||||
|
||||
export { store, persistor };
|
||||
|
||||
|
||||
// Infer the `RootState` and `AppDispatch` types from the store itself
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
|
@ -100,7 +100,7 @@ class ApplicationContainer extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
isRenderRequire: true,
|
||||
isIos: Platform.OS !== 'android',
|
||||
// isIos: Platform.OS !== 'android',
|
||||
appState: AppState.currentState,
|
||||
foregroundNotificationData: null,
|
||||
};
|
||||
@ -145,7 +145,7 @@ class ApplicationContainer extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isGlobalRenderRequired, dispatch } = this.props;
|
||||
|
||||
if (isGlobalRenderRequired !== prevProps.isGlobalRenderRequired && isGlobalRenderRequired) {
|
||||
@ -218,7 +218,7 @@ class ApplicationContainer extends Component {
|
||||
};
|
||||
|
||||
_handleDeepLink = async (url = '') => {
|
||||
const { currentAccount, intl } = this.props;
|
||||
const { currentAccount } = this.props;
|
||||
|
||||
if (!url) {
|
||||
return;
|
||||
@ -308,6 +308,7 @@ class ApplicationContainer extends Component {
|
||||
|
||||
if (appState.match(/inactive|background/) && nextAppState === 'active') {
|
||||
this._refreshGlobalProps();
|
||||
this._refreshUnreadActivityCount();
|
||||
if (_isPinCodeOpen && this._pinCodeTimer) {
|
||||
clearTimeout(this._pinCodeTimer);
|
||||
}
|
||||
@ -356,7 +357,7 @@ class ApplicationContainer extends Component {
|
||||
const type = get(push, 'type', '');
|
||||
const fullPermlink =
|
||||
get(push, 'permlink1', '') + get(push, 'permlink2', '') + get(push, 'permlink3', '');
|
||||
const username = get(push, 'target', '');
|
||||
// const username = get(push, 'target', '');
|
||||
const activity_id = get(push, 'id', '');
|
||||
|
||||
switch (type) {
|
||||
@ -487,8 +488,14 @@ class ApplicationContainer extends Component {
|
||||
actions.fetchCoinQuotes();
|
||||
};
|
||||
|
||||
_refreshUnreadActivityCount = async () => {
|
||||
const { dispatch } = this.props as any;
|
||||
const unreadActivityCount = await getUnreadNotificationCount();
|
||||
dispatch(updateUnreadActivityCount(unreadActivityCount));
|
||||
};
|
||||
|
||||
_getUserDataFromRealm = async () => {
|
||||
const { dispatch, pinCode, isPinCodeOpen: _isPinCodeOpen, isConnected } = this.props;
|
||||
const { dispatch, isPinCodeOpen: _isPinCodeOpen, isConnected } = this.props;
|
||||
let realmData = [];
|
||||
|
||||
const res = await getAuthStatus();
|
||||
@ -643,8 +650,12 @@ 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;
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
useGetSchedulesQuery,
|
||||
useMoveScheduleToDraftsMutation,
|
||||
useScheduleDeleteMutation,
|
||||
} from '../../../providers/queries/draftQueries';
|
||||
} from '../../../providers/queries';
|
||||
|
||||
// Middleware
|
||||
|
||||
@ -52,14 +52,11 @@ const DraftsContainer = ({ currentAccount, navigation, route }) => {
|
||||
};
|
||||
|
||||
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,
|
||||
draftId: id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -8,8 +8,7 @@ import { isArray } from 'lodash';
|
||||
|
||||
// Services and Actions
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryClient, useQueryClient } from '@tanstack/react-query';
|
||||
import { addDraft, updateDraft, getDrafts, addSchedule } from '../../../providers/ecency/ecency';
|
||||
import { toastNotification, setRcOffer } from '../../../redux/actions/uiAction';
|
||||
import {
|
||||
@ -22,6 +21,7 @@ import {
|
||||
|
||||
// Constants
|
||||
import { default as ROUTES } from '../../../constants/routeNames';
|
||||
|
||||
// Utilities
|
||||
import {
|
||||
generatePermlink,
|
||||
@ -34,6 +34,7 @@ import {
|
||||
extractImageUrls,
|
||||
} from '../../../utils/editor';
|
||||
// import { generateSignature } from '../../../utils/image';
|
||||
|
||||
// Component
|
||||
import EditorScreen from '../screen/editorScreen';
|
||||
import { removeBeneficiaries, setBeneficiaries } from '../../../redux/actions/editorActions';
|
||||
@ -85,7 +86,7 @@ class EditorContainer extends Component<any, any> {
|
||||
// Component Life Cycle Functions
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
const { currentAccount, route } = this.props;
|
||||
const { currentAccount, route, queryClient } = this.props;
|
||||
const username = currentAccount && currentAccount.name ? currentAccount.name : '';
|
||||
let isReply;
|
||||
let draftId;
|
||||
@ -98,16 +99,22 @@ class EditorContainer extends Component<any, any> {
|
||||
const navigationParams = route.params;
|
||||
hasSharedIntent = navigationParams.hasSharedIntent;
|
||||
|
||||
if (navigationParams.draft) {
|
||||
_draft = navigationParams.draft;
|
||||
if (navigationParams.draftId) {
|
||||
draftId = navigationParams.draftId;
|
||||
const cachedDrafts: any = queryClient.getQueryData([QUERIES.DRAFTS.GET]);
|
||||
|
||||
// this._loadMeta(_draft);
|
||||
if (cachedDrafts && cachedDrafts.length) {
|
||||
//get draft from query cache
|
||||
const _draft = cachedDrafts.find((draft) => draft._id === draftId);
|
||||
|
||||
this.setState({
|
||||
draftId: _draft._id,
|
||||
});
|
||||
this._getStorageDraft(username, isReply, _draft);
|
||||
this.setState({
|
||||
draftId,
|
||||
});
|
||||
|
||||
this._getStorageDraft(username, isReply, _draft);
|
||||
}
|
||||
}
|
||||
|
||||
if (navigationParams.community) {
|
||||
this.setState({
|
||||
community: navigationParams.community,
|
||||
@ -240,9 +247,9 @@ class EditorContainer extends Component<any, any> {
|
||||
: paramDraft.tags.split(',');
|
||||
this.setState({
|
||||
draftPost: {
|
||||
title: paramDraft.title,
|
||||
body: paramDraft.body,
|
||||
tags: _tags,
|
||||
title: paramDraft.title || '',
|
||||
body: paramDraft.body || '',
|
||||
tags: _tags || [],
|
||||
meta: paramDraft.meta ? paramDraft.meta : null,
|
||||
},
|
||||
draftId: paramDraft._id,
|
||||
|
@ -1,239 +0,0 @@
|
||||
/* eslint-disable react/no-unused-state */
|
||||
import React, { Component } from 'react';
|
||||
import { Alert } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import get from 'lodash/get';
|
||||
import { injectIntl } from 'react-intl';
|
||||
|
||||
// Actions and Services
|
||||
import { unionBy } from 'lodash';
|
||||
import { getNotifications, markNotifications } from '../../../providers/ecency/ecency';
|
||||
import { updateUnreadActivityCount } from '../../../redux/actions/accountAction';
|
||||
|
||||
// Constants
|
||||
import ROUTES from '../../../constants/routeNames';
|
||||
|
||||
// Components
|
||||
import NotificationScreen from '../screen/notificationScreen';
|
||||
import { showProfileModal } from '../../../redux/actions/uiAction';
|
||||
import { markHiveNotifications } from '../../../providers/hive/dhive';
|
||||
import bugsnapInstance from '../../../config/bugsnag';
|
||||
|
||||
class NotificationContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
notificationsMap: new Map(),
|
||||
lastNotificationId: null,
|
||||
isRefreshing: true,
|
||||
isLoading: false,
|
||||
selectedFilter: 'activities',
|
||||
endOfNotification: false,
|
||||
selectedIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { isConnected } = this.props;
|
||||
if (isConnected) {
|
||||
this._getActivities();
|
||||
}
|
||||
}
|
||||
|
||||
_getActivities = (type = 'activities', loadMore = false, loadUnread = false) => {
|
||||
const { lastNotificationId, endOfNotification, isLoading, notificationsMap } = this.state;
|
||||
const since = loadMore ? lastNotificationId : null;
|
||||
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!endOfNotification || !loadMore || loadUnread) {
|
||||
this.setState({
|
||||
isRefreshing: !loadMore,
|
||||
isLoading: true,
|
||||
});
|
||||
getNotifications({ filter: type, since: since, limit: 20 })
|
||||
.then((res) => {
|
||||
const lastId = res.length > 0 ? [...res].pop().id : null;
|
||||
|
||||
if (loadMore && (lastId === lastNotificationId || res.length === 0)) {
|
||||
this.setState({
|
||||
endOfNotification: true,
|
||||
isRefreshing: false,
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
console.log('');
|
||||
const stateNotifications = notificationsMap.get(type) || [];
|
||||
const _notifications = loadMore
|
||||
? unionBy(stateNotifications, res, 'id')
|
||||
: loadUnread
|
||||
? unionBy(res, stateNotifications, 'id')
|
||||
: res;
|
||||
notificationsMap.set(type, _notifications);
|
||||
this.setState({
|
||||
notificationsMap,
|
||||
lastNotificationId: lastId,
|
||||
isRefreshing: false,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => this.setState({ isRefreshing: false, isLoading: false }));
|
||||
}
|
||||
};
|
||||
|
||||
_navigateToNotificationRoute = (data) => {
|
||||
const { navigation, dispatch } = this.props;
|
||||
const type = get(data, 'type');
|
||||
const permlink = get(data, 'permlink');
|
||||
const author = get(data, 'author');
|
||||
let routeName;
|
||||
let params;
|
||||
let key;
|
||||
if (data && !data.read) {
|
||||
markNotifications(data.id).then((result) => {
|
||||
const { unread } = result;
|
||||
dispatch(updateUnreadActivityCount(unread));
|
||||
});
|
||||
}
|
||||
|
||||
if (permlink && author) {
|
||||
routeName = ROUTES.SCREENS.POST;
|
||||
key = permlink;
|
||||
params = {
|
||||
author,
|
||||
permlink,
|
||||
};
|
||||
} else if (type === 'follow') {
|
||||
routeName = ROUTES.SCREENS.PROFILE;
|
||||
key = get(data, 'follower');
|
||||
params = {
|
||||
username: get(data, 'follower'),
|
||||
};
|
||||
} else if (type === 'transfer') {
|
||||
routeName = ROUTES.TABBAR.WALLET;
|
||||
} else if (type === 'spin') {
|
||||
routeName = ROUTES.SCREENS.BOOST;
|
||||
} else if (type === 'inactive') {
|
||||
routeName = ROUTES.SCREENS.EDITOR;
|
||||
}
|
||||
|
||||
if (routeName) {
|
||||
navigation.navigate({
|
||||
name: routeName,
|
||||
params,
|
||||
key,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_handleOnUserPress = (username) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(showProfileModal(username));
|
||||
};
|
||||
|
||||
_readAllNotification = () => {
|
||||
const { dispatch, intl, isConnected, currentAccount, pinCode } = this.props;
|
||||
const { notificationsMap } = this.state;
|
||||
|
||||
if (!isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ isRefreshing: true });
|
||||
|
||||
markNotifications()
|
||||
.then(() => {
|
||||
notificationsMap.forEach((notifications, key) => {
|
||||
const updatedNotifications = notifications.map((item) => ({ ...item, read: 1 }));
|
||||
notificationsMap.set(key, updatedNotifications);
|
||||
});
|
||||
|
||||
dispatch(updateUnreadActivityCount(0));
|
||||
markHiveNotifications(currentAccount, pinCode)
|
||||
.then(() => {
|
||||
console.log('Hive notifications marked as Read');
|
||||
})
|
||||
.catch((err) => {
|
||||
bugsnapInstance.notify(err);
|
||||
});
|
||||
this.setState({ notificationsMap, isRefreshing: false });
|
||||
})
|
||||
.catch(() => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({ id: 'alert.error' }),
|
||||
intl.formatMessage({ d: 'alert.unknow_error' }),
|
||||
);
|
||||
this.setState({ isRefreshing: false });
|
||||
});
|
||||
};
|
||||
|
||||
_handleOnPressLogin = () => {
|
||||
const { navigation } = this.props;
|
||||
|
||||
navigation.navigate(ROUTES.SCREENS.LOGIN);
|
||||
};
|
||||
|
||||
_changeSelectedFilter = async (value, ind) => {
|
||||
this.setState({ selectedFilter: value, endOfNotification: false, selectedIndex: ind });
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const { selectedFilter, notificationsMap } = this.state;
|
||||
const { currentAccount } = this.props;
|
||||
if (currentAccount && nextProps.currentAccount) {
|
||||
if (nextProps.currentAccount.name !== currentAccount.name) {
|
||||
this.setState(
|
||||
{
|
||||
endOfNotification: false,
|
||||
notificationsMap: new Map(),
|
||||
},
|
||||
() => this._getActivities(selectedFilter),
|
||||
);
|
||||
} else if (
|
||||
nextProps.currentAccount.unread_activity_count > currentAccount.unread_activity_count
|
||||
) {
|
||||
notificationsMap.forEach((value, key) => {
|
||||
console.log('fetching new activities for ', key);
|
||||
this._getActivities(key, false, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLoggedIn, globalProps } = this.props;
|
||||
const { notificationsMap, selectedFilter, isRefreshing, isLoading } = this.state;
|
||||
|
||||
const _notifications = notificationsMap.get(selectedFilter) || [];
|
||||
return (
|
||||
<NotificationScreen
|
||||
getActivities={this._getActivities}
|
||||
notifications={_notifications}
|
||||
navigateToNotificationRoute={this._navigateToNotificationRoute}
|
||||
handleOnUserPress={this._handleOnUserPress}
|
||||
readAllNotification={this._readAllNotification}
|
||||
handleLoginPress={this._handleOnPressLogin}
|
||||
isNotificationRefreshing={isRefreshing}
|
||||
isLoggedIn={isLoggedIn}
|
||||
changeSelectedFilter={this._changeSelectedFilter}
|
||||
globalProps={globalProps}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isLoggedIn: state.application.isLoggedIn,
|
||||
isConnected: state.application.isConnected,
|
||||
pinCode: state.application.pin,
|
||||
currentAccount: state.account.currentAccount,
|
||||
globalProps: state.account.globalProps,
|
||||
activeBottomTab: state.ui.activeBottomTab,
|
||||
});
|
||||
|
||||
export default injectIntl(connect(mapStateToProps)(NotificationContainer));
|
||||
/* eslint-enable */
|
150
src/screens/notification/container/notificationContainer.tsx
Normal file
150
src/screens/notification/container/notificationContainer.tsx
Normal file
@ -0,0 +1,150 @@
|
||||
/* eslint-disable react/no-unused-state */
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import get from 'lodash/get';
|
||||
|
||||
// Actions and Services
|
||||
import { useEffect } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
// Constants
|
||||
import ROUTES from '../../../constants/routeNames';
|
||||
|
||||
// Components
|
||||
import NotificationScreen from '../screen/notificationScreen';
|
||||
import { showProfileModal } from '../../../redux/actions/uiAction';
|
||||
import { useAppSelector } from '../../../hooks';
|
||||
import { useNotificationReadMutation, useNotificationsQuery } from '../../../providers/queries';
|
||||
import { NotificationFilters } from '../../../providers/ecency/ecency.types';
|
||||
import QUERIES from '../../../providers/queries/queryKeys';
|
||||
|
||||
const NotificationContainer = ({ navigation }) => {
|
||||
const dispatch = useDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
||||
const isConnected = useAppSelector((state) => state.application.isConnected);
|
||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||
const globalProps = useAppSelector((state) => state.account.globalProps);
|
||||
|
||||
const unreadCountRef = useRef(currentAccount.unread_acitivity_count || 0);
|
||||
const curUsername = useRef(currentAccount.username);
|
||||
|
||||
const notificationReadMutation = useNotificationReadMutation();
|
||||
const allNotificationsQuery = useNotificationsQuery(NotificationFilters.ACTIVITIES);
|
||||
const repliesNotificationsQuery = useNotificationsQuery(NotificationFilters.REPLIES);
|
||||
const mentiosnNotificationsQuery = useNotificationsQuery(NotificationFilters.MENTIONS);
|
||||
|
||||
const [selectedFilter, setSelectedFilter] = useState(NotificationFilters.ACTIVITIES);
|
||||
|
||||
const selectedQuery =
|
||||
selectedFilter === NotificationFilters.REPLIES
|
||||
? repliesNotificationsQuery
|
||||
: selectedFilter === NotificationFilters.MENTIONS
|
||||
? mentiosnNotificationsQuery
|
||||
: allNotificationsQuery;
|
||||
|
||||
useEffect(() => {
|
||||
if (curUsername.current !== currentAccount.username) {
|
||||
queryClient.removeQueries([QUERIES.NOTIFICATIONS.GET]);
|
||||
selectedQuery.refresh();
|
||||
curUsername.current = currentAccount.useranme;
|
||||
}
|
||||
}, [currentAccount.username]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentAccount.unread_activity_count > unreadCountRef.current) {
|
||||
queryClient.invalidateQueries([QUERIES.NOTIFICATIONS.GET]);
|
||||
//TODO: fetch new notifications instead
|
||||
}
|
||||
unreadCountRef.current = currentAccount.unread_activity_count;
|
||||
}, [currentAccount.unread_activity_count]);
|
||||
|
||||
const _getActivities = (loadMore = false) => {
|
||||
if (loadMore) {
|
||||
console.log('load more notifications');
|
||||
selectedQuery.fetchNextPage();
|
||||
} else {
|
||||
console.log('refreshing');
|
||||
selectedQuery.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
const _navigateToNotificationRoute = (data) => {
|
||||
const type = get(data, 'type');
|
||||
const permlink = get(data, 'permlink');
|
||||
const author = get(data, 'author');
|
||||
let routeName;
|
||||
let params;
|
||||
let key;
|
||||
if (data && !data.read) {
|
||||
notificationReadMutation.mutate(data.id);
|
||||
}
|
||||
|
||||
if (permlink && author) {
|
||||
routeName = ROUTES.SCREENS.POST;
|
||||
key = permlink;
|
||||
params = {
|
||||
author,
|
||||
permlink,
|
||||
};
|
||||
} else if (type === 'follow') {
|
||||
routeName = ROUTES.SCREENS.PROFILE;
|
||||
key = get(data, 'follower');
|
||||
params = {
|
||||
username: get(data, 'follower'),
|
||||
};
|
||||
} else if (type === 'transfer') {
|
||||
routeName = ROUTES.TABBAR.WALLET;
|
||||
} else if (type === 'spin') {
|
||||
routeName = ROUTES.SCREENS.BOOST;
|
||||
} else if (type === 'inactive') {
|
||||
routeName = ROUTES.SCREENS.EDITOR;
|
||||
}
|
||||
|
||||
if (routeName) {
|
||||
navigation.navigate({
|
||||
name: routeName,
|
||||
params,
|
||||
key,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const _handleOnUserPress = (username) => {
|
||||
dispatch(showProfileModal(username));
|
||||
};
|
||||
|
||||
//TODO: handle mark as read mutations
|
||||
const _readAllNotification = () => {
|
||||
if (!isConnected) {
|
||||
return;
|
||||
}
|
||||
notificationReadMutation.mutate();
|
||||
};
|
||||
|
||||
const _handleOnPressLogin = () => {
|
||||
navigation.navigate(ROUTES.SCREENS.LOGIN);
|
||||
};
|
||||
|
||||
const _notifications = selectedQuery.data;
|
||||
|
||||
return (
|
||||
<NotificationScreen
|
||||
getActivities={_getActivities}
|
||||
notifications={_notifications}
|
||||
navigateToNotificationRoute={_navigateToNotificationRoute}
|
||||
handleOnUserPress={_handleOnUserPress}
|
||||
readAllNotification={_readAllNotification}
|
||||
handleLoginPress={_handleOnPressLogin}
|
||||
isNotificationRefreshing={selectedQuery.isRefreshing}
|
||||
isLoggedIn={isLoggedIn}
|
||||
changeSelectedFilter={setSelectedFilter}
|
||||
globalProps={globalProps}
|
||||
isLoading={selectedQuery.isLoading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationContainer;
|
||||
/* eslint-enable */
|
@ -5,195 +5,178 @@ import Config from 'react-native-config';
|
||||
import THEME_OPTIONS from '../constants/options/theme';
|
||||
import { getUnreadNotificationCount } from '../providers/ecency/ecency';
|
||||
import { getPointsSummary } from '../providers/ecency/ePoint';
|
||||
import { migrateToMasterKeyWithAccessToken, refreshSCToken, updatePinCode } from '../providers/hive/auth';
|
||||
import {
|
||||
migrateToMasterKeyWithAccessToken,
|
||||
refreshSCToken,
|
||||
updatePinCode,
|
||||
} from '../providers/hive/auth';
|
||||
import { getMutes } from '../providers/hive/dhive';
|
||||
import AUTH_TYPE from '../constants/authType';
|
||||
|
||||
// Services
|
||||
import {
|
||||
getSettings, getUserDataWithUsername,
|
||||
} from '../realm/realm';
|
||||
import { getSettings, getUserDataWithUsername } from '../realm/realm';
|
||||
import { updateCurrentAccount } from '../redux/actions/accountAction';
|
||||
|
||||
import {
|
||||
isDarkTheme,
|
||||
changeNotificationSettings,
|
||||
changeAllNotificationSettings,
|
||||
setApi,
|
||||
setCurrency,
|
||||
setLanguage,
|
||||
setNsfw,
|
||||
isDefaultFooter,
|
||||
isPinCodeOpen,
|
||||
setColorTheme,
|
||||
setSettingsMigrated,
|
||||
setPinCode,
|
||||
setEncryptedUnlockPin,
|
||||
setPostUpvotePercent,
|
||||
setCommentUpvotePercent,
|
||||
isDarkTheme,
|
||||
changeNotificationSettings,
|
||||
changeAllNotificationSettings,
|
||||
setApi,
|
||||
setCurrency,
|
||||
setLanguage,
|
||||
setNsfw,
|
||||
isDefaultFooter,
|
||||
isPinCodeOpen,
|
||||
setColorTheme,
|
||||
setSettingsMigrated,
|
||||
setPinCode,
|
||||
setEncryptedUnlockPin,
|
||||
setPostUpvotePercent,
|
||||
setCommentUpvotePercent,
|
||||
} from '../redux/actions/applicationActions';
|
||||
import { fetchSubscribedCommunities } from '../redux/actions/communitiesAction';
|
||||
import {
|
||||
hideActionModal,
|
||||
hideProfileModal,
|
||||
setRcOffer,
|
||||
toastNotification,
|
||||
hideActionModal,
|
||||
hideProfileModal,
|
||||
setRcOffer,
|
||||
toastNotification,
|
||||
} from '../redux/actions/uiAction';
|
||||
import { decryptKey, encryptKey } from './crypto';
|
||||
|
||||
|
||||
//migrates settings from realm to redux once and do no user realm for settings again;
|
||||
export const migrateSettings = async (dispatch: any, settingsMigratedV2: boolean) => {
|
||||
if (settingsMigratedV2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsMigratedV2) {
|
||||
return;
|
||||
//reset certain properties
|
||||
dispatch(hideActionModal());
|
||||
dispatch(hideProfileModal());
|
||||
dispatch(toastNotification(''));
|
||||
dispatch(setRcOffer(false));
|
||||
|
||||
const settings = await getSettings();
|
||||
|
||||
if (settings) {
|
||||
const isDarkMode = Appearance.getColorScheme() === 'dark';
|
||||
dispatch(isDarkTheme(settings.isDarkTheme !== null ? settings.isDarkTheme : isDarkMode));
|
||||
dispatch(setColorTheme(THEME_OPTIONS.findIndex((item) => item.value === settings.isDarkTheme)));
|
||||
await dispatch(isPinCodeOpen(!!settings.isPinCodeOpen));
|
||||
if (settings.language !== '') dispatch(setLanguage(settings.language));
|
||||
if (settings.server !== '') dispatch(setApi(settings.server));
|
||||
if (settings.upvotePercent !== '') {
|
||||
const percent = Number(settings.upvotePercent);
|
||||
dispatch(setPostUpvotePercent(percent));
|
||||
dispatch(setCommentUpvotePercent(percent));
|
||||
}
|
||||
if (settings.isDefaultFooter !== '') dispatch(isDefaultFooter(settings.isDefaultFooter)); //TODO: remove as not being used
|
||||
|
||||
if (settings.nsfw !== '') dispatch(setNsfw(settings.nsfw));
|
||||
|
||||
dispatch(setCurrency(settings.currency !== '' ? settings.currency : 'usd'));
|
||||
|
||||
if (settings.notification !== '') {
|
||||
dispatch(
|
||||
changeNotificationSettings({
|
||||
type: 'notification',
|
||||
action: settings.notification,
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch(changeAllNotificationSettings(settings));
|
||||
}
|
||||
|
||||
//reset certain properties
|
||||
dispatch(hideActionModal());
|
||||
dispatch(hideProfileModal());
|
||||
dispatch(toastNotification(''));
|
||||
dispatch(setRcOffer(false));
|
||||
|
||||
|
||||
const settings = await getSettings();
|
||||
|
||||
if (settings) {
|
||||
const isDarkMode = Appearance.getColorScheme() === 'dark';
|
||||
dispatch(isDarkTheme(settings.isDarkTheme !== null ? settings.isDarkTheme : isDarkMode));
|
||||
dispatch(setColorTheme(THEME_OPTIONS.findIndex(item => item.value === settings.isDarkTheme)));
|
||||
await dispatch(isPinCodeOpen(!!settings.isPinCodeOpen));
|
||||
if (settings.language !== '') dispatch(setLanguage(settings.language));
|
||||
if (settings.server !== '') dispatch(setApi(settings.server));
|
||||
if (settings.upvotePercent !== '') {
|
||||
const percent = Number(settings.upvotePercent);
|
||||
dispatch(setPostUpvotePercent(percent));
|
||||
dispatch(setCommentUpvotePercent(percent));
|
||||
}
|
||||
if (settings.isDefaultFooter !== '') dispatch(isDefaultFooter(settings.isDefaultFooter)); //TODO: remove as not being used
|
||||
|
||||
|
||||
if (settings.nsfw !== '') dispatch(setNsfw(settings.nsfw));
|
||||
|
||||
dispatch(setCurrency(settings.currency !== '' ? settings.currency : 'usd'));
|
||||
|
||||
if (settings.notification !== '') {
|
||||
dispatch(
|
||||
changeNotificationSettings({
|
||||
type: 'notification',
|
||||
action: settings.notification,
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch(changeAllNotificationSettings(settings));
|
||||
}
|
||||
|
||||
await dispatch(setSettingsMigrated(true))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await dispatch(setSettingsMigrated(true));
|
||||
}
|
||||
};
|
||||
|
||||
//migrates local user data to use default pin encruption instead of user pin encryption
|
||||
export const migrateUserEncryption = async (dispatch, currentAccount, encUserPin, onFailure) => {
|
||||
const oldPinCode = decryptKey(encUserPin, Config.PIN_KEY);
|
||||
|
||||
const oldPinCode = decryptKey(encUserPin, Config.PIN_KEY);
|
||||
if (oldPinCode === undefined || oldPinCode === Config.DEFAULT_PIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldPinCode === undefined || oldPinCode === Config.DEFAULT_PIN) {
|
||||
return;
|
||||
try {
|
||||
const pinData = {
|
||||
pinCode: Config.DEFAULT_PIN,
|
||||
username: currentAccount.username,
|
||||
oldPinCode,
|
||||
};
|
||||
|
||||
const response = updatePinCode(pinData);
|
||||
|
||||
const _currentAccount = currentAccount;
|
||||
_currentAccount.local = response;
|
||||
|
||||
dispatch(
|
||||
updateCurrentAccount({
|
||||
..._currentAccount,
|
||||
}),
|
||||
);
|
||||
|
||||
const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY);
|
||||
dispatch(setPinCode(encryptedPin));
|
||||
} catch (err) {
|
||||
console.warn('pin update failure: ', err);
|
||||
}
|
||||
|
||||
dispatch(setEncryptedUnlockPin(encUserPin));
|
||||
|
||||
const realmData = await getUserDataWithUsername(currentAccount.name);
|
||||
|
||||
let _currentAccount = currentAccount;
|
||||
_currentAccount.username = _currentAccount.name;
|
||||
_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,
|
||||
);
|
||||
}
|
||||
|
||||
//refresh access token
|
||||
const encryptedAccessToken = await refreshSCToken(_currentAccount.local, Config.DEFAULT_PIN);
|
||||
_currentAccount.local.accessToken = encryptedAccessToken;
|
||||
} catch (error) {
|
||||
onFailure(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const pinData = {
|
||||
pinCode: Config.DEFAULT_PIN,
|
||||
username: currentAccount.username,
|
||||
oldPinCode,
|
||||
};
|
||||
|
||||
const response = updatePinCode(pinData)
|
||||
|
||||
const _currentAccount = currentAccount;
|
||||
_currentAccount.local = response;
|
||||
|
||||
dispatch(
|
||||
updateCurrentAccount({
|
||||
..._currentAccount,
|
||||
}),
|
||||
);
|
||||
|
||||
const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY);
|
||||
dispatch(setPinCode(encryptedPin));
|
||||
|
||||
} catch (err) {
|
||||
console.warn('pin update failure: ', err);
|
||||
}
|
||||
|
||||
|
||||
dispatch(setEncryptedUnlockPin(encUserPin))
|
||||
|
||||
const realmData = await getUserDataWithUsername(currentAccount.name)
|
||||
|
||||
let _currentAccount = currentAccount;
|
||||
_currentAccount.username = _currentAccount.name;
|
||||
_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,
|
||||
);
|
||||
}
|
||||
|
||||
//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);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'Optional user data fetch failed, account can still function without them',
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(updateCurrentAccount({ ..._currentAccount }));
|
||||
dispatch(fetchSubscribedCommunities(_currentAccount.username));
|
||||
|
||||
}
|
||||
|
||||
//get unread notifications
|
||||
try {
|
||||
_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);
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
1: (state) => {
|
||||
state.application.notificationDetails.favoriteNotification = true
|
||||
return state;
|
||||
}
|
||||
}
|
||||
0: (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,
|
||||
migrateUserEncryption,
|
||||
reduxMigrations,
|
||||
}
|
||||
migrateSettings,
|
||||
migrateUserEncryption,
|
||||
reduxMigrations,
|
||||
};
|
||||
|
@ -8933,7 +8933,11 @@ react-native-image-zoom-viewer@^2.2.27:
|
||||
dependencies:
|
||||
react-native-image-pan-zoom "^2.1.9"
|
||||
|
||||
react-native-iphone-x-helper@^1.0.3, react-native-iphone-x-helper@^1.3.1:
|
||||
react-native-iphone-x-helper@Norcy/react-native-iphone-x-helper:
|
||||
version "2.0.0"
|
||||
resolved "https://codeload.github.com/Norcy/react-native-iphone-x-helper/tar.gz/aff3730b2614947a72bcdd86e35ba0217ca1054c"
|
||||
|
||||
react-native-iphone-x-helper@^1.0.3:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
||||
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
|
||||
|
Loading…
Reference in New Issue
Block a user