Merge branch 'development' into nt/post-pinning

This commit is contained in:
noumantahir 2022-03-28 00:42:24 +05:00
commit 1887765dc9
65 changed files with 2855 additions and 558 deletions

View File

@ -85,6 +85,7 @@
"react-native-animatable": "^1.3.3",
"react-native-autoheight-webview": "^1.5.8",
"react-native-camera": "^4.2.1",
"react-native-chart-kit": "^6.11.0",
"react-native-config": "luggit/react-native-config#master",
"react-native-crypto": "^2.2.0",
"react-native-date-picker": "^3.2.7",

View File

@ -25,6 +25,7 @@ import { useIntl } from 'react-intl';
import { useAppSelector } from '../../../hooks';
import { getUnreadNotificationCount } from '../../../providers/ecency/ecency';
import { decryptKey } from '../../../utils/crypto';
import { getUser as getEcencyUser} from '../../../providers/ecency/ePoint';
const AccountsBottomSheetContainer = ({ navigation }) => {
const intl = useIntl();
@ -106,6 +107,7 @@ const AccountsBottomSheetContainer = ({ navigation }) => {
decryptKey(encryptedAccessToken, getDigitPinCode(pinHash))
);
_currentAccount.mutes = await getMutes(_currentAccount.username);
_currentAccount.ecencyUserData = await getEcencyUser(_currentAccount.username);
dispatch(updateCurrentAccount(_currentAccount));
}

View File

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

View File

@ -0,0 +1 @@
export * from './refreshControl';

View File

@ -0,0 +1,23 @@
import React from 'react';
import { RefreshControl as RNRefreshControl } from 'react-native';
import { ThemeContainer } from '../../../containers';
export interface RefreshControlProps {
refreshing:boolean,
onRefresh:()=>void
}
export const RefreshControl = ({refreshing, onRefresh }:RefreshControlProps) => (
<ThemeContainer>
{({ isDarkTheme }) => (
<RNRefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
progressBackgroundColor="#357CE6"
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
titleColor="#fff"
colors={['#fff']}
/>
)}
</ThemeContainer>
);

View File

