diff --git a/src/components/expanded-state/chart/ChartExpandedStateHeader.js b/src/components/expanded-state/chart/ChartExpandedStateHeader.js index 0afd18adfd8..96f58ed9df5 100644 --- a/src/components/expanded-state/chart/ChartExpandedStateHeader.js +++ b/src/components/expanded-state/chart/ChartExpandedStateHeader.js @@ -1,19 +1,19 @@ import lang from 'i18n-js'; import React, { useMemo } from 'react'; +import { View } from 'react-native'; import Animated, { FadeIn, useAnimatedStyle, withTiming } from 'react-native-reanimated'; -import { ColumnWithMargins, Row } from '../../layout'; -import ChartContextButton from './ChartContextButton'; -import { ChartDateLabel, ChartPercentChangeLabel, ChartPriceLabel } from './chart-data-labels'; -import { useChartData } from '@/react-native-animated-charts/src'; +import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { Column, Columns, Text } from '@/design-system'; import ChartTypes from '@/helpers/chartTypes'; import { convertAmountToNativeDisplay } from '@/helpers/utilities'; import { useAccountSettings } from '@/hooks'; +import { useChartData } from '@/react-native-animated-charts/src'; import styled from '@/styled-thing'; import { padding } from '@/styles'; -import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { View } from 'react-native'; -import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; +import { ColumnWithMargins, Row } from '../../layout'; +import ChartContextButton from './ChartContextButton'; +import { ChartDateLabel, ChartPercentChangeLabel, ChartPriceLabel } from './chart-data-labels'; const noPriceData = lang.t('expanded_state.chart.no_price_data'); @@ -26,7 +26,6 @@ const Container = styled(ColumnWithMargins).attrs({ export default function ChartExpandedStateHeader({ asset, color: givenColors, - dateRef, isPool, latestChange, latestPrice = noPriceData, @@ -84,7 +83,7 @@ export default function ChartExpandedStateHeader({ const firstValue = data?.points?.[0]?.y; const lastValue = data?.points?.[data.points.length - 1]?.y; - return firstValue === Number(firstValue) ? lastValue / firstValue : 1; + return firstValue === Number(firstValue) ? lastValue / firstValue : undefined; }, [data]); const showPriceChangeStyle = useAnimatedStyle(() => { @@ -135,7 +134,7 @@ export default function ChartExpandedStateHeader({ - + diff --git a/src/components/expanded-state/chart/chart-data-labels/ChartDateLabel.js b/src/components/expanded-state/chart/chart-data-labels/ChartDateLabel.tsx similarity index 69% rename from src/components/expanded-state/chart/chart-data-labels/ChartDateLabel.js rename to src/components/expanded-state/chart/chart-data-labels/ChartDateLabel.tsx index 344067f6eec..6308b7a450d 100644 --- a/src/components/expanded-state/chart/chart-data-labels/ChartDateLabel.js +++ b/src/components/expanded-state/chart/chart-data-labels/ChartDateLabel.tsx @@ -1,8 +1,9 @@ import lang from 'i18n-js'; -import React from 'react'; -import { useAnimatedStyle } from 'react-native-reanimated'; +import React, { useCallback } from 'react'; +import Animated, { AnimatedStyle, FadeIn, useAnimatedStyle } from 'react-native-reanimated'; import { useRatio } from './useRatio'; import { ChartXLabel, useChartData } from '@/react-native-animated-charts/src'; +import { useTheme } from '@/theme'; const MONTHS = [ lang.t('expanded_state.chart.date.months.month_00'), @@ -19,7 +20,7 @@ const MONTHS = [ lang.t('expanded_state.chart.date.months.month_11'), ]; -function formatDatetime(value, chartTimeDefaultValue) { +function formatDatetime(value: string, chartTimeDefaultValue: string) { 'worklet'; // we have to do it manually due to limitations of reanimated if (value === '') { @@ -70,25 +71,37 @@ function formatDatetime(value, chartTimeDefaultValue) { return res; } -export default function ChartDateLabel({ chartTimeDefaultValue, ratio }) { +export default function ChartDateLabel({ + chartTimeDefaultValue, + ratio, + showPriceChangeStyle, +}: { + chartTimeDefaultValue: string; + ratio: number | undefined; + showPriceChangeStyle: AnimatedStyle; +}) { const { isActive } = useChartData(); - const sharedRatio = useRatio('ChartDataLabel'); + const sharedRatio = useRatio(); const { colors } = useTheme(); const textStyle = useAnimatedStyle(() => { const realRatio = isActive.value ? sharedRatio.value : ratio; return { - color: realRatio === 1 ? colors.blueGreyDark : realRatio < 1 ? colors.red : colors.green, + color: realRatio !== undefined ? (realRatio === 1 ? colors.blueGreyDark : realRatio < 1 ? colors.red : colors.green) : 'transparent', }; - }, [ratio]); + }); const formatWorklet = useCallback( - value => { + (value: string) => { 'worklet'; return formatDatetime(value, chartTimeDefaultValue); }, [chartTimeDefaultValue] ); - return ; + return ( + + + + ); } diff --git a/src/components/expanded-state/chart/chart-data-labels/ChartPercentChangeLabel.js b/src/components/expanded-state/chart/chart-data-labels/ChartPercentChangeLabel.tsx similarity index 57% rename from src/components/expanded-state/chart/chart-data-labels/ChartPercentChangeLabel.js rename to src/components/expanded-state/chart/chart-data-labels/ChartPercentChangeLabel.tsx index 8e14ee25ed3..2dca8a3670b 100644 --- a/src/components/expanded-state/chart/chart-data-labels/ChartPercentChangeLabel.js +++ b/src/components/expanded-state/chart/chart-data-labels/ChartPercentChangeLabel.tsx @@ -1,11 +1,13 @@ import React, { memo } from 'react'; -import { useAnimatedStyle, useDerivedValue } from 'react-native-reanimated'; +import { DerivedValue, SharedValue, useAnimatedStyle, useDerivedValue } from 'react-native-reanimated'; import { AnimatedText } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { useChartData } from '@/react-native-animated-charts/src'; +import { useTheme } from '@/theme'; import { useRatio } from './useRatio'; +import { DataType } from '@/react-native-animated-charts/src/helpers/ChartContext'; -function formatNumber(num) { +function formatNumber(num: string) { 'worklet'; const first = num.split('.'); const digits = first[0].split('').reverse(); @@ -19,25 +21,29 @@ function formatNumber(num) { return newDigits.reverse().join('') + '.' + first[1]; } -const formatWorklet = (originalY, data, latestChange) => { +const formatWorklet = (originalY: SharedValue, data: DataType, latestChange: number | undefined) => { 'worklet'; const firstValue = data?.points?.[0]?.y; const lastValue = data?.points?.[data.points.length - 1]?.y; return firstValue === Number(firstValue) && firstValue ? (() => { + const originalYNumber = Number(originalY?.value); const value = - originalY?.value === lastValue || !originalY?.value - ? parseFloat(latestChange ?? 0) - : ((originalY.value || lastValue) / firstValue) * 100 - 100; - const numValue = parseFloat(value); + originalYNumber === lastValue || !originalYNumber ? latestChange ?? 0 : ((originalYNumber || lastValue) / firstValue) * 100 - 100; - return (IS_ANDROID ? '' : numValue > 0 ? '↑' : numValue < 0 ? '↓' : '') + ' ' + formatNumber(Math.abs(numValue).toFixed(2)) + '%'; + return (IS_ANDROID ? '' : value > 0 ? '↑' : value < 0 ? '↓' : '') + ' ' + formatNumber(Math.abs(value).toFixed(2)) + '%'; })() : ''; }; -export default memo(function ChartPercentChangeLabel({ ratio, latestChange }) { +export default memo(function ChartPercentChangeLabel({ + latestChange, + ratio, +}: { + latestChange: DerivedValue; + ratio: number | undefined; +}) { const { originalY, data, isActive } = useChartData(); const { colors } = useTheme(); @@ -47,9 +53,9 @@ export default memo(function ChartPercentChangeLabel({ ratio, latestChange }) { const textStyle = useAnimatedStyle(() => { const realRatio = isActive.value ? sharedRatio.value : ratio; return { - color: realRatio === 1 ? colors.blueGreyDark : realRatio < 1 ? colors.red : colors.green, + color: realRatio !== undefined ? (realRatio === 1 ? colors.blueGreyDark : realRatio < 1 ? colors.red : colors.green) : 'transparent', }; - }, [ratio]); + }); return ( ({ const Container = styled(Column)({ paddingBottom: 30, - paddingTop: ios ? 0 : 20, + paddingTop: IS_IOS ? 0 : 4, width: '100%', }); diff --git a/src/components/value-chart/ExtremeLabels.js b/src/components/value-chart/ExtremeLabels.tsx similarity index 58% rename from src/components/value-chart/ExtremeLabels.js rename to src/components/value-chart/ExtremeLabels.tsx index 47b3bc07a78..c4a689039ed 100644 --- a/src/components/value-chart/ExtremeLabels.js +++ b/src/components/value-chart/ExtremeLabels.tsx @@ -1,23 +1,39 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { View } from 'react-native'; +import { LayoutChangeEvent, StyleProp, View, ViewStyle } from 'react-native'; import { formatNative } from '../expanded-state/chart/chart-data-labels/ChartPriceLabel'; import { useChartData } from '@/react-native-animated-charts/src'; import { Text } from '@/design-system'; import { useAccountSettings } from '@/hooks'; import { supportedNativeCurrencies } from '@/references'; +import { useTheme } from '@/theme'; +import { TextSize } from '@/design-system/typography/typeHierarchy'; -function trim(val) { +function trim(val: number) { return Math.min(Math.max(val, 0.05), 0.95); } -const CenteredLabel = ({ position, fontSize = '14px / 19px (Deprecated)', style, width, ...props }) => { +const CenteredLabel = ({ + children, + color, + position, + size = '14px / 19px (Deprecated)', + style, + width, +}: { + children: React.ReactNode; + color: string; + position: number; + size?: TextSize; + style: StyleProp; + width: number; +}) => { const [componentWidth, setWidth] = useState(0); const onLayout = useCallback( ({ nativeEvent: { layout: { width: newWidth }, }, - }) => { + }: LayoutChangeEvent) => { setWidth(newWidth); }, [setWidth] @@ -30,29 +46,30 @@ const CenteredLabel = ({ position, fontSize = '14px / 19px (Deprecated)', style, return ( - - {props.children} + + {children} ); }; -const Labels = ({ color, width, isCard }) => { +const Labels = ({ color, width, isCard }: { color: string; width: number; isCard: boolean }) => { const { nativeCurrency } = useAccountSettings(); const nativeSelected = supportedNativeCurrencies?.[nativeCurrency]; const { greatestX, greatestY, smallestX, smallestY } = useChartData(); const { colors } = useTheme(); - if (!greatestX) { - return null; - } + if (!greatestX || !greatestY || !smallestX || !smallestY) return null; + const positionMin = trim((smallestY.x - smallestX.x) / (greatestX.x - smallestX.x)); const positionMax = trim((greatestY.x - smallestX.x) / (greatestX.x - smallestX.x)); @@ -62,27 +79,22 @@ const Labels = ({ color, width, isCard }) => { - {formatNative(smallestY.y, null, nativeSelected)} + {formatNative(smallestY.y.toString(), null, nativeSelected)} ) : null} {positionMax ? ( - {formatNative(greatestY.y, null, nativeSelected)} + {formatNative(greatestY.y.toString(), null, nativeSelected)} ) : null} diff --git a/src/hooks/charts/useChartDataLabels.ts b/src/hooks/charts/useChartDataLabels.ts index 53d023ae749..2e6979739c3 100644 --- a/src/hooks/charts/useChartDataLabels.ts +++ b/src/hooks/charts/useChartDataLabels.ts @@ -1,15 +1,32 @@ import { useCallback } from 'react'; -import ChartTypes, { ChartType } from '@/helpers/chartTypes'; -import { ChartData } from './useChartInfo'; import { useDerivedValue } from 'react-native-reanimated'; +import ChartTypes, { ChartType } from '@/helpers/chartTypes'; import { toFixedWorklet } from '@/safe-math/SafeMath'; +import { AssetApiResponse, AssetMetadata } from '@/__swaps__/types/assets'; +import { ChartData } from './useChartInfo'; const formatPercentChange = (change = 0) => { 'worklet'; return toFixedWorklet(change, 2); }; -export default function useChartDataLabels({ asset, chartType, points }: { asset: any; chartType: ChartType; points: ChartData[] }) { +type AssetWithPrice = (AssetApiResponse | AssetMetadata) & { + price?: { + relative_change_24h?: number; + relativeChange24h?: number; + value: number; + }; +}; + +export default function useChartDataLabels({ + asset, + chartType, + points, +}: { + asset: AssetWithPrice; + chartType: ChartType; + points: ChartData[]; +}) { const latestPrice = asset?.price?.value; const getPercentChangeForPrice = useCallback( @@ -23,11 +40,14 @@ export default function useChartDataLabels({ asset, chartType, points }: { asset [points] ); - const latestChange = useDerivedValue(() => - !points || chartType === ChartTypes.day - ? formatPercentChange(asset?.price?.relative_change_24h || asset?.price?.relativeChange24h) - : getPercentChangeForPrice(points[0]?.y ?? 0) - ); + const latestChange = useDerivedValue(() => { + if (!points || chartType === ChartTypes.day) { + // Handle both AssetApiResponse and AssetMetadata price change fields + const change = asset?.price?.relative_change_24h ?? asset?.price?.relativeChange24h ?? 0; + return formatPercentChange(change); + } + return getPercentChangeForPrice(points[0]?.y ?? 0); + }); return { latestChange, diff --git a/src/react-native-animated-charts/src/charts/linear/ChartPathProvider.tsx b/src/react-native-animated-charts/src/charts/linear/ChartPathProvider.tsx index bc4659cba17..80d797dcb5a 100644 --- a/src/react-native-animated-charts/src/charts/linear/ChartPathProvider.tsx +++ b/src/react-native-animated-charts/src/charts/linear/ChartPathProvider.tsx @@ -19,7 +19,7 @@ interface ChartPathProviderProps { children: React.ReactNode; } -function getCurveType(curveType: keyof typeof CurveType) { +function getCurveType(curveType: keyof typeof CurveType | undefined) { switch (curveType) { case CurveType.basis: return shape.curveBasis; @@ -97,7 +97,7 @@ function createPath({ data, width, height, yRange }: CallbackType): PathData { .line() .x(item => scaleX(item.x)) .y(item => scaleY(item.y)) - .curve(getCurveType(data.curve!))(data.points); + .curve(getCurveType(data.curve))(data.points); if (path === null) { return { @@ -161,7 +161,7 @@ export const ChartPathProvider = React.memo(({ children, // because we do have initial data in the paths // we wait until we receive new stuff in deps if (initialized.current) { - setPaths(([_, curr]) => [curr, data.points.length ? createPath({ data, height, width, yRange }) : null]); + setPaths(([, curr]) => [curr, data.points.length ? createPath({ data, height, width, yRange }) : null]); } else { // componentDidMount hack initialized.current = true; diff --git a/src/react-native-animated-charts/src/helpers/ChartContext.ts b/src/react-native-animated-charts/src/helpers/ChartContext.ts index 118c8d60feb..cb30230808a 100644 --- a/src/react-native-animated-charts/src/helpers/ChartContext.ts +++ b/src/react-native-animated-charts/src/helpers/ChartContext.ts @@ -46,7 +46,9 @@ export interface PathScales { scaleY: (value: number) => number; } -export interface ChartData { +type WithPathData = Pick; + +export type ChartData = { data: DataType; width: number; height: number; @@ -61,6 +63,6 @@ export interface ChartData { positionY: SharedValue; previousPath: PathData | null; currentPath: PathData | null; -} +} & WithPathData; export const ChartContext = React.createContext(null);