Skip to content

Commit bb2b6bd

Browse files
committed
chore: graph tuning
1 parent be7261b commit bb2b6bd

18 files changed

Lines changed: 341 additions & 204 deletions

File tree

suite-common/formatters/src/formatters/prepareDateTimeFormatter.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { A, pipe } from '@mobily/ts-belt';
2-
31
import { makeFormatter } from '../makeFormatter';
42
import { FormatterConfig } from '../types';
53
import { prepareDateFormatter } from './prepareDateFormatter';
@@ -11,5 +9,5 @@ export const prepareDateTimeFormatter = (config: FormatterConfig) =>
119
const DateFormatter = prepareDateFormatter(config);
1210
const TimeFormatter = prepareTimeFormatter(config);
1311

14-
return pipe([TimeFormatter.format(value), DateFormatter.format(value)], A.join(' '));
12+
return `${DateFormatter.format(value)}, ${TimeFormatter.format(value)}`;
1513
});

suite-common/graph/src/graphDataFetching.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,24 @@ export const addBalanceForAccountMovementHistory = (
2222
data: AccountMovementHistory[],
2323
symbol: NetworkSymbol,
2424
): AccountHistoryBalancePoint[] => {
25-
let balance = '0';
25+
let balance = new BigNumber('0');
2626
const historyWithBalance = data.map(dataPoint => {
2727
// subtract sentToSelf field as we don't want to include amounts received/sent to the same account
2828
const normalizedReceived = dataPoint.sentToSelf
29-
? new BigNumber(dataPoint.received).minus(dataPoint.sentToSelf || 0).toFixed()
29+
? new BigNumber(dataPoint.received).minus(dataPoint.sentToSelf || 0)
3030
: dataPoint.received;
3131
const normalizedSent = dataPoint.sentToSelf
32-
? new BigNumber(dataPoint.sent).minus(dataPoint.sentToSelf || 0).toFixed()
32+
? new BigNumber(dataPoint.sent).minus(dataPoint.sentToSelf || 0)
3333
: dataPoint.sent;
3434

35-
balance = new BigNumber(balance).plus(normalizedReceived).minus(normalizedSent).toFixed();
35+
balance = new BigNumber(balance).plus(normalizedReceived).minus(normalizedSent);
36+
37+
// for some coins like ETH, simple sum of received and sent is not enough and could result in nonsense like negative balance
38+
balance = balance.isNegative() ? new BigNumber('0') : balance;
3639

3740
return {
3841
time: dataPoint.time,
39-
cryptoBalance: formatNetworkAmount(balance, symbol),
42+
cryptoBalance: formatNetworkAmount(balance.toFixed(), symbol),
4043
};
4144
});
4245

@@ -61,24 +64,40 @@ export const getAccountBalanceHistory = async ({
6164
return accountBalanceHistoryCache[cacheKey];
6265
}
6366

64-
const accountMovementHistory = await TrezorConnect.blockchainGetAccountBalanceHistory({
65-
coin,
66-
descriptor,
67-
to: endTimeFrameTimestamp,
68-
// we don't need currencies at all here, this will just reduce transferred data size
69-
// TODO: doesn't work at all, fix it in connect or blockchain-link?
70-
currencies: ['usd'],
71-
});
67+
const [accountMovementHistory, accountInfo] = await Promise.all([
68+
TrezorConnect.blockchainGetAccountBalanceHistory({
69+
coin,
70+
descriptor,
71+
to: endTimeFrameTimestamp,
72+
// we don't need currencies at all here, this will just reduce transferred data size
73+
// TODO: doesn't work at all, fix it in connect or blockchain-link?
74+
currencies: ['usd'],
75+
}),
76+
TrezorConnect.getAccountInfo({ coin, descriptor }),
77+
]);
7278

7379
if (!accountMovementHistory?.success) {
74-
throw new Error(`Get account balance error: ${accountMovementHistory.payload.error}`);
80+
throw new Error(
81+
`Get account balance movement error: ${accountMovementHistory.payload.error}`,
82+
);
83+
}
84+
85+
if (!accountInfo?.success) {
86+
throw new Error(`Get account balance info error: ${accountInfo.payload.error}`);
7587
}
7688

7789
const accountMovementHistoryWithBalance = addBalanceForAccountMovementHistory(
7890
accountMovementHistory.payload,
7991
coin,
8092
);
8193

94+
// Last point must be balance from getAccountInfo because blockchainGetAccountBalanceHistory it's not always reliable for coins like ETH.
95+
// TODO: We can get value from redux store instead of fetching it again?
96+
accountMovementHistoryWithBalance.push({
97+
time: endTimeFrameTimestamp,
98+
cryptoBalance: formatNetworkAmount(accountInfo.payload.balance, coin),
99+
});
100+
82101
accountBalanceHistoryCache[cacheKey] = accountMovementHistoryWithBalance;
83102

84103
return accountMovementHistoryWithBalance;
@@ -166,11 +185,12 @@ export const getMultipleAccountBalanceHistoryWithFiat = async ({
166185
);
167186
}
168187

169-
const timestamps = getTimestampsInTimeFrame(
170-
startOfTimeFrameDate,
171-
endOfTimeFrameDate,
172-
numberOfPoints,
173-
);
188+
// Last timestamp must be endOfTimeFrameDate because blockchainGetAccountBalanceHistory it's not always reliable for coins like ETH.
189+
// So we manually add balance from getAccountInfo for last point in getAccountBalanceHistory.
190+
const timestamps = [
191+
...getTimestampsInTimeFrame(startOfTimeFrameDate, endOfTimeFrameDate, numberOfPoints - 1),
192+
getUnixTime(endOfTimeFrameDate),
193+
];
174194

175195
const coins = pipe(
176196
accounts,

suite-native/formatters/src/components/FiatAmountFormatter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { FormatterProps } from '../types';
99
import { EmptyAmountText } from './EmptyAmountText';
1010
import { AmountText } from './AmountText';
1111

12-
type CryptoToFiatAmountFormatterProps = FormatterProps<string | number | null> &
12+
type FiatAmountFormatterProps = FormatterProps<string | null> &
1313
TextProps & {
1414
network?: NetworkSymbol;
1515
isDiscreetText?: boolean;
@@ -20,7 +20,7 @@ export const FiatAmountFormatter = ({
2020
value,
2121
isDiscreetText = true,
2222
...textProps
23-
}: CryptoToFiatAmountFormatterProps) => {
23+
}: FiatAmountFormatterProps) => {
2424
const { FiatAmountFormatter: formatter } = useFormatters();
2525

2626
const isTestnetValue = !!network && isTestnet(network);

suite-native/graph/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@
1313
"dependencies": {
1414
"@mobily/ts-belt": "^3.13.1",
1515
"@shopify/react-native-skia": "0.1.173",
16+
"@suite-common/formatters": "workspace:*",
1617
"@suite-common/graph": "workspace:*",
1718
"@suite-common/wallet-core": "workspace:*",
1819
"@suite-common/wallet-types": "workspace:*",
1920
"@suite-native/atoms": "workspace:*",
2021
"@suite-native/formatters": "workspace:*",
2122
"@suite-native/react-native-graph": "workspace:*",
23+
"@trezor/icons": "workspace:*",
2224
"@trezor/styles": "workspace:*",
2325
"date-fns": "^2.29.3",
26+
"jotai": "1.9.1",
2427
"react": "18.2.0",
2528
"react-native": "0.71.3",
2629
"react-native-reanimated": "2.14.4",

suite-native/graph/src/components/AxisLabel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const AxisLabel = ({ x, value }: AxisLabelProps) => {
2323

2424
return (
2525
<View style={applyStyle(axisLabelStyle, { x })}>
26-
<FiatAmountFormatter value={value} />
26+
<FiatAmountFormatter value={String(value)} />
2727
</View>
2828
);
2929
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
3+
import { differenceInDays, isSameDay } from 'date-fns';
4+
import { Atom, useAtomValue } from 'jotai';
5+
6+
import { useFormatters } from '@suite-common/formatters';
7+
8+
import { EnhancedGraphPoint } from '../utils';
9+
10+
type SelectedPointAtom = Atom<EnhancedGraphPoint>;
11+
12+
type GraphDateFormatterProps = {
13+
firstPointDate: Date;
14+
selectedPointAtom: Atom<EnhancedGraphPoint>;
15+
};
16+
17+
const SameDayFormatter = ({ selectedPointAtom }: { selectedPointAtom: SelectedPointAtom }) => {
18+
const { TimeFormatter } = useFormatters();
19+
const point = useAtomValue(selectedPointAtom);
20+
return <TimeFormatter value={point.date} />;
21+
};
22+
23+
const WeekFormatter = ({ selectedPointAtom }: { selectedPointAtom: SelectedPointAtom }) => {
24+
const { DateTimeFormatter, TimeFormatter } = useFormatters();
25+
const { originalDate: value } = useAtomValue(selectedPointAtom);
26+
if (isSameDay(value, new Date())) {
27+
return <TimeFormatter value={value} />;
28+
}
29+
return <DateTimeFormatter value={value} />;
30+
};
31+
32+
const OtherDateFormatter = ({ selectedPointAtom }: { selectedPointAtom: SelectedPointAtom }) => {
33+
const { DateFormatter } = useFormatters();
34+
35+
const { originalDate: value } = useAtomValue(selectedPointAtom);
36+
return <DateFormatter value={value} />;
37+
};
38+
39+
export const GraphDateFormatter = ({
40+
firstPointDate,
41+
selectedPointAtom,
42+
}: GraphDateFormatterProps) => {
43+
if (isSameDay(firstPointDate, new Date())) {
44+
return <SameDayFormatter selectedPointAtom={selectedPointAtom} />;
45+
}
46+
if (differenceInDays(firstPointDate, new Date()) < 7) {
47+
return <WeekFormatter selectedPointAtom={selectedPointAtom} />;
48+
}
49+
50+
return <OtherDateFormatter selectedPointAtom={selectedPointAtom} />;
51+
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React from 'react';
2+
3+
import { Atom, useAtom } from 'jotai';
4+
5+
import { Box, Text } from '@suite-native/atoms';
6+
import { Icon, IconName } from '@trezor/icons';
7+
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
8+
9+
type PriceChangeIndicatorProps = {
10+
percentageChangeAtom: PercentageChangeAtom;
11+
hasPriceIncreasedAtom: HasPriceIncreasedAtom;
12+
};
13+
14+
const getColorForPercentageChange = (hasIncreased: boolean) =>
15+
hasIncreased ? 'textPrimaryDefault' : 'textAlertRed';
16+
17+
type PercentageChangeAtom = Atom<number>;
18+
type HasPriceIncreasedAtom = Atom<boolean>;
19+
20+
type PercentageChangeProps = {
21+
percentageChangeAtom: PercentageChangeAtom;
22+
hasPriceIncreasedAtom: HasPriceIncreasedAtom;
23+
};
24+
25+
const PercentageChange = ({
26+
percentageChangeAtom,
27+
hasPriceIncreasedAtom,
28+
}: PercentageChangeProps) => {
29+
const [percentageChange] = useAtom(percentageChangeAtom);
30+
const [hasPriceIncreased] = useAtom(hasPriceIncreasedAtom);
31+
32+
return (
33+
<Text color={getColorForPercentageChange(hasPriceIncreased)} variant="hint">
34+
{percentageChange.toFixed(2)}%
35+
</Text>
36+
);
37+
};
38+
39+
const PercentageChangeArrow = ({
40+
hasPriceIncreasedAtom,
41+
}: {
42+
hasPriceIncreasedAtom: HasPriceIncreasedAtom;
43+
}) => {
44+
const [hasPriceIncreased] = useAtom(hasPriceIncreasedAtom);
45+
46+
const iconName: IconName = hasPriceIncreased ? 'arrowUp' : 'arrowDown';
47+
48+
return (
49+
<Icon
50+
name={iconName}
51+
color={getColorForPercentageChange(hasPriceIncreased)}
52+
size="extraSmall"
53+
/>
54+
);
55+
};
56+
57+
const arrowStyle = prepareNativeStyle(() => ({
58+
marginRight: 4,
59+
}));
60+
61+
const priceIncreaseWrapperStyle = prepareNativeStyle<{ hasPriceIncreased: boolean }>(
62+
(utils, { hasPriceIncreased }) => ({
63+
backgroundColor: hasPriceIncreased
64+
? utils.colors.backgroundPrimarySubtleOnElevation0
65+
: utils.colors.backgroundAlertRedSubtleOnElevation0,
66+
flexDirection: 'row',
67+
alignItems: 'center',
68+
paddingHorizontal: utils.spacings.small,
69+
paddingVertical: utils.spacings.small / 4,
70+
borderRadius: utils.borders.radii.round,
71+
}),
72+
);
73+
74+
export const PriceChangeIndicator = ({
75+
hasPriceIncreasedAtom,
76+
percentageChangeAtom,
77+
}: PriceChangeIndicatorProps) => {
78+
const { applyStyle } = useNativeStyles();
79+
const [hasPriceIncreased] = useAtom(hasPriceIncreasedAtom);
80+
81+
return (
82+
<Box style={applyStyle(priceIncreaseWrapperStyle, { hasPriceIncreased })}>
83+
<Box style={applyStyle(arrowStyle)}>
84+
<PercentageChangeArrow hasPriceIncreasedAtom={hasPriceIncreasedAtom} />
85+
</Box>
86+
<PercentageChange
87+
hasPriceIncreasedAtom={hasPriceIncreasedAtom}
88+
percentageChangeAtom={percentageChangeAtom}
89+
/>
90+
</Box>
91+
);
92+
};

suite-native/graph/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export * from './components/Graph';
22
export * from './components/TimeSwitch';
33
export * from './utils';
44
export * from './hooks';
5+
export * from './components/GraphDateFormatter';
6+
export * from './components/PriceChangeIndicator';

suite-native/graph/src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,8 @@ export const getExtremaFromGraphPoints = (points: EnhancedGraphPoint[]) => {
7474
};
7575
}
7676
};
77+
78+
export const percentageDiff = (a: number, b: number) => {
79+
if (a === 0 || b === 0) return 0;
80+
return 100 * ((b - a) / ((b + a) / 2));
81+
};

suite-native/graph/tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
"extends": "../../tsconfig.json",
33
"compilerOptions": { "outDir": "libDev" },
44
"references": [
5+
{
6+
"path": "../../suite-common/formatters"
7+
},
58
{ "path": "../../suite-common/graph" },
69
{
710
"path": "../../suite-common/wallet-core"
@@ -12,6 +15,7 @@
1215
{ "path": "../atoms" },
1316
{ "path": "../formatters" },
1417
{ "path": "../react-native-graph" },
18+
{ "path": "../../packages/icons" },
1519
{ "path": "../../packages/styles" }
1620
]
1721
}

0 commit comments

Comments
 (0)