Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feature/tx-review
Browse files Browse the repository at this point in the history
  • Loading branch information
banklesss committed Sep 20, 2024
2 parents cea677e + de98527 commit 26eefda
Show file tree
Hide file tree
Showing 19 changed files with 156 additions and 135 deletions.
Binary file not shown.
Binary file removed apps/wallet-mobile/src/assets/img/[email protected]
Binary file not shown.
Binary file removed apps/wallet-mobile/src/assets/img/[email protected]
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const Price = ({amount, textStyle, ignorePrivacy, hidePrimaryPair}: Props) => {

if (isPrivacyActive && !ignorePrivacy) return `${privacyPlaceholder} ${currency}`

if (!isPrimaryToken(amount.info) && tokenPrice == null) return `— ${currency}`
if (!isPrimaryToken(amount.info) && tokenPrice == null) return `— ${currency}`

if (hidePrimaryPair && isPrimaryToken(amount.info) && isPrimaryTokenActive) return ''

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {isRight} from '@yoroi/common'
import {isPrimaryToken} from '@yoroi/portfolio'
import {Chain} from '@yoroi/types'
import {Chain, Portfolio} from '@yoroi/types'
import {useQuery, UseQueryOptions} from 'react-query'

import {supportedCurrencies, time} from '../../../../kernel/constants'
import {useLanguage} from '../../../../kernel/i18n'
import {logger} from '../../../../kernel/logger/logger'
import {fetchPtPriceActivity} from '../../../../yoroi-wallets/cardano/usePrimaryTokenActivity'
import {useCurrencyPairing} from '../../../Settings/Currency/CurrencyContext'
import {useSelectedNetwork} from '../../../WalletManager/common/hooks/useSelectedNetwork'
import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet'
import {networkConfigs} from '../../../WalletManager/network-manager/network-manager'
import {priceChange} from '../helpers/priceChange'
Expand All @@ -30,60 +32,6 @@ type TokenChartData = {
changeValue: number
}

function generateMockChartData(timeInterval: TokenChartInterval = TOKEN_CHART_INTERVAL.DAY): TokenChartData[] {
const dataPoints = 50
const startValue = 100
const volatility = 50

const startDate = new Date('2024-02-02T15:09:00')

function getTimeIncrement(interval: TokenChartInterval): number {
switch (interval) {
case TOKEN_CHART_INTERVAL.DAY:
return 60 * 60 * 1000 // 1 hour
case TOKEN_CHART_INTERVAL.WEEK:
return 24 * 60 * 60 * 1000 // 1 day
case TOKEN_CHART_INTERVAL.MONTH:
return 30 * 24 * 60 * 60 * 1000 // 1 month (approximated as 30 days)
case TOKEN_CHART_INTERVAL.SIX_MONTHS:
return 6 * 30 * 24 * 60 * 60 * 1000 // 6 months
case TOKEN_CHART_INTERVAL.YEAR:
return 12 * 30 * 24 * 60 * 60 * 1000 // 1 year (approximated as 360 days)
default:
return 60 * 1000 // Default to 1 minute
}
}

const increment = getTimeIncrement(timeInterval)
const chartData: TokenChartData[] = []

let previousValue = startValue

for (let i = 0; i < dataPoints; i++) {
const date = new Date(startDate.getTime() + i * increment)
const label = `${date.getDate().toString().padStart(2, '0')}/${(date.getMonth() + 1)
.toString()
.padStart(2, '0')}/${date.getFullYear().toString().substr(-2)} ${date.getHours()}:${date
.getMinutes()
.toString()
.padStart(2, '0')}`
const value = i === 0 ? startValue : previousValue + (Math.random() - 0.5) * volatility
const changeValue = i === 0 ? 0 : value - previousValue
const changePercent = i === 0 ? 0 : (changeValue / previousValue) * 100

chartData.push({
label,
value,
changePercent,
changeValue,
})

previousValue = value // Update previousValue for the next iteration
}

return chartData
}

const getTimestamps = (timeInterval: TokenChartInterval) => {
const now = Date.now()
const [from, resolution] = {
Expand All @@ -104,16 +52,19 @@ const ptTicker = networkConfigs[Chain.Network.Mainnet].primaryTokenInfo.ticker
export const useGetPortfolioTokenChart = (
timeInterval = TOKEN_CHART_INTERVAL.DAY as TokenChartInterval,
options: UseQueryOptions<
TokenChartData[],
TokenChartData[] | null,
Error,
TokenChartData[],
TokenChartData[] | null,
['useGetPortfolioTokenChart', string, TokenChartInterval, ReturnType<typeof useCurrencyPairing>['currency']?]
> = {},
) => {
const {id: tokenId} = usePortfolioTokenDetailParams()
const {
wallet: {balances},
} = useSelectedWallet()
const {
networkManager: {tokenManager},
} = useSelectedNetwork()
const tokenInfo = balances.records.get(tokenId)
const {currency} = useCurrencyPairing()
const {languageCode} = useLanguage()
Expand All @@ -135,6 +86,8 @@ export const useGetPortfolioTokenChart = (
if (response.value.data.error) throw new Error(response.value.data.error)

const tickers = response.value.data.tickers
if (tickers.length === 0) return null

const validCurrency = currency === ptTicker ? supportedCurrencies.USD : currency ?? supportedCurrencies.USD

const initialPrice = tickers[0].prices[validCurrency]
Expand All @@ -153,7 +106,8 @@ export const useGetPortfolioTokenChart = (

return records
}
throw new Error('Failed to fetch token chart data')
logger.error('Failed to fetch token chart data for PT')
return null
},
})

Expand All @@ -164,10 +118,42 @@ export const useGetPortfolioTokenChart = (
...options,
queryKey: ['useGetPortfolioTokenChart', tokenInfo?.info.id ?? '', timeInterval],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1))
return generateMockChartData(timeInterval)
const response = await tokenManager.api.tokenHistory(tokenId, chartIntervalToHistoryPeriod(timeInterval))
if (isRight(response)) {
const prices = response.value.data.prices

if (prices.length === 0) return null

const initialPrice = prices[0].open.toNumber()
const records = prices
.map((price) => {
const value = price.close.toNumber()
if (value === undefined) return undefined
const {changePercent, changeValue} = priceChange(initialPrice, value)
const label = new Date(price.ts).toLocaleString(languageCode, {
dateStyle: 'short',
timeStyle: 'short',
})
return {label, value, changePercent, changeValue}
})
.filter(Boolean) as TokenChartData[]

return records
}
logger.error(`Failed to fetch token chart data for ${tokenId}`)
return null
},
})

