This commit is contained in:
Nouman Tahir 2022-10-17 11:28:45 +05:00
parent d34fa30f40
commit 3c63b00f87
132 changed files with 5256 additions and 5569 deletions

View File

@ -3,14 +3,13 @@ import AppCenter from 'appcenter';
import { name as appName } from './app.json';
import 'core-js';
import 'intl';
import 'intl/locale-data/jsonp/en-US'
import 'intl/locale-data/jsonp/en-US';
// set check frequency options
const EcencyApp = require('./App').default;
AppCenter.setLogLevel(AppCenter.LogLevel.VERBOSE);
// TODO Remove ignoreLogs when referenced issue is fixed properly
// ref: https://github.com/ecency/ecency-mobile/issues/2466
// ignore warnings

View File

@ -1,12 +1,27 @@
import React, { useEffect, useRef, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Alert } from 'react-native';
import { useIntl } from 'react-intl';
import { navigate } from '../../../navigation/service';
import { removeOtherAccount, updateCurrentAccount } from '../../../redux/actions/accountAction';
import { isPinCodeOpen, isRenderRequired, login, logout, logoutDone } from '../../../redux/actions/applicationActions';
import {
isPinCodeOpen,
isRenderRequired,
login,
logout,
logoutDone,
} from '../../../redux/actions/applicationActions';
import { getUserDataWithUsername, removeAllUserData, removePinCode, setAuthStatus, setExistUser, setPinCodeOpen } from '../../../realm/realm';
import {
getUserDataWithUsername,
removeAllUserData,
removePinCode,
setAuthStatus,
setExistUser,
setPinCodeOpen,
} from '../../../realm/realm';
import {
migrateToMasterKeyWithAccessToken,
refreshSCToken,
@ -20,12 +35,10 @@ import { toggleAccountsBottomSheet } from '../../../redux/actions/uiAction';
import AUTH_TYPE from '../../../constants/authType';
import { getDigitPinCode, getMutes } from '../../../providers/hive/dhive';
import { setFeedPosts, setInitPosts } from '../../../redux/actions/postsAction';
import { Alert } from 'react-native';
import { useIntl } from 'react-intl';
import { useAppSelector } from '../../../hooks';
import { getUnreadNotificationCount } from '../../../providers/ecency/ecency';
import { decryptKey } from '../../../utils/crypto';
import { getPointsSummary} from '../../../providers/ecency/ePoint';
import { getPointsSummary } from '../../../providers/ecency/ePoint';
import { fetchSubscribedCommunities } from '../../../redux/actions/communitiesAction';
import { clearSubscribedCommunitiesCache } from '../../../redux/actions/cacheActions';
@ -71,7 +84,6 @@ const AccountsBottomSheetContainer = ({ navigation }) => {
dispatch(logout());
};
const _handleSwitch = async (switchingAccount = {}) => {
try {
const accountData = accounts.filter(
@ -94,7 +106,11 @@ const AccountsBottomSheetContainer = ({ navigation }) => {
//migreate account to use access token for master key auth type
if (realmData[0].authType !== AUTH_TYPE.STEEM_CONNECT && realmData[0].accessToken === '') {
_currentAccount = await migrateToMasterKeyWithAccessToken(_currentAccount, realmData[0], pinHash);
_currentAccount = await migrateToMasterKeyWithAccessToken(
_currentAccount,
realmData[0],
pinHash,
);
}
//refresh access token
@ -112,10 +128,8 @@ const AccountsBottomSheetContainer = ({ navigation }) => {
dispatch(updateCurrentAccount(_currentAccount));
dispatch(clearSubscribedCommunitiesCache());
dispatch(fetchSubscribedCommunities(_currentAccount.username))
}
catch(error){
dispatch(fetchSubscribedCommunities(_currentAccount.username));
} catch (error) {
Alert.alert(
intl.formatMessage({
id: 'alert.fail',

View File

@ -1,2 +1,2 @@
export * from './optionsModal';
export * from './progressBar'
export * from './progressBar';

View File

@ -1,17 +1,17 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container:{
backgroundColor:'$primaryLightBackground',
flexDirection:'row',
borderRadius:16,
height:16,
alignSelf:'stretch',
marginHorizontal:8,
marginBottom:12,
container: {
backgroundColor: '$primaryLightBackground',
flexDirection: 'row',
borderRadius: 16,
height: 16,
alignSelf: 'stretch',
marginHorizontal: 8,
marginBottom: 12,
},
filled:{
borderRadius:16,
backgroundColor:'$primaryBlue'
filled: {
borderRadius: 16,
backgroundColor: '$primaryBlue',
},
});

View File

@ -1,20 +1,15 @@
import React from 'react';
import { View } from "react-native"
import { View } from 'react-native';
import styles from '../children/progresBarStyles';
export const ProgressBar = ({
progress
}) => {
const containerStyle = {...styles.container};
const filledStyle = {...styles.filled, flex:progress};
const unfilledStyle = {flex:100 - progress}
export const ProgressBar = ({ progress }) => {
const containerStyle = { ...styles.container };
const filledStyle = { ...styles.filled, flex: progress };
const unfilledStyle = { flex: 100 - progress };
return (
<View style={containerStyle}>
<View style={filledStyle} />
<View style={unfilledStyle} />
</View>
)
}
);
};

View File

@ -1,63 +1,59 @@
import React, { useEffect, useState } from "react";
import { Image } from "react-native";
import EStyleSheet from "react-native-extended-stylesheet";
import FastImage from "react-native-fast-image";
import { TouchableOpacity } from "react-native-gesture-handler";
import React, { useEffect, useState } from 'react';
import { Image } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import FastImage from 'react-native-fast-image';
import { TouchableOpacity } from 'react-native-gesture-handler';
interface AutoHeightImageProps {
contentWidth:number,
imgUrl:string,
isAnchored:boolean,
activeOpacity?:number,
onPress:()=>void,
}
interface AutoHeightImageProps {
contentWidth: number;
imgUrl: string;
isAnchored: boolean;
activeOpacity?: number;
onPress: () => void;
}
export const AutoHeightImage = ({
export const AutoHeightImage = ({
contentWidth,
imgUrl,
isAnchored,
activeOpacity,
onPress
}:AutoHeightImageProps) => {
onPress,
}: AutoHeightImageProps) => {
const [imgWidth, setImgWidth] = useState(contentWidth);
const [imgHeight, setImgHeight] = useState(imgWidth * (9/16))
const [imgHeight, setImgHeight] = useState(imgWidth * (9 / 16));
const [onLoadCalled, setOnLoadCalled] = useState(false);
useEffect(() => {
_fetchImageBounds();
}, [])
}, []);
const _fetchImageBounds = () => {
Image.getSize(imgUrl, (width, height)=>{
Image.getSize(imgUrl, (width, height) => {
const newWidth = width < contentWidth ? width : contentWidth;
const newHeight = (height / width) * newWidth;
setImgHeight(newHeight);
setImgWidth(newWidth);
})
}
});
};
const imgStyle = {
width:imgWidth - 10,
height:imgHeight,
backgroundColor: onLoadCalled ? 'transparent' : EStyleSheet.value('$primaryGray')
}
width: imgWidth - 10,
height: imgHeight,
backgroundColor: onLoadCalled ? 'transparent' : EStyleSheet.value('$primaryGray'),
};
const _onLoad = () => {
setOnLoadCalled(true);
}
};
return (
<TouchableOpacity onPress={onPress} disabled={isAnchored} activeOpacity={activeOpacity || 1}>
<FastImage
style={imgStyle}
source={{uri:imgUrl}}
source={{ uri: imgUrl }}
resizeMode={FastImage.resizeMode.contain}
onLoad={_onLoad}
/>
</TouchableOpacity>
)
}
);
};

View File

@ -9,12 +9,11 @@ import { setHidePostsThumbnails } from '../../../redux/actions/applicationAction
// Components
import BasicHeaderView from '../view/basicHeaderView';
interface BackHeaderProps {
backIconName:'close'|'arrow-back';
backIconName: 'close' | 'arrow-back';
}
const BasicHeaderContainer = (props:BackHeaderProps) => {
const BasicHeaderContainer = (props: BackHeaderProps) => {
const dispatch = useAppDispatch();
const isHideImages = useAppSelector((state) => state.application.hidePostsThumbnails);

View File

@ -3,6 +3,7 @@ import { View, Text, ActivityIndicator, SafeAreaView } from 'react-native';
import { injectIntl } from 'react-intl';
// Components
import EStyleSheet from 'react-native-extended-stylesheet';
import { TextButton } from '../..';
import { IconButton } from '../../iconButton';
import { DropdownButton } from '../../dropdownButton';
@ -12,7 +13,6 @@ import { TextInput } from '../../textInput';
// Styles
import styles from './basicHeaderStyles';
import { OptionsModal } from '../../atoms';
import EStyleSheet from 'react-native-extended-stylesheet';
const BasicHeaderView = ({
disabled,
@ -48,11 +48,9 @@ const BasicHeaderView = ({
handleSettingsPress,
backIconName,
}) => {
const [isInputVisible, setIsInputVisible] = useState(false);
const rewardMenuRef = useRef(null);
/**
*
* ACTION HANDLERS
@ -79,7 +77,6 @@ const BasicHeaderView = ({
handleOnSearch(value);
};
const _handleRewardMenuSelect = (index) => {
let rewardType = 'default';
@ -99,8 +96,6 @@ const BasicHeaderView = ({
}
};
/**
*
* UI RENDERER
@ -206,7 +201,10 @@ const BasicHeaderView = ({
onPress={() => handleOnSaveButtonPress && handleOnSaveButtonPress()}
/>
) : (
<ActivityIndicator style={styles.textButtonWrapper} color={EStyleSheet.value('$primaryBlue')} />
<ActivityIndicator
style={styles.textButtonWrapper}
color={EStyleSheet.value('$primaryBlue')}
/>
)}
</Fragment>
)}
@ -229,13 +227,15 @@ const BasicHeaderView = ({
text={rightButtonText}
/>
) : (
<ActivityIndicator style={[styles.textButtonWrapper]} color={EStyleSheet.value('$primaryBlue')} />
<ActivityIndicator
style={[styles.textButtonWrapper]}
color={EStyleSheet.value('$primaryBlue')}
/>
)}
</Fragment>
)}
</View>
<OptionsModal
ref={rewardMenuRef}
options={[
@ -248,7 +248,6 @@ const BasicHeaderView = ({
title="Reward"
onPress={_handleRewardMenuSelect}
/>
</SafeAreaView>
);
};

View File

@ -3,11 +3,11 @@ import { View, FlatList, Text, TouchableOpacity } from 'react-native';
import { useIntl } from 'react-intl';
import { isArray, debounce } from 'lodash';
import styles from './styles';
import EStyleSheet from 'react-native-extended-stylesheet';
import styles from './styles';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { BeneficiaryModal, CheckBox, FormInput, IconButton, TextButton } from '../../components';
import { BeneficiaryModal, CheckBox, FormInput, IconButton, TextButton } from '..';
import { Beneficiary } from '../../redux/reducers/editorReducer';
import { lookupAccounts } from '../../providers/hive/dhive';
import { TEMP_BENEFICIARIES_ID } from '../../redux/constants/constants';
@ -17,12 +17,11 @@ import {
} from '../../redux/actions/editorActions';
interface BeneficiarySelectionContentProps {
draftId: string;
setDisableDone: (value: boolean) => void;
powerDown?: boolean;
label?:string;
labelStyle?:string;
label?: string;
labelStyle?: string;
powerDownBeneficiaries?: Beneficiary[];
handleSaveBeneficiary?: (beneficiaries: Beneficiary[]) => void;
handleRemoveBeneficiary?: (beneficiary: Beneficiary) => void;
@ -38,7 +37,6 @@ const BeneficiarySelectionContent = ({
handleSaveBeneficiary,
handleRemoveBeneficiary,
}: BeneficiarySelectionContentProps) => {
const intl = useIntl();
const dispatch = useAppDispatch();
@ -76,7 +74,7 @@ const BeneficiarySelectionContent = ({
const readPowerDownBeneficiaries = () => {
const tempBeneficiaries = [
{ account: username, weight: 10000, autoPowerUp: false },
...powerDownBeneficiaries as Beneficiary[],
...(powerDownBeneficiaries as Beneficiary[]),
];
if (isArray(tempBeneficiaries) && tempBeneficiaries.length > 0) {
@ -96,7 +94,10 @@ const BeneficiarySelectionContent = ({
const readTempBeneficiaries = async () => {
if (beneficiariesMap) {
const savedBeneficiareis = beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID];
const tempBeneficiaries = savedBeneficiareis && savedBeneficiareis.length ? [DEFAULT_BENEFICIARY, ...beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID]] : [DEFAULT_BENEFICIARY];
const tempBeneficiaries =
savedBeneficiareis && savedBeneficiareis.length
? [DEFAULT_BENEFICIARY, ...beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID]]
: [DEFAULT_BENEFICIARY];
if (isArray(tempBeneficiaries) && tempBeneficiaries.length > 0) {
//weight correction algorithm.
@ -242,7 +243,7 @@ const BeneficiarySelectionContent = ({
onChange={(value) => _onWeightInputChange(value)}
selectTextOnFocus={true}
autoFocus={true}
returnKeyType={'next'}
returnKeyType="next"
keyboardType="numeric"
/>
</View>
@ -315,7 +316,7 @@ const BeneficiarySelectionContent = ({
beneficiaries[0].weight = beneficiaries[0].weight + item.weight;
const removedBeneficiary = beneficiaries.splice(index, 1);
setBeneficiaries([...beneficiaries]);
if(handleRemoveBeneficiary){
if (handleRemoveBeneficiary) {
handleRemoveBeneficiary(removedBeneficiary[0]);
return;
}
@ -364,7 +365,9 @@ const BeneficiarySelectionContent = ({
return (
<View style={styles.container}>
<Text style={labelStyle || styles.settingLabel}>{label || intl.formatMessage({ id: 'editor.beneficiaries' })}</Text>
<Text style={labelStyle || styles.settingLabel}>
{label || intl.formatMessage({ id: 'editor.beneficiaries' })}
</Text>
<FlatList
data={beneficiaries}
renderItem={_renderItem}

View File

@ -2,64 +2,63 @@ 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'
thumbStyle: {
width: 72,
height: 72,
marginVertical: 8,
marginRight: 8,
borderRadius: 12,
backgroundColor: '$primaryLightGray',
},
selectedStyle:{
borderWidth:4,
borderColor:'$primaryBlack'
selectedStyle: {
borderWidth: 4,
borderColor: '$primaryBlack',
},
settingLabel:{
settingLabel: {
color: '$primaryDarkGray',
fontSize: 14,
fontWeight: 'bold',
flexGrow: 1,
textAlign:'left',
textAlign: 'left',
},
listContainer:{
paddingBottom:getBottomSpace() + 16,
listContainer: {
paddingBottom: getBottomSpace() + 16,
},
container:{
paddingVertical: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 },
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, },
usernameInput: { flex: 1, color: '$primaryBlack', marginLeft: 16 },
usernameFormInputWrapper: { marginTop: 8, marginRight: 12 },
footerWrapper: { paddingTop:16 },
footerWrapper: { paddingTop: 16 },
saveButton: {
width: 140,
height: 44,
alignSelf: 'flex-end',
justifyContent: 'center',
},
doneButton:{borderRadius:16, backgroundColor:'$primaryBlue'},
thumbSelectContainer:{
marginTop:12,
doneButton: { borderRadius: 16, backgroundColor: '$primaryBlue' },
thumbSelectContainer: {
marginTop: 12,
},
checkBoxHeader: {
width: 50,
},
checkBoxContainer: {
width: 50,
marginTop:12,
}
marginTop: 12,
},
});

View File

@ -1,11 +1,12 @@
import React, { useEffect } from 'react';
import { SafeAreaView, View, TouchableOpacity } from 'react-native';
// Components
// import TabBar from './tabbar';
// Constants
import { useDispatch } from 'react-redux';
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
import ROUTES from '../../../constants/routeNames';
// Styles
@ -13,40 +14,29 @@ import styles from './bottomTabBarStyles';
import Icon, { IconContainer } from '../../icon';
import scalePx from '../../../utils/scalePx';
import { updateActiveBottomTab } from '../../../redux/actions/uiAction';
import { useDispatch } from 'react-redux';
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
const BottomTabBarView = ({
state : { routes , index },
state: { routes, index },
navigation,
descriptors
}:BottomTabBarProps) => {
descriptors,
}: BottomTabBarProps) => {
const dispatch = useDispatch();
useEffect(()=>{
dispatch(updateActiveBottomTab(routes[index].name))
},[index])
useEffect(() => {
dispatch(updateActiveBottomTab(routes[index].name));
}, [index]);
const _jumpTo = (route, isFocused) => {
if(route.name === ROUTES.TABBAR.POST_BUTTON){
navigation.navigate(ROUTES.SCREENS.EDITOR, {key: 'editor_post'})
if (route.name === ROUTES.TABBAR.POST_BUTTON) {
navigation.navigate(ROUTES.SCREENS.EDITOR, { key: 'editor_post' });
return;
}
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
})
});
//TODO: also enable tap to scroll up feature
if (!isFocused && !event.defaultPrevented) {
@ -54,48 +44,38 @@ const BottomTabBarView = ({
}
};
const _tabButtons = routes.map((route, idx) => {
const {tabBarActiveTintColor, tabBarInactiveTintColor} = descriptors[route.key].options
const { tabBarActiveTintColor, tabBarInactiveTintColor } = descriptors[route.key].options;
const isFocused = index == idx;
const iconColor = isFocused ? tabBarActiveTintColor : tabBarInactiveTintColor
const iconColor = isFocused ? tabBarActiveTintColor : tabBarInactiveTintColor;
let _iconProps = {
iconType:"MaterialIcons",
style:{ padding: 15 },
name:route.params.iconName,
color:iconColor,
size:scalePx(26),
}
iconType: 'MaterialIcons',
style: { padding: 15 },
name: route.params.iconName,
color: iconColor,
size: scalePx(26),
};
let _tabBarIcon = <Icon {..._iconProps}/>
let _tabBarIcon = <Icon {..._iconProps} />;
switch (route.name) {
case ROUTES.TABBAR.NOTIFICATION:
_tabBarIcon = (<IconContainer
isBadge badgeType="notification" {..._iconProps}/>)
_tabBarIcon = <IconContainer isBadge badgeType="notification" {..._iconProps} />;
break;
case ROUTES.TABBAR.POST_BUTTON:
_iconProps.iconType = "MaterialCommunityIcons"
_tabBarIcon = <Icon {..._iconProps} />
_iconProps.iconType = 'MaterialCommunityIcons';
_tabBarIcon = <Icon {..._iconProps} />;
break;
}
return (
<View key={route.key} style={{ flex: 1, alignItems: 'center' }}>
<TouchableOpacity onPress={() => _jumpTo(route, isFocused)}>
{_tabBarIcon}
</TouchableOpacity>
<TouchableOpacity onPress={() => _jumpTo(route, isFocused)}>{_tabBarIcon}</TouchableOpacity>
</View>
)
})
return (
<SafeAreaView style={styles.wrapper}>
{_tabButtons}
</SafeAreaView>
);
});
return <SafeAreaView style={styles.wrapper}>{_tabButtons}</SafeAreaView>;
};
export default BottomTabBarView;

View File

@ -4,6 +4,7 @@ import { useIntl } from 'react-intl';
import get from 'lodash/get';
import { View as AnimatedView } from 'react-native-animatable';
import { useDispatch } from 'react-redux';
import { getTimeFromNow } from '../../../utils/time';
// Constants
@ -18,7 +19,6 @@ import { TextWithIcon } from '../../basicUIElements';
import styles from './commentStyles';
import { useAppSelector } from '../../../hooks';
import { OptionsModal } from '../../atoms';
import { useDispatch } from 'react-redux';
import { showReplyModal } from '../../../redux/actions/uiAction';
import postTypes from '../../../constants/postTypes';
@ -42,14 +42,16 @@ const CommentView = ({
hideManyCommentsButton,
openReplyThread,
fetchedAt,
incrementRepliesCount
incrementRepliesCount,
}) => {
const intl = useIntl();
const dispatch = useDispatch();
const actionSheet = useRef(null);
const repliesContainerRef = useRef<AnimatedView>(null);
const isMuted = useAppSelector(state => state.account.currentAccount.mutes?.indexOf(comment.author) > -1);
const isMuted = useAppSelector(
(state) => state.account.currentAccount.mutes?.indexOf(comment.author) > -1,
);
const lastCacheUpdate = useAppSelector((state) => state.cache.lastUpdate);
const cachedComments = useAppSelector((state) => state.cache.comments);
@ -61,17 +63,16 @@ const CommentView = ({
const [childCount, setChildCount] = useState(comment.children);
const [replies, setReplies] = useState(comment.replies);
useEffect(()=>{
if(isShowSubComments){
setTimeout(()=>{
if(repliesContainerRef.current){
useEffect(() => {
if (isShowSubComments) {
setTimeout(() => {
if (repliesContainerRef.current) {
setIsShowSubComments(true);
repliesContainerRef.current.slideInRight(300);
}
},150)
}, 150);
}
},[])
}, []);
useEffect(() => {
if (comment) {
@ -79,7 +80,6 @@ const CommentView = ({
}
}, [comment]);
useEffect(() => {
const postPath = `${comment.author || ''}/${comment.permlink || ''}`;
//this conditional makes sure on targetted already fetched post is updated
@ -107,11 +107,10 @@ const CommentView = ({
}, [lastCacheUpdate]);
const _showSubCommentsToggle = (force) => {
if (((replies && replies.length > 0) || force)) {
if ((replies && replies.length > 0) || force) {
if (repliesContainerRef.current) {
if (_isShowSubComments) {
repliesContainerRef.current.slideOutRight(300).then(()=>{
repliesContainerRef.current.slideOutRight(300).then(() => {
setIsShowSubComments(false);
});
} else {
@ -121,34 +120,32 @@ const CommentView = ({
}
setIsPressedShowButton(true);
} else if (openReplyThread) {
openReplyThread();
}
};
};
const _handleCacheVoteIncrement = () => {
const _handleCacheVoteIncrement = () => {
//fake increment vote using based on local change
setCacheVoteIcrement(1);
};
};
const _incrementRepliesCount = () => {
const _incrementRepliesCount = () => {
if (commentNumber > 1 && incrementRepliesCount) {
incrementRepliesCount();
}
setChildCount(childCount + 1);
}
};
const _handleOnReplyPress = () => {
const _handleOnReplyPress = () => {
if (isLoggedIn) {
dispatch(showReplyModal(comment));
} else {
console.log('Not LoggedIn');
}
}
};
const _renderReadMoreButton = () => (
const _renderReadMoreButton = () => (
<TextWithIcon
wrapperStyle={styles.rightButton}
textStyle={!isPressedShowButton && styles.moreText}
@ -157,21 +154,14 @@ const _renderReadMoreButton = () => (
iconStyle={styles.iconStyle}
iconSize={16}
onPress={() => openReplyThread && openReplyThread()}
text={
!isPressedShowButton
? intl.formatMessage({ id: 'comments.read_more' })
: ''
}
text={!isPressedShowButton ? intl.formatMessage({ id: 'comments.read_more' }) : ''}
/>
);
)
const _renderReplies = () => {
const _renderReplies = () => {
return (
<AnimatedView ref={repliesContainerRef}>
{_isShowSubComments &&
{_isShowSubComments && (
<Comments
isShowComments={isShowComments}
commentNumber={commentNumber + 1}
@ -189,15 +179,14 @@ const _renderReplies = () => {
fetchedAt={fetchedAt}
incrementRepliesCount={_incrementRepliesCount}
handleOnReplyPress={_handleOnReplyPress}
/>}
/>
)}
</AnimatedView>
);
};
)
}
const _renderComment = () => {
return ((
const _renderComment = () => {
return (
<View style={[{ marginLeft: 2, marginTop: -6 }]}>
<CommentBody
commentDepth={comment.depth}
@ -211,21 +200,14 @@ const _renderComment = () => {
/>
<Fragment>
<View style={styles.footerWrapper}>
{_renderActionPanel()}
</View>
{commentNumber > 1 &&
childCount > 0 &&
!replies?.length &&
_renderReadMoreButton()
}
<View style={styles.footerWrapper}>{_renderActionPanel()}</View>
{commentNumber > 1 && childCount > 0 && !replies?.length && _renderReadMoreButton()}
</Fragment>
</View>
))
}
);
};
const _renderActionPanel = () => {
const _renderActionPanel = () => {
return (
<>
<Upvote
@ -261,7 +243,6 @@ const _renderActionPanel = () => {
/>
)}
{currentAccountUsername === comment.author && (
<Fragment>
<IconButton
@ -300,7 +281,6 @@ const _renderActionPanel = () => {
</Fragment>
)}
{commentNumber === 1 && childCount > 0 && (
<View style={styles.rightButtonWrapper}>
<TextWithIcon
@ -316,14 +296,13 @@ const _renderActionPanel = () => {
/>
</View>
)}
</>
)
}
);
};
const customContainerStyle = commentNumber > 2 ? { marginLeft: 44 } : null
const customContainerStyle = commentNumber > 2 ? { marginLeft: 44 } : null;
return (
return (
<Fragment>
<View style={{ ...styles.commentContainer, ...customContainerStyle }}>
<PostHeaderDescription
@ -345,7 +324,7 @@ return (
{commentNumber > 0 && _renderReplies()}
</View>
</Fragment>
);
);
};
export default CommentView;

View File

@ -4,14 +4,13 @@ import get from 'lodash/get';
import { useIntl } from 'react-intl';
// Components
import EStyleSheet from 'react-native-extended-stylesheet';
import { Comment, TextButton } from '../..';
// Styles
import styles from './commentStyles';
import EStyleSheet from 'react-native-extended-stylesheet';
import { OptionsModal } from '../../atoms';
const CommentsView = ({
avatarSize,
commentCount,
@ -36,13 +35,12 @@ const CommentsView = ({
flatListProps,
openReplyThread,
fetchedAt,
incrementRepliesCount
incrementRepliesCount,
}) => {
const [selectedComment, setSelectedComment] = useState(null);
const intl = useIntl();
const commentMenu = useRef<any>();
const _openCommentMenu = (item) => {
if (commentMenu.current) {
setSelectedComment(item);
@ -52,21 +50,20 @@ const CommentsView = ({
const _openReplyThread = (item) => {
if (item && openReplyThread) {
openReplyThread(item)
}
openReplyThread(item);
}
};
const _readMoreComments = () => {
if (comments[0] && openReplyThread) {
openReplyThread(comments[0])
openReplyThread(comments[0]);
}
};
const _onMenuItemPress = (index) => {
handleOnPressCommentMenu(index, selectedComment)
handleOnPressCommentMenu(index, selectedComment);
setSelectedComment(null);
}
};
const menuItems = [
intl.formatMessage({ id: 'post.copy_link' }),
@ -75,7 +72,6 @@ const CommentsView = ({
intl.formatMessage({ id: 'alert.cancel' }),
];
if (!hideManyCommentsButton && hasManyComments) {
return (
<TextButton
@ -87,7 +83,6 @@ const CommentsView = ({
);
}
const _renderItem = ({ item }) => {
return (
<Comment
@ -115,29 +110,30 @@ const CommentsView = ({
fetchedAt={fetchedAt}
incrementRepliesCount={incrementRepliesCount}
/>
)
);
};
const styleOerride = commentNumber > 1 ? {
const styleOerride =
commentNumber > 1
? {
backgroundColor: EStyleSheet.value('$primaryLightBackground'),
marginTop: 8,
} : null
}
: null;
const _renderEmptyContent = () => {
if(commentNumber > 1){
if (commentNumber > 1) {
return;
}
const _onPress = () => {
handleOnReplyPress()
}
handleOnReplyPress();
};
return (
<Text onPress={_onPress} style={styles.emptyText}>
{intl.formatMessage({ id: "comments.no_comments" })}
{intl.formatMessage({ id: 'comments.no_comments' })}
</Text>
)
}
);
};
return (
<Fragment>

View File

@ -1,12 +1,12 @@
import { View, Text } from 'react-native'
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
import UserAvatar from '../../userAvatar';
import { View, Text } from 'react-native';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import { View as AnimatedView } from 'react-native-animatable';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { useIntl } from 'react-intl';
import UserAvatar from '../../userAvatar';
import styles from './WriteCommentButtonStyles';
import { useAppSelector } from '../../../hooks';
import showLoginAlert from '../../../utils/showLoginAlert';
import { useIntl } from 'react-intl';
interface WriteCommentButton {
onPress: () => void;
@ -17,12 +17,12 @@ export const WriteCommentButton = forwardRef(({ onPress }, ref) => {
const animatedContainer = useRef<AnimatedView>();
const isLoggedIn = useAppSelector(state => state.application.isLoggedIn);
const currentAccount = useAppSelector(state => state.account.currentAccount);
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
const currentAccount = useAppSelector((state) => state.account.currentAccount);
useImperativeHandle(ref, () => ({
bounce: () => {
console.log("bouncing")
console.log('bouncing');
if (animatedContainer.current) {
animatedContainer.current.swing(1000);
}
@ -31,13 +31,13 @@ export const WriteCommentButton = forwardRef(({ onPress }, ref) => {
const _onPress = () => {
if (!isLoggedIn) {
showLoginAlert({ intl })
showLoginAlert({ intl });
return;
}
if (onPress) {
onPress();
}
}
};
return (
<AnimatedView ref={animatedContainer}>
@ -46,12 +46,11 @@ export const WriteCommentButton = forwardRef(({ onPress }, ref) => {
<UserAvatar username={currentAccount.username} />
<View style={styles.inputContainer}>
<Text style={styles.inputPlaceholder}>
{intl.formatMessage({id:'quick_reply.placeholder'})}
{intl.formatMessage({ id: 'quick_reply.placeholder' })}
</Text>
</View>
</View>
</TouchableOpacity>
</AnimatedView>
)
})
);
});

View File

@ -5,32 +5,32 @@ import getWindowDimensions from '../../utils/getWindowDimensions';
export default EStyleSheet.create({
modalStyle: {
backgroundColor: '$primaryBackgroundColor',
margin:0,
paddingTop:32,
paddingBottom:8,
margin: 0,
paddingTop: 32,
paddingBottom: 8,
},
sheetContent: {
backgroundColor: '$primaryBackgroundColor',
},
container:{
marginTop:16,
marginBottom:44,
paddingHorizontal:24,
alignItems:'center',
justifyContent:'space-between',
container: {
marginTop: 16,
marginBottom: 44,
paddingHorizontal: 24,
alignItems: 'center',
justifyContent: 'space-between',
} as ViewStyle,
imageStyle:{
marginTop:8,
height:150,
width:150,
imageStyle: {
marginTop: 8,
height: 150,
width: 150,
} as ImageStyle,
textContainer:{
marginTop:32,
marginBottom:44,
textContainer: {
marginTop: 32,
marginBottom: 44,
} as ViewStyle,
title: {
@ -47,47 +47,41 @@ export default EStyleSheet.create({
textAlign: 'center',
fontSize: 16,
fontWeight: '600',
marginTop:4,
marginTop: 4,
} as TextStyle,
btnText:{
color:'$pureWhite'
btnText: {
color: '$pureWhite',
} as TextStyle,
button:{
backgroundColor:'$primaryBlue',
width:150,
paddingVertical:16,
borderRadius:32,
justifyContent:'center',
alignItems:'center'
button: {
backgroundColor: '$primaryBlue',
width: 150,
paddingVertical: 16,
borderRadius: 32,
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
actionPanel:{
width:'100%',
flexDirection:'row',
justifyContent:'space-around',
alignItems:'center',
actionPanel: {
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
} as ViewStyle,
checkView: {
width:getWindowDimensions().width - 80,
width: getWindowDimensions().width - 80,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems:'center',
alignItems: 'center',
marginHorizontal: 20,
marginVertical:4,
marginVertical: 4,
} as ViewStyle,
informationText: {
color: '$primaryBlack',
margin: 10,
fontSize:18,
fontSize: 18,
} as TextStyle,
})
});

View File

@ -1,5 +1,5 @@
import React from 'react';
import { View, Text, ActivityIndicator, TouchableHighlight} from 'react-native';
import { View, Text, ActivityIndicator, TouchableHighlight } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
// External components
import ModalDropdown from 'react-native-modal-dropdown';
@ -42,11 +42,11 @@ const renderDropdownRow = (
</Text>
</View>
);
const adjustDropdownFrame = (style:any) => {
style.left = 'auto'
style.right = 10
return style
}
const adjustDropdownFrame = (style: any) => {
style.left = 'auto';
style.right = 10;
return style;
};
const DropdownButtonView = ({
childIconWrapperStyle,
children,
@ -72,7 +72,10 @@ const DropdownButtonView = ({
<ModalDropdown
ref={dropdownRef}
renderRowComponent={TouchableHighlight}
renderRowProps={{ underlayColor: EStyleSheet.value('$modalBackground'), style:styles.rowWrapper}}
renderRowProps={{
underlayColor: EStyleSheet.value('$modalBackground'),
style: styles.rowWrapper,
}}
style={[!style ? styles.button : style]}
textStyle={[textStyle || styles.buttonText]}
dropdownStyle={[styles.dropdown, dropdownStyle, { height: 32 * (options.length + 1.5) }]}
@ -94,13 +97,11 @@ const DropdownButtonView = ({
dropdownRowWrapper,
)
}
adjustFrame={(style: any) => adjustDropdownFrame(style) }
adjustFrame={(style: any) => adjustDropdownFrame(style)}
>
{isHasChildIcon && !isLoading ? (
<View style={styles.childrenWrapper}>
<Text style={[textStyle || styles.buttonText]}>
{defaultText}
</Text>
<Text style={[textStyle || styles.buttonText]}>{defaultText}</Text>
<View style={[styles.iconWrapper, childIconWrapperStyle && childIconWrapperStyle]}>
<Icon
style={[styles.dropdownIcon, iconStyle]}

View File

@ -16,8 +16,7 @@ import { Tag } from '../../../basicUIElements';
import { isCommunity } from '../../../../utils/communityValidation';
import { toastNotification } from '../../../../redux/actions/uiAction';
const SEPARATOR_REGEX = /[,\s]/
const SEPARATOR_REGEX = /[,\s]/;
const TagInput = ({ value, handleTagChanged, intl, isPreviewActive, autoFocus, setCommunity }) => {
const dispatch = useAppDispatch();

View File

@ -3,6 +3,7 @@ import React, { useEffect, useRef, useState } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { View as AnimatedView } from 'react-native-animatable';
import { useDispatch } from 'react-redux';
import { useIntl } from 'react-intl';
import { IconButton } from '..';
import { toastNotification } from '../../redux/actions/uiAction';
import UserAvatar from '../userAvatar';
@ -11,7 +12,6 @@ import ROUTES from '../../constants/routeNames';
// Styles
import styles, { CONTAINER_HEIGHT } from './styles';
import { navigate } from '../../navigation/service';
import { useIntl } from 'react-intl';
interface RemoteMessage {
data: {
@ -26,15 +26,13 @@ interface RemoteMessage {
notification: {
body: string;
title: string;
}
};
}
interface Props {
remoteMessage: RemoteMessage
remoteMessage: RemoteMessage;
}
const ForegroundNotification = ({ remoteMessage }: Props) => {
const intl = useIntl();
@ -48,26 +46,23 @@ const ForegroundNotification = ({ remoteMessage }: Props) => {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
useEffect(() => {
if (remoteMessage) {
const { source, target, type, id } = remoteMessage.data;
if (activeId !== id && (type === 'reply' || type === 'mention')) {
let titlePrefixId = '';
switch (type) {
case 'reply':
titlePrefixId = 'notification.reply_on'
titlePrefixId = 'notification.reply_on';
break;
case 'mention':
titlePrefixId = 'notification.mention_on'
titlePrefixId = 'notification.mention_on';
break;
}
setActiveId(id);
setUsername(source);
setTitle(`${intl.formatMessage({ id: titlePrefixId })} @${target}`)
setTitle(`${intl.formatMessage({ id: titlePrefixId })} @${target}`);
setBody(intl.formatMessage({ id: 'notification.reply_body' }));
show();
}
@ -77,14 +72,14 @@ const ForegroundNotification = ({ remoteMessage }: Props) => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
}
};
}, [remoteMessage]);
const show = () => {
setIsVisible(true)
setIsVisible(true);
hideTimeoutRef.current = setTimeout(() => {
hide();
}, duration)
}, duration);
};
@ -93,14 +88,12 @@ const ForegroundNotification = ({ remoteMessage }: Props) => {
await containerRef.current.fadeOutUp(300);
setIsVisible(false);
if(hideTimeoutRef.current){
if (hideTimeoutRef.current){
clearTimeout(hideTimeoutRef.current);
}
}
};
const _onPress = () => {
const { data } = remoteMessage;
const fullPermlink =
@ -110,7 +103,7 @@ const ForegroundNotification = ({ remoteMessage }: Props) => {
author: get(data, 'source', ''),
permlink: fullPermlink,
};
let key = fullPermlink
let key = fullPermlink;
let routeName = ROUTES.SCREENS.POST;
navigate({
@ -119,41 +112,38 @@ const ForegroundNotification = ({ remoteMessage }: Props) => {
key,
});
hide();
}
};
return (
isVisible &&
isVisible && (
<AnimatedView
ref={containerRef}
style={styles.container}
animation='slideInDown'
duration={500}>
animation="slideInDown"
duration={500}
>
<View style={styles.contentContainer}>
<TouchableOpacity onPress={_onPress} style={{ flexShrink: 1 }}>
<View style={{ flexDirection: 'row', alignItems: 'center', marginRight: 24 }}>
<UserAvatar username={username} />
<View style={{ flexShrink: 1 }}>
<Text style={styles.text} numberOfLines={1}>{title}</Text>
<Text style={styles.text} numberOfLines={1}>{body}</Text>
<Text style={styles.text} numberOfLines={1}>
{title}
</Text>
<Text style={styles.text} numberOfLines={1}>
{body}
</Text>
</View>
</View>
</TouchableOpacity>
<IconButton
name='close'
color="white"
size={28}
onPress={hide}
/>
<IconButton name="close" color="white" size={28} onPress={hide} />
</View>
</AnimatedView>
)
}
);
);
};
export default ForegroundNotification;

View File

@ -5,16 +5,16 @@ import { getStatusBarHeight } from 'react-native-iphone-x-helper';
export const CONTAINER_HEIGHT = getStatusBarHeight() + 100;
export default EStyleSheet.create({
container:{
container: {
position: 'absolute',
top:0,
justifyContent:'center',
top: 0,
justifyContent: 'center',
zIndex: 9999,
marginHorizontal:8,
paddingTop:16,
marginHorizontal: 8,
paddingTop: 16,
marginTop: Platform.select({
ios:getStatusBarHeight() + 12,
android:8,
ios: getStatusBarHeight() + 12,
android: 8,
}),
backgroundColor: '$darkGrayBackground',
shadowColor: '#5f5f5fbf',
@ -23,20 +23,20 @@ export default EStyleSheet.create({
height: 5,
},
elevation: 3,
borderRadius:12,
borderRadius: 12,
width: '$deviceWidth - 16',
},
contentContainer:{
flexDirection:'row',
justifyContent:'space-between',
alignItems:'center',
paddingBottom:16,
paddingHorizontal:16
contentContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingBottom: 16,
paddingHorizontal: 16,
},
text: {
color: 'white',
fontWeight: 'bold',
fontSize: 14,
paddingLeft:16,
paddingLeft: 16,
},
});

View File

@ -13,21 +13,20 @@ import { getResizedAvatar } from '../../../utils/image';
// Styles
import styles from './formInputStyles';
interface Props extends TextInputProps {
type:string;
isFirstImage:boolean;
isEditable?:boolean;
leftIconName?:string;
rightIconName?:string;
iconType?:string;
wrapperStyle:ViewStyle;
height:number;
inputStyle:TextStyle;
isValid:boolean;
onChange?:(value:string)=>void;
onFocus?:()=>void;
onBlur?:()=>void;
type: string;
isFirstImage: boolean;
isEditable?: boolean;
leftIconName?: string;
rightIconName?: string;
iconType?: string;
wrapperStyle: ViewStyle;
height: number;
inputStyle: TextStyle;
isValid: boolean;
onChange?: (value: string) => void;
onFocus?: () => void;
onBlur?: () => void;
}
const FormInputView = ({
@ -48,7 +47,7 @@ const FormInputView = ({
onBlur,
onFocus,
...props
}:Props) => {
}: Props) => {
const [_value, setValue] = useState(value || '');
const [inputBorderColor, setInputBorderColor] = useState('#e7e7e7');
const [_isValid, setIsValid] = useState(true);
@ -65,7 +64,7 @@ const FormInputView = ({
const _handleOnFocus = () => {
setInputBorderColor('#357ce6');
if(onFocus){
if (onFocus) {
onFocus();
}
};

View File

@ -46,13 +46,8 @@ const HeaderView = ({
});
};
const _renderAvatar = () => (
<TouchableOpacity
style={styles.avatarWrapper}
onPress={handleOpenDrawer}
disabled={isReverse}
>
<TouchableOpacity style={styles.avatarWrapper} onPress={handleOpenDrawer} disabled={isReverse}>
<LinearGradient
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
@ -69,14 +64,17 @@ const HeaderView = ({
/>
</LinearGradient>
</TouchableOpacity>
)
);
const _renderTitle = () => (
<>
{displayName || username ? (
<View style={[styles.titleWrapper, isReverse && styles.titleWrapperReverse]}>
{displayName && <Text numberOfLines={1} style={styles.title}>{displayName}</Text>}
{displayName && (
<Text numberOfLines={1} style={styles.title}>
{displayName}
</Text>
)}
<Text style={styles.subTitle}>
{`@${username}`}
{reputation && ` (${reputation})`}
@ -94,8 +92,7 @@ const HeaderView = ({
</View>
)}
</>
)
);
const _renderActionButtons = () => (
<>
@ -123,11 +120,10 @@ const HeaderView = ({
</View>
)}
</>
)
);
return (
<SafeAreaView style={[styles.container, isReverse && styles.containerReverse]}>
{!hideUser && (
<>
<SearchModal

View File

@ -1,21 +1,15 @@
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import {
Platform,
Text,
TouchableOpacity,
View,
ActivityIndicator,
} from 'react-native';
import { Platform, Text, TouchableOpacity, View, ActivityIndicator } from 'react-native';
import { renderPostBody } from '@ecency/render-helper';
import { ScrollView } from 'react-native-gesture-handler';
import Clipboard from '@react-native-clipboard/clipboard';
import { MainButton, PostBody, TextButton } from '..';
import styles from './insertLinkModalStyles';
import TextInput from '../textInput';
import { delay } from '../../utils/editor';
import { isStringWebLink } from '../markdownEditor/children/formats/utils';
import { renderPostBody } from '@ecency/render-helper';
import { ScrollView } from 'react-native-gesture-handler';
import applyWebLinkFormat from '../markdownEditor/children/formats/applyWebLinkFormat';
import Clipboard from '@react-native-clipboard/clipboard';
import getWindowDimensions from '../../utils/getWindowDimensions';
import Modal from '../modal';
@ -49,7 +43,6 @@ export const InsertLinkModal = forwardRef(
const labelInputRef = useRef(null);
const urlInputRef = useRef(null);
useImperativeHandle(ref, () => ({
showModal: async ({ selectedText, selection }) => {
if (selectedText) {
@ -141,8 +134,8 @@ export const InsertLinkModal = forwardRef(
<View style={styles.floatingContainer}>
<TextButton
style={styles.cancelButton}
onPress={() => setVisible(false)}// sheetModalRef.current?.setModalVisible(false)}
text={'Cancel'}
onPress={() => setVisible(false)} // sheetModalRef.current?.setModalVisible(false)}
text="Cancel"
/>
<MainButton
style={styles.insertBtn}
@ -277,13 +270,13 @@ export const InsertLinkModal = forwardRef(
) : null}
</View>
</ScrollView>
{isLoading && <ActivityIndicator color={'$primaryBlue'} />}
{isLoading && <ActivityIndicator color="$primaryBlue" />}
</View>
</>
);
};
const _renderContent = (
<ScrollView style={styles.container} keyboardShouldPersistTaps={'handled'}>
<ScrollView style={styles.container} keyboardShouldPersistTaps="handled">
{_renderInputs()}
{_renderPreview()}
{_renderFloatingPanel()}
@ -291,7 +284,6 @@ export const InsertLinkModal = forwardRef(
);
return (
<Modal
isOpen={visible}
handleOnModalClose={_handleOnCloseSheet}
@ -302,7 +294,6 @@ export const InsertLinkModal = forwardRef(
>
{_renderContent}
</Modal>
);
},
);

View File

@ -1,26 +1,31 @@
import { Keyboard, View, ViewStyle } from 'react-native'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { IconButton, UploadsGalleryModal } from '../..'
import { FlatList, HandlerStateChangeEvent, PanGestureHandler, PanGestureHandlerEventPayload } from 'react-native-gesture-handler';
import { Keyboard, View, ViewStyle } from 'react-native';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
FlatList,
HandlerStateChangeEvent,
PanGestureHandler,
PanGestureHandlerEventPayload,
} from 'react-native-gesture-handler';
import { getBottomSpace } from 'react-native-iphone-x-helper';
import Animated, { Easing, Extrapolate } from 'react-native-reanimated';
import { IconButton, UploadsGalleryModal } from '../..';
import styles from '../styles/editorToolbarStyles';
import { useAppSelector } from '../../../hooks';
import { MediaInsertData } from '../../uploadsGalleryModal/container/uploadsGalleryModal';
import Formats from './formats/formats';
import { getBottomSpace } from 'react-native-iphone-x-helper';
import Animated, { Easing, Extrapolate } from 'react-native-reanimated';
type Props = {
insertedMediaUrls: string[],
paramFiles: any[]
isEditing: boolean,
isPreviewActive: boolean,
insertedMediaUrls: string[];
paramFiles: any[];
isEditing: boolean;
isPreviewActive: boolean;
setIsUploading: (isUploading: boolean) => void;
handleMediaInsert: (data: MediaInsertData[]) => void;
handleOnAddLinkPress: () => void;
handleOnClearPress: () => void;
handleOnMarkupButtonPress: (item) => void;
handleShowSnippets: () => void;
}
};
export const EditorToolbar = ({
insertedMediaUrls,
@ -32,11 +37,9 @@ export const EditorToolbar = ({
handleOnAddLinkPress,
handleOnClearPress,
handleOnMarkupButtonPress,
handleShowSnippets
handleShowSnippets,
}: Props) => {
const currentAccount = useAppSelector(state => state.account.currentAccount)
const currentAccount = useAppSelector((state) => state.account.currentAccount);
const uploadsGalleryModalRef = useRef<typeof UploadsGalleryModal>(null);
const translateY = useRef(new Animated.Value(200));
const shouldHideExtension = useRef(false);
@ -47,18 +50,12 @@ export const EditorToolbar = ({
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => {
const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
setKeyboardVisible(true); // or some other action
}
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => {
});
const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
setKeyboardVisible(false); // or some other action
}
);
});
return () => {
keyboardDidHideListener.remove();
@ -74,7 +71,9 @@ export const EditorToolbar = ({
iconStyle={styles.icon}
iconType={item.iconType}
name={item.icon}
onPress={() => { handleOnMarkupButtonPress && handleOnMarkupButtonPress(item) }}
onPress={() => {
handleOnMarkupButtonPress && handleOnMarkupButtonPress(item);
}}
/>
</View>
);
@ -86,8 +85,7 @@ export const EditorToolbar = ({
uploadsGalleryModalRef.current.toggleModal(true);
_revealExtension();
}
}
};
//handles extension closing
const _onGestureEvent = Animated.event(
@ -99,30 +97,38 @@ export const EditorToolbar = ({
},
],
{
useNativeDriver: false
}
useNativeDriver: false,
},
);
const consY = useMemo(() => translateY.current.interpolate({
const consY = useMemo(
() =>
translateY.current.interpolate({
inputRange: [0, 500],
outputRange: [0, 500],
extrapolate: Extrapolate.CLAMP
}), [translateY.current]);
extrapolate: Extrapolate.CLAMP,
}),
[translateY.current],
);
const _animatedStyle = {
transform: [
{
translateY: consY,
},
]
}
],
};
const _onPanHandlerStateChange = (e: HandlerStateChangeEvent<PanGestureHandlerEventPayload>) => {
console.log("handler state change", e.nativeEvent.velocityY, e.nativeEvent.velocityY > 300, e.nativeEvent.translationY);
shouldHideExtension.current = e.nativeEvent.velocityY > 300 || e.nativeEvent.translationY > (extensionHeight.current / 2);
}
console.log(
'handler state change',
e.nativeEvent.velocityY,
e.nativeEvent.velocityY > 300,
e.nativeEvent.translationY,
);
shouldHideExtension.current =
e.nativeEvent.velocityY > 300 || e.nativeEvent.translationY > extensionHeight.current / 2;
};
const _revealExtension = () => {
if (!isExtensionVisible) {
@ -135,9 +141,8 @@ export const EditorToolbar = ({
duration: 200,
toValue: 0,
easing: Easing.inOut(Easing.ease),
}).start()
}
}).start();
};
const _hideExtension = () => {
Animated.timing(translateY.current, {
@ -151,29 +156,31 @@ export const EditorToolbar = ({
uploadsGalleryModalRef.current.toggleModal(false);
}
});
}
};
const _onPanEnded = () => {
if (shouldHideExtension.current) {
_hideExtension()
_hideExtension();
} else {
_revealExtension();
}
}
};
const _renderExtension = () => {
return (
<PanGestureHandler onGestureEvent={_onGestureEvent}
<PanGestureHandler
onGestureEvent={_onGestureEvent}
onHandlerStateChange={_onPanHandlerStateChange}
onEnded={_onPanEnded}>
onEnded={_onPanEnded}
>
<Animated.View style={_animatedStyle}>
<View onLayout={(e) => {
<View
onLayout={(e) => {
extensionHeight.current = e.nativeEvent.layout.height;
console.log('extension height', extensionHeight.current)
}} style={styles.dropShadow}>
console.log('extension height', extensionHeight.current);
}}
style={styles.dropShadow}
>
{isExtensionVisible && <View style={styles.indicator} />}
<UploadsGalleryModal
ref={uploadsGalleryModalRef}
@ -184,23 +191,25 @@ export const EditorToolbar = ({
username={currentAccount.username}
hideToolbarExtension={_hideExtension}
handleMediaInsert={handleMediaInsert}
setIsUploading={setIsUploading} />
setIsUploading={setIsUploading}
/>
</View>
</Animated.View>
</PanGestureHandler>
)
}
);
};
const _containerStyle: ViewStyle = isExtensionVisible ? styles.container : styles.shadowedContainer;
const _containerStyle: ViewStyle = isExtensionVisible
? styles.container
: styles.shadowedContainer;
const _buttonsContainerStyle: ViewStyle = {
...styles.buttonsContainer,
borderTopWidth: isExtensionVisible ? 1 : 0,
paddingBottom: !isKeyboardVisible ? getBottomSpace() : 0
}
paddingBottom: !isKeyboardVisible ? getBottomSpace() : 0,
};
return (
<View style={_containerStyle}>
{_renderExtension()}
{!isPreviewActive && (
@ -220,10 +229,14 @@ export const EditorToolbar = ({
iconStyle={styles.icon}
iconType="FontAwesome"
name="link"
onPress={() => { handleOnAddLinkPress && handleOnAddLinkPress() }}
onPress={() => {
handleOnAddLinkPress && handleOnAddLinkPress();
}}
/>
<IconButton
onPress={() => { handleShowSnippets && handleShowSnippets() }}
onPress={() => {
handleShowSnippets && handleShowSnippets();
}}
style={styles.rightIcons}
size={20}
iconStyle={styles.icon}
@ -240,7 +253,9 @@ export const EditorToolbar = ({
/>
<View style={styles.clearButtonWrapper}>
<IconButton
onPress={() => { handleOnClearPress && handleOnClearPress() }}
onPress={() => {
handleOnClearPress && handleOnClearPress();
}}
size={20}
iconStyle={styles.clearIcon}
iconType="FontAwesome"
@ -251,9 +266,6 @@ export const EditorToolbar = ({
</View>
</View>
)}
</View>
)
}
);
};

View File

@ -1,42 +1,42 @@
import { MediaInsertData, MediaInsertStatus } from '../../../uploadsGalleryModal/container/uploadsGalleryModal';
import {
MediaInsertData,
MediaInsertStatus,
} from '../../../uploadsGalleryModal/container/uploadsGalleryModal';
import { replaceBetween } from './utils';
interface Selection {
start: number,
end: number
start: number;
end: number;
}
interface Args {
text: string;
selection: Selection;
setTextAndSelection: ({ selection: Selection, text: string }) => void,
items: MediaInsertData[]
setTextAndSelection: ({ selection: Selection, text: string }) => void;
items: MediaInsertData[];
}
export default async ({ text, selection, setTextAndSelection, items }: Args) => {
//TODO: check if placeholder already present in text body
// check if cursor position is after or before media position
// replace placeholder with url or failure message
// calclulate change of cursor position
const imagePrefix = '!';
const placeholderPrefix = 'Uploading... '
const placeholderPrefix = 'Uploading... ';
let newText = text;
let newSelection = selection;
const _insertFormatedString = (text, value) => {
const formatedText = `\n${imagePrefix}[${text}](${value})\n`;
newText = replaceBetween(newText, newSelection, formatedText);
const newIndex = newText && newText.indexOf(value, newSelection.start) + value.length + 2;
newSelection = {
start: newIndex,
end: newIndex
}
}
end: newIndex,
};
};
const _replaceFormatedString = (placeholder: string, url: string) => {
const replaceStr = `(${placeholder})`;
@ -45,38 +45,34 @@ export default async ({ text, selection, setTextAndSelection, items }: Args) =>
newText = newText.replace(replaceStr, `(${url})`);
if (newSelection.start >= endingIndex) {
const lengthDiff = url.length - placeholder.length
const lengthDiff = url.length - placeholder.length;
newSelection = {
start: newSelection.start + lengthDiff,
end: newSelection.end + lengthDiff
end: newSelection.end + lengthDiff,
};
}
}
}
};
const _removeFormatedString = (placeholder) => {
const formatedText = `${imagePrefix}[](${placeholder})`
const formatedText = `${imagePrefix}[](${placeholder})`;
const formatedTextIndex = newText.indexOf(formatedText);
newText = newText.replace(formatedText, '')
newText = newText.replace(formatedText, '');
if (newSelection.start > formatedTextIndex) {
newSelection = {
start: newSelection.start - formatedText.length,
end: newSelection.end - formatedText.length
}
}
end: newSelection.end - formatedText.length,
};
}
};
items.forEach(item => {
items.forEach((item) => {
const _placeholder = item.filename && `${placeholderPrefix}${item.filename}`;
switch (item.status) {
case MediaInsertStatus.UPLOADING: //means only filename is available
if (!_placeholder) return;
_insertFormatedString(item.text, _placeholder)
_insertFormatedString(item.text, _placeholder);
break;
case MediaInsertStatus.READY: //means url is ready but filename may be available
@ -100,9 +96,7 @@ export default async ({ text, selection, setTextAndSelection, items }: Args) =>
}
break;
}
});
setTextAndSelection({ text: newText, selection: newSelection });
};

View File

@ -1,6 +1,6 @@
import {replaceBetween } from './utils';
import { replaceBetween } from './utils';
export default async ({ text, selection, setTextAndSelection, snippetText}) => {
export default async ({ text, selection, setTextAndSelection, snippetText }) => {
const newText = replaceBetween(text, selection, `${snippetText}`);
const newSelection = {
start: selection.start,

View File

@ -1,10 +1,14 @@
import { extractWordAtIndex } from '../../../../utils/editor';
import {replaceBetween } from './utils';
import { replaceBetween } from './utils';
export default async ({ text, selection, setTextAndSelection, username}) => {
export default async ({ text, selection, setTextAndSelection, username }) => {
const _word = extractWordAtIndex(text, selection.start);
const _insertAt = text.indexOf(_word, selection.start - _word.length);
const _text = replaceBetween(text, {start:_insertAt, end:_insertAt + _word.length}, `@${username} `)
const _text = replaceBetween(
text,
{ start: _insertAt, end: _insertAt + _word.length },
`@${username} `,
);
const _newPos = _insertAt + username.length + 2;
const _selection = { start: _newPos, end: _newPos };
setTextAndSelection({ selection: _selection, text: _text });

View File

@ -1,82 +1,93 @@
import React, {useState, useEffect, useCallback} from 'react';
import { View, FlatList, Text, TouchableOpacity } from "react-native"
import React, { useState, useEffect, useCallback } from 'react';
import { View, FlatList, Text, TouchableOpacity } from 'react-native';
import {debounce} from 'lodash';
import { UserAvatar } from '../..';
import { lookupAccounts } from '../../../providers/hive/dhive';
import { extractWordAtIndex } from '../../../utils/editor';
import styles from '../styles/markdownEditorStyles';
import {debounce} from 'lodash';
interface Props {
text:string,
selection:{
start:number,
end:number
}
onApplyUsername:(username:string)=>void;
text: string;
selection: {
start: number;
end: number;
};
onApplyUsername: (username: string) => void;
}
export const UsernameAutofillBar = ({text, selection, onApplyUsername}:Props) => {
const [searchedUsers, setSearchedUsers] = useState([])
export const UsernameAutofillBar = ({ text, selection, onApplyUsername }: Props) => {
const [searchedUsers, setSearchedUsers] = useState([]);
const [query, setQuery] = useState('');
useEffect(() => {
if (selection.start === selection.end && text) {
_processTextForSearch(text, selection.start);
}
}, [text, selection])
}, [text, selection]);
const _processTextForSearch = useCallback(debounce((text:string, index:number) => {
const _processTextForSearch = useCallback(
debounce(
(text: string, index: number) => {
const word = extractWordAtIndex(text, index);
console.log('selection word is: ', word);
if (word.startsWith('@') && word.length > 1) {
_handleUserSearch(word.substring(1));
} else {
setSearchedUsers([]);
setQuery('')
setQuery('');
_handleUserSearch.cancel();
}
}, 300, {leading:true}),[]);
},
300,
{ leading: true },
),
[],
);
const _handleUserSearch = useCallback(debounce(async (username) => {
if(query !== username){
const _handleUserSearch = useCallback(
debounce(
async (username) => {
if (query !== username) {
let users = [];
if (username) {
setQuery(username)
setQuery(username);
users = await lookupAccounts(username);
console.log('result users for', username, users);
}
setSearchedUsers(users);
}
}, 200, {leading:true}), []);
200,
{ leading: true },
),
);
const _onUserSelect = (username) => {
onApplyUsername(username)
onApplyUsername(username);
setSearchedUsers([]);
setQuery('')
setQuery('');
};
if(!searchedUsers || searchedUsers.length === 0 || query === ''){
if (!searchedUsers || searchedUsers.length === 0 || query === '') {
return null;
}
const _renderItem = ({item}:{item:string}) => {
const _renderItem = ({ item }: { item: string }) => {
const username = item;
return (
<TouchableOpacity onPress={()=>{_onUserSelect(username)}}>
<TouchableOpacity
onPress={() => {
_onUserSelect(username);
}}
>
<View style={styles.userBubble}>
<UserAvatar username={username}/>
<UserAvatar username={username} />
<Text style={styles.userBubbleText}>{username}</Text>
</View>
</TouchableOpacity>
)
}
);
};
return (
<View style={styles.searchAccountsContainer}>
@ -86,8 +97,8 @@ export const UsernameAutofillBar = ({text, selection, onApplyUsername}:Props) =>
keyboardShouldPersistTaps="always"
showsHorizontalScrollIndicator={false}
renderItem={_renderItem}
keyExtractor={(item)=>`searched-user-${item}`}
keyExtractor={(item) => `searched-user-${item}`}
/>
</View>
)
}
);
};

View File

@ -10,11 +10,11 @@ const _dropShadow = {
},
backgroundColor: '$primaryBackgroundColor',
borderColor: '$primaryLightBackground',
borderTopWidth : Platform.select({
borderTopWidth: Platform.select({
android: 1,
ios: 0
})
}
ios: 0,
}),
};
export default EStyleSheet.create({
container: {
@ -22,13 +22,13 @@ export default EStyleSheet.create({
elevation: 3,
backgroundColor: '$primaryBackgroundColor',
},
shadowedContainer:{
shadowedContainer: {
elevation: 3,
width: '$deviceWidth',
..._dropShadow
..._dropShadow,
},
dropShadow: {
..._dropShadow
..._dropShadow,
},
buttonsContainer: {
justifyContent: 'space-between',
@ -36,7 +36,7 @@ export default EStyleSheet.create({
width: '$deviceWidth',
backgroundColor: '$primaryBackgroundColor',
borderColor: '$primaryLightBackground',
paddingBottom: getBottomSpace()
paddingBottom: getBottomSpace(),
},
clearIcon: {
color: '$primaryLightGray',
@ -75,8 +75,6 @@ export default EStyleSheet.create({
backgroundColor: '$primaryLightBackground',
borderRadius: 8,
margin: 8,
alignSelf: 'center'
}
alignSelf: 'center',
},
});

View File

@ -74,11 +74,11 @@ const MarkdownEditorView = ({
autoFocusText,
sharedSnippetText,
onLoadDraftPress,
setIsUploading
setIsUploading,
}) => {
const dispatch = useDispatch();
const isDarkTheme = useAppSelector(state => state.application.isDarkTheme);
const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme);
const [editable, setEditable] = useState(true);
const [bodyInputHeight, setBodyInputHeight] = useState(MIN_BODY_INPUT_HEIGHT);
@ -87,7 +87,6 @@ const MarkdownEditorView = ({
const [isEditing, setIsEditing] = useState(false);
const [insertedMediaUrls, setInsertedMediaUrls] = useState([]);
const inputRef = useRef(null);
const clearRef = useRef(null);
const insertLinkModalRef = useRef(null);
@ -164,7 +163,6 @@ const MarkdownEditorView = ({
}
}, [isLoading]);
useEffect(() => {
bodyText = draftBody;
}, [draftBody]);
@ -178,8 +176,6 @@ const MarkdownEditorView = ({
}
}, [autoFocusText]);
const changeUser = async () => {
dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet));
};
@ -193,23 +189,26 @@ const MarkdownEditorView = ({
});
};
const _debouncedOnTextChange = useCallback(debounce(() => {
console.log("setting is editing to", false)
setIsEditing(false)
const urls = extractImageUrls({ body: bodyText })
const _debouncedOnTextChange = useCallback(
debounce(() => {
console.log('setting is editing to', false);
setIsEditing(false);
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) => {
const _changeText = useCallback(
(input) => {
bodyText = input;
if (!isEditing) {
console.log('force setting is editing to true', true)
setIsEditing(true)
console.log('force setting is editing to true', true);
setIsEditing(true);
}
_debouncedOnTextChange();
@ -218,8 +217,9 @@ const MarkdownEditorView = ({
if (onChange) {
onChange(input);
}
}, [isEditing]);
},
[isEditing],
);
const _handleOnSelectionChange = async (event) => {
bodySelection = event.nativeEvent.selection;
@ -233,11 +233,11 @@ const MarkdownEditorView = ({
});
const _updateSelection = () => {
bodySelection = _selection
bodySelection = _selection;
inputRef?.current?.setNativeProps({
selection: _selection,
});
}
};
// Workaround for iOS selection update issue
if (Platform.OS === 'ios') {
@ -245,7 +245,7 @@ const MarkdownEditorView = ({
_updateSelection();
}, 100);
} else {
_updateSelection()
_updateSelection();
}
if (isSnippetsOpen) {
@ -275,8 +275,6 @@ const MarkdownEditorView = ({
setIsSnippetsOpen(false);
};
const _handleMediaInsert = (mediaArray: MediaInsertData[]) => {
if (mediaArray.length) {
applyMediaLink({
@ -288,9 +286,6 @@ const MarkdownEditorView = ({
}
};
const _handleOnAddLinkPress = () => {
insertLinkModalRef.current?.showModal({
selectedText: bodyText.slice(bodySelection.start, bodySelection.end),
@ -314,7 +309,6 @@ const MarkdownEditorView = ({
insertLinkModalRef.current?.hideModal();
};
const _renderFloatingDraftButton = () => {
if (showDraftLoadButton) {
const _onPress = () => {
@ -354,7 +348,7 @@ const MarkdownEditorView = ({
_setTextAndSelection({ text: '', selection: { start: 0, end: 0 } });
}
};
const _renderEditor = (editorScrollEnabled:boolean) => (
const _renderEditor = (editorScrollEnabled: boolean) => (
<>
{isReply && !isEdit && <SummaryArea summary={headerText} />}
{!isReply && (
@ -421,14 +415,20 @@ const MarkdownEditorView = ({
</>
);
const _editorWithScroll = <ScrollView style={styles.container}>{_renderEditor(false)}</ScrollView>;
const _editorWithScroll = (
<ScrollView style={styles.container}>{_renderEditor(false)}</ScrollView>
);
const _editorWithoutScroll = <View style={styles.container}>{_renderEditor(true)}</View>;
const _renderContent = () => {
const _innerContent = (
<>
{isAndroidOreo() ? _editorWithoutScroll : _editorWithScroll}
<UsernameAutofillBar text={bodyText} selection={bodySelection} onApplyUsername={_onApplyUsername} />
<UsernameAutofillBar
text={bodyText}
selection={bodySelection}
onApplyUsername={_onApplyUsername}
/>
{_renderFloatingDraftButton()}
<EditorToolbar
@ -445,11 +445,10 @@ const MarkdownEditorView = ({
text: bodyText,
selection: bodySelection,
setTextAndSelection: _setTextAndSelection,
item
})
item,
});
}}
/>
</>
);
@ -480,8 +479,6 @@ const MarkdownEditorView = ({
<SnippetsModal handleOnSelect={_handleOnSnippetReceived} />
</Modal>
<InsertLinkModal
ref={insertLinkModalRef}
handleOnInsertLink={_handleInsertLink}

View File

@ -1,12 +1,10 @@
import {ViewStyle } from 'react-native';
import { ViewStyle } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
justifyContent:"flex-end",
justifyContent: 'flex-end',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
} as ViewStyle,
})
});

View File

@ -1,17 +1,16 @@
import React, { useEffect, useRef, useState } from 'react';
import { View as AnimatedView } from 'react-native-animatable'
import { View as AnimatedView } from 'react-native-animatable';
import { Portal } from 'react-native-portalize';
import styles from '../children/inputSupportModal.styles';
import { KeyboardAvoidingView, Platform, View } from 'react-native';
import styles from '../children/inputSupportModal.styles';
export interface InputSupportModalProps {
visible: boolean;
onClose: () => void;
children?: any
children?: any;
}
export const InputSupportModal = ({ children, visible, onClose }: InputSupportModalProps, ref) => {
const container = useRef<AnimatedView>(null);
const innerContainer = useRef<AnimatedView>(null);
@ -20,48 +19,38 @@ export const InputSupportModal = ({ children, visible, onClose }: InputSupportMo
useEffect(() => {
if (visible) {
setShowModal(true);
}
else if (!visible && container.current && innerContainer.current) {
innerContainer.current.slideOutDown(1000)
} else if (!visible && container.current && innerContainer.current) {
innerContainer.current.slideOutDown(1000);
setTimeout(async () => {
await container.current?.fadeOut(200)
await container.current?.fadeOut(200);
setShowModal(false);
}, 300)
}, 300);
}
}, [visible])
}, [visible]);
return showModal && (
return (
showModal && (
<Portal>
<AnimatedView
ref={container}
animation='fadeIn'
duration={300}
style={styles.container} >
<AnimatedView ref={container} animation="fadeIn" duration={300} style={styles.container}>
<AnimatedView
ref={innerContainer}
style={{ flex: 1 }}
animation='slideInUp'
duration={300}>
animation="slideInUp"
duration={300}
>
<View style={{ flex: 1 }} onTouchEnd={onClose} />
<View
style={{ flex: 1 }}
onTouchEnd={onClose} />
{
Platform.select({
{Platform.select({
ios: (
<KeyboardAvoidingView behavior="padding" style={{}}>
{children}
</KeyboardAvoidingView>
),
android: <View>{children}</View>,
})
}
})}
</AnimatedView>
</AnimatedView>
</Portal>
)
);
};

View File

@ -290,7 +290,7 @@ class PostDropdownContainer extends PureComponent {
buttons: [
{
text: intl.formatMessage({ id: 'alert.cancel' }),
onPress: () => { },
onPress: () => {},
},
{
text: intl.formatMessage({ id: 'alert.confirm' }),
@ -337,9 +337,8 @@ class PostDropdownContainer extends PureComponent {
isLoggedIn,
pinCode,
navigation,
userActivityMutation
} = this
.props as any;
userActivityMutation,
} = this.props as any;
if (!isLoggedIn) {
showLoginAlert({ navigation, intl });
return;
@ -349,9 +348,9 @@ class PostDropdownContainer extends PureComponent {
.then((response) => {
//track user activity points ty=130
userActivityMutation.mutate({
pointsTy:PointActivityIds.REBLOG,
transactionId:response.id
})
pointsTy: PointActivityIds.REBLOG,
transactionId: response.id,
});
dispatch(
toastNotification(
@ -517,9 +516,11 @@ const mapStateToProps = (state) => ({
});
const mapQueriesToProps = () => ({
userActivityMutation: useUserActivityMutation()
})
userActivityMutation: useUserActivityMutation(),
});
export default withNavigation(connect(mapStateToProps)(injectIntl((props) => (
<PostDropdownContainer {...props} {...mapQueriesToProps()} />)))
export default withNavigation(
connect(mapStateToProps)(
injectIntl((props) => <PostDropdownContainer {...props} {...mapQueriesToProps()} />),
),
);

View File

@ -1,4 +1,4 @@
import React, { Fragment, useState, useRef } from 'react';
import React, { Fragment, useState, useRef, useCallback } from 'react';
import { Linking, Modal, PermissionsAndroid, Platform, View } from 'react-native';
import { useIntl } from 'react-intl';
import CameraRoll from '@react-native-community/cameraroll';
@ -8,6 +8,7 @@ import ActionsSheetView from 'react-native-actions-sheet';
// import AutoHeightWebView from 'react-native-autoheight-webview';
import EStyleSheet from 'react-native-extended-stylesheet';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import { navigate } from '../../../../navigation/service';
// Constants
@ -21,8 +22,7 @@ import styles from './commentBodyStyles';
// Services and Actions
import { writeToClipboard } from '../../../../utils/clipboard';
import { toastNotification } from '../../../../redux/actions/uiAction';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import { useCallback } from 'react';
import { OptionsModal } from '../../../atoms';
import { useAppDispatch } from '../../../../hooks';
import { isCommunity } from '../../../../utils/communityValidation';
@ -39,10 +39,9 @@ const CommentBody = ({
created,
commentDepth,
reputation = 25,
isMuted
isMuted,
}) => {
const _contentWidth = WIDTH - (40 + 28 + (commentDepth > 2 ? 44 : 0))
const _contentWidth = WIDTH - (40 + 28 + (commentDepth > 2 ? 44 : 0));
const dispatch = useAppDispatch();
@ -52,7 +51,7 @@ const CommentBody = ({
const [selectedLink, setSelectedLink] = useState(null);
const [revealComment, setRevealComment] = useState(reputation > 0 && !isMuted);
const [videoUrl, setVideoUrl] = useState(null);
const [youtubeVideoId, setYoutubeVideoId] = useState(null)
const [youtubeVideoId, setYoutubeVideoId] = useState(null);
const [videoStartTime, setVideoStartTime] = useState(0);
const intl = useIntl();
@ -60,18 +59,16 @@ const CommentBody = ({
const actionLink = useRef(null);
const youtubePlayerRef = useRef(null);
const _onLongPressStateChange = ({nativeEvent}) => {
if(nativeEvent.state === State.ACTIVE){
const _onLongPressStateChange = ({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
handleOnLongPress();
}
}
};
const _showLowComment = () => {
setRevealComment(true);
};
const handleImagePress = (ind) => {
if (ind === 1) {
//open gallery mode
@ -130,10 +127,9 @@ const CommentBody = ({
}
setSelectedLink(null);
};
const _handleTagPress = (tag:string, filter:string = GLOBAL_POST_FILTERS_VALUE[0]) => {
const _handleTagPress = (tag: string, filter: string = GLOBAL_POST_FILTERS_VALUE[0]) => {
if (tag) {
const routeName = isCommunity(tag) ? ROUTES.SCREENS.COMMUNITY : ROUTES.SCREENS.TAG_RESULT;
const key = `${filter}/${tag}`;
@ -148,21 +144,21 @@ const CommentBody = ({
}
};
const _handleSetSelectedLink = (link:string) => {
setSelectedLink(link)
const _handleSetSelectedLink = (link: string) => {
setSelectedLink(link);
actionLink.current.show();
}
};
const _handleSetSelectedImage = (imageLink:string, postImgUrls:string[]) => {
if(postImages.length !== postImgUrls.length){
const _handleSetSelectedImage = (imageLink: string, postImgUrls: string[]) => {
if (postImages.length !== postImgUrls.length) {
setPostImages(postImgUrls);
}
setSelectedImage(imageLink);
actionImage.current.show();
}
};
const _handleOnPostPress = (permlink, author) => {
if(handleOnPostPress){
if (handleOnPostPress) {
handleOnUserPress(permlink, author);
return;
}
@ -179,7 +175,7 @@ const CommentBody = ({
};
const _handleOnUserPress = (username) => {
if(handleOnUserPress){
if (handleOnUserPress) {
handleOnUserPress(username);
return;
}
@ -279,17 +275,16 @@ const CommentBody = ({
const _handleVideoPress = (embedUrl) => {
if (embedUrl && youtubePlayerRef.current) {
setVideoUrl(embedUrl);
setVideoStartTime(0)
setVideoStartTime(0);
youtubePlayerRef.current.setModalVisible(true);
}
};
return (
<Fragment>
<Modal key={`mkey-${created.toString()}`} visible={isImageModalOpen} transparent={true}>
<ImageViewer
imageUrls={postImages.map((url)=>({url}))}
imageUrls={postImages.map((url) => ({ url }))}
enableSwipeDown
onCancel={() => setIsImageModalOpen(false)}
onClick={() => setIsImageModalOpen(false)}
@ -339,7 +334,6 @@ const CommentBody = ({
/>
</View>
</LongPressGestureHandler>
) : (
<TextButton
style={styles.revealButton}
@ -371,4 +365,3 @@ const CommentBody = ({
};
export default CommentBody;

View File

@ -1,21 +1,21 @@
import React, { memo, useMemo, useRef } from 'react';
import RenderHTML, { CustomRendererProps, Element, TNode } from 'react-native-render-html';
import { useHtmlIframeProps, iframeModel } from '@native-html/iframe-plugin';
import WebView from 'react-native-webview';
import { ScrollView } from 'react-native-gesture-handler';
import { prependChild, removeElement } from 'htmlparser2/node_modules/domutils';
import styles from './postHtmlRendererStyles';
import { LinkData, parseLinkData } from './linkDataParser';
import VideoThumb from './videoThumb';
import { AutoHeightImage } from '../autoHeightImage/autoHeightImage';
import WebView from 'react-native-webview';
import { VideoPlayer } from '..';
import { ScrollView } from 'react-native-gesture-handler';
import { prependChild, removeElement } from 'htmlparser2/node_modules/domutils';
interface PostHtmlRendererProps {
contentWidth: number;
body: string;
isComment?: boolean;
onLoaded?: () => void;
setSelectedImage: (imgUrl: string, postImageUrls:string[]) => void;
setSelectedImage: (imgUrl: string, postImageUrls: string[]) => void;
setSelectedLink: (url: string) => void;
handleOnPostPress: (permlink: string, authro: string) => void;
handleOnUserPress: (username: string) => void;
@ -38,7 +38,6 @@ export const PostHtmlRenderer = memo(
handleVideoPress,
handleYoutubePress,
}: PostHtmlRendererProps) => {
const postImgUrlsRef = useRef<string[]>([]);
//new renderer functions
@ -146,8 +145,8 @@ export const PostHtmlRenderer = memo(
if (element.tagName === 'img' && element.attribs.src) {
const imgUrl = element.attribs.src;
console.log('img element detected', imgUrl);
if(!postImgUrlsRef.current.includes(imgUrl)){
postImgUrlsRef.current.push(imgUrl)
if (!postImgUrlsRef.current.includes(imgUrl)) {
postImgUrlsRef.current.push(imgUrl);
}
}
@ -291,7 +290,6 @@ export const PostHtmlRenderer = memo(
);
};
// iframe renderer for rendering iframes in body
const _iframeRenderer = function IframeRenderer(props) {
const iframeProps = useHtmlIframeProps(props);

View File

@ -1,5 +1,5 @@
import { ImageStyle, Platform } from 'react-native';
import { ViewStyle, TextStyle } from 'react-native';
import { ImageStyle, Platform, ViewStyle, TextStyle } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
@ -13,82 +13,82 @@ export default EStyleSheet.create({
color: '$primaryBlack',
} as TextStyle,
div: {
width:'100%',
},
p:{
marginTop:6,
marginBottom:6,
flexDirection:'row',
alignItems:'center',
flexWrap:'wrap'
} as TextStyle,
h6:{
fontSize:14,
} as TextStyle,
pLi:{
marginTop:0,
marginBottom:0
} as TextStyle,
a:{
color: '$primaryBlue'
} as TextStyle,
img:{
width: '100%',
alignSelf:'center',
marginTop:4,
marginBottom:4,
backgroundColor:'red'
},
p: {
marginTop: 6,
marginBottom: 6,
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
} as TextStyle,
h6: {
fontSize: 14,
} as TextStyle,
pLi: {
marginTop: 0,
marginBottom: 0,
} as TextStyle,
a: {
color: '$primaryBlue',
} as TextStyle,
img: {
width: '100%',
alignSelf: 'center',
marginTop: 4,
marginBottom: 4,
backgroundColor: 'red',
} as ImageStyle,
th:{
th: {
flex: 1,
justifyContent: 'center',
alignItems:'center',
alignItems: 'center',
fontWeight: 'bold',
color: '$primaryBlack',
backgroundColor:'$darkIconColor',
backgroundColor: '$darkIconColor',
fontSize: 14,
padding: 10,
} as TextStyle,
tr:{
flexDirection:'row',
tr: {
flexDirection: 'row',
} as ViewStyle,
td:{
flex:1,
td: {
flex: 1,
borderWidth: 0.5,
padding:10,
padding: 10,
borderColor: '$tableBorderColor',
backgroundColor: '$tableTrColor',
alignItems:'center',
justifyContent:'center',
alignItems: 'center',
justifyContent: 'center',
} as ViewStyle,
table:{
table: {
width: '100%',
} as ViewStyle,
li:{
marginBottom:12
li: {
marginBottom: 12,
} as ViewStyle,
blockquote: {
borderLeftWidth: 5,
borderStyle:'solid',
marginLeft:5,
paddingLeft:5,
borderStyle: 'solid',
marginLeft: 5,
paddingLeft: 5,
borderColor: '$darkIconColor',
} as ViewStyle,
code:{
backgroundColor:'$darkIconColor',
fontFamily:'$editorFont',
code: {
backgroundColor: '$darkIconColor',
fontFamily: '$editorFont',
} as TextStyle,
textCenter: {
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
} as TextStyle,
phishy:{
color:'$primaryRed',
phishy: {
color: '$primaryRed',
} as TextStyle,
textJustify:{
textAlign: Platform.select({ios:'justify', android:'auto'}), //justify with selectable on android causes ends of text getting clipped,
letterSpacing:0
textJustify: {
textAlign: Platform.select({ ios: 'justify', android: 'auto' }), //justify with selectable on android causes ends of text getting clipped,
letterSpacing: 0,
} as TextStyle,
revealButton: {
backgroundColor: '$iconColor',
@ -103,20 +103,18 @@ export default EStyleSheet.create({
color: '$white',
fontSize: 14,
},
videoThumb:{
width:'100%',
alignItems:'center',
justifyContent:'center',
backgroundColor:'$darkIconColor'
videoThumb: {
width: '100%',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '$darkIconColor',
},
playButton:{
alignItems:'center',
justifyContent:'center',
playButton: {
alignItems: 'center',
justifyContent: 'center',
width: 60,
height: 60,
borderRadius: 30,
backgroundColor:'$primaryBlack'
} as ViewStyle
backgroundColor: '$primaryBlack',
} as ViewStyle,
});

View File

@ -70,8 +70,8 @@ const PostDisplayView = ({
if (isLoggedIn && get(currentAccount, 'name') && !isNewPost) {
//track user activity for view post
userActivityMutation.mutate({
pointsTy:PointActivityIds.VIEW_POST
})
pointsTy: PointActivityIds.VIEW_POST,
});
}
}, []);

View File

@ -1,28 +1,29 @@
import React, {forwardRef, memo, useRef, useImperativeHandle, useState, useEffect} from 'react'
import PostCard from '../../postCard';
import React, { forwardRef, memo, useRef, useImperativeHandle, useState, useEffect } from 'react';
import { get } from 'lodash';
import { FlatListProps, FlatList, RefreshControl, ActivityIndicator, View } from 'react-native';
import { useSelector } from 'react-redux';
import PostCard from '../../postCard';
import { ThemeContainer } from '../../../containers';
import styles from '../view/postsListStyles';
export interface PostsListRef {
scrollToTop:()=>void
scrollToTop: () => void;
}
interface postsListContainerProps extends FlatListProps<any> {
promotedPosts:Array<any>;
isFeedScreen:boolean;
onLoadPosts?:(shouldReset:boolean)=>void;
isLoading:boolean;
isRefreshing:boolean;
pageType:'main'|'profile'|'ownProfile'|'community';
showQuickReplyModal:(post:any)=>void;
promotedPosts: Array<any>;
isFeedScreen: boolean;
onLoadPosts?: (shouldReset: boolean) => void;
isLoading: boolean;
isRefreshing: boolean;
pageType: 'main' | 'profile' | 'ownProfile' | 'community';
showQuickReplyModal: (post: any) => void;
}
let _onEndReachedCalledDuringMomentum = true;
const postsListContainer = ({
const postsListContainer = (
{
promotedPosts,
isFeedScreen,
onLoadPosts,
@ -31,7 +32,9 @@ const postsListContainer = ({
pageType,
showQuickReplyModal,
...props
}:postsListContainerProps, ref) => {
}: postsListContainerProps,
ref,
) => {
const flatListRef = useRef(null);
@ -39,16 +42,12 @@ const postsListContainer = ({
const isHideImages = useSelector((state) => state.application.hidePostsThumbnails);
const posts = useSelector((state) => {
return isFeedScreen
? state.posts.feedPosts
: state.posts.otherPosts
return isFeedScreen ? state.posts.feedPosts : state.posts.otherPosts;
});
const mutes = useSelector((state) => state.account.currentAccount.mutes);
const scrollPosition = useSelector((state) => {
return isFeedScreen
? state.posts.feedScrollPosition
: state.posts.otherScrollPosition
return isFeedScreen ? state.posts.feedScrollPosition : state.posts.otherScrollPosition;
});
useImperativeHandle(ref, () => ({
@ -58,38 +57,36 @@ const postsListContainer = ({
}));
useEffect(() => {
console.log("Scroll Position: ", scrollPosition)
if(posts && posts.length == 0){
console.log('Scroll Position: ', scrollPosition);
if (posts && posts.length == 0) {
flatListRef.current?.scrollToOffset({
offset: 0,
animated: false
animated: false,
});
}
}, [posts])
}, [posts]);
useEffect(() => {
console.log("Scroll Position: ", scrollPosition)
console.log('Scroll Position: ', scrollPosition);
flatListRef.current?.scrollToOffset({
offset: (posts && posts.length == 0) ? 0 : scrollPosition,
animated: false
offset: posts && posts.length == 0 ? 0 : scrollPosition,
animated: false,
});
}, [scrollPosition])
}, [scrollPosition]);
const _setImageHeightInMap = (mapKey:string, height:number) => {
if(mapKey && height){
const _setImageHeightInMap = (mapKey: string, height: number) => {
if (mapKey && height) {
setImageHeights(imageHeights.set(mapKey, height));
}
}
};
const _renderFooter = () => {
if (isLoading && !isRefreshing) {
return (
<View style={styles.flatlistFooter}>
<ActivityIndicator animating size="large" color={'#2e3d51'} />
<ActivityIndicator animating size="large" color="#2e3d51" />
</View>
);
}
@ -104,8 +101,7 @@ const postsListContainer = ({
}
};
const _renderItem = ({ item, index }:{item:any, index:number}) => {
const _renderItem = ({ item, index }: { item: any; index: number }) => {
const e = [] as any;
if (index % 3 === 0) {
@ -114,7 +110,11 @@ const postsListContainer = ({
const p = promotedPosts[ix];
let isMuted = mutes && mutes.indexOf(p.author) > -1;
if (!isMuted && get(p, 'author', null) && posts && posts.filter((x) => x.permlink === p.permlink).length <= 0) {
if (
!isMuted &&
get(p, 'author', null) &&
posts &&
posts.filter((x) => x.permlink === p.permlink).length <= 0
//get image height from cache if available
const localId = p.author + p.permlink;
@ -127,10 +127,10 @@ const postsListContainer = ({
isHideImage={isHideImages}
imageHeight={imgHeight}
pageType={pageType}
setImageHeight = {_setImageHeightInMap}
setImageHeight={_setImageHeightInMap}
showQuickReplyModal={showQuickReplyModal}
mutes={mutes}
/>
/>,
);
}
}
@ -140,7 +140,7 @@ const postsListContainer = ({
if (!isMuted && get(item, 'author', null)) {
//get image height from cache if available
const localId = item.author + item.permlink;
const imgHeight = imageHeights.get(localId)
const imgHeight = imageHeights.get(localId);
e.push(
<PostCard
@ -148,7 +148,7 @@ const postsListContainer = ({
content={item}
isHideImage={isHideImages}
imageHeight={imgHeight}
setImageHeight = {_setImageHeightInMap}
setImageHeight={_setImageHeightInMap}
pageType={pageType}
showQuickReplyModal={showQuickReplyModal}
mutes={mutes}
@ -181,7 +181,11 @@ const postsListContainer = ({
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={()=>{if(onLoadPosts){onLoadPosts(true)}}}
onRefresh={() => {
if (onLoadPosts) {
onLoadPosts(true);
}
}}
progressBackgroundColor="#357CE6"
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
titleColor="#fff"
@ -192,8 +196,8 @@ const postsListContainer = ({
/>
)}
</ThemeContainer>
)
}
);
};
export default forwardRef(postsListContainer);

View File

@ -1,25 +1,30 @@
import React, {useEffect, useState} from 'react';
import React, { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import {ActivityIndicator, RefreshControl, View } from 'react-native';
import { ActivityIndicator, RefreshControl, View } from 'react-native';
import { unionBy } from 'lodash';
import { Comments, NoPost } from '../..';
import { useAppSelector } from '../../../hooks';
import { getAccountPosts } from '../../../providers/hive/dhive';
import styles from '../profileStyles';
import {unionBy } from 'lodash';
interface CommentsTabContentProps {
username:string,
type:'comments'|'replies',
isOwnProfile:boolean,
selectedUser:any,
onScroll:()=>void,
username: string;
type: 'comments' | 'replies';
isOwnProfile: boolean;
selectedUser: any;
onScroll: () => void;
}
const CommentsTabContent = ({isOwnProfile, username, type, onScroll, selectedUser }: CommentsTabContentProps) => {
const CommentsTabContent = ({
isOwnProfile,
username,
type,
onScroll,
selectedUser,
}: CommentsTabContentProps) => {
const intl = useIntl();
const isHideImage = useAppSelector(state => state.application.hidePostsThumbnails);
const isHideImage = useAppSelector((state) => state.application.hidePostsThumbnails);
const [data, setData] = useState([]);
const [lastAuthor, setLastAuthor] = useState('');
@ -28,58 +33,55 @@ const CommentsTabContent = ({isOwnProfile, username, type, onScroll, selectedUse
const [refreshing, setRefreshing] = useState(false);
const [noMore, setNoMore] = useState(false);
useEffect(() => {
if(selectedUser){
if (selectedUser) {
_fetchData();
}
}, [selectedUser])
}, [selectedUser]);
const _fetchData = async ({refresh}:{refresh?:boolean} = {}) => {
if(loading || (!refresh && noMore)){
const _fetchData = async ({ refresh }: { refresh?: boolean } = {}) => {
if (loading || (!refresh && noMore)) {
return;
}
setLoading(true);
if(refresh){
if (refresh) {
setRefreshing(true);
}
const query:any = {
account:username,
const query: any = {
account: username,
start_author: refresh ? '' : lastAuthor,
start_permlink: refresh ? '' : lastPermlink,
limit:10,
observer:'',
sort:type
limit: 10,
observer: '',
sort: type,
};
const result = await getAccountPosts(query)
let _comments:any[] = refresh ? result : unionBy(data, result, 'permlink');
const result = await getAccountPosts(query);
let _comments: any[] = refresh ? result : unionBy(data, result, 'permlink');
if(Array.isArray(_comments)){
if (Array.isArray(_comments)) {
setData(_comments);
if(_comments.length > 0){
setLastAuthor(_comments[_comments.lastIndex].author)
setLastPermlink(_comments[_comments.lastIndex].permlink)
if (_comments.length > 0) {
setLastAuthor(_comments[_comments.lastIndex].author);
setLastPermlink(_comments[_comments.lastIndex].permlink);
}
if(result.length == 0){
if (result.length == 0) {
setNoMore(true);
}
}else{
} else {
setData([]);
setNoMore(true);
}
setLoading(false);
setRefreshing(false);
}
};
const _renderListEmpty = () => {
if(loading){
return null
if (loading) {
return null;
}
return (
<NoPost
@ -91,39 +93,34 @@ const CommentsTabContent = ({isOwnProfile, username, type, onScroll, selectedUse
id: 'profile.login_to_see',
})}
/>
)
}
);
};
const _renderListFooter = () => {
return (
<View style={styles.commentsListFooter}>
{loading && (
<ActivityIndicator size='large'/>
)}
</View>
)
}
<View style={styles.commentsListFooter}>{loading && <ActivityIndicator size="large" />}</View>
);
};
return (
<View key="profile.comments" style={styles.commentsTabBar}>
<Comments
comments={data}
fetchPost={()=>{}}
fetchPost={() => {}}
isOwnProfile={isOwnProfile}
isHideImage={isHideImage}
flatListProps={{
onEndReached:_fetchData,
onScroll:onScroll,
ListEmptyComponent:_renderListEmpty,
ListFooterComponent:_renderListFooter,
onEndReachedThreshold:1,
refreshControl:(
onEndReached: _fetchData,
onScroll: onScroll,
ListEmptyComponent: _renderListEmpty,
ListFooterComponent: _renderListFooter,
onEndReachedThreshold: 1,
refreshControl: (
<RefreshControl
refreshing={refreshing}
onRefresh={()=>_fetchData({refresh:true})}
onRefresh={() => _fetchData({ refresh: true })}
/>
)
),
}}
/>
</View>

View File

@ -6,6 +6,8 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view
import { injectIntl } from 'react-intl';
// Images
import FastImage from 'react-native-fast-image';
import EStyleSheet from 'react-native-extended-stylesheet';
import LIGHT_COVER_IMAGE from '../../assets/default_cover_image.png';
import DARK_COVER_IMAGE from '../../assets/dark_cover_image.png';
@ -18,22 +20,19 @@ import { getResizedImage } from '../../utils/image';
// Styles
import styles from './profileEditFormStyles';
import FastImage from 'react-native-fast-image';
import EStyleSheet from 'react-native-extended-stylesheet';
import { MainButton } from '../mainButton';
interface ProfileEditFormProps {
coverUrl:string;
formData:any;
handleOnItemChange:()=>void;
handleOnSubmit:()=>void;
intl:any,
isDarkTheme:boolean,
isLoading:boolean,
isUploading:boolean,
showImageUploadActions:boolean,
saveEnabled:boolean,
coverUrl: string;
formData: any;
handleOnItemChange: () => void;
handleOnSubmit: () => void;
intl: any;
isDarkTheme: boolean;
isLoading: boolean;
isUploading: boolean;
showImageUploadActions: boolean;
saveEnabled: boolean;
}
const ProfileEditFormView = ({
@ -48,17 +47,14 @@ const ProfileEditFormView = ({
showImageUploadActions,
saveEnabled,
...props
}:ProfileEditFormProps) => (
}: ProfileEditFormProps) => (
<View style={styles.container}>
<KeyboardAwareScrollView
enableAutoAutomaticScroll={Platform.OS === 'ios'}
contentContainerStyle={styles.contentContainer}
enableOnAndroid={true}
>
<TouchableOpacity style={styles.coverImgWrapper} onPress={showImageUploadActions}>
<FastImage
style={styles.coverImg}
source={
@ -69,16 +65,13 @@ const ProfileEditFormView = ({
: LIGHT_COVER_IMAGE
}
/>
{
isUploading && (
{isUploading && (
<ActivityIndicator
style={styles.activityIndicator}
color={EStyleSheet.value('$white')}
size='large'
size="large"
/>
)
}
)}
<IconButton
iconStyle={styles.addIcon}
@ -115,7 +108,7 @@ const ProfileEditFormView = ({
{saveEnabled && (
<AnimatedView style={styles.floatingContainer} animation="bounceInRight">
<MainButton
style={{ width: isLoading ? null : 120, marginBottom:24, alignSelf:'flex-end' }}
style={{ width: isLoading ? null : 120, marginBottom: 24, alignSelf: 'flex-end' }}
onPress={handleOnSubmit}
iconName="save"
iconType="MaterialIcons"
@ -125,8 +118,6 @@ const ProfileEditFormView = ({
/>
</AnimatedView>
)}
</View>
);

View File

@ -2,14 +2,14 @@ import React, { useEffect, useRef, useState } from 'react';
import { ActivityIndicator, Alert, PermissionsAndroid, Platform, Text, View } from 'react-native';
import ActionSheet from 'react-native-actions-sheet';
import EStyleSheet from 'react-native-extended-stylesheet';
import QRCodeScanner from 'react-native-qrcode-scanner';
import { useIntl } from 'react-intl';
import { check, request, PERMISSIONS, RESULTS, openSettings } from 'react-native-permissions';
import styles from './qrModalStyles';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { toggleQRModal } from '../../redux/actions/uiAction';
import QRCodeScanner from 'react-native-qrcode-scanner';
import { deepLinkParser } from '../../utils/deepLinkParser';
import { useIntl } from 'react-intl';
import { navigate } from '../../navigation/service';
import { check, request, PERMISSIONS, RESULTS, openSettings } from 'react-native-permissions';
import getWindowDimensions from '../../utils/getWindowDimensions';
export interface QRModalProps {}
@ -155,7 +155,7 @@ export const QRModal = ({}: QRModalProps) => {
/>
{isProcessing && (
<View style={styles.activityIndicatorContainer}>
<ActivityIndicator color={'white'} style={styles.activityIndicator} />
<ActivityIndicator color="white" style={styles.activityIndicator} />
</View>
)}
</View>

View File

@ -1,10 +1,19 @@
import React, { useEffect, useState, useCallback, useRef } from 'react';
import React, {
useEffect,
useState,
useCallback,
useRef,
useImperativeHandle,
forwardRef,
} from 'react';
import EStyleSheet from 'react-native-extended-stylesheet';
import styles from './quickReplyModalStyles';
import { View, Text, Alert, TouchableOpacity, Keyboard, Platform } from 'react-native';
import { useIntl } from 'react-intl';
import { IconButton, MainButton, TextButton, TextInput, UserAvatar } from '..';
import { useSelector, useDispatch } from 'react-redux';
import { get } from 'lodash';
import { postBodySummary } from '@ecency/render-helper';
import styles from './quickReplyModalStyles';
import { IconButton, MainButton, TextButton, TextInput, UserAvatar } from '..';
import { delay, generateReplyPermlink, generateRndStr } from '../../utils/editor';
import { postComment } from '../../providers/hive/dhive';
import { toastNotification } from '../../redux/actions/uiAction';
@ -14,13 +23,10 @@ import {
updateDraftCache,
} from '../../redux/actions/cacheActions';
import { default as ROUTES } from '../../constants/routeNames';
import { get } from 'lodash';
import { navigate } from '../../navigation/service';
import { postBodySummary } from '@ecency/render-helper';
import { Draft } from '../../redux/reducers/cacheReducer';
import { RootState } from '../../redux/store/store';
import { useImperativeHandle } from 'react';
import { forwardRef } from 'react';
import { PointActivityIds } from '../../providers/ecency/ecency.types';
import { useUserActivityMutation } from '../../providers/queries/pointQueries';
@ -30,10 +36,8 @@ export interface QuickReplyModalContentProps {
onClose: () => void;
}
export const QuickReplyModalContent = forwardRef(({
selectedPost,
onClose,
}: QuickReplyModalContentProps, ref) => {
export const QuickReplyModalContent = forwardRef(
({ selectedPost, onClose }: QuickReplyModalContentProps, ref) => {
const intl = useIntl();
const dispatch = useDispatch();
const userActivityMutation = useUserActivityMutation();
@ -47,24 +51,22 @@ export const QuickReplyModalContent = forwardRef(({
const [commentValue, setCommentValue] = useState('');
const [isSending, setIsSending] = useState(false);
const headerText =
selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS as any));
selectedPost &&
(selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS as any));
let parentAuthor = selectedPost ? selectedPost.author : '';
let parentPermlink = selectedPost ? selectedPost.permlink : '';
let draftId = `${currentAccount.name}/${parentAuthor}/${parentPermlink}`; //different draftId for each user acount
useImperativeHandle(ref, () => ({
handleSheetClose() {
_addQuickCommentIntoCache();
},
}));
// load quick comment value from cache
useEffect(() => {
let _value = ''
let _value = '';
if (drafts.has(draftId) && currentAccount.name === drafts.get(draftId).author) {
const quickComment: Draft = drafts.get(draftId);
_value = quickComment.body;
@ -72,34 +74,28 @@ export const QuickReplyModalContent = forwardRef(({
if (inputRef.current) {
inputRef.current.setNativeProps({
text: _value
})
setCommentValue(_value)
text: _value,
});
setCommentValue(_value);
}
}, [selectedPost]);
// add quick comment value into cache
const _addQuickCommentIntoCache = (value = commentValue) => {
const quickCommentDraftData: Draft = {
author: currentAccount.name,
body: value
body: value,
};
//add quick comment cache entry
dispatch(updateDraftCache(draftId, quickCommentDraftData));
};
// handle close press
const _handleClosePress = () => {
onClose()
onClose();
};
// navigate to post on summary press
const _handleOnSummaryPress = () => {
Keyboard.dismiss();
@ -113,10 +109,8 @@ export const QuickReplyModalContent = forwardRef(({
});
};
// handle submit reply
const _submitReply = async () => {
if (!commentValue) {
return;
}
@ -153,16 +147,16 @@ export const QuickReplyModalContent = forwardRef(({
)
.then((response) => {
userActivityMutation.mutate({
pointsTy:PointActivityIds.COMMENT,
transactionId:response.id
})
pointsTy: PointActivityIds.COMMENT,
transactionId: response.id,
});
setIsSending(false);
setCommentValue('');
if(inputRef.current){
if (inputRef.current) {
inputRef.current.setNativeProps({
text: ''
})
text: '',
});
}
dispatch(
@ -207,10 +201,8 @@ export const QuickReplyModalContent = forwardRef(({
error.message || JSON.stringify(error),
);
setIsSending(false);
_addQuickCommentIntoCache(); //add comment value into cache if there is error while posting comment
});
console.log('status : ', status);
}
@ -232,7 +224,6 @@ export const QuickReplyModalContent = forwardRef(({
}
};
//REMOVED FOR TESTING, CAN BE PUT BACK IF APP STILL CRASHES
// const _deboucedCacheUpdate = useCallback(debounce(_addQuickCommentIntoCache, 500), [])
@ -240,13 +231,10 @@ export const QuickReplyModalContent = forwardRef(({
setCommentValue(value);
//REMOVED FOR TESTING, CAN BE PUT BACK IF APP STILL CRASHES
// _deboucedCacheUpdate(value)
}
};
//VIEW_RENDERERS
const _renderSummary = () => (
<TouchableOpacity onPress={() => _handleOnSummaryPress()}>
<Text numberOfLines={2} style={styles.summaryStyle}>
@ -296,8 +284,6 @@ export const QuickReplyModalContent = forwardRef(({
</View>
);
const _renderContent = (
<View style={styles.modalContainer}>
{_renderSummary()}
@ -322,7 +308,8 @@ export const QuickReplyModalContent = forwardRef(({
{_renderReplyBtn()}
</View>
</View>
)
);
return _renderContent
});
return _renderContent;
},
);

View File

@ -3,18 +3,18 @@ import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
sheetContent: {
backgroundColor: '$primaryBackgroundColor',
marginTop:132,
marginTop: 132,
},
container: {
flex: 1,
justifyContent:"flex-end",
justifyContent: 'flex-end',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
modalContainer: {
margin:16,
borderRadius:16,
margin: 16,
borderRadius: 16,
backgroundColor: '$primaryBackgroundColor',
paddingTop: 16,
paddingBottom: 16,
@ -44,7 +44,7 @@ export default EStyleSheet.create({
},
textInput: {
color: '$primaryBlack',
paddingHorizontal:16,
paddingHorizontal: 16,
fontSize: 16,
flexGrow: 1,
fontWeight: '500',

View File

@ -5,34 +5,27 @@ import { useAppDispatch, useAppSelector } from '../../hooks';
import { hideReplyModal } from '../../redux/actions/uiAction';
const QuickReplyModal = () => {
const dispatch = useAppDispatch();
const replyModalVisible = useAppSelector((state) => state.ui.replyModalVisible);
const replyModalPost = useAppSelector(state => state.ui.replyModalPost)
const replyModalPost = useAppSelector((state) => state.ui.replyModalPost);
const modalContentRef = useRef(null);
const _onClose = () => {
if(modalContentRef.current){
if (modalContentRef.current) {
modalContentRef.current.handleSheetClose();
}
dispatch(hideReplyModal());
}
};
return (
<InputSupportModal
visible={replyModalVisible && !!replyModalPost}
onClose={_onClose}
>
<InputSupportModal visible={replyModalVisible && !!replyModalPost} onClose={_onClose}>
<QuickReplyModalContent
ref={modalContentRef}
selectedPost={replyModalPost}
onClose={_onClose}
/>
</InputSupportModal>
);
};

View File

@ -1,22 +1,27 @@
import React from 'react'
import { LineChart } from 'react-native-chart-kit'
import React from 'react';
import { LineChart } from 'react-native-chart-kit';
import EStyleSheet from 'react-native-extended-stylesheet';
interface CoinChartProps {
data:number[],
baseWidth:number,
chartHeight:number,
showLine:boolean,
showLabels?:boolean,
data: number[];
baseWidth: number;
chartHeight: number;
showLine: boolean;
showLabels?: boolean;
}
export const SimpleChart = ({data, baseWidth, chartHeight, showLine, showLabels = false}:CoinChartProps) => {
if(!data || !data.length){
export const SimpleChart = ({
data,
baseWidth,
chartHeight,
showLine,
showLabels = false,
}: CoinChartProps) => {
if (!data || !data.length) {
return null;
}
const _chartWidth = baseWidth + baseWidth/(data.length -1)
const _chartWidth = baseWidth + baseWidth / (data.length - 1);
const _chartBackgroundColor = EStyleSheet.value('$primaryLightBackground');
return (
<LineChart
@ -24,8 +29,8 @@ export const SimpleChart = ({data, baseWidth, chartHeight, showLine, showLabels
labels: [],
datasets: [
{
data
}
data,
},
],
}}
width={_chartWidth} // from react-native
@ -36,15 +41,14 @@ export const SimpleChart = ({data, baseWidth, chartHeight, showLine, showLabels
withDots={false}
withInnerLines={false}
chartConfig={{
backgroundColor:_chartBackgroundColor,
backgroundColor: _chartBackgroundColor,
backgroundGradientFrom: _chartBackgroundColor,
backgroundGradientTo: _chartBackgroundColor,
fillShadowGradient: EStyleSheet.value('$chartBlue'),
fillShadowGradientOpacity:0.8,
labelColor:() => EStyleSheet.value('$primaryDarkText'),
color: () => showLine?EStyleSheet.value('$chartBlue'):'transparent',
fillShadowGradientOpacity: 0.8,
labelColor: () => EStyleSheet.value('$primaryDarkText'),
color: () => (showLine ? EStyleSheet.value('$chartBlue') : 'transparent'),
}}
/>
)
}
);
};

View File

@ -1,7 +1,7 @@
import { getAccountPosts, getPost, getRankedPosts } from "../../../providers/hive/dhive";
import { filterLatestPosts, getUpdatedPosts } from "./tabbedPostsHelpers";
import { LoadPostsOptions } from "./tabbedPostsModels";
import { getPromotedEntries } from "../../../providers/ecency/ecency";
import { getAccountPosts, getPost, getRankedPosts } from '../../../providers/hive/dhive';
import { filterLatestPosts, getUpdatedPosts } from './tabbedPostsHelpers';
import { LoadPostsOptions } from './tabbedPostsModels';
import { getPromotedEntries } from '../../../providers/ecency/ecency';
const POSTS_FETCH_COUNT = 20;
@ -20,16 +20,15 @@ export const loadPosts = async ({
pageType,
tag,
nsfw,
}:LoadPostsOptions) => {
}: LoadPostsOptions) => {
let filter = filterKey;
//match filter with api if is friends
if(filter === 'friends'){
if (filter === 'friends') {
filter = 'feed';
}
const {isLoading, startPermlink, startAuthor} = tabMeta;
const { isLoading, startPermlink, startAuthor } = tabMeta;
//reject update if already loading
if (
@ -45,17 +44,17 @@ export const loadPosts = async ({
if (!isConnected && (refreshing || isLoading)) {
setTabMeta({
...tabMeta,
isLoading:false,
isRefreshing:false,
})
isLoading: false,
isRefreshing: false,
});
return;
}
setTabMeta({
...tabMeta,
isLoading:true,
isRefreshing:refreshing,
})
isLoading: true,
isRefreshing: refreshing,
});
let options = {} as any;
const limit = isLatestPostsCheck ? 5 : POSTS_FETCH_COUNT;
@ -86,7 +85,10 @@ export const loadPosts = async ({
sort: filter,
};
if ((pageType === 'profile' || pageType === 'ownProfile') && (filter === 'feed' || filter === 'posts')) {
if (
(pageType === 'profile' || pageType === 'ownProfile') &&
(filter === 'feed' || filter === 'posts')
) {
options.sort = 'posts';
}
}
@ -99,7 +101,6 @@ export const loadPosts = async ({
};
}
if (startAuthor && startPermlink && !refreshing && !isLatestPostsCheck) {
options.start_author = startAuthor;
options.start_permlink = startPermlink;
@ -107,86 +108,85 @@ export const loadPosts = async ({
try {
//fetching posts
const result:any[] = await func(options, feedUsername, nsfw);
const result: any[] = await func(options, feedUsername, nsfw);
if(result.length > 0){
if(filter === 'reblogs'){
if (result.length > 0) {
if (filter === 'reblogs') {
for (let i = result.length - 1; i >= 0; i--) {
if (result[i].author === feedUsername) {
result.splice(i, 1);
}
}
}
if((pageType === 'profile' || pageType === 'ownProfile') && pinnedPermlink){
if ((pageType === 'profile' || pageType === 'ownProfile') && pinnedPermlink) {
let pinnedIndex = -1;
result.forEach((post, index)=>{
if(post.author === feedUsername && post.permlink === pinnedPermlink){
result.forEach((post, index) => {
if (post.author === feedUsername && post.permlink === pinnedPermlink) {
pinnedIndex = index;
}
})
});
result.splice(pinnedIndex, 1);
}
}
//if filter is feed convert back to reducer filter
if(filter === 'feed'){
filter = 'friends'
if (filter === 'feed') {
filter = 'friends';
}
// cacheDispatch(updateFilterCache(filter, result, refreshing))
setTabMeta({
...tabMeta,
isLoading:false,
isRefreshing:false,
})
isLoading: false,
isRefreshing: false,
});
const retData = {
latestPosts:null,
updatedPosts:null
}
latestPosts: null,
updatedPosts: null,
};
if(isLatestPostsCheck){
if (isLatestPostsCheck) {
const latestPosts = filterLatestPosts(result, prevPosts.slice(0, 5));
retData.latestPosts = latestPosts
}else{
retData.latestPosts = latestPosts;
} else {
const updatedPosts = getUpdatedPosts(
startAuthor && startPermlink ? prevPosts:[],
startAuthor && startPermlink ? prevPosts : [],
result,
refreshing,
tabMeta,
setTabMeta,
)
);
retData.updatedPosts = updatedPosts;
}
//fetch add pinned posts if applicable
if(retData.updatedPosts && pinnedPermlink && retData.updatedPosts[0].permlink !== pinnedPermlink){
if (
retData.updatedPosts &&
pinnedPermlink &&
retData.updatedPosts[0].permlink !== pinnedPermlink
) {
const pinnedPost = await getPost(feedUsername, pinnedPermlink);
pinnedPost.stats = {is_pinned_blog:true, ...pinnedPost.stats};
pinnedPost.stats = { is_pinned_blog: true, ...pinnedPost.stats };
retData.updatedPosts = [pinnedPost, ...retData.updatedPosts];
}
return retData
return retData;
} catch (err) {
setTabMeta({
...tabMeta,
isLoading:false,
isRefreshing:false,
})
isLoading: false,
isRefreshing: false,
});
}
};
};
export const fetchPromotedEntries = async (username:string) => {
export const fetchPromotedEntries = async (username: string) => {
try {
const posts = await getPromotedEntries(username);
return Array.isArray(posts) ? posts : [];
} catch(err){
console.warn("Failed to get promoted posts, ", err)
}
} catch (err) {
console.warn('Failed to get promoted posts, ', err);
}
};

View File

@ -1,7 +1,5 @@
import unionBy from 'lodash/unionBy';
import { TabMeta } from "./tabbedPostsModels";
import { TabMeta } from './tabbedPostsModels';
//cacludate posts check refresh time for selected filter;
export const calculateTimeLeftForPostCheck = (firstPost: any) => {
@ -17,58 +15,50 @@ export const calculateTimeLeftForPostCheck = (firstPost: any) => {
timeLeft = refetchTime;
}
return timeLeft;
}
};
//filter posts that are not present in top 5 posts currently in list.
export const filterLatestPosts = (fetchedPosts: any[], cachedPosts: any[]) => {
console.log("Comparing: ", fetchedPosts, cachedPosts);
console.log('Comparing: ', fetchedPosts, cachedPosts);
let latestPosts = [];
fetchedPosts.forEach((post) => {
const newPostAuthPrem = post.author + post.permlink;
const postExist = cachedPosts.find((cPost) => (cPost.author + post.permlink) === newPostAuthPrem);
const postExist = cachedPosts.find((cPost) => cPost.author + post.permlink === newPostAuthPrem);
if (!postExist) {
latestPosts.push(post);
}
});
if (latestPosts.length > 0) {
return latestPosts.slice(0, 5);
} else {
return [];
}
};
//process posts result and return updated posts for the list.
export const getUpdatedPosts = (
prevPosts: any[],
nextPosts: any[],
shouldReset: boolean,
tabMeta: TabMeta,
setTabMeta: (meta: TabMeta) => void
setTabMeta: (meta: TabMeta) => void,
) => {
//return state as is if component is unmounter
let _posts = nextPosts;
if (nextPosts.length === 0) {
setTabMeta({
...tabMeta,
isNoPost: true
isNoPost: true,
});
return shouldReset ? [] : prevPosts;
}
const refreshing = tabMeta.isRefreshing;
if (prevPosts.length > 0 && !shouldReset) {
if (refreshing) {
_posts = unionBy(_posts, prevPosts, 'permlink');
@ -81,8 +71,7 @@ export const getUpdatedPosts = (
...tabMeta,
startAuthor: _posts[_posts.length - 1] && _posts[_posts.length - 1].author,
startPermlink: _posts[_posts.length - 1] && _posts[_posts.length - 1].permlink,
})
return _posts
}
});
return _posts;
};

View File

@ -1,63 +1,59 @@
export interface TabbedPostsProps {
filterOptions:string[],
filterOptionsValue:string[],
isFeedScreen:boolean,
feedUsername:string,
selectedOptionIndex:number,
feedSubfilterOptions:string[],
feedSubfilterOptionsValue:string[],
getFor:string,
pageType:'main'|'community'|'profile'|'ownProfile',
tag:string,
forceLoadPosts:boolean,
tabContentOverrides:Map<number, any>,
imagesToggleEnabled?:boolean,
stackedTabs:boolean,
pinnedPermlink?:string,
onTabChange:(index:number)=>void
handleOnScroll:()=>void,
filterOptions: string[];
filterOptionsValue: string[];
isFeedScreen: boolean;
feedUsername: string;
selectedOptionIndex: number;
feedSubfilterOptions: string[];
feedSubfilterOptionsValue: string[];
getFor: string;
pageType: 'main' | 'community' | 'profile' | 'ownProfile';
tag: string;
forceLoadPosts: boolean;
tabContentOverrides: Map<number, any>;
imagesToggleEnabled?: boolean;
stackedTabs: boolean;
pinnedPermlink?: string;
onTabChange: (index: number) => void;
handleOnScroll: () => void;
}
export interface TabMeta {
startPermlink:string,
startAuthor:string,
isLoading:boolean,
isRefreshing:boolean,
isNoPost:boolean,
}
startPermlink: string;
startAuthor: string;
isLoading: boolean;
isRefreshing: boolean;
isNoPost: boolean;
}
export interface LoadPostsOptions {
export interface LoadPostsOptions {
filterKey: string;
prevPosts: any[];
tabMeta: TabMeta;
setTabMeta: (meta: TabMeta) => void;
getFor: string;
isConnected: boolean;
isLoggedIn: boolean;
feedUsername: string;
pinnedPermlink: string;
pageType: string;
tag: string;
nsfw: string;
isLatestPostsCheck?: boolean;
refreshing?: boolean;
}
filterKey:string;
prevPosts:any[];
tabMeta:TabMeta;
setTabMeta:(meta:TabMeta)=>void,
getFor:string,
isConnected:boolean,
isLoggedIn:boolean,
feedUsername:string,
pinnedPermlink:string,
pageType:string,
tag:string,
nsfw:string,
isLatestPostsCheck?:boolean,
refreshing?:boolean,
}
export interface TabContentProps {
filterKey:string,
isFeedScreen:boolean,
isInitialTab:boolean,
getFor:string,
pageType:'main'|'profile'|'ownProfile'|'community',
feedUsername:string,
tag:string,
forceLoadPosts:boolean,
filterScrollRequest:string,
pinnedPermlink?:string,
onScrollRequestProcessed:()=>void
handleOnScroll:()=>void;
}
export interface TabContentProps {
filterKey: string;
isFeedScreen: boolean;
isInitialTab: boolean;
getFor: string;
pageType: 'main' | 'profile' | 'ownProfile' | 'community';
feedUsername: string;
tag: string;
forceLoadPosts: boolean;
filterScrollRequest: string;
pinnedPermlink?: string;
onScrollRequestProcessed: () => void;
handleOnScroll: () => void;
}

View File

@ -1,30 +1,29 @@
import React, {useEffect, useState} from 'react';
import React, { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { get } from 'lodash';
import { Text, View, FlatList } from 'react-native';
import { withNavigation } from '@react-navigation/compat';
import { useSelector, useDispatch } from 'react-redux';
import { NoPost, PostCardPlaceHolder, UserListItem } from '../..';
import globalStyles from '../../../globalStyles';
import { CommunityListItem, EmptyScreen } from '../../basicUIElements';
import styles from './tabbedPostsStyles';
import { default as ROUTES } from '../../../constants/routeNames';
import { withNavigation } from '@react-navigation/compat';
import {useSelector, useDispatch } from 'react-redux';
import { fetchCommunities, leaveCommunity, subscribeCommunity } from '../../../redux/actions/communitiesAction';
import {
fetchCommunities,
leaveCommunity,
subscribeCommunity,
} from '../../../redux/actions/communitiesAction';
import { fetchLeaderboard, followUser, unfollowUser } from '../../../redux/actions/userAction';
import { getCommunity } from '../../../providers/hive/dhive';
interface TabEmptyViewProps {
filterKey:string,
isNoPost:boolean,
navigation:any,
filterKey: string;
isNoPost: boolean;
navigation: any;
}
const TabEmptyView = ({
filterKey,
isNoPost,
navigation,
}: TabEmptyViewProps) => {
const TabEmptyView = ({ filterKey, isNoPost, navigation }: TabEmptyViewProps) => {
const intl = useIntl();
const dispatch = useDispatch();
//redux properties
@ -43,23 +42,22 @@ const TabEmptyView = ({
//hooks
useEffect(()=>{
useEffect(() => {
if (isNoPost) {
if (filterKey === 'friends') {
if (recommendedUsers.length === 0) {
_getRecommendedUsers();
}
} else if(filterKey === 'communities') {
} else if (filterKey === 'communities') {
if (recommendedCommunities.length === 0) {
_getRecommendedCommunities();
}
}
}
}, [isNoPost])
}, [isNoPost]);
useEffect(() => {
const {loading, error, data} = leaderboard;
const { loading, error, data } = leaderboard;
if (!loading) {
if (!error && data && data.length > 0) {
_formatRecommendedUsers(data);
@ -68,7 +66,7 @@ const TabEmptyView = ({
}, [leaderboard]);
useEffect(() => {
const {loading, error, data} = communities;
const { loading, error, data } = communities;
if (!loading) {
if (!error && data && data?.length > 0) {
_formatRecommendedCommunities(data);
@ -76,7 +74,6 @@ const TabEmptyView = ({
}
}, [communities]);
useEffect(() => {
const recommendeds = [...recommendedCommunities];
@ -103,8 +100,6 @@ const TabEmptyView = ({
setRecommendedCommunities(recommendeds);
}, [subscribingCommunities]);
useEffect(() => {
const recommendeds = [...recommendedUsers];
@ -131,7 +126,6 @@ const TabEmptyView = ({
setRecommendedUsers(recommendeds);
}, [followingUsers]);
//fetching
const _getRecommendedUsers = () => dispatch(fetchLeaderboard());
const _getRecommendedCommunities = () => dispatch(fetchCommunities('', 10));
@ -192,7 +186,6 @@ const TabEmptyView = ({
);
};
const _handleFollowUserButtonPress = (data, isFollowing) => {
let followAction;
let successToastText = '';
@ -223,14 +216,15 @@ const TabEmptyView = ({
dispatch(followAction(currentAccount, pinCode, data, successToastText, failToastText));
};
const _handleOnPressLogin = () => {
navigation.navigate(ROUTES.SCREENS.LOGIN);
};
//render related operations
if ((filterKey === 'feed' || filterKey === 'friends' || filterKey === 'communities') && !isLoggedIn) {
//render related operations
if (
(filterKey === 'feed' || filterKey === 'friends' || filterKey === 'communities') &&
!isLoggedIn
) {
return (
<NoPost
imageStyle={styles.noImage}
@ -245,7 +239,6 @@ const TabEmptyView = ({
if (isNoPost) {
if (filterKey === 'friends') {
return (
<>
<Text style={[globalStyles.subTitle, styles.noPostTitle]}>
@ -328,9 +321,7 @@ const TabEmptyView = ({
</>
);
} else {
return (
<EmptyScreen style={styles.emptyAnimationContainer} />
)
return <EmptyScreen style={styles.emptyAnimationContainer} />;
}
}
@ -342,4 +333,3 @@ const TabEmptyView = ({
};
export default withNavigation(TabEmptyView);

View File

@ -1,26 +1,26 @@
import React, { useRef, useState } from "react";
import { useIntl } from "react-intl";
import { useDispatch, useSelector } from "react-redux";
import { CustomiseFiltersModal, FilterBar } from "../..";
import { setHidePostsThumbnails } from "../../../redux/actions/applicationActions";
import { CustomiseFiltersModalRef } from "../../customiseFiltersModal/customiseFiltersModal";
import React, { useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import { useDispatch, useSelector } from 'react-redux';
import { CustomiseFiltersModal, FilterBar } from '../..';
import { setHidePostsThumbnails } from '../../../redux/actions/applicationActions';
import { CustomiseFiltersModalRef } from '../../customiseFiltersModal/customiseFiltersModal';
export interface TabItem {
filterKey:string;
label:string;
filterKey: string;
label: string;
}
interface StackedTabBarProps {
activeTab:boolean;
goToPage:(pageIndex)=>void;
tabs:string[];
pageType?:'main'|'community'|'profile'|'ownProfile'
shouldStack:boolean;
firstStack:TabItem[];
secondStack:TabItem[];
initialFirstStackIndex:number;
onFilterSelect:(filterKey:string)=>void;
toggleHideImagesFlag:boolean;
activeTab: boolean;
goToPage: (pageIndex) => void;
tabs: string[];
pageType?: 'main' | 'community' | 'profile' | 'ownProfile';
shouldStack: boolean;
firstStack: TabItem[];
secondStack: TabItem[];
initialFirstStackIndex: number;
onFilterSelect: (filterKey: string) => void;
toggleHideImagesFlag: boolean;
}
export const StackedTabBar = ({
@ -32,10 +32,8 @@ export const StackedTabBar = ({
initialFirstStackIndex,
onFilterSelect,
toggleHideImagesFlag,
pageType
}:StackedTabBarProps) => {
pageType,
}: StackedTabBarProps) => {
const dispatch = useDispatch();
const intl = useIntl();
@ -49,16 +47,15 @@ export const StackedTabBar = ({
const enableCustomTabs = pageType !== undefined;
const _onCustomisePress = () => {
if(customiseModalRef.current){
if (customiseModalRef.current) {
customiseModalRef.current.show();
}
}
};
const _onToggleImagesPress = () => {
dispatch(setHidePostsThumbnails(!isHideImages))
}
dispatch(setHidePostsThumbnails(!isHideImages));
};
return (
<>
@ -66,54 +63,43 @@ export const StackedTabBar = ({
options={firstStack.map((item, index) => {
return tabs[index]
? tabs[index]
: intl.formatMessage({ id: item.label.toLowerCase() }).toUpperCase()
})
}
: intl.formatMessage({ id: item.label.toLowerCase() }).toUpperCase();
})}
selectedOptionIndex={selectedFilterIndex}
rightIconName={toggleHideImagesFlag && "view-module"}
rightIconType={toggleHideImagesFlag && "MaterialIcons"}
rightIconName={toggleHideImagesFlag && 'view-module'}
rightIconType={toggleHideImagesFlag && 'MaterialIcons'}
enableCustomiseButton={enableCustomTabs}
onCustomisePress={_onCustomisePress}
onDropdownSelect={(index)=>{
onDropdownSelect={(index) => {
setSelectedFilterIndex(index);
if(index == 0 && shouldStack){
if (index == 0 && shouldStack) {
const tabIndex = firstStack.length + selectedSecondStackIndex;
onFilterSelect(secondStack[selectedSecondStackIndex].filterKey);
goToPage(tabIndex)
}else{
goToPage(tabIndex);
} else {
onFilterSelect(firstStack[index].filterKey);
goToPage(index);
}
}}
onRightIconPress={_onToggleImagesPress}
/>
{
selectedFilterIndex == 0 && shouldStack && (
{selectedFilterIndex == 0 && shouldStack && (
<FilterBar
options={secondStack.map((item) =>
intl.formatMessage({ id: item.label.toLowerCase() }).toUpperCase(),
)}
selectedOptionIndex={selectedSecondStackIndex}
onDropdownSelect={(index)=>{
setSelectedSecondStackIndex(index)
onDropdownSelect={(index) => {
setSelectedSecondStackIndex(index);
onFilterSelect(secondStack[index].filterKey);
goToPage(firstStack.length + index);
}}
/>
)
}
{enableCustomTabs && (
<CustomiseFiltersModal
pageType={pageType}
ref={customiseModalRef}
/>
)}
{enableCustomTabs && <CustomiseFiltersModal pageType={pageType} ref={customiseModalRef} />}
</>
)
}
);
};

View File

@ -1,23 +1,23 @@
import React, {useState, useEffect, useRef} from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { AppState, NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
import { debounce } from 'lodash';
import PostsList from '../../postsList';
import { fetchPromotedEntries, loadPosts } from '../services/tabbedPostsFetch';
import { LoadPostsOptions, TabContentProps, TabMeta } from '../services/tabbedPostsModels';
import {useSelector, useDispatch } from 'react-redux';
import TabEmptyView from './listEmptyView';
import { setInitPosts } from '../../../redux/actions/postsAction';
import { showReplyModal } from '../../../redux/actions/uiAction';
import { calculateTimeLeftForPostCheck } from '../services/tabbedPostsHelpers';
import { AppState, NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
import { PostsListRef } from '../../postsList/container/postsListContainer';
import ScrollTopPopup from './scrollTopPopup';
import { debounce } from 'lodash';
const DEFAULT_TAB_META = {
startAuthor:'',
startPermlink:'',
isLoading:false,
isRefreshing:false,
} as TabMeta;
startAuthor: '',
startPermlink: '',
isLoading: false,
isRefreshing: false,
} as TabMeta;
var scrollOffset = 0;
var blockPopup = false;
@ -39,31 +39,29 @@ const TabContent = ({
}: TabContentProps) => {
let _isMounted = true;
//redux properties
const dispatch = useDispatch();
const isLoggedIn = useSelector((state) => state.application.isLoggedIn);
const nsfw = useSelector((state) => state.application.nsfw);
const isConnected = useSelector((state) => state.application.isConnected);
const currentAccount = useSelector((state) => state.account.currentAccount);
const initPosts = useSelector((state) => state.posts.initPosts)
const initPosts = useSelector((state) => state.posts.initPosts);
const username = currentAccount.username;
const userPinned = currentAccount.about?.profile?.pinned;
//state
const [posts, setPosts] = useState([]);
const [promotedPosts, setPromotedPosts] = useState([]);
const [sessionUser, setSessionUser] = useState(username);
const [tabMeta, setTabMeta] = useState(DEFAULT_TAB_META);
const [latestPosts, setLatestPosts] = useState<any[]>([]);
const [postFetchTimer, setPostFetchTimer] = useState(0)
const [postFetchTimer, setPostFetchTimer] = useState(0);
const [enableScrollTop, setEnableScrollTop] = useState(false);
const [curPinned, setCurPinned] = useState(pinnedPermlink)
const [curPinned, setCurPinned] = useState(pinnedPermlink);
//refs
let postsListRef = useRef<PostsListRef>()
let postsListRef = useRef<PostsListRef>();
const appState = useRef(AppState.currentState);
const postsRef = useRef(posts);
const sessionUserRef = useRef(sessionUser);
@ -72,12 +70,8 @@ const TabContent = ({
postsRef.current = posts;
sessionUserRef.current = sessionUser;
//side effects
useEffect(() => {
if (isFeedScreen) {
AppState.addEventListener('change', _handleAppStateChange);
}
@ -85,187 +79,181 @@ const TabContent = ({
_initContent(true, feedUsername);
return _cleanup;
}, [tag])
useEffect(()=>{
if(isConnected && (username !== sessionUser || forceLoadPosts)){
_initContent(false, username);
}
}, [username, forceLoadPosts])
}, [tag]);
useEffect(() => {
if(filterScrollRequest && filterScrollRequest === filterKey){
if (isConnected && (username !== sessionUser || forceLoadPosts)) {
_initContent(false, username);
}
}, [username, forceLoadPosts]);
useEffect(() => {
if (filterScrollRequest && filterScrollRequest === filterKey) {
_scrollToTop();
if(onScrollRequestProcessed){
if (onScrollRequestProcessed) {
onScrollRequestProcessed();
}
}
}, [filterScrollRequest])
}, [filterScrollRequest]);
useEffect(()=>{
console.log("curPinned change", userPinned);
if(pageType === 'ownProfile' && userPinned !== curPinned ){
useEffect(() => {
console.log('curPinned change', userPinned);
if (pageType === 'ownProfile' && userPinned !== curPinned) {
_scrollToTop();
_loadPosts({shouldReset:true, _pinnedPermlink:userPinned})
_loadPosts({ shouldReset: true, _pinnedPermlink: userPinned });
setCurPinned(userPinned);
}
},[userPinned])
}, [userPinned]);
const _cleanup = () => {
_isMounted = false;
if(postFetchTimer){
if (postFetchTimer) {
clearTimeout(postFetchTimer);
}
if (isFeedScreen) {
AppState.removeEventListener('change', _handleAppStateChange);
}
}
};
//actions
const _handleAppStateChange = (nextAppState) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active' && posts.length > 0) {
if (
appState.current.match(/inactive|background/) &&
nextAppState === 'active' &&
posts.length > 0
) {
const isLatestPostsCheck = true;
_loadPosts({
shouldReset:false,
isLatestPostsCheck
shouldReset: false,
isLatestPostsCheck,
});
}
appState.current = nextAppState;
};
const _initContent = (isFirstCall = false, _feedUsername:string) => {
const _initContent = (isFirstCall = false, _feedUsername: string) => {
_scrollToTop();
const initialPosts = isFirstCall && isFeedScreen && isInitialTab ? initPosts : [];
setPosts(initialPosts);
setTabMeta(DEFAULT_TAB_META)
setTabMeta(DEFAULT_TAB_META);
setSessionUser(_feedUsername);
setLatestPosts([]);
if(postFetchTimer){
if (postFetchTimer) {
clearTimeout(postFetchTimer);
}
if(username || (filterKey !== 'friends' && filterKey !== 'communities')){
if (username || (filterKey !== 'friends' && filterKey !== 'communities')) {
_loadPosts({
shouldReset:!isFirstCall,
shouldReset: !isFirstCall,
isFirstCall,
isLatestPostsCheck:false,
isLatestPostsCheck: false,
_feedUsername,
_posts:initialPosts,
_tabMeta:DEFAULT_TAB_META
_posts: initialPosts,
_tabMeta: DEFAULT_TAB_META,
});
_getPromotedPosts();
}
}
};
//fetch posts from server
const _loadPosts = async ({
shouldReset = false,
isLatestPostsCheck = false,
isFirstCall = false,
_feedUsername = isFeedScreen? sessionUserRef.current:feedUsername,
_feedUsername = isFeedScreen ? sessionUserRef.current : feedUsername,
_posts = postsRef.current,
_tabMeta = tabMeta,
_pinnedPermlink = curPinned
}:{
shouldReset?:boolean;
isLatestPostsCheck?:boolean;
isFirstCall?:boolean;
_feedUsername?:string;
_posts?:any[];
_tabMeta?:TabMeta;
_pinnedPermlink?:string
_pinnedPermlink = curPinned,
}: {
shouldReset?: boolean;
isLatestPostsCheck?: boolean;
isFirstCall?: boolean;
_feedUsername?: string;
_posts?: any[];
_tabMeta?: TabMeta;
_pinnedPermlink?: string;
}) => {
const options = {
setTabMeta:(meta:TabMeta) => {
if(_isMounted){
setTabMeta(meta)
setTabMeta: (meta: TabMeta) => {
if (_isMounted) {
setTabMeta(meta);
}
},
filterKey,
prevPosts:_posts,
tabMeta:_tabMeta,
prevPosts: _posts,
tabMeta: _tabMeta,
isLoggedIn,
nsfw,
isConnected,
isFeedScreen,
refreshing:shouldReset,
refreshing: shouldReset,
pageType,
isLatestPostsCheck,
feedUsername:_feedUsername,
pinnedPermlink:_pinnedPermlink,
feedUsername: _feedUsername,
pinnedPermlink: _pinnedPermlink,
tag,
...props
} as LoadPostsOptions
...props,
} as LoadPostsOptions;
const result = await loadPosts(options)
if(_isMounted && result){
if(shouldReset || isFirstCall){
const result = await loadPosts(options);
if (_isMounted && result) {
if (shouldReset || isFirstCall) {
setPosts([]);
}
_postProcessLoadResult(result)
_postProcessLoadResult(result);
}
}
};
const _getPromotedPosts = async () => {
if(pageType === 'profile' || pageType === 'ownProfile' || pageType === 'community'){
if (pageType === 'profile' || pageType === 'ownProfile' || pageType === 'community') {
return;
}
const pPosts = await fetchPromotedEntries(username)
if(pPosts){
setPromotedPosts(pPosts)
const pPosts = await fetchPromotedEntries(username);
if (pPosts) {
setPromotedPosts(pPosts);
}
}
};
//schedules post fetch
const _scheduleLatestPostsCheck = (firstPost:any) => {
const _scheduleLatestPostsCheck = (firstPost: any) => {
if (firstPost) {
if (postFetchTimer) {
clearTimeout(postFetchTimer);
}
const timeLeft = calculateTimeLeftForPostCheck(firstPost)
const timeLeft = calculateTimeLeftForPostCheck(firstPost);
const _postFetchTimer = setTimeout(() => {
const isLatestPostsCheck = true;
_loadPosts({
shouldReset:false,
isLatestPostsCheck
shouldReset: false,
isLatestPostsCheck,
});
},
timeLeft
);
setPostFetchTimer(_postFetchTimer)
}, timeLeft);
setPostFetchTimer(_postFetchTimer);
}
};
//processes response from loadPost
const _postProcessLoadResult = ({updatedPosts, latestPosts}:any) => {
const _postProcessLoadResult = ({ updatedPosts, latestPosts }: any) => {
//process new posts avatart
if(latestPosts && Array.isArray(latestPosts)){
if(latestPosts.length > 0){
setLatestPosts(latestPosts)
}else{
_scheduleLatestPostsCheck(posts[0])
if (latestPosts && Array.isArray(latestPosts)) {
if (latestPosts.length > 0) {
setLatestPosts(latestPosts);
} else {
_scheduleLatestPostsCheck(posts[0]);
}
}
//process returned data
if(Array.isArray(updatedPosts)){
if(updatedPosts.length){
if (Array.isArray(updatedPosts)) {
if (updatedPosts.length) {
//match new and old first post
const firstPostChanged = posts.length == 0 || (posts[0].permlink !== updatedPosts[0].permlink);
const firstPostChanged =
posts.length == 0 || posts[0].permlink !== updatedPosts[0].permlink;
if (isFeedScreen && firstPostChanged) {
//schedule refetch of new posts by checking time of current post
_scheduleLatestPostsCheck(updatedPosts[0]);
@ -274,84 +262,83 @@ const TabContent = ({
dispatch(setInitPosts(updatedPosts));
}
}
} else if (isFeedScreen && isInitialTab){
} else if (isFeedScreen && isInitialTab) {
//clear posts cache if no first tab posts available, precautionary measure for accoutn change
dispatch(setInitPosts([]))
dispatch(setInitPosts([]));
}
setPosts(updatedPosts);
}
}
};
//view related routines
const _onPostsPopupPress = () => {
_scrollToTop();
_getPromotedPosts()
setPosts([...latestPosts, ...posts])
_getPromotedPosts();
setPosts([...latestPosts, ...posts]);
_scheduleLatestPostsCheck(latestPosts[0]);
setLatestPosts([]);
}
};
const _scrollToTop = () => {
postsListRef.current.scrollToTop();
setEnableScrollTop(false);
scrollPopupDebouce.cancel();
blockPopup = true;
setTimeout(()=>{
setTimeout(() => {
blockPopup = false;
}, 1000)
}, 1000);
};
const _handleOnScroll = () => {
if(handleOnScroll){
handleOnScroll()
}
if (handleOnScroll) {
handleOnScroll();
}
};
//view rendereres
const _renderEmptyContent = () => {
return <TabEmptyView filterKey={filterKey} isNoPost={tabMeta.isNoPost}/>
}
return <TabEmptyView filterKey={filterKey} isNoPost={tabMeta.isNoPost} />;
};
const scrollPopupDebouce = debounce((value)=>{
const scrollPopupDebouce = debounce(
(value) => {
setEnableScrollTop(value);
}, 500, {leading:true})
},
500,
{ leading: true },
);
const _onScroll = (event:NativeSyntheticEvent<NativeScrollEvent>)=>{
const _onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
var currentOffset = event.nativeEvent.contentOffset.y;
var scrollUp = currentOffset < scrollOffset;
scrollOffset = currentOffset;
if(scrollUp && !blockPopup && currentOffset > SCROLL_POPUP_THRESHOLD){
scrollPopupDebouce(true)
if (scrollUp && !blockPopup && currentOffset > SCROLL_POPUP_THRESHOLD) {
scrollPopupDebouce(true);
}
};
// show quick reply modal
const _showQuickReplyModal = (post:any) => {
const _showQuickReplyModal = (post: any) => {
if (isLoggedIn) {
dispatch(showReplyModal(post))
dispatch(showReplyModal(post));
} else {
//TODO: show proper alert message
console.log('Not LoggedIn');
}
}
};
return (
<>
<PostsList
ref={postsListRef}
data={posts}
isFeedScreen={isFeedScreen}
promotedPosts={promotedPosts}
onLoadPosts={(shouldReset)=>{
_loadPosts({shouldReset})
if(shouldReset){
_getPromotedPosts()
onLoadPosts={(shouldReset) => {
_loadPosts({ shouldReset });
if (shouldReset) {
_getPromotedPosts();
}
}}
onScroll={_onScroll}
@ -363,11 +350,11 @@ const TabContent = ({
showQuickReplyModal={_showQuickReplyModal}
/>
<ScrollTopPopup
popupAvatars={latestPosts.map(post=>post.avatar || '')}
popupAvatars={latestPosts.map((post) => post.avatar || '')}
enableScrollTop={enableScrollTop}
onPress={_onPostsPopupPress}
onClose={()=>{
setLatestPosts([])
onClose={() => {
setLatestPosts([]);
setEnableScrollTop(false);
}}
/>
@ -376,4 +363,3 @@ const TabContent = ({
};
export default TabContent;

View File

@ -7,13 +7,13 @@ import { useAppSelector } from '../../../hooks';
import styles from './textInputStyles';
interface Props extends TextInputProps {
innerRef: Ref<TextInput>,
height: number,
style: TextStyle
innerRef: Ref<TextInput>;
height: number;
style: TextStyle;
}
const TextInputView = ({ innerRef, height, style, ...props }: Props) => {
const isDarkTheme = useAppSelector(state => state.application.isDarkTheme);
const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme);
return (
<TextInput
ref={innerRef}
@ -22,7 +22,7 @@ const TextInputView = ({ innerRef, height, style, ...props }: Props) => {
{...props}
style={[styles.input, { minHeight: height }, style]}
/>
)
);
};
export default TextInputView;

View File

@ -54,7 +54,7 @@ export default EStyleSheet.create({
textAlign: 'right',
},
centerDescription: {
marginTop:8,
marginTop: 8,
fontSize: 12,
color: '$primaryBlack',
fontWeight: '600',

View File

@ -57,7 +57,7 @@ const UploadsGalleryContent = ({
const animatedHeightRef = useRef(new Animated.Value(COMPACT_HEIGHT));
const isDeleting = deleteMediaMutation.isLoading
const isDeleting = deleteMediaMutation.isLoading;
useEffect(() => {
if (isExpandedMode) {
@ -65,13 +65,12 @@ const UploadsGalleryContent = ({
}
}, [isExpandedMode]);
const _deleteMedia = async () => {
deleteMediaMutation.mutate(deleteIds, {
onSettled: () => {
setIsDeleteMode(false);
setDeleteIds([]);
}
},
});
};

View File

@ -15,7 +15,7 @@ export default EStyleSheet.create({
},
listContentContainer: {
marginTop:8,
marginTop: 8,
paddingRight: 72,
paddingLeft: 16,
},
@ -30,7 +30,7 @@ export default EStyleSheet.create({
height: THUMB_SIZE,
width: THUMB_SIZE,
borderRadius: 16,
backgroundColor: '$primaryLightGray'
backgroundColor: '$primaryLightGray',
} as ImageStyle,
gridMediaItem: {
@ -39,11 +39,11 @@ export default EStyleSheet.create({
width: GRID_THUMB_SIZE,
marginVertical: 8,
borderRadius: 16,
backgroundColor: '$primaryLightGray'
backgroundColor: '$primaryLightGray',
} as ImageStyle,
inputContainer: {
flex: 1
flex: 1,
} as ViewStyle,
titleInput: {
color: '$primaryBlack',
@ -53,7 +53,7 @@ export default EStyleSheet.create({
paddingVertical: 0,
backgroundColor: '$primaryBackgroundColor',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '$primaryDarkGray'
borderBottomColor: '$primaryDarkGray',
} as TextStyle,
title: {
@ -69,20 +69,19 @@ export default EStyleSheet.create({
fontSize: 16,
color: '$primaryBlack',
marginLeft: 12,
alignSelf:'center'
alignSelf: 'center',
},
btnText: {
color: '$pureWhite'
color: '$pureWhite',
} as TextStyle,
saveButton: {
backgroundColor: '$primaryBlue',
width: 150,
paddingVertical: 16,
borderRadius: 32,
justifyContent: 'center',
alignItems: 'center'
alignItems: 'center',
} as ViewStyle,
closeButton: {
@ -90,14 +89,14 @@ export default EStyleSheet.create({
paddingVertical: 8,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center'
alignItems: 'center',
} as ViewStyle,
actionPanel: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
marginBottom: 16
marginBottom: 16,
} as ViewStyle,
itemIcon: {
@ -113,8 +112,8 @@ export default EStyleSheet.create({
selectButtonsContainer: {
justifyContent: 'space-around',
paddingVertical: 8,
marginRight:8,
height: THUMB_SIZE
marginRight: 8,
height: THUMB_SIZE,
} as ViewStyle,
selectButton: {
@ -128,14 +127,14 @@ export default EStyleSheet.create({
marginTop: -16,
zIndex: 2,
borderRadius: 12,
backgroundColor: "$primaryBlack"
backgroundColor: '$primaryBlack',
} as ViewStyle,
selectButtonLabel: {
fontSize: 16,
textAlignVertical: 'top',
color: '$primaryBlack',
marginLeft: 4
marginLeft: 4,
} as TextStyle,
pillBtnContainer: {
@ -153,16 +152,15 @@ export default EStyleSheet.create({
width: THUMB_SIZE / 1.8,
borderRadius: 0,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20
borderBottomRightRadius: 20,
} as ViewStyle,
deleteButtonContainer: {
position: 'absolute',
right: 0,
top: 0,
bottom: 0,
justifyContent: 'center'
justifyContent: 'center',
},
deleteButton: {
@ -171,14 +169,13 @@ export default EStyleSheet.create({
borderRadius: 0,
borderBottomLeftRadius: 20,
borderTopLeftRadius: 20,
backgroundColor: '$primaryRed'
backgroundColor: '$primaryRed',
} as ViewStyle,
itemIconWrapper: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '$primaryRed',
} as ViewStyle,
minusContainer: {
@ -200,15 +197,14 @@ export default EStyleSheet.create({
height: 24,
width: 24,
justifyContent: 'center',
alignItems: 'center'
alignItems: 'center',
} as ViewStyle,
counterText: {
color: '$primaryBlack',
fontSize: 16
fontSize: 16,
} as TextStyle,
checkStyle: {
backgroundColor: '$white',
} as ViewStyle,
@ -224,7 +220,6 @@ export default EStyleSheet.create({
marginLeft: 8,
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center'
} as ViewStyle
})
alignItems: 'center',
} as ViewStyle,
});

View File

@ -198,12 +198,14 @@ export const UploadsGalleryModal = forwardRef(
setIsAddingToUploads(true);
}
await mediaUploadMutation.mutateAsync({
await mediaUploadMutation.mutateAsync(
{
media,
addToUploads: !shouldInsert
}, {
addToUploads: !shouldInsert,
},
{
onSuccess: (data) => {
console.log('upload successfully', data, media, shouldInsert)
console.log('upload successfully', data, media, shouldInsert);
if (data && data.url && shouldInsert) {
_handleMediaInsertion({
filename: media.filename,
@ -219,9 +221,11 @@ export const UploadsGalleryModal = forwardRef(
}
setIsAddingToUploads(false);
},
onError: (err) => { throw err },
})
onError: (err) => {
throw err;
},
},
);
} catch (error) {
console.log('error while uploading image : ', error);
@ -300,8 +304,6 @@ export const UploadsGalleryModal = forwardRef(
}
};
//fetch images from server
const _getMediaUploads = async () => {
try {

View File

@ -140,8 +140,8 @@ const UpvoteView = ({
//record user points
userActivityMutation.mutate({
pointsTy: PointActivityIds.VOTE,
transactionId: response.id
})
transactionId: response.id,
});
if (!response || !response.id) {
Alert.alert(
@ -214,8 +214,8 @@ const UpvoteView = ({
//record usr points
userActivityMutation.mutate({
pointsTy: PointActivityIds.VOTE,
transactionId: response.id
})
transactionId: response.id,
});
setUpvote(!!sliderValue);
setIsVoting(false);
onVote(amount, true);

View File

@ -8,5 +8,4 @@ const coingeckoApi = axios.create({
baseURL: `${BASE_URL}/${PATH_API}/${API_VERSION}`,
});
export default coingeckoApi;

View File

@ -5,7 +5,7 @@ import { get } from 'lodash';
import { store } from '../redux/store/store';
import { getDigitPinCode } from '../providers/hive/dhive';
import { decryptKey } from '../utils/crypto';
import bugsnagInstance from '../config/bugsnag';
import bugsnagInstance from './bugsnag';
const api = axios.create({
baseURL: Config.ECENCY_BACKEND_API,
@ -19,16 +19,17 @@ api.interceptors.request.use((request) => {
console.log('Starting ecency Request', request);
//skip code addition is register and token refresh endpoint is triggered
if (request.url === '/private-api/account-create'
|| request.url === '/auth-api/hs-token-refresh'
|| request.url === '/private-api/promoted-entries'
|| request.url.startsWith('private-api/leaderboard')
|| request.url.startsWith('/private-api/received-vesting/')
|| request.url.startsWith('/private-api/referrals/')
|| request.url.startsWith('/private-api/market-data')
|| request.url.startsWith('/private-api/comment-history')
if (
request.url === '/private-api/account-create' ||
request.url === '/auth-api/hs-token-refresh' ||
request.url === '/private-api/promoted-entries' ||
request.url.startsWith('private-api/leaderboard') ||
request.url.startsWith('/private-api/received-vesting/') ||
request.url.startsWith('/private-api/referrals/') ||
request.url.startsWith('/private-api/market-data') ||
request.url.startsWith('/private-api/comment-history')
) {
return request
return request;
}
if (!request.data?.code) {
@ -47,12 +48,15 @@ api.interceptors.request.use((request) => {
console.log('Added access token:', accessToken);
} else {
const isLoggedIn = state.application.isLoggedIn;
console.warn("Failed to inject accessToken", `isLoggedIn:${isLoggedIn}`)
bugsnagInstance.notify(new Error(`Failed to inject accessToken in ${request.url} call. isLoggedIn:${isLoggedIn}, local.acccessToken:${token}, pin:${pin}`))
console.warn('Failed to inject accessToken', `isLoggedIn:${isLoggedIn}`);
bugsnagInstance.notify(
new Error(
`Failed to inject accessToken in ${request.url} call. isLoggedIn:${isLoggedIn}, local.acccessToken:${token}, pin:${pin}`,
),
);
}
}
return request;
});

View File

@ -1,29 +1,33 @@
import { CoinBase } from "../redux/reducers/walletReducer"
import { CoinBase } from '../redux/reducers/walletReducer';
const DEFAULT_COINS = [{
id:'ecency',
symbol:'Points',
notCrypto:true
},{
id:'hive_power',
symbol:'HP',
notCrypto:true
},{
id:'hive',
symbol:'HIVE',
notCrypto:false
},{
id:'hive_dollar',
symbol:'HBD',
notCrypto:false
}] as CoinBase[]
const DEFAULT_COINS = [
{
id: 'ecency',
symbol: 'Points',
notCrypto: true,
},
{
id: 'hive_power',
symbol: 'HP',
notCrypto: true,
},
{
id: 'hive',
symbol: 'HIVE',
notCrypto: false,
},
{
id: 'hive_dollar',
symbol: 'HBD',
notCrypto: false,
},
] as CoinBase[];
export const COIN_IDS = {
ECENCY:'ecency',
HIVE:'hive',
HBD:'hive_dollar',
HP:'hive_power'
}
ECENCY: 'ecency',
HIVE: 'hive',
HBD: 'hive_dollar',
HP: 'hive_power',
};
export default DEFAULT_COINS
export default DEFAULT_COINS;

View File

@ -1,6 +1,5 @@
export default [
{key:'settings.theme.system', value: null},
{key:'settings.theme.light', value: false},
{key:'settings.theme.dark', value: true}
];
{ key: 'settings.theme.system', value: null },
{ key: 'settings.theme.light', value: false },
{ key: 'settings.theme.dark', value: true },
];

View File

@ -4,5 +4,5 @@ const DELETE_ACCOUNT = 'delete_account';
export default {
SHOW_HIDE_IMGS,
DELETE_ACCOUNT
DELETE_ACCOUNT,
};

View File

@ -10,7 +10,7 @@ const DELEGATE = 'delegate';
const POWER_DOWN = 'power_down';
const ADDRESS_VIEW = 'address_view';
const DELEGATE_VESTING_SHARES = 'delegate_vesting_shares';
const WITHDRAW_VESTING = 'withdraw_vesting'
const WITHDRAW_VESTING = 'withdraw_vesting';
export default {
TRANSFER_TOKEN,
@ -25,5 +25,5 @@ export default {
POWER_DOWN,
ADDRESS_VIEW,
DELEGATE_VESTING_SHARES,
WITHDRAW_VESTING
WITHDRAW_VESTING,
};

View File

@ -47,18 +47,15 @@ class InAppPurchaseContainer extends Component {
RNIap.endConnection();
}
_initContainer = async () => {
const {
intl,
} = this.props;
const { intl } = this.props;
try {
await RNIap.initConnection();
if (Platform.OS === 'android') {
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
}
await this._consumeAvailablePurchases()
await this._consumeAvailablePurchases();
this._getItems();
this._purchaseUpdatedListener();
await this._handleQrPurchase();
@ -70,17 +67,15 @@ class InAppPurchaseContainer extends Component {
intl.formatMessage({
id: 'alert.connection_issues',
}),
err.message
err.message,
);
}
}
};
//this snippet consumes all previously bought purchases
//that are set to be consumed yet
_consumeAvailablePurchases = async () => {
try{
try {
//get available purchase
const purchases = await RNIap.getAvailablePurchases();
//check consumeable status
@ -88,12 +83,11 @@ class InAppPurchaseContainer extends Component {
//consume item using finishTransactionx
await RNIap.finishTransaction(purchases[i], true);
}
}catch(err){
} catch (err) {
bugsnagInstance.notify(err);
console.warn(err.code, err.message);
}
}
};
// Component Functions
@ -157,7 +151,7 @@ class InAppPurchaseContainer extends Component {
intl.formatMessage({
id: 'alert.warning',
}),
error.message
error.message,
);
}
this.setState({ isProcessing: false });
@ -186,7 +180,7 @@ class InAppPurchaseContainer extends Component {
intl.formatMessage({
id: 'alert.connection_issues',
}),
error.message
error.message,
);
}
@ -219,23 +213,29 @@ class InAppPurchaseContainer extends Component {
const productId = navigation.getParam('productId', '');
const username = navigation.getParam('username', '');
const product:Product = productId && products && products.find((product) => product.productId === productId)
const product: Product =
productId && products && products.find((product) => product.productId === productId);
if (product) {
let body = intl.formatMessage({
id: 'boost.confirm_purchase_summary'
}, {
let body = intl.formatMessage(
{
id: 'boost.confirm_purchase_summary',
},
{
points: this._getTitle(product.title),
username,
price: `${product.currency} ${product.price}`
});
price: `${product.currency} ${product.price}`,
},
);
let title = intl.formatMessage({
id: 'boost.confirm_purchase'
}, {
let title = intl.formatMessage(
{
id: 'boost.confirm_purchase',
},
{
username,
});
},
);
dispatch(
showActionModal({
@ -251,12 +251,11 @@ class InAppPurchaseContainer extends Component {
onPress: async () => await this._buyItem(productId),
},
],
headerContent: <UserAvatar username={username} size='xl' />,
headerContent: <UserAvatar username={username} size="xl" />,
}),
);
}
}
};
render() {
const { children, isNoSpin, navigation } = this.props;
@ -276,7 +275,7 @@ class InAppPurchaseContainer extends Component {
getItems: this._getItems,
getTitle: this._getTitle,
spinProduct: productList.filter((item) => item.productId.includes('spins')),
navigation: navigation
navigation: navigation,
})
);
}

View File

@ -10,22 +10,19 @@ import { setTopLevelNavigator } from './service';
import ROUTES from '../constants/routeNames';
import parseVersionNumber from '../utils/parseVersionNumber';
export const AppNavigator = () => {
const lastAppVersion = useAppSelector(state => state.application.lastAppVersion)
const lastAppVersion = useAppSelector((state) => state.application.lastAppVersion);
const [appVersion] = useState(VersionNumber.appVersion);
const _initRoute = (!lastAppVersion || (parseVersionNumber(lastAppVersion) < parseVersionNumber(appVersion))) ?
ROUTES.SCREENS.WELCOME : ROUTES.SCREENS.FEED;
const _initRoute =
!lastAppVersion || parseVersionNumber(lastAppVersion) < parseVersionNumber(appVersion)
? ROUTES.SCREENS.WELCOME
: ROUTES.SCREENS.FEED;
return (
<NavigationContainer ref={ref=>setTopLevelNavigator(ref)}>
<NavigationContainer ref={(ref) => setTopLevelNavigator(ref)}>
<StackNavigator initRoute={_initRoute} />
</NavigationContainer>
)
}
);
};

View File

@ -1,38 +1,29 @@
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import ROUTES from '../constants/routeNames';
import { BottomTabBar } from '../components';
import {
Feed,
Notification,
Profile,
Wallet,
} from '../screens';
import { Feed, Notification, Profile, Wallet } from '../screens';
const Tab = createBottomTabNavigator();
export const BottomTabNavigator = () => {
return (
<Tab.Navigator
tabBar={(props) => <BottomTabBar {...props} /> }
backBehavior='initialRoute'
tabBar={(props) => <BottomTabBar {...props} />}
backBehavior="initialRoute"
screenOptions={{
headerShown:false,
headerShown: false,
tabBarShowLabel: false,
tabBarActiveTintColor: '#357ce6',
tabBarInactiveTintColor: '#c1c5c7',}}
tabBarInactiveTintColor: '#c1c5c7',
}}
>
<Tab.Screen
name={ROUTES.TABBAR.FEED}
component={Feed}
initialParams={{
iconName: 'view-day' //read in bottomTabBarView
iconName: 'view-day', //read in bottomTabBarView
}}
/>
@ -40,7 +31,7 @@ export const BottomTabNavigator = () => {
name={ROUTES.TABBAR.NOTIFICATION}
component={Notification}
initialParams={{
iconName: 'notifications' //read in bottomTabBarView
iconName: 'notifications', //read in bottomTabBarView
}}
/>
@ -48,7 +39,7 @@ export const BottomTabNavigator = () => {
name={ROUTES.TABBAR.POST_BUTTON}
component={EmptyScreen}
initialParams={{
iconName: 'pencil' //read in bottomTabBarView
iconName: 'pencil', //read in bottomTabBarView
}}
/>
@ -56,7 +47,7 @@ export const BottomTabNavigator = () => {
name={ROUTES.TABBAR.WALLET}
component={Wallet}
initialParams={{
iconName: 'account-balance-wallet' //read in bottomTabBarView
iconName: 'account-balance-wallet', //read in bottomTabBarView
}}
/>
@ -64,12 +55,11 @@ export const BottomTabNavigator = () => {
name={ROUTES.TABBAR.PROFILE}
component={Profile}
initialParams={{
iconName: 'person' //read in bottomTabBarView
iconName: 'person', //read in bottomTabBarView
}}
/>
</Tab.Navigator>
)
}
);
};
const EmptyScreen = () => null
const EmptyScreen = () => null;

View File

@ -1,19 +1,20 @@
import React from 'react';
import { SideMenu } from "../components"
import { BottomTabNavigator } from "./botomTabNavigator"
import { createDrawerNavigator } from '@react-navigation/drawer';
import { SideMenu } from '../components';
import { BottomTabNavigator } from './botomTabNavigator';
// Constants
import ROUTES from '../constants/routeNames';
import { createDrawerNavigator } from "@react-navigation/drawer";
const Drawer = createDrawerNavigator();
export const DrawerNavigator = () => {
return (
<Drawer.Navigator screenOptions={{headerShown:false}} drawerContent={(props) => <SideMenu {...props}/>} >
<Drawer.Navigator
screenOptions={{ headerShown: false }}
drawerContent={(props) => <SideMenu {...props} />}
>
<Drawer.Screen name={ROUTES.SCREENS.FEED} component={BottomTabNavigator} />
</Drawer.Navigator>
)
}
);
};

View File

@ -1,9 +1,9 @@
import React from 'react';
// Constants
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import ROUTES from '../constants/routeNames';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
// Screens
import {
Bookmarks,
@ -36,13 +36,12 @@ import {
} from '../screens';
import { DrawerNavigator } from './drawerNavigator';
const RootStack = createNativeStackNavigator();
const MainStack = createNativeStackNavigator();
const MainStackNavigator = () => {
return (
<MainStack.Navigator screenOptions={{ headerShown: false, animation: 'slide_from_right' }} >
<MainStack.Navigator screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
<MainStack.Screen name={ROUTES.DRAWER.MAIN} component={DrawerNavigator} />
<MainStack.Screen name={ROUTES.SCREENS.PROFILE} component={Profile} />
<MainStack.Screen name={ROUTES.SCREENS.PROFILE_EDIT} component={ProfileEdit} />
@ -70,25 +69,27 @@ const MainStackNavigator = () => {
<MainStack.Screen name={ROUTES.SCREENS.EDITOR} component={Editor} />
</MainStack.Group>
</MainStack.Navigator>
)
}
export const StackNavigator = ({initRoute}) => {
);
};
export const StackNavigator = ({ initRoute }) => {
return (
<RootStack.Navigator
initialRouteName={initRoute}
screenOptions={{ headerShown: false, animation:'slide_from_bottom' }}>
screenOptions={{ headerShown: false, animation: 'slide_from_bottom' }}
<RootStack.Screen name={ROUTES.STACK.MAIN} component={MainStackNavigator} />
<RootStack.Screen name={ROUTES.SCREENS.REGISTER} component={Register} />
<RootStack.Screen name={ROUTES.SCREENS.LOGIN} component={Login} />
<RootStack.Screen name={ROUTES.SCREENS.WELCOME} component={WelcomeScreen}/>
<RootStack.Screen name={ROUTES.SCREENS.PINCODE} options={{gestureEnabled:false}} component={PinCode}/>
<RootStack.Screen name={ROUTES.SCREENS.WELCOME} component={WelcomeScreen} />
<RootStack.Screen
name={ROUTES.SCREENS.PINCODE}
options={{ gestureEnabled: false }}
component={PinCode}
/>
</RootStack.Navigator>
)
}
);
};

View File

@ -3,7 +3,6 @@ import coingeckoApi from '../../config/coingeckoApi';
import { convertMarketData } from './converters';
import { MarketData } from './models';
const PATH_COINS = 'coins';
const PATH_MARKET_CHART = 'market_chart';
@ -11,32 +10,31 @@ export const INTERVAL_HOURLY = 'hourly';
export const INTERVAL_DAILY = 'daily';
export const fetchMarketChart = async (
coingeckoId:string,
vs_currency:string,
days:number,
interval:'daily'|'hourly' = 'daily'
): Promise<MarketData> => {
try{
coingeckoId: string,
vs_currency: string,
days: number,
interval: 'daily' | 'hourly' = 'daily',
): Promise<MarketData> => {
try {
const params = {
vs_currency,
days,
interval
}
interval,
};
const res = await coingeckoApi.get(`/${PATH_COINS}/${coingeckoId}/${PATH_MARKET_CHART}`, {
params
params,
});
const rawData = res.data;
if(!rawData){
throw new Error("Tag name not available")
if (!rawData) {
throw new Error('Tag name not available');
}
const data = convertMarketData(rawData);
return data;
}catch(error){
} catch (error) {
bugsnagInstance.notify(error);
throw error;
}
}
};

View File

@ -1,23 +1,23 @@
import { ChartItem, MarketData } from "./models"
import { ChartItem, MarketData } from './models';
export const convertChartItem = (rawData:any) => {
if(!rawData){
export const convertChartItem = (rawData: any) => {
if (!rawData) {
return null;
}
return {
xValue:rawData[0],
yValue:rawData[1]
xValue: rawData[0],
yValue: rawData[1],
} as ChartItem;
}
};
export const convertMarketData = (rawData:any) => {
if(!rawData){
export const convertMarketData = (rawData: any) => {
if (!rawData) {
return null;
}
return {
prices:rawData.prices ? rawData.prices.map(convertChartItem) : [],
marketCaps:rawData.market_caps ? rawData.market_caps.map(convertChartItem) : [],
totalVolumes:rawData.total_volumes ? rawData.total_volumes.map(convertChartItem) : []
prices: rawData.prices ? rawData.prices.map(convertChartItem) : [],
marketCaps: rawData.market_caps ? rawData.market_caps.map(convertChartItem) : [],
totalVolumes: rawData.total_volumes ? rawData.total_volumes.map(convertChartItem) : [],
} as MarketData;
}
};

View File

@ -1,10 +1,10 @@
export interface ChartItem {
xValue:number,
yValue:number
xValue: number;
yValue: number;
}
export interface MarketData {
prices:Array<ChartItem>;
marketCaps:Array<ChartItem>;
totalVolumes:Array<ChartItem>;
prices: Array<ChartItem>;
marketCaps: Array<ChartItem>;
totalVolumes: Array<ChartItem>;
}

View File

@ -1,6 +1,12 @@
import { COIN_IDS } from '../../constants/defaultCoins';
import { Referral } from '../../models';
import { CommentHistoryItem, LatestMarketPrices, LatestQuotes, QuoteItem, ReferralStat } from './ecency.types';
import {
CommentHistoryItem,
LatestMarketPrices,
LatestQuotes,
QuoteItem,
ReferralStat,
} from './ecency.types';
export const convertReferral = (rawData: any) => {
return {
@ -19,23 +25,23 @@ export const convertReferralStat = (rawData: any) => {
} as ReferralStat;
};
export const convertQuoteItem = (rawData:any, currencyRate:number) => {
if(!rawData){
export const convertQuoteItem = (rawData: any, currencyRate: number) => {
if (!rawData) {
return null;
}
return {
price:rawData.price * currencyRate,
percentChange:rawData.percent_change,
lastUpdated:rawData.last_updated,
} as QuoteItem
}
price: rawData.price * currencyRate,
percentChange: rawData.percent_change,
lastUpdated: rawData.last_updated,
} as QuoteItem;
};
export const convertLatestQuotes = (rawData: any, currencyRate:number) => {
export const convertLatestQuotes = (rawData: any, currencyRate: number) => {
return {
[COIN_IDS.HIVE]:convertQuoteItem(rawData.hive.quotes.usd, currencyRate),
[COIN_IDS.HP]:convertQuoteItem(rawData.hive.quotes.usd, currencyRate),
[COIN_IDS.HBD]:convertQuoteItem(rawData.hbd.quotes.usd, currencyRate),
[COIN_IDS.ECENCY]:convertQuoteItem(rawData.estm.quotes.usd, currencyRate)
[COIN_IDS.HIVE]: convertQuoteItem(rawData.hive.quotes.usd, currencyRate),
[COIN_IDS.HP]: convertQuoteItem(rawData.hive.quotes.usd, currencyRate),
[COIN_IDS.HBD]: convertQuoteItem(rawData.hbd.quotes.usd, currencyRate),
[COIN_IDS.ECENCY]: convertQuoteItem(rawData.estm.quotes.usd, currencyRate),
} as LatestQuotes;
};

View File

@ -4,7 +4,6 @@ import ecencyApi from '../../config/ecencyApi';
import bugsnagInstance from '../../config/bugsnag';
import { EcencyUser, UserPoint } from './ecency.types';
/**
* Records user activty and reward poinsts
* @param ty points
@ -15,7 +14,6 @@ import { EcencyUser, UserPoint } from './ecency.types';
export const userActivity = async (ty: number, tx: string = '', bl: string | number = '') => {
try {
const data: {
ty: number;
bl?: string | number;
tx?: string | number;
@ -24,28 +22,27 @@ export const userActivity = async (ty: number, tx: string = '', bl: string | num
if (bl) data.bl = bl;
if (tx) data.tx = tx;
const response = await ecencyApi.post('/private-api/usr-activity', data)
const response = await ecencyApi.post('/private-api/usr-activity', data);
return response.data;
} catch (error) {
console.warn("Failed to push user activity point", error);
bugsnagInstance.notify(error)
throw error
}
}
export const getPointsSummary = async (username:string): Promise<EcencyUser> => {
try {
const data = {username};
const response = await ecencyApi.post('/private-api/points', data);
console.log("returning user points data", response.data);
return response.data;
} catch (error) {
console.warn("Failed to get points", error);
console.warn('Failed to push user activity point', error);
bugsnagInstance.notify(error);
throw new Error(error.response?.data?.message || error.message)
throw error;
}
}
};
export const getPointsSummary = async (username: string): Promise<EcencyUser> => {
try {
const data = { username };
const response = await ecencyApi.post('/private-api/points', data);
console.log('returning user points data', response.data);
return response.data;
} catch (error) {
console.warn('Failed to get points', error);
bugsnagInstance.notify(error);
throw new Error(error.response?.data?.message || error.message);
}
};
export const getPointsHistory = (username: string): Promise<UserPoint[]> =>
new Promise((resolve) => {
@ -60,18 +57,16 @@ export const getPointsHistory = (username: string): Promise<UserPoint[]> =>
});
});
export const claimPoints = async () => {
try {
const response = await ecencyApi.post('/private-api/points-claim')
const response = await ecencyApi.post('/private-api/points-claim');
return response.data;
} catch (error) {
console.warn("Failed to calim points", error);
console.warn('Failed to calim points', error);
bugsnagInstance.notify(error);
throw new Error(error.response?.data?.message || error.message)
throw new Error(error.response?.data?.message || error.message);
}
}
};
export const gameStatusCheck = (username, type) =>
new Promise((resolve, reject) => {

View File

@ -66,10 +66,10 @@ export interface CommentHistoryItem {
}
export interface PointActivity {
pointsTy:number;
username?:string;
transactionId?:string;
blockNum?:number|string;
pointsTy: number;
username?: string;
transactionId?: string;
blockNum?: number | string;
}
export enum ScheduledPostStatus {
@ -80,18 +80,17 @@ export enum ScheduledPostStatus {
}
export enum NotificationFilters {
ACTIVITIES = "activities",
RVOTES = "rvotes",
MENTIONS = "mentions",
FOLLOWS = "follows",
REPLIES = "replies",
REBLOGS = "reblogs",
TRANFERS = "transfers",
DELEGATIONS = "delegations",
FAVOURITES = "nfavorites"
ACTIVITIES = 'activities',
RVOTES = 'rvotes',
MENTIONS = 'mentions',
FOLLOWS = 'follows',
REPLIES = 'replies',
REBLOGS = 'reblogs',
TRANFERS = 'transfers',
DELEGATIONS = 'delegations',
FAVOURITES = 'nfavorites',
}
export enum PointActivityIds {
VIEW_POST = 10,
LOGIN = 20,

View File

@ -1,4 +1,3 @@
export interface Vote {
percent: number;
reputation: number;
@ -56,21 +55,20 @@ export interface MarketStatistics {
}
export interface OpenOrderItem {
id: number,
created: string,
expiration: string,
seller: string,
orderid: number,
for_sale: number,
id: number;
created: string;
expiration: string;
seller: string;
orderid: number;
for_sale: number;
sell_price: {
base: string,
quote: string
},
real_price: string,
rewarded: boolean
base: string;
quote: string;
};
real_price: string;
rewarded: boolean;
}
export interface OrdersDataItem {
created: string;
hbd: number;
@ -78,7 +76,7 @@ export interface OrdersDataItem {
order_price: {
base: string;
quote: string;
}
};
real_price: string;
}

View File

@ -28,8 +28,6 @@ interface MediaUploadVars {
addToUploads: boolean;
}
/** GET QUERIES **/
export const useMediaQuery = () => {
@ -54,12 +52,8 @@ export const useSnippetsQuery = () => {
});
};
/** ADD UPDATE MUTATIONS **/
export const useAddToUploadsMutation = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
@ -77,11 +71,9 @@ export const useAddToUploadsMutation = () => {
} else {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
}
}
})
}
},
});
};
export const useMediaUploadMutation = () => {
const intl = useIntl();
@ -89,8 +81,8 @@ export const useMediaUploadMutation = () => {
const addToUploadsMutation = useAddToUploadsMutation();
const currentAccount = useAppSelector(state => state.account.currentAccount);
const pinCode = useAppSelector(state => state.application.pin);
const currentAccount = useAppSelector((state) => state.account.currentAccount);
const pinCode = useAppSelector((state) => state.application.pin);
return useMutation<Image, undefined, MediaUploadVars>(
async ({ media }) => {
@ -111,7 +103,7 @@ export const useMediaUploadMutation = () => {
},
},
);
}
};
export const useSnippetsMutation = () => {
const intl = useIntl();
@ -164,26 +156,26 @@ export const useSnippetsMutation = () => {
);
};
/** DELETE MUTATIONS **/
export const useMediaDeleteMutation = () => {
const queryClient = useQueryClient();
const dispatch = useAppDispatch();
const intl = useIntl();
return useMutation<string[], undefined, string[]>(async (deleteIds) => {
return useMutation<string[], undefined, string[]>(
async (deleteIds) => {
for (const i in deleteIds) {
await deleteImage(deleteIds[i]);
}
return deleteIds;
}, {
},
{
retry: 3,
onSuccess: (deleteIds) => {
console.log('Success media deletion delete', deleteIds);
const data: MediaItem[] | undefined = queryClient.getQueryData([QUERIES.MEDIA.GET]);
if (data) {
const _newData = data.filter((item) => (!deleteIds.includes(item._id)))
const _newData = data.filter((item) => !deleteIds.includes(item._id));
queryClient.setQueryData([QUERIES.MEDIA.GET], _newData);
}
},
@ -191,10 +183,10 @@ export const useMediaDeleteMutation = () => {
dispatch(toastNotification(intl.formatMessage({ id: 'uploads_modal.delete_failed' })));
queryClient.invalidateQueries([QUERIES.MEDIA.GET]);
},
});
},
);
};
export const useSnippetDeleteMutation = () => {
const queryClient = useQueryClient();
const dispatch = useAppDispatch();

View File

@ -26,4 +26,4 @@ export const initQueryClient = () => {
export * from './notificationQueries';
export * from './draftQueries';
export * from './editorQueries';
export * from './pointQueries'
export * from './pointQueries';

View File

@ -1,77 +1,72 @@
import { useMutation } from "@tanstack/react-query"
import { useMutation } from '@tanstack/react-query';
import { useAppSelector, useAppDispatch } from '../../hooks';
import { deletePointActivityCache, updatePointActivityCache } from "../../redux/actions/cacheActions";
import { generateRndStr } from "../../utils/editor";
import { PointActivity, PointActivityIds } from "../ecency/ecency.types";
import { userActivity } from "../ecency/ePoint"
import {
deletePointActivityCache,
updatePointActivityCache,
} from '../../redux/actions/cacheActions';
import { generateRndStr } from '../../utils/editor';
import { PointActivity, PointActivityIds } from '../ecency/ecency.types';
import { userActivity } from '../ecency/ePoint';
interface UserActivityMutationVars {
pointsTy: PointActivityIds;
blockNum?: string|number;
blockNum?: string | number;
transactionId?: string;
cacheId?: string
cacheId?: string;
}
export const useUserActivityMutation = () => {
const dispatch = useAppDispatch();
const currentAccount = useAppSelector(state => state.account.currentAccount);
const pointActivitiesCache:Map<string, PointActivity> = useAppSelector(state => state.cache.pointActivities);
const currentAccount = useAppSelector((state) => state.account.currentAccount);
const pointActivitiesCache: Map<string, PointActivity> = useAppSelector(
(state) => state.cache.pointActivities,
);
const _mutationFn = async ({ pointsTy, blockNum, transactionId }: UserActivityMutationVars) => {
await userActivity(pointsTy, transactionId, blockNum)
await userActivity(pointsTy, transactionId, blockNum);
return true;
}
};
const mutation = useMutation<boolean, Error, UserActivityMutationVars>(_mutationFn, {
retry: 2,
onSuccess: (data, vars) => {
console.log("successfully logged activity", data, vars)
console.log('successfully logged activity', data, vars);
//remove entry from redux
if (vars.cacheId) {
console.log("must remove from redux")
dispatch(deletePointActivityCache(vars.cacheId))
console.log('must remove from redux');
dispatch(deletePointActivityCache(vars.cacheId));
}
},
onError: (error, vars) => {
console.log("failed to log activity", error, vars)
console.log('failed to log activity', error, vars);
//add entry in redux
if (!vars.cacheId && currentAccount) {
console.log("must add to from redux")
console.log('must add to from redux');
const cacheId = generateRndStr();
const { username } = currentAccount;
dispatch(updatePointActivityCache(cacheId, { ...vars, username }))
dispatch(updatePointActivityCache(cacheId, { ...vars, username }));
}
}
})
},
});
const lazyMutatePendingActivities = () => {
setTimeout(()=>{
setTimeout(() => {
//read pending activities from redux
if (currentAccount && pointActivitiesCache && pointActivitiesCache.size) {
Array.from(pointActivitiesCache).forEach(([id, activity]) => {
if (currentAccount.username === activity.username) {
mutation.mutate({
cacheId: id,
...activity,
})
});
}
})
});
}
}, 3000)
}
}, 3000);
};
return {
...mutation,
lazyMutatePendingActivities,
}
}
};
};

