Skip to content

Commit

Permalink
Merge pull request #1804 from Emurgo/pending-tx
Browse files Browse the repository at this point in the history
TxHistoryListItem migrating to tx
  • Loading branch information
stackchain committed Dec 5, 2021
2 parents d501ac0 + 92a3fbf commit a617eaf
Show file tree
Hide file tree
Showing 10 changed files with 659 additions and 85 deletions.
8 changes: 5 additions & 3 deletions legacy/selectors.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import BigNumber from 'bignumber.js'

import {Token, TransactionInfo} from '../src/TxHistory/types'
import {DefaultAsset, Token, TransactionInfo} from '../src/types/cardano'
import type {State, WalletMeta} from './state'

export var availableAssetsSelector: (state: State) => Record<string, Token>
export var availableAssetsSelector: (state: State) => Record<string, Token | DefaultAsset>
export var customPinHashSelector: (state: State) => string | undefined
export var hasAnyTransaction: (state: State) => boolean
export var installationIdSelector: (state: State) => boolean
Expand All @@ -27,10 +27,12 @@ export var easyConfirmationSelector: (state: State) => boolean
export var isHWSelector: (state: State) => boolean
export var walletNameSelector: (state: State) => string
export var walletNamesSelector: (state: State) => Array<string>
export var defaultNetworkAssetSelector: (state: State) => DefaultAsset
export var externalAddressIndexSelector: (state: State) => Record<string, number>
export var internalAddressIndexSelector: (state: State) => Record<string, number>
export var canGenerateNewReceiveAddressSelector: (state: State) => boolean
export var isUsedAddressIndexSelector: (state: State) => Record<string, boolean>
export var receiveAddressesSelector: (state: State) => Array<string>
export var externalAddressIndexSelector: (state: State) => Record<string, number>
export var hwDeviceInfoSelector: (state: State) => {bip44AccountPublic: string; hwFeatures: HWFeatures} | null

// prettier-ignore
Expand Down
2 changes: 1 addition & 1 deletion src/TxHistory/AssetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {formatTokenAmount, getAssetDenominationOrId} from '../../legacy/utils/fo
import AdaImage from '../assets/img/icon/asset_ada.png'
import NoImage from '../assets/img/icon/asset_no_image.png'
import {Spacer} from '../components/Spacer'
import {Token, TokenEntry} from '../types/cardano'
import {TxListActionsBannerForAssetsTab} from './TxListActionsBanner'
import {Token, TokenEntry} from './types'

