Skip to content

Commit

Permalink
feature(mobile): New chart data provider (#716)
Browse files Browse the repository at this point in the history
  • Loading branch information
voloshinskii authored Feb 16, 2024
1 parent f7f27a7 commit 7eda434
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 119 deletions.
223 changes: 192 additions & 31 deletions packages/@core-js/src/TonAPI/TonAPIGenerated.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/mobile/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type AppConfigVars = {
holdersAppEndpoint: string;
holdersService: string;
disable_battery: boolean;
disable_jetton_charts: boolean;
disable_battery_iap_module: boolean;
disable_battery_send: boolean;
disable_show_unverified_token: boolean;
Expand Down Expand Up @@ -85,6 +86,7 @@ const defaultConfig: Partial<AppConfigVars> = {
disable_show_unverified_token: false,
disable_tonstakers: false,
disable_holders_cards: true,
disable_jetton_charts: false,
};

export const config = new AppConfig<AppConfigVars>({
Expand Down
9 changes: 6 additions & 3 deletions packages/mobile/src/core/Jetton/Jetton.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export const Wrap = styled.View`
flex: 1;
`;

export const ChartWrap = styled.View`
margin-bottom: ${ns(24.5)}px;
margin-top: 18px;
`;

export const HeaderWrap = styled.View`
align-items: center;
padding-horizontal: ${ns(16)}px;
Expand Down Expand Up @@ -101,15 +106,13 @@ export const ActionLabelWrapper = styled.View`
margin-top: ${ns(2)}px;
`;


export const ActionsContainer = styled.View`
justify-content: center;
flex-direction: row;
margin-bottom: ${ns(12)}px;
`;

export const IconWrap = styled.View`
`;
export const IconWrap = styled.View``;

export const HeaderViewDetailsButton = styled(TouchableOpacity).attrs({
activeOpacity: Opacity.ForSmall,
Expand Down
20 changes: 17 additions & 3 deletions packages/mobile/src/core/Jetton/Jetton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import { openReceiveJettonModal } from '@tonkeeper/shared/modals/ReceiveJettonMo
import { TokenType } from '$core/Send/Send.interface';
import { config } from '$config';
import { openUnverifiedTokenDetailsModal } from '@tonkeeper/shared/modals/UnverifiedTokenDetailsModal';
import { useWallet } from '@tonkeeper/shared/hooks';
import { useWallet, useWalletCurrency } from '@tonkeeper/shared/hooks';
import { tk } from '$wallet';
import { Chart } from '$shared/components/Chart/new/Chart';

const unverifiedTokenHitSlop = { top: 4, left: 4, bottom: 4, right: 4 };

Expand All @@ -38,6 +39,8 @@ export const Jetton: React.FC<JettonProps> = ({ route }) => {
const wallet = useWallet();

const isWatchOnly = wallet && wallet.isWatchOnly;
const fiatCurrency = useWalletCurrency();
const shouldShowChart = !config.get('disable_jetton_charts') && jettonPrice.fiat !== 0;

const nav = useNavigation();

Expand Down Expand Up @@ -123,6 +126,14 @@ export const Jetton: React.FC<JettonProps> = ({ route }) => {
) : null}
</S.ActionsContainer>
<S.Divider style={{ marginBottom: 10 }} />
{shouldShowChart && (
<>
<S.ChartWrap>
<Chart currency={fiatCurrency} token={route.params.jettonAddress} />
</S.ChartWrap>
<S.Divider style={{ marginBottom: 0 }} />
</>
)}
</S.HeaderWrap>
);
}, [
Expand All @@ -134,6 +145,9 @@ export const Jetton: React.FC<JettonProps> = ({ route }) => {
showSwap,
flags.disable_swap,
handlePressSwap,
shouldShowChart,
fiatCurrency,
route.params.jettonAddress,
]);

if (!jetton) {
Expand Down Expand Up @@ -170,12 +184,12 @@ export const Jetton: React.FC<JettonProps> = ({ route }) => {
shouldCloseMenu
onPress={handleOpenExplorer}
text={t('jetton_open_explorer')}
icon={<Icon name="ic-globe-16" color="accentPrimary" />}
icon={<Icon name="ic-globe-16" color="accentBlue" />}
/>,
]}
>
<S.HeaderViewDetailsButton onPress={() => null}>
<Icon name="ic-ellipsis-16" color="foregroundPrimary" />
<Icon name="ic-ellipsis-16" color="iconPrimary" />
</S.HeaderViewDetailsButton>
</PopupMenu>
}
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/core/Wallet/ToncoinScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ const HeaderList = memo(() => {
{shouldShowChart && (
<>
<S.ChartWrap>
<Chart />
<Chart currency={fiatCurrency} token={'ton'} />
</S.ChartWrap>
<S.Divider style={{ marginBottom: 0 }} />
</>
Expand Down
6 changes: 4 additions & 2 deletions packages/mobile/src/hooks/usePreloadChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import { useChartStore } from '$store/zustand/chart';
import { loadChartData } from '$shared/components/Chart/new/Chart.api';
import { useEffect } from 'react';
import { useQueryClient } from 'react-query';
import { useWalletCurrency } from '@tonkeeper/shared/hooks';

export function usePreloadChart() {
const fiatCurrency = useWalletCurrency();
const selectedPeriod = useChartStore(
(state) => state.selectedPeriod,
() => true,
);
const queryClient = useQueryClient();

useEffect(() => {
queryClient.prefetchQuery(['chartFetch', selectedPeriod], () =>
loadChartData(selectedPeriod),
queryClient.prefetchQuery(['chartFetch', 'ton', fiatCurrency, selectedPeriod], () =>
loadChartData(selectedPeriod, 'ton', fiatCurrency),
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down
36 changes: 31 additions & 5 deletions packages/mobile/src/shared/components/Chart/new/Chart.api.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
import { config } from '$config';
import { ChartPeriod } from '$store/zustand/chart';
import { tk } from '$wallet';

export const loadChartData = (period) =>
fetch(`${config.get('tonkeeperEndpoint')}/stock/chart-new?period=${period}`).then(
(res) => res.json(),
);
const ONE_HOUR = 60 * 60;
const ONE_DAY = 24 * ONE_HOUR;

function getPeriodFromTimestamp(period) {
const dateNowSec = Math.round(Date.now() / 1000);
switch (period) {
case ChartPeriod.ONE_HOUR:
return dateNowSec - ONE_HOUR;
case ChartPeriod.ONE_DAY:
return dateNowSec - ONE_DAY;
case ChartPeriod.SEVEN_DAYS:
return dateNowSec - ONE_DAY * 7;
case ChartPeriod.ONE_MONTH:
return dateNowSec - ONE_DAY * 31;
case ChartPeriod.SIX_MONTHS:
return dateNowSec - ONE_DAY * 183;
case ChartPeriod.ONE_YEAR:
return dateNowSec - ONE_DAY * 366;
}
}

export function loadChartData(period: ChartPeriod, token: string, currency: string) {
return tk.wallet.tonapi.rates.getChartRates({
token,
currency,
end_date: Math.round(Date.now() / 1000),
start_date: getPeriodFromTimestamp(period),
});
}
95 changes: 40 additions & 55 deletions packages/mobile/src/shared/components/Chart/new/Chart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import {
ChartDot,
ChartPath,
Expand All @@ -8,8 +8,6 @@ import {
} from '@rainbow-me/animated-charts';
import { Dimensions, View } from 'react-native';
import { useTheme } from '$hooks/useTheme';
import { useTokenPrice } from '$hooks/useTokenPrice';
import { CryptoCurrencies } from '$shared/constants';
import { formatFiatCurrencyAmount } from '$utils/currency';
import { PriceLabel } from './PriceLabel/PriceLabel';
import { PercentDiff } from './PercentDiff/PercentDiff';
Expand All @@ -22,71 +20,56 @@ import { changeAlphaValue, convertHexToRGBA, ns } from '$utils';
import { ChartXLabels } from './ChartXLabels/ChartXLabels';
import { ChartPeriod, useChartStore } from '$store/zustand/chart';
import { Fallback } from './Fallback/Fallback';
import BigNumber from 'bignumber.js';
import { isIOS } from '@tonkeeper/uikit';
import { useWalletCurrency } from '@tonkeeper/shared/hooks';
import { WalletCurrency } from '@tonkeeper/core';
import { WalletCurrency } from '$shared/constants';

export const { width: SIZE } = Dimensions.get('window');

const ChartComponent: React.FC = () => {
export interface ChartProps {
token: string;
currency: WalletCurrency;
}

const ChartComponent: React.FC<ChartProps> = (props) => {
const theme = useTheme();
const selectedPeriod = useChartStore((state) => state.selectedPeriod);
const setChartPeriod = useChartStore((state) => state.actions.setChartPeriod);

const { isLoading, isFetching, data, isError } = useQuery({
queryKey: ['chartFetch', selectedPeriod],
queryFn: () => loadChartData(selectedPeriod),
queryKey: ['chartFetch', props.token, props.currency, selectedPeriod],
queryFn: () => loadChartData(selectedPeriod, props.token, props.currency),
refetchInterval: 60 * 1000,
keepPreviousData: true,
staleTime: 120 * 1000,
});

const [cachedData, setCachedData] = useState(data?.data ?? []);

useEffect(() => {
if ((data && !isFetching && !isLoading) || !cachedData) {
setCachedData(
selectedPeriod === ChartPeriod.ONE_HOUR
? stepInterpolation(data.data)
: data.data,
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, selectedPeriod, isFetching, isLoading]);

const fiatCurrency = useWalletCurrency();
const shouldRenderChart = !!cachedData.length;

const tonPrice = useTokenPrice(CryptoCurrencies.Ton);

const fiatRate = useMemo(() => {
if (fiatCurrency === WalletCurrency.USD) {
return 1;
}
const points = useMemo(
() => (data?.points ? data.points.map(([x, y]) => ({ x, y })).reverse() : []),
[data],
);

return new BigNumber(tonPrice.fiat).dividedBy(tonPrice.usd).toNumber();
}, [fiatCurrency, tonPrice.fiat, tonPrice.usd]);
const shouldRenderChart = !!points.length;

const [maxPrice, minPrice] = React.useMemo(() => {
if (!cachedData.length) {
if (!points.length) {
return ['0', '0'];
}
const mappedPoints = cachedData.map((o) => o.y);
const mappedPoints = points.map((o) => o.y);
return [Math.max(...mappedPoints), Math.min(...mappedPoints)].map((value) =>
formatFiatCurrencyAmount((value * fiatRate).toFixed(2), fiatCurrency, true),
formatFiatCurrencyAmount(value.toFixed(2), props.currency, true),
);
}, [cachedData, fiatRate, fiatCurrency]);
}, [points, props.currency]);

const [firstPoint, latestPoint] = React.useMemo(() => {
if (!cachedData.length) {
if (!points.length) {
return [0, 0];
}
const latest = cachedData[cachedData.length - 1].y;
const first = cachedData[0].y;
const latest = points[points.length - 1].y;
const first = points[0].y;
return [first, latest];
}, [cachedData]);
}, [points]);

if (isLoading && !cachedData) {
if (isLoading && !points) {
return null;
}

Expand All @@ -95,22 +78,24 @@ const ChartComponent: React.FC = () => {
<View>
<ChartPathProvider
data={{
points: cachedData,
points:
selectedPeriod === ChartPeriod.ONE_HOUR
? stepInterpolation(points)
: points,
}}
>
<View style={{ paddingHorizontal: 28 }}>
<Rate
fiatCurrency={fiatCurrency}
fiatRate={fiatRate}
latestPoint={latestPoint}
/>
<PercentDiff
fiatCurrency={fiatCurrency}
fiatRate={fiatRate}
latestPoint={latestPoint}
firstPoint={firstPoint}
/>
<PriceLabel selectedPeriod={selectedPeriod} />
{latestPoint ? (
<>
<Rate fiatCurrency={props.currency} latestPoint={latestPoint} />
<PercentDiff
fiatCurrency={props.currency}
latestPoint={latestPoint}
firstPoint={firstPoint}
/>
<PriceLabel selectedPeriod={selectedPeriod} />
</>
) : null}
</View>
<View style={{ paddingVertical: 30 }}>
<View>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { useTheme } from '$hooks/useTheme';
import { t } from '@tonkeeper/shared/i18n';
import { Skeleton, Text } from '$uikit';
import { deviceWidth, ns } from '$utils';
import { Skeleton } from '$uikit';
import { deviceWidth } from '$utils';
import { useNetInfo } from '@react-native-community/netinfo';
import React from 'react';
import { View } from 'react-native';
import { Steezy, View, Text } from '@tonkeeper/uikit';

export const Fallback: React.FC<{ isError?: boolean }> = (props) => {
const theme = useTheme();
const netInfo = useNetInfo();

return (
<View style={{ height: ns(180), alignItems: 'center', justifyContent: 'center' }}>
{props.isError || !netInfo.isConnected ? (
<View style={styles.wrap}>
{props.isError || netInfo.isConnected === false ? (
<>
<Text style={{ marginBottom: ns(2) }} variant="label1">
<Text style={styles.label.static} type="label1">
{t('chart.no_internet')}
</Text>
<Text color="textSecondary" variant="body2">
<Text color="textSecondary" type="body2">
{t('chart.check_connection')}
</Text>
</>
Expand All @@ -33,3 +33,14 @@ export const Fallback: React.FC<{ isError?: boolean }> = (props) => {
</View>
);
};

const styles = Steezy.create({
wrap: {
height: 248,
alignItems: 'center',
justifyContent: 'center',
},
label: {
marginBottom: 2,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Platform } from 'react-native';
export interface PercentDiffProps {
latestPoint: number;
firstPoint: number;
fiatRate: number;
fiatCurrency: WalletCurrency;
}

Expand Down Expand Up @@ -41,12 +40,9 @@ const PercentDiffComponent: React.FC<PercentDiffProps> = (props) => {
const fiatInfo = React.useMemo(() => {
let percent = '0 %';
let color: TonThemeColor = 'foregroundSecondary';
let amountResult: string;

const diffInFiat = formatFiatCurrencyAmount(
Math.abs(
((props.firstPoint * parseFloat(priceDiff)) / 100) * props.fiatRate,
).toFixed(2),
Math.abs((props.firstPoint * parseFloat(priceDiff)) / 100).toFixed(2),
props.fiatCurrency,
true,
);
Expand Down Expand Up @@ -74,7 +70,7 @@ const PercentDiffComponent: React.FC<PercentDiffProps> = (props) => {
diffInFiat,
color,
};
}, [activePoint, priceDiff, props.fiatRate, props.fiatCurrency, priceDiffNumber]);
}, [props.firstPoint, props.fiatCurrency, priceDiff, priceDiffNumber]);

const formatPriceWrapper = (point: number) => {
if (!point) {
Expand Down
Loading

0 comments on commit 7eda434

Please sign in to comment.