View File

@ -13,87 +13,99 @@ import {
DELETE_SUBSCRIBED_COMMUNITY_CACHE,
CLEAR_SUBSCRIBED_COMMUNITIES_CACHE,
DELETE_POINT_ACTIVITY_CACHE_ENTRY,
UPDATE_POINT_ACTIVITY_CACHE
UPDATE_POINT_ACTIVITY_CACHE,
} from '../constants/constants';
import { Comment, CommentCacheStatus, Draft, SubscribedCommunity, Vote } from '../reducers/cacheReducer';
import {
Comment,
CommentCacheStatus,
Draft,
SubscribedCommunity,
Vote,
} from '../reducers/cacheReducer';
export const updateVoteCache = (postPath: string, vote: Vote) => ({
payload: {
postPath,
vote
vote,
},
type: UPDATE_VOTE_CACHE
})
type: UPDATE_VOTE_CACHE,
});
interface CommentCacheOptions {
isUpdate?: boolean;
parentTags?: Array<string>;
}
export const updateCommentCache = (commentPath: string, comment: Comment, options: CommentCacheOptions = { isUpdate: false }) => {
console.log("body received:", comment.markdownBody);
export const updateCommentCache = (
commentPath: string,
comment: Comment,
options: CommentCacheOptions = { isUpdate: false },
) => {
console.log('body received:', comment.markdownBody);
const updated = new Date();
updated.setSeconds(updated.getSeconds() - 5); //make cache delayed by 5 seconds to avoid same updated stamp in post data
const updatedStamp = updated.toISOString().substring(0, 19); //server only return 19 character time string without timezone part
if (options.isUpdate && !comment.created) {
throw new Error("For comment update, created prop must be provided from original comment data to update local cache");
throw new Error(
'For comment update, created prop must be provided from original comment data to update local cache',
);
}
if (!options.parentTags && !comment.json_metadata) {
throw new Error("either of json_metadata in comment data or parentTags in options must be provided");
throw new Error(
'either of json_metadata in comment data or parentTags in options must be provided',
);
}
comment.created = comment.created || updatedStamp; //created will be set only once for new comment;
comment.updated = comment.updated || updatedStamp;
comment.expiresAt = comment.expiresAt || updated.getTime() + 6000000;//600000;
comment.expiresAt = comment.expiresAt || updated.getTime() + 6000000; //600000;
comment.active_votes = comment.active_votes || [];
comment.net_rshares = comment.net_rshares || 0;
comment.author_reputation = comment.author_reputation || 25;
comment.total_payout = comment.total_payout || 0;
comment.json_metadata = comment.json_metadata || makeJsonMetadataReply(options.parentTags)
comment.json_metadata = comment.json_metadata || makeJsonMetadataReply(options.parentTags);
comment.isDeletable = comment.isDeletable || true;
comment.status = comment.status || CommentCacheStatus.PENDING;
comment.body = renderPostBody({
comment.body = renderPostBody(
{
author: comment.author,
permlink: comment.permlink,
last_update: comment.updated,
body: comment.markdownBody,
}, true, Platform.OS === 'android');
},
true,
Platform.OS === 'android',
);
return ({
return {
payload: {
commentPath,
comment
comment,
},
type: UPDATE_COMMENT_CACHE
})
}
type: UPDATE_COMMENT_CACHE,
};
};
export const deleteCommentCacheEntry = (commentPath: string) => ({
payload: commentPath,
type: DELETE_COMMENT_CACHE_ENTRY
})
type: DELETE_COMMENT_CACHE_ENTRY,
});
export const updateDraftCache = (id: string, draft: Draft) => ({
payload: {
id,
draft
draft,
},
type: UPDATE_DRAFT_CACHE
})
type: UPDATE_DRAFT_CACHE,
});
export const deleteDraftCacheEntry = (id: string) => ({
payload: id,
type: DELETE_DRAFT_CACHE_ENTRY
})
type: DELETE_DRAFT_CACHE_ENTRY,
});
export const updateSubscribedCommunitiesCache = (data: any) => {
const path = data.communityId;
@ -102,43 +114,42 @@ export const updateSubscribedCommunitiesCache = (data: any) => {
const userRole = data.userRole ? data.userRole : '';
const userLabel = data.userLabel ? data.userLabel : '';
const subscribedCommunity:SubscribedCommunity = {
data : [data.communityId, communityTitle, userRole, userLabel, !data.isSubscribed],
expiresAt : created.getTime() + 86400000,
const subscribedCommunity: SubscribedCommunity = {
data: [data.communityId, communityTitle, userRole, userLabel, !data.isSubscribed],
expiresAt: created.getTime() + 86400000,
};
return ({
return {
payload: {
path,
subscribedCommunity
subscribedCommunity,
},
type: UPDATE_SUBSCRIBED_COMMUNITY_CACHE
})
}
type: UPDATE_SUBSCRIBED_COMMUNITY_CACHE,
};
};
export const deleteSubscribedCommunityCacheEntry = (path: string) => ({
payload: path,
type: DELETE_SUBSCRIBED_COMMUNITY_CACHE
})
type: DELETE_SUBSCRIBED_COMMUNITY_CACHE,
});
export const clearSubscribedCommunitiesCache = () => ({
type: CLEAR_SUBSCRIBED_COMMUNITIES_CACHE
})
type: CLEAR_SUBSCRIBED_COMMUNITIES_CACHE,
});
export const updatePointActivityCache = (id:string, pointActivity: PointActivity) => ({
export const updatePointActivityCache = (id: string, pointActivity: PointActivity) => ({
payload: {
id,
pointActivity
pointActivity,
},
type: UPDATE_POINT_ACTIVITY_CACHE
})
type: UPDATE_POINT_ACTIVITY_CACHE,
});
export const deletePointActivityCache = (id: string) => ({
payload: id,
type: DELETE_POINT_ACTIVITY_CACHE_ENTRY
})
type: DELETE_POINT_ACTIVITY_CACHE_ENTRY,
});
export const purgeExpiredCache = () => ({
type: PURGE_EXPIRED_CACHE
})
type: PURGE_EXPIRED_CACHE,
});

