Skip to content

Commit

Permalink
Merge c63876d into 5de2f0f
Browse files Browse the repository at this point in the history
  • Loading branch information
maxbbb authored Jan 29, 2025
2 parents 5de2f0f + c63876d commit 71fbba1
Show file tree
Hide file tree
Showing 31 changed files with 736 additions and 470 deletions.
54 changes: 39 additions & 15 deletions src/components/expanded-state/chart/ChartExpandedStateHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import React, { useMemo } from 'react';
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
import { TIMING_CONFIGS } from '@/components/animations/animationConfigs';
import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon';
import { Stack, Text, TextShadow, Bleed } from '@/design-system';
import { Stack, Text, TextShadow, Bleed, Box } from '@/design-system';
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 { ColumnWithMargins } from '../../layout';
import { ChartPercentChangeLabel, ChartPriceLabel } from './chart-data-labels';
import { ChartPercentChangeLabel, ChartPriceLabel, ChartDateLabel } from './chart-data-labels';
import ChartTypes from '@/helpers/chartTypes';

const noPriceData = lang.t('expanded_state.chart.no_price_data');

const Container = styled(ColumnWithMargins).attrs({
margin: 20,
})(({ showChart }) => ({
...padding.object(0, 19, showChart ? 36 : 0),
...padding.object(0, 24, showChart ? 36 : 0),
}));

