Skip to content

Commit

Permalink
feat(suite-native): Add coin account UI polish
Browse files Browse the repository at this point in the history
  • Loading branch information
vytick committed Feb 22, 2024
1 parent 444b665 commit 600d4b7
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 86 deletions.
25 changes: 23 additions & 2 deletions suite-common/wallet-core/src/accounts/accountsReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,12 @@ export const selectDeviceAccounts = (state: AccountsRootState & DeviceRootState)
export const selectDeviceAccountsForNetworkSymbolAndAccountType = memoizeWithArgs(
(
state: AccountsRootState & DeviceRootState,
networkSymbol: NetworkSymbol,
accountType: AccountType,
networkSymbol?: NetworkSymbol,
accountType?: AccountType,
) => {
if (!networkSymbol || !accountType) {
return [];
}
const accounts = selectDeviceAccounts(state);

return accounts.filter(
Expand All @@ -172,6 +175,24 @@ export const selectDeviceAccountsForNetworkSymbolAndAccountType = memoizeWithArg
{ size: D.keys(networks).length },
);

export const selectDeviceAccountsForNetworkSymbolAndAccountTypeWithIndex = (
state: AccountsRootState & DeviceRootState,
networkSymbol?: NetworkSymbol,
accountType?: AccountType,
accountIndex?: number,
) => {
if (!networkSymbol || !accountType || accountIndex === undefined || accountIndex < 0) {
return undefined;
}
const accounts = selectDeviceAccountsForNetworkSymbolAndAccountType(
state,
networkSymbol,
accountType,
);

return accounts[accountIndex];
};

export const selectDeviceAccountsLengthPerNetwork = (state: AccountsRootState & DeviceRootState) =>
pipe(
selectDeviceAccounts(state),
Expand Down
7 changes: 7 additions & 0 deletions suite-native/intl/src/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,11 +531,18 @@ export const en = {
},
},
transactions: {
title: 'Transactions',
receive: 'Receive',
phishing: {
badge: 'Caution!',
warning:
"Caution! This transaction may be a scam. If you’re unsure, don't engage. <blogLink>Read more</blogLink>",
},
emptyState: {
title: 'No transactions',
subtitle: 'Get started by receiving coins',
button: 'Receive',
},
},
deviceManager: {
deviceButtons: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useSelector } from 'react-redux';

import { Box, Text, Card, RoundedIcon, Badge, Loader } from '@suite-native/atoms';
import { Box, Text, Card, RoundedIcon, Badge, BoxSkeleton } from '@suite-native/atoms';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { networks } from '@suite-common/wallet-config';
import { FiatAmountFormatter } from '@suite-native/formatters';
Expand All @@ -14,7 +14,7 @@ type CoinPriceCardProps = {
};

type PriceChangeIndicatorProps = {
valuePercentageChange: number;
valuePercentageChange: number | null;
};

const cardStyle = prepareNativeStyle(utils => ({
Expand All @@ -41,25 +41,31 @@ const indicatorContainer = prepareNativeStyle(utils => ({

const PriceChangeIndicator = ({ valuePercentageChange }: PriceChangeIndicatorProps) => {
const { applyStyle } = useNativeStyles();
const priceHasIncreased = valuePercentageChange >= 0;

const percentageChange = valuePercentageChange ?? 0;
const priceHasIncreased = percentageChange >= 0;

const icon = priceHasIncreased ? 'arrowUp' : 'arrowDown';
const badgeVariant = priceHasIncreased ? 'green' : 'red';
const formattedPercentage = `${valuePercentageChange.toPrecision(3)} %`;
const formattedPercentage = `${percentageChange.toPrecision(3)} %`;

return (
<Box style={applyStyle(indicatorContainer)}>
<Text variant="label" color="textSubdued">
24h change
</Text>
<Box justifyContent="center" alignItems="center" flexDirection="row">
<Badge
icon={icon}
iconSize="extraSmall"
size="medium"
variant={badgeVariant}
label={formattedPercentage}
/>
{valuePercentageChange ? (
<Badge
icon={icon}
iconSize="extraSmall"
size="medium"
variant={badgeVariant}
label={formattedPercentage}
/>
) : (
<BoxSkeleton width={70} height={24} borderRadius={12} />
)}
</Box>
</Box>
);
Expand Down Expand Up @@ -99,13 +105,8 @@ export const CoinPriceCard = ({ accountKey }: CoinPriceCardProps) => {
)}
</Box>
</Box>
{valuePercentageChange ? (
<PriceChangeIndicator valuePercentageChange={valuePercentageChange} />
) : (
<Box alignItems="center" justifyContent="center">
<Loader size="large" />
</Box>
)}

<PriceChangeIndicator valuePercentageChange={valuePercentageChange} />
</Card>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RootStackRoutes,
TabToStackCompositeNavigationProp,
} from '@suite-native/navigation';
import { Translation, useTranslate } from '@suite-native/intl';

import { AccountDetailGraph } from './AccountDetailGraph';
import { AccountDetailCryptoValue } from './AccountDetailCryptoValue';
Expand Down Expand Up @@ -92,6 +93,7 @@ export const TransactionListHeader = memo(
toggleIncludeTokenTransactions,
tokenContract,
}: AccountDetailHeaderProps) => {
const { translate } = useTranslate();
const navigation = useNavigation<AccountsNavigationProps>();

const account = useSelector((state: AccountsRootState) =>
Expand Down Expand Up @@ -127,16 +129,26 @@ export const TransactionListHeader = memo(
{accountHasTransactions && (
<Box marginVertical="medium" paddingHorizontal="medium">
<Button iconLeft="receive" size="large" onPress={handleReceive}>
Receive
{translate('transactions.receive')}
</Button>
</Box>
)}
{isPriceCardDisplayed && <CoinPriceCard accountKey={accountKey} />}
{isPriceCardDisplayed && (
<Box marginBottom={accountHasTransactions ? undefined : 'medium'}>
<CoinPriceCard accountKey={accountKey} />
</Box>
)}

<Divider />
<Box marginVertical="small" marginHorizontal="large">
<Text variant="titleSmall">Transactions</Text>
</Box>
{accountHasTransactions && (
<>
<Divider />
<Box marginVertical="small" marginHorizontal="large">
<Text variant="titleSmall">
<Translation id="transactions.title" />
</Text>
</Box>
</>
)}
</VStack>

{isEthereumAccountDetail && accountHasTransactions && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dimensions } from 'react-native';

import { RouteProp, useRoute } from '@react-navigation/native';

import { RootStackParamList, RootStackRoutes, Screen } from '@suite-native/navigation';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import {
RootStackParamList,
RootStackRoutes,
Screen,
ScreenSubHeader,
} from '@suite-native/navigation';
import {
AccountsRootState,
fetchTransactionsThunk,
selectAccountLabel,
selectAccountByKey,
TransactionsRootState,
DeviceRootState,
selectDeviceAccountsForNetworkSymbolAndAccountTypeWithIndex,
} from '@suite-common/wallet-core';
import { FiatRatesRootState } from '@suite-native/fiat-rates';
import { TransactionList } from '@suite-native/transactions';
Expand All @@ -19,39 +28,92 @@ import {
} from '@suite-native/ethereum-tokens';
import { analytics, EventType } from '@suite-native/analytics';
import { SettingsSliceRootState } from '@suite-native/module-settings';
import { BoxSkeleton, Card, VStack } from '@suite-native/atoms';

import { TransactionListHeader } from '../components/TransactionListHeader';
import { AccountDetailScreenHeader } from '../components/AccountDetailScreenHeader';
import { TokenAccountDetailScreenSubHeader } from '../components/TokenAccountDetailScreenSubHeader';

const SCREEN_WIDTH = Dimensions.get('window').width;

const cardStyle = prepareNativeStyle(utils => ({
padding: utils.spacings.small,
}));

const LoadingAccountDetailScreen = () => {
const { applyStyle } = useNativeStyles();

return (
<Screen screenHeader={<ScreenSubHeader />}>
<VStack spacing="extraLarge" alignItems="center">
<Card style={applyStyle(cardStyle)}>
<BoxSkeleton width={SCREEN_WIDTH - 32} height={70} />
</Card>
<Card style={applyStyle(cardStyle)}>
<VStack spacing="large" alignItems="center" paddingHorizontal="large">
<BoxSkeleton width={104} height={104} borderRadius={52} />
<BoxSkeleton width={160} height={30} />
<BoxSkeleton width={200} height={24} />
<BoxSkeleton width={SCREEN_WIDTH - 80} height={48} borderRadius={24} />
</VStack>
</Card>
</VStack>
</Screen>
);
};

export const AccountDetailScreen = memo(() => {
const route = useRoute<RouteProp<RootStackParamList, RootStackRoutes.AccountDetail>>();
const { accountKey, tokenContract } = route.params;
const {
accountKey: routeAccountKey,
tokenContract,
networkSymbol,
accountType,
accountIndex,
} = route.params;

const dispatch = useDispatch();

const [areTokensIncluded, setAreTokensIncluded] = useState(false);

const foundAccountKey = useSelector((state: AccountsRootState & DeviceRootState) =>
selectDeviceAccountsForNetworkSymbolAndAccountTypeWithIndex(
state,
networkSymbol,
accountType,
accountIndex,
),
)?.key;

const accountKey = routeAccountKey ?? foundAccountKey;

const account = useSelector((state: AccountsRootState) =>
selectAccountByKey(state, accountKey),
);
const accountLabel = useSelector((state: AccountsRootState) =>
selectAccountLabel(state, accountKey),
);

const accountTransactions = useSelector(
(state: TransactionsRootState & FiatRatesRootState & SettingsSliceRootState) =>
selectAccountOrTokenAccountTransactions(
state,
accountKey,
tokenContract ?? null,
areTokensIncluded,
),
accountKey
? selectAccountOrTokenAccountTransactions(
state,
accountKey,
tokenContract ?? null,
areTokensIncluded,
)
: [],
);
const token = useSelector((state: AccountsRootState) =>
selectEthereumAccountTokenInfo(state, accountKey, tokenContract),
);

const fetchMoreTransactions = useCallback(
(pageToFetch: number, perPage: number) => {
if (!accountKey) {
return;
}
dispatch(
fetchTransactionsThunk({
accountKey,
Expand Down Expand Up @@ -81,20 +143,21 @@ export const AccountDetailScreen = memo(() => {
}, []);

const listHeaderComponent = useMemo(
() => (
<TransactionListHeader
accountKey={accountKey}
tokenContract={tokenContract}
areTokensIncluded={areTokensIncluded}
toggleIncludeTokenTransactions={toggleIncludeTokenTransactions}
/>
),
() =>
accountKey ? (
<TransactionListHeader
accountKey={accountKey}
tokenContract={tokenContract}
areTokensIncluded={areTokensIncluded}
toggleIncludeTokenTransactions={toggleIncludeTokenTransactions}
/>
) : null,
[accountKey, tokenContract, areTokensIncluded, toggleIncludeTokenTransactions],
);

if (!account) return null;

return (
return !account || !listHeaderComponent || !accountKey ? (
<LoadingAccountDetailScreen />
) : (
<Screen
screenHeader={
token?.name ? (
Expand Down
Loading

0 comments on commit 600d4b7

Please sign in to comment.