View File

@ -1,6 +1,13 @@
import { getLatestQuotes } from '../../providers/ecency/ecency';
import { fetchCoinsData } from '../../utils/wallet';
import { SET_SELECTED_COINS, SET_PRICE_HISTORY, SET_COINS_DATA, SET_COIN_ACTIVITIES, SET_COIN_QUOTES, RESET_WALLET_DATA } from '../constants/constants';
import {
SET_SELECTED_COINS,
SET_PRICE_HISTORY,
SET_COINS_DATA,
SET_COIN_ACTIVITIES,
SET_COIN_QUOTES,
RESET_WALLET_DATA,
} from '../constants/constants';
import { CoinActivitiesCollection, CoinBase, CoinData } from '../reducers/walletReducer';
import { AppDispatch, RootState } from '../store/store';
@ -9,55 +16,56 @@ export const setSelectedCoins = (coins: CoinBase[]) => ({
type: SET_SELECTED_COINS,
});
export const setCoinsData = (data: { [key: string]: CoinData }, vsCurrency: string, username: string) => ({
export const setCoinsData = (
data: { [key: string]: CoinData },
vsCurrency: string,
username: string,
) => ({
payload: {
data,
vsCurrency,
username,
},
type: SET_COINS_DATA
})
type: SET_COINS_DATA,
});
export const setPriceHistory = (coinId: string, vsCurrency: string, data: number[]) => ({
payload: {
id: coinId,
vsCurrency,
data
data,
},
type: SET_PRICE_HISTORY
})
type: SET_PRICE_HISTORY,
});
export const setCoinActivities = (coinId: string, data: CoinActivitiesCollection) => ({
payload: {
id: coinId,
data,
},
type: SET_COIN_ACTIVITIES
})
type: SET_COIN_ACTIVITIES,
});
export const resetWalletData = () => ({
type: RESET_WALLET_DATA
})
type: RESET_WALLET_DATA,
});
export const fetchCoinQuotes = () => (dispatch, getState) => {
const currency = getState().application.currency;
console.log("fetching quotes for currency", currency)
getLatestQuotes(currency.currencyRate)
.then((quotes) => {
console.log("Fetched quotes", quotes)
console.log('fetching quotes for currency', currency);
getLatestQuotes(currency.currencyRate).then((quotes) => {
console.log('Fetched quotes', quotes);
dispatch({
type: SET_COIN_QUOTES,
payload: { ...quotes },
})
})
}
});
});
};
export const fetchAndSetCoinsData = (refresh: boolean = false) => async (dispatch: AppDispatch, getState: RootState) => {
export const fetchAndSetCoinsData = (refresh: boolean = false) => async (
dispatch: AppDispatch,
getState: RootState,
) => {
const coins = getState().wallet.selectedCoins;
const quotes = getState().wallet.quotes;
const currentAccount = getState().account.currentAccount;
@ -71,12 +79,8 @@ export const fetchAndSetCoinsData = (refresh: boolean = false) => async (dispatc
currencyRate: currency.currencyRate,
globalProps,
quotes,
refresh
})
refresh,
});
return dispatch(setCoinsData(
coinsData,
currency.currency,
currentAccount.username
))
}
return dispatch(setCoinsData(coinsData, currency.currency, currentAccount.username));
};

