diff --git a/src/assets/img/asset_ada.png b/src/assets/img/asset_ada.png
new file mode 100644
index 0000000000..5df0efc13d
Binary files /dev/null and b/src/assets/img/asset_ada.png differ
diff --git a/src/assets/img/asset_ada@2x.png b/src/assets/img/asset_ada@2x.png
new file mode 100644
index 0000000000..ade879edf4
Binary files /dev/null and b/src/assets/img/asset_ada@2x.png differ
diff --git a/src/assets/img/asset_ada@3x.png b/src/assets/img/asset_ada@3x.png
new file mode 100644
index 0000000000..dfb82fd52b
Binary files /dev/null and b/src/assets/img/asset_ada@3x.png differ
diff --git a/src/assets/img/asset_no_image.png b/src/assets/img/asset_no_image.png
new file mode 100644
index 0000000000..9f6b85f340
Binary files /dev/null and b/src/assets/img/asset_no_image.png differ
diff --git a/src/assets/img/asset_no_image@2x.png b/src/assets/img/asset_no_image@2x.png
new file mode 100644
index 0000000000..67072da553
Binary files /dev/null and b/src/assets/img/asset_no_image@2x.png differ
diff --git a/src/assets/img/asset_no_image@3x.png b/src/assets/img/asset_no_image@3x.png
new file mode 100644
index 0000000000..b09bd95f70
Binary files /dev/null and b/src/assets/img/asset_no_image@3x.png differ
diff --git a/src/components/Common/MultiAsset/AssetList.js b/src/components/Common/MultiAsset/AssetList.js
index 75d7311bb1..5f5614c633 100644
--- a/src/components/Common/MultiAsset/AssetList.js
+++ b/src/components/Common/MultiAsset/AssetList.js
@@ -1,18 +1,13 @@
// @flow
import React from 'react'
-import {type IntlShape, injectIntl} from 'react-intl'
+import {defineMessages, useIntl} from 'react-intl'
import {FlatList, Text, TouchableOpacity, View} from 'react-native'
import type {TokenEntry} from '../../../crypto/MultiToken'
import globalMessages, {txLabels} from '../../../i18n/global-messages'
import type {Token} from '../../../types/HistoryTransaction'
-import {
- ASSET_DENOMINATION,
- formatTokenAmount,
- getAssetDenomination,
- getAssetDenominationOrUnknown,
-} from '../../../utils/format'
+import {formatTokenAmount, getName, getTicker, getTokenFingerprint} from '../../../utils/format'
import assetListSendStyle from './styles/AssetListSend.style'
import assetListTransactionStyle from './styles/AssetListTransaction.style'
import baseStyle from './styles/Base.style'
@@ -25,21 +20,23 @@ type AssetRowProps = {|
assetMetadata: Token,
backColor: {|backgroundColor: string|},
onSelect?: (TokenEntry) => any,
- intl: IntlShape,
|}
-const AssetRow = ({styles, asset, assetMetadata, backColor, onSelect, intl}: AssetRowProps) => {
+const AssetRow = ({styles, asset, assetMetadata, backColor, onSelect}: AssetRowProps) => {
+ const intl = useIntl()
const item = (
<>
{assetMetadata.isDefault
- ? getAssetDenominationOrUnknown(assetMetadata, ASSET_DENOMINATION.TICKER, intl)
- : getAssetDenominationOrUnknown(assetMetadata, ASSET_DENOMINATION.NAME, intl)}
+ ? getTicker(assetMetadata) || intl.formatMessage(messages.unknownAssetName)
+ : getName(assetMetadata) || intl.formatMessage(messages.unknownAssetName)}
+
- {assetMetadata.isDefault ? '' : getAssetDenomination(assetMetadata, ASSET_DENOMINATION.FINGERPRINT)}
+ {assetMetadata.isDefault ? '' : getTokenFingerprint(assetMetadata)}
+
{formatTokenAmount(asset.amount, assetMetadata, 15)}
@@ -62,10 +59,9 @@ type AssetListProps = {
assetsMetadata: Dict,
styles: NodeStyle,
onSelect?: (TokenEntry) => any,
- intl: IntlShape,
}
-
-const AssetList = ({assets, assetsMetadata, styles, onSelect, intl}: AssetListProps) => {
+const AssetList = ({assets, assetsMetadata, styles, onSelect}: AssetListProps) => {
+ const intl = useIntl()
const colors = [styles.rowColor1, styles.rowColor2]
return (
@@ -74,10 +70,11 @@ const AssetList = ({assets, assetsMetadata, styles, onSelect, intl}: AssetListPr
{intl.formatMessage(globalMessages.assetsLabel)}
{intl.formatMessage(txLabels.amount)}
+
index.toString()}
+ data={assets.sort((a) => (assetsMetadata[a.identifier].isDefault ? -1 : 1))}
+ keyExtractor={(item) => item.identifier}
renderItem={({item, index}) => (
)}
/>
@@ -93,4 +89,11 @@ const AssetList = ({assets, assetsMetadata, styles, onSelect, intl}: AssetListPr
)
}
-export default injectIntl(AssetList)
+export default AssetList
+
+const messages = defineMessages({
+ unknownAssetName: {
+ id: 'utils.format.unknownAssetName',
+ defaultMessage: '!!![Unknown asset name]',
+ },
+})
diff --git a/src/components/Common/MultiAsset/AssetList.stories.js b/src/components/Common/MultiAsset/AssetList.stories.js
new file mode 100644
index 0000000000..86931a265e
--- /dev/null
+++ b/src/components/Common/MultiAsset/AssetList.stories.js
@@ -0,0 +1,177 @@
+// @flow
+
+import {action} from '@storybook/addon-actions'
+import {storiesOf} from '@storybook/react-native'
+import {BigNumber} from 'bignumber.js'
+import React from 'react'
+
+import type {TokenEntry} from '../../../crypto/MultiToken'
+import {type Token} from '../../../types/HistoryTransaction'
+import AssetList from './AssetList'
+import sendStyle from './styles/AssetListSend.style'
+import baseStyle from './styles/Base.style'
+
+storiesOf('AssetList', module)
+ .add('baseStyle', () => (
+
+ ))
+ .add('sendStyle', () => (
+
+ ))
+
+const assetTokens: Array = [
+ {
+ networkId: 123,
+ identifier: 'policyId123assetName123',
+ amount: new BigNumber(12344.00234523),
+ },
+ {
+ networkId: 456,
+ identifier: 'policyId456assetName456',
+ amount: new BigNumber(10),
+ },
+ {
+ networkId: 789,
+ identifier: 'policyId789assetName789',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 111,
+ identifier: 'policyId111assetName111',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 222,
+ identifier: 'policyId222assetName222',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 333,
+ identifier: 'policyId333assetName333',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 444,
+ identifier: 'policyId444assetName444',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 555,
+ identifier: 'policyId555assetName555',
+ amount: new BigNumber(0.00001),
+ },
+]
+const assetTokenInfos: Dict = {
+ policyId123assetName123: {
+ networkId: 123,
+ isDefault: false,
+ identifier: 'policyId123assetName123',
+ metadata: {
+ assetName: 'assetName123',
+ longName: 'longName123',
+ maxSupply: 'maxSupply123',
+ numberOfDecimals: 10,
+ policyId: 'policyId1233',
+ ticker: 'ticker123',
+ type: 'Cardano',
+ },
+ },
+ policyId456assetName456: {
+ networkId: 456,
+ isDefault: true,
+ identifier: 'policyId456assetName456',
+ metadata: {
+ assetName: 'assetName456',
+ longName: 'longName456',
+ maxSupply: 'maxSupply456',
+ numberOfDecimals: 10,
+ policyId: 'policyId4566',
+ ticker: 'ticker456',
+ type: 'Cardano',
+ },
+ },
+ policyId789assetName789: {
+ networkId: 789,
+ isDefault: false,
+ identifier: 'policyId789assetName789',
+ metadata: {
+ assetName: 'assetName789',
+ longName: 'longName789',
+ maxSupply: 'maxSupply789',
+ numberOfDecimals: 10,
+ policyId: 'policyId7899',
+ ticker: 'ticker789',
+ type: 'Cardano',
+ },
+ },
+ policyId111assetName111: {
+ networkId: 111,
+ isDefault: false,
+ identifier: 'policyId111assetName111',
+ metadata: {
+ assetName: 'assetName111',
+ longName: 'longName111',
+ maxSupply: 'maxSupply111',
+ numberOfDecimals: 10,
+ policyId: 'policyId1119',
+ ticker: 'ticker111',
+ type: 'Cardano',
+ },
+ },
+ policyId222assetName222: {
+ networkId: 222,
+ isDefault: false,
+ identifier: 'policyId222assetName222',
+ metadata: {
+ assetName: 'assetName222',
+ longName: 'longName222',
+ maxSupply: 'maxSupply222',
+ numberOfDecimals: 10,
+ policyId: 'policyId2229',
+ ticker: 'ticker222',
+ type: 'Cardano',
+ },
+ },
+ policyId333assetName333: {
+ networkId: 333,
+ isDefault: false,
+ identifier: 'policyId333assetName333',
+ metadata: {
+ assetName: 'assetName333',
+ longName: 'longName333',
+ maxSupply: 'maxSupply333',
+ numberOfDecimals: 10,
+ policyId: 'policyId3339',
+ ticker: 'ticker333',
+ type: 'Cardano',
+ },
+ },
+ policyId444assetName444: {
+ networkId: 444,
+ isDefault: false,
+ identifier: 'policyId444assetName444',
+ metadata: {
+ assetName: 'assetName444',
+ longName: 'longName444',
+ maxSupply: 'maxSupply444',
+ numberOfDecimals: 10,
+ policyId: 'policyId4449',
+ ticker: 'ticker444',
+ type: 'Cardano',
+ },
+ },
+ policyId555assetName555: {
+ networkId: 555,
+ isDefault: false,
+ identifier: 'policyId555assetName555',
+ metadata: {
+ assetName: 'assetName555',
+ longName: 'longName555',
+ maxSupply: 'maxSupply555',
+ numberOfDecimals: 10,
+ policyId: 'policyId5559',
+ ticker: 'ticker555',
+ type: 'Cardano',
+ },
+ },
+}
diff --git a/src/components/Common/MultiAsset/AssetSelector.js b/src/components/Common/MultiAsset/AssetSelector.js
deleted file mode 100644
index 16817b38b0..0000000000
--- a/src/components/Common/MultiAsset/AssetSelector.js
+++ /dev/null
@@ -1,86 +0,0 @@
-// @flow
-
-import React, {useState} from 'react'
-import {type IntlShape, defineMessages, injectIntl} from 'react-intl'
-import {Image, LayoutAnimation, TouchableOpacity, View} from 'react-native'
-
-import arrowDown from '../../../assets/img/arrow_down_fill.png'
-import arrowUp from '../../../assets/img/arrow_up_fill.png'
-import closeIcon from '../../../assets/img/cross_fill.png'
-import type {TokenEntry} from '../../../crypto/MultiToken'
-import type {Token} from '../../../types/HistoryTransaction'
-import {getAssetDenominationOrId} from '../../../utils/format'
-import {Text} from '../../UiKit'
-import AssetList from './AssetList'
-import assetListStyle from './styles/AssetListSend.style'
-import styles from './styles/AssetSelector.style'
-
-const messages = defineMessages({
- placeHolder: {
- id: 'components.ma.assetSelector.placeHolder',
- defaultMessage: '!!!Select an asset',
- },
-})
-
-type Props = {
- label?: string,
- assets: Array,
- assetsMetadata: Dict,
- onSelect: (TokenEntry | void) => any,
- selectedAsset: TokenEntry | null,
- unselectEnabled: boolean,
- intl: IntlShape,
-}
-
-const AssetSelector = ({label, assets, assetsMetadata, onSelect, selectedAsset, unselectEnabled, intl}: Props) => {
- const [expanded, setExpanded] = useState(false)
-
- const toggleExpand = () => {
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
- setExpanded(!expanded)
- }
- return (
-
- toggleExpand()}>
- {selectedAsset == null ? (
- {intl.formatMessage(messages.placeHolder)}
- ) : (
-
- {' '}
- {getAssetDenominationOrId(assetsMetadata[selectedAsset.identifier])}{' '}
-
- )}
-
- {unselectEnabled && (
- onSelect()}>
-
-
- )}
-
-
-
-
-
- {label != null && (
-
- {label}
-
- )}
- {expanded && (
-
- {
- onSelect(item)
- toggleExpand()
- }}
- styles={assetListStyle}
- assets={assets}
- assetsMetadata={assetsMetadata}
- />
-
- )}
-
- )
-}
-
-export default injectIntl(AssetSelector)
diff --git a/src/components/Send/AmountField.js b/src/components/Send/AmountField.js
index 7b8589e546..4e45ec037c 100644
--- a/src/components/Send/AmountField.js
+++ b/src/components/Send/AmountField.js
@@ -3,7 +3,7 @@
import React from 'react'
import {defineMessages, useIntl} from 'react-intl'
-import {ValidatedTextInput} from '../UiKit'
+import {TextInput} from '../UiKit'
import {editedFormatter, pastedFormatter} from './amountUtils'
export const messages = defineMessages({
@@ -34,13 +34,13 @@ const AmountField = ({amount, error, editable, setAmount}: Props) => {
}
return (
-
)
diff --git a/src/components/Send/AssetSelectorScreen/AssetSelectorScreen.js b/src/components/Send/AssetSelectorScreen/AssetSelectorScreen.js
new file mode 100644
index 0000000000..6f3dc5e01e
--- /dev/null
+++ b/src/components/Send/AssetSelectorScreen/AssetSelectorScreen.js
@@ -0,0 +1,150 @@
+/* eslint-disable react-native/no-inline-styles */
+// @flow
+
+import React from 'react'
+import {defineMessages} from 'react-intl'
+import {useIntl} from 'react-intl'
+import {FlatList, TouchableOpacity, View} from 'react-native'
+import {Avatar} from 'react-native-paper'
+import {SafeAreaView} from 'react-native-safe-area-context'
+
+import AdaImage from '../../../assets/img/asset_ada.png'
+import NoImage from '../../../assets/img/asset_no_image.png'
+import type {TokenEntry} from '../../../crypto/MultiToken'
+import globalMessages, {txLabels} from '../../../i18n/global-messages'
+import {COLORS} from '../../../styles/config'
+import {type Token} from '../../../types/HistoryTransaction'
+import {decodeHexAscii, getAssetDenominationOrId} from '../../../utils/format'
+import {Button, Spacer, Text, TextInput} from '../../UiKit'
+
+type Props = {
+ assetTokens: Array,
+ assetTokenInfos: Dict,
+ onSelect: (TokenEntry) => mixed,
+ onSelectAll: () => mixed,
+}
+export const AssetSelectorScreen = ({assetTokens, assetTokenInfos, onSelect, onSelectAll}: Props) => {
+ const intl = useIntl()
+
+ const [filter, setFilter] = React.useState('')
+ const visibleAssetTokens = matches(assetTokenInfos, assetTokens, filter)
+ .sort((a: TokenEntry, b: TokenEntry) => (a.amount.isGreaterThan(b.amount) ? -1 : 1))
+ .sort((a) => (getTokenInfo(assetTokenInfos, a).isDefault ? -1 : 1))
+
+ return (
+
+
+ setFilter(normalize(text))} />
+
+
+ {intl.formatMessage(globalMessages.assetsLabel)}
+ {intl.formatMessage(txLabels.amount)}
+
+
+
+
+
+
+
+ (
+
+ )}
+ ItemSeparatorComponent={() => }
+ bounces={false}
+ contentContainerStyle={{paddingTop: 16, paddingHorizontal: 16}}
+ keyExtractor={(item) => item.identifier}
+ />
+
+
+
+
+ )
+}
+
+export default AssetSelectorScreen
+
+type AssetSelectorItemProps = {
+ assetToken: TokenEntry,
+ tokenInfo: Token,
+ onPress: (TokenEntry) => mixed,
+}
+const AssetSelectorItem = ({assetToken, tokenInfo, onPress}: AssetSelectorItemProps) => {
+ const intl = useIntl()
+
+ return (
+ onPress(assetToken)}>
+
+
+
+
+
+
+
+ {getAssetDenominationOrId(tokenInfo) || intl.formatMessage(messages.unknownAsset)}
+
+
+ {tokenInfo.metadata.assetName}
+
+
+
+
+ {String(assetToken.amount)}
+
+
+
+ )
+}
+
+const HR = (props) =>
+const Icon = (props) => (
+
+)
+const Actions = (props) =>
+
+const normalize = (text: string) => text.trim().toLowerCase()
+
+const SearchInput = (props) => {
+ const intl = useIntl()
+ return
+}
+const getTokenInfo = (tokenInfos: Dict, token: TokenEntry) => tokenInfos[token.identifier]
+const matches = (tokenInfos: Dict, tokens: Array, filter: string) =>
+ tokens.filter((assetToken) => {
+ const tokenInfo = getTokenInfo(tokenInfos, assetToken)
+
+ return (
+ normalize(decodeHexAscii(tokenInfo.metadata.assetName) || '').includes(filter) ||
+ normalize(tokenInfo.metadata.ticker || '').includes(filter) ||
+ normalize(tokenInfo.metadata.longName || '').includes(filter) ||
+ normalize(tokenInfo.identifier).includes(filter) ||
+ normalize(tokenInfo.metadata.assetName).includes(filter) ||
+ normalize(tokenInfo.metadata.policyId).includes(filter)
+ )
+ })
+
+const messages = defineMessages({
+ searchLabel: {
+ id: 'components.send.assetselectorscreen.searchlabel',
+ defaultMessage: '!!!Search by name or subject',
+ },
+ sendAllAssets: {
+ id: 'components.send.assetselectorscreen.sendallassets',
+ defaultMessage: '!!!SELECT ALL ASSETS',
+ },
+ unknownAsset: {
+ id: 'components.send.assetselectorscreen.unknownasset',
+ defaultMessage: '!!!Unknown asset',
+ },
+})
diff --git a/src/components/Send/AssetSelectorScreen/AssetSelectorScreen.stories.js b/src/components/Send/AssetSelectorScreen/AssetSelectorScreen.stories.js
new file mode 100644
index 0000000000..5ec6ffb483
--- /dev/null
+++ b/src/components/Send/AssetSelectorScreen/AssetSelectorScreen.stories.js
@@ -0,0 +1,179 @@
+/* eslint-disable no-use-before-define */
+// @flow
+
+import {action} from '@storybook/addon-actions'
+import {storiesOf} from '@storybook/react-native'
+import {BigNumber} from 'bignumber.js'
+import React from 'react'
+
+import type {TokenEntry} from '../../../crypto/MultiToken'
+import {type Token} from '../../../types/HistoryTransaction'
+import AssetSelectorScreen from './AssetSelectorScreen'
+
+storiesOf('AssetSelectorScreen', module).add('Default', () => {
+ return (
+
+ )
+})
+
+const assetTokens: Array = [
+ {
+ networkId: 123,
+ identifier: 'policyId123assetName123',
+ amount: new BigNumber(12344.00234523),
+ },
+ {
+ networkId: 456,
+ identifier: 'policyId456assetName456',
+ amount: new BigNumber(10),
+ },
+ {
+ networkId: 789,
+ identifier: 'policyId789assetName789',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 111,
+ identifier: 'policyId111assetName111',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 222,
+ identifier: 'policyId222assetName222',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 333,
+ identifier: 'policyId333assetName333',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 444,
+ identifier: 'policyId444assetName444',
+ amount: new BigNumber(0.00001),
+ },
+ {
+ networkId: 555,
+ identifier: 'policyId555assetName555',
+ amount: new BigNumber(0.00001),
+ },
+]
+const assetTokenInfos: Dict = {
+ policyId123assetName123: {
+ networkId: 123,
+ isDefault: false,
+ identifier: 'policyId123assetName123',
+ metadata: {
+ assetName: 'assetName123',
+ longName: 'longName123',
+ maxSupply: 'maxSupply123',
+ numberOfDecimals: 10,
+ policyId: 'policyId1233',
+ ticker: 'ticker123',
+ type: 'Cardano',
+ },
+ },
+ policyId456assetName456: {
+ networkId: 456,
+ isDefault: true,
+ identifier: 'policyId456assetName456',
+ metadata: {
+ assetName: 'assetName456',
+ longName: 'longName456',
+ maxSupply: 'maxSupply456',
+ numberOfDecimals: 10,
+ policyId: 'policyId4566',
+ ticker: 'ticker456',
+ type: 'Cardano',
+ },
+ },
+ policyId789assetName789: {
+ networkId: 789,
+ isDefault: false,
+ identifier: 'policyId789assetName789',
+ metadata: {
+ assetName: 'assetName789',
+ longName: 'longName789',
+ maxSupply: 'maxSupply789',
+ numberOfDecimals: 10,
+ policyId: 'policyId7899',
+ ticker: 'ticker789',
+ type: 'Cardano',
+ },
+ },
+ policyId111assetName111: {
+ networkId: 111,
+ isDefault: false,
+ identifier: 'policyId111assetName111',
+ metadata: {
+ assetName: 'assetName111',
+ longName: 'longName111',
+ maxSupply: 'maxSupply111',
+ numberOfDecimals: 10,
+ policyId: 'policyId1119',
+ ticker: 'ticker111',
+ type: 'Cardano',
+ },
+ },
+ policyId222assetName222: {
+ networkId: 222,
+ isDefault: false,
+ identifier: 'policyId222assetName222',
+ metadata: {
+ assetName: 'assetName222',
+ longName: 'longName222',
+ maxSupply: 'maxSupply222',
+ numberOfDecimals: 10,
+ policyId: 'policyId2229',
+ ticker: 'ticker222',
+ type: 'Cardano',
+ },
+ },
+ policyId333assetName333: {
+ networkId: 333,
+ isDefault: false,
+ identifier: 'policyId333assetName333',
+ metadata: {
+ assetName: 'assetName333',
+ longName: 'longName333',
+ maxSupply: 'maxSupply333',
+ numberOfDecimals: 10,
+ policyId: 'policyId3339',
+ ticker: 'ticker333',
+ type: 'Cardano',
+ },
+ },
+ policyId444assetName444: {
+ networkId: 444,
+ isDefault: false,
+ identifier: 'policyId444assetName444',
+ metadata: {
+ assetName: 'assetName444',
+ longName: 'longName444',
+ maxSupply: 'maxSupply444',
+ numberOfDecimals: 10,
+ policyId: 'policyId4449',
+ ticker: 'ticker444',
+ type: 'Cardano',
+ },
+ },
+ policyId555assetName555: {
+ networkId: 555,
+ isDefault: false,
+ identifier: 'policyId555assetName555',
+ metadata: {
+ assetName: 'assetName555',
+ longName: 'longName555',
+ maxSupply: 'maxSupply555',
+ numberOfDecimals: 10,
+ policyId: 'policyId5559',
+ ticker: 'ticker555',
+ type: 'Cardano',
+ },
+ },
+}
diff --git a/src/components/Send/AssetSelectorScreen/index.js b/src/components/Send/AssetSelectorScreen/index.js
new file mode 100644
index 0000000000..4770d9ad3d
--- /dev/null
+++ b/src/components/Send/AssetSelectorScreen/index.js
@@ -0,0 +1,3 @@
+// @flow
+
+export * from './AssetSelectorScreen'
diff --git a/src/components/Send/SendScreen.js b/src/components/Send/SendScreen.js
index 0a23226395..f3cc160548 100644
--- a/src/components/Send/SendScreen.js
+++ b/src/components/Send/SendScreen.js
@@ -2,15 +2,15 @@
/* eslint-disable-next-line camelcase */
import {BigNum, min_ada_required} from '@emurgo/react-native-haskell-shelley'
+import {useNavigation} from '@react-navigation/native'
import {BigNumber} from 'bignumber.js'
import _ from 'lodash'
-import type {ComponentType} from 'react'
import React, {Component} from 'react'
-import {type IntlShape, defineMessages, injectIntl} from 'react-intl'
-import {ActivityIndicator, ScrollView, View} from 'react-native'
+import {type IntlShape, defineMessages, useIntl} from 'react-intl'
+import {ActivityIndicator, Image, ScrollView, View} from 'react-native'
+import {TouchableOpacity} from 'react-native-gesture-handler'
import {SafeAreaView} from 'react-native-safe-area-context'
-import {connect} from 'react-redux'
-import {compose} from 'redux'
+import {useDispatch, useSelector} from 'react-redux'
import {fetchUTXOs} from '../../actions/utxo'
import type {RawUtxo} from '../../api/types'
@@ -51,292 +51,16 @@ import {InvalidAssetAmount, parseAmountDecimal} from '../../utils/parsing'
import type {AddressValidationErrors, AmountValidationErrors, BalanceValidationErrors} from '../../utils/validators'
import {validateAddressAsync, validateAmount} from '../../utils/validators'
import DangerousActionModal from '../Common/DangerousActionModal'
-import AssetSelector from '../Common/MultiAsset/AssetSelector'
-import {Banner, Button, Checkbox, OfflineBanner, StatusBar, Text, ValidatedTextInput} from '../UiKit'
+import {Banner, Button, Checkbox, OfflineBanner, Spacer, StatusBar, Text, TextInput} from '../UiKit'
import AmountField from './AmountField'
import styles from './styles/SendScreen.style'
import UtxoAutoRefresher from './UtxoAutoRefresher'
-const amountInputErrorMessages = defineMessages({
- INVALID_AMOUNT: {
- id: 'components.send.sendscreen.amountInput.error.INVALID_AMOUNT',
- defaultMessage: '!!!Please enter valid amount',
- },
- TOO_MANY_DECIMAL_PLACES: {
- id: 'components.send.sendscreen.amountInput.error.TOO_MANY_DECIMAL_PLACES',
- defaultMessage: '!!!Please enter valid amount',
- },
- TOO_LARGE: {
- id: 'components.send.sendscreen.amountInput.error.TOO_LARGE',
- defaultMessage: '!!!Amount too large',
- },
- TOO_LOW: {
- id: 'components.send.sendscreen.amountInput.error.TOO_LOW',
- defaultMessage: '!!!Amount is too low',
- },
- LT_MIN_UTXO: {
- id: 'components.send.sendscreen.amountInput.error.LT_MIN_UTXO',
- defaultMessage: '!!!Cannot send less than {minUtxo} {ticker}',
- },
- NEGATIVE: {
- id: 'components.send.sendscreen.amountInput.error.NEGATIVE',
- defaultMessage: '!!!Amount must be positive',
- },
- insufficientBalance: {
- id: 'components.send.sendscreen.amountInput.error.insufficientBalance',
- defaultMessage: '!!!Not enough money to make this transaction',
- },
- assetOverflow: {
- id: 'components.send.sendscreen.amountInput.error.assetOverflow',
- defaultMessage: '!!!!Maximum value of a token inside a UTXO exceeded (overflow).',
- },
-})
-
-const messages = defineMessages({
- feeLabel: {
- id: 'components.send.sendscreen.feeLabel',
- defaultMessage: '!!!Fee',
- },
- feeNotAvailable: {
- id: 'components.send.sendscreen.feeNotAvailable',
- defaultMessage: '!!!-',
- },
- balanceAfterLabel: {
- id: 'components.send.sendscreen.balanceAfterLabel',
- defaultMessage: '!!!Balance after',
- },
- balanceAfterNotAvailable: {
- id: 'components.send.sendscreen.balanceAfterNotAvailable',
- defaultMessage: '!!!-',
- },
- availableFundsBannerIsFetching: {
- id: 'components.send.sendscreen.availableFundsBannerIsFetching',
- defaultMessage: '!!!Checking balance...',
- },
- availableFundsBannerNotAvailable: {
- id: 'components.send.sendscreen.availableFundsBannerNotAvailable',
- defaultMessage: '!!!-',
- },
- addressInputErrorInvalidAddress: {
- id: 'components.send.sendscreen.addressInputErrorInvalidAddress',
- defaultMessage: '!!!Please enter valid address',
- },
- addressInputLabel: {
- id: 'components.send.sendscreen.addressInputLabel',
- defaultMessage: '!!!Address',
- },
- checkboxSendAllAssets: {
- id: 'components.send.sendscreen.checkboxSendAllAssets',
- defaultMessage: '!!!Send all assets (including all tokens)',
- },
- checkboxSendAll: {
- id: 'components.send.sendscreen.checkboxSendAll',
- defaultMessage: '!!!Send all {assetId}',
- },
- sendAllWarningTitle: {
- id: 'components.send.sendscreen.sendAllWarningTitle',
- defaultMessage: '!!!Do you really want to send all?',
- },
- sendAllWarningText: {
- id: 'components.send.sendscreen.sendAllWarningText',
- defaultMessage:
- '!!!You have selected the send all option. Please confirm that you understand how this feature works.',
- },
- sendAllWarningAlert1: {
- id: 'components.send.sendscreen.sendAllWarningAlert1',
- defaultMessage: '!!!All you {assetNameOrId} balance will be transferred in this transaction.',
- },
- sendAllWarningAlert2: {
- id: 'components.send.sendscreen.sendAllWarningAlert2',
- defaultMessage:
- '!!!All your tokens, including NFTs and any other native ' +
- 'assets in your wallet, will also be transferred in this transaction.',
- },
- sendAllWarningAlert3: {
- id: 'components.send.sendscreen.sendAllWarningAlert3',
- defaultMessage: '!!!After you confirm the transaction in the next screen, your wallet will be emptied.',
- },
- continueButton: {
- id: 'components.send.sendscreen.continueButton',
- defaultMessage: '!!!Continue',
- },
- errorBannerNetworkError: {
- id: 'components.send.sendscreen.errorBannerNetworkError',
- defaultMessage: '!!!We are experiencing issues with fetching your current balance. Click to retry.',
- },
- errorBannerPendingOutgoingTransaction: {
- id: 'components.send.sendscreen.errorBannerPendingOutgoingTransaction',
- defaultMessage: '!!!You cannot send a new transaction while an existing one is still pending',
- },
-})
-
-const Indicator = () => (
-
-
-
-)
-
-const getMinAda = async (selectedToken: Token, defaultAsset: DefaultAsset) => {
- const networkConfig = getCardanoNetworkConfigById(defaultAsset.networkId)
- const fakeAmount = new BigNumber('0') // amount doesn't matter for calculating min UTXO amount
- const fakeMultitoken = new MultiToken(
- [
- {
- identifier: defaultAsset.identifier,
- networkId: defaultAsset.networkId,
- amount: fakeAmount,
- },
- {
- identifier: selectedToken.identifier,
- networkId: selectedToken.networkId,
- amount: fakeAmount,
- },
- ],
- {
- defaultNetworkId: defaultAsset.networkId,
- defaultIdentifier: defaultAsset.identifier,
- },
- )
- const minAmount = await min_ada_required(
- await cardanoValueFromMultiToken(fakeMultitoken),
- await BigNum.from_str(networkConfig.MINIMUM_UTXO_VAL),
- )
- // if the user is sending a token, we need to make sure the resulting utxo
- // has at least the minimum amount of ADA in it
- return minAmount.to_str()
-}
-
-const getTransactionData = async (
- utxos: Array,
- address: string,
- amount: string,
- sendAll: boolean,
- defaultAsset: DefaultAsset,
- selectedToken: Token,
- serverTime: Date | void,
-): Promise => {
- const defaultTokenEntry = {
- defaultNetworkId: defaultAsset.networkId,
- defaultIdentifier: defaultAsset.identifier,
- }
- const sendTokenList = []
-
- if (sendAll) {
- sendTokenList.push({
- token: selectedToken,
- shouldSendAll: true,
- })
- } else {
- const amountBigNum = parseAmountDecimal(amount, selectedToken)
- sendTokenList.push({
- token: selectedToken,
- amount: amountBigNum.toString(),
- })
- }
- if (!selectedToken.isDefault && isHaskellShelleyNetwork(selectedToken.networkId)) {
- sendTokenList.push({
- token: defaultAsset,
- amount: await getMinAda(selectedToken, defaultAsset),
- })
- }
- return await walletManager.createUnsignedTx(utxos, address, sendTokenList, defaultTokenEntry, serverTime)
-}
-
-const recomputeAll = async ({amount, address, utxos, sendAll, defaultAsset, selectedTokenMeta, tokenBalance}) => {
- let amountErrors = validateAmount(amount, selectedTokenMeta)
- const addressErrors = await validateAddressAsync(address)
- let balanceErrors = Object.freeze({})
- let fee = null
- let balanceAfter = null
- let recomputedAmount = amount
-
- if (_.isEmpty(addressErrors) && utxos) {
- try {
- let _fee: ?MultiToken
-
- // we'll substract minAda from ADA balance if we are sending a token
- const minAda =
- !selectedTokenMeta.isDefault && isHaskellShelleyNetwork(selectedTokenMeta.networkId)
- ? new BigNumber(await getMinAda(selectedTokenMeta, defaultAsset))
- : new BigNumber('0')
-
- if (sendAll) {
- const unsignedTx = await getTransactionData(utxos, address, amount, sendAll, defaultAsset, selectedTokenMeta)
- _fee = await unsignedTx.fee()
-
- if (selectedTokenMeta.isDefault) {
- recomputedAmount = normalizeTokenAmount(
- tokenBalance.getDefault().minus(_fee.getDefault()),
- selectedTokenMeta,
- ).toString()
- balanceAfter = new BigNumber('0')
- } else {
- const selectedTokenBalance = tokenBalance.get(selectedTokenMeta.identifier)
- if (selectedTokenBalance == null) {
- throw new Error('selectedTokenBalance is null, shouldnt happen')
- }
- recomputedAmount = normalizeTokenAmount(selectedTokenBalance, selectedTokenMeta).toString()
- balanceAfter = tokenBalance.getDefault().minus(_fee.getDefault()).minus(minAda)
- }
-
- // for sendAll we set the amount so the format is error-free
- amountErrors = Object.freeze({})
- } else if (_.isEmpty(amountErrors)) {
- const parsedAmount = selectedTokenMeta.isDefault
- ? parseAmountDecimal(amount, selectedTokenMeta)
- : new BigNumber('0')
- const unsignedTx = await getTransactionData(utxos, address, amount, false, defaultAsset, selectedTokenMeta)
- _fee = await unsignedTx.fee()
- balanceAfter = tokenBalance.getDefault().minus(parsedAmount).minus(minAda).minus(_fee.getDefault())
- }
- // now we can update fee as well
- fee = _fee != null ? _fee.getDefault() : null
- } catch (err) {
- if (err instanceof InsufficientFunds) {
- balanceErrors = {insufficientBalance: true}
- } else if (err instanceof AssetOverflowError) {
- balanceErrors = {assetOverflow: true}
- }
- }
- }
- return {
- amount: recomputedAmount,
- amountErrors,
- addressErrors,
- balanceErrors,
- fee,
- balanceAfter,
- }
-}
-
-const getAmountErrorText = (intl, amountErrors, balanceErrors, defaultAsset) => {
- if (amountErrors.invalidAmount != null) {
- const msgOptions = {}
- if (amountErrors.invalidAmount === InvalidAssetAmount.ERROR_CODES.LT_MIN_UTXO) {
- const networkConfig = getCardanoNetworkConfigById(defaultAsset.networkId)
- const amount = new BigNumber(networkConfig.MINIMUM_UTXO_VAL)
- // remove decimal part if it's equal to 0
- const decimalPart = amount.modulo(Math.pow(10, defaultAsset.metadata.numberOfDecimals))
- const minUtxo = decimalPart.eq('0')
- ? formatTokenInteger(amount, defaultAsset)
- : formatTokenAmount(amount, defaultAsset)
- const ticker = defaultAsset.metadata.ticker
- Object.assign(msgOptions, {minUtxo, ticker})
- }
- return intl.formatMessage(amountInputErrorMessages[amountErrors.invalidAmount], msgOptions)
- }
- if (balanceErrors.insufficientBalance === true) {
- return intl.formatMessage(amountInputErrorMessages.insufficientBalance)
- }
- if (balanceErrors.assetOverflow === true) {
- return intl.formatMessage(amountInputErrorMessages.assetOverflow)
- }
- return null
-}
-
-type Props = {
- navigation: Navigation,
+type LegacyProps = {|
intl: IntlShape,
+ navigation: Navigation,
+ selectedAsset: TokenEntry,
+ sendAll: boolean,
tokenBalance: MultiToken,
isFetchingBalance: boolean,
lastFetchingError: any,
@@ -347,7 +71,10 @@ type Props = {
hasPendingOutgoingTransaction: boolean,
fetchUTXOs: () => void,
serverStatus: ServerStatusCache,
-}
+ selectedAsset: TokenEntry,
+ fetchUTXOs: () => void,
+ onSendAll: (boolean) => mixed,
+|}
type State = {
address: string,
@@ -357,14 +84,12 @@ type State = {
balanceErrors: BalanceValidationErrors,
fee: ?BigNumber,
balanceAfter: ?BigNumber,
- sendAll: boolean,
- selectedAsset: TokenEntry,
recomputing: boolean,
showSendAllWarning: boolean,
}
// eslint-disable-next-line react-prefer-function-component/react-prefer-function-component
-class SendScreen extends Component {
+class SendScreenLegacy extends Component {
state = {
address: '',
addressErrors: {addressIsRequired: true},
@@ -373,8 +98,6 @@ class SendScreen extends Component {
fee: null,
balanceAfter: null,
balanceErrors: Object.freeze({}),
- sendAll: false,
- selectedAsset: this.props.tokenBalance.getDefaultEntry(),
recomputing: false,
showSendAllWarning: false,
}
@@ -389,25 +112,36 @@ class SendScreen extends Component {
this.props.navigation.setParams({onScanAmount: this.handleAmountChange})
}
- async componentDidUpdate(prevProps, prevState) {
- const utxos = this.props.utxos
- const {address, amount, sendAll, selectedAsset} = this.state
+ async componentDidUpdate(prevProps: LegacyProps, prevState: State) {
+ const {selectedAsset, utxos, sendAll} = this.props
+ const {address, amount} = this.state
- const prevUtxos = prevProps.utxos
- const {address: prevAddress, amount: prevAmount, sendAll: prevSendAll, selectedAsset: prevSelectedAsset} = prevState
+ const {address: prevAddress, amount: prevAmount} = prevState
if (
- prevUtxos !== utxos ||
+ prevProps.utxos !== utxos ||
prevAddress !== address ||
prevAmount !== amount ||
- prevSendAll !== sendAll ||
- prevSelectedAsset !== selectedAsset
+ prevProps.sendAll !== sendAll ||
+ prevProps.selectedAsset !== selectedAsset
) {
await this.revalidate({utxos, address, amount, sendAll, selectedAsset})
}
}
- async revalidate({utxos, address, amount, sendAll, selectedAsset}) {
+ async revalidate({
+ utxos,
+ address,
+ amount,
+ sendAll,
+ selectedAsset,
+ }: {
+ utxos: ?Array,
+ address: string,
+ amount: string,
+ sendAll: boolean,
+ selectedAsset: TokenEntry,
+ }) {
this.setState({
fee: null,
balanceAfter: null,
@@ -430,7 +164,7 @@ class SendScreen extends Component {
if (
this.state.address !== address ||
this.state.amount !== amount ||
- this.state.sendAll !== sendAll ||
+ this.props.sendAll !== sendAll ||
this.props.utxos !== utxos
) {
return
@@ -444,24 +178,14 @@ class SendScreen extends Component {
handleAddressChange: (string) => void = (address) => this.setState({address})
- onAssetSelect: (TokenEntry | void) => void = (token) => {
- if (token === undefined) {
- this.setState({selectedAsset: this.props.tokenBalance.getDefaultEntry()})
- } else {
- this.setState({selectedAsset: token})
- }
- }
-
handleAmountChange: (string) => void = (amount) => this.setState({amount})
- handleCheckBoxChange: (boolean) => void = (sendAll) => this.setState({sendAll})
-
openSendAllWarning: () => void = () => this.setState({showSendAllWarning: true})
closeSendAllWarning: () => void = () => this.setState({showSendAllWarning: false})
onConfirm: () => Promise = async () => {
- if (this.state.sendAll) {
+ if (this.props.sendAll) {
this.openSendAllWarning()
return
}
@@ -469,8 +193,9 @@ class SendScreen extends Component {
}
handleConfirm: () => Promise = async () => {
- const {navigation, utxos, tokenBalance, defaultAsset, tokenMetadata, serverStatus} = this.props
- const {address, amount, sendAll, selectedAsset} = this.state
+ const {navigation, utxos, tokenBalance, defaultAsset, tokenMetadata, serverStatus, sendAll, selectedAsset} =
+ this.props
+ const {address, amount} = this.state
const selectedTokenMeta = tokenMetadata[selectedAsset.identifier]
if (selectedTokenMeta == null) {
@@ -499,7 +224,7 @@ class SendScreen extends Component {
_.isEmpty(balanceErrors) &&
this.state.amount === amount &&
this.state.address === address &&
- this.state.selectedAsset === selectedAsset &&
+ this.props.selectedAsset === selectedAsset &&
this.props.utxos === utxos
if (isValid === true) {
@@ -513,6 +238,7 @@ class SendScreen extends Component {
selectedTokenMeta,
serverStatus.serverTime,
)
+
const fee = (await transactionData.fee()).getDefault()
const defaultAssetAmount = selectedTokenMeta.isDefault
@@ -618,8 +344,8 @@ class SendScreen extends Component {
}
renderSendAllWarning = () => {
- const {intl, tokenMetadata} = this.props
- const {showSendAllWarning, selectedAsset} = this.state
+ const {intl, tokenMetadata, selectedAsset} = this.props
+ const {showSendAllWarning} = this.state
const selectedTokenMeta = tokenMetadata[selectedAsset.identifier]
const isDefault = selectedTokenMeta.isDefault
@@ -669,11 +395,13 @@ class SendScreen extends Component {
isOnline,
hasPendingOutgoingTransaction,
defaultAsset,
- tokenBalance,
tokenMetadata,
+ selectedAsset,
+ navigation,
+ sendAll,
} = this.props
- const {address, amount, amountErrors, addressErrors, balanceErrors, sendAll, selectedAsset} = this.state
+ const {amount, amountErrors, addressErrors, balanceErrors} = this.state
const isValid =
isOnline &&
@@ -685,97 +413,397 @@ class SendScreen extends Component {
_.isEmpty(amountErrors) &&
_.isEmpty(balanceErrors)
- const amountErrorText = getAmountErrorText(intl, amountErrors, balanceErrors, defaultAsset)
+ const amountErrorText = getAmountErrorText(intl, amountErrors, balanceErrors, defaultAsset)
+
+ const selectedAssetMeta = tokenMetadata[selectedAsset.identifier]
+
+ const assetDenomination = truncateWithEllipsis(getAssetDenominationOrId(selectedAssetMeta), 20)
+
+ return (
+
+
+
+
+ {this.renderErrorBanners()}
+ {this.renderAvailableAmountBanner()}
+
+
+ {this.renderBalanceAfterTransaction()}
+ {this.renderFee()}
+
+
+
+ navigation.navigate('select-asset')}>
+ }
+ editable={false}
+ label={'Select Asset'}
+ value={`${assetDenomination}: ${String(selectedAsset.amount)}`}
+ />
+
+
+
+
+
+
+ {
+ this.props.onSendAll(sendAll)
+ }}
+ text={
+ selectedAssetMeta.isDefault
+ ? intl.formatMessage(messages.checkboxSendAllAssets)
+ : intl.formatMessage(messages.checkboxSendAll, {assetId: assetDenomination})
+ }
+ />
+
+ {this.state.recomputing && }
+
+
+
+
+
+
+ {this.renderSendAllWarning()}
+
+ )
+ }
+}
+
+type Props = {|
+ selectedAsset: TokenEntry,
+ sendAll: boolean,
+ onSendAll: (boolean) => mixed,
+|}
+export const SendScreen = (props: Props) => {
+ const intl = useIntl()
+ const navigation = useNavigation()
+
+ const tokenBalance = useSelector(tokenBalanceSelector)
+ const isFetchingBalance = useSelector(isFetchingUtxosSelector)
+ const lastFetchingError = useSelector(lastUtxosFetchErrorSelector)
+ const tokenMetadata = useSelector(tokenInfoSelector)
+ const defaultAsset = useSelector(defaultNetworkAssetSelector)
+ const utxos = useSelector(utxosSelector)
+ const hasPendingOutgoingTransaction = useSelector(hasPendingOutgoingTransactionSelector)
+ const isOnline = useSelector(isOnlineSelector)
+ const serverStatus = useSelector(serverStatusSelector)
+
+ const dispatch = useDispatch()
+
+ return (
+ dispatch(fetchUTXOs())}
+ tokenBalance={tokenBalance}
+ isFetchingBalance={isFetchingBalance}
+ lastFetchingError={lastFetchingError}
+ defaultAsset={defaultAsset}
+ tokenMetadata={tokenMetadata}
+ utxos={utxos}
+ hasPendingOutgoingTransaction={hasPendingOutgoingTransaction}
+ isOnline={isOnline}
+ serverStatus={serverStatus}
+ {...props}
+ />
+ )
+}
+
+export default SendScreen
+
+const Indicator = () => (
+
+
+
+)
+
+const getMinAda = async (selectedToken: Token, defaultAsset: DefaultAsset) => {
+ const networkConfig = getCardanoNetworkConfigById(defaultAsset.networkId)
+ const fakeAmount = new BigNumber('0') // amount doesn't matter for calculating min UTXO amount
+ const fakeMultitoken = new MultiToken(
+ [
+ {
+ identifier: defaultAsset.identifier,
+ networkId: defaultAsset.networkId,
+ amount: fakeAmount,
+ },
+ {
+ identifier: selectedToken.identifier,
+ networkId: selectedToken.networkId,
+ amount: fakeAmount,
+ },
+ ],
+ {
+ defaultNetworkId: defaultAsset.networkId,
+ defaultIdentifier: defaultAsset.identifier,
+ },
+ )
+ const minAmount = await min_ada_required(
+ await cardanoValueFromMultiToken(fakeMultitoken),
+ await BigNum.from_str(networkConfig.MINIMUM_UTXO_VAL),
+ )
+ // if the user is sending a token, we need to make sure the resulting utxo
+ // has at least the minimum amount of ADA in it
+ return minAmount.to_str()
+}
+
+const getTransactionData = async (
+ utxos: Array,
+ address: string,
+ amount: string,
+ sendAll: boolean,
+ defaultAsset: DefaultAsset,
+ selectedToken: Token,
+ serverTime: Date | void,
+): Promise => {
+ const defaultTokenEntry = {
+ defaultNetworkId: defaultAsset.networkId,
+ defaultIdentifier: defaultAsset.identifier,
+ }
+ const sendTokenList = []
+
+ if (sendAll) {
+ sendTokenList.push({
+ token: selectedToken,
+ shouldSendAll: true,
+ })
+ } else {
+ const amountBigNum = parseAmountDecimal(amount, selectedToken)
+ sendTokenList.push({
+ token: selectedToken,
+ amount: amountBigNum.toString(),
+ })
+ }
+ if (!selectedToken.isDefault && isHaskellShelleyNetwork(selectedToken.networkId)) {
+ sendTokenList.push({
+ token: defaultAsset,
+ amount: await getMinAda(selectedToken, defaultAsset),
+ })
+ }
+ return await walletManager.createUnsignedTx(utxos, address, sendTokenList, defaultTokenEntry, serverTime)
+}
+
+const recomputeAll = async ({amount, address, utxos, sendAll, defaultAsset, selectedTokenMeta, tokenBalance}) => {
+ let amountErrors = validateAmount(amount, selectedTokenMeta)
+ const addressErrors = await validateAddressAsync(address)
+ let balanceErrors = Object.freeze({})
+ let fee = null
+ let balanceAfter = null
+ let recomputedAmount = amount
- const selectedAssetMeta = tokenMetadata[selectedAsset.identifier]
+ if (_.isEmpty(addressErrors) && utxos) {
+ try {
+ let _fee: ?MultiToken
- const assetDenomination = truncateWithEllipsis(getAssetDenominationOrId(selectedAssetMeta), 20)
+ // we'll substract minAda from ADA balance if we are sending a token
+ const minAda =
+ !selectedTokenMeta.isDefault && isHaskellShelleyNetwork(selectedTokenMeta.networkId)
+ ? new BigNumber(await getMinAda(selectedTokenMeta, defaultAsset))
+ : new BigNumber('0')
- return (
-
-
+ if (sendAll) {
+ const unsignedTx = await getTransactionData(utxos, address, amount, sendAll, defaultAsset, selectedTokenMeta)
+ _fee = await unsignedTx.fee()
-
- {this.renderErrorBanners()}
- {this.renderAvailableAmountBanner()}
+ if (selectedTokenMeta.isDefault) {
+ recomputedAmount = normalizeTokenAmount(
+ tokenBalance.getDefault().minus(_fee.getDefault()),
+ selectedTokenMeta,
+ ).toString()
+ balanceAfter = new BigNumber('0')
+ } else {
+ const selectedTokenBalance = tokenBalance.get(selectedTokenMeta.identifier)
+ if (selectedTokenBalance == null) {
+ throw new Error('selectedTokenBalance is null, shouldnt happen')
+ }
+ recomputedAmount = normalizeTokenAmount(selectedTokenBalance, selectedTokenMeta).toString()
+ balanceAfter = tokenBalance.getDefault().minus(_fee.getDefault()).minus(minAda)
+ }
-
- {this.renderBalanceAfterTransaction()}
- {this.renderFee()}
+ // for sendAll we set the amount so the format is error-free
+ amountErrors = Object.freeze({})
+ } else if (_.isEmpty(amountErrors)) {
+ const parsedAmount = selectedTokenMeta.isDefault
+ ? parseAmountDecimal(amount, selectedTokenMeta)
+ : new BigNumber('0')
+ const unsignedTx = await getTransactionData(utxos, address, amount, false, defaultAsset, selectedTokenMeta)
+ _fee = await unsignedTx.fee()
+ balanceAfter = tokenBalance.getDefault().minus(parsedAmount).minus(minAda).minus(_fee.getDefault())
+ }
+ // now we can update fee as well
+ fee = _fee != null ? _fee.getDefault() : null
+ } catch (err) {
+ if (err instanceof InsufficientFunds) {
+ balanceErrors = {insufficientBalance: true}
+ } else if (err instanceof AssetOverflowError) {
+ balanceErrors = {assetOverflow: true}
+ }
+ }
+ }
+ return {
+ amount: recomputedAmount,
+ amountErrors,
+ addressErrors,
+ balanceErrors,
+ fee,
+ balanceAfter,
+ }
+}
-
-
-
-
- {this.state.recomputing && }
-
-
-
-
- {this.renderSendAllWarning()}
-
- )
+const getAmountErrorText = (intl, amountErrors, balanceErrors, defaultAsset) => {
+ if (amountErrors.invalidAmount != null) {
+ const msgOptions = {}
+ if (amountErrors.invalidAmount === InvalidAssetAmount.ERROR_CODES.LT_MIN_UTXO) {
+ const networkConfig = getCardanoNetworkConfigById(defaultAsset.networkId)
+ const amount = new BigNumber(networkConfig.MINIMUM_UTXO_VAL)
+ // remove decimal part if it's equal to 0
+ const decimalPart = amount.modulo(Math.pow(10, defaultAsset.metadata.numberOfDecimals))
+ const minUtxo = decimalPart.eq('0')
+ ? formatTokenInteger(amount, defaultAsset)
+ : formatTokenAmount(amount, defaultAsset)
+ const ticker = defaultAsset.metadata.ticker
+ Object.assign(msgOptions, {minUtxo, ticker})
+ }
+ return intl.formatMessage(amountInputErrorMessages[amountErrors.invalidAmount], msgOptions)
+ }
+ if (balanceErrors.insufficientBalance === true) {
+ return intl.formatMessage(amountInputErrorMessages.insufficientBalance)
+ }
+ if (balanceErrors.assetOverflow === true) {
+ return intl.formatMessage(amountInputErrorMessages.assetOverflow)
}
+ return null
}
-type ExternalProps = {|
- navigation: Navigation,
- route: any,
- intl: IntlShape,
-|}
+const amountInputErrorMessages = defineMessages({
+ INVALID_AMOUNT: {
+ id: 'components.send.sendscreen.amountInput.error.INVALID_AMOUNT',
+ defaultMessage: '!!!Please enter valid amount',
+ },
+ TOO_MANY_DECIMAL_PLACES: {
+ id: 'components.send.sendscreen.amountInput.error.TOO_MANY_DECIMAL_PLACES',
+ defaultMessage: '!!!Please enter valid amount',
+ },
+ TOO_LARGE: {
+ id: 'components.send.sendscreen.amountInput.error.TOO_LARGE',
+ defaultMessage: '!!!Amount too large',
+ },
+ TOO_LOW: {
+ id: 'components.send.sendscreen.amountInput.error.TOO_LOW',
+ defaultMessage: '!!!Amount is too low',
+ },
+ LT_MIN_UTXO: {
+ id: 'components.send.sendscreen.amountInput.error.LT_MIN_UTXO',
+ defaultMessage: '!!!Cannot send less than {minUtxo} {ticker}',
+ },
+ NEGATIVE: {
+ id: 'components.send.sendscreen.amountInput.error.NEGATIVE',
+ defaultMessage: '!!!Amount must be positive',
+ },
+ insufficientBalance: {
+ id: 'components.send.sendscreen.amountInput.error.insufficientBalance',
+ defaultMessage: '!!!Not enough money to make this transaction',
+ },
+ assetOverflow: {
+ id: 'components.send.sendscreen.amountInput.error.assetOverflow',
+ defaultMessage: '!!!!Maximum value of a token inside a UTXO exceeded (overflow).',
+ },
+})
-export default injectIntl(
- (compose(
- connect(
- (state) => ({
- tokenBalance: tokenBalanceSelector(state),
- isFetchingBalance: isFetchingUtxosSelector(state),
- lastFetchingError: lastUtxosFetchErrorSelector(state),
- tokenMetadata: tokenInfoSelector(state),
- defaultAsset: defaultNetworkAssetSelector(state),
- utxos: utxosSelector(state),
- hasPendingOutgoingTransaction: hasPendingOutgoingTransactionSelector(state),
- isOnline: isOnlineSelector(state),
- serverStatus: serverStatusSelector(state),
- }),
- {
- fetchUTXOs,
- },
- ),
- )(SendScreen): ComponentType),
-)
+const messages = defineMessages({
+ feeLabel: {
+ id: 'components.send.sendscreen.feeLabel',
+ defaultMessage: '!!!Fee',
+ },
+ feeNotAvailable: {
+ id: 'components.send.sendscreen.feeNotAvailable',
+ defaultMessage: '!!!-',
+ },
+ balanceAfterLabel: {
+ id: 'components.send.sendscreen.balanceAfterLabel',
+ defaultMessage: '!!!Balance after',
+ },
+ balanceAfterNotAvailable: {
+ id: 'components.send.sendscreen.balanceAfterNotAvailable',
+ defaultMessage: '!!!-',
+ },
+ availableFundsBannerIsFetching: {
+ id: 'components.send.sendscreen.availableFundsBannerIsFetching',
+ defaultMessage: '!!!Checking balance...',
+ },
+ availableFundsBannerNotAvailable: {
+ id: 'components.send.sendscreen.availableFundsBannerNotAvailable',
+ defaultMessage: '!!!-',
+ },
+ addressInputErrorInvalidAddress: {
+ id: 'components.send.sendscreen.addressInputErrorInvalidAddress',
+ defaultMessage: '!!!Please enter valid address',
+ },
+ addressInputLabel: {
+ id: 'components.send.sendscreen.addressInputLabel',
+ defaultMessage: '!!!Address',
+ },
+ checkboxSendAllAssets: {
+ id: 'components.send.sendscreen.checkboxSendAllAssets',
+ defaultMessage: '!!!Send all assets (including all tokens)',
+ },
+ checkboxSendAll: {
+ id: 'components.send.sendscreen.checkboxSendAll',
+ defaultMessage: '!!!Send all {assetId}',
+ },
+ sendAllWarningTitle: {
+ id: 'components.send.sendscreen.sendAllWarningTitle',
+ defaultMessage: '!!!Do you really want to send all?',
+ },
+ sendAllWarningText: {
+ id: 'components.send.sendscreen.sendAllWarningText',
+ defaultMessage:
+ '!!!You have selected the send all option. Please confirm that you understand how this feature works.',
+ },
+ sendAllWarningAlert1: {
+ id: 'components.send.sendscreen.sendAllWarningAlert1',
+ defaultMessage: '!!!All you {assetNameOrId} balance will be transferred in this transaction.',
+ },
+ sendAllWarningAlert2: {
+ id: 'components.send.sendscreen.sendAllWarningAlert2',
+ defaultMessage:
+ '!!!All your tokens, including NFTs and any other native ' +
+ 'assets in your wallet, will also be transferred in this transaction.',
+ },
+ sendAllWarningAlert3: {
+ id: 'components.send.sendscreen.sendAllWarningAlert3',
+ defaultMessage: '!!!After you confirm the transaction in the next screen, your wallet will be emptied.',
+ },
+ continueButton: {
+ id: 'components.send.sendscreen.continueButton',
+ defaultMessage: '!!!Continue',
+ },
+ errorBannerNetworkError: {
+ id: 'components.send.sendscreen.errorBannerNetworkError',
+ defaultMessage: '!!!We are experiencing issues with fetching your current balance. Click to retry.',
+ },
+ errorBannerPendingOutgoingTransaction: {
+ id: 'components.send.sendscreen.errorBannerPendingOutgoingTransaction',
+ defaultMessage: '!!!You cannot send a new transaction while an existing one is still pending',
+ },
+})
diff --git a/src/components/Send/SendScreen.stories.js b/src/components/Send/SendScreen.stories.js
new file mode 100644
index 0000000000..78fb31f02f
--- /dev/null
+++ b/src/components/Send/SendScreen.stories.js
@@ -0,0 +1,29 @@
+// @flow
+
+import {action} from '@storybook/addon-actions'
+import {storiesOf} from '@storybook/react-native'
+import {BigNumber} from 'bignumber.js'
+import React from 'react'
+
+import {type TokenEntry} from '../../crypto/MultiToken'
+import SendScreen from './SendScreen'
+
+storiesOf('SendScreen', module)
+ .add('Default', () => {
+ const selectedAsset: TokenEntry = {
+ networkId: 300,
+ identifier: '',
+ amount: new BigNumber(12344.00234523),
+ }
+
+ return
+ })
+ .add('sendAll', () => {
+ const selectedAsset: TokenEntry = {
+ networkId: 300,
+ identifier: '',
+ amount: new BigNumber(12344.00234523),
+ }
+
+ return
+ })
diff --git a/src/components/Send/SendScreenNavigator.js b/src/components/Send/SendScreenNavigator.js
index 0143837bd2..1deac438e3 100644
--- a/src/components/Send/SendScreenNavigator.js
+++ b/src/components/Send/SendScreenNavigator.js
@@ -3,13 +3,17 @@
import {createStackNavigator} from '@react-navigation/stack'
import React from 'react'
import {defineMessages, useIntl} from 'react-intl'
+import {useSelector} from 'react-redux'
import iconQR from '../../assets/img/qr_code.png'
+import type {TokenEntry} from '../../crypto/MultiToken'
import {defaultNavigationOptions, defaultStackNavigatorOptions} from '../../navigationOptions'
import {SEND_ROUTES} from '../../RoutesList'
+import {tokenBalanceSelector, tokenInfoSelector} from '../../selectors'
import {Button} from '../UiKit'
import AddressReaderQR from './AddressReaderQR'
import {pastedFormatter} from './amountUtils'
+import AssetSelectorScreen from './AssetSelectorScreen/AssetSelectorScreen'
import BiometricAuthScreen from './BiometricAuthScreen'
import ConfirmScreen from './ConfirmScreen'
import SendScreen from './SendScreen'
@@ -24,6 +28,10 @@ const messages = defineMessages({
id: 'components.send.addressreaderqr.title',
defaultMessage: '!!!Scan QR code address',
},
+ selectAssetTitle: {
+ id: 'components.send.selectasset.title',
+ defaultMessage: '!!!Select asset',
+ },
confirmTitle: {
id: 'components.send.confirmscreen.title',
defaultMessage: '!!!Send',
@@ -52,6 +60,7 @@ const setAmount = (amount, route) => {
type SendScreenNavigatorRoutes = {
'send-ada': any,
+ 'select-asset': any,
'address-reader-qr': any,
'send-ada-confirm': any,
'biometrics-signing': any,
@@ -62,6 +71,11 @@ const Stack = createStackNavigator()
const SendScreenNavigator = () => {
const intl = useIntl()
+ const tokenBalance = useSelector(tokenBalanceSelector)
+ const [selectedAsset, setSelectedAsset] = React.useState(tokenBalance.getDefaultEntry())
+ const tokenInfos = useSelector(tokenInfoSelector)
+ const [sendAll, setSendAll] = React.useState(false)
+
return (
{
>
({
title: intl.formatMessage(messages.sendTitle),
headerRight: () => (
@@ -117,17 +130,41 @@ const SendScreenNavigator = () => {
),
...defaultNavigationOptions,
})}
- />
+ >
+ {() => }
+
+
+
+ {({navigation}) => (
+ {
+ setSendAll(false)
+ setSelectedAsset(token)
+ navigation.navigate('send-ada')
+ }}
+ onSelectAll={() => {
+ setSendAll(true)
+ setSelectedAsset(tokenBalance.getDefaultEntry())
+ navigation.navigate('send-ada')
+ }}
+ />
+ )}
+
+
+
+
= flatten(
}),
)
-// Same as serialization-lib
-export const MAX_OUTPUT_SIZE = 4000
-export const MAX_TX_SIZE = 8000
+export const MAX_VALUE_BYTES = 5000
+export const MAX_TX_BYTES = 16 * 1024
diff --git a/src/crypto/shelley/transactions.js b/src/crypto/shelley/transactions.js
index 3f86d64308..223f98745e 100644
--- a/src/crypto/shelley/transactions.js
+++ b/src/crypto/shelley/transactions.js
@@ -37,7 +37,7 @@ import {BigNumber} from 'bignumber.js'
import type {RawUtxo} from '../../api/types'
/* eslint-enable camelcase */
import {CONFIG} from '../../config/config'
-import {MAX_OUTPUT_SIZE, MAX_TX_SIZE} from '../../config/networks'
+import {MAX_TX_BYTES, MAX_VALUE_BYTES} from '../../config/networks'
import {AssetOverflowError, InsufficientFunds, NoOutputsError} from '../errors'
import type {
Address,
@@ -94,9 +94,9 @@ export const sendAllUnsignedTxFromUtxo = async (
protocolParams.poolDeposit,
protocolParams.keyDeposit,
// $FlowFixMe sketchy-null-number
- protocolParams.maxOutputSize || MAX_OUTPUT_SIZE,
+ protocolParams.maxValueBytes || MAX_VALUE_BYTES,
// $FlowFixMe sketchy-null-number
- protocolParams.maxTxSize || MAX_TX_SIZE,
+ protocolParams.maxTxBytes || MAX_TX_BYTES,
)
await txBuilder.set_ttl(absSlotNumber.plus(defaultTtlOffset).toNumber())
for (const input of allUtxos) {
@@ -349,9 +349,9 @@ export const newAdaUnsignedTxFromUtxo = async (
protocolParams.poolDeposit,
protocolParams.keyDeposit,
// $FlowFixMe sketchy-null-number
- protocolParams.maxOutputSize || MAX_OUTPUT_SIZE,
+ protocolParams.maxValueBytes || MAX_VALUE_BYTES,
// $FlowFixMe sketchy-null-number
- protocolParams.maxTxSize || MAX_TX_SIZE,
+ protocolParams.maxTxBytes || MAX_TX_BYTES,
)
if (certificates.length > 0) {
const certsNative = await Certificates.new()
diff --git a/src/crypto/types.js b/src/crypto/types.js
index 515358938e..db3388ab87 100644
--- a/src/crypto/types.js
+++ b/src/crypto/types.js
@@ -166,6 +166,6 @@ export type ProtocolParameters = {|
+poolDeposit: BigNumber,
+keyDeposit: BigNumber,
+networkId: number,
- +maxOutputSize?: number,
- +maxTxSize?: number,
+ +maxValueBytes?: number,
+ +maxTxBytes?: number,
|}
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 1e4ed9a113..34114c71c2 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -96,6 +96,10 @@
"components.receive.receivescreen.unusedAddresses": "Unused addresses",
"components.receive.receivescreen.usedAddresses": "Used addresses",
"components.receive.receivescreen.verifyAddress": "Verify address",
+ "components.send.selectasset.title": "Select Asset",
+ "components.send.assetselectorscreen.searchlabel": "Search by name or subject",
+ "components.send.assetselectorscreen.sendallassets": "SELECT ALL ASSETS",
+ "components.send.assetselectorscreen.unknownAsset": "Unknown Asset",
"components.send.addressreaderqr.title": "Scan QR code address",
"components.send.amountfield.label": "Amount",
"components.send.biometricauthscreen.DECRYPTION_FAILED": "Biometrics login failed. Please use an alternate login method.",
diff --git a/src/state.js b/src/state.js
index 11fc07e67e..c2ff72e0e9 100644
--- a/src/state.js
+++ b/src/state.js
@@ -10,7 +10,6 @@ import {NETWORK_REGISTRY} from './config/types'
import {ISignRequest} from './crypto/ISignRequest'
import type {HWDeviceInfo} from './crypto/shelley/ledgerUtils'
import type {Token, Transaction} from './types/HistoryTransaction'
-
export type ServerStatusCache = {|
+isServerOk: boolean,
+isMaintenance: boolean,
@@ -229,7 +228,141 @@ export const mockState = (): State => {
TextPart: 'ZNXA-1056',
},
},
+ tokenInfo: {
+ isFetching: false,
+ lastFetchingError: null,
+ tokens: assetTokenInfos,
+ },
}
}
export default getInitialState
+
+const assetTokenInfos: Dict = {
+ ['']: {
+ networkId: 300,
+ isDefault: false,
+ identifier: '',
+ metadata: {
+ assetName: 'assetName',
+ longName: 'longName',
+ maxSupply: 'maxSupply',
+ numberOfDecimals: 10,
+ policyId: 'policyId',
+ ticker: 'ticker',
+ type: 'Cardano',
+ },
+ },
+ policyId123assetName123: {
+ networkId: 123,
+ isDefault: false,
+ identifier: 'policyId123assetName123',
+ metadata: {
+ assetName: 'assetName123',
+ longName: 'longName123',
+ maxSupply: 'maxSupply123',
+ numberOfDecimals: 10,
+ policyId: 'policyId1233',
+ ticker: 'ticker123',
+ type: 'Cardano',
+ },
+ },
+ policyId456assetName456: {
+ networkId: 456,
+ isDefault: true,
+ identifier: 'policyId456assetName456',
+ metadata: {
+ assetName: 'assetName456',
+ longName: 'longName456',
+ maxSupply: 'maxSupply456',
+ numberOfDecimals: 10,
+ policyId: 'policyId4566',
+ ticker: 'ticker456',
+ type: 'Cardano',
+ },
+ },
+ policyId789assetName789: {
+ networkId: 789,
+ isDefault: false,
+ identifier: 'policyId789assetName789',
+ metadata: {
+ assetName: 'assetName789',
+ longName: 'longName789',
+ maxSupply: 'maxSupply789',
+ numberOfDecimals: 10,
+ policyId: 'policyId7899',
+ ticker: 'ticker789',
+ type: 'Cardano',
+ },
+ },
+ policyId111assetName111: {
+ networkId: 111,
+ isDefault: false,
+ identifier: 'policyId111assetName111',
+ metadata: {
+ assetName: 'assetName111',
+ longName: 'longName111',
+ maxSupply: 'maxSupply111',
+ numberOfDecimals: 10,
+ policyId: 'policyId1119',
+ ticker: 'ticker111',
+ type: 'Cardano',
+ },
+ },
+ policyId222assetName222: {
+ networkId: 222,
+ isDefault: false,
+ identifier: 'policyId222assetName222',
+ metadata: {
+ assetName: 'assetName222',
+ longName: 'longName222',
+ maxSupply: 'maxSupply222',
+ numberOfDecimals: 10,
+ policyId: 'policyId2229',
+ ticker: 'ticker222',
+ type: 'Cardano',
+ },
+ },
+ policyId333assetName333: {
+ networkId: 333,
+ isDefault: false,
+ identifier: 'policyId333assetName333',
+ metadata: {
+ assetName: 'assetName333',
+ longName: 'longName333',
+ maxSupply: 'maxSupply333',
+ numberOfDecimals: 10,
+ policyId: 'policyId3339',
+ ticker: 'ticker333',
+ type: 'Cardano',
+ },
+ },
+ policyId444assetName444: {
+ networkId: 444,
+ isDefault: false,
+ identifier: 'policyId444assetName444',
+ metadata: {
+ assetName: 'assetName444',
+ longName: 'longName444',
+ maxSupply: 'maxSupply444',
+ numberOfDecimals: 10,
+ policyId: 'policyId4449',
+ ticker: 'ticker444',
+ type: 'Cardano',
+ },
+ },
+ policyId555assetName555: {
+ networkId: 555,
+ isDefault: false,
+ identifier: 'policyId555assetName555',
+ metadata: {
+ assetName: 'assetName555',
+ longName: 'longName555',
+ maxSupply: 'maxSupply555',
+ numberOfDecimals: 10,
+ policyId: 'policyId5559',
+ ticker: 'ticker555',
+ type: 'Cardano',
+ },
+ },
+}
diff --git a/src/styles/config.js b/src/styles/config.js
index 334200554d..81a9be7c25 100644
--- a/src/styles/config.js
+++ b/src/styles/config.js
@@ -47,6 +47,8 @@ export const COLORS = {
TEXT_INPUT: '#6B7384',
MODAL_HEADING: '#6B7384',
WORD_BADGE_TEXT: '#6B7384',
+ BLUE_LIGHTER: '#164FD6',
+ GREY_6: '#A7AFC0',
}
// TODO: use lowercase for key names. Eg navigationActive
diff --git a/src/utils/format.js b/src/utils/format.js
index 46345392dc..077310db32 100644
--- a/src/utils/format.js
+++ b/src/utils/format.js
@@ -24,7 +24,7 @@ const messages = defineMessages({
},
})
-const getTokenFingerprint = (token: Token | DefaultAsset) => {
+export const getTokenFingerprint = (token: Token | DefaultAsset) => {
const {policyId, assetName} = token.metadata
const assetFingerprint = new AssetFingerprint(Buffer.from(policyId, 'hex'), Buffer.from(assetName, 'hex'))
return assetFingerprint.fingerprint()
@@ -38,38 +38,34 @@ export const ASSET_DENOMINATION = {
}
export type AssetDenomination = $Values
+export const decodeHexAscii = (text: string) => {
+ const bytes = [...Buffer.from(text, 'hex')]
+ const isAscii = bytes.every((byte) => byte > 32 && byte < 127)
+
+ return isAscii ? String.fromCharCode(...bytes) : undefined
+}
+
+export const getTicker = (token: Token | DefaultAsset) => token.metadata.ticker
+export const getSymbol = (token: Token | DefaultAsset) =>
+ token.metadata.ticker
+ ? utfSymbols.CURRENCIES[token.metadata.ticker]
+ ? utfSymbols.CURRENCIES[token.metadata.ticker]
+ : token.metadata.ticker
+ : null
+export const getName = (token: Token | DefaultAsset) =>
+ token.metadata.longName || decodeHexAscii(token.metadata.assetName) || getTokenFingerprint(token) || undefined
+
// NOTE: There is a bug when starting fresh, the metadata is empty
export const getAssetDenomination = (token: Token | DefaultAsset, denomination: AssetDenomination): ?string => {
switch (denomination) {
case ASSET_DENOMINATION.TICKER:
- // $FlowFixMe
- return token?.metadata?.ticker
+ return getTicker(token)
case ASSET_DENOMINATION.SYMBOL:
- // if we don't have a symbol for this asset, default to ticker, though
- // ticker can still be null
- // $FlowFixMe
- return token?.metadata?.ticker
- ? utfSymbols.CURRENCIES[token.metadata.ticker]
- ? utfSymbols.CURRENCIES[token.metadata.ticker]
- : token.metadata.ticker
- : null
- case ASSET_DENOMINATION.NAME: {
- // $FlowFixMe
- if (token?.metadata?.longName !== null) {
- return token.metadata.longName
- }
- // $FlowFixMe
- if (token?.metadata?.assetName?.length > 0) {
- const bytes = [...Buffer.from(token.metadata.assetName, 'hex')]
- if (bytes.filter((byte) => byte <= 32 || byte >= 127).length === 0) {
- return String.fromCharCode(...bytes)
- }
- }
- return null
- }
- case ASSET_DENOMINATION.FINGERPRINT: {
+ return getSymbol(token)
+ case ASSET_DENOMINATION.NAME:
+ return getName(token)
+ case ASSET_DENOMINATION.FINGERPRINT:
return getTokenFingerprint(token)
- }
default:
return null
}
diff --git a/storybook/storyLoader.js b/storybook/storyLoader.js
index df718e73e6..b6a2596c74 100644
--- a/storybook/storyLoader.js
+++ b/storybook/storyLoader.js
@@ -11,6 +11,7 @@ function loadStories() {
require('../src/components/Common/ErrorModal.stories')
require('../src/components/Common/ExpandableItem.stories')
require('../src/components/Common/FingerprinScreenBase.stories')
+ require('../src/components/Common/MultiAsset/AssetList.stories')
require('../src/components/Common/PinRegistrationForm.stories')
require('../src/components/Delegation/DelegationConfirmation.stories')
require('../src/components/Delegation/FlawedWalletScreen.stories')
@@ -26,7 +27,9 @@ function loadStories() {
require('../src/components/MaintenanceScreen.stories')
require('../src/components/Receive/ReceiveScreen.stories')
require('../src/components/Send/AddressReaderQR.stories')
+ require('../src/components/Send/AssetSelectorScreen/AssetSelectorScreen.stories')
require('../src/components/Send/BiometricAuthScreen.stories')
+ require('../src/components/Send/SendScreen.stories')
require('../src/components/Settings/ApplicationSettingsScreen.stories')
require('../src/components/Settings/BiometricsLinkScreen.stories')
require('../src/components/Settings/ChangePasswordScreen.stories')
@@ -68,6 +71,7 @@ const stories = [
'../src/components/Common/ErrorModal.stories',
'../src/components/Common/ExpandableItem.stories',
'../src/components/Common/FingerprinScreenBase.stories',
+ '../src/components/Common/MultiAsset/AssetList.stories',
'../src/components/Common/PinRegistrationForm.stories',
'../src/components/Delegation/DelegationConfirmation.stories',
'../src/components/Delegation/FlawedWalletScreen.stories',
@@ -83,7 +87,9 @@ const stories = [
'../src/components/MaintenanceScreen.stories',
'../src/components/Receive/ReceiveScreen.stories',
'../src/components/Send/AddressReaderQR.stories',
+ '../src/components/Send/AssetSelectorScreen/AssetSelectorScreen.stories',
'../src/components/Send/BiometricAuthScreen.stories',
+ '../src/components/Send/SendScreen.stories',
'../src/components/Settings/ApplicationSettingsScreen.stories',
'../src/components/Settings/BiometricsLinkScreen.stories',
'../src/components/Settings/ChangePasswordScreen.stories',