-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b4259a5
commit 9d30546
Showing
18 changed files
with
392 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
apps/wallet-mobile/src/components/TokenAmountItem/AmountIcon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import {isString} from '@yoroi/common' | ||
import {useTheme} from '@yoroi/theme' | ||
import {Chain, Portfolio} from '@yoroi/types' | ||
import {Image} from 'expo-image' | ||
import React from 'react' | ||
import {StyleSheet, View} from 'react-native' | ||
|
||
import {Icon} from '../Icon' | ||
|
||
const headers = { | ||
Accept: 'image/webp', | ||
} as const | ||
|
||
const blurhash = | ||
'|rF?hV%2WCj[ayj[a|j[az_NaeWBj@ayfRayfQfQM{M|azj[azf6fQfQfQIpWXofj[ayj[j[fQayWCoeoeaya}j[ayfQa{oLj?j[WVj[ayayj[fQoff7azayj[ayj[j[ayofayayayj[fQj[ayayj[ayfjj[j[ayjuayj[' | ||
|
||
type AmountIconProps = { | ||
info: Portfolio.Token.Info | ||
isMainnet?: boolean | ||
size: 'sm' | 'md' | ||
} | ||
export const AmountIcon = React.memo(({info, isMainnet, size = 'md'}: AmountIconProps) => { | ||
const {styles} = useStyles() | ||
|
||
if (info.nature === Portfolio.Token.Nature.Primary) return <PrimaryIcon size={size} /> | ||
|
||
if (isString(info.icon) && info.icon.length > 0) { | ||
return ( | ||
<Image | ||
source={{uri: `data:image/png;base64,${info.icon}`}} | ||
style={[size === 'sm' ? styles.iconSmall : styles.iconMedium]} | ||
placeholder={blurhash} | ||
/> | ||
) | ||
} | ||
|
||
const [policy, name] = info.id.split('.') | ||
const network = isMainnet ? Chain.Network.Mainnet : Chain.Network.Preprod | ||
const uri = `https://${network}.processed-media.yoroiwallet.com/${policy}/${name}?width=64&height=64&kind=metadata&fit=cover` | ||
|
||
return ( | ||
<Image source={{uri, headers}} contentFit="cover" style={[size === 'sm' ? styles.iconSmall : styles.iconMedium]} /> | ||
) | ||
}) | ||
|
||
const PrimaryIcon = ({size = 'md'}: {size?: 'sm' | 'md'}) => { | ||
const {styles} = useStyles() | ||
return ( | ||
<View style={[size === 'sm' ? styles.iconSmall : styles.iconMedium, styles.primary]}> | ||
<Icon.Cardano color="white" height={size === 'sm' ? 20 : 35} width={size === 'sm' ? 20 : 35} /> | ||
</View> | ||
) | ||
} | ||
|
||
export const TokenIconPlaceholder = ({size = 'md'}: {size?: 'sm' | 'md'}) => { | ||
const {styles, colors} = useStyles() | ||
return ( | ||
<View style={[styles.iconMedium, styles.placeholder, size === 'sm' && styles.placeholderSmall]}> | ||
<Icon.Tokens color={colors.icon} size={35} /> | ||
</View> | ||
) | ||
} | ||
|
||
const useStyles = () => { | ||
const {theme} = useTheme() | ||
const {color} = theme | ||
const styles = StyleSheet.create({ | ||
primary: { | ||
backgroundColor: color.primary[600], | ||
}, | ||
iconMedium: { | ||
backgroundColor: 'transparent', | ||
width: 40, | ||
height: 40, | ||
borderRadius: 8, | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
overflow: 'hidden', | ||
}, | ||
iconSmall: { | ||
backgroundColor: 'transparent', | ||
width: 24, | ||
height: 24, | ||
borderRadius: 8, | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
overflow: 'hidden', | ||
}, | ||
placeholder: { | ||
backgroundColor: color.gray[100], | ||
}, | ||
placeholderSmall: { | ||
width: 24, | ||
height: 24, | ||
}, | ||
}) | ||
|
||
const colors = { | ||
icon: color.gray[600], | ||
} | ||
|
||
return {styles, colors} | ||
} |
41 changes: 41 additions & 0 deletions
41
apps/wallet-mobile/src/components/TokenAmountItem/AmountItem.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import {storiesOf} from '@storybook/react-native' | ||
import {tokenMocks} from '@yoroi/portfolio' | ||
import React from 'react' | ||
import {Text, View} from 'react-native' | ||
|
||
import {QueryProvider} from '../../../.storybook/decorators' | ||
import {SelectedWalletProvider} from '../../features/WalletManager/Context' | ||
import {mocks} from '../../yoroi-wallets/mocks' | ||
import {Spacer} from '..' | ||
import {AmountItem} from './AmountItem' | ||
|
||
const primaryAmount = tokenMocks.primaryETH.balance | ||
const secondaryAmount = tokenMocks.nftCryptoKitty.balance | ||
|
||
storiesOf('AmountItem', module).add('Gallery', () => ( | ||
<QueryProvider> | ||
<SelectedWalletProvider wallet={mocks.wallet}> | ||
<View style={{flex: 1, justifyContent: 'center', padding: 16}}> | ||
<Text>Fungible primary token</Text> | ||
|
||
<AmountItem | ||
privacyPlaceholder="-" | ||
wallet={mocks.wallet} | ||
amount={primaryAmount} | ||
style={{backgroundColor: 'white', padding: 16, borderRadius: 8}} | ||
/> | ||
|
||
<Spacer height={40} /> | ||
|
||
<Text>Fungible non-primary token</Text> | ||
|
||
<AmountItem | ||
privacyPlaceholder="-" | ||
wallet={mocks.wallet} | ||
amount={secondaryAmount} | ||
style={{backgroundColor: 'white', padding: 16, borderRadius: 8}} | ||
/> | ||
</View> | ||
</SelectedWalletProvider> | ||
</QueryProvider> | ||
)) |
178 changes: 178 additions & 0 deletions
178
apps/wallet-mobile/src/components/TokenAmountItem/AmountItem.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import {amountFormatter, infoExtractName} from '@yoroi/portfolio' | ||
import {useTheme} from '@yoroi/theme' | ||
import {Portfolio} from '@yoroi/types' | ||
import {Swap} from '@yoroi/types' | ||
import * as React from 'react' | ||
import {StyleSheet, View, ViewProps} from 'react-native' | ||
|
||
import {usePriceImpactRiskTheme} from '../../features/Swap/common/helpers' | ||
import {SwapPriceImpactRisk} from '../../features/Swap/common/types' | ||
import {YoroiWallet} from '../../yoroi-wallets/cardano/types' | ||
import {Icon, Spacer, Text} from '..' | ||
import {PairedBalance} from '../PairedBalance/PairedBalance' | ||
import {AmountIcon} from './AmountIcon' | ||
|
||
export type AmountItemProps = { | ||
wallet: YoroiWallet | ||
amount: Portfolio.Token.Amount | ||
privacyPlaceholder: string | ||
style?: ViewProps['style'] | ||
isPrivacyOff?: boolean | ||
inWallet?: boolean | ||
variant?: 'swap' | ||
priceImpactRisk?: SwapPriceImpactRisk | ||
orderType?: Swap.OrderType | ||
isMainnet?: boolean | ||
} | ||
|
||
export const AmountItem = ({ | ||
isMainnet, | ||
isPrivacyOff, | ||
privacyPlaceholder, | ||
style, | ||
amount, | ||
inWallet, | ||
variant, | ||
priceImpactRisk, | ||
orderType, | ||
}: AmountItemProps) => { | ||
const {styles, colors} = useStyles() | ||
const priceImpactRiskTheme = usePriceImpactRiskTheme(priceImpactRisk ?? 'none') | ||
|
||
const {info} = amount | ||
const isPrimary = info.nature === 'primary' | ||
const name = infoExtractName(info) | ||
const detail = isPrimary ? info.description : info.fingerprint | ||
|
||
const formattedQuantity = !isPrivacyOff ? amountFormatter()(amount) : privacyPlaceholder | ||
|
||
const showSwapDetails = !isPrimary && variant === 'swap' | ||
const priceImpactRiskTextColor = orderType === 'market' ? priceImpactRiskTheme.text : colors.text | ||
|
||
return ( | ||
<View style={[style, styles.container]} testID="assetItem"> | ||
<Left> | ||
<AmountIcon info={amount.info} size={variant === 'swap' ? 'sm' : 'md'} isMainnet={isMainnet} /> | ||
</Left> | ||
|
||
<Middle> | ||
<View style={styles.row}> | ||
<Text numberOfLines={1} ellipsizeMode="middle" style={styles.name} testID="tokenInfoText"> | ||
{name} | ||
</Text> | ||
|
||
{showSwapDetails && ( | ||
<> | ||
<Spacer width={4} /> | ||
|
||
{inWallet && <Icon.Portfolio size={22} color={colors.icon} />} | ||
</> | ||
)} | ||
</View> | ||
|
||
<Text numberOfLines={1} ellipsizeMode="middle" style={styles.detail} testID="tokenFingerprintText"> | ||
{detail} | ||
</Text> | ||
</Middle> | ||
|
||
<Right> | ||
{info.type !== Portfolio.Token.Type.NFT && variant !== 'swap' && ( | ||
<View style={styles.row} testID="tokenAmountText"> | ||
{priceImpactRisk === 'moderate' && <Icon.Info size={24} color={priceImpactRiskTextColor} />} | ||
|
||
{priceImpactRisk === 'high' && <Icon.Warning size={24} color={priceImpactRiskTextColor} />} | ||
|
||
<Text style={[styles.quantity, {color: priceImpactRiskTextColor}]}>{formattedQuantity}</Text> | ||
</View> | ||
)} | ||
|
||
{isPrimary && variant !== 'swap' && ( | ||
<PairedBalance isPrivacyOff={isPrivacyOff} amount={amount} privacyPlaceholder={privacyPlaceholder} /> | ||
)} | ||
</Right> | ||
</View> | ||
) | ||
} | ||
|
||
const Left = ({style, ...props}: ViewProps) => <View style={style} {...props} /> | ||
const Middle = ({style, ...props}: ViewProps) => ( | ||
<View style={[style, {flex: 1, justifyContent: 'center', paddingHorizontal: 8}]} {...props} /> | ||
) | ||
const Right = ({style, ...props}: ViewProps) => <View style={style} {...props} /> | ||
|
||
export const AmountItemPlaceholder = ({style}: ViewProps) => { | ||
const {colors} = useStyles() | ||
return ( | ||
<View | ||
style={[ | ||
style, | ||
{ | ||
display: 'flex', | ||
flexDirection: 'row', | ||
gap: 12, | ||
height: 56, | ||
}, | ||
]} | ||
> | ||
<View | ||
style={{ | ||
backgroundColor: colors.background, | ||
borderRadius: 8, | ||
flexGrow: 3, | ||
}} | ||
/> | ||
|
||
<View | ||
style={{ | ||
backgroundColor: colors.background, | ||
borderRadius: 8, | ||
flexGrow: 1, | ||
}} | ||
/> | ||
</View> | ||
) | ||
} | ||
|
||
const useStyles = () => { | ||
const {theme} = useTheme() | ||
const {color, typography} = theme | ||
const styles = StyleSheet.create({ | ||
container: { | ||
flexDirection: 'row', | ||
justifyContent: 'space-between', | ||
alignItems: 'center', | ||
}, | ||
name: { | ||
color: color.gray[900], | ||
fontSize: 16, | ||
lineHeight: 22, | ||
fontWeight: '500', | ||
fontFamily: 'Rubik-Medium', | ||
}, | ||
detail: { | ||
color: color.gray[600], | ||
fontSize: 12, | ||
lineHeight: 18, | ||
maxWidth: 140, | ||
}, | ||
quantity: { | ||
color: color.gray[900], | ||
...typography['body-1-l-regular'], | ||
textAlign: 'right', | ||
flexGrow: 1, | ||
}, | ||
row: { | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
}, | ||
}) | ||
|
||
const colors = { | ||
text: color.gray[900], | ||
background: color.gray[200], | ||
icon: color.secondary[600], | ||
} | ||
|
||
return {styles, colors} | ||
} |
Oops, something went wrong.