View File

@ -1,6 +1,5 @@
export const statusMessage = {
PENDING : 'PENDING',
SUCCESS : 'SUCCESS',
FAIL : 'FAIL',
PENDING: 'PENDING',
SUCCESS: 'SUCCESS',
FAIL: 'FAIL',
};

View File

@ -12,25 +12,23 @@ import {
SET_GLOBAL_PROPS,
} from '../constants/constants';
export interface GlobalProps {
hivePerMVests:number;
base:number;
quote:number;
fundRecentClaims:number;
fundRewardBalance:number;
hbdPrintRate:number;
hivePerMVests: number;
base: number;
quote: number;
fundRecentClaims: number;
fundRewardBalance: number;
hbdPrintRate: number;
}
interface AccountState {
isFetching:boolean;
isFetching: boolean;
currentAccount: any;
otherAccounts: any[];
hasError: boolean;
errorMessage: string;
isLogingOut: boolean;
globalProps:GlobalProps|null;
globalProps: GlobalProps | null;
}
const initialState: AccountState = {
@ -40,7 +38,7 @@ const initialState: AccountState = {
hasError: false,
errorMessage: null,
isLogingOut: false,
globalProps: null
globalProps: null,
};
export default function (state = initialState, action) {

View File

@ -1,4 +1,4 @@
import { PointActivity } from "../../providers/ecency/ecency.types";
import { PointActivity } from '../../providers/ecency/ecency.types';
import {
PURGE_EXPIRED_CACHE,
UPDATE_VOTE_CACHE,
@ -10,8 +10,8 @@ import {
DELETE_SUBSCRIBED_COMMUNITY_CACHE,
CLEAR_SUBSCRIBED_COMMUNITIES_CACHE,
UPDATE_POINT_ACTIVITY_CACHE,
DELETE_POINT_ACTIVITY_CACHE_ENTRY
} from "../constants/constants";
DELETE_POINT_ACTIVITY_CACHE_ENTRY,
} from '../constants/constants';
export enum CommentCacheStatus {
PENDING = 'PENDING',
@ -28,52 +28,51 @@ export interface Vote {
}
export interface Comment {
author: string,
permlink: string,
parent_author: string,
parent_permlink: string,
body?: string,
markdownBody: string,
author_reputation?: number,
total_payout?: number,
net_rshares?: number,
active_votes?: Array<{ rshares: number, voter: string }>,
json_metadata?: any,
isDeletable?: boolean,
created?: string, //handle created and updated separatly
updated?: string,
expiresAt?: number,
status: CommentCacheStatus
author: string;
permlink: string;
parent_author: string;
parent_permlink: string;
body?: string;
markdownBody: string;
author_reputation?: number;
total_payout?: number;
net_rshares?: number;
active_votes?: Array<{ rshares: number; voter: string }>;
json_metadata?: any;
isDeletable?: boolean;
created?: string; //handle created and updated separatly
updated?: string;
expiresAt?: number;
status: CommentCacheStatus;
}
export interface Draft {
author: string,
body: string,
title?: string,
tags?: string,
meta?: any,
created?: number,
updated?: number,
author: string;
body: string;
title?: string;
tags?: string;
meta?: any;
created?: number;
updated?: number;
expiresAt?: number;
}
export interface SubscribedCommunity {
data: Array<any>,
data: Array<any>;
expiresAt?: number;
}
interface State {
votes: Map<string, Vote>
comments: Map<string, Comment> //TODO: handle comment array per post, if parent is same
drafts: Map<string, Draft>
subscribedCommunities: Map<string, SubscribedCommunity>
pointActivities: Map<string, PointActivity>
votes: Map<string, Vote>;
comments: Map<string, Comment>; //TODO: handle comment array per post, if parent is same
drafts: Map<string, Draft>;
subscribedCommunities: Map<string, SubscribedCommunity>;
pointActivities: Map<string, PointActivity>;
lastUpdate: {
postPath: string,
updatedAt: number,
type: 'vote' | 'comment' | 'draft',
}
postPath: string;
updatedAt: number;
type: 'vote' | 'comment' | 'draft';
};
}
const initialState: State = {
@ -99,7 +98,7 @@ export default function (state = initialState, action) {
postPath: payload.postPath,
updatedAt: new Date().getTime(),
type: 'vote',
}
},
};
case UPDATE_COMMENT_CACHE:
@ -112,15 +111,15 @@ export default function (state = initialState, action) {
lastUpdate: {
postPath: payload.commentPath,
updatedAt: new Date().getTime(),
type: 'comment'
}
type: 'comment',
},
};
case DELETE_COMMENT_CACHE_ENTRY:
if (state.comments && state.comments.has(payload)) {
state.comments.delete(payload);
}
return { ...state }
return { ...state };
case UPDATE_DRAFT_CACHE:
if (!state.drafts) {
@ -133,7 +132,7 @@ export default function (state = initialState, action) {
payloadDraft.created = curDraft ? curDraft.created : curTime;
payloadDraft.updated = curTime;
payloadDraft.expiresAt = curTime + 604800000 // 7 days ms
payloadDraft.expiresAt = curTime + 604800000; // 7 days ms
state.drafts.set(payload.id, payloadDraft);
return {
@ -149,7 +148,7 @@ export default function (state = initialState, action) {
if (state.drafts && state.drafts.has(payload)) {
state.drafts.delete(payload);
}
return { ...state }
return { ...state };
case UPDATE_SUBSCRIBED_COMMUNITY_CACHE:
if (!state.subscribedCommunities) {
@ -159,19 +158,19 @@ export default function (state = initialState, action) {
subscribedCommunities.set(payload.path, payload.subscribedCommunity);
return {
...state, //spread operator in requried here, otherwise persist do not register change
subscribedCommunities: subscribedCommunities
subscribedCommunities: subscribedCommunities,
};
case DELETE_SUBSCRIBED_COMMUNITY_CACHE:
if (state.subscribedCommunities && state.subscribedCommunities.has(payload)) {
state.subscribedCommunities.delete(payload);
}
return { ...state }
return { ...state };
case CLEAR_SUBSCRIBED_COMMUNITIES_CACHE:
state.subscribedCommunities = new Map<string, SubscribedCommunity>();
return { ...state }
return { ...state };
case UPDATE_POINT_ACTIVITY_CACHE:
if (!state.pointActivities) {
@ -186,7 +185,7 @@ export default function (state = initialState, action) {
if (state.pointActivities && state.pointActivities.has(payload)) {
state.pointActivities.delete(payload);
}
return { ...state }
return { ...state };
case PURGE_EXPIRED_CACHE:
const currentTime = new Date().getTime();
@ -196,7 +195,7 @@ export default function (state = initialState, action) {
if (entry[1].expiresAt < currentTime) {
state.votes.delete(entry[0]);
}
})
});
}
if (state.comments && state.comments.size) {
@ -204,7 +203,7 @@ export default function (state = initialState, action) {
if (entry[1].expiresAt < currentTime) {
state.comments.delete(entry[0]);
}
})
});
}
if (state.drafts && state.drafts.size) {
@ -212,9 +211,7 @@ export default function (state = initialState, action) {
if (entry[1].expiresAt < currentTime || !entry[1].body) {
state.drafts.delete(entry[0]);
}
})
});
}
if (state.subscribedCommunities && state.subscribedCommunities.size) {
@ -222,14 +219,13 @@ export default function (state = initialState, action) {
if (entry[1].expiresAt < currentTime) {
state.subscribedCommunities.delete(entry[0]);
}
})
});
}
return {
...state
}
...state,
};
default:
return state;
}
}

