Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { A, pipe } from '@mobily/ts-belt';

import { makeFormatter } from '../makeFormatter';
import { FormatterConfig } from '../types';
import { prepareDateFormatter } from './prepareDateFormatter';
Expand All @@ -11,5 +9,5 @@ export const prepareDateTimeFormatter = (config: FormatterConfig) =>
const DateFormatter = prepareDateFormatter(config);
const TimeFormatter = prepareTimeFormatter(config);

return pipe([TimeFormatter.format(value), DateFormatter.format(value)], A.join(' '));
return `${DateFormatter.format(value)}, ${TimeFormatter.format(value)}`;
});
58 changes: 39 additions & 19 deletions suite-common/graph/src/graphDataFetching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,24 @@ export const addBalanceForAccountMovementHistory = (
data: AccountMovementHistory[],
symbol: NetworkSymbol,
): AccountHistoryBalancePoint[] => {
let balance = '0';
let balance = new BigNumber('0');
const historyWithBalance = data.map(dataPoint => {
// subtract sentToSelf field as we don't want to include amounts received/sent to the same account
const normalizedReceived = dataPoint.sentToSelf
? new BigNumber(dataPoint.received).minus(dataPoint.sentToSelf || 0).toFixed()
? new BigNumber(dataPoint.received).minus(dataPoint.sentToSelf || 0)
: dataPoint.received;
const normalizedSent = dataPoint.sentToSelf
? new BigNumber(dataPoint.sent).minus(dataPoint.sentToSelf || 0).toFixed()
? new BigNumber(dataPoint.sent).minus(dataPoint.sentToSelf || 0)
: dataPoint.sent;

balance = new BigNumber(balance).plus(normalizedReceived).minus(normalizedSent).toFixed();
balance = new BigNumber(balance).plus(normalizedReceived).minus(normalizedSent);

// for some coins like ETH, simple sum of received and sent is not enough and could result in nonsense like negative balance
balance = balance.isNegative() ? new BigNumber('0') : balance;

return {
time: dataPoint.time,
cryptoBalance: formatNetworkAmount(balance, symbol),
cryptoBalance: formatNetworkAmount(balance.toFixed(), symbol),
};
});

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

const accountMovementHistory = await TrezorConnect.blockchainGetAccountBalanceHistory({
coin,
descriptor,
to: endTimeFrameTimestamp,
// we don't need currencies at all here, this will just reduce transferred data size
// TODO: doesn't work at all, fix it in connect or blockchain-link?
currencies: ['usd'],
});
const [accountMovementHistory, accountInfo] = await Promise.all([
TrezorConnect.blockchainGetAccountBalanceHistory({
coin,
descriptor,
to: endTimeFrameTimestamp,
// we don't need currencies at all here, this will just reduce transferred data size
// TODO: doesn't work at all, fix it in connect or blockchain-link?
currencies: ['usd'],
}),
TrezorConnect.getAccountInfo({ coin, descriptor }),
]);

if (!accountMovementHistory?.success) {
throw new Error(`Get account balance error: ${accountMovementHistory.payload.error}`);
throw new Error(
`Get account balance movement error: ${accountMovementHistory.payload.error}`,
);
}

if (!accountInfo?.success) {
throw new Error(`Get account balance info error: ${accountInfo.payload.error}`);
}

const accountMovementHistoryWithBalance = addBalanceForAccountMovementHistory(
accountMovementHistory.payload,
coin,
);

// Last point must be balance from getAccountInfo because blockchainGetAccountBalanceHistory it's not always reliable for coins like ETH.
// TODO: We can get value from redux store instead of fetching it again?
accountMovementHistoryWithBalance.push({
time: endTimeFrameTimestamp,
cryptoBalance: formatNetworkAmount(accountInfo.payload.balance, coin),
});

accountBalanceHistoryCache[cacheKey] = accountMovementHistoryWithBalance;

return accountMovementHistoryWithBalance;
Expand Down Expand Up @@ -166,11 +185,12 @@ export const getMultipleAccountBalanceHistoryWithFiat = async ({
);
}

const timestamps = getTimestampsInTimeFrame(
startOfTimeFrameDate,
endOfTimeFrameDate,
numberOfPoints,
);
// Last timestamp must be endOfTimeFrameDate because blockchainGetAccountBalanceHistory it's not always reliable for coins like ETH.
// So we manually add balance from getAccountInfo for last point in getAccountBalanceHistory.
const timestamps = [
...getTimestampsInTimeFrame(startOfTimeFrameDate, endOfTimeFrameDate, numberOfPoints - 1),
getUnixTime(endOfTimeFrameDate),
];

const coins = pipe(
accounts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { FormatterProps } from '../types';
import { EmptyAmountText } from './EmptyAmountText';
import { AmountText } from './AmountText';

type CryptoToFiatAmountFormatterProps = FormatterProps<string | number | null> &
type FiatAmountFormatterProps = FormatterProps<string | null> &
TextProps & {
network?: NetworkSymbol;
isDiscreetText?: boolean;
Expand All @@ -20,7 +20,7 @@ export const FiatAmountFormatter = ({
value,
isDiscreetText = true,
...textProps
}: CryptoToFiatAmountFormatterProps) => {
}: FiatAmountFormatterProps) => {
const { FiatAmountFormatter: formatter } = useFormatters();

const isTestnetValue = !!network && isTestnet(network);
Expand Down
3 changes: 3 additions & 0 deletions suite-native/graph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
"dependencies": {
"@mobily/ts-belt": "^3.13.1",
"@shopify/react-native-skia": "0.1.173",
"@suite-common/formatters": "workspace:*",
"@suite-common/graph": "workspace:*",
"@suite-common/wallet-core": "workspace:*",
"@suite-common/wallet-types": "workspace:*",
"@suite-native/atoms": "workspace:*",
"@suite-native/formatters": "workspace:*",
"@suite-native/react-native-graph": "workspace:*",
"@trezor/icons": "workspace:*",
"@trezor/styles": "workspace:*",
"date-fns": "^2.29.3",
"jotai": "1.9.1",
"react": "18.2.0",
"react-native": "0.71.3",
"react-native-reanimated": "2.14.4",
Expand Down
2 changes: 1 addition & 1 deletion suite-native/graph/src/components/AxisLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const AxisLabel = ({ x, value }: AxisLabelProps) => {

return (
<View style={applyStyle(axisLabelStyle, { x })}>
<FiatAmountFormatter value={value} />
<FiatAmountFormatter value={String(value)} />
</View>
);
};
51 changes: 51 additions & 0 deletions suite-native/graph/src/components/GraphDateFormatter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';

import { differenceInDays, isSameDay } from 'date-fns';
import { Atom, useAtomValue } from 'jotai';

import { useFormatters } from '@suite-common/formatters';

import { EnhancedGraphPoint } from '../utils';

type SelectedPointAtom = Atom<EnhancedGraphPoint>;

type GraphDateFormatterProps = {
firstPointDate: Date;
selectedPointAtom: Atom<EnhancedGraphPoint>;
};

const SameDayFormatter = ({ selectedPointAtom }: { selectedPointAtom: SelectedPointAtom }) => {
const { TimeFormatter } = useFormatters();
const point = useAtomValue(selectedPointAtom);
return <TimeFormatter value={point.date} />;
};

const WeekFormatter = ({ selectedPointAtom }: { selectedPointAtom: SelectedPointAtom }) => {
const { DateTimeFormatter, TimeFormatter } = useFormatters();
const { originalDate: value } = useAtomValue(selectedPointAtom);
if (isSameDay(value, new Date())) {
return <TimeFormatter value={value} />;
}
return <DateTimeFormatter value={value} />;
};

const OtherDateFormatter = ({ selectedPointAtom }: { selectedPointAtom: SelectedPointAtom }) => {
const { DateFormatter } = useFormatters();

const { originalDate: value } = useAtomValue(selectedPointAtom);
return <DateFormatter value={value} />;
};

export const GraphDateFormatter = ({
firstPointDate,
selectedPointAtom,
}: GraphDateFormatterProps) => {
if (isSameDay(firstPointDate, new Date())) {
return <SameDayFormatter selectedPointAtom={selectedPointAtom} />;
}
if (differenceInDays(firstPointDate, new Date()) < 7) {
return <WeekFormatter selectedPointAtom={selectedPointAtom} />;
}

return <OtherDateFormatter selectedPointAtom={selectedPointAtom} />;
};
92 changes: 92 additions & 0 deletions suite-native/graph/src/components/PriceChangeIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React from 'react';

import { Atom, useAtom } from 'jotai';

import { Box, Text } from '@suite-native/atoms';
import { Icon, IconName } from '@trezor/icons';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';

type PriceChangeIndicatorProps = {
percentageChangeAtom: PercentageChangeAtom;
hasPriceIncreasedAtom: HasPriceIncreasedAtom;
};

const getColorForPercentageChange = (hasIncreased: boolean) =>
hasIncreased ? 'textPrimaryDefault' : 'textAlertRed';

type PercentageChangeAtom = Atom<number>;
type HasPriceIncreasedAtom = Atom<boolean>;

type PercentageChangeProps = {
percentageChangeAtom: PercentageChangeAtom;
hasPriceIncreasedAtom: HasPriceIncreasedAtom;
};

const PercentageChange = ({
percentageChangeAtom,
hasPriceIncreasedAtom,
}: PercentageChangeProps) => {
const [percentageChange] = useAtom(percentageChangeAtom);
const [hasPriceIncreased] = useAtom(hasPriceIncreasedAtom);

return (
<Text color={getColorForPercentageChange(hasPriceIncreased)} variant="hint">
{percentageChange.toFixed(2)}%
</Text>
);
};

const PercentageChangeArrow = ({
hasPriceIncreasedAtom,
}: {
hasPriceIncreasedAtom: HasPriceIncreasedAtom;
}) => {
const [hasPriceIncreased] = useAtom(hasPriceIncreasedAtom);

const iconName: IconName = hasPriceIncreased ? 'arrowUp' : 'arrowDown';

return (
<Icon
name={iconName}
color={getColorForPercentageChange(hasPriceIncreased)}
size="extraSmall"
/>
);
};

const arrowStyle = prepareNativeStyle(() => ({
marginRight: 4,
}));

const priceIncreaseWrapperStyle = prepareNativeStyle<{ hasPriceIncreased: boolean }>(
(utils, { hasPriceIncreased }) => ({
backgroundColor: hasPriceIncreased
? utils.colors.backgroundPrimarySubtleOnElevation0
: utils.colors.backgroundAlertRedSubtleOnElevation0,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: utils.spacings.small,
paddingVertical: utils.spacings.small / 4,
borderRadius: utils.borders.radii.round,
}),
);

export const PriceChangeIndicator = ({
hasPriceIncreasedAtom,
percentageChangeAtom,
}: PriceChangeIndicatorProps) => {
const { applyStyle } = useNativeStyles();
const [hasPriceIncreased] = useAtom(hasPriceIncreasedAtom);

return (
<Box style={applyStyle(priceIncreaseWrapperStyle, { hasPriceIncreased })}>
<Box style={applyStyle(arrowStyle)}>
<PercentageChangeArrow hasPriceIncreasedAtom={hasPriceIncreasedAtom} />
</Box>
<PercentageChange
hasPriceIncreasedAtom={hasPriceIncreasedAtom}
percentageChangeAtom={percentageChangeAtom}
/>
</Box>
);
};
2 changes: 2 additions & 0 deletions suite-native/graph/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export * from './components/Graph';
export * from './components/TimeSwitch';
export * from './utils';
export * from './hooks';
export * from './components/GraphDateFormatter';
export * from './components/PriceChangeIndicator';
5 changes: 5 additions & 0 deletions suite-native/graph/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@ export const getExtremaFromGraphPoints = (points: EnhancedGraphPoint[]) => {
};
}
};

export const percentageDiff = (a: number, b: number) => {
if (a === 0 || b === 0) return 0;
return 100 * ((b - a) / ((b + a) / 2));
};
4 changes: 4 additions & 0 deletions suite-native/graph/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"extends": "../../tsconfig.json",
"compilerOptions": { "outDir": "libDev" },
"references": [
{
"path": "../../suite-common/formatters"
},
{ "path": "../../suite-common/graph" },
{
"path": "../../suite-common/wallet-core"
Expand All @@ -12,6 +15,7 @@
{ "path": "../atoms" },
{ "path": "../formatters" },
{ "path": "../react-native-graph" },
{ "path": "../../packages/icons" },
{ "path": "../../packages/styles" }
]
}
Loading