From c310a2299e8fe8cc7cd66f173d8b7f0f6799aaf2 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Wed, 11 Oct 2023 15:43:29 -0700 Subject: [PATCH] Parse EdgeTxAction for TransactionList UI --- CHANGELOG.md | 1 + src/actions/CategoriesActions.ts | 100 +++++++++++++++++- .../scenes/TransactionDetailsScene.tsx | 41 ++++--- src/components/themed/TransactionListRow.tsx | 6 +- src/components/themed/TransactionRow.tsx | 14 ++- src/constants/txActionConstants.ts | 14 +++ src/locales/en_US.ts | 22 ++++ src/locales/strings/enUS.json | 20 ++++ src/types/routerTypes.tsx | 1 + 9 files changed, 200 insertions(+), 19 deletions(-) create mode 100644 src/constants/txActionConstants.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a4ba5ce8e71..c966af80a81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - added: Paybis buy/sell in Brazil, Columbia, and Mexico +- added: EdgeTxAction information to Transaction List - added: "Paused Wallet" visual indicator on wallet list - added: Paybis sell to debit card - added: Signup captcha experiment. diff --git a/src/actions/CategoriesActions.ts b/src/actions/CategoriesActions.ts index df1669574ae..06ecbc37778 100644 --- a/src/actions/CategoriesActions.ts +++ b/src/actions/CategoriesActions.ts @@ -1,8 +1,10 @@ -import { EdgeAccount } from 'edge-core-js' +import { EdgeAccount, EdgeAssetAmount, EdgeCurrencyWallet, EdgeTransaction } from 'edge-core-js' +import { sprintf } from 'sprintf-js' import { showError } from '../components/services/AirshipInstance' import { lstrings } from '../locales/strings' import { ThunkAction } from '../types/reduxTypes' +import { getCurrencyCode } from '../util/CurrencyInfoHelpers' export type Category = 'transfer' | 'exchange' | 'expense' | 'income' @@ -255,3 +257,99 @@ export const defaultCategories = [ 'Transfer:Mycelium', 'Transfer:Dark Wallet' ] + +/** + * Given an EdgeTxAction, returns the display value for pre-filling the + * 'Category' and 'Notes' tiles, if they are not already user-modified. + */ +export const getTxActionDisplayInfo = ( + tx: EdgeTransaction, + wallet: EdgeCurrencyWallet, + tokenId?: string +): { splitCategory: SplitCategory; notes?: string } | undefined => { + const { action } = tx + if (action == null) return + const { type } = action + + const getCurrencyCodes = (assets: EdgeAssetAmount[]) => assets.map(asset => getCurrencyCode(wallet, asset.tokenId)) + + switch (type) { + case 'swap': + case 'swapOrderFill': { + // Determine if the swap destination was to a different asset or if the + // swap source was from a different asset. + const txSrcSameAsset = action.sourceAsset.tokenId === tokenId && action.sourceAsset.pluginId === wallet.currencyInfo.pluginId + const toFromStr = txSrcSameAsset ? lstrings.transaction_details_swap_to_subcat_1s : lstrings.transaction_details_swap_from_subcat_1s + const otherAsset = txSrcSameAsset ? action.destAsset : action.sourceAsset + + return { + splitCategory: { + category: 'exchange', + subcategory: sprintf(toFromStr, getCurrencyCode(wallet, otherAsset.tokenId)) + } + } + } + case 'swapOrderPost': + return { + splitCategory: { + category: 'expense', + subcategory: sprintf(lstrings.transaction_details_swap_order_post) + } + } + case 'swapOrderCancel': + return { + splitCategory: { + category: 'expense', + subcategory: sprintf(lstrings.transaction_details_swap_order_cancel) + } + } + case 'stake': { + let subcategory + if (action.stakeAssets.length === 1) subcategory = sprintf(lstrings.transaction_details_stake_subcat_1s, ...getCurrencyCodes(action.stakeAssets)) + else if (action.stakeAssets.length === 2) subcategory = sprintf(lstrings.transaction_details_stake_subcat_2s, ...getCurrencyCodes(action.stakeAssets)) + else { + console.warn(`Unsupported number of assets for '${type}' EdgeTxActionSwapType`) + return + } + return { splitCategory: { category: 'transfer', subcategory } } + } + case 'stakeOrder': { + let notes + if (action.stakeAssets.length === 1) notes = sprintf(lstrings.transaction_details_unstake_order_notes_1s, ...getCurrencyCodes(action.stakeAssets)) + else if (action.stakeAssets.length === 2) notes = sprintf(lstrings.transaction_details_unstake_order_notes_2s, ...getCurrencyCodes(action.stakeAssets)) + else { + console.error(`Unsupported number of assets for '${type}' EdgeTxActionSwapType`) + return + } + return { + splitCategory: { category: 'expense', subcategory: lstrings.transaction_details_stake_order_subcat }, + notes + } + } + case 'unstake': { + let subcategory + if (action.stakeAssets.length === 1) subcategory = sprintf(lstrings.transaction_details_unstake_subcat_1s, ...getCurrencyCodes(action.stakeAssets)) + else if (action.stakeAssets.length === 2) subcategory = sprintf(lstrings.transaction_details_unstake_subcat_2s, ...getCurrencyCodes(action.stakeAssets)) + else { + console.error(`Unsupported number of assets for '${type}' EdgeTxActionSwapType`) + return + } + return { splitCategory: { category: 'transfer', subcategory } } + } + case 'unstakeOrder': { + let notes + if (action.stakeAssets.length === 1) notes = sprintf(lstrings.transaction_details_unstake_order_notes_1s, ...getCurrencyCodes(action.stakeAssets)) + else if (action.stakeAssets.length === 2) notes = sprintf(lstrings.transaction_details_unstake_order_notes_2s, ...getCurrencyCodes(action.stakeAssets)) + else { + console.error(`Unsupported number of assets for '${type}' EdgeTxActionSwapType`) + return + } + return { + splitCategory: { category: 'expense', subcategory: lstrings.transaction_details_unstake_order }, + notes + } + } + default: + console.error(`Unsupported EdgeTxAction type: '${type}'`) + } +} diff --git a/src/components/scenes/TransactionDetailsScene.tsx b/src/components/scenes/TransactionDetailsScene.tsx index 63a59360f54..46baeec63a4 100644 --- a/src/components/scenes/TransactionDetailsScene.tsx +++ b/src/components/scenes/TransactionDetailsScene.tsx @@ -6,8 +6,9 @@ import FastImage from 'react-native-fast-image' import IonIcon from 'react-native-vector-icons/Ionicons' import { sprintf } from 'sprintf-js' -import { formatCategory, joinCategory, splitCategory } from '../../actions/CategoriesActions' +import { formatCategory, getTxActionDisplayInfo, joinCategory, splitCategory } from '../../actions/CategoriesActions' import { playSendSound } from '../../actions/SoundActions' +import { TX_ACTION_LABEL_MAP } from '../../constants/txActionConstants' import { useContactThumbnail } from '../../hooks/redux/useContactThumbnail' import { lstrings } from '../../locales/strings' import { EdgeSceneProps } from '../../types/routerTypes' @@ -52,27 +53,38 @@ interface State { class TransactionDetailsComponent extends React.Component { constructor(props: Props) { super(props) - const { edgeTransaction } = props.route.params - const { metadata = {} } = edgeTransaction - const { name = '', notes = '' } = metadata + const { wallet } = props + const { edgeTransaction, tokenId } = props.route.params + const { metadata } = edgeTransaction const isSentTransaction = edgeTransaction.nativeAmount.startsWith('-') || (eq(edgeTransaction.nativeAmount, '0') && edgeTransaction.isSend) const direction = isSentTransaction ? 'send' : 'receive' - const category = joinCategory( - splitCategory( - metadata.category, - // Pick the right default: - direction === 'receive' ? 'income' : 'expense' - ) - ) + + // Choose a default category based on metadata or the txAction + const txActionInfo = getTxActionDisplayInfo(edgeTransaction, wallet, tokenId) + const txActionSplitCat = txActionInfo?.splitCategory + const txActionNotes = txActionInfo?.notes + + const splitCat = + metadata?.category != null || txActionSplitCat == null + ? splitCategory( + metadata?.category, + // Pick the right default: + direction === 'receive' ? 'income' : 'expense' + ) + : txActionSplitCat + + const category = joinCategory(splitCat) + + const notes = metadata?.notes == null ? txActionNotes : metadata.notes this.state = { acceleratedTx: null, bizId: 0, category, - name, + name: metadata?.name ?? '', direction, - notes + notes: notes ?? '' } } @@ -189,11 +201,12 @@ class TransactionDetailsComponent extends React.Component { render() { const { navigation, route, theme, thumbnailPath, wallet } = this.props const { edgeTransaction } = route.params + const { action } = edgeTransaction const { direction, acceleratedTx, name, notes, category } = this.state const styles = getStyles(theme) const personLabel = direction === 'receive' ? lstrings.transaction_details_sender : lstrings.transaction_details_recipient - const personName = name !== '' ? name : personLabel + const personName = action != null ? TX_ACTION_LABEL_MAP[action.type] : name !== '' ? name : personLabel const personHeader = sprintf(lstrings.transaction_details_person_name, personLabel) // spendTargets recipient addresses format diff --git a/src/components/themed/TransactionListRow.tsx b/src/components/themed/TransactionListRow.tsx index 1b27ff09541..94e002cc2b2 100644 --- a/src/components/themed/TransactionListRow.tsx +++ b/src/components/themed/TransactionListRow.tsx @@ -30,12 +30,13 @@ import { TransactionRow } from './TransactionRow' interface Props { navigation: NavigationBase wallet: EdgeCurrencyWallet + tokenId?: string currencyCode: string transaction: EdgeTransaction } export function TransactionListRow(props: Props) { - const { navigation, currencyCode, wallet, transaction } = props + const { navigation, currencyCode, wallet, tokenId, transaction } = props const { metadata } = transaction const { name, amountFiat: defaultAmountFiat = 0 } = metadata ?? {} @@ -84,7 +85,8 @@ export function TransactionListRow(props: Props) { } navigation.push('transactionDetails', { edgeTransaction: transaction, - walletId: wallet.id + walletId: wallet.id, + tokenId }) }) diff --git a/src/components/themed/TransactionRow.tsx b/src/components/themed/TransactionRow.tsx index d21c6002c76..9191bad8835 100644 --- a/src/components/themed/TransactionRow.tsx +++ b/src/components/themed/TransactionRow.tsx @@ -7,6 +7,7 @@ import Ionicons from 'react-native-vector-icons/Ionicons' import { sprintf } from 'sprintf-js' import { formatCategory, splitCategory } from '../../actions/CategoriesActions' +import { TX_ACTION_LABEL_MAP } from '../../constants/txActionConstants' import { useHandler } from '../../hooks/useHandler' import { lstrings } from '../../locales/strings' import { triggerHaptic } from '../../util/haptic' @@ -48,6 +49,7 @@ const TransactionRowComponent = (props: Props) => { const styles = getStyles(theme) const { canReplaceByFee = false } = wallet.currencyInfo + const { action } = transaction const isSentTransaction = transaction.nativeAmount.startsWith('-') || (eq(transaction.nativeAmount, '0') && transaction.isSend) @@ -58,12 +60,20 @@ const TransactionRowComponent = (props: Props) => { let transactionText, transactionIcon, transactionStyle if (isSentTransaction) { transactionText = - transaction.metadata && transaction.metadata.name ? transaction.metadata.name : lstrings.fragment_transaction_list_sent_prefix + selectedCurrencyName + action != null + ? TX_ACTION_LABEL_MAP[action.type] + : transaction.metadata && transaction.metadata.name + ? transaction.metadata.name + : lstrings.fragment_transaction_list_sent_prefix + selectedCurrencyName transactionIcon = transactionStyle = styles.iconSent } else { transactionText = - transaction.metadata && transaction.metadata.name ? transaction.metadata.name : lstrings.fragment_transaction_list_receive_prefix + selectedCurrencyName + action != null + ? TX_ACTION_LABEL_MAP[action.type] + : transaction.metadata && transaction.metadata.name + ? transaction.metadata.name + : lstrings.fragment_transaction_list_receive_prefix + selectedCurrencyName transactionIcon = transactionStyle = styles.iconRequest } diff --git a/src/constants/txActionConstants.ts b/src/constants/txActionConstants.ts new file mode 100644 index 00000000000..29135715f5b --- /dev/null +++ b/src/constants/txActionConstants.ts @@ -0,0 +1,14 @@ +import { EdgeTxActionStakeType, EdgeTxActionSwapType } from 'edge-core-js' + +import { lstrings } from '../locales/strings' + +export const TX_ACTION_LABEL_MAP: Record = { + swap: lstrings.transaction_details_swap, + swapOrderPost: lstrings.transaction_details_swap_order_post, + swapOrderFill: lstrings.transaction_details_swap_order_fill, + swapOrderCancel: lstrings.transaction_details_swap_order_cancel, + stake: lstrings.transaction_details_stake, + stakeOrder: lstrings.transaction_details_stake_order, + unstake: lstrings.transaction_details_unstake, + unstakeOrder: lstrings.transaction_details_unstake_order +} diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 64f0a09913e..7835e0aaca4 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -677,6 +677,28 @@ const strings = { transaction_details_exchange_support: 'Exchange Support', transaction_details_exchange_support_request: '%s Support Request', transaction_details_fee_warning: 'High Network Fees', + transaction_details_swap: 'Swap Funds', + transaction_details_swap_order_cancel: 'Swap Order Canceled', + transaction_details_swap_order_post: 'Swap Order Opened', + transaction_details_swap_order_fill: 'Swap Order Filled', + transaction_details_stake: 'Stake Funds', + transaction_details_stake_order: 'Stake Order', + transaction_details_unstake: 'Unstake Funds', + transaction_details_unstake_order: 'Unstake Order', + + transaction_details_swap_to_subcat_1s: 'to %1$s', + transaction_details_swap_from_subcat_1s: 'from %1$s', + transaction_details_swap_order_notes_2s: '%1$s to %2$s', + transaction_details_stake_subcat_1s: 'Stake %1$s', + transaction_details_stake_subcat_2s: 'Stake %1$s and %2$s', + transaction_details_stake_order_notes_1s: 'Create Stake %1$s Order', + transaction_details_stake_order_notes_2s: 'Create Stake %1$s and %2$s Order', + transaction_details_stake_order_subcat: 'Create Stake Order', + transaction_details_unstake_subcat_1s: 'Unstake %1$s', + transaction_details_unstake_subcat_2s: 'Unstake %1$s and %2$s', + transaction_details_unstake_order_notes_1s: 'Create Unstake %1$s Order', + transaction_details_unstake_order_notes_2s: 'Create Unstake %1$s and %2$s Order', + my_receive_addresses_title: 'My Receive Addresses', category_modal_title: 'Choose a Category', diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index ab1a7cd603b..5c1fba8d908 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -608,6 +608,26 @@ "transaction_details_exchange_support": "Exchange Support", "transaction_details_exchange_support_request": "%s Support Request", "transaction_details_fee_warning": "High Network Fees", + "transaction_details_swap": "Swap Funds", + "transaction_details_swap_order_cancel": "Swap Order Canceled", + "transaction_details_swap_order_post": "Swap Order Opened", + "transaction_details_swap_order_fill": "Swap Order Filled", + "transaction_details_stake": "Stake Funds", + "transaction_details_stake_order": "Stake Order", + "transaction_details_unstake": "Unstake Funds", + "transaction_details_unstake_order": "Unstake Order", + "transaction_details_swap_to_subcat_1s": "to %1$s", + "transaction_details_swap_from_subcat_1s": "from %1$s", + "transaction_details_swap_order_notes_2s": "%1$s to %2$s", + "transaction_details_stake_subcat_1s": "Stake %1$s", + "transaction_details_stake_subcat_2s": "Stake %1$s and %2$s", + "transaction_details_stake_order_notes_1s": "Create Stake %1$s Order", + "transaction_details_stake_order_notes_2s": "Create Stake %1$s and %2$s Order", + "transaction_details_stake_order_subcat": "Create Stake Order", + "transaction_details_unstake_subcat_1s": "Unstake %1$s", + "transaction_details_unstake_subcat_2s": "Unstake %1$s and %2$s", + "transaction_details_unstake_order_notes_1s": "Create Unstake %1$s Order", + "transaction_details_unstake_order_notes_2s": "Create Unstake %1$s and %2$s Order", "my_receive_addresses_title": "My Receive Addresses", "category_modal_title": "Choose a Category", "transaction_details_notes_title": "Notes", diff --git a/src/types/routerTypes.tsx b/src/types/routerTypes.tsx index 713f9edf81b..7dd69ebeff1 100644 --- a/src/types/routerTypes.tsx +++ b/src/types/routerTypes.tsx @@ -284,6 +284,7 @@ export interface RouteParamList { transactionDetails: { edgeTransaction: EdgeTransaction walletId: string + tokenId?: string } transactionList: TransactionListParams transactionsExport: {