mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-11-22 05:42:33 +03:00
lint
This commit is contained in:
parent
d34fa30f40
commit
3c63b00f87
3
index.js
3
index.js
@ -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
|
||||
|
@ -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,8 +106,12 @@ 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
|
||||
const encryptedAccessToken = await refreshSCToken(
|
||||
@ -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',
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from './optionsModal';
|
||||
export * from './progressBar'
|
||||
export * from './progressBar';
|
||||
|
@ -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,
|
||||
},
|
||||
filled:{
|
||||
borderRadius:16,
|
||||
backgroundColor:'$primaryBlue'
|
||||
},
|
||||
container: {
|
||||
backgroundColor: '$primaryLightBackground',
|
||||
flexDirection: 'row',
|
||||
borderRadius: 16,
|
||||
height: 16,
|
||||
alignSelf: 'stretch',
|
||||
marginHorizontal: 8,
|
||||
marginBottom: 12,
|
||||
},
|
||||
filled: {
|
||||
borderRadius: 16,
|
||||
backgroundColor: '$primaryBlue',
|
||||
},
|
||||
});
|
||||
|
@ -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}
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<View style={filledStyle} />
|
||||
<View style={unfilledStyle} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
@ -1 +1 @@
|
||||
export * from './container/progressBar';
|
||||
export * from './container/progressBar';
|
||||
|
@ -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 = ({
|
||||
contentWidth,
|
||||
imgUrl,
|
||||
isAnchored,
|
||||
activeOpacity,
|
||||
onPress,
|
||||
}: AutoHeightImageProps) => {
|
||||
const [imgWidth, setImgWidth] = useState(contentWidth);
|
||||
const [imgHeight, setImgHeight] = useState(imgWidth * (9 / 16));
|
||||
const [onLoadCalled, setOnLoadCalled] = useState(false);
|
||||
|
||||
export const AutoHeightImage = ({
|
||||
contentWidth,
|
||||
imgUrl,
|
||||
isAnchored,
|
||||
activeOpacity,
|
||||
onPress
|
||||
}:AutoHeightImageProps) => {
|
||||
useEffect(() => {
|
||||
_fetchImageBounds();
|
||||
}, []);
|
||||
|
||||
const _fetchImageBounds = () => {
|
||||
Image.getSize(imgUrl, (width, height) => {
|
||||
const newWidth = width < contentWidth ? width : contentWidth;
|
||||
const newHeight = (height / width) * newWidth;
|
||||
setImgHeight(newHeight);
|
||||
setImgWidth(newWidth);
|
||||
});
|
||||
};
|
||||
|
||||
const [imgWidth, setImgWidth] = useState(contentWidth);
|
||||
const [imgHeight, setImgHeight] = useState(imgWidth * (9/16))
|
||||
const [onLoadCalled, setOnLoadCalled] = useState(false);
|
||||
const imgStyle = {
|
||||
width: imgWidth - 10,
|
||||
height: imgHeight,
|
||||
backgroundColor: onLoadCalled ? 'transparent' : EStyleSheet.value('$primaryGray'),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
_fetchImageBounds();
|
||||
}, [])
|
||||
const _onLoad = () => {
|
||||
setOnLoadCalled(true);
|
||||
};
|
||||
|
||||
const _fetchImageBounds = () => {
|
||||
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')
|
||||
}
|
||||
|
||||
const _onLoad = () => {
|
||||
setOnLoadCalled(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} disabled={isAnchored} activeOpacity={activeOpacity || 1}>
|
||||
<FastImage
|
||||
style={imgStyle}
|
||||
source={{uri:imgUrl}}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
onLoad={_onLoad}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
)
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} disabled={isAnchored} activeOpacity={activeOpacity || 1}>
|
||||
<FastImage
|
||||
style={imgStyle}
|
||||
source={{ uri: imgUrl }}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
onLoad={_onLoad}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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,8 +94,11 @@ 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.
|
||||
let othersWeight = 0;
|
||||
@ -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}
|
||||
|
@ -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',
|
||||
},
|
||||
selectedStyle: {
|
||||
borderWidth: 4,
|
||||
borderColor: '$primaryBlack',
|
||||
},
|
||||
settingLabel: {
|
||||
color: '$primaryDarkGray',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
flexGrow: 1,
|
||||
textAlign: 'left',
|
||||
},
|
||||
listContainer: {
|
||||
paddingBottom: getBottomSpace() + 16,
|
||||
},
|
||||
container: {
|
||||
paddingVertical: 16,
|
||||
},
|
||||
bodyWrapper: { flex: 1, paddingTop: 20, paddingBottom: 20 },
|
||||
inputWrapper: { flexDirection: 'row', alignItems: 'center' },
|
||||
contentLabel: { color: '$iconColor', marginTop: 4, textAlign: 'left' },
|
||||
weightInput: { width: 80 },
|
||||
weightFormInput: { flex: 1, color: '$primaryBlack', paddingLeft: 12 },
|
||||
weightFormInputWrapper: { marginTop: 8 },
|
||||
usernameInput: { flex: 1, color: '$primaryBlack', marginLeft: 16 },
|
||||
usernameFormInputWrapper: { marginTop: 8, marginRight: 12 },
|
||||
footerWrapper: { paddingTop: 16 },
|
||||
saveButton: {
|
||||
width: 140,
|
||||
height: 44,
|
||||
alignSelf: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
doneButton: { borderRadius: 16, backgroundColor: '$primaryBlue' },
|
||||
thumbSelectContainer: {
|
||||
marginTop: 12,
|
||||
},
|
||||
checkBoxHeader: {
|
||||
width: 50,
|
||||
},
|
||||
checkBoxContainer: {
|
||||
width: 50,
|
||||
marginTop: 12,
|
||||
},
|
||||
thumbStyle:{
|
||||
width:72,
|
||||
height:72,
|
||||
marginVertical:8,
|
||||
marginRight:8,
|
||||
borderRadius:12,
|
||||
backgroundColor:'$primaryLightGray'
|
||||
},
|
||||
selectedStyle:{
|
||||
borderWidth:4,
|
||||
borderColor:'$primaryBlack'
|
||||
},
|
||||
settingLabel:{
|
||||
color: '$primaryDarkGray',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
flexGrow: 1,
|
||||
textAlign:'left',
|
||||
},
|
||||
listContainer:{
|
||||
paddingBottom:getBottomSpace() + 16,
|
||||
},
|
||||
container:{
|
||||
paddingVertical:16
|
||||
},
|
||||
bodyWrapper: { flex: 1, paddingTop: 20, paddingBottom:20},
|
||||
inputWrapper: { flexDirection: 'row', alignItems: 'center'},
|
||||
contentLabel: { color: '$iconColor', marginTop:4, textAlign:'left' },
|
||||
weightInput: {width:80},
|
||||
weightFormInput: { flex:1, color: '$primaryBlack', paddingLeft: 12 },
|
||||
weightFormInputWrapper: { marginTop: 8 },
|
||||
usernameInput: { flex:1, color: '$primaryBlack', marginLeft: 16, },
|
||||
usernameFormInputWrapper: { marginTop: 8, marginRight: 12 },
|
||||
footerWrapper: { paddingTop:16 },
|
||||
saveButton: {
|
||||
width: 140,
|
||||
height: 44,
|
||||
alignSelf: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
doneButton:{borderRadius:16, backgroundColor:'$primaryBlue'},
|
||||
thumbSelectContainer:{
|
||||
marginTop:12,
|
||||
},
|
||||
checkBoxHeader: {
|
||||
width: 50,
|
||||
},
|
||||
checkBoxContainer: {
|
||||
width: 50,
|
||||
marginTop:12,
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
@ -119,233 +118,213 @@ const CommentView = ({
|
||||
repliesContainerRef.current.slideInRight(300);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setIsPressedShowButton(true);
|
||||
|
||||
} else if (openReplyThread) {
|
||||
openReplyThread();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const _handleCacheVoteIncrement = () => {
|
||||
//fake increment vote using based on local change
|
||||
setCacheVoteIcrement(1);
|
||||
};
|
||||
|
||||
const _incrementRepliesCount = () => {
|
||||
if (commentNumber > 1 && incrementRepliesCount) {
|
||||
incrementRepliesCount();
|
||||
}
|
||||
setChildCount(childCount + 1);
|
||||
}
|
||||
|
||||
const _handleOnReplyPress = () => {
|
||||
if (isLoggedIn) {
|
||||
dispatch(showReplyModal(comment));
|
||||
} else {
|
||||
console.log('Not LoggedIn');
|
||||
}
|
||||
}
|
||||
|
||||
const _renderReadMoreButton = () => (
|
||||
<TextWithIcon
|
||||
wrapperStyle={styles.rightButton}
|
||||
textStyle={!isPressedShowButton && styles.moreText}
|
||||
iconType="MaterialIcons"
|
||||
isClickable
|
||||
iconStyle={styles.iconStyle}
|
||||
iconSize={16}
|
||||
onPress={() => openReplyThread && openReplyThread()}
|
||||
text={
|
||||
!isPressedShowButton
|
||||
? intl.formatMessage({ id: 'comments.read_more' })
|
||||
: ''
|
||||
} else if (openReplyThread) {
|
||||
openReplyThread();
|
||||
}
|
||||
/>
|
||||
};
|
||||
|
||||
)
|
||||
const _handleCacheVoteIncrement = () => {
|
||||
//fake increment vote using based on local change
|
||||
setCacheVoteIcrement(1);
|
||||
};
|
||||
|
||||
const _renderReplies = () => {
|
||||
const _incrementRepliesCount = () => {
|
||||
if (commentNumber > 1 && incrementRepliesCount) {
|
||||
incrementRepliesCount();
|
||||
}
|
||||
setChildCount(childCount + 1);
|
||||
};
|
||||
|
||||
const _handleOnReplyPress = () => {
|
||||
if (isLoggedIn) {
|
||||
dispatch(showReplyModal(comment));
|
||||
} else {
|
||||
console.log('Not LoggedIn');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedView ref={repliesContainerRef}>
|
||||
{_isShowSubComments &&
|
||||
<Comments
|
||||
isShowComments={isShowComments}
|
||||
commentNumber={commentNumber + 1}
|
||||
marginLeft={20}
|
||||
isShowSubComments={true}
|
||||
avatarSize={avatarSize || 24}
|
||||
author={comment.author}
|
||||
permlink={comment.permlink}
|
||||
commentCount={childCount}
|
||||
comments={comment.replies}
|
||||
hasManyComments={commentNumber === 5 && get(comment, 'children') > 0}
|
||||
fetchPost={fetchPost}
|
||||
hideManyCommentsButton={hideManyCommentsButton}
|
||||
mainAuthor={mainAuthor}
|
||||
fetchedAt={fetchedAt}
|
||||
incrementRepliesCount={_incrementRepliesCount}
|
||||
handleOnReplyPress={_handleOnReplyPress}
|
||||
/>}
|
||||
</AnimatedView>
|
||||
const _renderReadMoreButton = () => (
|
||||
<TextWithIcon
|
||||
wrapperStyle={styles.rightButton}
|
||||
textStyle={!isPressedShowButton && styles.moreText}
|
||||
iconType="MaterialIcons"
|
||||
isClickable
|
||||
iconStyle={styles.iconStyle}
|
||||
iconSize={16}
|
||||
onPress={() => openReplyThread && openReplyThread()}
|
||||
text={!isPressedShowButton ? intl.formatMessage({ id: 'comments.read_more' }) : ''}
|
||||
/>
|
||||
);
|
||||
|
||||
)
|
||||
}
|
||||
const _renderReplies = () => {
|
||||
return (
|
||||
<AnimatedView ref={repliesContainerRef}>
|
||||
{_isShowSubComments && (
|
||||
<Comments
|
||||
isShowComments={isShowComments}
|
||||
commentNumber={commentNumber + 1}
|
||||
marginLeft={20}
|
||||
isShowSubComments={true}
|
||||
avatarSize={avatarSize || 24}
|
||||
author={comment.author}
|
||||
permlink={comment.permlink}
|
||||
commentCount={childCount}
|
||||
comments={comment.replies}
|
||||
hasManyComments={commentNumber === 5 && get(comment, 'children') > 0}
|
||||
fetchPost={fetchPost}
|
||||
hideManyCommentsButton={hideManyCommentsButton}
|
||||
mainAuthor={mainAuthor}
|
||||
fetchedAt={fetchedAt}
|
||||
incrementRepliesCount={_incrementRepliesCount}
|
||||
handleOnReplyPress={_handleOnReplyPress}
|
||||
/>
|
||||
)}
|
||||
</AnimatedView>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const _renderComment = () => {
|
||||
return ((
|
||||
<View style={[{ marginLeft: 2, marginTop: -6 }]}>
|
||||
<CommentBody
|
||||
commentDepth={comment.depth}
|
||||
reputation={comment.author_reputation}
|
||||
handleOnUserPress={handleOnUserPress}
|
||||
handleOnLongPress={handleOnLongPress}
|
||||
body={comment.body}
|
||||
created={comment.created}
|
||||
key={`key-${comment.permlink}`}
|
||||
isMuted={isMuted}
|
||||
/>
|
||||
|
||||
<Fragment>
|
||||
<View style={styles.footerWrapper}>
|
||||
{_renderActionPanel()}
|
||||
</View>
|
||||
{commentNumber > 1 &&
|
||||
childCount > 0 &&
|
||||
!replies?.length &&
|
||||
_renderReadMoreButton()
|
||||
}
|
||||
</Fragment>
|
||||
</View>
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
const _renderActionPanel = () => {
|
||||
return (
|
||||
<>
|
||||
<Upvote
|
||||
activeVotes={activeVotes}
|
||||
isShowPayoutValue
|
||||
content={comment}
|
||||
handleCacheVoteIncrement={_handleCacheVoteIncrement}
|
||||
parentType={postTypes.COMMENT}
|
||||
/>
|
||||
<TextWithIcon
|
||||
iconName="heart-outline"
|
||||
iconSize={20}
|
||||
wrapperStyle={styles.leftButton}
|
||||
iconType="MaterialCommunityIcons"
|
||||
isClickable
|
||||
onPress={() =>
|
||||
handleOnVotersPress &&
|
||||
activeVotes.length > 0 &&
|
||||
handleOnVotersPress(activeVotes, comment)
|
||||
}
|
||||
text={activeVotes.length + cacheVoteIcrement}
|
||||
textStyle={styles.voteCountText}
|
||||
/>
|
||||
|
||||
{isLoggedIn && (
|
||||
<IconButton
|
||||
size={20}
|
||||
iconStyle={styles.leftIcon}
|
||||
style={styles.leftButton}
|
||||
name="comment-outline"
|
||||
onPress={_handleOnReplyPress}
|
||||
iconType="MaterialCommunityIcons"
|
||||
const _renderComment = () => {
|
||||
return (
|
||||
<View style={[{ marginLeft: 2, marginTop: -6 }]}>
|
||||
<CommentBody
|
||||
commentDepth={comment.depth}
|
||||
reputation={comment.author_reputation}
|
||||
handleOnUserPress={handleOnUserPress}
|
||||
handleOnLongPress={handleOnLongPress}
|
||||
body={comment.body}
|
||||
created={comment.created}
|
||||
key={`key-${comment.permlink}`}
|
||||
isMuted={isMuted}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
{currentAccountUsername === comment.author && (
|
||||
<Fragment>
|
||||
<View style={styles.footerWrapper}>{_renderActionPanel()}</View>
|
||||
{commentNumber > 1 && childCount > 0 && !replies?.length && _renderReadMoreButton()}
|
||||
</Fragment>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderActionPanel = () => {
|
||||
return (
|
||||
<>
|
||||
<Upvote
|
||||
activeVotes={activeVotes}
|
||||
isShowPayoutValue
|
||||
content={comment}
|
||||
handleCacheVoteIncrement={_handleCacheVoteIncrement}
|
||||
parentType={postTypes.COMMENT}
|
||||
/>
|
||||
<TextWithIcon
|
||||
iconName="heart-outline"
|
||||
iconSize={20}
|
||||
wrapperStyle={styles.leftButton}
|
||||
iconType="MaterialCommunityIcons"
|
||||
isClickable
|
||||
onPress={() =>
|
||||
handleOnVotersPress &&
|
||||
activeVotes.length > 0 &&
|
||||
handleOnVotersPress(activeVotes, comment)
|
||||
}
|
||||
text={activeVotes.length + cacheVoteIcrement}
|
||||
textStyle={styles.voteCountText}
|
||||
/>
|
||||
|
||||
{isLoggedIn && (
|
||||
<IconButton
|
||||
size={20}
|
||||
iconStyle={styles.leftIcon}
|
||||
style={styles.leftButton}
|
||||
name="create"
|
||||
onPress={() => handleOnEditPress && handleOnEditPress(comment)}
|
||||
iconType="MaterialIcons"
|
||||
name="comment-outline"
|
||||
onPress={_handleOnReplyPress}
|
||||
iconType="MaterialCommunityIcons"
|
||||
/>
|
||||
{!childCount && !activeVotes.length && comment.isDeletable && (
|
||||
<Fragment>
|
||||
<IconButton
|
||||
size={20}
|
||||
iconStyle={styles.leftIcon}
|
||||
style={styles.leftButton}
|
||||
name="delete-forever"
|
||||
onPress={() => actionSheet.current.show()}
|
||||
iconType="MaterialIcons"
|
||||
/>
|
||||
<OptionsModal
|
||||
ref={actionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.delete' }),
|
||||
intl.formatMessage({ id: 'alert.cancel' }),
|
||||
]}
|
||||
title={intl.formatMessage({ id: 'alert.delete' })}
|
||||
destructiveButtonIndex={0}
|
||||
cancelButtonIndex={1}
|
||||
onPress={(index) => {
|
||||
index === 0 ? handleDeleteComment(comment.permlink) : null;
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
)}
|
||||
|
||||
{currentAccountUsername === comment.author && (
|
||||
<Fragment>
|
||||
<IconButton
|
||||
size={20}
|
||||
iconStyle={styles.leftIcon}
|
||||
style={styles.leftButton}
|
||||
name="create"
|
||||
onPress={() => handleOnEditPress && handleOnEditPress(comment)}
|
||||
iconType="MaterialIcons"
|
||||
/>
|
||||
{!childCount && !activeVotes.length && comment.isDeletable && (
|
||||
<Fragment>
|
||||
<IconButton
|
||||
size={20}
|
||||
iconStyle={styles.leftIcon}
|
||||
style={styles.leftButton}
|
||||
name="delete-forever"
|
||||
onPress={() => actionSheet.current.show()}
|
||||
iconType="MaterialIcons"
|
||||
/>
|
||||
<OptionsModal
|
||||
ref={actionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.delete' }),
|
||||
intl.formatMessage({ id: 'alert.cancel' }),
|
||||
]}
|
||||
title={intl.formatMessage({ id: 'alert.delete' })}
|
||||
destructiveButtonIndex={0}
|
||||
cancelButtonIndex={1}
|
||||
onPress={(index) => {
|
||||
index === 0 ? handleDeleteComment(comment.permlink) : null;
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{commentNumber === 1 && childCount > 0 && (
|
||||
<View style={styles.rightButtonWrapper}>
|
||||
<TextWithIcon
|
||||
wrapperStyle={styles.rightButton}
|
||||
iconName={_isShowSubComments ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
||||
textStyle={styles.moreText}
|
||||
iconType="MaterialIcons"
|
||||
isClickable
|
||||
iconStyle={styles.iconStyle}
|
||||
iconSize={16}
|
||||
onPress={() => _showSubCommentsToggle()}
|
||||
text={`${childCount} ${intl.formatMessage({ id: 'comments.more_replies' })}`}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{commentNumber === 1 && childCount > 0 && (
|
||||
<View style={styles.rightButtonWrapper}>
|
||||
<TextWithIcon
|
||||
wrapperStyle={styles.rightButton}
|
||||
iconName={_isShowSubComments ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
||||
textStyle={styles.moreText}
|
||||
iconType="MaterialIcons"
|
||||
isClickable
|
||||
iconStyle={styles.iconStyle}
|
||||
iconSize={16}
|
||||
onPress={() => _showSubCommentsToggle()}
|
||||
text={`${childCount} ${intl.formatMessage({ id: 'comments.more_replies' })}`}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
const customContainerStyle = commentNumber > 2 ? { marginLeft: 44 } : null;
|
||||
|
||||
const customContainerStyle = commentNumber > 2 ? { marginLeft: 44 } : null
|
||||
return (
|
||||
<Fragment>
|
||||
<View style={{ ...styles.commentContainer, ...customContainerStyle }}>
|
||||
<PostHeaderDescription
|
||||
key={comment.permlink}
|
||||
date={getTimeFromNow(comment.created)}
|
||||
name={comment.author}
|
||||
reputation={comment.author_reputation}
|
||||
size={avatarSize || 40}
|
||||
currentAccountUsername={currentAccountUsername}
|
||||
isShowOwnerIndicator={mainAuthor === comment.author}
|
||||
isHideImage={isHideImage}
|
||||
inlineTime={true}
|
||||
customStyle={{ alignItems: 'flex-start', paddingLeft: 12 }}
|
||||
showDotMenuButton={true}
|
||||
handleOnDotPress={handleOnLongPress}
|
||||
secondaryContentComponent={_renderComment()}
|
||||
/>
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<View style={{ ...styles.commentContainer, ...customContainerStyle }}>
|
||||
<PostHeaderDescription
|
||||
key={comment.permlink}
|
||||
date={getTimeFromNow(comment.created)}
|
||||
name={comment.author}
|
||||
reputation={comment.author_reputation}
|
||||
size={avatarSize || 40}
|
||||
currentAccountUsername={currentAccountUsername}
|
||||
isShowOwnerIndicator={mainAuthor === comment.author}
|
||||
isHideImage={isHideImage}
|
||||
inlineTime={true}
|
||||
customStyle={{ alignItems: 'flex-start', paddingLeft: 12 }}
|
||||
showDotMenuButton={true}
|
||||
handleOnDotPress={handleOnLongPress}
|
||||
secondaryContentComponent={_renderComment()}
|
||||
/>
|
||||
|
||||
{commentNumber > 0 && _renderReplies()}
|
||||
</View>
|
||||
</Fragment>
|
||||
);
|
||||
{commentNumber > 0 && _renderReplies()}
|
||||
</View>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommentView;
|
||||
|
@ -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 ? {
|
||||
backgroundColor: EStyleSheet.value('$primaryLightBackground'),
|
||||
marginTop: 8,
|
||||
} : null
|
||||
const styleOerride =
|
||||
commentNumber > 1
|
||||
? {
|
||||
backgroundColor: EStyleSheet.value('$primaryLightBackground'),
|
||||
marginTop: 8,
|
||||
}
|
||||
: 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>
|
||||
|
@ -1,57 +1,56 @@
|
||||
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;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
export const WriteCommentButton = forwardRef(({ onPress }, ref) => {
|
||||
const intl = useIntl();
|
||||
const intl = useIntl();
|
||||
|
||||
const animatedContainer = useRef<AnimatedView>();
|
||||
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")
|
||||
if (animatedContainer.current) {
|
||||
animatedContainer.current.swing(1000);
|
||||
}
|
||||
},
|
||||
}));
|
||||
useImperativeHandle(ref, () => ({
|
||||
bounce: () => {
|
||||
console.log('bouncing');
|
||||
if (animatedContainer.current) {
|
||||
animatedContainer.current.swing(1000);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const _onPress = () => {
|
||||
if (!isLoggedIn) {
|
||||
showLoginAlert({ intl })
|
||||
return;
|
||||
}
|
||||
if (onPress) {
|
||||
onPress();
|
||||
}
|
||||
const _onPress = () => {
|
||||
if (!isLoggedIn) {
|
||||
showLoginAlert({ intl });
|
||||
return;
|
||||
}
|
||||
if (onPress) {
|
||||
onPress();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedView ref={animatedContainer}>
|
||||
<TouchableOpacity onPress={_onPress}>
|
||||
<View style={styles.container}>
|
||||
<UserAvatar username={currentAccount.username} />
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputPlaceholder}>
|
||||
{intl.formatMessage({id:'quick_reply.placeholder'})}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</AnimatedView>
|
||||
|
||||
)
|
||||
})
|
||||
return (
|
||||
<AnimatedView ref={animatedContainer}>
|
||||
<TouchableOpacity onPress={_onPress}>
|
||||
<View style={styles.container}>
|
||||
<UserAvatar username={currentAccount.username} />
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputPlaceholder}>
|
||||
{intl.formatMessage({ id: 'quick_reply.placeholder' })}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</AnimatedView>
|
||||
);
|
||||
});
|
||||
|
@ -3,91 +3,85 @@ import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
import getWindowDimensions from '../../utils/getWindowDimensions';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
modalStyle: {
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
margin:0,
|
||||
paddingTop:32,
|
||||
paddingBottom:8,
|
||||
},
|
||||
modalStyle: {
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
margin: 0,
|
||||
paddingTop: 32,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
|
||||
sheetContent: {
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
sheetContent: {
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
|
||||
container:{
|
||||
marginTop:16,
|
||||
marginBottom:44,
|
||||
paddingHorizontal:24,
|
||||
alignItems:'center',
|
||||
justifyContent:'space-between',
|
||||
} as ViewStyle,
|
||||
container: {
|
||||
marginTop: 16,
|
||||
marginBottom: 44,
|
||||
paddingHorizontal: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
} as ViewStyle,
|
||||
|
||||
imageStyle:{
|
||||
marginTop:8,
|
||||
height:150,
|
||||
width:150,
|
||||
} as ImageStyle,
|
||||
imageStyle: {
|
||||
marginTop: 8,
|
||||
height: 150,
|
||||
width: 150,
|
||||
} as ImageStyle,
|
||||
|
||||
textContainer:{
|
||||
marginTop:32,
|
||||
marginBottom:44,
|
||||
} as ViewStyle,
|
||||
textContainer: {
|
||||
marginTop: 32,
|
||||
marginBottom: 44,
|
||||
} as ViewStyle,
|
||||
|
||||
title: {
|
||||
color: '$primaryBlack',
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
fontSize: 20,
|
||||
fontWeight: '800',
|
||||
} as TextStyle,
|
||||
title: {
|
||||
color: '$primaryBlack',
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
fontSize: 20,
|
||||
fontWeight: '800',
|
||||
} as TextStyle,
|
||||
|
||||
bodyText: {
|
||||
color: '$primaryBlack',
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginTop:4,
|
||||
} as TextStyle,
|
||||
bodyText: {
|
||||
color: '$primaryBlack',
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginTop: 4,
|
||||
} as TextStyle,
|
||||
|
||||
btnText:{
|
||||
color:'$pureWhite'
|
||||
} as TextStyle,
|
||||
btnText: {
|
||||
color: '$pureWhite',
|
||||
} as TextStyle,
|
||||
|
||||
button:{
|
||||
button: {
|
||||
backgroundColor: '$primaryBlue',
|
||||
width: 150,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 32,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
|
||||
backgroundColor:'$primaryBlue',
|
||||
width:150,
|
||||
paddingVertical:16,
|
||||
borderRadius:32,
|
||||
justifyContent:'center',
|
||||
alignItems:'center'
|
||||
} as ViewStyle,
|
||||
actionPanel: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
} as ViewStyle,
|
||||
|
||||
checkView: {
|
||||
width: getWindowDimensions().width - 80,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 20,
|
||||
marginVertical: 4,
|
||||
} as ViewStyle,
|
||||
|
||||
actionPanel:{
|
||||
width:'100%',
|
||||
flexDirection:'row',
|
||||
justifyContent:'space-around',
|
||||
alignItems:'center',
|
||||
} as ViewStyle,
|
||||
|
||||
checkView: {
|
||||
width:getWindowDimensions().width - 80,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems:'center',
|
||||
marginHorizontal: 20,
|
||||
marginVertical:4,
|
||||
} as ViewStyle,
|
||||
|
||||
|
||||
informationText: {
|
||||
color: '$primaryBlack',
|
||||
margin: 10,
|
||||
fontSize:18,
|
||||
} as TextStyle,
|
||||
|
||||
|
||||
|
||||
})
|
||||
informationText: {
|
||||
color: '$primaryBlack',
|
||||
margin: 10,
|
||||
fontSize: 18,
|
||||
} as TextStyle,
|
||||
});
|
||||
|
@ -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';
|
||||
@ -25,28 +25,28 @@ const renderDropdownRow = (
|
||||
noHighlight,
|
||||
dropdownRowWrapper,
|
||||
) => (
|
||||
<View
|
||||
<View
|
||||
style={[
|
||||
styles.dropdownRow,
|
||||
dropdownRowWrapper,
|
||||
!noHighlight && highlighted && styles.highlightedRow,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.dropdownRow,
|
||||
dropdownRowWrapper,
|
||||
!noHighlight && highlighted && styles.highlightedRow,
|
||||
rowTextStyle || styles.rowText,
|
||||
!noHighlight && highlighted && styles.highlightedRowText,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
rowTextStyle || styles.rowText,
|
||||
!noHighlight && highlighted && styles.highlightedRowText,
|
||||
]}
|
||||
>
|
||||
{rowData}
|
||||
</Text>
|
||||
</View>
|
||||
{rowData}
|
||||
</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]}
|
||||
|
@ -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();
|
||||
|
@ -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 &&
|
||||
<AnimatedView
|
||||
ref={containerRef}
|
||||
style={styles.container}
|
||||
animation='slideInDown'
|
||||
duration={500}>
|
||||
isVisible && (
|
||||
<AnimatedView
|
||||
ref={containerRef}
|
||||
style={styles.container}
|
||||
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={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>
|
||||
<View style={{ flexShrink: 1 }}>
|
||||
<Text style={styles.text} numberOfLines={1}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={styles.text} numberOfLines={1}>
|
||||
{body}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<IconButton
|
||||
name='close'
|
||||
color="white"
|
||||
size={28}
|
||||
onPress={hide}
|
||||
/>
|
||||
</View>
|
||||
</AnimatedView>
|
||||
)
|
||||
}
|
||||
</TouchableOpacity>
|
||||
|
||||
<IconButton name="close" color="white" size={28} onPress={hide} />
|
||||
</View>
|
||||
</AnimatedView>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default ForegroundNotification;
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -19,7 +19,7 @@ export default EStyleSheet.create({
|
||||
paddingTop: 32,
|
||||
paddingBottom: 16,
|
||||
},
|
||||
|
||||
|
||||
container: {
|
||||
paddingVertical: 8,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
|
@ -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',
|
||||
() => {
|
||||
setKeyboardVisible(true); // or some other action
|
||||
}
|
||||
);
|
||||
const keyboardDidHideListener = Keyboard.addListener(
|
||||
'keyboardDidHide',
|
||||
() => {
|
||||
setKeyboardVisible(false); // or some other action
|
||||
}
|
||||
);
|
||||
const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
|
||||
setKeyboardVisible(true); // or some other action
|
||||
});
|
||||
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({
|
||||
inputRange: [0, 500],
|
||||
outputRange: [0, 500],
|
||||
extrapolate: Extrapolate.CLAMP
|
||||
}), [translateY.current]);
|
||||
|
||||
const consY = useMemo(
|
||||
() =>
|
||||
translateY.current.interpolate({
|
||||
inputRange: [0, 500],
|
||||
outputRange: [0, 500],
|
||||
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}>
|
||||
<Animated.View style={_animatedStyle}>
|
||||
<View onLayout={(e) => {
|
||||
extensionHeight.current = e.nativeEvent.layout.height;
|
||||
console.log('extension height', extensionHeight.current)
|
||||
|
||||
}} style={styles.dropShadow}>
|
||||
onEnded={_onPanEnded}
|
||||
>
|
||||
<Animated.View style={_animatedStyle}>
|
||||
<View
|
||||
onLayout={(e) => {
|
||||
extensionHeight.current = e.nativeEvent.layout.height;
|
||||
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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,108 +1,102 @@
|
||||
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[]
|
||||
text: string;
|
||||
selection: Selection;
|
||||
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
|
||||
|
||||
//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 imagePrefix = '!';
|
||||
const placeholderPrefix = 'Uploading... '
|
||||
let newText = text;
|
||||
let newSelection = selection;
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
const _replaceFormatedString = (placeholder: string, url: string) => {
|
||||
const replaceStr = `(${placeholder})`;
|
||||
|
||||
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
|
||||
}
|
||||
const endingIndex = newText.indexOf(replaceStr) + replaceStr.length + 1;
|
||||
newText = newText.replace(replaceStr, `(${url})`);
|
||||
|
||||
if (newSelection.start >= endingIndex) {
|
||||
const lengthDiff = url.length - placeholder.length;
|
||||
newSelection = {
|
||||
start: newSelection.start + lengthDiff,
|
||||
end: newSelection.end + lengthDiff,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const _removeFormatedString = (placeholder) => {
|
||||
const formatedText = `${imagePrefix}[](${placeholder})`;
|
||||
const formatedTextIndex = newText.indexOf(formatedText);
|
||||
newText = newText.replace(formatedText, '');
|
||||
|
||||
const _replaceFormatedString = (placeholder: string, url: string) => {
|
||||
const replaceStr = `(${placeholder})`;
|
||||
|
||||
const endingIndex = newText.indexOf(replaceStr) + replaceStr.length + 1;
|
||||
newText = newText.replace(replaceStr, `(${url})`);
|
||||
|
||||
if (newSelection.start >= endingIndex) {
|
||||
const lengthDiff = url.length - placeholder.length
|
||||
newSelection = {
|
||||
start: newSelection.start + lengthDiff,
|
||||
end: newSelection.end + lengthDiff
|
||||
}
|
||||
}
|
||||
if (newSelection.start > formatedTextIndex) {
|
||||
newSelection = {
|
||||
start: newSelection.start - formatedText.length,
|
||||
end: newSelection.end - formatedText.length,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
items.forEach((item) => {
|
||||
const _placeholder = item.filename && `${placeholderPrefix}${item.filename}`;
|
||||
|
||||
const _removeFormatedString = (placeholder) => {
|
||||
const formatedText = `${imagePrefix}[](${placeholder})`
|
||||
const formatedTextIndex = newText.indexOf(formatedText);
|
||||
newText = newText.replace(formatedText, '')
|
||||
switch (item.status) {
|
||||
case MediaInsertStatus.UPLOADING: //means only filename is available
|
||||
if (!_placeholder) return;
|
||||
_insertFormatedString(item.text, _placeholder);
|
||||
break;
|
||||
|
||||
if (newSelection.start > formatedTextIndex) {
|
||||
newSelection = {
|
||||
start: newSelection.start - formatedText.length,
|
||||
end: newSelection.end - formatedText.length
|
||||
}
|
||||
case MediaInsertStatus.READY: //means url is ready but filename may be available
|
||||
if (_placeholder && newText.includes(_placeholder)) {
|
||||
//means placeholder is preset is needs replacing
|
||||
_replaceFormatedString(_placeholder, item.url);
|
||||
} else if (item.url) {
|
||||
_insertFormatedString(item.text, item.url);
|
||||
}
|
||||
break;
|
||||
|
||||
case MediaInsertStatus.FAILED: //filename available but upload failed
|
||||
if (_placeholder && newText.includes(_placeholder)) {
|
||||
_removeFormatedString(_placeholder);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (item.url) {
|
||||
_insertFormatedString(item.text, item.url);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
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)
|
||||
break;
|
||||
|
||||
case MediaInsertStatus.READY: //means url is ready but filename may be available
|
||||
if (_placeholder && newText.includes(_placeholder)) {
|
||||
//means placeholder is preset is needs replacing
|
||||
_replaceFormatedString(_placeholder, item.url);
|
||||
} else if (item.url) {
|
||||
_insertFormatedString(item.text, item.url);
|
||||
}
|
||||
break;
|
||||
|
||||
case MediaInsertStatus.FAILED: //filename available but upload failed
|
||||
if (_placeholder && newText.includes(_placeholder)) {
|
||||
_removeFormatedString(_placeholder);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (item.url) {
|
||||
_insertFormatedString(item.text, item.url);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
setTextAndSelection({ text: newText, selection: newSelection });
|
||||
setTextAndSelection({ text: newText, selection: newSelection });
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
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,
|
||||
end: selection.start + (snippetText && snippetText.length),
|
||||
};
|
||||
const newSelection = {
|
||||
start: selection.start,
|
||||
end: selection.start + (snippetText && snippetText.length),
|
||||
};
|
||||
setTextAndSelection({ text: newText, selection: newSelection });
|
||||
};
|
||||
};
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { extractWordAtIndex } from '../../../../utils/editor';
|
||||
import {replaceBetween } from './utils';
|
||||
import { replaceBetween } from './utils';
|
||||
|
||||
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 _newPos = _insertAt + username.length + 2;
|
||||
const _selection = { start: _newPos, end: _newPos };
|
||||
setTextAndSelection({ selection: _selection, text: _text });
|
||||
};
|
||||
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 _newPos = _insertAt + username.length + 2;
|
||||
const _selection = { start: _newPos, end: _newPos };
|
||||
setTextAndSelection({ selection: _selection, text: _text });
|
||||
};
|
||||
|
@ -1,93 +1,104 @@
|
||||
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) => {
|
||||
export const UsernameAutofillBar = ({ text, selection, onApplyUsername }: Props) => {
|
||||
const [searchedUsers, setSearchedUsers] = useState([]);
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const [searchedUsers, setSearchedUsers] = useState([])
|
||||
const [query, setQuery] = useState('');
|
||||
useEffect(() => {
|
||||
if (selection.start === selection.end && text) {
|
||||
_processTextForSearch(text, selection.start);
|
||||
}
|
||||
}, [text, selection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selection.start === selection.end && text) {
|
||||
_processTextForSearch(text, selection.start);
|
||||
}
|
||||
}, [text, selection])
|
||||
|
||||
|
||||
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('')
|
||||
_handleUserSearch.cancel();
|
||||
}
|
||||
}, 300, {leading:true}),[]);
|
||||
|
||||
|
||||
const _handleUserSearch = useCallback(debounce(async (username) => {
|
||||
if(query !== username){
|
||||
let users = [];
|
||||
if (username) {
|
||||
setQuery(username)
|
||||
users = await lookupAccounts(username);
|
||||
console.log('result users for', username, users);
|
||||
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('');
|
||||
_handleUserSearch.cancel();
|
||||
}
|
||||
setSearchedUsers(users);
|
||||
}
|
||||
|
||||
}, 200, {leading:true}), []);
|
||||
},
|
||||
300,
|
||||
{ leading: true },
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const _handleUserSearch = useCallback(
|
||||
debounce(
|
||||
async (username) => {
|
||||
if (query !== username) {
|
||||
let users = [];
|
||||
if (username) {
|
||||
setQuery(username);
|
||||
users = await lookupAccounts(username);
|
||||
console.log('result users for', username, users);
|
||||
}
|
||||
setSearchedUsers(users);
|
||||
}
|
||||
|
||||
|
||||
const _onUserSelect = (username) => {
|
||||
onApplyUsername(username)
|
||||
setSearchedUsers([]);
|
||||
setQuery('')
|
||||
};
|
||||
200,
|
||||
{ leading: true },
|
||||
),
|
||||
|
||||
if(!searchedUsers || searchedUsers.length === 0 || query === ''){
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
const _renderItem = ({item}:{item:string}) => {
|
||||
const _onUserSelect = (username) => {
|
||||
onApplyUsername(username);
|
||||
setSearchedUsers([]);
|
||||
setQuery('');
|
||||
};
|
||||
|
||||
const username = item;
|
||||
return (
|
||||
<TouchableOpacity onPress={()=>{_onUserSelect(username)}}>
|
||||
<View style={styles.userBubble}>
|
||||
<UserAvatar username={username}/>
|
||||
<Text style={styles.userBubbleText}>{username}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.searchAccountsContainer}>
|
||||
<FlatList
|
||||
horizontal={true}
|
||||
data={searchedUsers}
|
||||
keyboardShouldPersistTaps="always"
|
||||
showsHorizontalScrollIndicator={false}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(item)=>`searched-user-${item}`}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
if (!searchedUsers || searchedUsers.length === 0 || query === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const _renderItem = ({ item }: { item: string }) => {
|
||||
const username = item;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
_onUserSelect(username);
|
||||
}}
|
||||
>
|
||||
<View style={styles.userBubble}>
|
||||
<UserAvatar username={username} />
|
||||
<Text style={styles.userBubbleText}>{username}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.searchAccountsContainer}>
|
||||
<FlatList
|
||||
horizontal={true}
|
||||
data={searchedUsers}
|
||||
keyboardShouldPersistTaps="always"
|
||||
showsHorizontalScrollIndicator={false}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(item) => `searched-user-${item}`}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
@ -50,7 +50,7 @@ import { useAppSelector } from '../../../hooks';
|
||||
|
||||
const MIN_BODY_INPUT_HEIGHT = 300;
|
||||
|
||||
//These variable keep track of body text input state,
|
||||
//These variable keep track of body text input state,
|
||||
//this helps keep load on minimal compared to both useState and useRef;
|
||||
var bodyText = '';
|
||||
var bodySelection = { start: 0, end: 0 };
|
||||
@ -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,33 +189,37 @@ const MarkdownEditorView = ({
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
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), [])
|
||||
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),
|
||||
[],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const _changeText = useCallback((input) => {
|
||||
bodyText = input;
|
||||
const _changeText = useCallback(
|
||||
(input) => {
|
||||
bodyText = input;
|
||||
|
||||
if (!isEditing) {
|
||||
console.log('force setting is editing to true', true)
|
||||
setIsEditing(true)
|
||||
}
|
||||
if (!isEditing) {
|
||||
console.log('force setting is editing to true', true);
|
||||
setIsEditing(true);
|
||||
}
|
||||
|
||||
_debouncedOnTextChange();
|
||||
|
||||
//NOTE: onChange method is called by direct parent of MarkdownEditor that is PostForm, do not remove
|
||||
if (onChange) {
|
||||
onChange(input);
|
||||
}
|
||||
}, [isEditing]);
|
||||
_debouncedOnTextChange();
|
||||
|
||||
//NOTE: onChange method is called by direct parent of MarkdownEditor that is PostForm, do not remove
|
||||
if (onChange) {
|
||||
onChange(input);
|
||||
}
|
||||
},
|
||||
[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 && (
|
||||
@ -397,38 +391,44 @@ const MarkdownEditorView = ({
|
||||
</View>
|
||||
)}
|
||||
{!isPreviewActive ? (
|
||||
<TextInput
|
||||
multiline
|
||||
autoCorrect={true}
|
||||
autoFocus={!draftBtnTooltipRegistered ? false : true}
|
||||
onChangeText={_changeText}
|
||||
onSelectionChange={_handleOnSelectionChange}
|
||||
placeholder={intl.formatMessage({
|
||||
id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder',
|
||||
})}
|
||||
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
|
||||
selectionColor="#357ce6"
|
||||
style={styles.textWrapper}
|
||||
underlineColorAndroid="transparent"
|
||||
innerRef={inputRef}
|
||||
editable={editable}
|
||||
contextMenuHidden={false}
|
||||
scrollEnabled={editorScrollEnabled}
|
||||
/>
|
||||
<TextInput
|
||||
multiline
|
||||
autoCorrect={true}
|
||||
autoFocus={!draftBtnTooltipRegistered ? false : true}
|
||||
onChangeText={_changeText}
|
||||
onSelectionChange={_handleOnSelectionChange}
|
||||
placeholder={intl.formatMessage({
|
||||
id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder',
|
||||
})}
|
||||
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
|
||||
selectionColor="#357ce6"
|
||||
style={styles.textWrapper}
|
||||
underlineColorAndroid="transparent"
|
||||
innerRef={inputRef}
|
||||
editable={editable}
|
||||
contextMenuHidden={false}
|
||||
scrollEnabled={editorScrollEnabled}
|
||||
/>
|
||||
) : (
|
||||
_renderPreview()
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
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}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from './quickProfileModal';
|
||||
export * from './inputSupportModal';
|
||||
export * from './inputSupportModal';
|
||||
|
@ -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",
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||
} as ViewStyle,
|
||||
|
||||
})
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||
} as ViewStyle,
|
||||
});
|
||||
|
@ -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 && (
|
||||
<Portal>
|
||||
|
||||
<AnimatedView
|
||||
ref={container}
|
||||
animation='fadeIn'
|
||||
duration={300}
|
||||
style={styles.container} >
|
||||
|
||||
<AnimatedView
|
||||
ref={innerContainer}
|
||||
style={{ flex: 1 }}
|
||||
animation='slideInUp'
|
||||
duration={300}>
|
||||
|
||||
<View
|
||||
return (
|
||||
showModal && (
|
||||
<Portal>
|
||||
<AnimatedView ref={container} animation="fadeIn" duration={300} style={styles.container}>
|
||||
<AnimatedView
|
||||
ref={innerContainer}
|
||||
style={{ flex: 1 }}
|
||||
onTouchEnd={onClose} />
|
||||
animation="slideInUp"
|
||||
duration={300}
|
||||
>
|
||||
<View style={{ flex: 1 }} onTouchEnd={onClose} />
|
||||
|
||||
{
|
||||
Platform.select({
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<KeyboardAvoidingView behavior="padding" style={{}}>
|
||||
{children}
|
||||
</KeyboardAvoidingView>
|
||||
),
|
||||
android: <View>{children}</View>,
|
||||
})
|
||||
}
|
||||
})}
|
||||
</AnimatedView>
|
||||
</AnimatedView>
|
||||
</AnimatedView>
|
||||
</Portal>
|
||||
</Portal>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
@ -1 +1 @@
|
||||
export * from './container/inputSupportModal';
|
||||
export * from './container/inputSupportModal';
|
||||
|
@ -82,11 +82,11 @@ class PostDropdownContainer extends PureComponent {
|
||||
const _canUpdateCommunityPin =
|
||||
subscribedCommunities.data && !!content && content.community
|
||||
? subscribedCommunities.data.reduce((role, subscription) => {
|
||||
if (content.community === subscription[0]) {
|
||||
return ['owner', 'admin', 'mod'].includes(subscription[2]);
|
||||
}
|
||||
return role;
|
||||
}, false)
|
||||
if (content.community === subscription[0]) {
|
||||
return ['owner', 'admin', 'mod'].includes(subscription[2]);
|
||||
}
|
||||
return role;
|
||||
}, false)
|
||||
: false;
|
||||
const _isPinnedInCommunity = !!content && content.stats?.is_pinned;
|
||||
|
||||
@ -290,7 +290,7 @@ class PostDropdownContainer extends PureComponent {
|
||||
buttons: [
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.cancel' }),
|
||||
onPress: () => { },
|
||||
onPress: () => {},
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.confirm' }),
|
||||
@ -329,17 +329,16 @@ class PostDropdownContainer extends PureComponent {
|
||||
};
|
||||
|
||||
_reblog = () => {
|
||||
const {
|
||||
content,
|
||||
currentAccount,
|
||||
dispatch,
|
||||
intl,
|
||||
isLoggedIn,
|
||||
pinCode,
|
||||
const {
|
||||
content,
|
||||
currentAccount,
|
||||
dispatch,
|
||||
intl,
|
||||
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()} />),
|
||||
),
|
||||
);
|
||||
|
@ -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
|
||||
@ -128,12 +125,11 @@ 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}
|
||||
@ -359,11 +353,11 @@ const CommentBody = ({
|
||||
setVideoUrl(null);
|
||||
}}
|
||||
>
|
||||
<VideoPlayer
|
||||
<VideoPlayer
|
||||
mode={youtubeVideoId ? 'youtube' : 'uri'}
|
||||
youtubeVideoId={youtubeVideoId}
|
||||
uri={videoUrl}
|
||||
startTime={videoStartTime}
|
||||
youtubeVideoId={youtubeVideoId}
|
||||
uri={videoUrl}
|
||||
startTime={videoStartTime}
|
||||
/>
|
||||
</ActionsSheetView>
|
||||
</Fragment>
|
||||
@ -371,4 +365,3 @@ const CommentBody = ({
|
||||
};
|
||||
|
||||
export default CommentBody;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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:{
|
||||
width: '100%',
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
@ -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,65 +32,61 @@ const postsListContainer = ({
|
||||
pageType,
|
||||
showQuickReplyModal,
|
||||
...props
|
||||
}:postsListContainerProps, ref) => {
|
||||
|
||||
}: postsListContainerProps,
|
||||
ref,
|
||||
) => {
|
||||
|
||||
const flatListRef = useRef(null);
|
||||
|
||||
const [imageHeights, setImageHeights] = useState(new Map<string, number>());
|
||||
const [imageHeights, setImageHeights] = useState(new Map<string, number>());
|
||||
|
||||
const isHideImages = useSelector((state) => state.application.hidePostsThumbnails);
|
||||
const posts = useSelector((state) => {
|
||||
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
|
||||
const isHideImages = useSelector((state) => state.application.hidePostsThumbnails);
|
||||
const posts = useSelector((state) => {
|
||||
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;
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
scrollToTop() {
|
||||
flatListRef.current?.scrollToOffset({ x: 0, y: 0, animated: true });
|
||||
},
|
||||
}));
|
||||
useImperativeHandle(ref, () => ({
|
||||
scrollToTop() {
|
||||
flatListRef.current?.scrollToOffset({ x: 0, y: 0, animated: true });
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Scroll Position: ", scrollPosition)
|
||||
if(posts && posts.length == 0){
|
||||
flatListRef.current?.scrollToOffset({
|
||||
offset: 0,
|
||||
animated: false
|
||||
});
|
||||
}
|
||||
|
||||
}, [posts])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Scroll Position: ", scrollPosition)
|
||||
useEffect(() => {
|
||||
console.log('Scroll Position: ', scrollPosition);
|
||||
if (posts && posts.length == 0) {
|
||||
flatListRef.current?.scrollToOffset({
|
||||
offset: (posts && posts.length == 0) ? 0 : scrollPosition,
|
||||
animated: false
|
||||
offset: 0,
|
||||
animated: false,
|
||||
});
|
||||
|
||||
}, [scrollPosition])
|
||||
|
||||
const _setImageHeightInMap = (mapKey:string, height:number) => {
|
||||
if(mapKey && height){
|
||||
setImageHeights(imageHeights.set(mapKey, height));
|
||||
}
|
||||
}
|
||||
|
||||
}, [posts]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Scroll Position: ', scrollPosition);
|
||||
flatListRef.current?.scrollToOffset({
|
||||
offset: posts && posts.length == 0 ? 0 : scrollPosition,
|
||||
animated: false,
|
||||
});
|
||||
|
||||
}, [scrollPosition]);
|
||||
|
||||
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,96 +101,103 @@ const postsListContainer = ({
|
||||
}
|
||||
};
|
||||
|
||||
const _renderItem = ({ item, index }: { item: any; index: number }) => {
|
||||
const e = [] as any;
|
||||
|
||||
const _renderItem = ({ item, index }:{item:any, index:number}) => {
|
||||
const e = [] as any;
|
||||
|
||||
if (index % 3 === 0) {
|
||||
const ix = index / 3 - 1;
|
||||
if (promotedPosts[ix] !== undefined) {
|
||||
const p = promotedPosts[ix];
|
||||
let isMuted = mutes && mutes.indexOf(p.author) > -1;
|
||||
if (index % 3 === 0) {
|
||||
const ix = index / 3 - 1;
|
||||
if (promotedPosts[ix] !== undefined) {
|
||||
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) {
|
||||
|
||||
//get image height from cache if available
|
||||
const localId = p.author + p.permlink;
|
||||
const imgHeight = imageHeights.get(localId);
|
||||
if (
|
||||
!isMuted &&
|
||||
get(p, 'author', null) &&
|
||||
posts &&
|
||||
posts.filter((x) => x.permlink === p.permlink).length <= 0
|
||||
|
||||
e.push(
|
||||
<PostCard
|
||||
key={`${p.author}-${p.permlink}-prom`}
|
||||
content={p}
|
||||
isHideImage={isHideImages}
|
||||
imageHeight={imgHeight}
|
||||
pageType={pageType}
|
||||
setImageHeight = {_setImageHeightInMap}
|
||||
showQuickReplyModal={showQuickReplyModal}
|
||||
mutes={mutes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isMuted = mutes && mutes.indexOf(item.author) > -1;
|
||||
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 localId = p.author + p.permlink;
|
||||
const imgHeight = imageHeights.get(localId);
|
||||
|
||||
e.push(
|
||||
<PostCard
|
||||
key={`${item.author}-${item.permlink}`}
|
||||
content={item}
|
||||
key={`${p.author}-${p.permlink}-prom`}
|
||||
content={p}
|
||||
isHideImage={isHideImages}
|
||||
imageHeight={imgHeight}
|
||||
setImageHeight = {_setImageHeightInMap}
|
||||
pageType={pageType}
|
||||
setImageHeight={_setImageHeightInMap}
|
||||
showQuickReplyModal={showQuickReplyModal}
|
||||
mutes={mutes}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
return e;
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContainer>
|
||||
{({ isDarkTheme }) => (
|
||||
<FlatList
|
||||
ref={flatListRef}
|
||||
data={posts}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(content, index) => `${content.author}/${content.permlink}-${index}`}
|
||||
removeClippedSubviews
|
||||
onEndReachedThreshold={1}
|
||||
maxToRenderPerBatch={3}
|
||||
initialNumToRender={3}
|
||||
windowSize={5}
|
||||
extraData={imageHeights}
|
||||
onEndReached={_onEndReached}
|
||||
onMomentumScrollBegin={() => {
|
||||
_onEndReachedCalledDuringMomentum = false;
|
||||
}
|
||||
}
|
||||
|
||||
let isMuted = mutes && mutes.indexOf(item.author) > -1;
|
||||
if (!isMuted && get(item, 'author', null)) {
|
||||
//get image height from cache if available
|
||||
const localId = item.author + item.permlink;
|
||||
const imgHeight = imageHeights.get(localId);
|
||||
|
||||
e.push(
|
||||
<PostCard
|
||||
key={`${item.author}-${item.permlink}`}
|
||||
content={item}
|
||||
isHideImage={isHideImages}
|
||||
imageHeight={imgHeight}
|
||||
setImageHeight={_setImageHeightInMap}
|
||||
pageType={pageType}
|
||||
showQuickReplyModal={showQuickReplyModal}
|
||||
mutes={mutes}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
return e;
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContainer>
|
||||
{({ isDarkTheme }) => (
|
||||
<FlatList
|
||||
ref={flatListRef}
|
||||
data={posts}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(content, index) => `${content.author}/${content.permlink}-${index}`}
|
||||
removeClippedSubviews
|
||||
onEndReachedThreshold={1}
|
||||
maxToRenderPerBatch={3}
|
||||
initialNumToRender={3}
|
||||
windowSize={5}
|
||||
extraData={imageHeights}
|
||||
onEndReached={_onEndReached}
|
||||
onMomentumScrollBegin={() => {
|
||||
_onEndReachedCalledDuringMomentum = false;
|
||||
}}
|
||||
ListFooterComponent={_renderFooter}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isRefreshing}
|
||||
onRefresh={() => {
|
||||
if (onLoadPosts) {
|
||||
onLoadPosts(true);
|
||||
}
|
||||
}}
|
||||
ListFooterComponent={_renderFooter}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isRefreshing}
|
||||
onRefresh={()=>{if(onLoadPosts){onLoadPosts(true)}}}
|
||||
progressBackgroundColor="#357CE6"
|
||||
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
|
||||
titleColor="#fff"
|
||||
colors={['#fff']}
|
||||
/>
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</ThemeContainer>
|
||||
)
|
||||
}
|
||||
progressBackgroundColor="#357CE6"
|
||||
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
|
||||
titleColor="#fff"
|
||||
colors={['#fff']}
|
||||
/>
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</ThemeContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default forwardRef(postsListContainer);
|
||||
|
@ -1,133 +1,130 @@
|
||||
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 intl = useIntl();
|
||||
const isHideImage = useAppSelector((state) => state.application.hidePostsThumbnails);
|
||||
|
||||
const isHideImage = useAppSelector(state => state.application.hidePostsThumbnails);
|
||||
const [data, setData] = useState([]);
|
||||
const [lastAuthor, setLastAuthor] = useState('');
|
||||
const [lastPermlink, setLastPermlink] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [noMore, setNoMore] = useState(false);
|
||||
|
||||
const [data, setData] = useState([]);
|
||||
const [lastAuthor, setLastAuthor] = useState('');
|
||||
const [lastPermlink, setLastPermlink] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [noMore, setNoMore] = useState(false);
|
||||
useEffect(() => {
|
||||
if (selectedUser) {
|
||||
_fetchData();
|
||||
}
|
||||
}, [selectedUser]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(selectedUser){
|
||||
_fetchData();
|
||||
}
|
||||
}, [selectedUser])
|
||||
|
||||
|
||||
const _fetchData = async ({refresh}:{refresh?:boolean} = {}) => {
|
||||
if(loading || (!refresh && noMore)){
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
if(refresh){
|
||||
setRefreshing(true);
|
||||
}
|
||||
|
||||
const query:any = {
|
||||
account:username,
|
||||
start_author: refresh ? '' : lastAuthor,
|
||||
start_permlink: refresh ? '' : lastPermlink,
|
||||
limit:10,
|
||||
observer:'',
|
||||
sort:type
|
||||
};
|
||||
|
||||
const result = await getAccountPosts(query)
|
||||
let _comments:any[] = refresh ? result : unionBy(data, result, 'permlink');
|
||||
|
||||
if(Array.isArray(_comments)){
|
||||
setData(_comments);
|
||||
if(_comments.length > 0){
|
||||
setLastAuthor(_comments[_comments.lastIndex].author)
|
||||
setLastPermlink(_comments[_comments.lastIndex].permlink)
|
||||
}
|
||||
if(result.length == 0){
|
||||
setNoMore(true);
|
||||
}
|
||||
}else{
|
||||
setData([]);
|
||||
setNoMore(true);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
|
||||
const _fetchData = async ({ refresh }: { refresh?: boolean } = {}) => {
|
||||
if (loading || (!refresh && noMore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _renderListEmpty = () => {
|
||||
if(loading){
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<NoPost
|
||||
name={username}
|
||||
text={intl.formatMessage({
|
||||
id: 'profile.havent_commented',
|
||||
})}
|
||||
defaultText={intl.formatMessage({
|
||||
id: 'profile.login_to_see',
|
||||
})}
|
||||
/>
|
||||
)
|
||||
setLoading(true);
|
||||
if (refresh) {
|
||||
setRefreshing(true);
|
||||
}
|
||||
|
||||
const _renderListFooter = () => {
|
||||
return (
|
||||
<View style={styles.commentsListFooter}>
|
||||
{loading && (
|
||||
<ActivityIndicator size='large'/>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
const query: any = {
|
||||
account: username,
|
||||
start_author: refresh ? '' : lastAuthor,
|
||||
start_permlink: refresh ? '' : lastPermlink,
|
||||
limit: 10,
|
||||
observer: '',
|
||||
sort: type,
|
||||
};
|
||||
|
||||
const result = await getAccountPosts(query);
|
||||
let _comments: any[] = refresh ? result : unionBy(data, result, 'permlink');
|
||||
|
||||
if (Array.isArray(_comments)) {
|
||||
setData(_comments);
|
||||
if (_comments.length > 0) {
|
||||
setLastAuthor(_comments[_comments.lastIndex].author);
|
||||
setLastPermlink(_comments[_comments.lastIndex].permlink);
|
||||
}
|
||||
if (result.length == 0) {
|
||||
setNoMore(true);
|
||||
}
|
||||
} else {
|
||||
setData([]);
|
||||
setNoMore(true);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const _renderListEmpty = () => {
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View key="profile.comments" style={styles.commentsTabBar}>
|
||||
<Comments
|
||||
comments={data}
|
||||
fetchPost={()=>{}}
|
||||
isOwnProfile={isOwnProfile}
|
||||
isHideImage={isHideImage}
|
||||
flatListProps={{
|
||||
onEndReached:_fetchData,
|
||||
onScroll:onScroll,
|
||||
ListEmptyComponent:_renderListEmpty,
|
||||
ListFooterComponent:_renderListFooter,
|
||||
onEndReachedThreshold:1,
|
||||
refreshControl:(
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={()=>_fetchData({refresh:true})}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<NoPost
|
||||
name={username}
|
||||
text={intl.formatMessage({
|
||||
id: 'profile.havent_commented',
|
||||
})}
|
||||
defaultText={intl.formatMessage({
|
||||
id: 'profile.login_to_see',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderListFooter = () => {
|
||||
return (
|
||||
<View style={styles.commentsListFooter}>{loading && <ActivityIndicator size="large" />}</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View key="profile.comments" style={styles.commentsTabBar}>
|
||||
<Comments
|
||||
comments={data}
|
||||
fetchPost={() => {}}
|
||||
isOwnProfile={isOwnProfile}
|
||||
isHideImage={isHideImage}
|
||||
flatListProps={{
|
||||
onEndReached: _fetchData,
|
||||
onScroll: onScroll,
|
||||
ListEmptyComponent: _renderListEmpty,
|
||||
ListFooterComponent: _renderListFooter,
|
||||
onEndReachedThreshold: 1,
|
||||
refreshControl: (
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={() => _fetchData({ refresh: true })}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommentsTabContent;
|
||||
|
@ -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,37 +47,31 @@ 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={
|
||||
coverUrl
|
||||
? { uri: getResizedImage(coverUrl, 600) }
|
||||
: isDarkTheme
|
||||
? DARK_COVER_IMAGE
|
||||
: LIGHT_COVER_IMAGE
|
||||
}
|
||||
/>
|
||||
{
|
||||
isUploading && (
|
||||
<ActivityIndicator
|
||||
style={styles.activityIndicator}
|
||||
color={EStyleSheet.value('$white')}
|
||||
size='large'
|
||||
/>
|
||||
)
|
||||
<FastImage
|
||||
style={styles.coverImg}
|
||||
source={
|
||||
coverUrl
|
||||
? { uri: getResizedImage(coverUrl, 600) }
|
||||
: isDarkTheme
|
||||
? DARK_COVER_IMAGE
|
||||
: LIGHT_COVER_IMAGE
|
||||
}
|
||||
|
||||
/>
|
||||
{isUploading && (
|
||||
<ActivityIndicator
|
||||
style={styles.activityIndicator}
|
||||
color={EStyleSheet.value('$white')}
|
||||
size="large"
|
||||
/>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
iconStyle={styles.addIcon}
|
||||
@ -111,11 +104,11 @@ const ProfileEditFormView = ({
|
||||
</View>
|
||||
))}
|
||||
</KeyboardAwareScrollView>
|
||||
|
||||
|
||||
{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>
|
||||
);
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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,299 +36,280 @@ export interface QuickReplyModalContentProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const QuickReplyModalContent = forwardRef(({
|
||||
selectedPost,
|
||||
onClose,
|
||||
}: QuickReplyModalContentProps, ref) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const userActivityMutation = useUserActivityMutation();
|
||||
export const QuickReplyModalContent = forwardRef(
|
||||
({ selectedPost, onClose }: QuickReplyModalContentProps, ref) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const userActivityMutation = useUserActivityMutation();
|
||||
|
||||
const inputRef = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const currentAccount = useSelector((state: RootState) => state.account.currentAccount);
|
||||
const pinCode = useSelector((state: RootState) => state.application.pin);
|
||||
const drafts = useSelector((state: RootState) => state.cache.drafts);
|
||||
const currentAccount = useSelector((state: RootState) => state.account.currentAccount);
|
||||
const pinCode = useSelector((state: RootState) => state.application.pin);
|
||||
const drafts = useSelector((state: RootState) => state.cache.drafts);
|
||||
|
||||
const [commentValue, setCommentValue] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [commentValue, setCommentValue] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
|
||||
const headerText =
|
||||
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
|
||||
|
||||
const headerText =
|
||||
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 = '';
|
||||
if (drafts.has(draftId) && currentAccount.name === drafts.get(draftId).author) {
|
||||
const quickComment: Draft = drafts.get(draftId);
|
||||
_value = quickComment.body;
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleSheetClose() {
|
||||
_addQuickCommentIntoCache();
|
||||
},
|
||||
}));
|
||||
if (inputRef.current) {
|
||||
inputRef.current.setNativeProps({
|
||||
text: _value,
|
||||
});
|
||||
setCommentValue(_value);
|
||||
}
|
||||
}, [selectedPost]);
|
||||
|
||||
// add quick comment value into cache
|
||||
const _addQuickCommentIntoCache = (value = commentValue) => {
|
||||
const quickCommentDraftData: Draft = {
|
||||
author: currentAccount.name,
|
||||
body: value,
|
||||
};
|
||||
|
||||
// load quick comment value from cache
|
||||
useEffect(() => {
|
||||
let _value = ''
|
||||
if (drafts.has(draftId) && currentAccount.name === drafts.get(draftId).author) {
|
||||
const quickComment: Draft = drafts.get(draftId);
|
||||
_value = quickComment.body;
|
||||
}
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.setNativeProps({
|
||||
text: _value
|
||||
})
|
||||
setCommentValue(_value)
|
||||
}
|
||||
|
||||
}, [selectedPost]);
|
||||
|
||||
|
||||
|
||||
// add quick comment value into cache
|
||||
const _addQuickCommentIntoCache = (value = commentValue) => {
|
||||
|
||||
const quickCommentDraftData: Draft = {
|
||||
author: currentAccount.name,
|
||||
body: value
|
||||
//add quick comment cache entry
|
||||
dispatch(updateDraftCache(draftId, quickCommentDraftData));
|
||||
};
|
||||
|
||||
//add quick comment cache entry
|
||||
dispatch(updateDraftCache(draftId, quickCommentDraftData));
|
||||
};
|
||||
// handle close press
|
||||
const _handleClosePress = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
// handle close press
|
||||
const _handleClosePress = () => {
|
||||
onClose()
|
||||
};
|
||||
|
||||
|
||||
// navigate to post on summary press
|
||||
const _handleOnSummaryPress = () => {
|
||||
Keyboard.dismiss();
|
||||
onClose();
|
||||
navigate({
|
||||
routeName: ROUTES.SCREENS.POST,
|
||||
params: {
|
||||
content: selectedPost,
|
||||
},
|
||||
key: get(selectedPost, 'permlink'),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// handle submit reply
|
||||
const _submitReply = async () => {
|
||||
|
||||
if (!commentValue) {
|
||||
return;
|
||||
}
|
||||
if (isSending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentAccount) {
|
||||
setIsSending(true);
|
||||
|
||||
const permlink = generateReplyPermlink(selectedPost.author);
|
||||
|
||||
const parentAuthor = selectedPost.author;
|
||||
const parentPermlink = selectedPost.permlink;
|
||||
const parentTags = selectedPost.json_metadata.tags;
|
||||
console.log(
|
||||
currentAccount,
|
||||
pinCode,
|
||||
parentAuthor,
|
||||
parentPermlink,
|
||||
permlink,
|
||||
commentValue,
|
||||
parentTags,
|
||||
);
|
||||
|
||||
const status = await postComment(
|
||||
currentAccount,
|
||||
pinCode,
|
||||
parentAuthor,
|
||||
parentPermlink,
|
||||
permlink,
|
||||
commentValue,
|
||||
parentTags,
|
||||
)
|
||||
.then((response) => {
|
||||
userActivityMutation.mutate({
|
||||
pointsTy:PointActivityIds.COMMENT,
|
||||
transactionId:response.id
|
||||
})
|
||||
setIsSending(false);
|
||||
setCommentValue('');
|
||||
|
||||
if(inputRef.current){
|
||||
inputRef.current.setNativeProps({
|
||||
text: ''
|
||||
})
|
||||
}
|
||||
|
||||
dispatch(
|
||||
toastNotification(
|
||||
intl.formatMessage({
|
||||
id: 'alert.success',
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
//add comment cache entry
|
||||
dispatch(
|
||||
updateCommentCache(
|
||||
`${parentAuthor}/${parentPermlink}`,
|
||||
{
|
||||
author: currentAccount.name,
|
||||
permlink,
|
||||
parent_author: parentAuthor,
|
||||
parent_permlink: parentPermlink,
|
||||
markdownBody: commentValue,
|
||||
},
|
||||
{
|
||||
parentTags: parentTags || ['ecency'],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// delete quick comment draft cache if it exist
|
||||
if (drafts.has(draftId)) {
|
||||
dispatch(deleteDraftCacheEntry(draftId));
|
||||
}
|
||||
|
||||
//close should alwasy be called at method end
|
||||
onClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
error.message || JSON.stringify(error),
|
||||
);
|
||||
|
||||
|
||||
setIsSending(false);
|
||||
_addQuickCommentIntoCache(); //add comment value into cache if there is error while posting comment
|
||||
|
||||
});
|
||||
console.log('status : ', status);
|
||||
}
|
||||
};
|
||||
|
||||
const _handleExpandBtn = async () => {
|
||||
if (selectedPost) {
|
||||
// navigate to post on summary press
|
||||
const _handleOnSummaryPress = () => {
|
||||
Keyboard.dismiss();
|
||||
onClose();
|
||||
await delay(50);
|
||||
navigate({
|
||||
routeName: ROUTES.SCREENS.EDITOR,
|
||||
key: 'editor_replay',
|
||||
routeName: ROUTES.SCREENS.POST,
|
||||
params: {
|
||||
isReply: true,
|
||||
post: selectedPost,
|
||||
content: selectedPost,
|
||||
},
|
||||
key: get(selectedPost, 'permlink'),
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// handle submit reply
|
||||
const _submitReply = async () => {
|
||||
if (!commentValue) {
|
||||
return;
|
||||
}
|
||||
if (isSending) {
|
||||
return;
|
||||
}
|
||||
|
||||
//REMOVED FOR TESTING, CAN BE PUT BACK IF APP STILL CRASHES
|
||||
// const _deboucedCacheUpdate = useCallback(debounce(_addQuickCommentIntoCache, 500), [])
|
||||
if (currentAccount) {
|
||||
setIsSending(true);
|
||||
|
||||
const permlink = generateReplyPermlink(selectedPost.author);
|
||||
|
||||
const parentAuthor = selectedPost.author;
|
||||
const parentPermlink = selectedPost.permlink;
|
||||
const parentTags = selectedPost.json_metadata.tags;
|
||||
console.log(
|
||||
currentAccount,
|
||||
pinCode,
|
||||
parentAuthor,
|
||||
parentPermlink,
|
||||
permlink,
|
||||
commentValue,
|
||||
parentTags,
|
||||
);
|
||||
|
||||
const status = await postComment(
|
||||
currentAccount,
|
||||
pinCode,
|
||||
parentAuthor,
|
||||
parentPermlink,
|
||||
permlink,
|
||||
commentValue,
|
||||
parentTags,
|
||||
)
|
||||
.then((response) => {
|
||||
userActivityMutation.mutate({
|
||||
pointsTy: PointActivityIds.COMMENT,
|
||||
transactionId: response.id,
|
||||
});
|
||||
setIsSending(false);
|
||||
setCommentValue('');
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.setNativeProps({
|
||||
text: '',
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(
|
||||
toastNotification(
|
||||
intl.formatMessage({
|
||||
id: 'alert.success',
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
//add comment cache entry
|
||||
dispatch(
|
||||
updateCommentCache(
|
||||
`${parentAuthor}/${parentPermlink}`,
|
||||
{
|
||||
author: currentAccount.name,
|
||||
permlink,
|
||||
parent_author: parentAuthor,
|
||||
parent_permlink: parentPermlink,
|
||||
markdownBody: commentValue,
|
||||
},
|
||||
{
|
||||
parentTags: parentTags || ['ecency'],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// delete quick comment draft cache if it exist
|
||||
if (drafts.has(draftId)) {
|
||||
dispatch(deleteDraftCacheEntry(draftId));
|
||||
}
|
||||
|
||||
//close should alwasy be called at method end
|
||||
onClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
error.message || JSON.stringify(error),
|
||||
);
|
||||
|
||||
setIsSending(false);
|
||||
_addQuickCommentIntoCache(); //add comment value into cache if there is error while posting comment
|
||||
});
|
||||
console.log('status : ', status);
|
||||
}
|
||||
};
|
||||
|
||||
const _handleExpandBtn = async () => {
|
||||
if (selectedPost) {
|
||||
Keyboard.dismiss();
|
||||
onClose();
|
||||
await delay(50);
|
||||
navigate({
|
||||
routeName: ROUTES.SCREENS.EDITOR,
|
||||
key: 'editor_replay',
|
||||
params: {
|
||||
isReply: true,
|
||||
post: selectedPost,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const _onChangeText = (value) => {
|
||||
setCommentValue(value);
|
||||
//REMOVED FOR TESTING, CAN BE PUT BACK IF APP STILL CRASHES
|
||||
// _deboucedCacheUpdate(value)
|
||||
}
|
||||
// const _deboucedCacheUpdate = useCallback(debounce(_addQuickCommentIntoCache, 500), [])
|
||||
|
||||
const _onChangeText = (value) => {
|
||||
setCommentValue(value);
|
||||
//REMOVED FOR TESTING, CAN BE PUT BACK IF APP STILL CRASHES
|
||||
// _deboucedCacheUpdate(value)
|
||||
};
|
||||
|
||||
//VIEW_RENDERERS
|
||||
|
||||
//VIEW_RENDERERS
|
||||
const _renderSummary = () => (
|
||||
<TouchableOpacity onPress={() => _handleOnSummaryPress()}>
|
||||
<Text numberOfLines={2} style={styles.summaryStyle}>
|
||||
{headerText}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
|
||||
const _renderSummary = () => (
|
||||
<TouchableOpacity onPress={() => _handleOnSummaryPress()}>
|
||||
<Text numberOfLines={2} style={styles.summaryStyle}>
|
||||
{headerText}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
const _renderAvatar = () => (
|
||||
<View style={styles.avatarAndNameContainer}>
|
||||
<UserAvatar noAction username={currentAccount.username} />
|
||||
<View style={styles.nameContainer}>
|
||||
<Text style={styles.name}>{`@${currentAccount.username}`}</Text>
|
||||
const _renderAvatar = () => (
|
||||
<View style={styles.avatarAndNameContainer}>
|
||||
<UserAvatar noAction username={currentAccount.username} />
|
||||
<View style={styles.nameContainer}>
|
||||
<Text style={styles.name}>{`@${currentAccount.username}`}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
);
|
||||
|
||||
const _renderExpandBtn = () => (
|
||||
<View style={styles.expandBtnContainer}>
|
||||
<IconButton
|
||||
iconStyle={styles.backIcon}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="arrow-expand"
|
||||
onPress={_handleExpandBtn}
|
||||
size={28}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
const _renderReplyBtn = () => (
|
||||
<View style={styles.replyBtnContainer}>
|
||||
<TextButton
|
||||
style={styles.cancelButton}
|
||||
onPress={_handleClosePress}
|
||||
text={intl.formatMessage({
|
||||
id: 'quick_reply.close',
|
||||
})}
|
||||
/>
|
||||
<MainButton
|
||||
style={styles.commentBtn}
|
||||
onPress={() => _submitReply()}
|
||||
text={intl.formatMessage({
|
||||
id: 'quick_reply.reply',
|
||||
})}
|
||||
isLoading={isSending}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
|
||||
const _renderContent = (
|
||||
<View style={styles.modalContainer}>
|
||||
{_renderSummary()}
|
||||
{_renderAvatar()}
|
||||
<View style={styles.inputContainer}>
|
||||
<TextInput
|
||||
innerRef={inputRef}
|
||||
onChangeText={_onChangeText}
|
||||
autoFocus
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'quick_reply.placeholder',
|
||||
})}
|
||||
placeholderTextColor="#c1c5c7"
|
||||
style={styles.textInput}
|
||||
multiline={true}
|
||||
numberOfLines={5}
|
||||
textAlignVertical="top"
|
||||
const _renderExpandBtn = () => (
|
||||
<View style={styles.expandBtnContainer}>
|
||||
<IconButton
|
||||
iconStyle={styles.backIcon}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="arrow-expand"
|
||||
onPress={_handleExpandBtn}
|
||||
size={28}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.footer}>
|
||||
{_renderExpandBtn()}
|
||||
{_renderReplyBtn()}
|
||||
);
|
||||
const _renderReplyBtn = () => (
|
||||
<View style={styles.replyBtnContainer}>
|
||||
<TextButton
|
||||
style={styles.cancelButton}
|
||||
onPress={_handleClosePress}
|
||||
text={intl.formatMessage({
|
||||
id: 'quick_reply.close',
|
||||
})}
|
||||
/>
|
||||
<MainButton
|
||||
style={styles.commentBtn}
|
||||
onPress={() => _submitReply()}
|
||||
text={intl.formatMessage({
|
||||
id: 'quick_reply.reply',
|
||||
})}
|
||||
isLoading={isSending}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
);
|
||||
|
||||
return _renderContent
|
||||
});
|
||||
const _renderContent = (
|
||||
<View style={styles.modalContainer}>
|
||||
{_renderSummary()}
|
||||
{_renderAvatar()}
|
||||
<View style={styles.inputContainer}>
|
||||
<TextInput
|
||||
innerRef={inputRef}
|
||||
onChangeText={_onChangeText}
|
||||
autoFocus
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'quick_reply.placeholder',
|
||||
})}
|
||||
placeholderTextColor="#c1c5c7"
|
||||
style={styles.textInput}
|
||||
multiline={true}
|
||||
numberOfLines={5}
|
||||
textAlignVertical="top"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.footer}>
|
||||
{_renderExpandBtn()}
|
||||
{_renderReplyBtn()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
return _renderContent;
|
||||
},
|
||||
);
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1 +1 @@
|
||||
export * from './simpleChart';
|
||||
export * from './simpleChart';
|
||||
|
@ -1,50 +1,54 @@
|
||||
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){
|
||||
return null;
|
||||
}
|
||||
|
||||
const _chartWidth = baseWidth + baseWidth/(data.length -1)
|
||||
const _chartBackgroundColor = EStyleSheet.value('$primaryLightBackground');
|
||||
return (
|
||||
<LineChart
|
||||
data={{
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
data
|
||||
}
|
||||
],
|
||||
}}
|
||||
width={_chartWidth} // from react-native
|
||||
height={chartHeight}
|
||||
withHorizontalLabels={showLabels}
|
||||
withVerticalLabels={false}
|
||||
withHorizontalLines={false}
|
||||
withDots={false}
|
||||
withInnerLines={false}
|
||||
chartConfig={{
|
||||
backgroundColor:_chartBackgroundColor,
|
||||
backgroundGradientFrom: _chartBackgroundColor,
|
||||
backgroundGradientTo: _chartBackgroundColor,
|
||||
fillShadowGradient: EStyleSheet.value('$chartBlue'),
|
||||
fillShadowGradientOpacity:0.8,
|
||||
labelColor:() => EStyleSheet.value('$primaryDarkText'),
|
||||
color: () => showLine?EStyleSheet.value('$chartBlue'):'transparent',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export const SimpleChart = ({
|
||||
data,
|
||||
baseWidth,
|
||||
chartHeight,
|
||||
showLine,
|
||||
showLabels = false,
|
||||
}: CoinChartProps) => {
|
||||
if (!data || !data.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const _chartWidth = baseWidth + baseWidth / (data.length - 1);
|
||||
const _chartBackgroundColor = EStyleSheet.value('$primaryLightBackground');
|
||||
return (
|
||||
<LineChart
|
||||
data={{
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
data,
|
||||
},
|
||||
],
|
||||
}}
|
||||
width={_chartWidth} // from react-native
|
||||
height={chartHeight}
|
||||
withHorizontalLabels={showLabels}
|
||||
withVerticalLabels={false}
|
||||
withHorizontalLines={false}
|
||||
withDots={false}
|
||||
withInnerLines={false}
|
||||
chartConfig={{
|
||||
backgroundColor: _chartBackgroundColor,
|
||||
backgroundGradientFrom: _chartBackgroundColor,
|
||||
backgroundGradientTo: _chartBackgroundColor,
|
||||
fillShadowGradient: EStyleSheet.value('$chartBlue'),
|
||||
fillShadowGradientOpacity: 0.8,
|
||||
labelColor: () => EStyleSheet.value('$primaryDarkText'),
|
||||
color: () => (showLine ? EStyleSheet.value('$chartBlue') : 'transparent'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,192 +1,192 @@
|
||||
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;
|
||||
|
||||
export const loadPosts = async ({
|
||||
filterKey,
|
||||
prevPosts,
|
||||
tabMeta,
|
||||
setTabMeta,
|
||||
isLatestPostsCheck = false,
|
||||
getFor,
|
||||
isConnected,
|
||||
isLoggedIn,
|
||||
refreshing,
|
||||
feedUsername,
|
||||
pinnedPermlink,
|
||||
pageType,
|
||||
tag,
|
||||
nsfw,
|
||||
filterKey,
|
||||
prevPosts,
|
||||
tabMeta,
|
||||
setTabMeta,
|
||||
isLatestPostsCheck = false,
|
||||
getFor,
|
||||
isConnected,
|
||||
isLoggedIn,
|
||||
refreshing,
|
||||
feedUsername,
|
||||
pinnedPermlink,
|
||||
pageType,
|
||||
tag,
|
||||
nsfw,
|
||||
}: LoadPostsOptions) => {
|
||||
let filter = filterKey;
|
||||
|
||||
}:LoadPostsOptions) => {
|
||||
let filter = filterKey;
|
||||
|
||||
//match filter with api if is friends
|
||||
if(filter === 'friends'){
|
||||
filter = 'feed';
|
||||
}
|
||||
//match filter with api if is friends
|
||||
if (filter === 'friends') {
|
||||
filter = 'feed';
|
||||
}
|
||||
|
||||
const {isLoading, startPermlink, startAuthor} = tabMeta;
|
||||
|
||||
//reject update if already loading
|
||||
if (
|
||||
isLoading ||
|
||||
!isConnected ||
|
||||
(!isLoggedIn && filterKey === 'feed') ||
|
||||
(!isLoggedIn && filterKey === 'communities')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { isLoading, startPermlink, startAuthor } = tabMeta;
|
||||
|
||||
//reject update if no connection
|
||||
if (!isConnected && (refreshing || isLoading)) {
|
||||
setTabMeta({
|
||||
...tabMeta,
|
||||
isLoading:false,
|
||||
isRefreshing:false,
|
||||
})
|
||||
return;
|
||||
}
|
||||
//reject update if already loading
|
||||
if (
|
||||
isLoading ||
|
||||
!isConnected ||
|
||||
(!isLoggedIn && filterKey === 'feed') ||
|
||||
(!isLoggedIn && filterKey === 'communities')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
//reject update if no connection
|
||||
if (!isConnected && (refreshing || isLoading)) {
|
||||
setTabMeta({
|
||||
...tabMeta,
|
||||
isLoading:true,
|
||||
isRefreshing:refreshing,
|
||||
})
|
||||
|
||||
let options = {} as any;
|
||||
const limit = isLatestPostsCheck ? 5 : POSTS_FETCH_COUNT;
|
||||
let func = null;
|
||||
isLoading: false,
|
||||
isRefreshing: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
filter === 'feed' ||
|
||||
filter === 'communities' ||
|
||||
filter === 'posts' ||
|
||||
filter === 'blog' ||
|
||||
getFor === 'blog' ||
|
||||
filter === 'reblogs'
|
||||
) {
|
||||
if (filter === 'communities') {
|
||||
func = getRankedPosts;
|
||||
options = {
|
||||
observer: feedUsername,
|
||||
sort: 'created',
|
||||
tag: 'my',
|
||||
limit,
|
||||
};
|
||||
} else {
|
||||
func = getAccountPosts;
|
||||
options = {
|
||||
observer: feedUsername || '',
|
||||
account: feedUsername,
|
||||
limit,
|
||||
sort: filter,
|
||||
};
|
||||
setTabMeta({
|
||||
...tabMeta,
|
||||
isLoading: true,
|
||||
isRefreshing: refreshing,
|
||||
});
|
||||
|
||||
if ((pageType === 'profile' || pageType === 'ownProfile') && (filter === 'feed' || filter === 'posts')) {
|
||||
options.sort = 'posts';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let options = {} as any;
|
||||
const limit = isLatestPostsCheck ? 5 : POSTS_FETCH_COUNT;
|
||||
let func = null;
|
||||
|
||||
if (
|
||||
filter === 'feed' ||
|
||||
filter === 'communities' ||
|
||||
filter === 'posts' ||
|
||||
filter === 'blog' ||
|
||||
getFor === 'blog' ||
|
||||
filter === 'reblogs'
|
||||
) {
|
||||
if (filter === 'communities') {
|
||||
func = getRankedPosts;
|
||||
options = {
|
||||
tag,
|
||||
observer: feedUsername,
|
||||
sort: 'created',
|
||||
tag: 'my',
|
||||
limit,
|
||||
};
|
||||
} else {
|
||||
func = getAccountPosts;
|
||||
options = {
|
||||
observer: feedUsername || '',
|
||||
account: feedUsername,
|
||||
limit,
|
||||
sort: filter,
|
||||
};
|
||||
|
||||
if (
|
||||
(pageType === 'profile' || pageType === 'ownProfile') &&
|
||||
(filter === 'feed' || filter === 'posts')
|
||||
) {
|
||||
options.sort = 'posts';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
func = getRankedPosts;
|
||||
options = {
|
||||
tag,
|
||||
limit,
|
||||
sort: filter,
|
||||
};
|
||||
}
|
||||
|
||||
if (startAuthor && startPermlink && !refreshing && !isLatestPostsCheck) {
|
||||
options.start_author = startAuthor;
|
||||
options.start_permlink = startPermlink;
|
||||
}
|
||||
|
||||
if (startAuthor && startPermlink && !refreshing && !isLatestPostsCheck) {
|
||||
options.start_author = startAuthor;
|
||||
options.start_permlink = startPermlink;
|
||||
}
|
||||
try {
|
||||
//fetching posts
|
||||
const result: any[] = await func(options, feedUsername, nsfw);
|
||||
|
||||
try {
|
||||
//fetching posts
|
||||
const result:any[] = await func(options, feedUsername, nsfw);
|
||||
|
||||
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 (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){
|
||||
let pinnedIndex = -1;
|
||||
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 ((pageType === 'profile' || pageType === 'ownProfile') && pinnedPermlink) {
|
||||
let pinnedIndex = -1;
|
||||
result.forEach((post, index) => {
|
||||
if (post.author === feedUsername && post.permlink === pinnedPermlink) {
|
||||
pinnedIndex = index;
|
||||
}
|
||||
});
|
||||
result.splice(pinnedIndex, 1);
|
||||
}
|
||||
|
||||
// cacheDispatch(updateFilterCache(filter, result, refreshing))
|
||||
setTabMeta({
|
||||
...tabMeta,
|
||||
isLoading:false,
|
||||
isRefreshing:false,
|
||||
})
|
||||
|
||||
const retData = {
|
||||
latestPosts:null,
|
||||
updatedPosts:null
|
||||
}
|
||||
|
||||
if(isLatestPostsCheck){
|
||||
const latestPosts = filterLatestPosts(result, prevPosts.slice(0, 5));
|
||||
retData.latestPosts = latestPosts
|
||||
}else{
|
||||
const updatedPosts = getUpdatedPosts(
|
||||
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){
|
||||
const pinnedPost = await getPost(feedUsername, pinnedPermlink);
|
||||
pinnedPost.stats = {is_pinned_blog:true, ...pinnedPost.stats};
|
||||
retData.updatedPosts = [pinnedPost, ...retData.updatedPosts];
|
||||
}
|
||||
|
||||
return retData
|
||||
|
||||
|
||||
} catch (err) {
|
||||
setTabMeta({
|
||||
...tabMeta,
|
||||
isLoading:false,
|
||||
isRefreshing:false,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
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)
|
||||
//if filter is feed convert back to reducer filter
|
||||
if (filter === 'feed') {
|
||||
filter = 'friends';
|
||||
}
|
||||
}
|
||||
|
||||
// cacheDispatch(updateFilterCache(filter, result, refreshing))
|
||||
setTabMeta({
|
||||
...tabMeta,
|
||||
isLoading: false,
|
||||
isRefreshing: false,
|
||||
});
|
||||
|
||||
const retData = {
|
||||
latestPosts: null,
|
||||
updatedPosts: null,
|
||||
};
|
||||
|
||||
if (isLatestPostsCheck) {
|
||||
const latestPosts = filterLatestPosts(result, prevPosts.slice(0, 5));
|
||||
retData.latestPosts = latestPosts;
|
||||
} else {
|
||||
const updatedPosts = getUpdatedPosts(
|
||||
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
|
||||
) {
|
||||
const pinnedPost = await getPost(feedUsername, pinnedPermlink);
|
||||
pinnedPost.stats = { is_pinned_blog: true, ...pinnedPost.stats };
|
||||
retData.updatedPosts = [pinnedPost, ...retData.updatedPosts];
|
||||
}
|
||||
|
||||
return retData;
|
||||
} catch (err) {
|
||||
setTabMeta({
|
||||
...tabMeta,
|
||||
isLoading: false,
|
||||
isRefreshing: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
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,
|
||||
startPermlink: string;
|
||||
startAuthor: string;
|
||||
isLoading: boolean;
|
||||
isRefreshing: boolean;
|
||||
isNoPost: boolean;
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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];
|
||||
|
||||
@ -130,13 +125,12 @@ const TabEmptyView = ({
|
||||
|
||||
setRecommendedUsers(recommendeds);
|
||||
}, [followingUsers]);
|
||||
|
||||
|
||||
//fetching
|
||||
const _getRecommendedUsers = () => dispatch(fetchLeaderboard());
|
||||
const _getRecommendedCommunities = () => dispatch(fetchCommunities('', 10));
|
||||
|
||||
//formating
|
||||
//formating
|
||||
const _formatRecommendedCommunities = async (communitiesArray) => {
|
||||
try {
|
||||
const ecency = await getCommunity('hive-125125');
|
||||
@ -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,92 +239,89 @@ const TabEmptyView = ({
|
||||
|
||||
if (isNoPost) {
|
||||
if (filterKey === 'friends') {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text style={[globalStyles.subTitle, styles.noPostTitle]}>
|
||||
{intl.formatMessage({ id: 'profile.follow_people' })}
|
||||
</Text>
|
||||
<FlatList
|
||||
data={recommendedUsers}
|
||||
extraData={recommendedUsers}
|
||||
keyExtractor={(item, index) => `${item._id || item.id}${index}`}
|
||||
renderItem={({ item, index }) => (
|
||||
<UserListItem
|
||||
index={index}
|
||||
username={item._id}
|
||||
isHasRightItem
|
||||
rightText={
|
||||
item.isFollowing
|
||||
? intl.formatMessage({ id: 'user.unfollow' })
|
||||
: intl.formatMessage({ id: 'user.follow' })
|
||||
}
|
||||
rightTextStyle={[styles.followText, item.isFollowing && styles.unfollowText]}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isFollowing={item.isFollowing}
|
||||
isLoadingRightAction={
|
||||
followingUsers.hasOwnProperty(item._id) && followingUsers[item._id].loading
|
||||
}
|
||||
onPressRightText={_handleFollowUserButtonPress}
|
||||
handleOnPress={(username) =>
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.PROFILE,
|
||||
params: {
|
||||
username,
|
||||
},
|
||||
key: username,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else if (filterKey === 'communities') {
|
||||
return (
|
||||
<>
|
||||
<Text style={[globalStyles.subTitle, styles.noPostTitle]}>
|
||||
{intl.formatMessage({ id: 'profile.follow_communities' })}
|
||||
</Text>
|
||||
<FlatList
|
||||
data={recommendedCommunities}
|
||||
keyExtractor={(item, index) => `${item.id || item.title}${index}`}
|
||||
renderItem={({ item, index }) => (
|
||||
<CommunityListItem
|
||||
index={index}
|
||||
title={item.title}
|
||||
about={item.about}
|
||||
admins={item.admins}
|
||||
id={item.id}
|
||||
authors={item.num_authors}
|
||||
posts={item.num_pending}
|
||||
subscribers={item.subscribers}
|
||||
isNsfw={item.is_nsfw}
|
||||
name={item.name}
|
||||
handleOnPress={(name) =>
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.COMMUNITY,
|
||||
params: {
|
||||
tag: name,
|
||||
},
|
||||
})
|
||||
}
|
||||
handleSubscribeButtonPress={_handleSubscribeCommunityButtonPress}
|
||||
isSubscribed={item.isSubscribed}
|
||||
isLoadingRightAction={
|
||||
subscribingCommunities.hasOwnProperty(item.name) &&
|
||||
subscribingCommunities[item.name].loading
|
||||
}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EmptyScreen style={styles.emptyAnimationContainer} />
|
||||
)
|
||||
<>
|
||||
<Text style={[globalStyles.subTitle, styles.noPostTitle]}>
|
||||
{intl.formatMessage({ id: 'profile.follow_people' })}
|
||||
</Text>
|
||||
<FlatList
|
||||
data={recommendedUsers}
|
||||
extraData={recommendedUsers}
|
||||
keyExtractor={(item, index) => `${item._id || item.id}${index}`}
|
||||
renderItem={({ item, index }) => (
|
||||
<UserListItem
|
||||
index={index}
|
||||
username={item._id}
|
||||
isHasRightItem
|
||||
rightText={
|
||||
item.isFollowing
|
||||
? intl.formatMessage({ id: 'user.unfollow' })
|
||||
: intl.formatMessage({ id: 'user.follow' })
|
||||
}
|
||||
rightTextStyle={[styles.followText, item.isFollowing && styles.unfollowText]}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isFollowing={item.isFollowing}
|
||||
isLoadingRightAction={
|
||||
followingUsers.hasOwnProperty(item._id) && followingUsers[item._id].loading
|
||||
}
|
||||
onPressRightText={_handleFollowUserButtonPress}
|
||||
handleOnPress={(username) =>
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.PROFILE,
|
||||
params: {
|
||||
username,
|
||||
},
|
||||
key: username,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else if (filterKey === 'communities') {
|
||||
return (
|
||||
<>
|
||||
<Text style={[globalStyles.subTitle, styles.noPostTitle]}>
|
||||
{intl.formatMessage({ id: 'profile.follow_communities' })}
|
||||
</Text>
|
||||
<FlatList
|
||||
data={recommendedCommunities}
|
||||
keyExtractor={(item, index) => `${item.id || item.title}${index}`}
|
||||
renderItem={({ item, index }) => (
|
||||
<CommunityListItem
|
||||
index={index}
|
||||
title={item.title}
|
||||
about={item.about}
|
||||
admins={item.admins}
|
||||
id={item.id}
|
||||
authors={item.num_authors}
|
||||
posts={item.num_pending}
|
||||
subscribers={item.subscribers}
|
||||
isNsfw={item.is_nsfw}
|
||||
name={item.name}
|
||||
handleOnPress={(name) =>
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.COMMUNITY,
|
||||
params: {
|
||||
tag: name,
|
||||
},
|
||||
})
|
||||
}
|
||||
handleSubscribeButtonPress={_handleSubscribeCommunityButtonPress}
|
||||
isSubscribed={item.isSubscribed}
|
||||
isLoadingRightAction={
|
||||
subscribingCommunities.hasOwnProperty(item.name) &&
|
||||
subscribingCommunities[item.name].loading
|
||||
}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <EmptyScreen style={styles.emptyAnimationContainer} />;
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,4 +333,3 @@ const TabEmptyView = ({
|
||||
};
|
||||
|
||||
export default withNavigation(TabEmptyView);
|
||||
|
||||
|
@ -1,119 +1,105 @@
|
||||
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 = ({
|
||||
goToPage,
|
||||
tabs,
|
||||
shouldStack,
|
||||
firstStack,
|
||||
secondStack,
|
||||
initialFirstStackIndex,
|
||||
onFilterSelect,
|
||||
toggleHideImagesFlag,
|
||||
pageType
|
||||
goToPage,
|
||||
tabs,
|
||||
shouldStack,
|
||||
firstStack,
|
||||
secondStack,
|
||||
initialFirstStackIndex,
|
||||
onFilterSelect,
|
||||
toggleHideImagesFlag,
|
||||
pageType,
|
||||
}: StackedTabBarProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
}:StackedTabBarProps) => {
|
||||
const customiseModalRef = useRef<CustomiseFiltersModalRef>();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
//redux properties
|
||||
const isHideImages = useSelector((state) => state.application.hidePostsThumbnails);
|
||||
|
||||
const customiseModalRef = useRef<CustomiseFiltersModalRef>();
|
||||
|
||||
//redux properties
|
||||
const isHideImages = useSelector((state) => state.application.hidePostsThumbnails);
|
||||
const [selectedFilterIndex, setSelectedFilterIndex] = useState(initialFirstStackIndex);
|
||||
const [selectedSecondStackIndex, setSelectedSecondStackIndex] = useState(0);
|
||||
|
||||
const [selectedFilterIndex, setSelectedFilterIndex] = useState(initialFirstStackIndex);
|
||||
const [selectedSecondStackIndex, setSelectedSecondStackIndex] = useState(0);
|
||||
const enableCustomTabs = pageType !== undefined;
|
||||
|
||||
const enableCustomTabs = pageType !== undefined;
|
||||
|
||||
|
||||
const _onCustomisePress = () => {
|
||||
if(customiseModalRef.current){
|
||||
customiseModalRef.current.show();
|
||||
}
|
||||
const _onCustomisePress = () => {
|
||||
if (customiseModalRef.current) {
|
||||
customiseModalRef.current.show();
|
||||
}
|
||||
};
|
||||
|
||||
const _onToggleImagesPress = () => {
|
||||
dispatch(setHidePostsThumbnails(!isHideImages))
|
||||
}
|
||||
const _onToggleImagesPress = () => {
|
||||
dispatch(setHidePostsThumbnails(!isHideImages));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
return (
|
||||
<>
|
||||
<FilterBar
|
||||
options={firstStack.map((item, index) => {
|
||||
return tabs[index]
|
||||
? tabs[index]
|
||||
: intl.formatMessage({ id: item.label.toLowerCase() }).toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
return tabs[index]
|
||||
? tabs[index]
|
||||
: 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 && (
|
||||
<FilterBar
|
||||
options={secondStack.map((item) =>
|
||||
intl.formatMessage({ id: item.label.toLowerCase() }).toUpperCase(),
|
||||
)}
|
||||
selectedOptionIndex={selectedSecondStackIndex}
|
||||
onDropdownSelect={(index)=>{
|
||||
setSelectedSecondStackIndex(index)
|
||||
onFilterSelect(secondStack[index].filterKey);
|
||||
goToPage(firstStack.length + index);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{enableCustomTabs && (
|
||||
<CustomiseFiltersModal
|
||||
pageType={pageType}
|
||||
ref={customiseModalRef}
|
||||
{selectedFilterIndex == 0 && shouldStack && (
|
||||
<FilterBar
|
||||
options={secondStack.map((item) =>
|
||||
intl.formatMessage({ id: item.label.toLowerCase() }).toUpperCase(),
|
||||
)}
|
||||
selectedOptionIndex={selectedSecondStackIndex}
|
||||
onDropdownSelect={(index) => {
|
||||
setSelectedSecondStackIndex(index);
|
||||
onFilterSelect(secondStack[index].filterKey);
|
||||
goToPage(firstStack.length + index);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{enableCustomTabs && <CustomiseFiltersModal pageType={pageType} ref={customiseModalRef} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,30 +1,30 @@
|
||||
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;
|
||||
const SCROLL_POPUP_THRESHOLD = 5000;
|
||||
|
||||
const TabContent = ({
|
||||
filterKey,
|
||||
filterKey,
|
||||
isFeedScreen,
|
||||
isInitialTab,
|
||||
pageType,
|
||||
@ -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,295 +79,287 @@ 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,
|
||||
_feedUsername,
|
||||
_posts:initialPosts,
|
||||
_tabMeta:DEFAULT_TAB_META
|
||||
isLatestPostsCheck: false,
|
||||
_feedUsername,
|
||||
_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
|
||||
});
|
||||
},
|
||||
timeLeft
|
||||
);
|
||||
setPostFetchTimer(_postFetchTimer)
|
||||
const isLatestPostsCheck = true;
|
||||
_loadPosts({
|
||||
shouldReset: false,
|
||||
isLatestPostsCheck,
|
||||
});
|
||||
}, 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]);
|
||||
//schedule refetch of new posts by checking time of current post
|
||||
_scheduleLatestPostsCheck(updatedPosts[0]);
|
||||
|
||||
if (isInitialTab) {
|
||||
dispatch(setInitPosts(updatedPosts));
|
||||
}
|
||||
if (isInitialTab) {
|
||||
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])
|
||||
_scheduleLatestPostsCheck(latestPosts[0]);
|
||||
setLatestPosts([]);
|
||||
}
|
||||
_scrollToTop();
|
||||
_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) => {
|
||||
setEnableScrollTop(value);
|
||||
},
|
||||
500,
|
||||
{ leading: true },
|
||||
);
|
||||
|
||||
const scrollPopupDebouce = debounce((value)=>{
|
||||
setEnableScrollTop(value);
|
||||
}, 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
|
||||
//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()
|
||||
}
|
||||
}}
|
||||
onScroll={_onScroll}
|
||||
onScrollEndDrag={_handleOnScroll}
|
||||
isRefreshing={tabMeta.isRefreshing}
|
||||
isLoading={tabMeta.isLoading}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
pageType={pageType}
|
||||
showQuickReplyModal={_showQuickReplyModal}
|
||||
/>
|
||||
<ScrollTopPopup
|
||||
popupAvatars={latestPosts.map(post=>post.avatar || '')}
|
||||
enableScrollTop={enableScrollTop}
|
||||
onPress={_onPostsPopupPress}
|
||||
onClose={()=>{
|
||||
setLatestPosts([])
|
||||
setEnableScrollTop(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<PostsList
|
||||
ref={postsListRef}
|
||||
data={posts}
|
||||
isFeedScreen={isFeedScreen}
|
||||
promotedPosts={promotedPosts}
|
||||
onLoadPosts={(shouldReset) => {
|
||||
_loadPosts({ shouldReset });
|
||||
if (shouldReset) {
|
||||
_getPromotedPosts();
|
||||
}
|
||||
}}
|
||||
onScroll={_onScroll}
|
||||
onScrollEndDrag={_handleOnScroll}
|
||||
isRefreshing={tabMeta.isRefreshing}
|
||||
isLoading={tabMeta.isLoading}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
pageType={pageType}
|
||||
showQuickReplyModal={_showQuickReplyModal}
|
||||
/>
|
||||
<ScrollTopPopup
|
||||
popupAvatars={latestPosts.map((post) => post.avatar || '')}
|
||||
enableScrollTop={enableScrollTop}
|
||||
onPress={_onPostsPopupPress}
|
||||
onClose={() => {
|
||||
setLatestPosts([]);
|
||||
setEnableScrollTop(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabContent;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -54,7 +54,7 @@ export default EStyleSheet.create({
|
||||
textAlign: 'right',
|
||||
},
|
||||
centerDescription: {
|
||||
marginTop:8,
|
||||
marginTop: 8,
|
||||
fontSize: 12,
|
||||
color: '$primaryBlack',
|
||||
fontWeight: '600',
|
||||
|
@ -2,13 +2,13 @@ import { proxifyImageSrc } from '@ecency/render-helper';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Keyboard,
|
||||
Platform,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Keyboard,
|
||||
Platform,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { View as AnimatedView } from 'react-native-animatable';
|
||||
import Animated, { Easing } from 'react-native-reanimated';
|
||||
@ -18,312 +18,311 @@ import { FlatList } from 'react-native-gesture-handler';
|
||||
import { Icon, IconButton } from '../..';
|
||||
import { UploadedMedia } from '../../../models';
|
||||
import styles, {
|
||||
COMPACT_HEIGHT,
|
||||
EXPANDED_HEIGHT,
|
||||
MAX_HORIZONTAL_THUMBS,
|
||||
COMPACT_HEIGHT,
|
||||
EXPANDED_HEIGHT,
|
||||
MAX_HORIZONTAL_THUMBS,
|
||||
} from './uploadsGalleryModalStyles';
|
||||
import { useMediaDeleteMutation } from '../../../providers/queries';
|
||||
|
||||
type Props = {
|
||||
insertedMediaUrls: string[];
|
||||
mediaUploads: any[];
|
||||
indices: Map<number, boolean>;
|
||||
isAddingToUploads: boolean;
|
||||
getMediaUploads: () => void;
|
||||
deleteMedia: (ids: string) => Promise<boolean>;
|
||||
insertMedia: (map: Map<number, boolean>) => void;
|
||||
handleOpenGallery: (addToUploads?: boolean) => void;
|
||||
handleOpenCamera: () => void;
|
||||
handleOpenForUpload: () => void;
|
||||
insertedMediaUrls: string[];
|
||||
mediaUploads: any[];
|
||||
indices: Map<number, boolean>;
|
||||
isAddingToUploads: boolean;
|
||||
getMediaUploads: () => void;
|
||||
deleteMedia: (ids: string) => Promise<boolean>;
|
||||
insertMedia: (map: Map<number, boolean>) => void;
|
||||
handleOpenGallery: (addToUploads?: boolean) => void;
|
||||
handleOpenCamera: () => void;
|
||||
handleOpenForUpload: () => void;
|
||||
};
|
||||
|
||||
const UploadsGalleryContent = ({
|
||||
insertedMediaUrls,
|
||||
mediaUploads,
|
||||
isAddingToUploads,
|
||||
getMediaUploads,
|
||||
deleteMedia,
|
||||
insertMedia,
|
||||
handleOpenGallery,
|
||||
handleOpenCamera,
|
||||
insertedMediaUrls,
|
||||
mediaUploads,
|
||||
isAddingToUploads,
|
||||
getMediaUploads,
|
||||
deleteMedia,
|
||||
insertMedia,
|
||||
handleOpenGallery,
|
||||
handleOpenCamera,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const intl = useIntl();
|
||||
|
||||
const deleteMediaMutation = useMediaDeleteMutation();
|
||||
const deleteMediaMutation = useMediaDeleteMutation();
|
||||
|
||||
const [deleteIds, setDeleteIds] = useState<string[]>([]);
|
||||
const [isDeleteMode, setIsDeleteMode] = useState(false);
|
||||
const [isExpandedMode, setIsExpandedMode] = useState(false);
|
||||
const [deleteIds, setDeleteIds] = useState<string[]>([]);
|
||||
const [isDeleteMode, setIsDeleteMode] = useState(false);
|
||||
const [isExpandedMode, setIsExpandedMode] = useState(false);
|
||||
|
||||
const animatedHeightRef = useRef(new Animated.Value(COMPACT_HEIGHT));
|
||||
const animatedHeightRef = useRef(new Animated.Value(COMPACT_HEIGHT));
|
||||
|
||||
const isDeleting = deleteMediaMutation.isLoading
|
||||
const isDeleting = deleteMediaMutation.isLoading;
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpandedMode) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
}, [isExpandedMode]);
|
||||
useEffect(() => {
|
||||
if (isExpandedMode) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
}, [isExpandedMode]);
|
||||
|
||||
|
||||
const _deleteMedia = async () => {
|
||||
deleteMediaMutation.mutate(deleteIds, {
|
||||
onSettled: () => {
|
||||
setIsDeleteMode(false);
|
||||
setDeleteIds([]);
|
||||
}
|
||||
});
|
||||
};
|
||||
const _deleteMedia = async () => {
|
||||
deleteMediaMutation.mutate(deleteIds, {
|
||||
onSettled: () => {
|
||||
setIsDeleteMode(false);
|
||||
setDeleteIds([]);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const _onDeletePress = async () => {
|
||||
if (isDeleteMode && deleteIds.length > 0) {
|
||||
const _onCancelPress = () => {
|
||||
setIsDeleteMode(false);
|
||||
setDeleteIds([]);
|
||||
};
|
||||
const _onDeletePress = async () => {
|
||||
if (isDeleteMode && deleteIds.length > 0) {
|
||||
const _onCancelPress = () => {
|
||||
setIsDeleteMode(false);
|
||||
setDeleteIds([]);
|
||||
};
|
||||
|
||||
Alert.alert(
|
||||
intl.formatMessage({ id: 'alert.delete' }),
|
||||
intl.formatMessage({ id: 'uploads_modal.confirm_delete' }),
|
||||
[
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.cancel' }),
|
||||
style: 'cancel',
|
||||
onPress: _onCancelPress,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.confirm' }),
|
||||
onPress: () => _deleteMedia(),
|
||||
},
|
||||
],
|
||||
);
|
||||
Alert.alert(
|
||||
intl.formatMessage({ id: 'alert.delete' }),
|
||||
intl.formatMessage({ id: 'uploads_modal.confirm_delete' }),
|
||||
[
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.cancel' }),
|
||||
style: 'cancel',
|
||||
onPress: _onCancelPress,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.confirm' }),
|
||||
onPress: () => _deleteMedia(),
|
||||
},
|
||||
],
|
||||
);
|
||||
} else {
|
||||
setIsDeleteMode(!isDeleteMode);
|
||||
}
|
||||
};
|
||||
|
||||
//render list item for snippet and handle actions;
|
||||
const _renderItem = ({ item, index }: { item: UploadedMedia; index: number }) => {
|
||||
const _onPress = () => {
|
||||
if (isDeleteMode) {
|
||||
const idIndex = deleteIds.indexOf(item._id);
|
||||
if (idIndex >= 0) {
|
||||
deleteIds.splice(idIndex, 1);
|
||||
} else {
|
||||
setIsDeleteMode(!isDeleteMode);
|
||||
deleteIds.push(item._id);
|
||||
}
|
||||
setDeleteIds([...deleteIds]);
|
||||
} else {
|
||||
insertMedia(new Map([[index, true]]));
|
||||
}
|
||||
};
|
||||
|
||||
//render list item for snippet and handle actions;
|
||||
const _renderItem = ({ item, index }: { item: UploadedMedia; index: number }) => {
|
||||
const _onPress = () => {
|
||||
if (isDeleteMode) {
|
||||
const idIndex = deleteIds.indexOf(item._id);
|
||||
if (idIndex >= 0) {
|
||||
deleteIds.splice(idIndex, 1);
|
||||
} else {
|
||||
deleteIds.push(item._id);
|
||||
}
|
||||
setDeleteIds([...deleteIds]);
|
||||
} else {
|
||||
insertMedia(new Map([[index, true]]));
|
||||
}
|
||||
};
|
||||
|
||||
const thumbUrl = proxifyImageSrc(item.url, 600, 500, Platform.OS === 'ios' ? 'match' : 'webp');
|
||||
let isInsertedTimes = 0;
|
||||
insertedMediaUrls.forEach((url) => (isInsertedTimes += url === item.url ? 1 : 0));
|
||||
const isToBeDeleted = deleteIds.indexOf(item._id) >= 0;
|
||||
const transformStyle = {
|
||||
transform: isToBeDeleted ? [{ scaleX: 0.7 }, { scaleY: 0.7 }] : [],
|
||||
};
|
||||
|
||||
const _renderMinus = () =>
|
||||
isDeleteMode && (
|
||||
<AnimatedView animation="zoomIn" duration={300} style={styles.minusContainer}>
|
||||
<Icon
|
||||
color={EStyleSheet.value('$pureWhite')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="minus"
|
||||
size={20}
|
||||
/>
|
||||
</AnimatedView>
|
||||
);
|
||||
|
||||
const _renderCounter = () =>
|
||||
isInsertedTimes > 0 &&
|
||||
!isDeleteMode && (
|
||||
<AnimatedView animation="zoomIn" duration={300} style={styles.counterContainer}>
|
||||
<Text style={styles.counterText}>{isInsertedTimes}</Text>
|
||||
</AnimatedView>
|
||||
);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={_onPress} disabled={isDeleting}>
|
||||
<View style={transformStyle}>
|
||||
<FastImage
|
||||
source={{ uri: thumbUrl }}
|
||||
style={isExpandedMode ? styles.gridMediaItem : styles.mediaItem}
|
||||
/>
|
||||
{_renderCounter()}
|
||||
{_renderMinus()}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
const thumbUrl = proxifyImageSrc(item.url, 600, 500, Platform.OS === 'ios' ? 'match' : 'webp');
|
||||
let isInsertedTimes = 0;
|
||||
insertedMediaUrls.forEach((url) => (isInsertedTimes += url === item.url ? 1 : 0));
|
||||
const isToBeDeleted = deleteIds.indexOf(item._id) >= 0;
|
||||
const transformStyle = {
|
||||
transform: isToBeDeleted ? [{ scaleX: 0.7 }, { scaleY: 0.7 }] : [],
|
||||
};
|
||||
|
||||
const _renderSelectButton = (iconName: string, text: string, onPress: () => void) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
onPress && onPress();
|
||||
}}
|
||||
>
|
||||
<View style={styles.selectButton}>
|
||||
<View style={styles.selectBtnPlus}>
|
||||
<Icon
|
||||
color={EStyleSheet.value('$primaryBackgroundColor')}
|
||||
iconType="FontAwesome5"
|
||||
name="plus-circle"
|
||||
size={12}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Icon
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name={iconName}
|
||||
size={24}
|
||||
/>
|
||||
|
||||
<Text style={styles.selectButtonLabel}>{text}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderHeaderContent = () => (
|
||||
<View style={{ ...styles.buttonsContainer, paddingVertical: isExpandedMode ? 8 : 0 }}>
|
||||
<View style={styles.selectButtonsContainer}>
|
||||
{_renderSelectButton('image', 'Gallery', handleOpenGallery)}
|
||||
{_renderSelectButton('camera', 'Camera', handleOpenCamera)}
|
||||
</View>
|
||||
<View style={styles.pillBtnContainer}>
|
||||
<IconButton
|
||||
style={styles.uploadsActionBtn}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="plus"
|
||||
size={28}
|
||||
onPress={() => {
|
||||
handleOpenGallery(true);
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
style={{
|
||||
...styles.uploadsActionBtn,
|
||||
backgroundColor: isDeleteMode ? EStyleSheet.value('$iconColor') : 'transparent',
|
||||
}}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="minus"
|
||||
size={28}
|
||||
onPress={() => {
|
||||
setIsDeleteMode(!isDeleteMode);
|
||||
setDeleteIds([]);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{isAddingToUploads && (
|
||||
<View style={styles.pillBtnContainer}>
|
||||
<ActivityIndicator color={EStyleSheet.value('$primaryBlack')} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{isExpandedMode && _renderExpansionButton()}
|
||||
{isExpandedMode && _renderDeleteButton()}
|
||||
</View>
|
||||
);
|
||||
|
||||
//render empty list placeholder
|
||||
const _renderEmptyContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Text style={styles.emptyText}>
|
||||
{intl.formatMessage({ id: 'uploads_modal.label_no_images' })}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderExpansionButton = () => (
|
||||
<IconButton
|
||||
style={styles.pillBtnContainer}
|
||||
const _renderMinus = () =>
|
||||
isDeleteMode && (
|
||||
<AnimatedView animation="zoomIn" duration={300} style={styles.minusContainer}>
|
||||
<Icon
|
||||
color={EStyleSheet.value('$pureWhite')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name={isExpandedMode ? 'arrow-collapse-vertical' : 'arrow-expand-vertical'}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
size={32}
|
||||
onPress={() => {
|
||||
Animated.timing(animatedHeightRef.current, {
|
||||
toValue: isExpandedMode ? COMPACT_HEIGHT : EXPANDED_HEIGHT,
|
||||
duration: 300,
|
||||
easing: Easing.inOut(Easing.cubic),
|
||||
}).start(() => {
|
||||
setIsExpandedMode(!isExpandedMode);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
name="minus"
|
||||
size={20}
|
||||
/>
|
||||
</AnimatedView>
|
||||
);
|
||||
|
||||
const _renderDeleteButton = () => {
|
||||
if (deleteIds.length > 0) {
|
||||
return isExpandedMode ? (
|
||||
<IconButton
|
||||
style={{
|
||||
...styles.pillBtnContainer,
|
||||
backgroundColor: EStyleSheet.value('$primaryRed'),
|
||||
}}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="delete-outline"
|
||||
color={EStyleSheet.value(deleteIds.length > 0 ? '$primaryBlack' : '$primaryBlack')}
|
||||
size={32}
|
||||
onPress={_onDeletePress}
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
) : (
|
||||
<AnimatedView
|
||||
animation={deleteIds.length > 0 ? 'slideInRight' : 'slideOutRight'}
|
||||
duration={300}
|
||||
style={styles.deleteButtonContainer}
|
||||
>
|
||||
<IconButton
|
||||
style={styles.deleteButton}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="delete-outline"
|
||||
disabled={isDeleting}
|
||||
size={28}
|
||||
onPress={_onDeletePress}
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const _renderCounter = () =>
|
||||
isInsertedTimes > 0 &&
|
||||
!isDeleteMode && (
|
||||
<AnimatedView animation="zoomIn" duration={300} style={styles.counterContainer}>
|
||||
<Text style={styles.counterText}>{isInsertedTimes}</Text>
|
||||
</AnimatedView>
|
||||
);
|
||||
|
||||
return (
|
||||
<Animated.View style={{ ...styles.container, height: animatedHeightRef.current }}>
|
||||
<FlatList
|
||||
key={isExpandedMode ? 'vertical_grid' : 'horizontal_list'}
|
||||
data={mediaUploads.slice(0, !isExpandedMode ? MAX_HORIZONTAL_THUMBS : undefined)}
|
||||
keyExtractor={(item) => `item_${item.url}`}
|
||||
renderItem={_renderItem}
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={
|
||||
isExpandedMode ? styles.gridContentContainer : styles.listContentContainer
|
||||
}
|
||||
ListHeaderComponent={_renderHeaderContent}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
ListFooterComponent={!isExpandedMode && mediaUploads.length > 0 && _renderExpansionButton}
|
||||
extraData={deleteIds}
|
||||
horizontal={!isExpandedMode}
|
||||
numColumns={isExpandedMode ? 2 : 1}
|
||||
keyboardShouldPersistTaps="always"
|
||||
/>
|
||||
|
||||
{!isExpandedMode && _renderDeleteButton()}
|
||||
</Animated.View>
|
||||
<TouchableOpacity onPress={_onPress} disabled={isDeleting}>
|
||||
<View style={transformStyle}>
|
||||
<FastImage
|
||||
source={{ uri: thumbUrl }}
|
||||
style={isExpandedMode ? styles.gridMediaItem : styles.mediaItem}
|
||||
/>
|
||||
{_renderCounter()}
|
||||
{_renderMinus()}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderSelectButton = (iconName: string, text: string, onPress: () => void) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
onPress && onPress();
|
||||
}}
|
||||
>
|
||||
<View style={styles.selectButton}>
|
||||
<View style={styles.selectBtnPlus}>
|
||||
<Icon
|
||||
color={EStyleSheet.value('$primaryBackgroundColor')}
|
||||
iconType="FontAwesome5"
|
||||
name="plus-circle"
|
||||
size={12}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Icon
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name={iconName}
|
||||
size={24}
|
||||
/>
|
||||
|
||||
<Text style={styles.selectButtonLabel}>{text}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderHeaderContent = () => (
|
||||
<View style={{ ...styles.buttonsContainer, paddingVertical: isExpandedMode ? 8 : 0 }}>
|
||||
<View style={styles.selectButtonsContainer}>
|
||||
{_renderSelectButton('image', 'Gallery', handleOpenGallery)}
|
||||
{_renderSelectButton('camera', 'Camera', handleOpenCamera)}
|
||||
</View>
|
||||
<View style={styles.pillBtnContainer}>
|
||||
<IconButton
|
||||
style={styles.uploadsActionBtn}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="plus"
|
||||
size={28}
|
||||
onPress={() => {
|
||||
handleOpenGallery(true);
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
style={{
|
||||
...styles.uploadsActionBtn,
|
||||
backgroundColor: isDeleteMode ? EStyleSheet.value('$iconColor') : 'transparent',
|
||||
}}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="minus"
|
||||
size={28}
|
||||
onPress={() => {
|
||||
setIsDeleteMode(!isDeleteMode);
|
||||
setDeleteIds([]);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{isAddingToUploads && (
|
||||
<View style={styles.pillBtnContainer}>
|
||||
<ActivityIndicator color={EStyleSheet.value('$primaryBlack')} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{isExpandedMode && _renderExpansionButton()}
|
||||
{isExpandedMode && _renderDeleteButton()}
|
||||
</View>
|
||||
);
|
||||
|
||||
//render empty list placeholder
|
||||
const _renderEmptyContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Text style={styles.emptyText}>
|
||||
{intl.formatMessage({ id: 'uploads_modal.label_no_images' })}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderExpansionButton = () => (
|
||||
<IconButton
|
||||
style={styles.pillBtnContainer}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name={isExpandedMode ? 'arrow-collapse-vertical' : 'arrow-expand-vertical'}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
size={32}
|
||||
onPress={() => {
|
||||
Animated.timing(animatedHeightRef.current, {
|
||||
toValue: isExpandedMode ? COMPACT_HEIGHT : EXPANDED_HEIGHT,
|
||||
duration: 300,
|
||||
easing: Easing.inOut(Easing.cubic),
|
||||
}).start(() => {
|
||||
setIsExpandedMode(!isExpandedMode);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const _renderDeleteButton = () => {
|
||||
if (deleteIds.length > 0) {
|
||||
return isExpandedMode ? (
|
||||
<IconButton
|
||||
style={{
|
||||
...styles.pillBtnContainer,
|
||||
backgroundColor: EStyleSheet.value('$primaryRed'),
|
||||
}}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="delete-outline"
|
||||
color={EStyleSheet.value(deleteIds.length > 0 ? '$primaryBlack' : '$primaryBlack')}
|
||||
size={32}
|
||||
onPress={_onDeletePress}
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
) : (
|
||||
<AnimatedView
|
||||
animation={deleteIds.length > 0 ? 'slideInRight' : 'slideOutRight'}
|
||||
duration={300}
|
||||
style={styles.deleteButtonContainer}
|
||||
>
|
||||
<IconButton
|
||||
style={styles.deleteButton}
|
||||
color={EStyleSheet.value('$primaryBlack')}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="delete-outline"
|
||||
disabled={isDeleting}
|
||||
size={28}
|
||||
onPress={_onDeletePress}
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Animated.View style={{ ...styles.container, height: animatedHeightRef.current }}>
|
||||
<FlatList
|
||||
key={isExpandedMode ? 'vertical_grid' : 'horizontal_list'}
|
||||
data={mediaUploads.slice(0, !isExpandedMode ? MAX_HORIZONTAL_THUMBS : undefined)}
|
||||
keyExtractor={(item) => `item_${item.url}`}
|
||||
renderItem={_renderItem}
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={
|
||||
isExpandedMode ? styles.gridContentContainer : styles.listContentContainer
|
||||
}
|
||||
ListHeaderComponent={_renderHeaderContent}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
ListFooterComponent={!isExpandedMode && mediaUploads.length > 0 && _renderExpansionButton}
|
||||
extraData={deleteIds}
|
||||
horizontal={!isExpandedMode}
|
||||
numColumns={isExpandedMode ? 2 : 1}
|
||||
keyboardShouldPersistTaps="always"
|
||||
/>
|
||||
|
||||
{!isExpandedMode && _renderDeleteButton()}
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadsGalleryContent;
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -12,339 +12,341 @@ import showLoginAlert from '../../../utils/showLoginAlert';
|
||||
import { useMediaQuery, useMediaUploadMutation } from '../../../providers/queries';
|
||||
|
||||
export interface UploadsGalleryModalRef {
|
||||
showModal: () => void;
|
||||
showModal: () => void;
|
||||
}
|
||||
|
||||
export enum MediaInsertStatus {
|
||||
UPLOADING = 'UPLOADING',
|
||||
READY = 'READY',
|
||||
FAILED = 'FAILED',
|
||||
UPLOADING = 'UPLOADING',
|
||||
READY = 'READY',
|
||||
FAILED = 'FAILED',
|
||||
}
|
||||
|
||||
export interface MediaInsertData {
|
||||
url: string;
|
||||
filename?: string;
|
||||
text: string;
|
||||
status: MediaInsertStatus;
|
||||
url: string;
|
||||
filename?: string;
|
||||
text: string;
|
||||
status: MediaInsertStatus;
|
||||
}
|
||||
|
||||
interface UploadsGalleryModalProps {
|
||||
insertedMediaUrls: string[];
|
||||
paramFiles: any[];
|
||||
username: string;
|
||||
isEditing: boolean;
|
||||
isPreviewActive: boolean;
|
||||
hideToolbarExtension: () => void;
|
||||
handleMediaInsert: (data: Array<MediaInsertData>) => void;
|
||||
setIsUploading: (status: boolean) => void;
|
||||
insertedMediaUrls: string[];
|
||||
paramFiles: any[];
|
||||
username: string;
|
||||
isEditing: boolean;
|
||||
isPreviewActive: boolean;
|
||||
hideToolbarExtension: () => void;
|
||||
handleMediaInsert: (data: Array<MediaInsertData>) => void;
|
||||
setIsUploading: (status: boolean) => void;
|
||||
}
|
||||
|
||||
export const UploadsGalleryModal = forwardRef(
|
||||
(
|
||||
{
|
||||
insertedMediaUrls,
|
||||
paramFiles,
|
||||
username,
|
||||
isEditing,
|
||||
isPreviewActive,
|
||||
hideToolbarExtension,
|
||||
handleMediaInsert,
|
||||
setIsUploading,
|
||||
}: UploadsGalleryModalProps,
|
||||
ref,
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
(
|
||||
{
|
||||
insertedMediaUrls,
|
||||
paramFiles,
|
||||
username,
|
||||
isEditing,
|
||||
isPreviewActive,
|
||||
hideToolbarExtension,
|
||||
handleMediaInsert,
|
||||
setIsUploading,
|
||||
}: UploadsGalleryModalProps,
|
||||
ref,
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const mediaQuery = useMediaQuery();
|
||||
const mediaUploadMutation = useMediaUploadMutation();
|
||||
const mediaQuery = useMediaQuery();
|
||||
const mediaUploadMutation = useMediaUploadMutation();
|
||||
|
||||
const pendingInserts = useRef<MediaInsertData[]>([]);
|
||||
const pendingInserts = useRef<MediaInsertData[]>([]);
|
||||
|
||||
const [mediaUploads, setMediaUploads] = useState([]);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [isAddingToUploads, setIsAddingToUploads] = useState(false);
|
||||
const [mediaUploads, setMediaUploads] = useState([]);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [isAddingToUploads, setIsAddingToUploads] = useState(false);
|
||||
|
||||
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
||||
const pinCode = useAppSelector((state) => state.application.pin);
|
||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
||||
const pinCode = useAppSelector((state) => state.application.pin);
|
||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
toggleModal: (value: boolean) => {
|
||||
if (!isLoggedIn) {
|
||||
showLoginAlert({ intl });
|
||||
return;
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
toggleModal: (value: boolean) => {
|
||||
if (!isLoggedIn) {
|
||||
showLoginAlert({ intl });
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === showModal) {
|
||||
return;
|
||||
}
|
||||
if (value === showModal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
_getMediaUploads();
|
||||
}
|
||||
setShowModal(value);
|
||||
if (value) {
|
||||
_getMediaUploads();
|
||||
}
|
||||
setShowModal(value);
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (paramFiles) {
|
||||
console.log('files : ', paramFiles);
|
||||
|
||||
//delay is a workaround to let editor ready before initiating uploads on mount
|
||||
delay(500).then(() => {
|
||||
const _mediaItems = paramFiles.map((el) => {
|
||||
if (el.filePath && el.fileName) {
|
||||
const _media = {
|
||||
path: el.filePath,
|
||||
mime: el.mimeType,
|
||||
filename: el.fileName,
|
||||
};
|
||||
|
||||
return _media;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
_handleMediaOnSelected(_mediaItems, true);
|
||||
});
|
||||
}
|
||||
}, [paramFiles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing && pendingInserts.current.length) {
|
||||
handleMediaInsert(pendingInserts.current);
|
||||
pendingInserts.current = [];
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
const _handleOpenImagePicker = (addToUploads?: boolean) => {
|
||||
ImagePicker.openPicker({
|
||||
includeBase64: true,
|
||||
multiple: true,
|
||||
mediaType: 'photo',
|
||||
smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'],
|
||||
})
|
||||
.then((images) => {
|
||||
if (images && !Array.isArray(images)) {
|
||||
images = [images];
|
||||
}
|
||||
_handleMediaOnSelected(images, !addToUploads);
|
||||
})
|
||||
.catch((e) => {
|
||||
_handleMediaOnSelectFailure(e);
|
||||
});
|
||||
};
|
||||
|
||||
const _handleOpenCamera = () => {
|
||||
ImagePicker.openCamera({
|
||||
includeBase64: true,
|
||||
mediaType: 'photo',
|
||||
})
|
||||
.then((image) => {
|
||||
_handleMediaOnSelected([image], true);
|
||||
})
|
||||
.catch((e) => {
|
||||
_handleMediaOnSelectFailure(e);
|
||||
});
|
||||
};
|
||||
|
||||
const _handleMediaOnSelected = async (media: Image[], shouldInsert: boolean) => {
|
||||
try {
|
||||
if (!media || media.length == 0) {
|
||||
throw new Error('New media items returned');
|
||||
}
|
||||
|
||||
if (shouldInsert) {
|
||||
setShowModal(false);
|
||||
hideToolbarExtension();
|
||||
media.forEach((element, index) => {
|
||||
if (element) {
|
||||
media[index].filename =
|
||||
element.filename ||
|
||||
extractFilenameFromPath({ path: element.path, mimeType: element.mime });
|
||||
handleMediaInsert([
|
||||
{
|
||||
filename: element.filename,
|
||||
url: '',
|
||||
text: '',
|
||||
status: MediaInsertStatus.UPLOADING,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (let index = 0; index < media.length; index++) {
|
||||
const element = media[index];
|
||||
if (element) {
|
||||
await _uploadImage(element, { shouldInsert });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Failed to upload image', error);
|
||||
|
||||
bugsnapInstance.notify(error);
|
||||
}
|
||||
};
|
||||
|
||||
const _uploadImage = async (media, { shouldInsert } = { shouldInsert: false }) => {
|
||||
if (!isLoggedIn) return;
|
||||
try {
|
||||
if (setIsUploading) {
|
||||
setIsUploading(true);
|
||||
}
|
||||
if (!shouldInsert) {
|
||||
setIsAddingToUploads(true);
|
||||
}
|
||||
|
||||
await mediaUploadMutation.mutateAsync(
|
||||
{
|
||||
media,
|
||||
addToUploads: !shouldInsert,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
console.log('upload successfully', data, media, shouldInsert);
|
||||
if (data && data.url && shouldInsert) {
|
||||
_handleMediaInsertion({
|
||||
filename: media.filename,
|
||||
url: data.url,
|
||||
text: '',
|
||||
status: MediaInsertStatus.READY,
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (paramFiles) {
|
||||
console.log('files : ', paramFiles);
|
||||
|
||||
//delay is a workaround to let editor ready before initiating uploads on mount
|
||||
delay(500).then(() => {
|
||||
const _mediaItems = paramFiles.map((el) => {
|
||||
if (el.filePath && el.fileName) {
|
||||
const _media = {
|
||||
path: el.filePath,
|
||||
mime: el.mimeType,
|
||||
filename: el.fileName,
|
||||
};
|
||||
|
||||
return _media;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
_handleMediaOnSelected(_mediaItems, true);
|
||||
});
|
||||
}
|
||||
}, [paramFiles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing && pendingInserts.current.length) {
|
||||
handleMediaInsert(pendingInserts.current);
|
||||
pendingInserts.current = [];
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
const _handleOpenImagePicker = (addToUploads?: boolean) => {
|
||||
ImagePicker.openPicker({
|
||||
includeBase64: true,
|
||||
multiple: true,
|
||||
mediaType: 'photo',
|
||||
smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'],
|
||||
})
|
||||
.then((images) => {
|
||||
if (images && !Array.isArray(images)) {
|
||||
images = [images];
|
||||
}
|
||||
_handleMediaOnSelected(images, !addToUploads);
|
||||
})
|
||||
.catch((e) => {
|
||||
_handleMediaOnSelectFailure(e);
|
||||
});
|
||||
};
|
||||
|
||||
const _handleOpenCamera = () => {
|
||||
ImagePicker.openCamera({
|
||||
includeBase64: true,
|
||||
mediaType: 'photo',
|
||||
})
|
||||
.then((image) => {
|
||||
_handleMediaOnSelected([image], true);
|
||||
})
|
||||
.catch((e) => {
|
||||
_handleMediaOnSelectFailure(e);
|
||||
});
|
||||
};
|
||||
|
||||
const _handleMediaOnSelected = async (media: Image[], shouldInsert: boolean) => {
|
||||
try {
|
||||
if (!media || media.length == 0) {
|
||||
throw new Error('New media items returned');
|
||||
}
|
||||
|
||||
if (shouldInsert) {
|
||||
setShowModal(false);
|
||||
hideToolbarExtension();
|
||||
media.forEach((element, index) => {
|
||||
if (element) {
|
||||
media[index].filename =
|
||||
element.filename ||
|
||||
extractFilenameFromPath({ path: element.path, mimeType: element.mime });
|
||||
handleMediaInsert([
|
||||
{
|
||||
filename: element.filename,
|
||||
url: '',
|
||||
text: '',
|
||||
status: MediaInsertStatus.UPLOADING,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (let index = 0; index < media.length; index++) {
|
||||
const element = media[index];
|
||||
if (element) {
|
||||
await _uploadImage(element, { shouldInsert });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Failed to upload image', error);
|
||||
|
||||
bugsnapInstance.notify(error);
|
||||
}
|
||||
};
|
||||
|
||||
const _uploadImage = async (media, { shouldInsert } = { shouldInsert: false }) => {
|
||||
if (!isLoggedIn) return;
|
||||
try {
|
||||
if (setIsUploading) {
|
||||
setIsUploading(true);
|
||||
}
|
||||
if (!shouldInsert) {
|
||||
setIsAddingToUploads(true);
|
||||
}
|
||||
|
||||
await mediaUploadMutation.mutateAsync({
|
||||
media,
|
||||
addToUploads: !shouldInsert
|
||||
}, {
|
||||
onSuccess: (data) => {
|
||||
console.log('upload successfully', data, media, shouldInsert)
|
||||
if (data && data.url && shouldInsert) {
|
||||
_handleMediaInsertion({
|
||||
filename: media.filename,
|
||||
url: data.url,
|
||||
text: '',
|
||||
status: MediaInsertStatus.READY,
|
||||
});
|
||||
}
|
||||
},
|
||||
onSettled: () => {
|
||||
if (setIsUploading) {
|
||||
setIsUploading(false);
|
||||
}
|
||||
setIsAddingToUploads(false);
|
||||
},
|
||||
onError: (err) => { throw err },
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.log('error while uploading image : ', error);
|
||||
|
||||
if (error.toString().includes('code 413')) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.payloadTooLarge',
|
||||
}),
|
||||
);
|
||||
} else if (error.toString().includes('code 429')) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.quotaExceeded',
|
||||
}),
|
||||
);
|
||||
} else if (error.toString().includes('code 400')) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.invalidImage',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
error.message || error.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldInsert) {
|
||||
_handleMediaInsertion({
|
||||
filename: media.filename,
|
||||
url: '',
|
||||
text: '',
|
||||
status: MediaInsertStatus.FAILED,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _handleMediaOnSelectFailure = (error) => {
|
||||
if (error.code === 'E_PERMISSION_MISSING') {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.permission_denied',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.permission_text',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
error.message || JSON.stringify(error),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const _handleMediaInsertion = (data: MediaInsertData) => {
|
||||
if (isEditing) {
|
||||
pendingInserts.current.push(data);
|
||||
} else if (handleMediaInsert) {
|
||||
handleMediaInsert([data]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//fetch images from server
|
||||
const _getMediaUploads = async () => {
|
||||
try {
|
||||
if (username) {
|
||||
console.log('getting images for: ' + username);
|
||||
const images = await getImages();
|
||||
console.log('images received', images);
|
||||
setMediaUploads(images || []);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to get images');
|
||||
}
|
||||
setIsAddingToUploads(false);
|
||||
};
|
||||
|
||||
//inserts media items in post body
|
||||
const _insertMedia = async (map: Map<number, boolean>) => {
|
||||
const data: MediaInsertData[] = [];
|
||||
for (const index of map.keys()) {
|
||||
console.log(index);
|
||||
const item = mediaUploads[index];
|
||||
data.push({
|
||||
url: item.url,
|
||||
text: '',
|
||||
status: MediaInsertStatus.READY,
|
||||
});
|
||||
}
|
||||
handleMediaInsert(data);
|
||||
};
|
||||
|
||||
return (
|
||||
!isPreviewActive &&
|
||||
showModal && (
|
||||
<UploadsGalleryContent
|
||||
insertedMediaUrls={insertedMediaUrls}
|
||||
mediaUploads={mediaQuery.data.slice()}
|
||||
isAddingToUploads={isAddingToUploads}
|
||||
getMediaUploads={_getMediaUploads}
|
||||
insertMedia={_insertMedia}
|
||||
handleOpenCamera={_handleOpenCamera}
|
||||
handleOpenGallery={_handleOpenImagePicker}
|
||||
/>
|
||||
)
|
||||
onSettled: () => {
|
||||
if (setIsUploading) {
|
||||
setIsUploading(false);
|
||||
}
|
||||
setIsAddingToUploads(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
throw err;
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
} catch (error) {
|
||||
console.log('error while uploading image : ', error);
|
||||
|
||||
if (error.toString().includes('code 413')) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.payloadTooLarge',
|
||||
}),
|
||||
);
|
||||
} else if (error.toString().includes('code 429')) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.quotaExceeded',
|
||||
}),
|
||||
);
|
||||
} else if (error.toString().includes('code 400')) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.invalidImage',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
error.message || error.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldInsert) {
|
||||
_handleMediaInsertion({
|
||||
filename: media.filename,
|
||||
url: '',
|
||||
text: '',
|
||||
status: MediaInsertStatus.FAILED,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _handleMediaOnSelectFailure = (error) => {
|
||||
if (error.code === 'E_PERMISSION_MISSING') {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.permission_denied',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'alert.permission_text',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.fail',
|
||||
}),
|
||||
error.message || JSON.stringify(error),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const _handleMediaInsertion = (data: MediaInsertData) => {
|
||||
if (isEditing) {
|
||||
pendingInserts.current.push(data);
|
||||
} else if (handleMediaInsert) {
|
||||
handleMediaInsert([data]);
|
||||
}
|
||||
};
|
||||
|
||||
//fetch images from server
|
||||
const _getMediaUploads = async () => {
|
||||
try {
|
||||
if (username) {
|
||||
console.log('getting images for: ' + username);
|
||||
const images = await getImages();
|
||||
console.log('images received', images);
|
||||
setMediaUploads(images || []);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to get images');
|
||||
}
|
||||
setIsAddingToUploads(false);
|
||||
};
|
||||
|
||||
//inserts media items in post body
|
||||
const _insertMedia = async (map: Map<number, boolean>) => {
|
||||
const data: MediaInsertData[] = [];
|
||||
for (const index of map.keys()) {
|
||||
console.log(index);
|
||||
const item = mediaUploads[index];
|
||||
data.push({
|
||||
url: item.url,
|
||||
text: '',
|
||||
status: MediaInsertStatus.READY,
|
||||
});
|
||||
}
|
||||
handleMediaInsert(data);
|
||||
};
|
||||
|
||||
return (
|
||||
!isPreviewActive &&
|
||||
showModal && (
|
||||
<UploadsGalleryContent
|
||||
insertedMediaUrls={insertedMediaUrls}
|
||||
mediaUploads={mediaQuery.data.slice()}
|
||||
isAddingToUploads={isAddingToUploads}
|
||||
getMediaUploads={_getMediaUploads}
|
||||
insertMedia={_insertMedia}
|
||||
handleOpenCamera={_handleOpenCamera}
|
||||
handleOpenGallery={_handleOpenImagePicker}
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -8,5 +8,4 @@ const coingeckoApi = axios.create({
|
||||
baseURL: `${BASE_URL}/${PATH_API}/${API_VERSION}`,
|
||||
});
|
||||
|
||||
|
||||
export default coingeckoApi;
|
||||
export default coingeckoApi;
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 },
|
||||
];
|
||||
|
@ -4,5 +4,5 @@ const DELETE_ACCOUNT = 'delete_account';
|
||||
|
||||
export default {
|
||||
SHOW_HIDE_IMGS,
|
||||
DELETE_ACCOUNT
|
||||
DELETE_ACCOUNT,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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 RNIap.flushFailedPurchasesCachedAsPendingAndroid();
|
||||
}
|
||||
|
||||
await this._consumeAvailablePurchases()
|
||||
await this._consumeAvailablePurchases();
|
||||
this._getItems();
|
||||
this._purchaseUpdatedListener();
|
||||
await this._handleQrPurchase();
|
||||
@ -70,31 +67,28 @@ class InAppPurchaseContainer extends Component {
|
||||
intl.formatMessage({
|
||||
id: 'alert.connection_issues',
|
||||
}),
|
||||
err.message
|
||||
err.message,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//this snippet consumes all previously bought purchases
|
||||
//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
|
||||
for (let i = 0; i < purchases.length; i++) {
|
||||
//consume item using finishTransactionx
|
||||
//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
|
||||
|
||||
_purchaseUpdatedListener = () => {
|
||||
@ -157,7 +151,7 @@ class InAppPurchaseContainer extends Component {
|
||||
intl.formatMessage({
|
||||
id: 'alert.warning',
|
||||
}),
|
||||
error.message
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
this.setState({ isProcessing: false });
|
||||
@ -169,7 +163,7 @@ class InAppPurchaseContainer extends Component {
|
||||
if (_title !== 'FREE POINTS') {
|
||||
_title = _title.replace(/[^0-9]+/g, '') + ' POINTS';
|
||||
}
|
||||
|
||||
|
||||
return _title;
|
||||
};
|
||||
|
||||
@ -183,11 +177,11 @@ class InAppPurchaseContainer extends Component {
|
||||
} catch (err) {
|
||||
bugsnagInstance.notify(err);
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'alert.connection_issues',
|
||||
}),
|
||||
error.message
|
||||
);
|
||||
intl.formatMessage({
|
||||
id: 'alert.connection_issues',
|
||||
}),
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
|
||||
this.setState({ isLoading: false });
|
||||
@ -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}`
|
||||
});
|
||||
username,
|
||||
price: `${product.currency} ${product.price}`,
|
||||
},
|
||||
);
|
||||
|
||||
let title = intl.formatMessage({
|
||||
id: 'boost.confirm_purchase'
|
||||
}, {
|
||||
username,
|
||||
});
|
||||
let title = intl.formatMessage(
|
||||
{
|
||||
id: 'boost.confirm_purchase',
|
||||
},
|
||||
{
|
||||
username,
|
||||
},
|
||||
);
|
||||
|
||||
dispatch(
|
||||
showActionModal({
|
||||
@ -251,13 +251,12 @@ 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;
|
||||
const { productList, isLoading, isProcessing } = this.state;
|
||||
@ -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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
);
|
||||
};
|
||||
|
@ -1,75 +1,65 @@
|
||||
|
||||
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"
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarShowLabel: false,
|
||||
tabBarActiveTintColor: '#357ce6',
|
||||
tabBarInactiveTintColor: '#c1c5c7',
|
||||
}}
|
||||
>
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.FEED}
|
||||
component={Feed}
|
||||
initialParams={{
|
||||
iconName: 'view-day', //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.NOTIFICATION}
|
||||
component={Notification}
|
||||
initialParams={{
|
||||
iconName: 'notifications', //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
tabBar={(props) => <BottomTabBar {...props} /> }
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.POST_BUTTON}
|
||||
component={EmptyScreen}
|
||||
initialParams={{
|
||||
iconName: 'pencil', //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
|
||||
backBehavior='initialRoute'
|
||||
screenOptions={{
|
||||
headerShown:false,
|
||||
tabBarShowLabel: false,
|
||||
tabBarActiveTintColor: '#357ce6',
|
||||
tabBarInactiveTintColor: '#c1c5c7',}}
|
||||
>
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.FEED}
|
||||
component={Feed}
|
||||
initialParams={{
|
||||
iconName: 'view-day' //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.WALLET}
|
||||
component={Wallet}
|
||||
initialParams={{
|
||||
iconName: 'account-balance-wallet', //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.NOTIFICATION}
|
||||
component={Notification}
|
||||
initialParams={{
|
||||
iconName: 'notifications' //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.PROFILE}
|
||||
component={Profile}
|
||||
initialParams={{
|
||||
iconName: 'person', //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.POST_BUTTON}
|
||||
component={EmptyScreen}
|
||||
initialParams={{
|
||||
iconName: 'pencil' //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.WALLET}
|
||||
component={Wallet}
|
||||
initialParams={{
|
||||
iconName: 'account-balance-wallet' //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tab.Screen
|
||||
name={ROUTES.TABBAR.PROFILE}
|
||||
component={Profile}
|
||||
initialParams={{
|
||||
iconName: 'person' //read in bottomTabBarView
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const EmptyScreen = () => null
|
||||
const EmptyScreen = () => null;
|
||||
|
@ -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.Screen name={ROUTES.SCREENS.FEED} component={BottomTabNavigator} />
|
||||
</Drawer.Navigator>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
screenOptions={{ headerShown: false }}
|
||||
drawerContent={(props) => <SideMenu {...props} />}
|
||||
>
|
||||
<Drawer.Screen name={ROUTES.SCREENS.FEED} component={BottomTabNavigator} />
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
};
|
||||
|
@ -1 +1 @@
|
||||
export * from './appNavigator';
|
||||
export * from './appNavigator';
|
||||
|
@ -1,94 +1,95 @@
|
||||
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,
|
||||
Boost,
|
||||
Drafts,
|
||||
Editor,
|
||||
Follows,
|
||||
Login,
|
||||
Post,
|
||||
Profile,
|
||||
ProfileEdit,
|
||||
Reblogs,
|
||||
Redeem,
|
||||
Register,
|
||||
SearchResult,
|
||||
Settings,
|
||||
SpinGame,
|
||||
Transfer,
|
||||
Voters,
|
||||
AccountBoost,
|
||||
TagResult,
|
||||
Community,
|
||||
Communities,
|
||||
WebBrowser,
|
||||
ReferScreen,
|
||||
CoinDetails,
|
||||
EditHistoryScreen,
|
||||
WelcomeScreen,
|
||||
PinCode,
|
||||
Bookmarks,
|
||||
Boost,
|
||||
Drafts,
|
||||
Editor,
|
||||
Follows,
|
||||
Login,
|
||||
Post,
|
||||
Profile,
|
||||
ProfileEdit,
|
||||
Reblogs,
|
||||
Redeem,
|
||||
Register,
|
||||
SearchResult,
|
||||
Settings,
|
||||
SpinGame,
|
||||
Transfer,
|
||||
Voters,
|
||||
AccountBoost,
|
||||
TagResult,
|
||||
Community,
|
||||
Communities,
|
||||
WebBrowser,
|
||||
ReferScreen,
|
||||
CoinDetails,
|
||||
EditHistoryScreen,
|
||||
WelcomeScreen,
|
||||
PinCode,
|
||||
} 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.Screen name={ROUTES.DRAWER.MAIN} component={DrawerNavigator} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.PROFILE} component={Profile} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.PROFILE_EDIT} component={ProfileEdit} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.SETTINGS} component={Settings} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.DRAFTS} component={Drafts} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.BOOKMARKS} component={Bookmarks} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.SEARCH_RESULT} component={SearchResult} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.TAG_RESULT} component={TagResult} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.BOOST} component={Boost} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.REDEEM} component={Redeem} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.SPIN_GAME} component={SpinGame} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.ACCOUNT_BOOST} component={AccountBoost} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.COMMUNITY} component={Community} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.COMMUNITIES} component={Communities} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.WEB_BROWSER} component={WebBrowser} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.REFER} component={ReferScreen} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.COIN_DETAILS} component={CoinDetails} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.EDIT_HISTORY} component={EditHistoryScreen} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.POST} component={Post} />
|
||||
<MainStack.Group screenOptions={{ animation: 'slide_from_bottom' }}>
|
||||
<MainStack.Screen name={ROUTES.SCREENS.REBLOGS} component={Reblogs} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.VOTERS} component={Voters} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.FOLLOWS} component={Follows} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.TRANSFER} component={Transfer} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.EDITOR} component={Editor} />
|
||||
</MainStack.Group>
|
||||
</MainStack.Navigator>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<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} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.SETTINGS} component={Settings} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.DRAFTS} component={Drafts} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.BOOKMARKS} component={Bookmarks} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.SEARCH_RESULT} component={SearchResult} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.TAG_RESULT} component={TagResult} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.BOOST} component={Boost} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.REDEEM} component={Redeem} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.SPIN_GAME} component={SpinGame} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.ACCOUNT_BOOST} component={AccountBoost} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.COMMUNITY} component={Community} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.COMMUNITIES} component={Communities} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.WEB_BROWSER} component={WebBrowser} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.REFER} component={ReferScreen} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.COIN_DETAILS} component={CoinDetails} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.EDIT_HISTORY} component={EditHistoryScreen} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.POST} component={Post} />
|
||||
<MainStack.Group screenOptions={{ animation: 'slide_from_bottom' }}>
|
||||
<MainStack.Screen name={ROUTES.SCREENS.REBLOGS} component={Reblogs} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.VOTERS} component={Voters} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.FOLLOWS} component={Follows} />
|
||||
<MainStack.Screen name={ROUTES.SCREENS.TRANSFER} component={Transfer} />
|
||||
<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' }}
|
||||
|
||||
return (
|
||||
<RootStack.Navigator
|
||||
<RootStack.Screen name={ROUTES.STACK.MAIN} component={MainStackNavigator} />
|
||||
|
||||
initialRouteName={initRoute}
|
||||
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.Navigator>
|
||||
)
|
||||
}
|
||||
<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.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
@ -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{
|
||||
const params = {
|
||||
vs_currency,
|
||||
days,
|
||||
interval
|
||||
}
|
||||
coingeckoId: string,
|
||||
vs_currency: string,
|
||||
days: number,
|
||||
interval: 'daily' | 'hourly' = 'daily',
|
||||
): Promise<MarketData> => {
|
||||
try {
|
||||
const params = {
|
||||
vs_currency,
|
||||
days,
|
||||
interval,
|
||||
};
|
||||
|
||||
const res = await coingeckoApi.get(`/${PATH_COINS}/${coingeckoId}/${PATH_MARKET_CHART}`, {
|
||||
params
|
||||
});
|
||||
const rawData = res.data;
|
||||
if(!rawData){
|
||||
throw new Error("Tag name not available")
|
||||
}
|
||||
const res = await coingeckoApi.get(`/${PATH_COINS}/${coingeckoId}/${PATH_MARKET_CHART}`, {
|
||||
params,
|
||||
});
|
||||
const rawData = res.data;
|
||||
if (!rawData) {
|
||||
throw new Error('Tag name not available');
|
||||
}
|
||||
|
||||
const data = convertMarketData(rawData);
|
||||
const data = convertMarketData(rawData);
|
||||
|
||||
return data;
|
||||
|
||||
}catch(error){
|
||||
bugsnagInstance.notify(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
bugsnagInstance.notify(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
@ -1,23 +1,23 @@
|
||||
import { ChartItem, MarketData } from "./models"
|
||||
import { ChartItem, MarketData } from './models';
|
||||
|
||||
export const convertChartItem = (rawData:any) => {
|
||||
if(!rawData){
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
xValue:rawData[0],
|
||||
yValue:rawData[1]
|
||||
} as ChartItem;
|
||||
}
|
||||
export const convertChartItem = (rawData: any) => {
|
||||
if (!rawData) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
xValue: rawData[0],
|
||||
yValue: rawData[1],
|
||||
} as ChartItem;
|
||||
};
|
||||
|
||||
export const convertMarketData = (rawData:any) => {
|
||||
if(!rawData){
|
||||
return null;
|
||||
}
|
||||
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) : []
|
||||
} as MarketData;
|
||||
}
|
||||
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) : [],
|
||||
} as MarketData;
|
||||
};
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
@ -47,4 +53,4 @@ export const convertCommentHistory = (rawData: any) => {
|
||||
title: rawData.title || '',
|
||||
v: rawData.v || 1,
|
||||
} as CommentHistoryItem;
|
||||
};
|
||||
};
|
||||
|
@ -4,18 +4,16 @@ 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
|
||||
* @param bl block number
|
||||
* @param tx transaction id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
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) => {
|
||||
|
@ -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,
|
||||
|
@ -1,113 +1,111 @@
|
||||
|
||||
export interface Vote {
|
||||
percent: number;
|
||||
reputation: number;
|
||||
rshares: string;
|
||||
time: string;
|
||||
timestamp?: number;
|
||||
voter: string;
|
||||
weight: number;
|
||||
reward?: number;
|
||||
percent: number;
|
||||
reputation: number;
|
||||
rshares: string;
|
||||
time: string;
|
||||
timestamp?: number;
|
||||
voter: string;
|
||||
weight: number;
|
||||
reward?: number;
|
||||
}
|
||||
|
||||
export interface DynamicGlobalProperties {
|
||||
hbd_print_rate: number;
|
||||
total_vesting_fund_hive: string;
|
||||
total_vesting_shares: string;
|
||||
hbd_interest_rate: number;
|
||||
head_block_number: number;
|
||||
vesting_reward_percent: number;
|
||||
virtual_supply: string;
|
||||
hbd_print_rate: number;
|
||||
total_vesting_fund_hive: string;
|
||||
total_vesting_shares: string;
|
||||
hbd_interest_rate: number;
|
||||
head_block_number: number;
|
||||
vesting_reward_percent: number;
|
||||
virtual_supply: string;
|
||||
}
|
||||
|
||||
export interface FeedHistory {
|
||||
current_median_history: {
|
||||
base: string;
|
||||
quote: string;
|
||||
};
|
||||
current_median_history: {
|
||||
base: string;
|
||||
quote: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RewardFund {
|
||||
recent_claims: string;
|
||||
reward_balance: string;
|
||||
recent_claims: string;
|
||||
reward_balance: string;
|
||||
}
|
||||
|
||||
export interface DelegatedVestingShare {
|
||||
id: number;
|
||||
delegatee: string;
|
||||
delegator: string;
|
||||
min_delegation_time: string;
|
||||
vesting_shares: string;
|
||||
id: number;
|
||||
delegatee: string;
|
||||
delegator: string;
|
||||
min_delegation_time: string;
|
||||
vesting_shares: string;
|
||||
}
|
||||
|
||||
export interface Follow {
|
||||
follower: string;
|
||||
following: string;
|
||||
what: string[];
|
||||
follower: string;
|
||||
following: string;
|
||||
what: string[];
|
||||
}
|
||||
|
||||
export interface MarketStatistics {
|
||||
hbd_volume: string;
|
||||
highest_bid: string;
|
||||
hive_volume: string;
|
||||
latest: string;
|
||||
lowest_ask: string;
|
||||
percent_change: string;
|
||||
hbd_volume: string;
|
||||
highest_bid: string;
|
||||
hive_volume: string;
|
||||
latest: string;
|
||||
lowest_ask: string;
|
||||
percent_change: string;
|
||||
}
|
||||
|
||||
export interface OpenOrderItem {
|
||||
id: number,
|
||||
created: string,
|
||||
expiration: string,
|
||||
seller: string,
|
||||
orderid: number,
|
||||
for_sale: number,
|
||||
sell_price: {
|
||||
base: string,
|
||||
quote: string
|
||||
},
|
||||
real_price: string,
|
||||
rewarded: boolean
|
||||
id: number;
|
||||
created: string;
|
||||
expiration: string;
|
||||
seller: string;
|
||||
orderid: number;
|
||||
for_sale: number;
|
||||
sell_price: {
|
||||
base: string;
|
||||
quote: string;
|
||||
};
|
||||
real_price: string;
|
||||
rewarded: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface OrdersDataItem {
|
||||
created: string;
|
||||
hbd: number;
|
||||
hive: number;
|
||||
order_price: {
|
||||
base: string;
|
||||
quote: string;
|
||||
}
|
||||
real_price: string;
|
||||
created: string;
|
||||
hbd: number;
|
||||
hive: number;
|
||||
order_price: {
|
||||
base: string;
|
||||
quote: string;
|
||||
};
|
||||
real_price: string;
|
||||
}
|
||||
|
||||
export interface TradeDataItem {
|
||||
current_pays: string;
|
||||
date: number;
|
||||
open_pays: string;
|
||||
current_pays: string;
|
||||
date: number;
|
||||
open_pays: string;
|
||||
}
|
||||
|
||||
export interface OrdersData {
|
||||
bids: OrdersDataItem[];
|
||||
asks: OrdersDataItem[];
|
||||
trading: OrdersDataItem[];
|
||||
bids: OrdersDataItem[];
|
||||
asks: OrdersDataItem[];
|
||||
trading: OrdersDataItem[];
|
||||
}
|
||||
|
||||
export interface ConversionRequest {
|
||||
amount: string;
|
||||
conversion_date: string;
|
||||
id: number;
|
||||
owner: string;
|
||||
requestid: number;
|
||||
amount: string;
|
||||
conversion_date: string;
|
||||
id: number;
|
||||
owner: string;
|
||||
requestid: number;
|
||||
}
|
||||
|
||||
export interface SavingsWithdrawRequest {
|
||||
id: number;
|
||||
from: string;
|
||||
to: string;
|
||||
memo: string;
|
||||
request_id: number;
|
||||
amount: string;
|
||||
complete: string;
|
||||
}
|
||||
id: number;
|
||||
from: string;
|
||||
to: string;
|
||||
memo: string;
|
||||
request_id: number;
|
||||
amount: string;
|
||||
complete: string;
|
||||
}
|
||||
|
@ -4,209 +4,201 @@ import { Image } from 'react-native-image-crop-picker';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { toastNotification } from '../../redux/actions/uiAction';
|
||||
import {
|
||||
addFragment,
|
||||
addImage,
|
||||
deleteFragment,
|
||||
deleteImage,
|
||||
getFragments,
|
||||
getImages,
|
||||
updateFragment,
|
||||
uploadImage,
|
||||
addFragment,
|
||||
addImage,
|
||||
deleteFragment,
|
||||
deleteImage,
|
||||
getFragments,
|
||||
getImages,
|
||||
updateFragment,
|
||||
uploadImage,
|
||||
} from '../ecency/ecency';
|
||||
import { MediaItem, Snippet } from '../ecency/ecency.types';
|
||||
import { signImage } from '../hive/dhive';
|
||||
import QUERIES from './queryKeys';
|
||||
|
||||
interface SnippetMutationVars {
|
||||
id: string | null;
|
||||
title: string;
|
||||
body: string;
|
||||
id: string | null;
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
interface MediaUploadVars {
|
||||
media: Image;
|
||||
addToUploads: boolean;
|
||||
media: Image;
|
||||
addToUploads: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** GET QUERIES **/
|
||||
|
||||
export const useMediaQuery = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
return useQuery<MediaItem[]>([QUERIES.MEDIA.GET], getImages, {
|
||||
initialData: [],
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
return useQuery<MediaItem[]>([QUERIES.MEDIA.GET], getImages, {
|
||||
initialData: [],
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSnippetsQuery = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
return useQuery<Snippet[]>([QUERIES.SNIPPETS.GET], getFragments, {
|
||||
initialData: [],
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
return useQuery<Snippet[]>([QUERIES.SNIPPETS.GET], getFragments, {
|
||||
initialData: [],
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/** ADD UPDATE MUTATIONS **/
|
||||
|
||||
|
||||
export const useAddToUploadsMutation = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<any[], Error, string>(addImage, {
|
||||
retry: 3,
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData([QUERIES.MEDIA.GET], data);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error.toString().includes('code 409')) {
|
||||
//means image ware already preset, refresh to get updated order
|
||||
queryClient.invalidateQueries([QUERIES.MEDIA.GET]);
|
||||
} else {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const useMediaUploadMutation = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const addToUploadsMutation = useAddToUploadsMutation();
|
||||
|
||||
const currentAccount = useAppSelector(state => state.account.currentAccount);
|
||||
const pinCode = useAppSelector(state => state.application.pin);
|
||||
|
||||
return useMutation<Image, undefined, MediaUploadVars>(
|
||||
async ({ media }) => {
|
||||
console.log('uploading media', media);
|
||||
let sign = await signImage(media, currentAccount, pinCode);
|
||||
return await uploadImage(media, currentAccount.name, sign);
|
||||
},
|
||||
{
|
||||
retry: 3,
|
||||
onSuccess: (response, { addToUploads }) => {
|
||||
if (addToUploads && response && response.url) {
|
||||
console.log('adding image to gallery', response.url);
|
||||
addToUploadsMutation.mutate(response.url);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const useSnippetsMutation = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Snippet[], undefined, SnippetMutationVars>(
|
||||
async (vars) => {
|
||||
console.log('going to add/update snippet', vars);
|
||||
if (vars.id) {
|
||||
const response = await updateFragment(vars.id, vars.title, vars.body);
|
||||
return response;
|
||||
} else {
|
||||
const response = await addFragment(vars.title, vars.body);
|
||||
return response;
|
||||
}
|
||||
},
|
||||
{
|
||||
onMutate: (vars) => {
|
||||
console.log('mutate snippets for add/update', vars);
|
||||
|
||||
const _newItem = {
|
||||
id: vars.id,
|
||||
title: vars.title,
|
||||
body: vars.body,
|
||||
created: new Date().toDateString(),
|
||||
modified: new Date().toDateString(),
|
||||
} as Snippet;
|
||||
|
||||
const data = queryClient.getQueryData<Snippet[]>([QUERIES.SNIPPETS.GET]);
|
||||
|
||||
let _newData: Snippet[] = data ? [...data] : [];
|
||||
if (vars.id) {
|
||||
const snipIndex = _newData.findIndex((item) => vars.id === item.id);
|
||||
_newData[snipIndex] = _newItem;
|
||||
} else {
|
||||
_newData = [_newItem, ..._newData];
|
||||
}
|
||||
|
||||
queryClient.setQueryData([QUERIES.SNIPPETS.GET], _newData);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
console.log('added/updated snippet', data);
|
||||
queryClient.invalidateQueries([QUERIES.SNIPPETS.GET]);
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'snippets.message_failed' })));
|
||||
},
|
||||
},
|
||||
);
|
||||
return useMutation<any[], Error, string>(addImage, {
|
||||
retry: 3,
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData([QUERIES.MEDIA.GET], data);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error.toString().includes('code 409')) {
|
||||
//means image ware already preset, refresh to get updated order
|
||||
queryClient.invalidateQueries([QUERIES.MEDIA.GET]);
|
||||
} else {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useMediaUploadMutation = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const addToUploadsMutation = useAddToUploadsMutation();
|
||||
|
||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||
const pinCode = useAppSelector((state) => state.application.pin);
|
||||
|
||||
return useMutation<Image, undefined, MediaUploadVars>(
|
||||
async ({ media }) => {
|
||||
console.log('uploading media', media);
|
||||
let sign = await signImage(media, currentAccount, pinCode);
|
||||
return await uploadImage(media, currentAccount.name, sign);
|
||||
},
|
||||
{
|
||||
retry: 3,
|
||||
onSuccess: (response, { addToUploads }) => {
|
||||
if (addToUploads && response && response.url) {
|
||||
console.log('adding image to gallery', response.url);
|
||||
addToUploadsMutation.mutate(response.url);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useSnippetsMutation = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Snippet[], undefined, SnippetMutationVars>(
|
||||
async (vars) => {
|
||||
console.log('going to add/update snippet', vars);
|
||||
if (vars.id) {
|
||||
const response = await updateFragment(vars.id, vars.title, vars.body);
|
||||
return response;
|
||||
} else {
|
||||
const response = await addFragment(vars.title, vars.body);
|
||||
return response;
|
||||
}
|
||||
},
|
||||
{
|
||||
onMutate: (vars) => {
|
||||
console.log('mutate snippets for add/update', vars);
|
||||
|
||||
const _newItem = {
|
||||
id: vars.id,
|
||||
title: vars.title,
|
||||
body: vars.body,
|
||||
created: new Date().toDateString(),
|
||||
modified: new Date().toDateString(),
|
||||
} as Snippet;
|
||||
|
||||
const data = queryClient.getQueryData<Snippet[]>([QUERIES.SNIPPETS.GET]);
|
||||
|
||||
let _newData: Snippet[] = data ? [...data] : [];
|
||||
if (vars.id) {
|
||||
const snipIndex = _newData.findIndex((item) => vars.id === item.id);
|
||||
_newData[snipIndex] = _newItem;
|
||||
} else {
|
||||
_newData = [_newItem, ..._newData];
|
||||
}
|
||||
|
||||
queryClient.setQueryData([QUERIES.SNIPPETS.GET], _newData);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
console.log('added/updated snippet', data);
|
||||
queryClient.invalidateQueries([QUERIES.SNIPPETS.GET]);
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'snippets.message_failed' })));
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/** DELETE MUTATIONS **/
|
||||
|
||||
export const useMediaDeleteMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
return useMutation<string[], undefined, string[]>(async (deleteIds) => {
|
||||
for (const i in deleteIds) {
|
||||
await deleteImage(deleteIds[i]);
|
||||
const queryClient = useQueryClient();
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
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));
|
||||
queryClient.setQueryData([QUERIES.MEDIA.GET], _newData);
|
||||
}
|
||||
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)))
|
||||
queryClient.setQueryData([QUERIES.MEDIA.GET], _newData);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'uploads_modal.delete_failed' })));
|
||||
queryClient.invalidateQueries([QUERIES.MEDIA.GET]);
|
||||
},
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'uploads_modal.delete_failed' })));
|
||||
queryClient.invalidateQueries([QUERIES.MEDIA.GET]);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const useSnippetDeleteMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
return useMutation<Snippet[], undefined, string>(deleteFragment, {
|
||||
retry: 3,
|
||||
onSuccess: (data) => {
|
||||
console.log('Success scheduled post delete', data);
|
||||
queryClient.setQueryData([QUERIES.SNIPPETS.GET], data);
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
const queryClient = useQueryClient();
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
return useMutation<Snippet[], undefined, string>(deleteFragment, {
|
||||
retry: 3,
|
||||
onSuccess: (data) => {
|
||||
console.log('Success scheduled post delete', data);
|
||||
queryClient.setQueryData([QUERIES.SNIPPETS.GET], data);
|
||||
},
|
||||
onError: () => {
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -26,4 +26,4 @@ export const initQueryClient = () => {
|
||||
export * from './notificationQueries';
|
||||
export * from './draftQueries';
|
||||
export * from './editorQueries';
|
||||
export * from './pointQueries'
|
||||
export * from './pointQueries';
|
||||
|
@ -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;
|
||||
transactionId?: string;
|
||||
cacheId?: string
|
||||
pointsTy: PointActivityIds;
|
||||
blockNum?: string | number;
|
||||
transactionId?: 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 dispatch = useAppDispatch();
|
||||
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);
|
||||
return true;
|
||||
};
|
||||
|
||||
const _mutationFn = async ({ pointsTy, blockNum, transactionId }: UserActivityMutationVars) => {
|
||||
|
||||
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);
|
||||
//remove entry from redux
|
||||
if (vars.cacheId) {
|
||||
console.log('must remove from redux');
|
||||
dispatch(deletePointActivityCache(vars.cacheId));
|
||||
}
|
||||
},
|
||||
onError: (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');
|
||||
const cacheId = generateRndStr();
|
||||
const { username } = currentAccount;
|
||||
dispatch(updatePointActivityCache(cacheId, { ...vars, username }));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const mutation = useMutation<boolean, Error, UserActivityMutationVars>(_mutationFn, {
|
||||
retry: 2,
|
||||
onSuccess: (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))
|
||||
}
|
||||
},
|
||||
onError: (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")
|
||||
const cacheId = generateRndStr();
|
||||
const { username } = currentAccount;
|
||||
dispatch(updatePointActivityCache(cacheId, { ...vars, username }))
|
||||
}
|
||||
}
|
||||
})
|
||||
const lazyMutatePendingActivities = () => {
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
const lazyMutatePendingActivities = () => {
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
...mutation,
|
||||
lazyMutatePendingActivities,
|
||||
}
|
||||
}
|
||||
return {
|
||||
...mutation,
|
||||
lazyMutatePendingActivities,
|
||||
};
|
||||
};
|
||||
|
@ -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.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({
|
||||
author: comment.author,
|
||||
permlink: comment.permlink,
|
||||
last_update: comment.updated,
|
||||
body: comment.markdownBody,
|
||||
}, true, Platform.OS === 'android');
|
||||
comment.body = renderPostBody(
|
||||
{
|
||||
author: comment.author,
|
||||
permlink: comment.permlink,
|
||||
last_update: comment.updated,
|
||||
body: comment.markdownBody,
|
||||
},
|
||||
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,
|
||||
});
|
||||
|
@ -1,82 +1,86 @@
|
||||
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';
|
||||
|
||||
export const setSelectedCoins = (coins: CoinBase[]) => ({
|
||||
payload: coins,
|
||||
type: SET_SELECTED_COINS,
|
||||
payload: coins,
|
||||
type: SET_SELECTED_COINS,
|
||||
});
|
||||
|
||||
|
||||
|
||||
export const setCoinsData = (data: { [key: string]: CoinData }, vsCurrency: string, username: string) => ({
|
||||
payload: {
|
||||
data,
|
||||
vsCurrency,
|
||||
username,
|
||||
},
|
||||
type: SET_COINS_DATA
|
||||
})
|
||||
export const setCoinsData = (
|
||||
data: { [key: string]: CoinData },
|
||||
vsCurrency: string,
|
||||
username: string,
|
||||
) => ({
|
||||
payload: {
|
||||
data,
|
||||
vsCurrency,
|
||||
username,
|
||||
},
|
||||
type: SET_COINS_DATA,
|
||||
});
|
||||
|
||||
export const setPriceHistory = (coinId: string, vsCurrency: string, data: number[]) => ({
|
||||
payload: {
|
||||
id: coinId,
|
||||
vsCurrency,
|
||||
data
|
||||
},
|
||||
type: SET_PRICE_HISTORY
|
||||
})
|
||||
payload: {
|
||||
id: coinId,
|
||||
vsCurrency,
|
||||
data,
|
||||
},
|
||||
type: SET_PRICE_HISTORY,
|
||||
});
|
||||
|
||||
export const setCoinActivities = (coinId: string, data: CoinActivitiesCollection) => ({
|
||||
payload: {
|
||||
id: coinId,
|
||||
data,
|
||||
},
|
||||
type: SET_COIN_ACTIVITIES
|
||||
})
|
||||
payload: {
|
||||
id: coinId,
|
||||
data,
|
||||
},
|
||||
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)
|
||||
dispatch({
|
||||
type: SET_COIN_QUOTES,
|
||||
payload: { ...quotes },
|
||||
})
|
||||
})
|
||||
}
|
||||
const currency = getState().application.currency;
|
||||
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,
|
||||
) => {
|
||||
const coins = getState().wallet.selectedCoins;
|
||||
const quotes = getState().wallet.quotes;
|
||||
const currentAccount = getState().account.currentAccount;
|
||||
const currency = getState().application.currency;
|
||||
const globalProps = getState().account.globalProps;
|
||||
|
||||
const coinsData = await fetchCoinsData({
|
||||
coins,
|
||||
currentAccount,
|
||||
vsCurrency: currency.currency,
|
||||
currencyRate: currency.currencyRate,
|
||||
globalProps,
|
||||
quotes,
|
||||
refresh,
|
||||
});
|
||||
|
||||
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;
|
||||
const currency = getState().application.currency;
|
||||
const globalProps = getState().account.globalProps;
|
||||
|
||||
const coinsData = await fetchCoinsData({
|
||||
coins,
|
||||
currentAccount,
|
||||
vsCurrency: currency.currency,
|
||||
currencyRate: currency.currencyRate,
|
||||
globalProps,
|
||||
quotes,
|
||||
refresh
|
||||
})
|
||||
|
||||
return dispatch(setCoinsData(
|
||||
coinsData,
|
||||
currency.currency,
|
||||
currentAccount.username
|
||||
))
|
||||
}
|
||||
return dispatch(setCoinsData(coinsData, currency.currency, currentAccount.username));
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
export const statusMessage = {
|
||||
PENDING : 'PENDING',
|
||||
SUCCESS : 'SUCCESS',
|
||||
FAIL : 'FAIL',
|
||||
PENDING: 'PENDING',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'FAIL',
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -1,235 +1,231 @@
|
||||
import { PointActivity } from "../../providers/ecency/ecency.types";
|
||||
import { PointActivity } from '../../providers/ecency/ecency.types';
|
||||
import {
|
||||
PURGE_EXPIRED_CACHE,
|
||||
UPDATE_VOTE_CACHE,
|
||||
UPDATE_COMMENT_CACHE,
|
||||
DELETE_COMMENT_CACHE_ENTRY,
|
||||
DELETE_DRAFT_CACHE_ENTRY,
|
||||
UPDATE_DRAFT_CACHE,
|
||||
UPDATE_SUBSCRIBED_COMMUNITY_CACHE,
|
||||
DELETE_SUBSCRIBED_COMMUNITY_CACHE,
|
||||
CLEAR_SUBSCRIBED_COMMUNITIES_CACHE,
|
||||
UPDATE_POINT_ACTIVITY_CACHE,
|
||||
DELETE_POINT_ACTIVITY_CACHE_ENTRY
|
||||
} from "../constants/constants";
|
||||
PURGE_EXPIRED_CACHE,
|
||||
UPDATE_VOTE_CACHE,
|
||||
UPDATE_COMMENT_CACHE,
|
||||
DELETE_COMMENT_CACHE_ENTRY,
|
||||
DELETE_DRAFT_CACHE_ENTRY,
|
||||
UPDATE_DRAFT_CACHE,
|
||||
UPDATE_SUBSCRIBED_COMMUNITY_CACHE,
|
||||
DELETE_SUBSCRIBED_COMMUNITY_CACHE,
|
||||
CLEAR_SUBSCRIBED_COMMUNITIES_CACHE,
|
||||
UPDATE_POINT_ACTIVITY_CACHE,
|
||||
DELETE_POINT_ACTIVITY_CACHE_ENTRY,
|
||||
} from '../constants/constants';
|
||||
|
||||
export enum CommentCacheStatus {
|
||||
PENDING = 'PENDING',
|
||||
POSTPONED = 'PUBLISHED',
|
||||
DELETED = 'DELETED',
|
||||
PENDING = 'PENDING',
|
||||
POSTPONED = 'PUBLISHED',
|
||||
DELETED = 'DELETED',
|
||||
}
|
||||
|
||||
export interface Vote {
|
||||
amount: number;
|
||||
isDownvote: boolean;
|
||||
incrementStep: number;
|
||||
votedAt: number;
|
||||
expiresAt: number;
|
||||
amount: number;
|
||||
isDownvote: boolean;
|
||||
incrementStep: number;
|
||||
votedAt: number;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
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,
|
||||
expiresAt?: number;
|
||||
author: string;
|
||||
body: string;
|
||||
title?: string;
|
||||
tags?: string;
|
||||
meta?: any;
|
||||
created?: number;
|
||||
updated?: number;
|
||||
expiresAt?: number;
|
||||
}
|
||||
|
||||
export interface SubscribedCommunity {
|
||||
data: Array<any>,
|
||||
expiresAt?: number;
|
||||
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>
|
||||
lastUpdate: {
|
||||
postPath: string,
|
||||
updatedAt: number,
|
||||
type: 'vote' | 'comment' | 'draft',
|
||||
}
|
||||
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';
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
votes: new Map(),
|
||||
comments: new Map(),
|
||||
drafts: new Map(),
|
||||
subscribedCommunities: new Map(),
|
||||
pointActivities: new Map(),
|
||||
lastUpdate: null,
|
||||
votes: new Map(),
|
||||
comments: new Map(),
|
||||
drafts: new Map(),
|
||||
subscribedCommunities: new Map(),
|
||||
pointActivities: new Map(),
|
||||
lastUpdate: null,
|
||||
};
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case UPDATE_VOTE_CACHE:
|
||||
if (!state.votes) {
|
||||
state.votes = new Map<string, Vote>();
|
||||
}
|
||||
state.votes.set(payload.postPath, payload.vote);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate: {
|
||||
postPath: payload.postPath,
|
||||
updatedAt: new Date().getTime(),
|
||||
type: 'vote',
|
||||
}
|
||||
};
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case UPDATE_VOTE_CACHE:
|
||||
if (!state.votes) {
|
||||
state.votes = new Map<string, Vote>();
|
||||
}
|
||||
state.votes.set(payload.postPath, payload.vote);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate: {
|
||||
postPath: payload.postPath,
|
||||
updatedAt: new Date().getTime(),
|
||||
type: 'vote',
|
||||
},
|
||||
};
|
||||
|
||||
case UPDATE_COMMENT_CACHE:
|
||||
if (!state.comments) {
|
||||
state.comments = new Map<string, Comment>();
|
||||
}
|
||||
state.comments.set(payload.commentPath, payload.comment);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate: {
|
||||
postPath: payload.commentPath,
|
||||
updatedAt: new Date().getTime(),
|
||||
type: 'comment'
|
||||
}
|
||||
};
|
||||
case UPDATE_COMMENT_CACHE:
|
||||
if (!state.comments) {
|
||||
state.comments = new Map<string, Comment>();
|
||||
}
|
||||
state.comments.set(payload.commentPath, payload.comment);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate: {
|
||||
postPath: payload.commentPath,
|
||||
updatedAt: new Date().getTime(),
|
||||
type: 'comment',
|
||||
},
|
||||
};
|
||||
|
||||
case DELETE_COMMENT_CACHE_ENTRY:
|
||||
if (state.comments && state.comments.has(payload)) {
|
||||
state.comments.delete(payload);
|
||||
}
|
||||
return { ...state }
|
||||
case DELETE_COMMENT_CACHE_ENTRY:
|
||||
if (state.comments && state.comments.has(payload)) {
|
||||
state.comments.delete(payload);
|
||||
}
|
||||
return { ...state };
|
||||
|
||||
case UPDATE_DRAFT_CACHE:
|
||||
if (!state.drafts) {
|
||||
state.drafts = new Map<string, Draft>();
|
||||
}
|
||||
case UPDATE_DRAFT_CACHE:
|
||||
if (!state.drafts) {
|
||||
state.drafts = new Map<string, Draft>();
|
||||
}
|
||||
|
||||
const curTime = new Date().getTime();
|
||||
const curDraft = state.drafts.get(payload.id);
|
||||
const payloadDraft = payload.draft;
|
||||
const curTime = new Date().getTime();
|
||||
const curDraft = state.drafts.get(payload.id);
|
||||
const payloadDraft = payload.draft;
|
||||
|
||||
payloadDraft.created = curDraft ? curDraft.created : curTime;
|
||||
payloadDraft.updated = curTime;
|
||||
payloadDraft.expiresAt = curTime + 604800000 // 7 days ms
|
||||
payloadDraft.created = curDraft ? curDraft.created : curTime;
|
||||
payloadDraft.updated = curTime;
|
||||
payloadDraft.expiresAt = curTime + 604800000; // 7 days ms
|
||||
|
||||
state.drafts.set(payload.id, payloadDraft);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate: {
|
||||
postPath: payload.id,
|
||||
updatedAt: new Date().getTime(),
|
||||
type: 'draft',
|
||||
},
|
||||
};
|
||||
state.drafts.set(payload.id, payloadDraft);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate: {
|
||||
postPath: payload.id,
|
||||
updatedAt: new Date().getTime(),
|
||||
type: 'draft',
|
||||
},
|
||||
};
|
||||
|
||||
case DELETE_DRAFT_CACHE_ENTRY:
|
||||
if (state.drafts && state.drafts.has(payload)) {
|
||||
state.drafts.delete(payload);
|
||||
}
|
||||
return { ...state }
|
||||
case DELETE_DRAFT_CACHE_ENTRY:
|
||||
if (state.drafts && state.drafts.has(payload)) {
|
||||
state.drafts.delete(payload);
|
||||
}
|
||||
return { ...state };
|
||||
|
||||
case UPDATE_SUBSCRIBED_COMMUNITY_CACHE:
|
||||
if (!state.subscribedCommunities) {
|
||||
state.subscribedCommunities = new Map<string, SubscribedCommunity>();
|
||||
}
|
||||
const subscribedCommunities = new Map(state.subscribedCommunities);
|
||||
subscribedCommunities.set(payload.path, payload.subscribedCommunity);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
subscribedCommunities: subscribedCommunities
|
||||
};
|
||||
case UPDATE_SUBSCRIBED_COMMUNITY_CACHE:
|
||||
if (!state.subscribedCommunities) {
|
||||
state.subscribedCommunities = new Map<string, SubscribedCommunity>();
|
||||
}
|
||||
const subscribedCommunities = new Map(state.subscribedCommunities);
|
||||
subscribedCommunities.set(payload.path, payload.subscribedCommunity);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
subscribedCommunities: subscribedCommunities,
|
||||
};
|
||||
|
||||
case DELETE_SUBSCRIBED_COMMUNITY_CACHE:
|
||||
if (state.subscribedCommunities && state.subscribedCommunities.has(payload)) {
|
||||
state.subscribedCommunities.delete(payload);
|
||||
}
|
||||
return { ...state }
|
||||
case DELETE_SUBSCRIBED_COMMUNITY_CACHE:
|
||||
if (state.subscribedCommunities && state.subscribedCommunities.has(payload)) {
|
||||
state.subscribedCommunities.delete(payload);
|
||||
}
|
||||
return { ...state };
|
||||
|
||||
case CLEAR_SUBSCRIBED_COMMUNITIES_CACHE:
|
||||
state.subscribedCommunities = new Map<string, SubscribedCommunity>();
|
||||
case CLEAR_SUBSCRIBED_COMMUNITIES_CACHE:
|
||||
state.subscribedCommunities = new Map<string, SubscribedCommunity>();
|
||||
|
||||
return { ...state }
|
||||
return { ...state };
|
||||
|
||||
case UPDATE_POINT_ACTIVITY_CACHE:
|
||||
if (!state.pointActivities) {
|
||||
state.pointActivities = new Map<string, PointActivity>();
|
||||
}
|
||||
state.pointActivities.set(payload.id, payload.pointActivity);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
};
|
||||
case UPDATE_POINT_ACTIVITY_CACHE:
|
||||
if (!state.pointActivities) {
|
||||
state.pointActivities = new Map<string, PointActivity>();
|
||||
}
|
||||
state.pointActivities.set(payload.id, payload.pointActivity);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
};
|
||||
|
||||
case DELETE_POINT_ACTIVITY_CACHE_ENTRY:
|
||||
if (state.pointActivities && state.pointActivities.has(payload)) {
|
||||
state.pointActivities.delete(payload);
|
||||
}
|
||||
return { ...state }
|
||||
case DELETE_POINT_ACTIVITY_CACHE_ENTRY:
|
||||
if (state.pointActivities && state.pointActivities.has(payload)) {
|
||||
state.pointActivities.delete(payload);
|
||||
}
|
||||
return { ...state };
|
||||
|
||||
case PURGE_EXPIRED_CACHE:
|
||||
const currentTime = new Date().getTime();
|
||||
case PURGE_EXPIRED_CACHE:
|
||||
const currentTime = new Date().getTime();
|
||||
|
||||
if (state.votes && state.votes.size) {
|
||||
Array.from(state.votes).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime) {
|
||||
state.votes.delete(entry[0]);
|
||||
}
|
||||
})
|
||||
}
|
||||
if (state.votes && state.votes.size) {
|
||||
Array.from(state.votes).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime) {
|
||||
state.votes.delete(entry[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state.comments && state.comments.size) {
|
||||
Array.from(state.comments).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime) {
|
||||
state.comments.delete(entry[0]);
|
||||
}
|
||||
})
|
||||
}
|
||||
if (state.comments && state.comments.size) {
|
||||
Array.from(state.comments).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime) {
|
||||
state.comments.delete(entry[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state.drafts && state.drafts.size) {
|
||||
Array.from(state.drafts).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime || !entry[1].body) {
|
||||
state.drafts.delete(entry[0]);
|
||||
}
|
||||
})
|
||||
if (state.drafts && state.drafts.size) {
|
||||
Array.from(state.drafts).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime || !entry[1].body) {
|
||||
state.drafts.delete(entry[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state.subscribedCommunities && state.subscribedCommunities.size) {
|
||||
Array.from(state.subscribedCommunities).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime) {
|
||||
state.subscribedCommunities.delete(entry[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (state.subscribedCommunities && state.subscribedCommunities.size) {
|
||||
Array.from(state.subscribedCommunities).forEach((entry) => {
|
||||
if (entry[1].expiresAt < currentTime) {
|
||||
state.subscribedCommunities.delete(entry[0]);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...state
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:{}
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
case REMOVE_BENEFICIARIES:
|
||||
delete state.beneficiariesMap[payload.draftId]
|
||||
return {
|
||||
...state //spread operator in requried here, otherwise persist do not register change
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
const initialState: State = {
|
||||
beneficiariesMap: {},
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
case REMOVE_BENEFICIARIES:
|
||||
delete state.beneficiariesMap[payload.draftId];
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
@ -1,140 +1,144 @@
|
||||
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;
|
||||
iconType: string;
|
||||
textKey: string;
|
||||
created: string;
|
||||
expires: string;
|
||||
icon: string;
|
||||
value:string;
|
||||
details: string;
|
||||
memo: string;
|
||||
trxIndex: number;
|
||||
iconType: string;
|
||||
textKey: string;
|
||||
created: string;
|
||||
expires: string;
|
||||
icon: 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:{
|
||||
[key: string]: CoinData;
|
||||
},
|
||||
priceHistories:{
|
||||
[key: string]: PriceHistory;
|
||||
}
|
||||
coinsActivities:{
|
||||
[key: string]:CoinActivitiesCollection;
|
||||
},
|
||||
quotes:{
|
||||
[key: string]: QuoteItem;
|
||||
}
|
||||
vsCurrency:string,
|
||||
username:string,
|
||||
updateTimestamp:number;
|
||||
selectedCoins: CoinBase[];
|
||||
coinsData: {
|
||||
[key: string]: CoinData;
|
||||
};
|
||||
priceHistories: {
|
||||
[key: string]: PriceHistory;
|
||||
};
|
||||
coinsActivities: {
|
||||
[key: string]: CoinActivitiesCollection;
|
||||
};
|
||||
quotes: {
|
||||
[key: string]: QuoteItem;
|
||||
};
|
||||
vsCurrency: string;
|
||||
username: string;
|
||||
updateTimestamp: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const initialState:State = {
|
||||
selectedCoins:DEFAULT_COINS,
|
||||
coinsData:{},
|
||||
priceHistories:{},
|
||||
coinsActivities:{},
|
||||
quotes: null,
|
||||
vsCurrency:'',
|
||||
username:'',
|
||||
updateTimestamp:0
|
||||
const initialState: State = {
|
||||
selectedCoins: DEFAULT_COINS,
|
||||
coinsData: {},
|
||||
priceHistories: {},
|
||||
coinsActivities: {},
|
||||
quotes: null,
|
||||
vsCurrency: '',
|
||||
username: '',
|
||||
updateTimestamp: 0,
|
||||
};
|
||||
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
const {type, payload} = action;
|
||||
switch (type) {
|
||||
case RESET_WALLET_DATA:{
|
||||
return {
|
||||
...initialState,
|
||||
selectedCoins:state.selectedCoins
|
||||
}
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case RESET_WALLET_DATA: {
|
||||
return {
|
||||
...initialState,
|
||||
selectedCoins: state.selectedCoins,
|
||||
};
|
||||
}
|
||||
case SET_SELECTED_COINS:{
|
||||
return {
|
||||
...state,
|
||||
selectedCoin:payload
|
||||
}
|
||||
case SET_SELECTED_COINS: {
|
||||
return {
|
||||
...state,
|
||||
selectedCoin: payload,
|
||||
};
|
||||
}
|
||||
case SET_COINS_DATA:{
|
||||
return {
|
||||
...state,
|
||||
coinsData:payload.data,
|
||||
vsCurrency:payload.vsCurrency,
|
||||
username:payload.username,
|
||||
updateTimestamp:new Date().getTime()
|
||||
}
|
||||
case SET_COINS_DATA: {
|
||||
return {
|
||||
...state,
|
||||
coinsData: payload.data,
|
||||
vsCurrency: payload.vsCurrency,
|
||||
username: payload.username,
|
||||
updateTimestamp: new Date().getTime(),
|
||||
};
|
||||
}
|
||||
case SET_PRICE_HISTORY:{
|
||||
state.priceHistories[payload.id] = {
|
||||
expiresAt:new Date().getTime() + ONE_HOUR_MS,
|
||||
vsCurrency:payload.vsCurrency,
|
||||
data:payload.data
|
||||
};
|
||||
return {
|
||||
...state
|
||||
}
|
||||
case SET_PRICE_HISTORY: {
|
||||
state.priceHistories[payload.id] = {
|
||||
expiresAt: new Date().getTime() + ONE_HOUR_MS,
|
||||
vsCurrency: payload.vsCurrency,
|
||||
data: payload.data,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
case SET_COIN_ACTIVITIES:{
|
||||
state.coinsActivities[payload.id] = payload.data
|
||||
return {
|
||||
...state
|
||||
}
|
||||
case SET_COIN_ACTIVITIES: {
|
||||
state.coinsActivities[payload.id] = payload.data;
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
case SET_COIN_QUOTES:{
|
||||
return {
|
||||
...state,
|
||||
quotes:payload,
|
||||
}
|
||||
case SET_COIN_QUOTES: {
|
||||
return {
|
||||
...state,
|
||||
quotes: payload,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const ONE_HOUR_MS = 60 * 60 * 1000;
|
||||
const ONE_HOUR_MS = 60 * 60 * 1000;
|
||||
|
@ -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'] },
|
||||
);
|
||||
|
@ -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,17 +116,14 @@ class ApplicationScreen extends Component {
|
||||
{!isConnected && <NoInternetConnection />}
|
||||
|
||||
<AppNavigator />
|
||||
|
||||
</Fragment>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
_renderAppModals() {
|
||||
const { toastNotification, foregroundNotificationData } = this.props;
|
||||
const { isShowToastNotification } = this.state;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ForegroundNotification remoteMessage={foregroundNotificationData} />
|
||||
@ -148,11 +140,9 @@ class ApplicationScreen extends Component {
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
};
|
||||
|
@ -2,14 +2,14 @@ import { ViewStyle } from 'react-native';
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
userRibbonContainer: {
|
||||
borderBottomWidth: EStyleSheet.hairlineWidth,
|
||||
borderColor: '$darkGrayBackground',
|
||||
marginBottom: 0, //without 0 margin, view will start overlapping UserRibbon
|
||||
paddingBottom: 32
|
||||
} as ViewStyle,
|
||||
userRibbonContainer: {
|
||||
borderBottomWidth: EStyleSheet.hairlineWidth,
|
||||
borderColor: '$darkGrayBackground',
|
||||
marginBottom: 0, //without 0 margin, view will start overlapping UserRibbon
|
||||
paddingBottom: 32,
|
||||
} as ViewStyle,
|
||||
|
||||
listContainer: {
|
||||
paddingTop: 16
|
||||
} as ViewStyle
|
||||
listContainer: {
|
||||
paddingTop: 16,
|
||||
} as ViewStyle,
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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:{
|
||||
color: '$primaryBlack',
|
||||
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'
|
||||
} as TextStyle,
|
||||
textNegative:{
|
||||
color: '$primaryRed'
|
||||
} as TextStyle,
|
||||
textBasicValue:{
|
||||
color: '$primaryBlack',
|
||||
fontWeight:'700',
|
||||
fontSize: 28,
|
||||
|
||||
textPositive: {
|
||||
color: '$primaryGreen',
|
||||
} as TextStyle,
|
||||
textBasicLabel:{
|
||||
textNegative: {
|
||||
color: '$primaryRed',
|
||||
} as TextStyle,
|
||||
textBasicValue: {
|
||||
color: '$primaryBlack',
|
||||
fontWeight: '700',
|
||||
fontSize: 28,
|
||||
} as TextStyle,
|
||||
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',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,38 +1,36 @@
|
||||
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[];
|
||||
onActionPress: (action: string) => void;
|
||||
actions: string[];
|
||||
onActionPress: (action: string) => void;
|
||||
}
|
||||
|
||||
export const CoinActions = withNavigation(({ actions, onActionPress }: CoinActionsProps) => {
|
||||
const intl = useIntl();
|
||||
const intl = useIntl();
|
||||
|
||||
const _renderItem = (item: string, index: number) => {
|
||||
|
||||
const _onPress = () => {
|
||||
onActionPress(item)
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity key={`action-${item}-${index}`} style={styles.actionContainer} containerStyle={styles.actionBtnContainer} onPress={_onPress}>
|
||||
<Fragment>
|
||||
<Text style={styles.actionText}>
|
||||
{intl.formatMessage({ id: `wallet.${item}` })}
|
||||
</Text>
|
||||
</Fragment>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
const _renderItem = (item: string, index: number) => {
|
||||
const _onPress = () => {
|
||||
onActionPress(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.actionsContainer}>
|
||||
{actions.map(_renderItem)}
|
||||
</View>
|
||||
)
|
||||
});
|
||||
<TouchableOpacity
|
||||
key={`action-${item}-${index}`}
|
||||
style={styles.actionContainer}
|
||||
containerStyle={styles.actionBtnContainer}
|
||||
onPress={_onPress}
|
||||
>
|
||||
<Fragment>
|
||||
<Text style={styles.actionText}>{intl.formatMessage({ id: `wallet.${item}` })}</Text>
|
||||
</Fragment>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
return <View style={styles.actionsContainer}>{actions.map(_renderItem)}</View>;
|
||||
});
|
||||
|
@ -1,76 +1,77 @@
|
||||
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
|
||||
valuePairs: DataPair[];
|
||||
extraData: DataPair[];
|
||||
coinSymbol: string;
|
||||
percentChange: number;
|
||||
onInfoPress: (id: string) => void;
|
||||
}
|
||||
|
||||
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}>
|
||||
{intl.formatMessage({ id: 'wallet.change' })}
|
||||
<Text
|
||||
style={percentChange > 0 ? styles.textPositive : styles.textNegative}>
|
||||
{` ${percentChange >= 0 ? '+' : ''}${percentChange.toFixed(1)}%`}
|
||||
</Text>
|
||||
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}>
|
||||
{intl.formatMessage({ id: 'wallet.change' })}
|
||||
<Text style={percentChange > 0 ? styles.textPositive : styles.textNegative}>
|
||||
{` ${percentChange >= 0 ? '+' : ''}${percentChange.toFixed(1)}%`}
|
||||
</Text>
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
const _renderValuePair = (args: DataPair, index: number) => {
|
||||
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 _renderValuePair = (args: DataPair, index: number) => {
|
||||
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 _renderExtraData = (args: DataPair, index: number) => {
|
||||
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}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text
|
||||
style={styles.textExtraValue}
|
||||
onPress={args.isClickable && _onPress}>
|
||||
{args.value}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
const _onPress = () => {
|
||||
onInfoPress(args.dataKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.card, styles.basicsContainer]}>
|
||||
{_renderCoinHeader}
|
||||
{valuePairs.map(_renderValuePair)}
|
||||
{extraData && extraData.map(_renderExtraData)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
<View key={`extra-data-${args.dataKey}-${index}`} style={styles.extraDataContainer}>
|
||||
<Text
|
||||
style={[styles.textExtraLabel, args.isClickable && styles.textUnderline]}
|
||||
onPress={args.isClickable && _onPress}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Text style={styles.textExtraValue} onPress={args.isClickable && _onPress}>
|
||||
{args.value}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.card, styles.basicsContainer]}>
|
||||
{_renderCoinHeader}
|
||||
{valuePairs.map(_renderValuePair)}
|
||||
{extraData && extraData.map(_renderExtraData)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -1,54 +1,50 @@
|
||||
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;
|
||||
return (
|
||||
<View style={styles.chartContainer}>
|
||||
<SimpleChart
|
||||
data={chartData || []}
|
||||
baseWidth={_baseWidth} // from react-native
|
||||
chartHeight={200}
|
||||
showLine={true}
|
||||
showLabels={true}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
const _renderGraph = () => {
|
||||
const _baseWidth = getWindowDimensions().width - 32 + CHART_NEGATIVE_MARGIN;
|
||||
return (
|
||||
<>
|
||||
<View style={styles.card}>
|
||||
{_renderGraph()}
|
||||
</View>
|
||||
<RangeSelector range={range} onRangeChange={_onRangeChange} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
<View style={styles.chartContainer}>
|
||||
<SimpleChart
|
||||
data={chartData || []}
|
||||
baseWidth={_baseWidth} // from react-native
|
||||
chartHeight={200}
|
||||
showLine={true}
|
||||
showLabels={true}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<View style={styles.card}>{_renderGraph()}</View>
|
||||
<RangeSelector range={range} onRangeChange={_onRangeChange} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,69 +1,61 @@
|
||||
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 = ({
|
||||
coinSymbol,
|
||||
id,
|
||||
coinData,
|
||||
percentChagne,
|
||||
onActionPress,
|
||||
onInfoPress
|
||||
}:CoinSummaryProps) => {
|
||||
const {
|
||||
balance,
|
||||
estimateValue,
|
||||
savings,
|
||||
extraDataPairs,
|
||||
actions
|
||||
} = coinData
|
||||
coinSymbol,
|
||||
id,
|
||||
coinData,
|
||||
percentChagne,
|
||||
onActionPress,
|
||||
onInfoPress,
|
||||
}: CoinSummaryProps) => {
|
||||
const { balance, estimateValue, savings, extraDataPairs, actions } = coinData;
|
||||
|
||||
const valuePairs = [
|
||||
{
|
||||
dataKey:'amount_desc',
|
||||
value:balance
|
||||
}
|
||||
] as DataPair[]
|
||||
const valuePairs = [
|
||||
{
|
||||
dataKey: 'amount_desc',
|
||||
value: balance,
|
||||
},
|
||||
] as DataPair[];
|
||||
|
||||
if(estimateValue !== undefined){
|
||||
valuePairs.push({
|
||||
dataKey:'estimated_value',
|
||||
value:<FormattedCurrency isApproximate isToken value={estimateValue} />,
|
||||
})
|
||||
}
|
||||
if (estimateValue !== undefined) {
|
||||
valuePairs.push({
|
||||
dataKey: 'estimated_value',
|
||||
value: <FormattedCurrency isApproximate isToken value={estimateValue} />,
|
||||
});
|
||||
}
|
||||
|
||||
if(savings !== undefined){
|
||||
valuePairs.push({
|
||||
dataKey:'savings',
|
||||
value:savings
|
||||
})
|
||||
}
|
||||
if (savings !== undefined) {
|
||||
valuePairs.push({
|
||||
dataKey: 'savings',
|
||||
value: savings,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<CoinBasics
|
||||
valuePairs={valuePairs}
|
||||
extraData={extraDataPairs}
|
||||
coinSymbol={coinSymbol}
|
||||
percentChange={percentChagne}
|
||||
onInfoPress={onInfoPress}
|
||||
/>
|
||||
<CoinActions actions={actions} onActionPress={onActionPress}/>
|
||||
{
|
||||
id !== COIN_IDS.ECENCY && id !== COIN_IDS.HP && <CoinChart coinId={id} />
|
||||
}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<CoinBasics
|
||||
valuePairs={valuePairs}
|
||||
extraData={extraDataPairs}
|
||||
coinSymbol={coinSymbol}
|
||||
percentChange={percentChagne}
|
||||
onInfoPress={onInfoPress}
|
||||
/>
|
||||
<CoinActions actions={actions} onActionPress={onActionPress} />
|
||||
{id !== COIN_IDS.ECENCY && id !== COIN_IDS.HP && <CoinChart coinId={id} />}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user