View File

@ -1,37 +1,36 @@
import { REMOVE_BENEFICIARIES, SET_BENEFICIARIES } from "../constants/constants";
import { REMOVE_BENEFICIARIES, SET_BENEFICIARIES } from '../constants/constants';
export interface Beneficiary {
account:string,
weight:number,
isValid?:boolean,
autoPowerUp?: boolean,
account: string;
weight: number;
isValid?: boolean;
autoPowerUp?: boolean;
}
interface State {
beneficiariesMap:{
[key: string]: Beneficiary[]
}
beneficiariesMap: {
[key: string]: Beneficiary[];
};
}
const initialState:State = {
beneficiariesMap:{}
};
const initialState: State = {
beneficiariesMap: {},
};
export default function (state = initialState, action) {
const {type, payload} = action;
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_BENEFICIARIES:
state.beneficiariesMap[payload.draftId] = payload.benficiaries;
return {
...state //spread operator in requried here, otherwise persist do not register change
...state, //spread operator in requried here, otherwise persist do not register change
};
case REMOVE_BENEFICIARIES:
delete state.beneficiariesMap[payload.draftId]
delete state.beneficiariesMap[payload.draftId];
return {
...state //spread operator in requried here, otherwise persist do not register change
...state, //spread operator in requried here, otherwise persist do not register change
};
default:
return state;
}
}
}