return tokenInfo && isPrimaryToken(tokenInfo.info) ? ptQuery : otherQuery
}

const chartIntervalToHistoryPeriod = (i: TokenChartInterval): Portfolio.Token.HistoryPeriod =>
({
[TOKEN_CHART_INTERVAL.DAY]: Portfolio.Token.HistoryPeriod.OneDay,
[TOKEN_CHART_INTERVAL.WEEK]: Portfolio.Token.HistoryPeriod.OneWeek,
[TOKEN_CHART_INTERVAL.MONTH]: Portfolio.Token.HistoryPeriod.OneMonth,
[TOKEN_CHART_INTERVAL.SIX_MONTHS]: Portfolio.Token.HistoryPeriod.SixMonth,
[TOKEN_CHART_INTERVAL.YEAR]: Portfolio.Token.HistoryPeriod.OneYear,
[TOKEN_CHART_INTERVAL.ALL]: Portfolio.Token.HistoryPeriod.All,
}[i] ?? Portfolio.Token.HistoryPeriod.OneDay)
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const DashboardTokenItem = ({tokenInfo}: Props) => {

<View style={styles.quantityContainer}>
<PnlTag withIcon variant={variantPnl}>
<Text>{isMissingPrices ? '— ' : formatPriceChange(changePercent)}%</Text>
<Text>{isMissingPrices ? '— ' : formatPriceChange(changePercent)}%</Text>
</PnlTag>

<Text ellipsizeMode="tail" numberOfLines={1} style={styles.tokenValue}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {useTheme} from '@yoroi/theme'
import React from 'react'
import {useWindowDimensions} from 'react-native'
import Svg, {Path} from 'react-native-svg'

export const ChartPlaceholder = () => {
const {width} = useWindowDimensions()
const {color} = useTheme()

return (
<Svg width={width} height={144} viewBox="0 0 345 114" fill="none">
<Path
d="M1 100.743l3.793-3.173c3.793-3.173 11.379-9.519 18.964-14.554 7.586-5.035 15.172-8.759 22.758-4.645 7.585 4.113 15.171 16.064 22.757 24.517 7.586 8.453 15.171 13.407 22.757 7.586 7.586-5.821 15.172-22.418 22.757-44.911 7.586-22.493 15.172-50.883 22.758-60.343 7.586-9.46 15.171.01 22.757-.466 7.586-.475 15.172-10.894 22.757 6.34 7.586 17.235 15.172 62.123 22.758 69.525 7.585 7.401 15.171-22.684 22.757-18.094 7.586 4.59 15.172 43.854 22.757 41.6 7.586-2.254 15.172-46.027 22.758-69.273 7.585-23.245 15.171-25.965 22.757-9.12 7.586 16.845 15.171 53.255 22.757 56.734 7.586 3.48 15.172-25.971 18.965-40.696L344 13.985"
stroke={color.gray_100}
strokeWidth={2}
/>
</Svg>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
TOKEN_CHART_INTERVAL,
useGetPortfolioTokenChart,
} from '../../../common/hooks/useGetPortfolioTokenChart'
import {ChartPlaceholder} from './ChartPlaceholder'
import {PortfolioTokenChartSkeleton} from './PortfolioTokenChartSkeleton'
import {TokenChart} from './TokenChart'
import {TokenChartToolbar} from './TokenChartToolBar'
Expand All @@ -29,16 +30,16 @@ export const PortfolioTokenChart = () => {

return (
<View style={styles.root}>
{isFetching || !data ? (
{isFetching ? (
<PortfolioTokenChartSkeleton />
) : (
<>
<TokenPerformance
tokenPerformance={data[Math.max(0, Math.min(data.length - 1, selectedIndex))]}
tokenPerformance={data?.[Math.max(0, Math.min(data.length - 1, selectedIndex))]}
timeInterval={timeInterval}
/>

<TokenChart onValueSelected={handleChartSelected} dataSources={data} />
{!data ? <ChartPlaceholder /> : <TokenChart onValueSelected={handleChartSelected} dataSources={data} />}
</>
)}

Expand All @@ -49,6 +50,7 @@ export const PortfolioTokenChart = () => {

const useStyles = () => {
const {atoms} = useTheme()

const styles = StyleSheet.create({
root: {
...atoms.flex_1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {useTheme} from '@yoroi/theme'
import * as React from 'react'
import {Image, StyleSheet, View} from 'react-native'
import {StyleSheet, View} from 'react-native'
import SkeletonPlaceholder from 'react-native-skeleton-placeholder'

import ChartPlaceholder from '../../../../../assets/img/chart-placeholder.png'
import {Icon} from '../../../../../components/Icon'
import {ChartPlaceholder} from './ChartPlaceholder'

export const PortfolioTokenChartSkeleton = () => {
const {color, styles} = useStyles()
Expand All @@ -19,16 +18,14 @@ export const PortfolioTokenChartSkeleton = () => {
<SkeletonPlaceholder.Item width={64} height={24} />
</SkeletonPlaceholder.Item>
</SkeletonPlaceholder>

<Icon.InfoCircle />
</View>

<SkeletonPlaceholder borderRadius={20} backgroundColor={color.gray_100}>
<SkeletonPlaceholder.Item width={64} height={16} />
</SkeletonPlaceholder>
</View>

<Image style={styles.chartPlaceholder} source={ChartPlaceholder} />
<ChartPlaceholder />
</View>
)
}
Expand All @@ -51,12 +48,6 @@ const useStyles = () => {
...atoms.align_center,
...atoms.gap_2xs,
},
chartPlaceholder: {
height: 112,
width: '100%',
marginVertical: 16,
resizeMode: 'stretch',
},
})
return {styles, color} as const
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {useTheme} from '@yoroi/theme'
import * as React from 'react'
import {StyleSheet, View} from 'react-native'

import {Icon} from '../../../../../components/Icon'
import {Text} from '../../../../../components/Text'
import {Tooltip} from '../../../../../components/Tooltip/Tooltip'
import {useCurrencyPairing} from '../../../../Settings/Currency/CurrencyContext'
Expand All @@ -13,7 +12,7 @@ import {useStrings} from '../../../common/hooks/useStrings'
import {PnlTag} from '../../../common/PnlTag/PnlTag'

type Props = {
tokenPerformance: {
tokenPerformance?: {
changePercent: number
changeValue: number
value: number
Expand All @@ -27,11 +26,12 @@ export const TokenPerformance = ({tokenPerformance, timeInterval}: Props) => {
const {currency, config} = useCurrencyPairing()

const variant = React.useMemo(() => {
if (!tokenPerformance) return 'neutral'
if (Number(tokenPerformance.changePercent) > 0) return 'success'
if (Number(tokenPerformance.changePercent) < 0) return 'danger'

return 'neutral'
}, [tokenPerformance.changePercent])
}, [tokenPerformance])

const intervalLabel = React.useMemo(() => {
switch (timeInterval) {
Expand All @@ -54,25 +54,31 @@ export const TokenPerformance = ({tokenPerformance, timeInterval}: Props) => {

return (
<View style={styles.root}>
<View style={styles.tokenChangeWrapper}>
<PnlTag withIcon={variant !== 'neutral'} variant={variant}>
{formatPriceChange(tokenPerformance.changePercent)}%
</PnlTag>
<Tooltip
numberOfLine={3}
title={!tokenPerformance ? strings.noDataFound : strings.tokenPriceChangeTooltip(intervalLabel)}
>
<View style={styles.tokenChangeWrapper}>
<PnlTag withIcon={variant !== 'neutral'} variant={variant}>
{!tokenPerformance ? '—' : formatPriceChange(tokenPerformance.changePercent)}%
</PnlTag>

<PnlTag variant={variant}>{`${formatPriceChange(
tokenPerformance.changeValue,
config.decimals,
)} ${currency}`}</PnlTag>

<Tooltip numberOfLine={3} title={strings.tokenPriceChangeTooltip(intervalLabel)}>
<Icon.InfoCircle />
</Tooltip>
</View>
<PnlTag variant={variant}>{`${
!tokenPerformance ? '—' : formatPriceChange(tokenPerformance.changeValue, config.decimals)
} ${currency}`}</PnlTag>
</View>
</Tooltip>

<View style={styles.tokenWrapper}>
<Text style={styles.tokenPrice}>{formatPriceChange(tokenPerformance.value, config.decimals)}</Text>
{!tokenPerformance ? (
<Text style={styles.tokenPriceSymbol}></Text>
) : (
<>
<Text style={styles.tokenPrice}>{formatPriceChange(tokenPerformance.value, config.decimals)}</Text>

<Text style={styles.tokenPriceSymbol}>{currency}</Text>
<Text style={styles.tokenPriceSymbol}>{currency}</Text>
</>
)}
</View>
</View>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {isPrimaryTokenInfo} from '@yoroi/portfolio'
import {useTheme} from '@yoroi/theme'
import {App} from '@yoroi/types'
import * as React from 'react'
Expand Down Expand Up @@ -30,6 +29,9 @@ const tabs: Record<ActiveTab, Tabs> = {
overview: 'Overview',
transactions: 'Transactions',
}

const HEADER_HEIGHT = 304

export const PortfolioTokenDetailsScreen = () => {
const strings = useStrings()
const {activeTab, setActiveTab} = usePortfolioTokenDetailContext()
Expand All @@ -38,8 +40,6 @@ export const PortfolioTokenDetailsScreen = () => {
const {id: tokenId} = usePortfolioTokenDetailParams()
const {wallet} = useSelectedWallet()
const tokenInfo = wallet.balances.records.get(tokenId)?.info
const isPrimaryToken = isPrimaryTokenInfo(tokenInfo)
const HEADER_HEIGHT = isPrimaryToken ? 304 : 85 // Graph only in PT
const {styles} = useStyles(HEADER_HEIGHT)

if (!tokenInfo) throwLoggedError(new App.Errors.InvalidState('Token info not found, invalid state'))
Expand Down Expand Up @@ -100,13 +100,9 @@ export const PortfolioTokenDetailsScreen = () => {

<Spacer height={16} />

{isPrimaryToken && (
<>
<PortfolioTokenChart />
<PortfolioTokenChart />

<Spacer height={16} />
</>
)}
<Spacer height={16} />
</Animated.View>

<Animated.View>{renderTabs}</Animated.View>
Expand Down
Loading

0 comments on commit 26eefda

Please sign in to comment.