type AssetListProps = {
refreshing: boolean
Expand Down
8 changes: 3 additions & 5 deletions src/TxHistory/TxHistoryList.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {useNavigation} from '@react-navigation/native'
import _ from 'lodash'
import React from 'react'
import {useIntl} from 'react-intl'
import {Alert, SectionList, StyleSheet, View} from 'react-native'
import {useSelector} from 'react-redux'

import TxHistoryListItem from '../../legacy/components/TxHistory/TxHistoryListItem'
import {Text} from '../../legacy/components/UiKit'
import {actionMessages} from '../../legacy/i18n/global-messages'
import {transactionsInfoSelector} from '../../legacy/selectors'
import {formatDateRelative} from '../../legacy/utils/format'
import features from '../features'
import {TransactionInfo} from '../types/cardano'
import {TxHistoryListItem} from './TxHistoryListItem'
import {TxListActionsBannerForTransactionsTab} from './TxListActionsBanner'
import {TransactionInfo} from './types'

type Props = {
refreshing: boolean
Expand All @@ -21,7 +20,6 @@ type Props = {

export const TxHistoryList = ({refreshing, onRefresh}: Props) => {
const strings = useStrings()
const navigation = useNavigation()
const transactionsInfo = useSelector(transactionsInfoSelector)
const groupedTransactions = getTransactionsByDate(transactionsInfo)

Expand All @@ -36,7 +34,7 @@ export const TxHistoryList = ({refreshing, onRefresh}: Props) => {
<SectionList
onRefresh={onRefresh}
refreshing={refreshing}
renderItem={({item}) => <TxHistoryListItem navigation={navigation} id={item.id} />}
renderItem={({item}) => <TxHistoryListItem transaction={item} />}
renderSectionHeader={({section: {data}}) => <DayHeader ts={data[0].submittedAt} />}
sections={groupedTransactions}
keyExtractor={(item) => item.id}
Expand Down
279 changes: 279 additions & 0 deletions src/TxHistory/TxHistoryListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
import {useNavigation} from '@react-navigation/native'
import {BigNumber} from 'bignumber.js'
import _ from 'lodash'
import React from 'react'
import {defineMessages, MessageDescriptor, useIntl} from 'react-intl'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {useSelector} from 'react-redux'

import {Text} from '../../legacy/components/UiKit'
import {MultiToken} from '../../legacy/crypto/MultiToken'
import {TX_HISTORY_ROUTES} from '../../legacy/RoutesList'
import {
availableAssetsSelector,
defaultNetworkAssetSelector,
externalAddressIndexSelector,
internalAddressIndexSelector,
} from '../../legacy/selectors'
import {COLORS} from '../../legacy/styles/config'
import {
ASSET_DENOMINATION,
formatTimeToSeconds,
formatTokenFractional,
formatTokenInteger,
getAssetDenominationOrId,
} from '../../legacy/utils/format'
import utfSymbols from '../../legacy/utils/utfSymbols'
import {Icon} from '../components/Icon'
import {DefaultAsset, IOData, TransactionAssurance, TransactionDirection, TransactionInfo} from '../types/cardano'

const filtersTxIO = (address: string) => {
const isMyReceive = (extAddrIdx) => extAddrIdx[address] != null
const isMyChange = (intAddrIdx) => intAddrIdx[address] != null
const isMyAddress = (extAddrIdx, intAddrIdx) => isMyReceive(extAddrIdx) || isMyChange(intAddrIdx)
return {
isMyReceive,
isMyChange,
isMyAddress,
}
}

const getTxIOMyWallet = (txIO: Array<IOData>, extAddrIdx, intAddrIdx) => {
const io = _.uniq(txIO).map(({address, assets}) => ({
address,
assets,
}))
const filtered = io.filter(({address}) => filtersTxIO(address).isMyAddress(extAddrIdx, intAddrIdx))
return filtered || []
}

type Props = {
transaction: TransactionInfo
}

export const TxHistoryListItem = ({transaction}: Props) => {
const strings = useStrings()
const navigation = useNavigation()

const showDetails = () => navigation.navigate(TX_HISTORY_ROUTES.TX_DETAIL, {id: transaction.id})
const submittedAt = formatTimeToSeconds(transaction.submittedAt)

const isPending = transaction.assurance === 'PENDING'
const isReceived = transaction.direction === 'RECEIVED'

const rootBgColor = bgColorByAssurance(transaction.assurance)

const availableAssets = useSelector(availableAssetsSelector)
const internalAddressIndex = useSelector(internalAddressIndexSelector)
const externalAddressIndex = useSelector(externalAddressIndexSelector)
const defaultNetworkAsset = useSelector(defaultNetworkAssetSelector)

const fee = transaction.fee ? transaction.fee[0] : null
const amountAsMT = MultiToken.fromArray(transaction.amount)
const amount: BigNumber = amountAsMT.getDefault()
const amountDefaultAsset = availableAssets[amountAsMT.getDefaultId()] as DefaultAsset

const defaultAsset = amountDefaultAsset || defaultNetworkAsset

// if we don't have a symbol for this asset, default to ticker first and
// then to identifier
const assetSymbol = getAssetDenominationOrId(defaultAsset, ASSET_DENOMINATION.SYMBOL)

const amountToDisplay = amount.plus(new BigNumber(fee?.amount || 0))
const amountStyle = amountToDisplay
? amountToDisplay.gte(0)
? styles.positiveAmount
: styles.negativeAmount
: styles.neutralAmount

const outputsToMyWallet =
(isReceived && getTxIOMyWallet(transaction.outputs, externalAddressIndex, internalAddressIndex)) || []

const totalAssets = outputsToMyWallet.reduce((acc, {assets}) => acc + Number(assets.length), 0) || 0

return (
<TouchableOpacity onPress={showDetails} activeOpacity={0.5}>
<View style={[styles.root, {backgroundColor: rootBgColor}]}>
<View style={styles.iconRoot}>
<Icon.Direction transaction={transaction} />
</View>
<View style={styles.transactionRoot}>
<View style={styles.row}>
<Text small secondary={isPending}>
{strings.direction(transaction.direction)}
</Text>
{transaction.amount ? (
<View style={styles.amount}>
<Text style={amountStyle} secondary={isPending}>
{formatTokenInteger(amountToDisplay, defaultAsset)}
</Text>
<Text small style={amountStyle} secondary={isPending}>
{formatTokenFractional(amountToDisplay, defaultAsset)}
</Text>
<Text style={amountStyle}>{`${utfSymbols.NBSP}${assetSymbol}`}</Text>
</View>
) : (
<Text style={amountStyle}>- -</Text>
)}
</View>
{totalAssets !== 0 && (
<View style={styles.row}>
<Text secondary small>
{submittedAt}
</Text>
<Text>{strings.assets(totalAssets)}</Text>
</View>
)}
<View style={styles.last}>
<Text secondary small>
{!totalAssets && submittedAt}
</Text>
<Text secondary small style={styles.assuranceText}>
{strings.assurance(transaction.assurance)}
</Text>
</View>
</View>
</View>
</TouchableOpacity>
)
}

const styles = StyleSheet.create({
root: {
flex: 1,
flexDirection: 'row',
borderRadius: 10,
elevation: 2,
shadowOffset: {width: 0, height: -2},
shadowRadius: 10,
shadowOpacity: 0.08,
shadowColor: '#181a1e',
backgroundColor: '#fff',
marginBottom: 16,
paddingHorizontal: 12,
paddingVertical: 12,
justifyContent: 'center',
marginHorizontal: 16,
},
last: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 3,
},
amount: {
flexDirection: 'row',
alignItems: 'flex-end',
},
positiveAmount: {
color: COLORS.POSITIVE_AMOUNT,
},
negativeAmount: {
color: COLORS.BLACK,
},
neutralAmount: {
color: COLORS.BLACK,
},
assuranceText: {
fontSize: 12,
},
iconRoot: {
paddingRight: 8,
},
transactionRoot: {
flex: 14,
},
})

const messages = defineMessages({
fee: {
id: 'components.txhistory.txhistorylistitem.fee',
defaultMessage: '!!!Fee',
},
transactionTypeSent: {
id: 'components.txhistory.txhistorylistitem.transactionTypeSent',
defaultMessage: '!!!ADA sent',
},
transactionTypeReceived: {
id: 'components.txhistory.txhistorylistitem.transactionTypeReceived',
defaultMessage: '!!!ADA received',
},
transactionTypeSelf: {
id: 'components.txhistory.txhistorylistitem.transactionTypeSelf',
defaultMessage: '!!!Intrawallet',
},
transactionTypeMulti: {
id: 'components.txhistory.txhistorylistitem.transactionTypeMulti',
defaultMessage: '!!!Multiparty',
},
assuranceLevelHeader: {
id: 'components.txhistory.txhistorylistitem.assuranceLevelHeader',
defaultMessage: '!!!Assurance level:',
},
assuranceLevelLow: {
id: 'components.txhistory.txhistorylistitem.assuranceLevelLow',
defaultMessage: '!!!Low',
},
assuranceLevelMedium: {
id: 'components.txhistory.txhistorylistitem.assuranceLevelMedium',
defaultMessage: '!!!Medium',
},
assuranceLevelHigh: {
id: 'components.txhistory.txhistorylistitem.assuranceLevelHigh',
defaultMessage: '!!!High',
},
assuranceLevelPending: {
id: 'components.txhistory.txhistorylistitem.assuranceLevelPending',
defaultMessage: '!!!Pending',
},
assuranceLevelFailed: {
id: 'components.txhistory.txhistorylistitem.assuranceLevelFailed',
defaultMessage: '!!!Failed',
},
assets: {
id: 'global.txLabels.assets',
defaultMessage: '!!!{cnt} assets',
description: 'The number of assets different assets, not the amount',
},
})

const assuranceMessages: Record<TransactionAssurance, MessageDescriptor> = Object.freeze({
LOW: messages.assuranceLevelLow,
MEDIUM: messages.assuranceLevelMedium,
HIGH: messages.assuranceLevelHigh,
PENDING: messages.assuranceLevelPending,
FAILED: messages.assuranceLevelFailed,
})

const directionMessages: Record<TransactionDirection, MessageDescriptor> = Object.freeze({
SENT: messages.transactionTypeSent,
RECEIVED: messages.transactionTypeReceived,
SELF: messages.transactionTypeSelf,
MULTI: messages.transactionTypeMulti,
})

const useStrings = () => {
const intl = useIntl()

return {
assurance: (level: TransactionAssurance) => intl.formatMessage(assuranceMessages[level]),
direction: (direction: TransactionDirection) => intl.formatMessage(directionMessages[direction]),
assets: (qty: number) => intl.formatMessage(messages.assets, {cnt: qty}),
}
}

const bgColorByAssurance = (assurance: TransactionAssurance) => {
switch (assurance) {
case 'PENDING':
return 'rgba(207, 217, 224, 0.6)'
case 'FAILED':
return '#F8D7DA'
default:
return '#FFF'
}
}

0 comments on commit a617eaf

Please sign in to comment.