Skip to content

Commit

Permalink
Merge pull request #5275 from EdgeApp/sam/activate-tokens-lag
Browse files Browse the repository at this point in the history
Dispatch `activateWalletTokens` from TransactionListScene
  • Loading branch information
samholmes authored Sep 30, 2024
2 parents fec780a + 52eeda0 commit aff0101
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 109 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- fixed: AddressTile2 touchable area states
- fixed: Cases where it was possible to create duplicate custom tokens
- fixed: Clear previous swap errors when new amounts are entered or swap assets are changed in `SwapCreateScene`
- fixed: Handle race condition when navigating to a token's transaction list which requires token activation (XRP, Algorand, etc)
- fixed: Message about overriding a built-in token contract, which is not possible to do
- fixed: Round Kado-provided amounts during sell

Expand Down
186 changes: 91 additions & 95 deletions src/actions/WalletActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { Airship, showError, showToast } from '../components/services/AirshipIns
import { getSpecialCurrencyInfo, SPECIAL_CURRENCY_INFO } from '../constants/WalletAndCurrencyConstants'
import { lstrings } from '../locales/strings'
import { selectDisplayDenomByCurrencyCode } from '../selectors/DenominationSelectors'
import { Dispatch, RootState, ThunkAction } from '../types/reduxTypes'
import { ThunkAction } from '../types/reduxTypes'
import { NavigationBase } from '../types/routerTypes'
import { MapObject } from '../types/types'
import { getCurrencyCode, getToken, isKeysOnlyPlugin } from '../util/CurrencyInfoHelpers'
import { getCurrencyCode, isKeysOnlyPlugin } from '../util/CurrencyInfoHelpers'
import { getWalletName } from '../util/CurrencyWalletHelpers'
import { fetchInfo } from '../util/network'
import { convertCurrencyFromExchangeRates } from '../util/utils'
Expand Down Expand Up @@ -53,7 +53,7 @@ export function selectWalletToken({ navigation, walletId, tokenId, alwaysActivat
if (tokenId != null) {
const { unactivatedTokenIds } = wallet
if (unactivatedTokenIds.find(unactivatedTokenId => unactivatedTokenId === tokenId) != null) {
await activateWalletTokens(dispatch, state, navigation, wallet, [tokenId])
await dispatch(activateWalletTokens(navigation, wallet, [tokenId]))
return false
}
if (walletId !== currentWalletId || currencyCode !== currentWalletCurrencyCode) {
Expand Down Expand Up @@ -147,101 +147,97 @@ export function updateMostRecentWalletsSelected(walletId: string, tokenId: EdgeT
}
}

const activateWalletTokens = async (
dispatch: Dispatch,
state: RootState,
navigation: NavigationBase,
wallet: EdgeCurrencyWallet,
tokenIds?: string[]
): Promise<void> => {
if (tokenIds == null) throw new Error('Activating mainnet wallets unsupported')
const { account } = state.core
const { defaultIsoFiat, defaultFiat } = state.ui.settings
const { assetOptions } = await account.getActivationAssets({ activateWalletId: wallet.id, activateTokenIds: tokenIds })
const { pluginId } = wallet.currencyInfo

// See if there is only one wallet option for activation
if (assetOptions.length === 1 && assetOptions[0].paymentWalletId != null) {
const { paymentWalletId, tokenId } = assetOptions[0]
const activationQuote = await account.activateWallet({
activateWalletId: wallet.id,
activateTokenIds: tokenIds,
paymentInfo: {
walletId: paymentWalletId,
tokenId
}
})
const tokensText = tokenIds.map(tokenId => {
const { currencyCode, displayName } = getToken(wallet, tokenId) ?? {}
return `${displayName} (${currencyCode})`
})
const tileTitle = tokenIds.length > 1 ? lstrings.activate_wallet_tokens_scene_tile_title : lstrings.activate_wallet_token_scene_tile_title
const tileBody = tokensText.join(', ')

const { networkFee } = activationQuote
const { nativeAmount: nativeFee, currencyPluginId, tokenId: feeTokenId } = networkFee
if (currencyPluginId !== pluginId) throw new Error('Internal Error: Fee asset mismatch.')

const paymentCurrencyCode = getCurrencyCode(wallet, feeTokenId)

const exchangeNetworkFee = await wallet.nativeToDenomination(nativeFee, paymentCurrencyCode)
const feeDenom = selectDisplayDenomByCurrencyCode(state, wallet.currencyConfig, paymentCurrencyCode)
const displayFee = div(nativeFee, feeDenom.multiplier, log10(feeDenom.multiplier))
let fiatFee = convertCurrencyFromExchangeRates(state.exchangeRates, paymentCurrencyCode, defaultIsoFiat, exchangeNetworkFee)
if (lt(fiatFee, '0.001')) fiatFee = '<0.001'
else fiatFee = round(fiatFee, -3)
const feeString = `${displayFee} ${feeDenom.name} (${fiatFee} ${defaultFiat})`
let bodyText = lstrings.activate_wallet_token_scene_body

const { tokenActivationAdditionalReserveText } = SPECIAL_CURRENCY_INFO[pluginId] ?? {}
if (tokenActivationAdditionalReserveText != null) {
bodyText += '\n\n' + tokenActivationAdditionalReserveText
}

navigation.navigate('confirmScene', {
titleText: lstrings.activate_wallet_token_scene_title,
bodyText,
infoTiles: [
{ label: tileTitle, value: tileBody },
{ label: lstrings.mining_fee, value: feeString }
],
onConfirm: (resetSlider: () => void) => {
if (lt(wallet.balanceMap.get(feeTokenId) ?? '0', nativeFee)) {
const msg = tokenIds.length > 1 ? lstrings.activate_wallet_tokens_insufficient_funds_s : lstrings.activate_wallet_token_insufficient_funds_s
Airship.show<'ok' | undefined>(bridge => (
<ButtonsModal
bridge={bridge}
title={lstrings.create_wallet_account_unfinished_activation_title}
message={sprintf(msg, feeString)}
buttons={{ ok: { label: lstrings.string_ok } }}
/>
)).catch(err => showError(err))
navigation.pop()
return
export function activateWalletTokens(navigation: NavigationBase, wallet: EdgeCurrencyWallet, tokenIds: EdgeTokenId[]): ThunkAction<Promise<void>> {
return async (_dispatch, getState) => {
const state = getState()
const { account } = state.core
const { defaultIsoFiat, defaultFiat } = state.ui.settings
const { assetOptions } = await account.getActivationAssets({ activateWalletId: wallet.id, activateTokenIds: tokenIds })
const { pluginId } = wallet.currencyInfo

// See if there is only one wallet option for activation
if (assetOptions.length === 1 && assetOptions[0].paymentWalletId != null) {
const { paymentWalletId, tokenId } = assetOptions[0]
const activationQuote = await account.activateWallet({
activateWalletId: wallet.id,
activateTokenIds: tokenIds,
paymentInfo: {
walletId: paymentWalletId,
tokenId
}
})
const tokensText = tokenIds.map(tokenId => {
const { currencyCode, displayName } = tokenId != null ? wallet.currencyConfig.allTokens[tokenId] : wallet.currencyInfo
return `${displayName} (${currencyCode})`
})
const tileTitle = tokenIds.length > 1 ? lstrings.activate_wallet_tokens_scene_tile_title : lstrings.activate_wallet_token_scene_tile_title
const tileBody = tokensText.join(', ')

const { networkFee } = activationQuote
const { nativeAmount: nativeFee, currencyPluginId, tokenId: feeTokenId } = networkFee
if (currencyPluginId !== pluginId) throw new Error('Internal Error: Fee asset mismatch.')

const paymentCurrencyCode = getCurrencyCode(wallet, feeTokenId)

const exchangeNetworkFee = await wallet.nativeToDenomination(nativeFee, paymentCurrencyCode)
const feeDenom = selectDisplayDenomByCurrencyCode(state, wallet.currencyConfig, paymentCurrencyCode)
const displayFee = div(nativeFee, feeDenom.multiplier, log10(feeDenom.multiplier))
let fiatFee = convertCurrencyFromExchangeRates(state.exchangeRates, paymentCurrencyCode, defaultIsoFiat, exchangeNetworkFee)
if (lt(fiatFee, '0.001')) fiatFee = '<0.001'
else fiatFee = round(fiatFee, -3)
const feeString = `${displayFee} ${feeDenom.name} (${fiatFee} ${defaultFiat})`
let bodyText = lstrings.activate_wallet_token_scene_body

const { tokenActivationAdditionalReserveText } = SPECIAL_CURRENCY_INFO[pluginId] ?? {}
if (tokenActivationAdditionalReserveText != null) {
bodyText += '\n\n' + tokenActivationAdditionalReserveText
}

const name = activateWalletName[pluginId]?.name ?? lstrings.activate_wallet_token_transaction_name_category_generic
const notes = activateWalletName[pluginId]?.notes ?? lstrings.activate_wallet_token_transaction_notes_generic
activationQuote
.approve({
metadata: {
name,
category: `Expense:${lstrings.activate_wallet_token_transaction_name_category_generic}`,
notes
}
})
.then(result => {
showToast(lstrings.activate_wallet_token_success, ACTIVATION_TOAST_AUTO_HIDE_MS)
navigation.pop()
})
.catch(e => {
navigation.navigate('confirmScene', {
titleText: lstrings.activate_wallet_token_scene_title,
bodyText,
infoTiles: [
{ label: tileTitle, value: tileBody },
{ label: lstrings.mining_fee, value: feeString }
],
onConfirm: (resetSlider: () => void) => {
if (lt(wallet.balanceMap.get(feeTokenId) ?? '0', nativeFee)) {
const msg = tokenIds.length > 1 ? lstrings.activate_wallet_tokens_insufficient_funds_s : lstrings.activate_wallet_token_insufficient_funds_s
Airship.show<'ok' | undefined>(bridge => (
<ButtonsModal
bridge={bridge}
title={lstrings.create_wallet_account_unfinished_activation_title}
message={sprintf(msg, feeString)}
buttons={{ ok: { label: lstrings.string_ok } }}
/>
)).catch(err => showError(err))
navigation.pop()
showError(e)
})
}
})
} else {
throw new Error('Activation with multiple wallet options not supported yet')
return
}

const name = activateWalletName[pluginId]?.name ?? lstrings.activate_wallet_token_transaction_name_category_generic
const notes = activateWalletName[pluginId]?.notes ?? lstrings.activate_wallet_token_transaction_notes_generic
activationQuote
.approve({
metadata: {
name,
category: `Expense:${lstrings.activate_wallet_token_transaction_name_category_generic}`,
notes
}
})
.then(result => {
showToast(lstrings.activate_wallet_token_success, ACTIVATION_TOAST_AUTO_HIDE_MS)
navigation.pop()
})
.catch(e => {
navigation.pop()
showError(e)
})
}
})
} else {
throw new Error('Activation with multiple wallet options not supported yet')
}
}
}

Expand Down
17 changes: 8 additions & 9 deletions src/components/scenes/ConfirmScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings'
import { useHandler } from '../../hooks/useHandler'
import { lstrings } from '../../locales/strings'
import { EdgeSceneProps } from '../../types/routerTypes'
import { EdgeButton } from '../buttons/EdgeButton'
import { SceneWrapper } from '../common/SceneWrapper'
import { EdgeRow } from '../rows/EdgeRow'
import { cacheStyles, Theme, useTheme } from '../services/ThemeContext'
import { EdgeText } from '../themed/EdgeText'
import { MainButton } from '../themed/MainButton'
import { SafeSlider } from '../themed/SafeSlider'
import { SceneHeader } from '../themed/SceneHeader'
import { SceneHeaderUi4 } from '../themed/SceneHeaderUi4'

interface Props extends EdgeSceneProps<'confirmScene'> {}

Expand All @@ -24,7 +24,7 @@ export interface ConfirmSceneParams {
onBack?: () => void
}

const ConfirmComponent = (props: Props) => {
const ConfirmSceneComponent = (props: Props) => {
const { navigation, route } = props
const theme = useTheme()
const styles = getStyles(theme)
Expand Down Expand Up @@ -52,9 +52,9 @@ const ConfirmComponent = (props: Props) => {
}, [onBack])

return (
<SceneWrapper>
<SceneWrapper scroll padding={theme.rem(0.5)}>
<KeyboardAwareScrollView extraScrollHeight={theme.rem(2.75)} enableOnAndroid scrollIndicatorInsets={SCROLL_INDICATOR_INSET_FIX}>
<SceneHeader title={titleText} underline />
<SceneHeaderUi4 title={titleText} />
<View style={styles.body}>
<EdgeText disableFontScaling numberOfLines={16}>
{bodyText}
Expand All @@ -63,14 +63,14 @@ const ConfirmComponent = (props: Props) => {
{renderInfoTiles()}
<View style={styles.footer}>
<SafeSlider disabled={false} onSlidingComplete={handleSliderComplete} />
<MainButton label={lstrings.string_cancel_cap} type="escape" marginRem={0.5} onPress={handleBackButton} />
<EdgeButton label={lstrings.string_cancel_cap} type="tertiary" marginRem={1} onPress={handleBackButton} />
</View>
</KeyboardAwareScrollView>
</SceneWrapper>
)
}

export const ConfirmScene = React.memo(ConfirmComponent)
export const ConfirmScene = React.memo(ConfirmSceneComponent)

const getStyles = cacheStyles((theme: Theme) => ({
titleText: {
Expand All @@ -79,8 +79,7 @@ const getStyles = cacheStyles((theme: Theme) => ({
body: {
alignItems: 'center',
justifyContent: 'center',
margin: theme.rem(1),
marginTop: theme.rem(1.5)
margin: theme.rem(0.5)
},
footer: {
margin: theme.rem(1),
Expand Down
21 changes: 20 additions & 1 deletion src/components/scenes/TransactionListScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { ListRenderItemInfo, Platform, RefreshControl, View } from 'react-native
import Animated from 'react-native-reanimated'
import { useSafeAreaFrame } from 'react-native-safe-area-context'

import { activateWalletTokens } from '../../actions/WalletActions'
import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings'
import { SPECIAL_CURRENCY_INFO } from '../../constants/WalletAndCurrencyConstants'
import { useAsyncEffect } from '../../hooks/useAsyncEffect'
import { useHandler } from '../../hooks/useHandler'
import { useIconColor } from '../../hooks/useIconColor'
import { useTransactionList } from '../../hooks/useTransactionList'
Expand All @@ -14,7 +16,7 @@ import { lstrings } from '../../locales/strings'
import { getExchangeDenomByCurrencyCode } from '../../selectors/DenominationSelectors'
import { FooterRender } from '../../state/SceneFooterState'
import { useSceneScrollHandler } from '../../state/SceneScrollState'
import { useSelector } from '../../types/reactRedux'
import { useDispatch, useSelector } from '../../types/reactRedux'
import { EdgeSceneProps } from '../../types/routerTypes'
import { infoServerData } from '../../util/network'
import { calculateSpamThreshold, darkenHexColor, unixToLocaleDateTime, zeroString } from '../../util/utils'
Expand Down Expand Up @@ -47,6 +49,7 @@ function TransactionListComponent(props: Props) {
const { navigation, route, wallet } = props
const theme = useTheme()
const styles = getStyles(theme)
const dispatch = useDispatch()

const { width: screenWidth } = useSafeAreaFrame()

Expand All @@ -70,6 +73,7 @@ function TransactionListComponent(props: Props) {

// Watchers:
const enabledTokenIds = useWatch(wallet, 'enabledTokenIds')
const unactivatedTokenIds = useWatch(wallet, 'unactivatedTokenIds')

// ---------------------------------------------------------------------------
// Derived values
Expand Down Expand Up @@ -139,6 +143,21 @@ function TransactionListComponent(props: Props) {
}
}, [enabledTokenIds, navigation, tokenId])

// Automatically navigate to the token activation confirmation scene if
// the token appears in the unactivatedTokenIds list once the wallet loads
// this state.
useAsyncEffect(
async () => {
if (unactivatedTokenIds.length > 0) {
if (unactivatedTokenIds.some(unactivatedTokenId => unactivatedTokenId === tokenId)) {
await dispatch(activateWalletTokens(navigation, wallet, [tokenId]))
}
}
},
[unactivatedTokenIds],
'TransactionListScene unactivatedTokenIds check'
)

//
// Handlers
//
Expand Down
4 changes: 2 additions & 2 deletions src/components/themed/MainButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ interface Props {
// Which visual style to use. Defaults to primary (solid):
type?: MainButtonType

// From ButtonUi4
// From EdgeButton
layout?: 'row' | 'column' | 'solo'
}

/**
* @deprecated
* Use ButtonUi4 instead, and consider whether there is a genuine need for
* Use EdgeButton instead, and consider whether there is a genuine need for
* special margins in MainButton use cases from a UI4 design perspective.
*/
export function MainButton(props: Props) {
Expand Down
1 change: 0 additions & 1 deletion src/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,6 @@ const strings = {
activate_wallet_token_transaction_name_xrp: 'XRP Ledger',
activate_wallet_token_transaction_notes_xrp: 'Activate XRP token by enabling Trust Line to issuer',
activate_wallet_token_scene_title: 'Activate Token',
activate_wallet_tokens_scene_title: 'Activate Tokens',
activate_wallet_token_scene_tile_title: 'Token to Activate',
activate_wallet_tokens_scene_tile_title: 'Tokens to Activate',
activate_wallet_token_scene_body:
Expand Down
1 change: 0 additions & 1 deletion src/locales/strings/enUS.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@
"activate_wallet_token_transaction_name_xrp": "XRP Ledger",
"activate_wallet_token_transaction_notes_xrp": "Activate XRP token by enabling Trust Line to issuer",
"activate_wallet_token_scene_title": "Activate Token",
"activate_wallet_tokens_scene_title": "Activate Tokens",
"activate_wallet_token_scene_tile_title": "Token to Activate",
"activate_wallet_tokens_scene_tile_title": "Tokens to Activate",
"activate_wallet_token_scene_body": "To send and receive the selected token you will first need to activate it with a blockchain transaction. This transaction will cost the following fee.\n\nPlease confirm using the slider below.",
Expand Down

0 comments on commit aff0101

Please sign in to comment.