View File

@ -1,136 +1,140 @@
import DEFAULT_COINS from "../../constants/defaultCoins";
import { SET_PRICE_HISTORY, SET_SELECTED_COINS, SET_COINS_DATA, SET_COIN_ACTIVITIES, SET_COIN_QUOTES, RESET_WALLET_DATA } from "../constants/constants";
import DEFAULT_COINS from '../../constants/defaultCoins';
import {
SET_PRICE_HISTORY,
SET_SELECTED_COINS,
SET_COINS_DATA,
SET_COIN_ACTIVITIES,
SET_COIN_QUOTES,
RESET_WALLET_DATA,
} from '../constants/constants';
export interface DataPair {
value:string|number;
dataKey:string;
isClickable?:boolean;
value: string | number;
dataKey: string;
isClickable?: boolean;
}
export interface CoinBase {
id:string,
symbol:string,
notCrypto:boolean,
id: string;
symbol: string;
notCrypto: boolean;
}
export interface CoinData {
currentPrice:number;
balance:number;
savings?:number;
unclaimedBalance:string,
estimateValue?:number;
vsCurrency:string;
actions:string[];
extraDataPairs?:DataPair[];
currentPrice: number;
balance: number;
savings?: number;
unclaimedBalance: string;
estimateValue?: number;
vsCurrency: string;
actions: string[];
extraDataPairs?: DataPair[];
}
export interface PriceHistory {
expiresAt:number;
vsCurrency:string;
data:number[];
expiresAt: number;
vsCurrency: string;
data: number[];
}
export interface CoinActivity {
trxIndex:number;
trxIndex: number;
iconType: string;
textKey: string;
created: string;
expires: string;
icon: string;
value:string;
value: string;
details: string;
memo: string;
}
export interface QuoteItem {
lastUpdated:string;
percentChange:number;
price:number;
lastUpdated: string;
percentChange: number;
price: number;
}
export interface CoinActivitiesCollection {
completed:CoinActivity[],
pending:CoinActivity[],
completed: CoinActivity[];
pending: CoinActivity[];
}
interface State {
selectedCoins:CoinBase[];
coinsData:{
selectedCoins: CoinBase[];
coinsData: {
[key: string]: CoinData;
},
priceHistories:{
};
priceHistories: {
[key: string]: PriceHistory;
}
coinsActivities:{
[key: string]:CoinActivitiesCollection;
},
quotes:{
};
coinsActivities: {
[key: string]: CoinActivitiesCollection;
};
quotes: {
[key: string]: QuoteItem;
}
vsCurrency:string,
username:string,
updateTimestamp:number;
};
vsCurrency: string;
username: string;
updateTimestamp: number;
}
const initialState:State = {
selectedCoins:DEFAULT_COINS,
coinsData:{},
priceHistories:{},
coinsActivities:{},
const initialState: State = {
selectedCoins: DEFAULT_COINS,
coinsData: {},
priceHistories: {},
coinsActivities: {},
quotes: null,
vsCurrency:'',
username:'',
updateTimestamp:0
vsCurrency: '',
username: '',
updateTimestamp: 0,
};
export default function (state = initialState, action) {
const {type, payload} = action;
const { type, payload } = action;
switch (type) {
case RESET_WALLET_DATA:{
case RESET_WALLET_DATA: {
return {
...initialState,
selectedCoins:state.selectedCoins
selectedCoins: state.selectedCoins,
};
}
}
case SET_SELECTED_COINS:{
case SET_SELECTED_COINS: {
return {
...state,
selectedCoin:payload
selectedCoin: payload,
};
}
}
case SET_COINS_DATA:{
case SET_COINS_DATA: {
return {
...state,
coinsData:payload.data,
vsCurrency:payload.vsCurrency,
username:payload.username,
updateTimestamp:new Date().getTime()
coinsData: payload.data,
vsCurrency: payload.vsCurrency,
username: payload.username,
updateTimestamp: new Date().getTime(),
};
}
}
case SET_PRICE_HISTORY:{
case SET_PRICE_HISTORY: {
state.priceHistories[payload.id] = {
expiresAt:new Date().getTime() + ONE_HOUR_MS,
vsCurrency:payload.vsCurrency,
data:payload.data
expiresAt: new Date().getTime() + ONE_HOUR_MS,
vsCurrency: payload.vsCurrency,
data: payload.data,
};
return {
...state
...state,
};
}
}
case SET_COIN_ACTIVITIES:{
state.coinsActivities[payload.id] = payload.data
return {
...state
}
}
case SET_COIN_QUOTES:{
case SET_COIN_ACTIVITIES: {
state.coinsActivities[payload.id] = payload.data;
return {
...state,
quotes:payload,
};
}
case SET_COIN_QUOTES: {
return {
...state,
quotes: payload,
};
}
default:
return state;

View File

@ -23,7 +23,7 @@ const transformCacheVoteMap = createTransform(
comments: new Map(outboundState.comments),
drafts: new Map(outboundState.drafts),
subscribedCommunities: new Map(outboundState.subscribedCommunities),
pointActivities: new Map(outboundState.pointActivities)
pointActivities: new Map(outboundState.pointActivities),
}),
{ whitelist: ['cache'] },
);

View File

@ -3,8 +3,6 @@ import { StatusBar, Platform, View, Alert, Text } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import { AppNavigator } from '../../../navigation';
@ -34,7 +32,6 @@ import {
import darkTheme from '../../../themes/darkTheme';
import lightTheme from '../../../themes/lightTheme';
class ApplicationScreen extends Component {
constructor(props) {
super(props);
@ -97,7 +94,6 @@ class ApplicationScreen extends Component {
}
}
_renderStatusBar() {
const { isDarkTheme } = this.props;
const barStyle = isDarkTheme ? 'light-content' : 'dark-content';
@ -110,10 +106,9 @@ class ApplicationScreen extends Component {
<StatusBar barStyle={barStyle} backgroundColor={barColor} />
)}
</>
)
);
}
_renderAppNavigator() {
const { isConnected } = this.props;
return (
@ -121,13 +116,10 @@ class ApplicationScreen extends Component {
{!isConnected && <NoInternetConnection />}
<AppNavigator />
</Fragment>
)
);
}
_renderAppModals() {
const { toastNotification, foregroundNotificationData } = this.props;
const { isShowToastNotification } = this.state;
@ -148,11 +140,9 @@ class ApplicationScreen extends Component {
/>
)}
</>
)
);
}
render() {
return (
<View style={{ flex: 1 }}>

View File

@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react';
import Orientation, { useDeviceOrientationChange } from 'react-native-orientation-locker';
import { isLandscape } from 'react-native-device-info';
import EStyleSheet from 'react-native-extended-stylesheet';
import { AppState } from 'react-native';
import { useAppDispatch, useAppSelector } from '../../../hooks';
import { setDeviceOrientation, setLockedOrientation } from '../../../redux/actions/uiAction';
import { orientations } from '../../../redux/constants/orientationsConstants';
@ -9,9 +10,6 @@ import isAndroidTablet from '../../../utils/isAndroidTablet';
import darkTheme from '../../../themes/darkTheme';
import lightTheme from '../../../themes/lightTheme';
import { useUserActivityMutation } from '../../../providers/queries';
import { AppState } from 'react-native';
export const useInitApplication = () => {
const dispatch = useAppDispatch();
@ -47,21 +45,15 @@ export const useInitApplication = () => {
return _cleanup;
}, []);
const _cleanup = () => {
AppState.removeEventListener('change', _handleAppStateChange);
}
};
const _handleAppStateChange = (nextAppState) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
userActivityMutation.lazyMutatePendingActivities();
}
appState.current = nextAppState;
};
};

