Skip to content

Commit

Permalink
feat(assets): add chart to token details screen (#4283)
Browse files Browse the repository at this point in the history
### Description

Reuses the existing celo chart component to display chart for CELO on
the token details page.

### Test plan

| | Token details | Celo |
|--------|--------|--------|
| iOS | <img
src="https://github.com/valora-inc/wallet/assets/5062591/12554248-fecd-41ce-b0b7-b8c9256adb7c"
width="250" /> | <img
src="https://github.com/valora-inc/wallet/assets/5062591/4a839311-46e9-4fb1-97bf-50350a732cfc"
width="250" /> |
| Android | <img
src="https://github.com/valora-inc/wallet/assets/5062591/0e6d05c1-a3c5-4eb5-8752-b88994a66ad3"
width="250" /> | <img
src="https://github.com/valora-inc/wallet/assets/5062591/9a3ba45a-93b8-448f-b378-ece45cd33dbc"
width="250" /> |

### Related issues

- Fixes ACT-916

### Backwards compatibility

Yes

---------

Co-authored-by: Charlie Andrews-Jubelt <[email protected]>
  • Loading branch information
satish-ravi and cajubelt authored Oct 10, 2023
1 parent 7207250 commit 97088fc
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 40 deletions.
47 changes: 31 additions & 16 deletions src/exchange/CeloGoldHistoryChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import BigNumber from 'bignumber.js'
import _ from 'lodash'
import React, { useCallback, useState } from 'react'
import { WithTranslation } from 'react-i18next'
import { ActivityIndicator, LayoutChangeEvent, StyleSheet, Text, View } from 'react-native'
import {
ActivityIndicator,
LayoutChangeEvent,
StyleSheet,
Text,
View,
ViewStyle,
} from 'react-native'
import { Circle, G, Line, Text as SvgText } from 'react-native-svg'
import { CeloExchangeEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
Expand All @@ -24,10 +31,12 @@ const CHART_WIDTH = variables.width
const CHART_HEIGHT = 180
const CHART_MIN_VERTICAL_RANGE = 0.1 // one cent
const CHART_DOMAIN_PADDING = { y: [30, 30] as [number, number], x: [5, 5] as [number, number] }
const CHART_PADDING = { left: variables.contentPadding, right: variables.contentPadding }

interface OwnProps {
testID?: string
color?: colors
containerStyle?: ViewStyle
chartPadding?: number
}

type Props = WithTranslation & OwnProps
Expand Down Expand Up @@ -92,7 +101,8 @@ function ChartAwareSvgText({

function renderPointOnChart(
chartData: Array<{ amount: number | BigNumber; displayValue: string }>,
chartWidth: number
chartWidth: number,
color: colors
) {
let lowestRateIdx = 0,
highestRateIdx = 0
Expand All @@ -112,15 +122,15 @@ function renderPointOnChart(
result.push(
<G key={idx + 'dot'}>
<Line x1={0} y1={y} x2={chartWidth} y2={y} stroke={colors.gray2} strokeWidth="1" />
<Circle cx={x} cy={y} r="4" fill={colors.goldUI} />
<Circle cx={x} cy={y} r="4" fill={color} />
</G>
)
break

case chartData.length - 1:
result.push(
<G key={idx + 'dot'}>
<Circle cx={x} cy={y} r="4" fill={colors.goldUI} />
<Circle cx={x} cy={y} r="4" fill={color} />
</G>
)
break
Expand Down Expand Up @@ -164,15 +174,21 @@ function renderPointOnChart(
}
}

function Loader() {
function Loader({ color }: { color: colors }) {
return (
<View style={styles.loader}>
<ActivityIndicator size="large" color={colors.goldUI} />
<ActivityIndicator size="large" color={color} />
</View>
)
}

function CeloGoldHistoryChart({ testID, i18n }: Props) {
function CeloGoldHistoryChart({
testID,
i18n,
color = colors.goldUI,
containerStyle,
chartPadding = variables.contentPadding,
}: Props) {
const localCurrencyCode = useSelector(getLocalCurrencyCode)
const displayLocalCurrency = useCallback(
(amount: BigNumber.Value) =>
Expand All @@ -193,7 +209,7 @@ function CeloGoldHistoryChart({ testID, i18n }: Props) {
}, [])

if (!exchangeHistory.aggregatedExchangeRates?.length) {
return <Loader />
return <Loader color={color} />
}

const chartData = exchangeHistory.aggregatedExchangeRates.map((exchangeRate) => {
Expand All @@ -210,7 +226,7 @@ function CeloGoldHistoryChart({ testID, i18n }: Props) {
null
const oldestGoldRateInLocalCurrency = chartData[0].amount
if (oldestGoldRateInLocalCurrency === null || currentGoldRateInLocalCurrency === null) {
return <Loader />
return <Loader color={color} />
}
// We need displayValue to show min/max on the chart. In case the
// current value is min/max we do not need to show it once again,
Expand All @@ -219,7 +235,7 @@ function CeloGoldHistoryChart({ testID, i18n }: Props) {
amount: currentGoldRateInLocalCurrency,
displayValue: displayLocalCurrency(currentGoldRateInLocalCurrency),
})
const RenderPoint = renderPointOnChart(chartData, CHART_WIDTH)
const RenderPoint = renderPointOnChart(chartData, CHART_WIDTH, color)

const values = chartData.map((el) => el.amount)
const min = Math.min(...values)
Expand All @@ -236,11 +252,11 @@ function CeloGoldHistoryChart({ testID, i18n }: Props) {
const latestExchangeRate = _.last(exchangeHistory.aggregatedExchangeRates)!

return (
<View style={styles.container} onTouchStart={onTap} testID={testID}>
<View style={[styles.container, containerStyle]} onTouchStart={onTap} testID={testID}>
<VictoryGroup
domainPadding={CHART_DOMAIN_PADDING}
singleQuadrantDomainPadding={false}
padding={CHART_PADDING}
padding={{ left: chartPadding, right: chartPadding }}
width={CHART_WIDTH}
height={CHART_HEIGHT}
data={chartData.map((el) => el.amount)}
Expand All @@ -252,11 +268,11 @@ function CeloGoldHistoryChart({ testID, i18n }: Props) {
<VictoryLine
interpolation="monotoneX"
style={{
data: { stroke: colors.goldUI },
data: { stroke: color },
}}
/>
</VictoryGroup>
<View style={styles.range}>
<View style={[styles.range, { paddingHorizontal: chartPadding }]}>
<Text style={styles.timeframe}>
{formatFeedDate(latestExchangeRate.timestamp - exchangeHistory.range, i18n)}
</Text>
Expand All @@ -281,7 +297,6 @@ const styles = StyleSheet.create({
fontSize: 16,
},
range: {
paddingHorizontal: variables.contentPadding,
marginTop: variables.contentPadding,
justifyContent: 'space-between',
flexDirection: 'row',
Expand Down
50 changes: 32 additions & 18 deletions src/exchange/__snapshots__/CeloGoldHistoryChart.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ exports[`renders properly 1`] = `
<View
onTouchStart={[Function]}
style={
{
"marginBottom": 24,
}
[
{
"marginBottom": 24,
},
undefined,
]
}
testID="SnapshotCeloGoldOverview"
>
Expand Down Expand Up @@ -164,12 +167,16 @@ exports[`renders properly 1`] = `
</View>
<View
style={
{
"flexDirection": "row",
"justifyContent": "space-between",
"marginTop": 16,
"paddingHorizontal": 16,
}
[
{
"flexDirection": "row",
"justifyContent": "space-between",
"marginTop": 16,
},
{
"paddingHorizontal": 16,
},
]
}
>
<Text
Expand Down Expand Up @@ -200,9 +207,12 @@ exports[`renders while update is in progress 1`] = `
<View
onTouchStart={[Function]}
style={
{
"marginBottom": 24,
}
[
{
"marginBottom": 24,
},
undefined,
]
}
testID="SnapshotCeloGoldOverview"
>
Expand Down Expand Up @@ -349,12 +359,16 @@ exports[`renders while update is in progress 1`] = `
</View>
<View
style={
{
"flexDirection": "row",
"justifyContent": "space-between",
"marginTop": 16,
"paddingHorizontal": 16,
}
[
{
"flexDirection": "row",
"justifyContent": "space-between",
"marginTop": 16,
},
{
"paddingHorizontal": 16,
},
]
}
>
<Text
Expand Down
29 changes: 28 additions & 1 deletion src/tokens/TokenDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import TokenDetailsScreen from 'src/tokens/TokenDetails'
import { ONE_DAY_IN_MILLIS } from 'src/utils/time'
import MockedNavigator from 'test/MockedNavigator'
import { createMockStore } from 'test/utils'
import { mockCeloTokenId, mockPoofTokenId, mockTokenBalances } from 'test/values'
import {
exchangePriceHistory,
mockCeloTokenId,
mockPoofTokenId,
mockTokenBalances,
} from 'test/values'

jest.mock('src/statsig', () => ({
getDynamicConfigParams: jest.fn(() => {
Expand Down Expand Up @@ -41,6 +46,7 @@ describe('TokenDetails', () => {
expect(getByText('tokenDetails.yourBalance')).toBeTruthy()
expect(getByTestId('TokenBalanceItem')).toBeTruthy()
expect(queryByTestId('TokenDetails/LearnMore')).toBeFalsy()
expect(queryByTestId('TokenDetails/Chart')).toBeFalsy()
})

it('renders learn more if token has infoUrl', () => {
Expand Down Expand Up @@ -161,6 +167,27 @@ describe('TokenDetails', () => {
expect(queryByText('tokenDetails.priceUnavailable')).toBeFalsy()
})

it('renders chart if token is native (celo)', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockCeloTokenId]: mockTokenBalances[mockCeloTokenId],
},
},
exchange: {
history: exchangePriceHistory,
},
})

const { getByTestId } = render(
<Provider store={store}>
<MockedNavigator component={TokenDetailsScreen} params={{ tokenId: mockCeloTokenId }} />
</Provider>
)

expect(getByTestId('TokenDetails/Chart')).toBeTruthy()
})

it('renders send action only if token has balance, is not swappable and not a CICO token', () => {
const store = createMockStore({
tokens: {
Expand Down
25 changes: 20 additions & 5 deletions src/tokens/TokenDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import TokenDisplay from 'src/components/TokenDisplay'
import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import Touchable from 'src/components/Touchable'
import { TOKEN_MIN_AMOUNT } from 'src/config'
import CeloGoldHistoryChart from 'src/exchange/CeloGoldHistoryChart'
import { CICOFlow } from 'src/fiatExchanges/utils'
import ArrowRightThick from 'src/icons/ArrowRightThick'
import DataDown from 'src/icons/DataDown'
Expand Down Expand Up @@ -77,9 +78,17 @@ export default function TokenDetailsScreen({ route }: Props) {
testID="TokenDetails/Balance"
/>
{!token.isStableCoin && <PriceInfo token={token} />}
{token.isNative && token.symbol === 'CELO' && (
<CeloGoldHistoryChart
color={Colors.dark}
containerStyle={styles.chartContainer}
chartPadding={Spacing.Thick24}
testID="TokenDetails/Chart"
/>
)}
<Actions token={token} />
<Text style={styles.yourBalance}>{t('tokenDetails.yourBalance')}</Text>
<TokenBalanceItem token={token} containerStyle={styles.tokenBalanceItem} />
<TokenBalanceItem token={token} />
{token.infoUrl && (
<LearnMore
tokenName={token.name}
Expand Down Expand Up @@ -273,12 +282,12 @@ function LearnMore({
const styles = StyleSheet.create({
container: {
flex: 1,
marginHorizontal: Spacing.Thick24,
},
titleContainer: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: Spacing.Smallest8,
marginHorizontal: Spacing.Thick24,
},
tokenName: {
...typeScale.labelLarge,
Expand All @@ -290,11 +299,17 @@ const styles = StyleSheet.create({
balance: {
...typeScale.titleLarge,
color: Colors.dark,
marginHorizontal: Spacing.Thick24,
},
chartContainer: {
marginTop: 40,
marginBottom: 0,
},
actions: {
flexDirection: 'row',
marginTop: 40,
marginBottom: Spacing.Regular16,
marginHorizontal: Spacing.Thick24,
},
actionButton: {
flexGrow: 1,
Expand All @@ -310,14 +325,13 @@ const styles = StyleSheet.create({
...typeScale.labelMedium,
color: Colors.dark,
marginTop: Spacing.Regular16,
},
tokenBalanceItem: {
marginHorizontal: 0,
marginHorizontal: Spacing.Thick24,
},
learnMoreContainer: {
borderTopColor: Colors.gray2,
borderTopWidth: 1,
paddingTop: Spacing.Regular16,
marginHorizontal: Spacing.Thick24,
},
learnMoreTouchableContainer: {
flexDirection: 'row',
Expand All @@ -330,6 +344,7 @@ const styles = StyleSheet.create({
},
priceInfo: {
marginTop: Spacing.Tiny4,
marginHorizontal: Spacing.Thick24,
},
priceInfoText: {
...typeScale.labelSmall,
Expand Down
1 change: 1 addition & 0 deletions test/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ export const mockTokenBalances: Record<string, StoredTokenBalance> = {
showZeroBalance: true,
isCashInEligible: true,
isCashOutEligible: true,
isNative: true,
},
[mockCrealTokenId]: {
priceUsd: '0.17',
Expand Down

0 comments on commit 97088fc

Please sign in to comment.