export default function ChartExpandedStateHeader({
Expand All @@ -28,6 +29,7 @@ export default function ChartExpandedStateHeader({
latestPrice = noPriceData,
priceRef,
showChart,
chartType,
}) {
const theme = useTheme();
const color = givenColors || theme.colors.dark;
Expand All @@ -45,24 +47,42 @@ export default function ChartExpandedStateHeader({

const { data } = useChartData();

const chartDataExists = useMemo(() => {
const firstValue = data?.points?.[0]?.y;
return firstValue === Number(firstValue) && !!firstValue;
}, [data]);

const chartTimeDefaultValue = useMemo(() => {
const invertedChartTypes = Object.entries(ChartTypes).reduce((acc, [key, value]) => {
acc[value] = key;
return acc;
}, {});
const timespan = invertedChartTypes[chartType];

const formattedTimespan = timespan.charAt(0).toUpperCase() + timespan.slice(1);
if (chartType === ChartTypes.day) {
return lang.t('expanded_state.chart.today');
} else if (chartType === ChartTypes.max) {
return lang.t('expanded_state.chart.all_time');
} else {
return lang.t('expanded_state.chart.past_timespan', {
formattedTimespan,
});
}
// we need to make sure we recreate this value only when chart's data change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);

const price = useMemo(
() => convertAmountToNativeDisplay(latestPrice, nativeCurrency),
// we need to make sure we recreate this value only when chart's data change
// eslint-disable-next-line react-hooks/exhaustive-deps
[data, latestPrice, nativeCurrency]
);

const ratio = useMemo(() => {
const firstValue = data?.points?.[0]?.y;
const lastValue = data?.points?.[data.points.length - 1]?.y;

return firstValue === Number(firstValue) ? lastValue / firstValue : undefined;
}, [data]);

const showPriceChangeStyle = useAnimatedStyle(() => {
const showPriceChange = !isNoPriceData && showChart && !isNaN(latestChange.value);
const showPriceChange = !isNoPriceData && chartDataExists && showChart && !isNaN(latestChange.value);
return {
display: showPriceChange ? 'flex' : 'none',
opacity: withTiming(showPriceChange ? 1 : 0, TIMING_CONFIGS.slowFadeConfig),
};
});
Expand All @@ -71,6 +91,7 @@ export default function ChartExpandedStateHeader({
<Container testID={'expanded-state-header'} showChart={showChart}>
<Stack space={'20px'}>
<RainbowCoinIcon
chainSize={20}
size={44}
icon={asset?.iconUrl}
chainId={asset?.chainId}
Expand All @@ -80,18 +101,21 @@ export default function ChartExpandedStateHeader({
<TextShadow blur={12} shadowOpacity={0.24}>
<Text
color={{ custom: isNoPriceData ? theme.colors.alpha(theme.colors.blueGreyDark, 0.8) : color }}
numberOfLines={1}
numberOfLines={2}
size="22pt"
testID={`chart-header-${titleOrNoPriceData}`}
weight={isNoPriceData ? 'semibold' : 'bold'}
weight={isNoPriceData ? 'bold' : 'heavy'}
>
{titleOrNoPriceData}
</Text>
</TextShadow>
<ChartPriceLabel defaultValue={title} isNoPriceData={isNoPriceData} isPool={isPool} priceRef={priceRef} priceValue={price} />
<Animated.View style={showPriceChangeStyle}>
<Bleed top={'6px'}>
<ChartPercentChangeLabel latestChange={latestChange} ratio={ratio} />
<Box gap={8} flexDirection="row" alignItems="center">
<ChartPercentChangeLabel latestChange={latestChange} />
<ChartDateLabel chartTimeDefaultValue={chartTimeDefaultValue} />
</Box>
</Bleed>
</Animated.View>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import lang from 'i18n-js';
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';
import { ChartXLabel } from '@/react-native-animated-charts/src';

const MONTHS = [
lang.t('expanded_state.chart.date.months.month_00'),
Expand Down Expand Up @@ -71,26 +68,7 @@ function formatDatetime(value: string, chartTimeDefaultValue: string) {
return res;
}

export default function ChartDateLabel({
chartTimeDefaultValue,
ratio,
showPriceChangeStyle,
}: {
chartTimeDefaultValue: string;
ratio: number | undefined;
showPriceChangeStyle: AnimatedStyle;
}) {
const { isActive } = useChartData();
const sharedRatio = useRatio();
const { colors } = useTheme();

const textStyle = useAnimatedStyle(() => {
const realRatio = isActive.value ? sharedRatio.value : ratio;
return {
color: realRatio !== undefined ? (realRatio === 1 ? colors.blueGreyDark : realRatio < 1 ? colors.red : colors.green) : 'transparent',
};
});

export default function ChartDateLabel({ chartTimeDefaultValue }: { chartTimeDefaultValue: string }) {
const formatWorklet = useCallback(
(value: string) => {
'worklet';
Expand All @@ -99,9 +77,5 @@ export default function ChartDateLabel({
[chartTimeDefaultValue]
);

return (
<Animated.View entering={FadeIn.duration(140)} style={showPriceChangeStyle}>
<ChartXLabel align="right" formatWorklet={formatWorklet} size="20pt" style={textStyle} tabularNumbers weight="semibold" />
</Animated.View>
);
return <ChartXLabel formatWorklet={formatWorklet} size="20pt" color="labelQuaternary" tabularNumbers weight="bold" />;
}
Original file line number Diff line number Diff line change
@@ -1,66 +1,70 @@
import React, { memo } from 'react';
import { DerivedValue, SharedValue, useAnimatedStyle, useDerivedValue } from 'react-native-reanimated';
import { AnimatedText, TextShadow } from '@/design-system';
import { DerivedValue, useAnimatedStyle, useDerivedValue } from 'react-native-reanimated';
import { AnimatedText, TextShadow, useColorMode, useForegroundColor } from '@/design-system';
import { IS_ANDROID } from '@/env';
import { useChartData } from '@/react-native-animated-charts/src';
import { opacityWorklet } from '@/__swaps__/utils/swaps';
import { useTheme } from '@/theme';
import { useRatio } from './useRatio';
import { DataType } from '@/react-native-animated-charts/src/helpers/ChartContext';
import { toFixedWorklet } from '@/safe-math/SafeMath';

function formatNumber(num: string) {
'worklet';
const first = num.split('.');
const digits = first[0].split('').reverse();
const newDigits = [];
for (let i = 0; i < digits.length; i++) {
newDigits.push(digits[i]);
if ((i + 1) % 3 === 0 && i !== digits.length - 1) {
newDigits.push(',');
const UP_ARROW = IS_ANDROID ? '' : '↑';
const DOWN_ARROW = IS_ANDROID ? '' : '↓';

export default memo(function ChartPercentChangeLabel({ latestChange }: { latestChange: DerivedValue<number | undefined> }) {
const { originalY, data } = useChartData();
const { colors } = useTheme();
const { isDarkMode } = useColorMode();
const labelSecondary = useForegroundColor('labelSecondary');

const percentageChange: DerivedValue<number | null> = useDerivedValue(() => {
const hasData = data?.points?.length > 0;
if (!hasData && latestChange.value === undefined) {
// important that string is not empty so that when actual value fills it does not cause a vertical layout shift
return null;
}
}
return newDigits.reverse().join('') + '.' + first[1];
}

const formatWorklet = (originalY: SharedValue<string>, data: DataType, latestChange: number | undefined) => {
'worklet';
const firstValue = data?.points?.[0]?.y;
const lastValue = data?.points?.[data.points.length - 1]?.y;
const firstPoint = data?.points?.[0]?.y;
const lastPoint = data?.points?.[data.points.length - 1]?.y;
// This is the current value of the scrubber
const originalYNumber = Number(originalY?.value);
const firstValue = firstPoint;
const lastValue = isNaN(originalYNumber) ? lastPoint : originalYNumber;

return firstValue === Number(firstValue) && firstValue
? (() => {
const originalYNumber = Number(originalY?.value);
const value =
originalYNumber === lastValue || !originalYNumber ? latestChange ?? 0 : ((originalYNumber || lastValue) / firstValue) * 100 - 100;
if (firstValue && lastValue) {
return ((lastValue - firstValue) / firstValue) * 100;
} else if (latestChange.value) {
return latestChange.value;
}

return (IS_ANDROID ? '' : value > 0 ? '↑' : value < 0 ? '↓' : '') + ' ' + formatNumber(Math.abs(value).toFixed(2)) + '%';
})()
: ' '; // important that string is not empty so that when actual value fills it does not cause a layout shift
};
return null;
}, [data, latestChange, originalY]);

export default memo(function ChartPercentChangeLabel({
latestChange,
ratio,
}: {
latestChange: DerivedValue<number | undefined>;
ratio: number | undefined;
}) {
const { originalY, data, isActive } = useChartData();
const { colors } = useTheme();
const percentageChangeText = useDerivedValue(() => {
if (percentageChange.value === null) {
// important that string is not empty so that when actual value fills it does not cause a vertical layout shift
return ' ';
}
const directionString = percentageChange.value > 0 ? UP_ARROW : percentageChange.value < 0 ? DOWN_ARROW : '';
const formattedPercentageChange = toFixedWorklet(Math.abs(percentageChange.value), 2);

const sharedRatio = useRatio();
const text = useDerivedValue(() => formatWorklet(originalY, data, latestChange.value));
return `${directionString}${formattedPercentageChange}%`;
});

const textStyle = useAnimatedStyle(() => {
const realRatio = isActive.value ? sharedRatio.value : ratio;
const isPositive = percentageChange.value !== null && percentageChange.value > 0;
const isNegative = percentageChange.value !== null && percentageChange.value < 0;
const color = percentageChange.value !== null ? (isPositive ? colors.green : isNegative ? colors.red : labelSecondary) : 'transparent';

return {
color: realRatio !== undefined ? (realRatio === 1 ? colors.blueGreyDark : realRatio < 1 ? colors.red : colors.green) : 'transparent',
color,
textShadowColor: isDarkMode ? opacityWorklet(color, 0.24) : 'transparent',
};
});

return (
<TextShadow blur={12} shadowOpacity={0.24}>
<AnimatedText align="left" numberOfLines={1} size="20pt" style={[{ width: '100%' }, textStyle]} tabularNumbers weight="bold">
{text}
<AnimatedText numberOfLines={1} size="20pt" style={textStyle} tabularNumbers weight="heavy">
{percentageChangeText}
</AnimatedText>
</TextShadow>
);
Expand Down
13 changes: 0 additions & 13 deletions src/components/expanded-state/chart/chart-data-labels/useRatio.ts

This file was deleted.

15 changes: 9 additions & 6 deletions src/components/sheet/SlackSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { BottomSheetScrollView } from '@gorhom/bottom-sheet';
import { BottomSheetContext } from '@gorhom/bottom-sheet/src/contexts/external';
import React, { forwardRef, Fragment, useContext, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
import { ColorValue, FlexStyle, Pressable, StyleSheet, TouchableWithoutFeedback, View, ViewStyle } from 'react-native';
import { ColorValue, FlexStyle, Insets, Pressable, StyleSheet, TouchableWithoutFeedback, View, ViewStyle } from 'react-native';
import Animated, { SharedValue, useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from '../../theme/ThemeContext';
Expand Down Expand Up @@ -106,6 +106,7 @@ interface SlackSheetProps extends ViewStyle {
onContentSizeChange?: () => void;
renderHeader?: (yPosition: Animated.SharedValue<number>) => React.ReactNode;
scrollEnabled?: boolean;
scrollIndicatorInsets?: Insets;
showsHorizontalScrollIndicator?: boolean;
showsVerticalScrollIndicator?: boolean;
showBlur?: boolean;
Expand All @@ -132,6 +133,7 @@ export default forwardRef<unknown, SlackSheetProps>(function SlackSheet(
onContentSizeChange,
renderHeader,
scrollEnabled = true,
scrollIndicatorInsets: scrollIndicatorInsetsProp,
showsHorizontalScrollIndicator = true,
showsVerticalScrollIndicator = true,
showBlur,
Expand Down Expand Up @@ -163,11 +165,12 @@ export default forwardRef<unknown, SlackSheetProps>(function SlackSheet(
useImperativeHandle(ref, () => sheet.current);

const scrollIndicatorInsets = useMemo(
() => ({
bottom: bottomInset,
top: borderRadius + SheetHandleFixedToTopHeight,
}),
[borderRadius, bottomInset]
() =>
scrollIndicatorInsetsProp || {
bottom: bottomInset,
top: borderRadius + SheetHandleFixedToTopHeight,
},
[borderRadius, bottomInset, scrollIndicatorInsetsProp]
);

// In discover sheet we need to set it additionally
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import lang from 'i18n-js';
import React, { useCallback } from 'react';
import SheetActionButton from './SheetActionButton';
import SheetActionButton, { SheetActionButtonProps } from './SheetActionButton';
import { analyticsV2 } from '@/analytics';
import { Text } from '@/design-system';
import showWalletErrorAlert from '@/helpers/support';
import { useWallets } from '@/hooks';

import Routes from '@/navigation/routesNames';
import { colors } from '@/styles';
import { useRoute } from '@react-navigation/native';
import useNavigationForNonReadOnlyWallets from '@/hooks/useNavigationForNonReadOnlyWallets';

function BuyActionButton({ color: givenColor, ...props }) {
const { colors } = useTheme();
type BuyActionButtonProps = SheetActionButtonProps;

function BuyActionButton({ color: givenColor, ...props }: BuyActionButtonProps) {
const color = givenColor || colors.paleBlue;
const navigate = useNavigationForNonReadOnlyWallets();
const { isDamaged } = useWallets();
Expand All @@ -30,7 +33,19 @@ function BuyActionButton({ color: givenColor, ...props }) {
});
}, [isDamaged, navigate, routeName]);

return <SheetActionButton {...props} color={color} label={`􀍰 ${lang.t('button.buy_eth')}`} onPress={handlePress} weight="bold" />;
return (
<SheetActionButton
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
color={color}
newShadows
onPress={handlePress}
>
<Text align="center" color="label" size="20pt" weight="heavy">
{`􀍰 ${lang.t('button.buy_eth')}`}
</Text>
</SheetActionButton>
);
}

export default React.memo(BuyActionButton);
Loading

0 comments on commit 71fbba1

Please sign in to comment.