View File

@ -6,10 +6,10 @@ export default EStyleSheet.create({
borderBottomWidth: EStyleSheet.hairlineWidth,
borderColor: '$darkGrayBackground',
marginBottom: 0, //without 0 margin, view will start overlapping UserRibbon
paddingBottom: 32
paddingBottom: 32,
} as ViewStyle,
listContainer: {
paddingTop: 16
} as ViewStyle
paddingTop: 16,
} as ViewStyle,
});

View File

@ -1,4 +1,4 @@
import React, { ComponentType, JSXElementConstructor, ReactElement } from 'react'
import React, { ComponentType, JSXElementConstructor, ReactElement } from 'react';
import { useIntl } from 'react-intl';
import { SectionList, Text, RefreshControl, ActivityIndicator } from 'react-native';
import { Transaction } from '../../../components';
@ -7,7 +7,7 @@ import { CoinActivity } from '../../../redux/reducers/walletReducer';
import styles from './children.styles';
interface ActivitiesListProps {
header: ComponentType<any> | ReactElement<any, string | JSXElementConstructor<any>>
header: ComponentType<any> | ReactElement<any, string | JSXElementConstructor<any>>;
pendingActivities: CoinActivity[];
completedActivities: CoinActivity[];
refreshing: boolean;
@ -16,7 +16,6 @@ interface ActivitiesListProps {
onRefresh: () => void;
}
const ActivitiesList = ({
header,
loading,
@ -24,31 +23,29 @@ const ActivitiesList = ({
completedActivities,
pendingActivities,
onEndReached,
onRefresh
onRefresh,
}: ActivitiesListProps) => {
const intl = useIntl();
const isDarkTheme = useAppSelector(state => state.ui.isDarkTheme);
const isDarkTheme = useAppSelector((state) => state.ui.isDarkTheme);
const _renderActivityItem = ({ item, index }) => {
return <Transaction item={item} index={index} />
}
return <Transaction item={item} index={index} />;
};
const sections = [];
if (pendingActivities && pendingActivities.length) {
sections.push({
title: intl.formatMessage({ id: 'wallet.pending_requests' }),
data: pendingActivities
})
data: pendingActivities,
});
}
sections.push({
title: intl.formatMessage({ id: 'wallet.activities' }),
data: completedActivities || []
})
data: completedActivities || [],
});
const _refreshControl = (
<RefreshControl
@ -59,8 +56,7 @@ const ActivitiesList = ({
titleColor="#fff"
colors={['#fff']}
/>
)
);
return (
<SectionList
@ -72,14 +68,16 @@ const ActivitiesList = ({
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.textActivities}>{title}</Text>
)}
ListFooterComponent={loading && <ActivityIndicator style={styles.activitiesFooterIndicator} />}
ListFooterComponent={
loading && <ActivityIndicator style={styles.activitiesFooterIndicator} />
}
ListHeaderComponent={header}
refreshControl={_refreshControl}
onEndReached={()=>{onEndReached()}}
onEndReached={() => {
onEndReached();
}}
/>
)
}
export default ActivitiesList
);
};
export default ActivitiesList;

View File