@ -96,6 +96,7 @@ import QuickReplyModal from './quickReplyModal/quickReplyModalView';
import Tooltip from './tooltip/tooltipView';
import VideoPlayer from './videoPlayer/videoPlayerView';
import QRModal from './qrModal/qrModalView';
import { SimpleChart } from './simpleChart';
// Basic UI Elements
import {
@ -240,4 +241,5 @@ export {
Tooltip,
VideoPlayer,
QRModal,
SimpleChart,
};

View File

@ -24,6 +24,7 @@ export default EStyleSheet.create({
color: 'white',
fontWeight: 'bold',
alignSelf: 'center',
textAlign: 'center',
fontSize: 14,
paddingLeft: 10,
paddingRight: 10,

View File

@ -34,7 +34,8 @@ export default EStyleSheet.create({
borderWidth: 1,
borderColor: '$borderColor',
borderRadius: 8,
padding: 10,
paddingVertical: 10,
paddingHorizontal: 12,
color: '$primaryBlack',
width: 172,
},
@ -110,7 +111,7 @@ export default EStyleSheet.create({
},
dropdownText: {
fontSize: 14,
paddingLeft: 16,
paddingLeft: 12,
paddingHorizontal: 14,
color: '$primaryDarkGray',
},
@ -130,7 +131,7 @@ export default EStyleSheet.create({
},
dropdown: {
flexGrow: 1,
width: 150,
width: 130,
},
slider: {
flex: 1,

View File

@ -33,7 +33,8 @@ export default EStyleSheet.create({
borderWidth: 1,
borderColor: '$borderColor',
borderRadius: 8,
padding: 10,
paddingVertical: 10,
paddingHorizontal: 12,
color: '$primaryBlack',
width: 172,
},
@ -108,7 +109,7 @@ export default EStyleSheet.create({
},
dropdownText: {
fontSize: 14,
paddingLeft: 16,
paddingLeft: 12,
paddingHorizontal: 14,
color: '$primaryDarkGray',
},
@ -128,7 +129,7 @@ export default EStyleSheet.create({
},
dropdown: {
flexGrow: 1,
width: 150,
width: 130,
},
slider: {
flex: 1,

View File

@ -0,0 +1 @@
export * from './simpleChart';

View File

@ -0,0 +1,50 @@
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,
}
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',
}}
/>
)
}

View File

@ -21,7 +21,10 @@ const TransactionView = ({ item, index }) => {
text={intl.formatMessage({
id: `wallet.${get(item, 'textKey')}`,
})}
description={getTimeFromNow(get(item, 'created'))}
description={
(item.expires ? intl.formatMessage({ id: 'wallet.expires' }) + ' ' : '') +
getTimeFromNow(item.expires || item.created)
}
isCircleIcon
isThin
circleIconColor="white"

View File

@ -0,0 +1,12 @@
import axios from 'axios';
const BASE_URL = 'https://api.coingecko.com';
const PATH_API = 'api';
const API_VERSION = 'v3';
const coingeckoApi = axios.create({
baseURL: `${BASE_URL}/${PATH_API}/${API_VERSION}`,
});
export default coingeckoApi;

View File

@ -25,6 +25,7 @@ api.interceptors.request.use((request) => {
|| 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')
){
return request
}

View File

@ -7,7 +7,9 @@
"transfer": "Transfer",
"power_up": "To Vesting",
"transfer_from_savings": "From Savings",
"withdraw_savings":"Withdraw Savings",
"withdraw_vesting": "Power Down",
"open_order":"Open Order",
"fill_order": "Fill Order",
"post": "Post",
"comment": "Comment",
@ -49,7 +51,12 @@
"to": "To",
"estimated_value_desc": "According to purchase value",
"estimated_value": "Estimated value",
"estimated_amount": "Vote value",
"vote_value": "Vote value",
"delegated_hive_power":"Delegated hive power",
"powering_down_hive_power":"Powering down",
"received_hive_power":"Received hive power",
"total_hive_power":"Total hive power",
"savings": "Savings",
"amount_information": "Drag the slider to adjust the amount",
"amount": "Amount",
"memo": "Memo",
@ -68,10 +75,11 @@
"next": "NEXT",
"delegate": "Delegate",
"power_down": "Power Down",
"withdraw_hive": "Withdraw HIVE",
"withdraw_hbd": "Withdraw HBD",
"withdraw_hive": "Withdraw Savings",
"withdraw_hbd": "Withdraw Savings",
"transfer_to_savings": "To Savings",
"convert": "Convert",
"convert_request":"Convert Request",
"escrow_transfer": "Escrow Transfer",
"escrow_dispute": "Escrow Dispute",
"escrow_release": "Escrow Release",
@ -81,8 +89,9 @@
"fill_convert_request": "Convert Executed",
"fill_transfer_from_savings": "Savings Executed",
"fill_vesting_withdraw": "PowerDown executed",
"estm": {
"ecency": {
"title": "Points",
"name":"Ecency Points",
"buy": "GET POINTS"
},
"savinghive": {
@ -93,6 +102,7 @@
},
"hive": {
"title": "HIVE",
"name": "Hive Token",
"buy": "GET HIVE"
},
"hbd": {
@ -100,13 +110,28 @@
"buy": "GET HBD"
},
"hive_power": {
"title": "HIVE POWER"
"title": "HIVE POWER",
"name":"Hive Power"
},
"hive_dollar":{
"name":"Hive Dollar"
},
"btc": {
"title": "BTC",
"buy": "GET BTC",
"address": "RECEIVE"
}
},
"last_updated":"Last Updated:",
"updating":"Updating...",
"coin_details":"Details",
"change":"Change",
"activities":"Activities",
"savings_withdrawal":"Pending Withdrawals",
"open_orders":"Open Orders",
"conversions_requested":"Conversions Requested",
"expires":"expires",
"pending_requests":"Pending Requests"
},
"notification": {
"vote": "voted",
@ -445,7 +470,9 @@
"failed_to_open": "Failed to open a link",
"restart_ecency": "Restart Ecency?",
"restart_ecency_desc": "Applying changes will require a restart.",
"invalid_response":"Could not process request, Try again later."
"invalid_response":"Could not process request, Try again later.",
"wallet_updating":"Wallet update in progress, try again as update finishes",
"claim_failed":"Failed to claim rewards, {message}\nTry again or write to support@ecency.com"
},
"post": {
"reblog_alert": "Are you sure, you want to reblog?",

View File

@ -0,0 +1,29 @@
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[]
export const COIN_IDS = {
ECENCY:'ecency',
HIVE:'hive',
HBD:'hive_dollar',
HP:'hive_power'
}
export default DEFAULT_COINS

View File

@ -33,6 +33,7 @@ export default {
WEB_BROWSER: `WebBrowser${SCREEN_SUFFIX}`,
REFER: `Refer${SCREEN_SUFFIX}`,
QR: `QR${SCREEN_SUFFIX}`,
COIN_DETAILS: `CoinDetails${SCREEN_SUFFIX}`,
},
DRAWER: {
MAIN: `Main${DRAWER_SUFFIX}`,

View File

@ -24,6 +24,8 @@ import { getUser } from '../providers/ecency/ePoint';
// Utils
import { countDecimals } from '../utils/number';
import bugsnagInstance from '../config/bugsnag';
import { fetchCoinsData } from '../utils/wallet';
import { fetchAndSetCoinsData } from '../redux/actions/walletActions';
/*
* Props Name Description Value
@ -78,7 +80,7 @@ class TransferContainer extends Component {
(transferType === 'purchase_estm' || transferType === 'transfer_token') &&
fundType === 'HIVE'
) {
balance = account[0].balance.replace(fundType, '');
balance = account.balance.replace(fundType, '');
}
if (
(transferType === 'purchase_estm' ||
@ -86,19 +88,19 @@ class TransferContainer extends Component {
transferType === 'transfer_token') &&
fundType === 'HBD'
) {
balance = account[0].hbd_balance.replace(fundType, '');
balance = account.hbd_balance.replace(fundType, '');
}
if (transferType === 'points' && fundType === 'ESTM') {
this._getUserPointsBalance(username);
}
if (transferType === 'transfer_to_savings' && fundType === 'HIVE') {
balance = account[0].balance.replace(fundType, '');
balance = account.balance.replace(fundType, '');
}
if (transferType === 'transfer_to_savings' && fundType === 'HBD') {
balance = account[0].hbd_balance.replace(fundType, '');
balance = account.hbd_balance.replace(fundType, '');
}
if (transferType === 'transfer_to_vesting' && fundType === 'HIVE') {
balance = account[0].balance.replace(fundType, '');
balance = account.balance.replace(fundType, '');
}
if (transferType === 'address_view' && fundType === 'BTC') {
//TODO implement transfer of custom tokens
@ -112,7 +114,7 @@ class TransferContainer extends Component {
}
this.setState({
selectedAccount: { ...account[0], local: local[0] },
selectedAccount: { ...account, local: local[0] },
});
});
};
@ -122,6 +124,13 @@ class TransferContainer extends Component {
return validUsers;
};
_delayedRefreshCoinsData = () => {
const { dispatch } = this.props;
setTimeout(() => {
dispatch(fetchAndSetCoinsData(true));
}, 3000);
};
_transferToAccount = async (from, destination, amount, memo) => {
const { pinCode, navigation, dispatch, intl } = this.props;
let { currentAccount } = this.props;
@ -192,6 +201,7 @@ class TransferContainer extends Component {
return func(currentAccount, pinCode, data)
.then(() => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.successful' })));
this._delayedRefreshCoinsData();
navigation.goBack();
})
.catch((err) => {
@ -218,6 +228,7 @@ class TransferContainer extends Component {
_handleOnModalClose = () => {
const { navigation } = this.props;
this._delayedRefreshCoinsData();
navigation.goBack();
};

View File

@ -20,6 +20,7 @@ import { getEstimatedAmount } from '../utils/vote';
// Constants
import ROUTES from '../constants/routeNames';
import { COIN_IDS } from '../constants/defaultCoins';
const HIVE_DROPDOWN = [
'purchase_estm',
@ -36,6 +37,7 @@ const HIVE_POWER_DROPDOWN = ['delegate', 'power_down'];
const WalletContainer = ({
children,
currentAccount,
coinSymbol,
globalProps,
handleOnScroll,
pinCode,
@ -166,13 +168,13 @@ const WalletContainer = ({
getAccount(currentAccount.name)
.then((account) => {
isHasUnclaimedRewards = _isHasUnclaimedRewards(account[0]);
isHasUnclaimedRewards = _isHasUnclaimedRewards(account);
if (isHasUnclaimedRewards) {
const {
reward_hive_balance: hiveBal,
reward_hbd_balance: hbdBal,
reward_vesting_balance: vestingBal,
} = account[0];
} = account;
return claimRewardBalance(currentAccount, pinCode, hiveBal, hbdBal, vestingBal);
}
setIsClaiming(false);
@ -273,6 +275,26 @@ const WalletContainer = ({
}
};
//process symbol based data
let balance = 0;
let estimateValue = 0;
let savings = 0;
switch (coinSymbol) {
case COIN_IDS.HIVE:
balance = hiveBalance;
estimateValue = estimatedHiveValue;
savings = hiveSavingBalance;
break;
case COIN_IDS.HBD:
balance = hbdBalance;
estimateValue = estimatedHbdValue;
savings = hbdSavingBalance;
break;
default:
break;
}
return (
children &&
children({
@ -309,6 +331,11 @@ const WalletContainer = ({
hivePowerDropdown: HIVE_POWER_DROPDOWN,
unclaimedBalance: unclaimedBalance && unclaimedBalance.trim(),
estimatedAmount,
//symbol based data
balance,
estimateValue,
savings,
})
);
};

View File

@ -40,6 +40,7 @@ import {
Communities,
WebBrowser,
ReferScreen,
CoinDetails,
} from '../screens';
const bottomTabNavigator = createBottomTabNavigator(
@ -154,6 +155,7 @@ const stackNavigator = createStackNavigator(
[ROUTES.SCREENS.COMMUNITIES]: { screen: Communities },
[ROUTES.SCREENS.WEB_BROWSER]: { screen: WebBrowser },
[ROUTES.SCREENS.REFER]: { screen: ReferScreen },
[ROUTES.SCREENS.COIN_DETAILS]: { screen: CoinDetails },
},
{
headerMode: 'none',

View File

@ -0,0 +1,42 @@
import bugsnagInstance from '../../config/bugsnag';
import coingeckoApi from '../../config/coingeckoApi';
import { convertMarketData } from './converters';
import { MarketData } from './models';
const PATH_COINS = 'coins';
const PATH_MARKET_CHART = 'market_chart';
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
}
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);
return data;
}catch(error){
bugsnagInstance.notify(error);
throw error;
}
}

View File

@ -0,0 +1,23 @@
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 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;
}

View File

View File

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

View File

@ -1,5 +1,6 @@
import { COIN_IDS } from '../../constants/defaultCoins';
import { Referral } from '../../models';
import { ReferralStat } from './ecency.types';
import { LatestMarketPrices, LatestQuotes, QuoteItem, ReferralStat } from './ecency.types';
export const convertReferral = (rawData: any) => {
return {
@ -17,3 +18,28 @@ export const convertReferralStat = (rawData: any) => {
rewarded: rawData.rewarded || 0,
} as ReferralStat;
};
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
}
export const convertLatestQuotes = (rawData: any, estmPrice:number, 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({
price:estmPrice,
percent_change:0,
last_updated:new Date().toISOString()
}, currencyRate)
} as LatestQuotes;
};

View File

@ -2,6 +2,7 @@ import { Alert } from 'react-native';
import ePointApi from '../../config/api';
import ecencyApi from '../../config/ecencyApi';
import bugsnagInstance from '../../config/bugsnag';
import { EcencyUser, UserPoint } from './ecency.types';
/**
@ -32,7 +33,7 @@ export const userActivity = async (ty:number, tx:string = '', bl:string|number =
}
export const getUser = (username) =>
export const getUser = (username:string):Promise<EcencyUser> =>
new Promise((resolve) => {
ePointApi
.get(`/users/${username}`)
@ -44,7 +45,7 @@ export const getUser = (username) =>
});
});
export const getUserPoints = (username) =>
export const getUserPoints = (username:string):Promise<UserPoint[]> =>
new Promise((resolve) => {
ePointApi
.get(`/users/${username}/points`)
@ -62,7 +63,8 @@ export const claimPoints = async () => {
return response.data;
}catch(error){
console.warn("Failed to calim points", error);
bugsnagInstance.notify(error)
bugsnagInstance.notify(error);
throw new Error(error.response?.data?.message || error.message)
}
}

View File

@ -6,8 +6,8 @@ import bugsnagInstance from '../../config/bugsnag';
import { SERVER_LIST } from '../../constants/options/api';
import { parsePost } from '../../utils/postParser';
import { extractMetadata, makeJsonMetadata } from '../../utils/editor';
import { ReceivedVestingShare, Referral, ReferralStat } from './ecency.types';
import { convertReferral, convertReferralStat } from './converters';
import { LatestMarketPrices, ReceivedVestingShare, Referral, ReferralStat } from './ecency.types';
import { convertLatestQuotes, convertReferral, convertReferralStat } from './converters';
@ -27,6 +27,27 @@ export const getCurrencyRate = (currency) =>
return 1;
});
export const getLatestQuotes = async (currencyRate:number):Promise<LatestMarketPrices> => {
try{
console.log('using currency rate', currencyRate);
const res = await ecencyApi.get(`/private-api/market-data/latest`);
const estmRes = await getCurrencyTokenRate('usd','estm')
if(!res.data || !estmRes){
throw new Error("No quote data returned");
}
const data = convertLatestQuotes(res.data, estmRes, currencyRate);
console.log('parsed quotes data', data);
return data;
} catch (error){
bugsnagInstance.notify(error);
console.warn(error);
throw error
}
}
export const getCurrencyTokenRate = (currency, token) =>
api
.get(`/market-data/currency-rate/${currency}/${token}`)
@ -52,6 +73,8 @@ export const getReceivedVestingShares = async (username: string):Promise<Receive
}
}

View File

@ -1,9 +1,20 @@
import { QuoteItem } from "../../redux/reducers/walletReducer";
export interface ReceivedVestingShare {
delegator:string;
delegatee:string;
vesting_shares:string;
timestamp:string;
}
export interface EcencyUser {
username:string;
points:string;
unclaimed_points:string;
points_by_type:{[key:string]:string};
unclaimed_points_by_type:{[key:string]:string};
}
export interface Referral {
id:number;
referral:string;
@ -15,4 +26,19 @@ export interface Referral {
export interface ReferralStat {
total: number;
rewarded: number;
}
}
export interface UserPoint {
id: number;
type: number;
amount: string;
created:string;
memo?: string;
receiver?: string;
sender?: string;
}
export interface LatestQuotes {
[key:string]:QuoteItem
}

View File

@ -5,6 +5,7 @@ import get from 'lodash/get';
import { Alert } from 'react-native';
import { getDigitPinCode, getMutes, getUser } from './dhive';
import { getUser as getEcencyUser } from '../ecency/ePoint';
import {
setUserData,
setAuthStatus,
@ -71,6 +72,7 @@ export const login = async (username, password, isPinCodeOpen) => {
scTokens ? scTokens.access_token : '',
);
account.mutes = await getMutes(account.username);
account.ecencyUserData = await getEcencyUser(account.username);
let jsonMetadata;
try {
@ -137,6 +139,7 @@ export const loginWithSC2 = async (code, isPinCodeOpen) => {
scTokens ? scTokens.access_token : '',
);
account.mutes = await getMutes(account.username);
account.ecencyUserData = await getEcencyUser(account.username);
let jsonMetadata;
try {

View File

@ -174,38 +174,82 @@ export const fetchGlobalProps = async () => {
};
/**
* @method getAccount get account data
* @param user username
* fetches all tranding orders that are not full-filled yet
* @param {string} username
* @returns {Promise<OpenOrderItem[]>} array of openorders both hive and hbd
*/
export const getAccount = (user) =>
new Promise((resolve, reject) => {
try {
const account = client.database.getAccounts([user]);
resolve(account);
} catch (error) {
reject(error);
export const getOpenOrders = async (username) => {
try {
const rawData = await client.call('condenser_api', 'get_open_orders', [username]);
if (!rawData || !rawData.length) {
return [];
}
return rawData;
} catch (err) {
console.warn('Failed to get open orders', err);
return [];
}
};
/**
* fetchs all pending converstion requests that are yet to be fullfilled
* @param {string} account
* @returns {Promise<ConversionRequest[]>} array of conversion requests
*/
export const getConversionRequests = async (username) => {
try {
const rawData = await client.database.call('get_conversion_requests', [username]);
if (!rawData || !rawData.length) {
return [];
}
return rawData;
} catch (err) {
console.warn('Failed to get open orders', err);
return [];
}
};
/**
* fetchs all pending converstion requests that are yet to be fullfilled
* @param {string} account
* @returns {Promise<SavingsWithdrawRequest[]>} array of requested savings withdraw
*/
export const getSavingsWithdrawFrom = async (username) => {
try {
const rawData = await client.database.call('get_savings_withdraw_from', [username]);
if (!rawData || !rawData.length) {
return [];
}
return rawData;
} catch (err) {
console.warn('Failed to get open orders', err);
return [];
}
};
/**
* @method getAccount fetch raw account data without post processings
* @param username username
*/
export const getAccount = (username) =>
new Promise((resolve, reject) => {
client.database
.getAccounts([username])
.then((response) => {
if (response.length) {
resolve(response[0]);
}
throw new Error('Account not found, ' + JSON.stringify(response));
})
.catch((error) => {
reject(error);
});
});
export const getAccountHistory = (user, type_token) =>
export const getAccountHistory = (user, operations) =>
new Promise((resolve, reject) => {
const op = utils.operationOrders;
let wallet_operations_bitmask = utils.makeBitMaskFilter([
op.transfer, //HIVE
op.author_reward, //HBD, HP
op.curation_reward, //HP
op.transfer_to_vesting, //HIVE, HP
op.withdraw_vesting, //HIVE, HP
op.interest, //HP
op.transfer_to_savings, //HIVE, HBD
op.transfer_from_savings, //HIVE, HBD
op.fill_convert_request, //HBD
op.fill_order, //HIVE, HBD
op.claim_reward_balance, //HP
op.sps_fund, //HBD
op.comment_benefactor_reward, //HP
op.return_vesting_delegation, //HP
]);
let wallet_operations_bitmask = utils.makeBitMaskFilter(operations);
try {
const ah = client.call('condenser_api', 'get_account_history', [
user,

View File

@ -0,0 +1,113 @@
export interface Vote {
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;
}
export interface FeedHistory {
current_median_history: {
base: string;
quote: string;
};
}
export interface RewardFund {
recent_claims: string;
reward_balance: string;
}
export interface DelegatedVestingShare {
id: number;
delegatee: string;
delegator: string;
min_delegation_time: string;
vesting_shares: string;
}
export interface Follow {
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;
}
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
}
export interface OrdersDataItem {
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;
}
export interface OrdersData {
bids: OrdersDataItem[];
asks: OrdersDataItem[];
trading: OrdersDataItem[];
}
export interface ConversionRequest {
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;
}

View File

@ -163,15 +163,14 @@ export const isDefaultFooter = (payload) => ({
/**
* MW
*/
export const setCurrency = (currency) => (dispatch) => {
export const setCurrency = (currency) => async (dispatch) => {
const currencySymbol = getSymbolFromCurrency(currency);
getCurrencyRate(currency).then((currencyRate) =>
dispatch({
type: SET_CURRENCY,
payload: { currency, currencyRate, currencySymbol },
}),
);
const currencyRate = await getCurrencyRate(currency)
dispatch({
type: SET_CURRENCY,
payload: { currency, currencyRate, currencySymbol },
})
};
export const setPinCode = (data) => ({

View File

@ -0,0 +1,82 @@
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 { CoinActivitiesCollection, CoinBase, CoinData } from '../reducers/walletReducer';
import { AppDispatch, RootState } from '../store/store';
export const setSelectedCoins = (coins: CoinBase[]) => ({
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 setPriceHistory = (coinId: string, vsCurrency: string, data: number[]) => ({
payload: {
id: coinId,
vsCurrency,
data
},
type: SET_PRICE_HISTORY
})
export const setCoinActivities = (coinId: string, data: CoinActivitiesCollection) => ({
payload: {
id: coinId,
data,
},
type: SET_COIN_ACTIVITIES
})
export const resetWalletData = () => ({
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 },
})
})
}
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
))
}

View File

@ -113,3 +113,11 @@ export const DELETE_COMMENT_CACHE_ENTRY = 'DELETE_COMMENT_CACHE_ENTRY';
// TOOLTIPS
export const REGISTER_TOOLTIP = 'REGISTER_TOOLTIP';
//WALLET
export const SET_SELECTED_COINS = 'SET_SELECTED_COINS';
export const SET_COINS_DATA = 'SET_COIN_DATA';
export const SET_PRICE_HISTORY = 'SET_PRICE_HISTORY';
export const SET_COIN_ACTIVITIES = 'SET_COIN_ACTIVITIES';
export const SET_COIN_QUOTES = 'SET_COIN_QUOTES';
export const RESET_WALLET_DATA = 'RESET_WALLET_DATA';

View File

@ -10,6 +10,7 @@ import customTabsReducer from './customTabsReducer';
import editorReducer from './editorReducer';
import cacheReducer from './cacheReducer';
import walkthroughReducer from './walkthroughReducer';
import walletReducer from './walletReducer';
export default combineReducers({
account: accountReducer,
@ -23,4 +24,5 @@ export default combineReducers({
user,
cache: cacheReducer,
walkthrough: walkthroughReducer,
wallet: walletReducer,
});

View File

@ -0,0 +1,138 @@
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;
labelId:string;
}
export interface CoinBase {
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[];
}
export interface PriceHistory {
expiresAt:number;
vsCurrency:string;
data:number[];
}
export interface CoinActivity {
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;
}
export interface CoinActivitiesCollection {
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;
}
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
}
}
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_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_QUOTES:{
return {
...state,
quotes:payload,
}
}
default:
return state;
}
}
const ONE_HOUR_MS = 60 * 60 * 1000;

View File

@ -27,6 +27,8 @@ const transformWalkthroughMap = createTransform(
{whitelist:['walkthrough']}
);
// Middleware: Redux Persist Config
const persistConfig = {
// Root
@ -36,7 +38,10 @@ const persistConfig = {
// Blacklist (Don't Save Specific Reducers)
blacklist: ['nav', 'application', 'communities', 'user'],
timeout: 0,
transforms:[transformCacheVoteMap,transformWalkthroughMap],
transforms:[
transformCacheVoteMap,
transformWalkthroughMap
]
};
// Middleware: Redux Persist Persisted Reducer
@ -58,4 +63,4 @@ export { store, persistor };
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
export type AppDispatch = typeof store.dispatch

View File

@ -16,7 +16,6 @@ import PushNotification from 'react-native-push-notification';
import VersionNumber from 'react-native-version-number';
import ReceiveSharingIntent from 'react-native-receive-sharing-intent';
import Matomo from 'react-native-matomo-sdk';
import uniqueId from 'react-native-unique-id';
// Constants
import AUTH_TYPE from '../../../constants/authType';
@ -41,6 +40,7 @@ import {
setLastUpdateCheck,
} from '../../../realm/realm';
import { getUser, getPost, getDigitPinCode, getMutes } from '../../../providers/hive/dhive';
import { getUser as getEcencyUser } from '../../../providers/ecency/ePoint';
import {
migrateToMasterKeyWithAccessToken,
refreshSCToken,
@ -51,6 +51,7 @@ import {
markActivityAsRead,
markNotifications,
getUnreadNotificationCount,
getLatestQuotes,
} from '../../../providers/ecency/ecency';
import { fetchLatestAppVersion } from '../../../providers/github/github';
import { navigate } from '../../../navigation/service';
@ -95,6 +96,7 @@ import {
updateActiveBottomTab,
} from '../../../redux/actions/uiAction';
import { setFeedPosts, setInitPosts } from '../../../redux/actions/postsAction';
import { fetchCoinQuotes } from '../../../redux/actions/walletActions';
import { encryptKey } from '../../../utils/crypto';
@ -619,6 +621,7 @@ class ApplicationContainer extends Component {
_refreshGlobalProps = () => {
const { actions } = this.props;
actions.fetchGlobalProperties();
actions.fetchCoinQuotes();
};
_getUserDataFromRealm = async () => {
@ -759,6 +762,7 @@ class ApplicationContainer extends Component {
accountData.unread_activity_count = await getUnreadNotificationCount();
accountData.mutes = await getMutes(realmObject.username);
accountData.ecencyUserData = await getEcencyUser(realmObject.username);
dispatch(updateCurrentAccount(accountData));
this._connectNotificationServer(accountData.name);
@ -812,9 +816,7 @@ class ApplicationContainer extends Component {
}
if (settings.nsfw !== '') dispatch(setNsfw(settings.nsfw));
if (settings.currency !== '') {
dispatch(setCurrency(settings.currency !== '' ? settings.currency : 'usd'));
}
await dispatch(setCurrency(settings.currency !== '' ? settings.currency : 'usd'));
}
};
@ -941,6 +943,7 @@ class ApplicationContainer extends Component {
_currentAccount.unread_activity_count = await getUnreadNotificationCount();
_currentAccount.mutes = await getMutes(_currentAccount.username);
_currentAccount.ecencyUserData = await getEcencyUser(_currentAccount.username);
dispatch(updateCurrentAccount(_currentAccount));
};
@ -1068,6 +1071,7 @@ export default connect(
...bindActionCreators(
{
fetchGlobalProperties,
fetchCoinQuotes,
},
dispatch,
),

View File

@ -0,0 +1,56 @@
import React, { ComponentType, JSXElementConstructor, ReactElement } from 'react'
import { useIntl } from 'react-intl';
import { SectionList, Text } from 'react-native';
import { Transaction } from '../../../components';
import { RefreshControl, RefreshControlProps } from '../../../components/atoms';
import { CoinActivity } from '../../../redux/reducers/walletReducer';
import styles from './children.styles';
interface ActivitiesListProps {
header: ComponentType<any> | ReactElement<any, string | JSXElementConstructor<any>>
pendingActivities: CoinActivity[];
completedActivities: CoinActivity[];
refreshControlProps:RefreshControlProps
}
const ActivitiesList = ({ header, completedActivities, pendingActivities, refreshControlProps }: ActivitiesListProps) => {
const intl = useIntl();
const _renderActivityItem = ({ item, index }) => {
return <Transaction item={item} index={index} />
}
const sections = [];
if (pendingActivities && pendingActivities.length) {
sections.push({
title: intl.formatMessage({id:'wallet.pending_requests'}),
data: pendingActivities
})
}
sections.push({
title: intl.formatMessage({id:'wallet.activities'}),
data: completedActivities || []
})
return (
<SectionList
style={styles.list}
contentContainerStyle={styles.listContent}
sections={sections}
renderItem={_renderActivityItem}
keyExtractor={(item, index) => `activity_item_${index}_${item.created}`}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.textActivities}>{title}</Text>
)}
ListHeaderComponent={header}
refreshControl={<RefreshControl {...refreshControlProps}/>}
/>
)
}
export default ActivitiesList

View File

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

View File

@ -0,0 +1,38 @@
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'
import styles from './children.styles'
interface CoinActionsProps {
actions: string[];
onActionPress: (action: string) => void;
}
export const CoinActions = withNavigation(({ actions, onActionPress }: CoinActionsProps) => {
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>
)
}
return (
<View style={styles.actionsContainer}>
{actions.map(_renderItem)}
</View>
)
});

View File

@ -0,0 +1,61 @@
import React, { Fragment } from 'react'
import { useIntl } from 'react-intl';
import { View, Text } from 'react-native'
import { DataPair } from '../../../redux/reducers/walletReducer'
import styles from './children.styles'
interface CoinBasicsProps {
valuePairs: DataPair[];
extraData: DataPair[];
coinSymbol: string;
percentChange: number;
}
export const CoinBasics = ({ valuePairs, extraData, coinSymbol, percentChange }: 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>
</>
)
const _renderValuePair = (args: DataPair, index: number) => {
const label = intl.formatMessage({ id: `wallet.${args.labelId}` })
return (
<Fragment key={`basic-data-${args.labelId}-${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.labelId}` })
return (
<View key={`extra-data-${args.labelId}-${index}`} style={styles.extraDataContainer}>
<Text style={styles.textExtraLabel}>{label}</Text>
<Text style={styles.textExtraValue}>{args.value}</Text>
</View>
)
}
return (
<View style={[styles.card, styles.basicsContainer]}>
{_renderCoinHeader}
{valuePairs.map(_renderValuePair)}
{extraData && extraData.map(_renderExtraData)}
</View>
)
}

View File

@ -0,0 +1,53 @@
import React, { useState, useEffect } from 'react'
import { View, Dimensions } from 'react-native'
import { RangeSelector } from '.';
import { SimpleChart } from '../../../components'
import { useAppSelector } from '../../../hooks';
import { fetchMarketChart } from '../../../providers/coingecko/coingecko';
import styles, { CHART_NEGATIVE_MARGIN } from './children.styles';
interface CoinChartProps {
coinId:string;
}
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 _onRangeChange = (range) => {
setRange(range);
_fetchMarketData(range);
}
const _renderGraph = () => {
const _baseWidth = Dimensions.get("window").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>
)}
return (
<>
<View style={styles.card}>
{_renderGraph()}
</View>
<RangeSelector range={range} onRangeChange={_onRangeChange} />
</>
)
}

View File

@ -0,0 +1,66 @@
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;
}
export const CoinSummary = ({
coinSymbol,
id,
coinData,
percentChagne,
onActionPress,
}:CoinSummaryProps) => {
const {
balance,
estimateValue,
savings,
extraDataPairs,
actions
} = coinData
const valuePairs = [
{
labelId:'amount_desc',
value:balance
}
] as DataPair[]
if(estimateValue !== undefined){
valuePairs.push({
labelId:'estimated_value',
value:<FormattedCurrency isApproximate isToken value={estimateValue} />,
})
}
if(savings !== undefined){
valuePairs.push({
labelId:'savings',
value:savings
})
}
return (
<View>
<CoinBasics
valuePairs={valuePairs}
extraData={extraDataPairs}
coinSymbol={coinSymbol}
percentChange={percentChagne}
/>
<CoinActions actions={actions} onActionPress={onActionPress}/>
{
id !== COIN_IDS.ECENCY && id !== COIN_IDS.HP && <CoinChart coinId={id} />
}
</View>
)
}

View File

@ -0,0 +1,6 @@
export * from './coinBasics';
export * from './coinChart';
export * from './rangeSelector';
export * from './coinSummary';
export * from './activitiesList';
export * from './coinActions';

View File

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

View File

@ -0,0 +1,3 @@
import CoinDetails from './screen/coinDetailsScreen';
export default CoinDetails;

View File

@ -0,0 +1,160 @@
import { View, Text, Alert, AppState, AppStateStatus } from 'react-native'
import React, { useEffect, useRef, useState } from 'react'
import { BasicHeader, Transaction } from '../../../components'
import { CoinSummary } from '../children'
import styles from './screen.styles';
import ActivitiesList from '../children/activitiesList'
import { withNavigation } from 'react-navigation'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { CoinActivitiesCollection, CoinActivity, CoinData, QuoteItem } from '../../../redux/reducers/walletReducer';
import { fetchCoinActivities } from '../../../utils/wallet';
import { fetchAndSetCoinsData, setCoinActivities } from '../../../redux/actions/walletActions';
import { openPinCodeModal } from '../../../redux/actions/applicationActions';
import { navigate } from '../../../navigation/service';
import ROUTES from '../../../constants/routeNames';
import { COIN_IDS } from '../../../constants/defaultCoins';
import { useIntl } from 'react-intl';
export interface CoinDetailsScreenParams {
coinId:string;
}
interface CoinDetailsScreenProps {
navigation:any
}
const CoinDetailsScreen = ({navigation}:CoinDetailsScreenProps) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const coinId = navigation.getParam('coinId');
if(!coinId){
throw new Error("Coin symbol must be passed")
}
//refs
const appState = useRef(AppState.currentState);
//redux props
const currentAccount = useAppSelector(state=>state.account.currentAccount);
const globalProps = useAppSelector(state=>state.account.globalProps);
const selectedCoins = useAppSelector(state=>state.wallet.selectedCoins);
const coinData:CoinData = useAppSelector(state=>state.wallet.coinsData[coinId]);
const quote:QuoteItem = useAppSelector(state=>state.wallet.quotes[coinId]);
const coinActivities:CoinActivitiesCollection = useAppSelector(state=>state.wallet.coinsActivities[coinId]);
const isPinCodeOpen = useAppSelector(state=>state.application.isPinCodeOpen);
//state
const [symbol] = useState(selectedCoins.find((item)=>item.id===coinId).symbol);
const [refreshing, setRefreshing] = useState(false);
//side-effects
useEffect(()=>{
_fetchDetails();
AppState.addEventListener('change', _handleAppStateChange);
return _cleanup;
}, [])
const _cleanup = () => {
AppState.removeEventListener('change', _handleAppStateChange);
}
const _handleAppStateChange = (nextAppState:AppStateStatus) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
console.log("updating coins activities on app resume", coinId)
_fetchDetails();
}
appState.current = nextAppState;
}
const _fetchDetails = async (refresh = false) => {
if(refresh){
setRefreshing(refresh);
dispatch(fetchAndSetCoinsData(refresh));
}
const _activites = await fetchCoinActivities(currentAccount.name, coinId, symbol, globalProps);
dispatch(setCoinActivities(coinId, _activites));
setRefreshing(false);
}
if(!coinData){
Alert.alert("Invalid coin data");
navigation.goBack();
}
const _onActionPress = (transferType:string) => {
let navigateTo = ROUTES.SCREENS.TRANSFER
let navigateParams = {};
if(coinId === COIN_IDS.ECENCY && transferType !== 'dropdown_transfer'){
navigateTo = ROUTES.SCREENS.REDEEM;
navigateParams = {
balance:coinData.balance,
redeemType:transferType === 'dropdown_promote'?'promote':'boost',
}
} else {
const balance = transferType === 'withdraw_hive' || transferType === 'withdraw_hbd'
? coinData.savings : coinData.balance;
navigateParams = {
transferType:coinId === COIN_IDS.ECENCY?'points':transferType,
fundType:coinId === COIN_IDS.ECENCY?'ESTM':symbol,
balance
};
}
if (isPinCodeOpen) {
dispatch(
openPinCodeModal({
navigateTo,
navigateParams,
}),
);
} else {
navigate({
routeName: navigateTo,
params: navigateParams
});
}
}
const _onRefresh = () => {
_fetchDetails(true);
}
const _renderHeaderComponent = (
<CoinSummary
id={coinId}
coinSymbol={symbol}
coinData={coinData}
percentChagne={quote.percentChange || 0}
onActionPress={_onActionPress} />
)
return (
<View style={styles.container}>
<BasicHeader title={intl.formatMessage({id:'wallet.coin_details'})} />
<ActivitiesList
header={_renderHeaderComponent}
completedActivities={coinActivities?.completed || []}
pendingActivities={coinActivities?.pending || []}
refreshControlProps={{
refreshing,
onRefresh:_onRefresh
}}
/>
</View>
)
}
export default withNavigation(CoinDetailsScreen)

View File

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

View File

@ -27,6 +27,7 @@ import TagResult from './tagResult';
import { Community } from './community';
import Communities from './communities';
import ReferScreen from './referScreen/referScreen';
import CoinDetails from './coinDetails';
export {
Bookmarks,
@ -58,4 +59,5 @@ export {
Communities,
WebBrowser,
ReferScreen,
CoinDetails,
};

View File

@ -36,6 +36,7 @@ import {
} from '../../../realm/realm';
import { updateCurrentAccount, removeOtherAccount } from '../../../redux/actions/accountAction';
import { getDigitPinCode, getMutes, getUser } from '../../../providers/hive/dhive';
import { getUser as getEcencyUser } from '../../../providers/ecency/ePoint';
// Utils
import { encryptKey, decryptKey } from '../../../utils/crypto';
@ -319,6 +320,7 @@ class PinCodeContainer extends Component {
//get unread notifications
_currentAccount.unread_activity_count = await getUnreadNotificationCount();
_currentAccount.mutes = await getMutes(_currentAccount.username);
_currentAccount.ecencyUserData = await getEcencyUser(_currentAccount.username);
dispatch(updateCurrentAccount({ ..._currentAccount }));
dispatch(closePinCodeModal());
}

View File

@ -43,7 +43,7 @@ export default EStyleSheet.create({
borderWidth: 1,
borderColor: '$borderColor',
borderRadius: 8,
paddingHorizontal: 10,
paddingHorizontal: 12,
color: '$primaryBlack',
width: 172,
minHeight: 35,
@ -102,7 +102,7 @@ export default EStyleSheet.create({
},
dropdownText: {
fontSize: 14,
paddingLeft: 16,
paddingLeft: 12,
paddingHorizontal: 14,
color: '$primaryDarkGray',
},
@ -118,7 +118,6 @@ export default EStyleSheet.create({
height: 44,
width: '100%',
borderRadius: 8,
marginHorizontal: 2,
},
dropdown: {
flexGrow: 1,

View File

@ -0,0 +1,125 @@
import { TextStyle, ViewStyle } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import { ImageStyle } from 'react-native-fast-image';
export default EStyleSheet.create({
cardContainer: {
backgroundColor:'$primaryLightBackground',
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 12,
overflow: 'hidden',
borderWidth: 2,
borderColor: "$primaryLightBackground"
} as ViewStyle,
cardHeader:{
flexDirection:'row',
alignItems:'center',
paddingHorizontal: 16,
paddingTop:16,
} as ViewStyle,
cardTitleContainer:{
marginHorizontal: 8,
flex:1,
} as ViewStyle,
cardValuesContainer:{
marginHorizontal: 8,
justifyContent:'flex-end'
} as ViewStyle,
claimContainer:{
flexDirection:'row',
justifyContent:'center',
alignItems:'center',
marginTop:8
} as ViewStyle,
claimBtn:{
flexDirection: 'row',
paddingHorizontal: 16
} as ViewStyle,
claimBtnTitle:{
color: '$pureWhite',
fontSize: 14,
fontWeight: 'bold',
alignSelf: 'center',
} as ViewStyle,
claimIconWrapper: {
backgroundColor: '$pureWhite',
justifyContent: 'center',
alignSelf: 'center',
alignItems: 'center',
borderRadius: 20,
marginLeft: 20,
width: 24,
height: 24,
} as ViewStyle,
logo:{
height:24,
width:24,
borderRadius:12,
backgroundColor:'$primaryBlue',
} as ImageStyle,
menuIcon:{
color: '$primaryBlue',
paddingLeft:12,
} as ViewStyle,
header: {
backgroundColor: '$primaryBackgroundColor',
},
dotStyle: {
backgroundColor: '$primaryDarkText',
},
chartContainer:{
height:112
},
cardFooter:{
position:'absolute',
bottom:8,
left:76,
right:16,
paddingTop:8,
flexDirection:'row',
justifyContent:'space-between',
borderColor:'$chartText',
borderTopWidth:EStyleSheet.hairlineWidth,
} as ViewStyle,
textDiffPositive:{
fontSize:18,
color: '$primaryGreen'
} as TextStyle,
textDiffNegative:{
fontSize:16,
color: '$primaryRed'
} as TextStyle,
textCurValue:{
fontSize:16,
color: '$primaryBlack',
fontWeight: '300',
} as TextStyle,
textTitle:{
fontSize:16,
color: '$primaryBlack',
fontWeight: '500'
},
textSubtitle:{
fontSize:14,
color: '$primaryDarkText',
fontWeight: '300',
} as TextStyle,
textSubtitleRight:{
fontSize:14,
color: '$primaryDarkText',
fontWeight: '300',
textAlign:'right'
} as TextStyle
});

View File

@ -0,0 +1,146 @@
import { View, Text, Dimensions, TouchableOpacity } from 'react-native';
import React, { ComponentType, Fragment, useEffect, useState } from 'react';
import styles from './children.styles';
import { Icon, MainButton, SimpleChart } from '../../../components';
import { useIntl } from 'react-intl';
import EStyleSheet from 'react-native-extended-stylesheet';
export interface CoinCardProps {
id: string;
chartData: number[];
name: string;
notCrypto: boolean;
symbol: string;
currencySymbol: string;
changePercent: number;
currentValue: number;
ownedTokens: number;
unclaimedRewards: string;
enableBuy?: boolean;
isClaiming?: boolean;
footerComponent: ComponentType<any>
onCardPress: () => void;
onClaimPress: () => void;
}
export const CoinCard = ({
id,
notCrypto,
chartData,
name,
currencySymbol,
symbol,
changePercent,
currentValue,
ownedTokens,
footerComponent,
unclaimedRewards,
enableBuy,
isClaiming,
onCardPress,
onClaimPress,
}: CoinCardProps) => {
const intl = useIntl();
const [claimExpected, setClaimExpected] = useState(false);
useEffect(() => {
if (!isClaiming && claimExpected) {
setClaimExpected(false);
}
}, [isClaiming])
const _onClaimPress = () => {
setClaimExpected(unclaimedRewards ? true : false)
onClaimPress();
}
const _renderHeader = (
<View style={styles.cardHeader}>
{/* <View style={styles.logo} /> */}
<View style={styles.cardTitleContainer}>
<Text style={styles.textTitle} >{symbol}</Text>
<Text style={styles.textSubtitle}>{intl.formatMessage({ id: `wallet.${id}.name` })}</Text>
</View>
<View style={styles.cardValuesContainer}>
<Text
style={styles.textTitle}>
{`${ownedTokens.toFixed(3)} ${symbol}`}
</Text>
<Text style={styles.textSubtitleRight}>
{`${(ownedTokens * currentValue).toFixed(2)}${currencySymbol}`}
</Text>
</View>
</View>
);
const _renderClaimSection = () => {
if (unclaimedRewards || enableBuy) {
const btnTitle = unclaimedRewards
? unclaimedRewards
: intl.formatMessage({ id: `wallet.${id}.buy` });
return (
<View style={styles.claimContainer}>
<MainButton
isLoading={isClaiming && claimExpected}
isDisable={isClaiming && claimExpected}
style={styles.claimBtn}
height={50}
onPress={_onClaimPress}
>
<Fragment>
<Text style={styles.claimBtnTitle}>
{btnTitle}
</Text>
<View style={styles.claimIconWrapper}>
<Icon name="add" iconType="MaterialIcons" color={EStyleSheet.value('$primaryBlue')} size={23} />
</View>
</Fragment>
</MainButton>
</View>
)
}
}
const _renderGraph = () => {
const _baseWidth = Dimensions.get("window").width - 32;
return (
<View style={styles.chartContainer}>
<SimpleChart
data={chartData}
baseWidth={_baseWidth}
showLine={false}
chartHeight={130}
/>
</View>
)
}
const _renderFooter = (
<View style={styles.cardFooter}>
<Text style={styles.textCurValue}>{`${currencySymbol} ${currentValue.toFixed(2)}`}</Text>
<Text style={changePercent > 0 ? styles.textDiffPositive : styles.textDiffNegative}>{`${changePercent >= 0 ? '+' : ''}${changePercent.toFixed(1)}%`}</Text>
</View>
)
return (
<TouchableOpacity onPress={onCardPress} >
<View style={styles.cardContainer}>
{_renderHeader}
{_renderClaimSection()}
{!notCrypto && _renderGraph()}
{!notCrypto ? _renderFooter : <View style={{ height: 12 }} />}
{footerComponent && footerComponent}
</View>
</TouchableOpacity>
);
};

View File

@ -0,0 +1 @@
export * from './coinCard';

View File

@ -60,7 +60,7 @@ const HpView = ({ handleOnSelected, index, currentIndex, refreshing: reload }) =
value: <FormattedCurrency isApproximate isToken value={estimatedHpValue} />,
},
{
textKey: 'estimated_amount',
textKey: 'vote_value',
value: <FormattedCurrency isApproximate value={estimatedAmount} />,
},
]}

View File

@ -1,192 +0,0 @@
/* eslint-disable react/jsx-wrap-multilines */
import React, { Fragment, useState, useEffect } from 'react';
import Swiper from 'react-native-swiper';
import { SafeAreaView, View, RefreshControl, Text } from 'react-native';
// Containers
import { FlatList } from 'react-native-gesture-handler';
import { useIntl } from 'react-intl';
import { LoggedInContainer } from '../../../containers';
// Components
import {
Header,
Transaction,
HorizontalIconList,
ListPlaceHolder,
CollapsibleCard,
} from '../../../components';
import EstmView from './estmView';
import HiveView from './hiveView';
import HpView from './hpView';
import HbdView from './hbdView';
// Styles
import globalStyles from '../../../globalStyles';
import styles from './walletScreenStyles';
import POINTS, { POINTS_KEYS } from '../../../constants/options/points';
import { useAppSelector } from '../../../hooks';
const HEADER_EXPANDED_HEIGHT = 312;
const WalletScreen = () => {
const intl = useIntl();
const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme);
const [selectedUserActivities, setSelectedUserActivities] = useState(null);
const [filteredActivites, setFilteredActivites] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [currentIndex, setCurrentIndex] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const [isExpanded, setIsExpanded] = useState(true);
useEffect(() => {
if (selectedUserActivities) {
const activities = selectedUserActivities
? selectedUserActivities.filter((item) => {
return (
item &&
item.value &&
item.value.indexOf(['Points', 'HIVE', 'HBD', 'HP'][currentIndex]) > -1
);
})
: [];
setFilteredActivites(activities);
} else {
setFilteredActivites([]);
}
}, [selectedUserActivities]);
const _handleSwipeItemChange = (userActivities, _isLoading) => {
setSelectedUserActivities(userActivities);
setIsLoading(_isLoading);
setRefreshing(false);
};
const _renderHeaderComponent = () => {
return (
<>
<CollapsibleCard
expanded={true}
isExpanded={isExpanded}
noContainer
noBorder
locked
titleComponent={<View />}
handleOnExpanded={() => {
setIsExpanded(true);
}}
>
<View style={[styles.header, { height: HEADER_EXPANDED_HEIGHT }]}>
<Swiper
loop={false}
showsPagination={true}
index={0}
dotStyle={styles.dotStyle}
onIndexChanged={(index) => setCurrentIndex(index)}
>
<EstmView
index={0}
handleOnSelected={_handleSwipeItemChange}
refreshing={refreshing}
currentIndex={currentIndex}
/>
<HiveView
index={1}
handleOnSelected={_handleSwipeItemChange}
refreshing={refreshing}
currentIndex={currentIndex}
/>
<HbdView
index={2}
handleOnSelected={_handleSwipeItemChange}
refreshing={refreshing}
currentIndex={currentIndex}
/>
<HpView
index={3}
refreshing={refreshing}
handleOnSelected={_handleSwipeItemChange}
currentIndex={currentIndex}
/>
</Swiper>
</View>
</CollapsibleCard>
<SafeAreaView style={styles.header}>
{currentIndex === 0 && <HorizontalIconList options={POINTS} optionsKeys={POINTS_KEYS} />}
</SafeAreaView>
</>
);
};
const _renderItem = ({ item, index }) => {
return <Transaction type={currentIndex} item={item} index={index} />;
};
const _renderLoading = () => {
if (isLoading) {
return <ListPlaceHolder />;
}
return (
<Text style={globalStyles.hintText}>{intl.formatMessage({ id: 'wallet.no_activity' })}</Text>
);
};
const _refreshControl = (
<RefreshControl
refreshing={refreshing}
onRefresh={() => setRefreshing(true)}
progressBackgroundColor="#357CE6"
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
titleColor="#fff"
colors={['#fff']}
/>
);
const _onScroll = (evt) => {
console.log(evt);
const expanded = evt.nativeEvent.contentOffset.y < 10;
if (isExpanded !== expanded) {
setIsExpanded(expanded);
}
};
return (
<Fragment>
<Header />
<SafeAreaView style={globalStyles.defaultContainer}>
<LoggedInContainer>
{() => (
<>
{_renderHeaderComponent()}
<View style={globalStyles.listWrapper}>
<FlatList
data={filteredActivites}
style={globalStyles.tabBarBottom}
ListEmptyComponent={_renderLoading}
renderItem={_renderItem}
keyExtractor={(item, index) => index.toString()}
maxToRenderPerBatch={5}
initialNumToRender={5}
refreshControl={_refreshControl}
windowSize={5}
onScroll={_onScroll}
onScrollToTop={() => {
setIsExpanded(true);
}}
/>
</View>
</>
)}
</LoggedInContainer>
</SafeAreaView>
</Fragment>
);
};
export default WalletScreen;
/* eslint-enable */

View File

@ -0,0 +1,290 @@
/* eslint-disable react/jsx-wrap-multilines */
import React, { Fragment, useState, useEffect, useRef } from 'react';
import { SafeAreaView, View, RefreshControl, Text, Alert, AppState, AppStateStatus } from 'react-native';
// Containers
import { FlatList } from 'react-native-gesture-handler';
import { useIntl } from 'react-intl';
import { LoggedInContainer } from '../../../containers';
// Components
import {
Header,
HorizontalIconList,
PostCardPlaceHolder,
} from '../../../components';
// Styles
import globalStyles from '../../../globalStyles';
import styles from './walletScreenStyles';
import { useAppDispatch, useAppSelector } from '../../../hooks';
import {CoinCard} from '../children';
import { fetchMarketChart, INTERVAL_HOURLY } from '../../../providers/coingecko/coingecko';
import { withNavigation } from 'react-navigation';
import ROUTES from '../../../constants/routeNames';
import { CoinDetailsScreenParams } from '../../coinDetails/screen/coinDetailsScreen';
import POINTS, { POINTS_KEYS } from '../../../constants/options/points';
import { CoinBase, CoinData } from '../../../redux/reducers/walletReducer';
import { fetchAndSetCoinsData, fetchCoinQuotes, resetWalletData, setPriceHistory } from '../../../redux/actions/walletActions';
import { COIN_IDS } from '../../../constants/defaultCoins';
import { claimPoints } from '../../../providers/ecency/ePoint';
import { claimRewardBalance, getAccount } from '../../../providers/hive/dhive';
import { toastNotification } from '../../../redux/actions/uiAction';
import moment from 'moment';
const CHART_DAYS_RANGE = 1;
const WalletScreen = ({navigation}) => {
const intl = useIntl();
const dispatch = useAppDispatch();
//refs
const appState = useRef(AppState.currentState);
//redux
const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme);
const currency = useAppSelector((state)=>state.application.currency);
const {
selectedCoins,
priceHistories,
coinsData,
updateTimestamp,
quotes,
...wallet
} = useAppSelector((state)=>state.wallet);
const currentAccount = useAppSelector((state)=>state.account.currentAccount);
const pinHash = useAppSelector((state)=>state.application.pin);
//state
const [isLoading, setIsLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [isClaiming, setIsClaiming] = useState(false);
//side-effects
useEffect(()=>{
AppState.addEventListener('change', _handleAppStateChange);
_fetchData();
return _cleanup;
},[])
useEffect(()=>{
if(currency.currency !== wallet.vsCurrency || currentAccount.username !== wallet.username ){
dispatch(resetWalletData());
_fetchData(true);
}
},[currency, currentAccount])
const _cleanup = () => {
AppState.removeEventListener('change', _handleAppStateChange);
}
//actions
const _handleAppStateChange = (nextAppState:AppStateStatus) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
console.log('updating selected coins data on app resume')
_fetchCoinsData(true)
}
appState.current = nextAppState;
};
const _fetchData = (refresh?:boolean) => {
if(!isLoading){
_fetchPriceHistory();
_fetchCoinsData(refresh);
}
}
const _fetchPriceHistory = () => {
selectedCoins.forEach(async (token:CoinBase)=>{
const expiresAt = priceHistories[token.id]?.expiresAt || 0;
const curTime = new Date().getTime();
if(!token.notCrypto && curTime > expiresAt ){
const marketChart = await fetchMarketChart(token.id, currency.currency, CHART_DAYS_RANGE, INTERVAL_HOURLY);
const priceData = marketChart.prices.map(item=>item.yValue);
dispatch(setPriceHistory(token.id, currency.currency, priceData));
}
})
}
const _fetchCoinsData = async (refresh?:boolean) => {
setIsLoading(true);
if(refresh || !quotes){
dispatch(fetchCoinQuotes());
}
await dispatch(fetchAndSetCoinsData(refresh));
setRefreshing(false);
setIsLoading(false);
}
const _claimEcencyPoints = async () => {
setIsClaiming(true);
try{
await claimPoints()
await _fetchCoinsData(true);
}catch(error){
Alert.alert(`${error.message}\nTry again or write to support@ecency.com`);
}
setIsClaiming(false);
};
const _claimRewardBalance = async () => {
setIsClaiming(true);
try{
const account = await getAccount(currentAccount.name);
await claimRewardBalance(
currentAccount,
pinHash,
account.reward_hive_balance,
account.reward_hbd_balance,
account.reward_vesting_balance,
)
await _fetchCoinsData(true);
dispatch(
toastNotification(
intl.formatMessage({
id: 'alert.claim_reward_balance_ok',
}),
),
);
}catch(error){
Alert.alert(intl.formatMessage(
{id:'alert.claim_failed'},
{message:error.message}
));
}
setIsClaiming(false);
}
const _claimRewards = (coinId:string) => {
if(isLoading){
setRefreshing(true);
Alert.alert(intl.formatMessage({id:'alert.wallet_updating'}) );
return;
}
switch(coinId){
case COIN_IDS.ECENCY:
_claimEcencyPoints();
break;
case COIN_IDS.HP:
_claimRewardBalance()
break;
}
}
const _renderItem = ({ item, index }:{item:CoinBase, index:number}) => {
const coinData:CoinData = coinsData[item.id] || {};
const _tokenMarketData:number[] = priceHistories[item.id] ? priceHistories[item.id].data : [];
const _balance = coinData.balance + (coinData.savings || 0);
const quote = quotes ? quotes[item.id] : {};
const _onCardPress = () => {
navigation.navigate(ROUTES.SCREENS.COIN_DETAILS, {
coinId:item.id
} as CoinDetailsScreenParams)
}
const _onClaimPress = () => {
if(coinData.unclaimedBalance){
_claimRewards(item.id);
} else if(item.id === COIN_IDS.ECENCY) {
navigation.navigate(ROUTES.SCREENS.BOOST)
}
}
return (
<CoinCard
chartData={_tokenMarketData || []}
currentValue={quote.price || 0}
changePercent={quote.percentChange || 0}
currencySymbol={currency.currencySymbol}
ownedTokens={_balance}
unclaimedRewards={coinData.unclaimedBalance}
enableBuy={!coinData.unclaimedBalance && item.id === COIN_IDS.ECENCY}
isClaiming={isClaiming}
onCardPress={_onCardPress}
onClaimPress={_onClaimPress}
footerComponent={index === 0 && <HorizontalIconList options={POINTS} optionsKeys={POINTS_KEYS} />}
{...item} />
);
};
const _renderHeader = () => {
return (
<View style={styles.header}>
<Text style={styles.lastUpdateText}>
{isLoading
? intl.formatMessage({id:'wallet.updating'})
:`${intl.formatMessage({id:'wallet.last_updated'})} ${moment(updateTimestamp).format('HH:mm:ss')}`}
</Text>
</View>
)
}
const _refreshControl = (
<RefreshControl
refreshing={refreshing}
onRefresh={() => {_fetchData(true); setRefreshing(true)}}
progressBackgroundColor="#357CE6"
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
titleColor="#fff"
colors={['#fff']}
/>
);
return (
<Fragment>
<Header />
<SafeAreaView style={globalStyles.defaultContainer}>
<LoggedInContainer>
{() => (
<View style={styles.listWrapper}>
<FlatList
data={updateTimestamp ? selectedCoins : []}
extraData={[coinsData, priceHistories]}
style={globalStyles.tabBarBottom}
ListEmptyComponent={<PostCardPlaceHolder />}
ListHeaderComponent={_renderHeader}
renderItem={_renderItem}
keyExtractor={(item, index) => index.toString()}
refreshControl={_refreshControl}
/>
</View>
)}
</LoggedInContainer>
</SafeAreaView>
</Fragment>
);
};
export default withNavigation(WalletScreen);
/* eslint-enable */

View File

@ -5,9 +5,18 @@ export default EStyleSheet.create({
padding: 0,
},
header: {
backgroundColor: '$primaryBackgroundColor',
alignItems: 'flex-end',
paddingHorizontal: 16,
},
lastUpdateText: {
color: '$iconColor',
fontSize: 10,
},
dotStyle: {
backgroundColor: '$primaryDarkText',
},
listWrapper: {
backgroundColor: '$primaryBackgroundColor',
flex: 1,
},
});

View File

@ -17,6 +17,7 @@ export default {
$primaryDarkGray: '#c1c5c7',
$primaryLightGray: '#f6f6f6',
$primaryRed: '#e63535',
$primaryGreen: '#4FD688',
$companyRed: '#c10000',
$primaryBlack: '#c1c5c7',
$primaryDarkText: '#526d91',
@ -42,6 +43,9 @@ export default {
$noConnectionColor: '#788187',
$borderedButtonBlue: '#5CCDFF',
$chartBlue: '#357CE6',
$chartText: '#f5f5f5',
// Devices Sizes
$deviceHeight:
Platform.OS === 'ios'

View File

@ -17,6 +17,7 @@ export default {
$primaryDarkGray: '#788187',
$primaryLightGray: '#f6f6f6',
$primaryRed: '#e63535',
$primaryGreen: '#4FD688',
$companyRed: '#c10000',
$primaryBlack: '#3c4449',
$primaryDarkText: '#788187',
@ -42,6 +43,9 @@ export default {
$noConnectionColor: '#788187',
$borderedButtonBlue: '#357ce6',
$chartBlue: '#357CE6',
$chartText: '#357ce6',
// Devices Sizes
$deviceHeight:
Platform.OS === 'ios'

View File

@ -0,0 +1,33 @@
export const getCurrentHpApr = (gprops) => {
// The inflation was set to 9.5% at block 7m
const initialInflationRate = 9.5;
const initialBlock = 7000000;
// It decreases by 0.01% every 250k blocks
const decreaseRate = 250000;
const decreasePercentPerIncrement = 0.01;
// How many increments have happened since block 7m?
const headBlock = gprops.headBlock;
const deltaBlocks = headBlock - initialBlock;
const decreaseIncrements = deltaBlocks / decreaseRate;
// Current inflation rate
let currentInflationRate =
initialInflationRate -
decreaseIncrements * decreasePercentPerIncrement;
// Cannot go lower than 0.95%
if (currentInflationRate < 0.95) {
currentInflationRate = 0.95;
}
// Now lets calculate the "APR"
const vestingRewardPercent = gprops.vestingRewardPercent / 10000;
const virtualSupply = gprops.virtualSupply;
const totalVestingFunds = gprops.totalVestingFund;
return (
(virtualSupply * currentInflationRate * vestingRewardPercent) / totalVestingFunds
);
};

View File

@ -1,288 +0,0 @@
import get from 'lodash/get';
import parseDate from './parseDate';
import parseToken from './parseToken';
import { vestsToHp } from './conversions';
import { getFeedHistory, getAccount, getAccountHistory } from '../providers/hive/dhive';
import { getCurrencyTokenRate } from '../providers/ecency/ecency';
export const transferTypes = [
'curation_reward',
'author_reward',
'comment_benefactor_reward',
'claim_reward_balance',
'transfer',
'transfer_to_savings',
'transfer_from_savings',
'transfer_to_vesting',
'withdraw_vesting',
'fill_order',
'escrow_transfer',
'escrow_dispute',
'escrow_release',
'escrow_approve',
'delegate_vesting_shares',
'cancel_transfer_from_savings',
'fill_convert_request',
'fill_transfer_from_savings',
'fill_vesting_withdraw',
];
export const groomingTransactionData = (transaction, hivePerMVests) => {
if (!transaction || !hivePerMVests) {
return [];
}
const result = {
iconType: 'MaterialIcons',
};
[result.textKey] = transaction[1].op;
const opData = transaction[1].op[1];
const { timestamp } = transaction[1];
result.created = timestamp;
result.icon = 'local-activity';
//TODO: Format other wallet related operations
switch (result.textKey) {
case 'curation_reward':
const { reward } = opData;
const { comment_author: commentAuthor, comment_permlink: commentPermlink } = opData;
result.value = `${vestsToHp(parseToken(reward), hivePerMVests)
.toFixed(3)
.replace(',', '.')} HP`;
result.details = commentAuthor ? `@${commentAuthor}/${commentPermlink}` : null;
break;
case 'author_reward':
case 'comment_benefactor_reward':
let {
hbd_payout: hbdPayout,
hive_payout: hivePayout,
vesting_payout: vestingPayout,
} = opData;
const { author, permlink } = opData;
hbdPayout = parseToken(hbdPayout).toFixed(3).replace(',', '.');
hivePayout = parseToken(hivePayout).toFixed(3).replace(',', '.');
vestingPayout = vestsToHp(parseToken(vestingPayout), hivePerMVests)
.toFixed(3)
.replace(',', '.');
result.value = `${hbdPayout > 0 ? `${hbdPayout} HBD` : ''} ${
hivePayout > 0 ? `${hivePayout} HIVE` : ''
} ${vestingPayout > 0 ? `${vestingPayout} HP` : ''}`;
result.details = author && permlink ? `@${author}/${permlink}` : null;
if (result.textKey === 'comment_benefactor_reward') {
result.icon = 'comment';
}
break;
case 'claim_reward_balance':
let { reward_hbd: rewardHdb, reward_hive: rewardHive, reward_vests: rewardVests } = opData;
rewardHdb = parseToken(rewardHdb).toFixed(3).replace(',', '.');
rewardHive = parseToken(rewardHive).toFixed(3).replace(',', '.');
rewardVests = vestsToHp(parseToken(rewardVests), hivePerMVests).toFixed(3).replace(',', '.');
result.value = `${rewardHdb > 0 ? `${rewardHdb} HBD` : ''} ${
rewardHive > 0 ? `${rewardHive} HIVE` : ''
} ${rewardVests > 0 ? `${rewardVests} HP` : ''}`;
break;
case 'transfer':
case 'transfer_to_savings':
case 'transfer_from_savings':
case 'transfer_to_vesting':
const { amount, memo, from, to } = opData;
result.value = `${amount}`;
result.icon = 'compare-arrows';
result.details = from && to ? `@${from} to @${to}` : null;
result.memo = memo || null;
break;
case 'withdraw_vesting':
const { acc } = opData;
let { vesting_shares: opVestingShares } = opData;
opVestingShares = parseToken(opVestingShares);
result.value = `${vestsToHp(opVestingShares, hivePerMVests).toFixed(3).replace(',', '.')} HP`;
result.icon = 'attach-money';
result.details = acc ? `@${acc}` : null;
break;
case 'fill_order':
const { current_pays: currentPays, open_pays: openPays } = opData;
result.value = `${currentPays} = ${openPays}`;
result.icon = 'reorder';
break;
case 'escrow_transfer':
case 'escrow_dispute':
case 'escrow_release':
case 'escrow_approve':
const { agent, escrow_id } = opData;
let { from: frome } = opData;
let { to: toe } = opData;
result.value = `${escrow_id}`;
result.icon = 'wb-iridescent';
result.details = frome && toe ? `@${frome} to @${toe}` : null;
result.memo = agent || null;
break;
case 'delegate_vesting_shares':
const { delegator, delegatee, vesting_shares } = opData;
result.value = `${vesting_shares}`;
result.icon = 'change-history';
result.details = delegatee && delegator ? `@${delegator} to @${delegatee}` : null;
break;
case 'cancel_transfer_from_savings':
let { from: from_who, request_id: requestId } = opData;
result.value = `${0}`;
result.icon = 'cancel';
result.details = from_who ? `from @${from_who}, id: ${requestId}` : null;
break;
case 'fill_convert_request':
let { owner: who, requestid: requestedId, amount_out: amount_out } = opData;
result.value = `${amount_out}`;
result.icon = 'hourglass-full';
result.details = who ? `@${who}, id: ${requestedId}` : null;
break;
case 'fill_transfer_from_savings':
let { from: fillwho, to: fillto, amount: fillamount, request_id: fillrequestId } = opData;
result.value = `${fillamount}`;
result.icon = 'hourglass-full';
result.details = fillwho ? `@${fillwho} to @${fillto}, id: ${fillrequestId}` : null;
break;
case 'fill_vesting_withdraw':
let { from_account: pd_who, to_account: pd_to, deposited: deposited } = opData;
result.value = `${deposited}`;
result.icon = 'hourglass-full';
result.details = pd_who ? `@${pd_who} to ${pd_to}` : null;
break;
default:
return [];
}
return result;
};
export const groomingWalletData = async (user, globalProps, userCurrency) => {
const walletData = {};
if (!user) {
return walletData;
}
const state = await getAccount(get(user, 'name'));
//const { accounts } = state;
//if (!accounts) {
// return walletData;
//}
const [userdata] = state;
// TODO: move them to utils these so big for a lifecycle function
walletData.rewardHiveBalance = parseToken(userdata.reward_hive_balance);
walletData.rewardHbdBalance = parseToken(userdata.reward_hbd_balance);
walletData.rewardVestingHive = parseToken(userdata.reward_vesting_hive);
walletData.hasUnclaimedRewards =
walletData.rewardHiveBalance > 0 ||
walletData.rewardHbdBalance > 0 ||
walletData.rewardVestingHive > 0;
walletData.balance = parseToken(userdata.balance);
walletData.vestingShares = parseToken(userdata.vesting_shares);
walletData.vestingSharesDelegated = parseToken(userdata.delegated_vesting_shares);
walletData.vestingSharesReceived = parseToken(userdata.received_vesting_shares);
walletData.vestingSharesTotal =
walletData.vestingShares - walletData.vestingSharesDelegated + walletData.vestingSharesReceived;
walletData.hbdBalance = parseToken(userdata.hbd_balance);
walletData.savingBalance = parseToken(userdata.savings_balance);
walletData.savingBalanceHbd = parseToken(userdata.savings_hbd_balance);
const feedHistory = await getFeedHistory();
const base = parseToken(feedHistory.current_median_history.base);
const quote = parseToken(feedHistory.current_median_history.quote);
walletData.hivePerMVests = globalProps.hivePerMVests;
const pricePerHive = base / quote;
const totalHive =
vestsToHp(walletData.vestingShares, walletData.hivePerMVests) +
walletData.balance +
walletData.savingBalance;
const totalHbd = walletData.hbdBalance + walletData.savingBalanceHbd;
walletData.estimatedValue = totalHive * pricePerHive + totalHbd;
const ppHbd = await getCurrencyTokenRate(userCurrency, 'hbd');
const ppHive = await getCurrencyTokenRate(userCurrency, 'hive');
walletData.estimatedHiveValue = (walletData.balance + walletData.savingBalance) * ppHive;
walletData.estimatedHbdValue = totalHbd * ppHbd;
walletData.estimatedHpValue =
vestsToHp(walletData.vestingShares, walletData.hivePerMVests) * ppHive;
walletData.showPowerDown = userdata.next_vesting_withdrawal !== '1969-12-31T23:59:59';
const timeDiff = Math.abs(parseDate(userdata.next_vesting_withdrawal) - new Date());
walletData.nextVestingWithdrawal = Math.round(timeDiff / (1000 * 3600));
const history = await getAccountHistory(get(user, 'name'));
const transfers = history.filter((tx) => transferTypes.includes(get(tx[1], 'op[0]', false)));
transfers.sort(compare);
walletData.transactions = transfers;
return walletData;
};
function compare(a, b) {
if (a[1].block < b[1].block) {
return 1;
}
if (a[1].block > b[1].block) {
return -1;
}
return 0;
}
export const groomingPointsTransactionData = (transaction) => {
if (!transaction) {
return null;
}
const result = {
...transaction,
};
result.details = get(transaction, 'sender')
? `from @${get(transaction, 'sender')}`
: get(transaction, 'receiver') && `to @${get(transaction, 'receiver')}`;
result.value = `${get(transaction, 'amount')} Points`;
return result;
};
export const getPointsEstimate = async (amount, userCurrency) => {
if (!amount) {
return 0;
}
const ppEstm = await getCurrencyTokenRate(userCurrency, 'estm');
return ppEstm * amount;
};
export const getBtcEstimate = async (amount, userCurrency) => {
if (!amount) {
return 0;
}
const ppBtc = await getCurrencyTokenRate(userCurrency, 'btc');
return ppBtc * amount;
};

738
src/utils/wallet.ts Normal file
View File

@ -0,0 +1,738 @@
import get from 'lodash/get';
import parseDate from './parseDate';
import parseToken from './parseToken';
import { vestsToHp } from './conversions';
import { getAccount, getAccountHistory, getConversionRequests, getFeedHistory, getOpenOrders, getSavingsWithdrawFrom } from '../providers/hive/dhive';
import { getCurrencyTokenRate, getLatestQuotes } from '../providers/ecency/ecency';
import { CoinActivitiesCollection, CoinActivity, CoinBase, CoinData, DataPair, QuoteItem } from '../redux/reducers/walletReducer';
import { GlobalProps } from '../redux/reducers/accountReducer';
import { getEstimatedAmount } from './vote';
import { getUser as getEcencyUser, getUserPoints } from '../providers/ecency/ePoint';
// Constant
import POINTS from '../constants/options/points';
import { COIN_IDS } from '../constants/defaultCoins';
import { operationOrders } from '@hiveio/dhive/lib/utils';
import { ConversionRequest, OpenOrderItem, OrdersData, SavingsWithdrawRequest } from '../providers/hive/hive.types';
import parseAsset from './parseAsset';
import { utils } from '@hiveio/dhive';
export const transferTypes = [
'curation_reward',
'author_reward',
'comment_benefactor_reward',
'claim_reward_balance',
'transfer',
'transfer_to_savings',
'transfer_from_savings',
'transfer_to_vesting',
'withdraw_vesting',
'fill_order',
'escrow_transfer',
'escrow_dispute',
'escrow_release',
'escrow_approve',
'delegate_vesting_shares',
'cancel_transfer_from_savings',
'fill_convert_request',
'fill_transfer_from_savings',
'fill_vesting_withdraw',
];
const ECENCY_ACTIONS = [
'dropdown_transfer', 'dropdown_promote', 'dropdown_boost'
];
const HIVE_ACTIONS = [
'purchase_estm',
'transfer_token',
'transfer_to_savings',
'transfer_to_vesting',
'withdraw_hive'
];
const HBD_ACTIONS = [
'purchase_estm',
'transfer_token',
'transfer_to_savings',
'convert',
'withdraw_hbd'
];
const HIVE_POWER_ACTIONS = ['delegate', 'power_down'];
export const groomingTransactionData = (transaction, hivePerMVests) => {
if (!transaction || !hivePerMVests) {
return [];
}
const result = {
iconType: 'MaterialIcons',
};
[result.textKey] = transaction[1].op;
const opData = transaction[1].op[1];
const { timestamp } = transaction[1];
result.created = timestamp;
result.icon = 'local-activity';
//TODO: Format other wallet related operations
switch (result.textKey) {
case 'curation_reward':
const { reward } = opData;
const { comment_author: commentAuthor, comment_permlink: commentPermlink } = opData;
result.value = `${vestsToHp(parseToken(reward), hivePerMVests)
.toFixed(3)
.replace(',', '.')} HP`;
result.details = commentAuthor ? `@${commentAuthor}/${commentPermlink}` : null;
break;
case 'author_reward':
case 'comment_benefactor_reward':
let {
hbd_payout: hbdPayout,
hive_payout: hivePayout,
vesting_payout: vestingPayout,
} = opData;
const { author, permlink } = opData;
hbdPayout = parseToken(hbdPayout).toFixed(3).replace(',', '.');
hivePayout = parseToken(hivePayout).toFixed(3).replace(',', '.');
vestingPayout = vestsToHp(parseToken(vestingPayout), hivePerMVests)
.toFixed(3)
.replace(',', '.');
result.value = `${hbdPayout > 0 ? `${hbdPayout} HBD` : ''} ${hivePayout > 0 ? `${hivePayout} HIVE` : ''
} ${vestingPayout > 0 ? `${vestingPayout} HP` : ''}`;
result.details = author && permlink ? `@${author}/${permlink}` : null;
if (result.textKey === 'comment_benefactor_reward') {
result.icon = 'comment';
}
break;
case 'claim_reward_balance':
let { reward_hbd: rewardHdb, reward_hive: rewardHive, reward_vests: rewardVests } = opData;
rewardHdb = parseToken(rewardHdb).toFixed(3).replace(',', '.');
rewardHive = parseToken(rewardHive).toFixed(3).replace(',', '.');
rewardVests = vestsToHp(parseToken(rewardVests), hivePerMVests).toFixed(3).replace(',', '.');
result.value = `${rewardHdb > 0 ? `${rewardHdb} HBD` : ''} ${rewardHive > 0 ? `${rewardHive} HIVE` : ''
} ${rewardVests > 0 ? `${rewardVests} HP` : ''}`;
break;
case 'transfer':
case 'transfer_to_savings':
case 'transfer_from_savings':
case 'transfer_to_vesting':
const { amount, memo, from, to } = opData;
result.value = `${amount}`;
result.icon = 'compare-arrows';
result.details = from && to ? `@${from} to @${to}` : null;
result.memo = memo || null;
break;
case 'withdraw_vesting':
const { acc } = opData;
let { vesting_shares: opVestingShares } = opData;
opVestingShares = parseToken(opVestingShares);
result.value = `${vestsToHp(opVestingShares, hivePerMVests).toFixed(3).replace(',', '.')} HP`;
result.icon = 'attach-money';
result.details = acc ? `@${acc}` : null;
break;
case 'fill_order':
const { current_pays: currentPays, open_pays: openPays } = opData;
result.value = `${currentPays} = ${openPays}`;
result.icon = 'reorder';
break;
case 'escrow_transfer':
case 'escrow_dispute':
case 'escrow_release':
case 'escrow_approve':
const { agent, escrow_id } = opData;
let { from: frome } = opData;
let { to: toe } = opData;
result.value = `${escrow_id}`;
result.icon = 'wb-iridescent';
result.details = frome && toe ? `@${frome} to @${toe}` : null;
result.memo = agent || null;
break;
case 'delegate_vesting_shares':
const { delegator, delegatee, vesting_shares } = opData;
result.value = `${vesting_shares}`;
result.icon = 'change-history';
result.details = delegatee && delegator ? `@${delegator} to @${delegatee}` : null;
break;
case 'cancel_transfer_from_savings':
let { from: from_who, request_id: requestId } = opData;
result.value = `${0}`;
result.icon = 'cancel';
result.details = from_who ? `from @${from_who}, id: ${requestId}` : null;
break;
case 'fill_convert_request':
let { owner: who, requestid: requestedId, amount_out: amount_out } = opData;
result.value = `${amount_out}`;
result.icon = 'hourglass-full';
result.details = who ? `@${who}, id: ${requestedId}` : null;
break;
case 'fill_transfer_from_savings':
let { from: fillwho, to: fillto, amount: fillamount, request_id: fillrequestId } = opData;
result.value = `${fillamount}`;
result.icon = 'hourglass-full';
result.details = fillwho ? `@${fillwho} to @${fillto}, id: ${fillrequestId}` : null;
break;
case 'fill_vesting_withdraw':
let { from_account: pd_who, to_account: pd_to, deposited: deposited } = opData;
result.value = `${deposited}`;
result.icon = 'hourglass-full';
result.details = pd_who ? `@${pd_who} to ${pd_to}` : null;
break;
default:
return [];
}
return result;
};
export const groomingWalletData = async (user, globalProps, userCurrency) => {
const walletData = {};
if (!user) {
return walletData;
}
const userdata = await getAccount(get(user, 'name'));
//const { accounts } = state;
//if (!accounts) {
// return walletData;
//}
walletData.rewardHiveBalance = parseToken(userdata.reward_hive_balance);
walletData.rewardHbdBalance = parseToken(userdata.reward_hbd_balance);
walletData.rewardVestingHive = parseToken(userdata.reward_vesting_hive);
walletData.hasUnclaimedRewards =
walletData.rewardHiveBalance > 0 ||
walletData.rewardHbdBalance > 0 ||
walletData.rewardVestingHive > 0;
walletData.balance = parseToken(userdata.balance);
walletData.vestingShares = parseToken(userdata.vesting_shares);
walletData.vestingSharesDelegated = parseToken(userdata.delegated_vesting_shares);
walletData.vestingSharesReceived = parseToken(userdata.received_vesting_shares);
walletData.vestingSharesTotal =
walletData.vestingShares - walletData.vestingSharesDelegated + walletData.vestingSharesReceived;
walletData.hbdBalance = parseToken(userdata.hbd_balance);
walletData.savingBalance = parseToken(userdata.savings_balance);
walletData.savingBalanceHbd = parseToken(userdata.savings_hbd_balance);
//TOOD: use base and quote from account.globalProps redux
const feedHistory = await getFeedHistory();
const base = parseToken(feedHistory.current_median_history.base);
const quote = parseToken(feedHistory.current_median_history.quote);
walletData.hivePerMVests = globalProps.hivePerMVests;
const pricePerHive = base / quote;
const totalHive =
vestsToHp(walletData.vestingShares, walletData.hivePerMVests) +
walletData.balance +
walletData.savingBalance;
const totalHbd = walletData.hbdBalance + walletData.savingBalanceHbd;
walletData.estimatedValue = totalHive * pricePerHive + totalHbd;
//TODO: cache data in redux or fetch once on wallet startup
const ppHbd = await getCurrencyTokenRate(userCurrency, 'hbd');
const ppHive = await getCurrencyTokenRate(userCurrency, 'hive');
walletData.estimatedHiveValue = (walletData.balance + walletData.savingBalance) * ppHive;
walletData.estimatedHbdValue = totalHbd * ppHbd;
walletData.estimatedHpValue =
vestsToHp(walletData.vestingShares, walletData.hivePerMVests) * ppHive;
walletData.showPowerDown = userdata.next_vesting_withdrawal !== '1969-12-31T23:59:59';
const timeDiff = Math.abs(parseDate(userdata.next_vesting_withdrawal) - new Date());
walletData.nextVestingWithdrawal = Math.round(timeDiff / (1000 * 3600));
//TOOD: transfer history can be separated from here
const op = utils.operationOrders
const ops = [
op.transfer, //HIVE
op.author_reward, //HBD, HP
op.curation_reward, //HP
op.transfer_to_vesting, //HIVE, HP
op.withdraw_vesting, //HIVE, HP
op.interest, //HP
op.transfer_to_savings, //HIVE, HBD
op.transfer_from_savings, //HIVE, HBD
op.fill_convert_request, //HBD
op.fill_order, //HIVE, HBD
op.claim_reward_balance, //HP
op.sps_fund, //HBD
op.comment_benefactor_reward, //HP
op.return_vesting_delegation, //HP
]
const history = await getAccountHistory(get(user, 'name'), ops);
const transfers = history.filter((tx) => transferTypes.includes(get(tx[1], 'op[0]', false)));
transfers.sort(compare);
walletData.transactions = transfers;
return walletData;
};
const fetchPendingRequests = async (username: string, coinSymbol: string): Promise<CoinActivity[]> => {
const _rawConversions = await getConversionRequests(username);
const _rawOpenOrdres = await getOpenOrders(username);
const _rawWithdrawRequests = await getSavingsWithdrawFrom(username);
console.log('fetched pending requests', _rawConversions, _rawOpenOrdres, _rawWithdrawRequests);
const openOrderRequests = _rawOpenOrdres
.filter(request => request.sell_price.base.includes(coinSymbol))
.map((request) => {
const {base, quote} = request?.sell_price || {};
return ({
iconType: "MaterialIcons",
textKey: 'open_order',
expires: request.expiration,
created: request.created,
icon: 'reorder',
value: base || '-- --',
details: base && quote ? `@ ${base} = ${quote}`:'',
} as CoinActivity)
})
const withdrawRequests = _rawWithdrawRequests
.filter(request => request.amount.includes(coinSymbol))
.map((request) => {
return ({
iconType: "MaterialIcons",
textKey: "withdraw_savings",
created: request.complete,
icon: "compare-arrows",
value: request.amount,
details: request.from && request.to ? `@${request.from} to @${request.to}` : null,
memo: request.memo || null
} as CoinActivity)
})
const conversionRequests = _rawConversions
.filter(request => request.amount.includes(coinSymbol))
.map((request) => {
return ({
iconType: "MaterialIcons",
textKey: "convert_request",
created: request.conversion_date,
icon: "hourglass-full",
value: request.amount
} as CoinActivity)
})
const pendingRequests = [
...openOrderRequests,
...withdrawRequests,
...conversionRequests
];
pendingRequests.sort((a, b) => (
new Date(a.expires || a.created).getTime() > new Date(b.expires || b.created).getTime() ? 1 : -1
))
return pendingRequests;
}
/**
*
* @param username
* @param coinId
* @param coinSymbol
* @param globalProps
* @returns {Promise<CoinActivitiesCollection>}
*/
export const fetchCoinActivities = async (username: string, coinId: string, coinSymbol: string, globalProps: GlobalProps): Promise<CoinActivitiesCollection> => {
const op = operationOrders;
let history = [];
switch (coinId) {
case COIN_IDS.ECENCY: {
const pointActivities = await getUserPoints(username);
console.log("Points Activities", pointActivities);
const completed = pointActivities && pointActivities.length ?
pointActivities.map((item) =>
groomingPointsTransactionData({
...item,
icon: get(POINTS[get(item, 'type')], 'icon'),
iconType: get(POINTS[get(item, 'type')], 'iconType'),
textKey: get(POINTS[get(item, 'type')], 'textKey'),
})
) : [];
return {
completed,
pending: [] as CoinActivity[]
}
}
case COIN_IDS.HIVE:
history = await getAccountHistory(username, [
op.transfer, //HIVE
op.transfer_to_vesting, //HIVE, HP
op.withdraw_vesting, //HIVE, HP
op.transfer_to_savings, //HIVE, HBD
op.transfer_from_savings, //HIVE, HBD
op.fill_order, //HIVE, HBD
]);
break;
case COIN_IDS.HBD:
history = await getAccountHistory(username, [
op.transfer, //HIVE //HBD
op.author_reward, //HBD, HP
op.transfer_to_savings, //HIVE, HBD
op.transfer_from_savings, //HIVE, HBD
op.fill_convert_request, //HBD
op.fill_order, //HIVE, HBD
op.sps_fund, //HBD
]);
break;
case COIN_IDS.HP:
history = await getAccountHistory(username, [
op.author_reward, //HBD, HP
op.curation_reward, //HP
op.transfer_to_vesting, //HIVE, HP
op.withdraw_vesting, //HIVE, HP
op.interest, //HP
op.claim_reward_balance, //HP
op.comment_benefactor_reward, //HP
op.return_vesting_delegation, //HP
]);
break;
}
const transfers = history.filter((tx) => transferTypes.includes(get(tx[1], 'op[0]', false)));
transfers.sort(compare);
const activities = transfers.map(item => groomingTransactionData(item, globalProps.hivePerMVests));
const filterdActivities: CoinActivity[] = activities
? activities.filter((item) => {
return (
item &&
item.value &&
item.value.includes(coinSymbol)
);
})
: [];
console.log('FILTERED comap', activities.length, filterdActivities.length)
const pendingRequests = await fetchPendingRequests(username, coinSymbol);
return {
completed: filterdActivities,
pending: pendingRequests,
}
}
const calculateConvertingAmount = (requests: ConversionRequest[]): number => {
if (!requests || !requests.length) {
return 0;
}
//TODO: add method body
// ecency-vision -> src/common/components/wallet-hive/index.tsx#fetchConvertingAmount
throw new Error("calculateConvertingAmount method body not implemented yet");
}
const calculateSavingsWithdrawalAmount = (requests: SavingsWithdrawRequest[], coinSymbol: string): number => {
return requests.reduce((prevVal, curRequest) => {
const _amount = curRequest.amount;
return _amount.includes(coinSymbol)
? prevVal + parseAsset(_amount).amount
: prevVal
}, 0);
}
const calculateOpenOrdersAmount = (requests: OpenOrderItem[], coinSymbol: string): number => {
return requests.reduce((prevVal, curRequest) => {
const _basePrice = curRequest.sell_price.base;
return _basePrice.includes(coinSymbol)
? prevVal + parseAsset(_basePrice).amount
: prevVal
}, 0);
}
export const fetchCoinsData = async ({
coins,
currentAccount,
vsCurrency,
currencyRate,
globalProps,
refresh,
quotes,
}: {
coins: CoinBase[],
currentAccount: any,
vsCurrency: string,
currencyRate: number,
globalProps: GlobalProps,
quotes: { [key: string]: QuoteItem }
refresh: boolean,
})
: Promise<{ [key: string]: CoinData }> => {
const username = currentAccount.username;
const { base, quote, hivePerMVests } = globalProps
const coinData = {} as { [key: string]: CoinData };
const walletData = {} as any;
if (!username) {
return walletData;
}
//TODO: Use already available accoutn for frist wallet start
const userdata = refresh ? await getAccount(username) : currentAccount;
const _ecencyUserData = refresh ? await getEcencyUser(username) : currentAccount.ecencyUserData
//TODO: cache data in redux or fetch once on wallet startup
const _prices = !refresh && quotes ? quotes : await getLatestQuotes(currencyRate); //TODO: figure out a way to handle other currencies
coins.forEach((coinBase) => {
switch (coinBase.id) {
case COIN_IDS.ECENCY: {
const balance = _ecencyUserData.points ? parseFloat(_ecencyUserData.points) : 0;
const unclaimedFloat = parseFloat(_ecencyUserData.unclaimed_points || '0');
const unclaimedBalance = unclaimedFloat ? unclaimedFloat + ' Points' : '';
const ppEstm = _prices[coinBase.id].price;
coinData[coinBase.id] = {
balance: Math.round(balance * 1000) / 1000,
estimateValue: balance * ppEstm,
vsCurrency: vsCurrency,
currentPrice: ppEstm,
unclaimedBalance: unclaimedBalance,
actions: ECENCY_ACTIONS,
}
break;
}
case COIN_IDS.HIVE: {
const balance = parseToken(userdata.balance);
const savings = parseToken(userdata.savings_balance);
const ppHive = _prices[coinBase.id].price;
coinData[coinBase.id] = {
balance: Math.round(balance * 1000) / 1000,
estimateValue: (balance + savings) * ppHive,
savings: Math.round(savings * 1000) / 1000,
vsCurrency: vsCurrency,
currentPrice: ppHive,
unclaimedBalance: '',
actions: HIVE_ACTIONS,
}
break;
}
case COIN_IDS.HBD: {
const balance = parseToken(userdata.hbd_balance);
const savings = parseToken(userdata.savings_hbd_balance);
const ppHbd = _prices[coinBase.id].price;
coinData[coinBase.id] = {
balance: Math.round(balance * 1000) / 1000,
estimateValue: (balance + savings) * ppHbd,
savings: Math.round(savings * 1000) / 1000,
vsCurrency: vsCurrency,
currentPrice: ppHbd,
unclaimedBalance: '',
actions: HBD_ACTIONS,
}
break;
}
case COIN_IDS.HP: {
const _getBalanceStr = (val: number, cur: string) => (val ? Math.round(val * 1000) / 1000 + cur : '');
const balance = Math.round(
vestsToHp(parseToken(userdata.vesting_shares), hivePerMVests) * 1000,
) / 1000;
const receivedHP = vestsToHp(
parseToken(userdata.received_vesting_shares),
hivePerMVests,
)
const delegatedHP = vestsToHp(
parseToken(userdata.delegated_vesting_shares),
hivePerMVests,
)
//agggregate claim button text
const unclaimedBalance = [
_getBalanceStr(parseToken(userdata.reward_hive_balance), ' HIVE'),
_getBalanceStr(parseToken(userdata.reward_hbd_balance), ' HBD'),
_getBalanceStr(parseToken(userdata.reward_vesting_hive), ' HP')
].reduce(
(prevVal, bal) => prevVal + (!bal ? '' : (`${prevVal !== '' ? ' ' : ''}${bal}`)),
''
);
//calculate power down
const isPoweringDown = userdata.next_vesting_withdrawal
? parseDate(userdata.next_vesting_withdrawal) > new Date()
: false;
const nextVestingSharesWithdrawal = isPoweringDown
? Math.min(
parseAsset(userdata.vesting_withdraw_rate).amount,
(Number(userdata.to_withdraw) - Number(userdata.withdrawn)) / 1e6
) : 0;
const nextVestingSharesWithdrawalHive = isPoweringDown ? vestsToHp(nextVestingSharesWithdrawal, hivePerMVests) : 0;
//TODO: assess how we can make this value change live.
const estimateVoteValueStr = '$ ' + getEstimatedAmount(userdata, globalProps);
const ppHive = _prices[COIN_IDS.HIVE].price;
coinData[coinBase.id] = {
balance: Math.round(balance * 1000) / 1000,
estimateValue: balance * ppHive,
unclaimedBalance,
vsCurrency: vsCurrency,
currentPrice: ppHive,
actions: HIVE_POWER_ACTIONS,
extraDataPairs: [
{
labelId: 'delegated_hive_power',
value: `- ${delegatedHP.toFixed(3)} HP`
}, {
labelId: 'received_hive_power',
value: `+ ${receivedHP.toFixed(3)} HP`
}, {
labelId: 'powering_down_hive_power',
value: `- ${nextVestingSharesWithdrawalHive.toFixed(3)} HP`
}, {
labelId: 'total_hive_power',
value: `${(balance - delegatedHP + receivedHP - nextVestingSharesWithdrawalHive).toFixed(3)} HP`
}, {
labelId: 'vote_value',
value: estimateVoteValueStr
}
]
}
break;
}
default:
break;
}
})
//TODO:discard unnessacry data processings towards the end of PR
walletData.rewardHiveBalance = parseToken(userdata.reward_hive_balance);
walletData.rewardHbdBalance = parseToken(userdata.reward_hbd_balance);
walletData.rewardVestingHive = parseToken(userdata.reward_vesting_hive);
walletData.hasUnclaimedRewards =
walletData.rewardHiveBalance > 0 ||
walletData.rewardHbdBalance > 0 ||
walletData.rewardVestingHive > 0;
walletData.balance = parseToken(userdata.balance);
walletData.vestingShares = parseToken(userdata.vesting_shares);
walletData.vestingSharesDelegated = parseToken(userdata.delegated_vesting_shares);
walletData.vestingSharesReceived = parseToken(userdata.received_vesting_shares);
walletData.vestingSharesTotal =
walletData.vestingShares - walletData.vestingSharesDelegated + walletData.vestingSharesReceived;
walletData.hbdBalance = parseToken(userdata.hbd_balance);
walletData.savingBalance = parseToken(userdata.savings_balance);
walletData.savingBalanceHbd = parseToken(userdata.savings_hbd_balance);
walletData.hivePerMVests = hivePerMVests;
const pricePerHive = base / quote;
const totalHive =
vestsToHp(walletData.vestingShares, walletData.hivePerMVests) +
walletData.balance +
walletData.savingBalance;
const totalHbd = walletData.hbdBalance + walletData.savingBalanceHbd;
walletData.estimatedValue = totalHive * pricePerHive + totalHbd;
walletData.showPowerDown = userdata.next_vesting_withdrawal !== '1969-12-31T23:59:59';
const timeDiff = Math.abs(parseDate(userdata.next_vesting_withdrawal) - new Date());
walletData.nextVestingWithdrawal = Math.round(timeDiff / (1000 * 3600));
return coinData;
}
function compare(a, b) {
if (a[1].block < b[1].block) {
return 1;
}
if (a[1].block > b[1].block) {
return -1;
}
return 0;
}
export const groomingPointsTransactionData = (transaction) => {
if (!transaction) {
return null;
}
const result = {
...transaction,
};
result.details = get(transaction, 'sender')
? `from @${get(transaction, 'sender')}`
: get(transaction, 'receiver') && `to @${get(transaction, 'receiver')}`;
result.value = `${get(transaction, 'amount')} Points`;
return result;
};
export const getPointsEstimate = async (amount, userCurrency) => {
if (!amount) {
return 0;
}
const ppEstm = await getCurrencyTokenRate(userCurrency, 'estm');
return ppEstm * amount;
};
export const getBtcEstimate = async (amount, userCurrency) => {
if (!amount) {
return 0;
}
const ppBtc = await getCurrencyTokenRate(userCurrency, 'btc');
return ppBtc * amount;
};

View File

@ -8088,6 +8088,11 @@ path@^0.12.7:
process "^0.11.1"
util "^0.10.3"
paths-js@^0.4.10:
version "0.4.11"
resolved "https://registry.yarnpkg.com/paths-js/-/paths-js-0.4.11.tgz#b2a9d5f94ee9949aa8fee945f78a12abff44599e"
integrity sha512-3mqcLomDBXOo7Fo+UlaenG6f71bk1ZezPQy2JCmYHy2W2k5VKpP+Jbin9H0bjXynelTbglCqdFhSEkeIkKTYUA==
pbkdf2@3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.8.tgz#2f8abf16ebecc82277945d748aba1d78761f61e2"
@ -8201,6 +8206,11 @@ pngjs@^3.3.0:
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
point-in-polygon@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/point-in-polygon/-/point-in-polygon-1.1.0.tgz#b0af2616c01bdee341cbf2894df643387ca03357"
integrity sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==
polygoat@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/polygoat/-/polygoat-1.1.4.tgz#329f9a0d1b2d4a45149e2539523c6f7dfd850a5f"
@ -8555,6 +8565,15 @@ react-native-camera@^4.2.1:
dependencies:
prop-types "^15.6.2"
react-native-chart-kit@^6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/react-native-chart-kit/-/react-native-chart-kit-6.11.0.tgz#513e0944cd8f946e1827facc17fe766ae487d91b"
integrity sha512-mRSfW+mcyVSi0UZKFucru5h5TPB6UcWrMi/DphICRpPJWlvIVAv/6VYGFA57NFDLoRVufFjbsRRrms+TOq61jw==
dependencies:
lodash "^4.17.13"
paths-js "^0.4.10"
point-in-polygon "^1.0.1"
react-native-config@luggit/react-native-config#master:
version "1.4.2"
resolved "https://codeload.github.com/luggit/react-native-config/tar.gz/81f599f5f912b84c41c9ef2901faf54995638c4e"
@ -10690,9 +10709,9 @@ uri-js@^4.2.2:
punycode "^2.1.0"
urijs@^1.19.6:
version "1.19.8"
resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.8.tgz#ee0407a18528934d3c383e691912f47043a58feb"
integrity sha512-iIXHrjomQ0ZCuDRy44wRbyTZVnfVNLVo3Ksz1yxNyE5wV1IDZW2S5Jszy45DTlw/UdsnRT7DyDhIz7Gy+vJumw==
version "1.19.10"
resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.10.tgz#8e2fe70a8192845c180f75074884278f1eea26cb"
integrity sha512-EzauQlgKuJgsXOqoMrCiePBf4At5jVqRhXykF3Wfb8ZsOBMxPcfiVBcsHXug4Aepb/ICm2PIgqAUGMelgdrWEg==
urix@^0.1.0:
version "0.1.0"