@ -1,147 +1,143 @@
import { TextStyle, ViewStyle } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
export const CHART_NEGATIVE_MARGIN = 12
export const CHART_NEGATIVE_MARGIN = 12;
export default EStyleSheet.create({
card: {
marginVertical:8,
borderRadius:12,
marginVertical: 8,
borderRadius: 12,
overflow: 'hidden',
backgroundColor: '$primaryLightBackground'
backgroundColor: '$primaryLightBackground',
} as ViewStyle,
basicsContainer:{
alignItems:'center',
padding:16
basicsContainer: {
alignItems: 'center',
padding: 16,
} as ViewStyle,
coinTitleContainer:{
flexDirection:'row',
marginTop:8
coinTitleContainer: {
flexDirection: 'row',
marginTop: 8,
} as ViewStyle,
textCoinTitle:{
textCoinTitle: {
color: '$primaryBlack',
fontSize: 34,
fontWeight:'700',
fontWeight: '700',
} as TextStyle,
textHeaderChange:{
textHeaderChange: {
color: '$primaryDarkText',
fontSize: 16,
marginBottom:32,
marginBottom: 32,
} as TextStyle,
textPositive:{
color: '$primaryGreen'
textPositive: {
color: '$primaryGreen',
} as TextStyle,
textNegative:{
color: '$primaryRed'
textNegative: {
color: '$primaryRed',
} as TextStyle,
textBasicValue:{
textBasicValue: {
color: '$primaryBlack',
fontWeight:'700',
fontWeight: '700',
fontSize: 28,
} as TextStyle,
textBasicLabel:{
textBasicLabel: {
color: '$primaryDarkText',
fontSize: 14,
marginBottom:16,
marginBottom: 16,
} as TextStyle,
extraDataContainer:{
flexDirection:'row',
extraDataContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems:'center',
width:'100%',
alignItems: 'center',
width: '100%',
marginVertical: 2,
} as ViewStyle,
textExtraValue:{
textExtraValue: {
color: '$primaryDarkText',
fontWeight:'700',
fontWeight: '700',
fontSize: 18,
} as TextStyle,
textExtraLabel:{
textExtraLabel: {
color: '$primaryDarkText',
fontSize: 14,
} as TextStyle,
rangeContainer:{
flexDirection:'row',
alignItems:'center',
justifyContent:'space-between',
borderRadius:32,
rangeContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderRadius: 32,
} as ViewStyle,
rangeOptionWrapper:{
borderRadius:32,
paddingVertical:16,
paddingHorizontal:24
rangeOptionWrapper: {
borderRadius: 32,
paddingVertical: 16,
paddingHorizontal: 24,
} as ViewStyle,
textRange:{
fontSize:16,
textRange: {
fontSize: 16,
} as TextStyle,
chartContainer:{
chartContainer: {
height: 168,
marginTop: 16,
marginLeft:-CHART_NEGATIVE_MARGIN,
marginLeft: -CHART_NEGATIVE_MARGIN,
overflow: 'hidden',
} as ViewStyle,
list:{
flex:1,
list: {
flex: 1,
} as ViewStyle,
listContent: {
paddingBottom: 56,
paddingHorizontal: 16,
} as ViewStyle,
listContent:{
paddingBottom:56,
paddingHorizontal:16,
} as ViewStyle ,
//COIN ACTIONS STYLES
actionBtnContainer:{
flexGrow:1
actionBtnContainer: {
flexGrow: 1,
} as ViewStyle,
actionsContainer:{
flexDirection:'row',
flexWrap:'wrap'
actionsContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
} as ViewStyle,
actionContainer:{
paddingHorizontal:16,
marginVertical:8,
marginHorizontal:4,
backgroundColor:'$primaryLightBackground',
actionContainer: {
paddingHorizontal: 16,
marginVertical: 8,
marginHorizontal: 4,
backgroundColor: '$primaryLightBackground',
height: 40,
borderRadius:20,
justifyContent:'center',
alignItems:'center',
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
actionText:{
color: '$primaryBlack'
actionText: {
color: '$primaryBlack',
} as TextStyle,
textActivities:{
color:'$primaryBlack',
fontWeight:'600',
fontSize:18,
paddingVertical:16,
backgroundColor:'$primaryBackgroundColor',
textActivities: {
color: '$primaryBlack',
fontWeight: '600',
fontSize: 18,
paddingVertical: 16,
backgroundColor: '$primaryBackgroundColor',
textAlign: 'left',
} as TextStyle,
activitiesFooterIndicator:{
marginVertical: 16
activitiesFooterIndicator: {
marginVertical: 16,
} as ViewStyle,
delegationsModal:{
delegationsModal: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
margin: 0,
paddingTop: 32,
paddingBottom: 16,
},
textUnderline:{
textDecorationLine:'underline',
textDecorationColor:'$primaryDarkText'
}
textUnderline: {
textDecorationLine: 'underline',
textDecorationColor: '$primaryDarkText',
},
});

View File

@ -1,9 +1,9 @@
import React, { Fragment } from 'react'
import { useIntl } from 'react-intl'
import { View, Text } from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler'
import { withNavigation } from '@react-navigation/compat'
import styles from './children.styles'
import React, { Fragment } from 'react';
import { useIntl } from 'react-intl';
import { View, Text } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { withNavigation } from '@react-navigation/compat';
import styles from './children.styles';
interface CoinActionsProps {
actions: string[];
@ -14,25 +14,23 @@ export const CoinActions = withNavigation(({ actions, onActionPress }: CoinActio
const intl = useIntl();
const _renderItem = (item: string, index: number) => {
const _onPress = () => {
onActionPress(item)
}
onActionPress(item);
};
return (
<TouchableOpacity key={`action-${item}-${index}`} style={styles.actionContainer} containerStyle={styles.actionBtnContainer} onPress={_onPress}>
<TouchableOpacity
key={`action-${item}-${index}`}
style={styles.actionContainer}
containerStyle={styles.actionBtnContainer}
onPress={_onPress}
>
<Fragment>
<Text style={styles.actionText}>
{intl.formatMessage({ id: `wallet.${item}` })}
</Text>
<Text style={styles.actionText}>{intl.formatMessage({ id: `wallet.${item}` })}</Text>
</Fragment>
</TouchableOpacity>
)
}
);
};
return (
<View style={styles.actionsContainer}>
{actions.map(_renderItem)}
</View>
)
return <View style={styles.actionsContainer}>{actions.map(_renderItem)}</View>;
});

View File

@ -1,70 +1,71 @@
import React, { Fragment } from 'react'
import React, { Fragment } from 'react';
import { useIntl } from 'react-intl';
import { View, Text, Alert } from 'react-native'
import { View, Text, Alert } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { DataPair } from '../../../redux/reducers/walletReducer'
import styles from './children.styles'
import { DataPair } from '../../../redux/reducers/walletReducer';
import styles from './children.styles';
interface CoinBasicsProps {
valuePairs: DataPair[];
extraData: DataPair[];
coinSymbol: string;
percentChange: number;
onInfoPress: (id:string)=>void
onInfoPress: (id: string) => void;
}
export const CoinBasics = ({ valuePairs, extraData, coinSymbol, percentChange, onInfoPress}: CoinBasicsProps) => {
export const CoinBasics = ({
valuePairs,
extraData,
coinSymbol,
percentChange,
onInfoPress,
}: CoinBasicsProps) => {
const intl = useIntl();
const _renderCoinHeader = (
<>
<View style={styles.coinTitleContainer}>
<Text style={styles.textCoinTitle}>{coinSymbol}</Text>
</View>
<Text
style={styles.textHeaderChange}>
<Text style={styles.textHeaderChange}>
{intl.formatMessage({ id: 'wallet.change' })}
<Text
style={percentChange > 0 ? styles.textPositive : styles.textNegative}>
<Text style={percentChange > 0 ? styles.textPositive : styles.textNegative}>
{` ${percentChange >= 0 ? '+' : ''}${percentChange.toFixed(1)}%`}
</Text>
</Text>
</>
)
);
const _renderValuePair = (args: DataPair, index: number) => {
const label = intl.formatMessage({ id: `wallet.${args.dataKey}` })
const label = intl.formatMessage({ id: `wallet.${args.dataKey}` });
return (
<Fragment key={`basic-data-${args.dataKey}-${index}`}>
<Text style={styles.textBasicValue}>{args.value}</Text>
<Text style={styles.textBasicLabel}>{label}</Text>
</Fragment>
)
}
);
};
const _renderExtraData = (args: DataPair, index: number) => {
const label = intl.formatMessage({ id: `wallet.${args.dataKey || args.labelId}` })
const label = intl.formatMessage({ id: `wallet.${args.dataKey || args.labelId}` });
const _onPress = () => {
onInfoPress(args.dataKey);
}
};
return (
<View key={`extra-data-${args.dataKey}-${index}`} style={styles.extraDataContainer}>
<Text
style={[styles.textExtraLabel, args.isClickable && styles.textUnderline]}
onPress={args.isClickable && _onPress}>
onPress={args.isClickable && _onPress}
>
{label}
</Text>
<Text
style={styles.textExtraValue}
onPress={args.isClickable && _onPress}>
<Text style={styles.textExtraValue} onPress={args.isClickable && _onPress}>
{args.value}
</Text>
</View>
)
}
);
};
return (
<View style={[styles.card, styles.basicsContainer]}>
@ -72,5 +73,5 @@ export const CoinBasics = ({ valuePairs, extraData, coinSymbol, percentChange, o
{valuePairs.map(_renderValuePair)}
{extraData && extraData.map(_renderExtraData)}
</View>
)
}
);
};

View File

@ -1,34 +1,31 @@
import React, { useState, useEffect } from 'react'
import { View } from 'react-native'
import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import { RangeSelector } from '.';
import { SimpleChart } from '../../../components'
import { SimpleChart } from '../../../components';
import { useAppSelector } from '../../../hooks';
import { fetchMarketChart } from '../../../providers/coingecko/coingecko';
import getWindowDimensions from '../../../utils/getWindowDimensions';
import styles, { CHART_NEGATIVE_MARGIN } from './children.styles';
interface CoinChartProps {
coinId:string;
coinId: string;
}
export const CoinChart = ({coinId}:CoinChartProps) => {
const priceHistory = useAppSelector(state=>state.wallet.priceHistories[coinId]);
export const CoinChart = ({ coinId }: CoinChartProps) => {
const priceHistory = useAppSelector((state) => state.wallet.priceHistories[coinId]);
const [range, setRange] = useState(1);
const [chartData, setChartData] = useState(priceHistory?.data);
const _fetchMarketData = async (days:number) => {
const marketData = await fetchMarketChart(coinId, 'usd', days, 'hourly')
setChartData(marketData.prices.map(item=>item.yValue));
}
const _fetchMarketData = async (days: number) => {
const marketData = await fetchMarketChart(coinId, 'usd', days, 'hourly');
setChartData(marketData.prices.map((item) => item.yValue));
};
const _onRangeChange = (range) => {
setRange(range);
_fetchMarketData(range);
}
};
const _renderGraph = () => {
const _baseWidth = getWindowDimensions().width - 32 + CHART_NEGATIVE_MARGIN;
@ -42,13 +39,12 @@ export const CoinChart = ({coinId}:CoinChartProps) => {
showLabels={true}
/>
</View>
)}
);
};
return (
<>
<View style={styles.card}>
{_renderGraph()}
</View>
<View style={styles.card}>{_renderGraph()}</View>
<RangeSelector range={range} onRangeChange={_onRangeChange} />
</>
)
}
);
};

View File

@ -1,17 +1,17 @@
import React from 'react'
import { View } from 'react-native'
import { CoinActions, CoinBasics, CoinChart } from '.'
import { FormattedCurrency } from '../../../components'
import { COIN_IDS } from '../../../constants/defaultCoins'
import { CoinData, DataPair } from '../../../redux/reducers/walletReducer'
import React from 'react';
import { View } from 'react-native';
import { CoinActions, CoinBasics, CoinChart } from '.';
import { FormattedCurrency } from '../../../components';
import { COIN_IDS } from '../../../constants/defaultCoins';
import { CoinData, DataPair } from '../../../redux/reducers/walletReducer';
export interface CoinSummaryProps {
id:string;
coinSymbol:string;
coinData:CoinData;
percentChagne:number;
onActionPress:(action:string)=>void;
onInfoPress:(dataKey:string)=>void;
id: string;
coinSymbol: string;
coinData: CoinData;
percentChagne: number;
onActionPress: (action: string) => void;
onInfoPress: (dataKey: string) => void;
}
export const CoinSummary = ({
@ -20,35 +20,29 @@ export const CoinSummary = ({
coinData,
percentChagne,
onActionPress,
onInfoPress
}:CoinSummaryProps) => {
const {
balance,
estimateValue,
savings,
extraDataPairs,
actions
} = coinData
onInfoPress,
}: CoinSummaryProps) => {
const { balance, estimateValue, savings, extraDataPairs, actions } = coinData;
const valuePairs = [
{
dataKey:'amount_desc',
value:balance
}
] as DataPair[]
dataKey: 'amount_desc',
value: balance,
},
] as DataPair[];
if(estimateValue !== undefined){
if (estimateValue !== undefined) {
valuePairs.push({
dataKey:'estimated_value',
value:<FormattedCurrency isApproximate isToken value={estimateValue} />,
})
dataKey: 'estimated_value',
value: <FormattedCurrency isApproximate isToken value={estimateValue} />,
});
}
if(savings !== undefined){
if (savings !== undefined) {
valuePairs.push({
dataKey:'savings',
value:savings
})
dataKey: 'savings',
value: savings,
});
}
return (
@ -60,10 +54,8 @@ export const CoinSummary = ({
percentChange={percentChagne}
onInfoPress={onInfoPress}
/>
<CoinActions actions={actions} onActionPress={onActionPress}/>
{
id !== COIN_IDS.ECENCY && id !== COIN_IDS.HP && <CoinChart coinId={id} />
}
<CoinActions actions={actions} onActionPress={onActionPress} />
{id !== COIN_IDS.ECENCY && id !== COIN_IDS.HP && <CoinChart coinId={id} />}
</View>
)
}
);
};

View File

@ -1,18 +1,18 @@
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import AccountListContainer from '../../../containers/accountListContainer'
import { useIntl } from 'react-intl'
import { FlatList } from 'react-native-gesture-handler'
import { useNavigation } from '@react-navigation/native'
import ROUTES from '../../../constants/routeNames'
import { StackNavigationProp } from '@react-navigation/stack'
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { useIntl } from 'react-intl';
import { FlatList } from 'react-native-gesture-handler';
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RefreshControl } from 'react-native';
import unionBy from 'lodash/unionBy';
import AccountListContainer from '../../../containers/accountListContainer';
import ROUTES from '../../../constants/routeNames';
import styles from './children.styles';
import { BasicHeader, Modal, UserListItem } from '../../../components'
import { useAppSelector } from '../../../hooks'
import { getVestingDelegations } from '../../../providers/hive/dhive'
import { RefreshControl } from 'react-native'
import { getReceivedVestingShares } from '../../../providers/ecency/ecency'
import { vestsToHp } from '../../../utils/conversions'
import unionBy from 'lodash/unionBy'
import { BasicHeader, Modal, UserListItem } from '../../../components';
import { useAppSelector } from '../../../hooks';
import { getVestingDelegations } from '../../../providers/hive/dhive';
import { getReceivedVestingShares } from '../../../providers/ecency/ecency';
import { vestsToHp } from '../../../utils/conversions';
export enum MODES {
DELEGATEED = 'delegated_hive_power',
@ -25,13 +25,13 @@ interface DelegationItem {
timestamp: string;
}
export const DelegationsModal = forwardRef(({ }, ref) => {
export const DelegationsModal = forwardRef(({}, ref) => {
const intl = useIntl();
const navigation = useNavigation<StackNavigationProp<any>>();
const currentAccount = useAppSelector(state => state.account.currentAccount);
const globalProps = useAppSelector(state => state.account.globalProps);
const isDarkTheme = useAppSelector(state => state.application.isDarkTheme);
const currentAccount = useAppSelector((state) => state.account.currentAccount);
const globalProps = useAppSelector((state) => state.account.globalProps);
const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme);
const [delegations, setDelegations] = useState<DelegationItem[]>([]);
const [showModal, setShowModal] = useState(false);
@ -40,51 +40,50 @@ export const DelegationsModal = forwardRef(({ }, ref) => {
useImperativeHandle(ref, () => ({
showModal: (_mode: MODES) => {
setDelegations([])
setShowModal(true)
setMode(_mode)
}
}))
setDelegations([]);
setShowModal(true);
setMode(_mode);
},
}));
useEffect(() => {
if (showModal) {
_getDelegations()
_getDelegations();
}
}, [mode, showModal])
}, [mode, showModal]);
const _getVestingDelegations = async (startUsername: string = '') => {
let resData:any = []
let resData: any = [];
let limit = 1000;
const response = await getVestingDelegations(currentAccount.username, startUsername, limit);
resData = response.map((item) => ({
resData = response.map(
(item) =>
({
username: item.delegatee,
vestingShares: item.vesting_shares,
timestamp: item.min_delegation_time
} as DelegationItem))
timestamp: item.min_delegation_time,
} as DelegationItem),
);
if (resData.length === limit) {
const data = await _getVestingDelegations(response[response.length - 1].delegatee)
resData = unionBy(resData, data, 'username')
const data = await _getVestingDelegations(response[response.length - 1].delegatee);
resData = unionBy(resData, data, 'username');
}
return resData;
}
};
const _getReceivedDelegations = async () => {
const response = await getReceivedVestingShares(currentAccount.username)
const response = await getReceivedVestingShares(currentAccount.username);
return response.map((item) => ({
username: item.delegator,
vestingShares: item.vesting_shares,
timestamp: item.timestamp
}))
}
timestamp: item.timestamp,
}));
};
const _getDelegations = async () => {
try {
setIsLoading(true);
let response: DelegationItem[] = [];
@ -99,26 +98,24 @@ export const DelegationsModal = forwardRef(({ }, ref) => {
setDelegations(response);
setIsLoading(false);
} catch (err) {
console.warn("Failed to get delegations", err)
console.warn('Failed to get delegations', err);
setIsLoading(false);
}
};
}
const _handleOnUserPress = (username:string) => {
const _handleOnUserPress = (username: string) => {
navigation.navigate({
name: ROUTES.SCREENS.PROFILE,
params: {
username,
},
key: username
key: username,
});
setShowModal(false);
};
const _handleOnPressUpdate = (username:string) => {
if(mode === MODES.DELEGATEED){
const _handleOnPressUpdate = (username: string) => {
if (mode === MODES.DELEGATEED) {
console.log('delegate HP!');
navigation.navigate({
name: ROUTES.SCREENS.TRANSFER,
@ -130,14 +127,15 @@ export const DelegationsModal = forwardRef(({ }, ref) => {
});
setShowModal(false);
}
}
};
const title = intl.formatMessage({ id: `wallet.${mode}` })
const title = intl.formatMessage({ id: `wallet.${mode}` });
const _renderItem = ({ item, index }: { item: DelegationItem, index: number }) => {
const _renderItem = ({ item, index }: { item: DelegationItem; index: number }) => {
const value = vestsToHp(item.vestingShares, globalProps.hivePerMVests).toFixed(3) + ' HP';
const timeString = new Date(item.timestamp).toDateString();
const subRightText = mode === MODES.DELEGATEED && intl.formatMessage({id:"wallet.tap_update"})
const subRightText =
mode === MODES.DELEGATEED && intl.formatMessage({ id: 'wallet.tap_update' });
return (
<UserListItem
@ -150,13 +148,11 @@ export const DelegationsModal = forwardRef(({ }, ref) => {
subRightText={subRightText}
isLoggedIn
handleOnPress={() => _handleOnUserPress(item.username)}
onPressRightText={()=>_handleOnPressUpdate(item.username)}
onPressRightText={() => _handleOnPressUpdate(item.username)}
isClickable
/>
);
}
};
const _renderContent = () => {
return (
@ -166,7 +162,9 @@ export const DelegationsModal = forwardRef(({ }, ref) => {
<BasicHeader
backIconName="close"
isModalHeader
handleOnPressClose={() => { setShowModal(false) }}
handleOnPressClose={() => {
setShowModal(false);
}}
title={`${title} (${data && data.length})`}
isHasSearch
handleOnSearch={(text) => handleSearch(text, 'username')}
@ -189,10 +187,9 @@ export const DelegationsModal = forwardRef(({ }, ref) => {
/>
</>
)}
</AccountListContainer>
)
}
);
};
return (
<Modal
@ -206,5 +203,5 @@ export const DelegationsModal = forwardRef(({ }, ref) => {
>
{_renderContent()}
</Modal>
)
})
);
});

View File

@ -1,75 +1,70 @@
import React, { useState } from 'react'
import { View, Text } from 'react-native'
import EStyleSheet from 'react-native-extended-stylesheet'
import { TouchableOpacity } from 'react-native-gesture-handler'
import styles from './children.styles'
import React, { useState } from 'react';
import { View, Text } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import { TouchableOpacity } from 'react-native-gesture-handler';
import styles from './children.styles';
interface RangeOption {
label:string;
value:number;
label: string;
value: number;
}
interface RangeSelectorProps {
range:number;
onRangeChange:(range:number)=>void;
range: number;
onRangeChange: (range: number) => void;
}
export const RangeSelector = ({range, onRangeChange}:RangeSelectorProps) => {
const _onSelection = (range:number) => {
console.log('selection', range)
export const RangeSelector = ({ range, onRangeChange }: RangeSelectorProps) => {
const _onSelection = (range: number) => {
console.log('selection', range);
onRangeChange(range);
//TODO: implement on range change prop
}
};
const _renderRangeButtons = FILTERS.map((item:RangeOption)=>(
<TouchableOpacity key={`range option-${item.value}`} onPress={()=>_onSelection(item.value)} >
<View style={{
const _renderRangeButtons = FILTERS.map((item: RangeOption) => (
<TouchableOpacity key={`range option-${item.value}`} onPress={() => _onSelection(item.value)}>
<View
style={{
...styles.rangeOptionWrapper,
backgroundColor: EStyleSheet.value(
item.value === range ?
'$darkGrayBackground':'$primaryLightBackground'
)
}}>
<Text style={{
item.value === range ? '$darkGrayBackground' : '$primaryLightBackground',
),
}}
>
<Text
style={{
...styles.textRange,
color: EStyleSheet.value(
item.value === range ?
'$white':'$primaryDarkText'
)
}}>
color: EStyleSheet.value(item.value === range ? '$white' : '$primaryDarkText'),
}}
>
{item.label}
</Text>
</View>
</TouchableOpacity>
))
));
return (
<View style={[styles.card, styles.rangeContainer]}>
{_renderRangeButtons}
</View>
)
}
return <View style={[styles.card, styles.rangeContainer]}>{_renderRangeButtons}</View>;
};
const FILTERS = [
{
label:'24H',
value:1
label: '24H',
value: 1,
},
{
label:'1W',
value:7
label: '1W',
value: 7,
},
{
label:'1M',
value:20
label: '1M',
value: 20,
},
{
label:'1Y',
value:365
label: '1Y',
value: 365,
},
{
label:'5Y',
value:365*5
label: '5Y',
value: 365 * 5,
},
] as RangeOption[]
] as RangeOption[];

View File

@ -1,17 +1,17 @@
import { View, Alert, AppState, AppStateStatus } from 'react-native'
import React, { useEffect, useRef, useState } from 'react'
import { BasicHeader } from '../../../components'
import { CoinSummary } from '../children'
import { View, Alert, AppState, AppStateStatus } from 'react-native';
import React, { useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import { BasicHeader } from '../../../components';
import { CoinSummary } from '../children';
import styles from './screen.styles';
import ActivitiesList from '../children/activitiesList'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import ActivitiesList from '../children/activitiesList';
import { useAppDispatch, useAppSelector } from '../../../hooks';
import { CoinActivitiesCollection, QuoteItem } from '../../../redux/reducers/walletReducer';
import { fetchCoinActivities } from '../../../utils/wallet';
import { fetchAndSetCoinsData, setCoinActivities } from '../../../redux/actions/walletActions';
import { navigate } from '../../../navigation/service';
import ROUTES from '../../../constants/routeNames';
import { COIN_IDS } from '../../../constants/defaultCoins';
import { useIntl } from 'react-intl';
import { DelegationsModal, MODES } from '../children/delegationsModal';
export interface CoinDetailsScreenParams {
@ -19,8 +19,8 @@ export interface CoinDetailsScreenParams {
}
interface CoinDetailsScreenProps {
navigation: any
route: any
navigation: any;
route: any;
}
const FETCH_ITEMS_LIMIT = 500;
@ -31,7 +31,7 @@ const CoinDetailsScreen = ({ navigation, route }: CoinDetailsScreenProps) => {
const coinId = route.params?.coinId;
if (!coinId) {
throw new Error("Coin symbol must be passed")
throw new Error('Coin symbol must be passed');
}
//refs
@ -39,13 +39,17 @@ const CoinDetailsScreen = ({ navigation, route }: CoinDetailsScreenProps) => {
const delegationsModalRef = useRef(null);
//redux props
const currentAccount = useAppSelector(state => state.account.currentAccount);
const globalProps = useAppSelector(state => state.account.globalProps);
const selectedCoins = useAppSelector(state => state.wallet.selectedCoins);
const coinData: CoinData = useAppSelector(state => state.wallet.coinsData[coinId]);
const quote: QuoteItem = useAppSelector(state => state.wallet.quotes ? state.wallet.quotes[coinId] : {});
const coinActivities: CoinActivitiesCollection = useAppSelector(state => state.wallet.coinsActivities[coinId]);
const isPinCodeOpen = useAppSelector(state => state.application.isPinCodeOpen);
const currentAccount = useAppSelector((state) => state.account.currentAccount);
const globalProps = useAppSelector((state) => state.account.globalProps);
const selectedCoins = useAppSelector((state) => state.wallet.selectedCoins);
const coinData: CoinData = useAppSelector((state) => state.wallet.coinsData[coinId]);
const quote: QuoteItem = useAppSelector((state) =>
state.wallet.quotes ? state.wallet.quotes[coinId] : {},
);
const coinActivities: CoinActivitiesCollection = useAppSelector(
(state) => state.wallet.coinsActivities[coinId],
);
const isPinCodeOpen = useAppSelector((state) => state.application.isPinCodeOpen);
//state
const [symbol] = useState(selectedCoins.find((item) => item.id === coinId).symbol);
@ -59,65 +63,73 @@ const CoinDetailsScreen = ({ navigation, route }: CoinDetailsScreenProps) => {
_fetchDetails(true);
AppState.addEventListener('change', _handleAppStateChange);
return _cleanup;
}, [])
}, []);
const _cleanup = () => {
AppState.removeEventListener('change', _handleAppStateChange);
}
};
const _handleAppStateChange = (nextAppState: AppStateStatus) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
console.log("updating coins activities on app resume", coinId)
console.log('updating coins activities on app resume', coinId);
_fetchDetails(true);
}
appState.current = nextAppState;
}
};
const _fetchDetails = async (refresh = false) => {
if (refresh) {
setRefreshing(refresh);
dispatch(fetchAndSetCoinsData(refresh));
} else if(noMoreActivities || loading) {
console.log('Skipping transaction fetch', completedActivities.lastItem?.trxIndex)
} else if (noMoreActivities || loading) {
console.log('Skipping transaction fetch', completedActivities.lastItem?.trxIndex);
return;
}
setLoading(true);
const startAt = refresh || !completedActivities.length ? -1 : completedActivities.lastItem?.trxIndex - 1;
const _activites = await fetchCoinActivities(currentAccount.name, coinId, symbol, globalProps, startAt, FETCH_ITEMS_LIMIT);
const startAt =
refresh || !completedActivities.length ? -1 : completedActivities.lastItem?.trxIndex - 1;
const _activites = await fetchCoinActivities(
currentAccount.name,
coinId,
symbol,
globalProps,
startAt,
FETCH_ITEMS_LIMIT,
);
if(refresh){
if (refresh) {
dispatch(setCoinActivities(coinId, _activites));
}
setCompletedActivities(refresh ? _activites.completed : [...completedActivities, ..._activites.completed]);
setNoMoreActivities(!_activites.completed.length || _activites.completed.lastItem.trxIndex < FETCH_ITEMS_LIMIT);
setCompletedActivities(
refresh ? _activites.completed : [...completedActivities, ..._activites.completed],
);
setNoMoreActivities(
!_activites.completed.length || _activites.completed.lastItem.trxIndex < FETCH_ITEMS_LIMIT,
);
setRefreshing(false);
setLoading(false);
}
};
if (!coinData) {
Alert.alert("Invalid coin data");
Alert.alert('Invalid coin data');
navigation.goBack();
}
const _onInfoPress = (dataKey:string) => {
if((dataKey === MODES.DELEGATEED || dataKey === MODES.RECEIVED) && delegationsModalRef.current) {
delegationsModalRef.current.showModal(dataKey)
const _onInfoPress = (dataKey: string) => {
if (
(dataKey === MODES.DELEGATEED || dataKey === MODES.RECEIVED) &&
delegationsModalRef.current
) {
delegationsModalRef.current.showModal(dataKey);
}
}
};
const _onActionPress = (transferType: string) => {
let navigateTo = ROUTES.SCREENS.TRANSFER
let navigateTo = ROUTES.SCREENS.TRANSFER;
let navigateParams = {};
if (coinId === COIN_IDS.ECENCY && transferType !== 'dropdown_transfer') {
@ -125,38 +137,38 @@ const CoinDetailsScreen = ({ navigation, route }: CoinDetailsScreenProps) => {
navigateParams = {
balance: coinData.balance,
redeemType: transferType === 'dropdown_promote' ? 'promote' : 'boost',
}
};
} else {
const balance = transferType === 'withdraw_hive' || transferType === 'withdraw_hbd'
? coinData.savings : coinData.balance;
const balance =
transferType === 'withdraw_hive' || transferType === 'withdraw_hbd'
? coinData.savings
: coinData.balance;
navigateParams = {
transferType: coinId === COIN_IDS.ECENCY ? 'points' : transferType,
fundType: coinId === COIN_IDS.ECENCY ? 'ESTM' : symbol,
balance
balance,
};
}
if (isPinCodeOpen) {
navigate({
routeName: ROUTES.SCREENS.PINCODE,
params:{
params: {
navigateTo,
navigateParams,
}
})
},
});
} else {
navigate({
routeName: navigateTo,
params: navigateParams
params: navigateParams,
});
}
}
};
const _onRefresh = () => {
_fetchDetails(true);
}
};
const _renderHeaderComponent = (
<CoinSummary
@ -165,9 +177,9 @@ const CoinDetailsScreen = ({ navigation, route }: CoinDetailsScreenProps) => {
coinData={coinData}
percentChagne={quote.percentChange || 0}
onActionPress={_onActionPress}
onInfoPress={_onInfoPress} />
)
onInfoPress={_onInfoPress}
/>
);
return (
<View style={styles.container}>
@ -183,7 +195,7 @@ const CoinDetailsScreen = ({ navigation, route }: CoinDetailsScreenProps) => {
/>
<DelegationsModal ref={delegationsModalRef} />
</View>
)
}
);
};
export default CoinDetailsScreen
export default CoinDetailsScreen;

View File

@ -2,9 +2,7 @@ import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex:1,
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
});

View File

@ -1,23 +1,15 @@
import React, { Fragment } from 'react';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import {
Alert,
import React, { Fragment, useEffect, useState } from 'react';
FlatList,
ScrollView,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { useIntl } from 'react-intl';
import { Alert, FlatList, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import AutoHeightWebView from 'react-native-autoheight-webview';
import { BasicHeader, Icon, PostPlaceHolder, TextInput } from '../../components';
// styles
import EStyleSheet from 'react-native-extended-stylesheet';
import styles from './editHistoryScreenStyles';
import { getCommentHistory } from '../../providers/ecency/ecency';
import { dateToFormatted } from '../../utils/time';
import AutoHeightWebView from 'react-native-autoheight-webview';
import historyBuilder from './historyBuilder';
import getWindowDimensions from '../../utils/getWindowDimensions';
@ -84,14 +76,9 @@ const EditHistoryScreen = ({ route }) => {
}
`;
const diffIconStyle =
{
color: showDiff
? EStyleSheet.value('$primaryBlue')
: EStyleSheet.value('$iconColor'),
}
;
const diffIconStyle = {
color: showDiff ? EStyleSheet.value('$primaryBlue') : EStyleSheet.value('$iconColor'),
};
useEffect(() => {
_getCommentHistory();
}, []);
@ -108,7 +95,6 @@ const EditHistoryScreen = ({ route }) => {
setIsLoading(false);
};
const _renderVersionsListItem = ({
item,
index,
@ -140,7 +126,6 @@ const EditHistoryScreen = ({ route }) => {
);
};
const _renderVersionsList = () => (
<View style={styles.versionsListContainer}>
<FlatList
@ -160,7 +145,7 @@ const EditHistoryScreen = ({ route }) => {
<View style={styles.diffContainer}>
<AutoHeightWebView source={{ html: item.titleDiff }} customStyle={customTitleStyle} />
<View style={styles.tagsContainer}>
<Icon style={styles.tagIcon} iconType="AntDesign" name={'tag'} />
<Icon style={styles.tagIcon} iconType="AntDesign" name="tag" />
<AutoHeightWebView source={{ html: item.tagsDiff }} customStyle={customTagsStyle} />
</View>
<AutoHeightWebView source={{ html: item.bodyDiff }} customStyle={customBodyStyle} />
@ -168,7 +153,6 @@ const EditHistoryScreen = ({ route }) => {
);
};
const _renderPlainBody = (selectedItem: CommentHistoryListItemDiff) => {
return (
<>
@ -180,7 +164,7 @@ const EditHistoryScreen = ({ route }) => {
editable={false}
/>
<View style={styles.tagsContainer}>
<Icon style={styles.tagIcon} iconType="AntDesign" name={'tag'} />
<Icon style={styles.tagIcon} iconType="AntDesign" name="tag" />
<Text style={styles.tags}>{selectedItem.tags}</Text>
</View>
</View>
@ -196,7 +180,6 @@ const EditHistoryScreen = ({ route }) => {
);
};
const _renderBody = () => {
const selectedItem = editHistory.find((x) => x.v === versionSelected);
if (!selectedItem) {

View File

@ -1,8 +1,8 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
mainContainer:{
flex:1,
mainContainer: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
versionsListContainer: {
@ -10,7 +10,7 @@ export default EStyleSheet.create({
paddingTop: 16,
},
versionsListContentContainer: {
paddingHorizontal: 16
paddingHorizontal: 16,
},
versionItemBtn: {
// backgroundColor: '$primaryBlue',
@ -25,7 +25,7 @@ export default EStyleSheet.create({
versionItemBtnText: {
color: '$pureWhite',
fontSize: 14,
fontWeight: '700'
fontWeight: '700',
},
versionItemBtnDate: {
color: '$black',
@ -34,9 +34,7 @@ export default EStyleSheet.create({
previewScrollContentContainer: {
paddingHorizontal: 16,
},
postHeaderContainer: {
},
postHeaderContainer: {},
postHeaderTitle: {
fontSize: 24,
color: '$primaryBlack',
@ -44,7 +42,7 @@ export default EStyleSheet.create({
fontFamily: '$primaryFont',
marginBottom: 11,
},
postBodyText:{
postBodyText: {
fontSize: 16,
color: '$primaryBlack',
fontFamily: '$primaryFont',

Some files were not shown because too many files have changed in this diff Show More