Skip to content

Commit

Permalink
feat(suite-native): Mobile Trade: Account and address picker stub
Browse files Browse the repository at this point in the history
  • Loading branch information
jbazant committed Feb 11, 2025
1 parent b6a7ae1 commit 1bccaf0
Show file tree
Hide file tree
Showing 18 changed files with 872 additions and 26 deletions.
9 changes: 9 additions & 0 deletions suite-native/intl/src/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,15 @@ export const en = {
emptyDescription:
'We couldn’t find a country matching your search. Try checking the spelling or exploring the list for the right option.',
},
accountSheet: {
addressEmptyTitle: 'No address found',
addressEmptyDescription:
'We couldn’t find an address matching your search. Try checking the spelling or exploring the list for the right option.',
titleStep1: 'Pick account',
newAddress: 'New address',
usedAddresses: 'Used addresses',
step2Hint: 'Select to display account addresses',
},
defaultSearchLabel: 'Search',
notSelected: 'Not selected',
},
Expand Down
4 changes: 3 additions & 1 deletion suite-native/module-trading/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
"@reduxjs/toolkit": "1.9.5",
"@suite-native/navigation": "workspace:*",
"@suite-native/test-utils": "workspace:*",
"@trezor/blockchain-link-types": "workspace:*",
"expo-linear-gradient": "^14.0.1",
"react": "18.2.0",
"react-native": "0.76.1",
"react-native-reanimated": "3.16.7"
"react-native-reanimated": "3.16.7",
"react-redux": "8.0.7"
}
}
25 changes: 3 additions & 22 deletions suite-native/module-trading/src/components/buy/BuyCard.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { useFormatters } from '@suite-common/formatters';
import { Card, HStack, Text, VStack } from '@suite-native/atoms';
import { Icon } from '@suite-native/icons';
import { Translation, useTranslate } from '@suite-native/intl';
import { Translation } from '@suite-native/intl';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';

import { ReceiveAccountPicker } from './ReceiveAccountPicker';
import { TradeableAssetPicker } from './TradeableAssetPicker';
import { useTradeSheetControls } from '../../hooks/useTradeSheetControls';
import { TradeableAsset } from '../../types';
import { TradingOverviewRow } from '../general/TradingOverviewRow';

const notImplementedCallback = () => {
// eslint-disable-next-line no-console
console.log('Not implemented');
};

const buySectionStyle = prepareNativeStyle(({ borders, colors, spacings }) => ({
borderBottomWidth: borders.widths.small,
Expand All @@ -21,7 +16,6 @@ const buySectionStyle = prepareNativeStyle(({ borders, colors, spacings }) => ({
}));

export const BuyCard = () => {
const { translate } = useTranslate();
const { FiatAmountFormatter, CryptoAmountFormatter } = useFormatters();
const { applyStyle } = useNativeStyles();

Expand Down Expand Up @@ -55,20 +49,7 @@ export const BuyCard = () => {
</HStack>
</HStack>
</VStack>
<TradingOverviewRow
title={translate('moduleTrading.tradingScreen.receiveAccount')}
onPress={notImplementedCallback}
noBottomBorder
>
<VStack spacing={0} paddingLeft="sp20">
<Text color="textSubdued" variant="body" textAlign="right">
Bitcoin Vault
</Text>
<Text color="textSubdued" variant="hint" ellipsizeMode="tail" numberOfLines={1}>
3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC
</Text>
</VStack>
</TradingOverviewRow>
<ReceiveAccountPicker selectedAsset={selectedValue} />
</Card>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Text, VStack } from '@suite-native/atoms';
import { Translation, useTranslate } from '@suite-native/intl';

import { useTradeSheetControls } from '../../hooks/useTradeSheetControls';
import { ReceiveAccount, TradeableAsset } from '../../types';
import { AccountSheet } from '../general/AccountSheet/AccountSheet';
import { TradingOverviewRow } from '../general/TradingOverviewRow';

type ReceiveAccountPickerProps = {
selectedAsset?: TradeableAsset;
};
type ReceiveAccountPickerSelectedAccountProps = {
selectedAccount?: ReceiveAccount;
};

const ReceiveAccountPickerRight = ({
selectedAccount,
}: ReceiveAccountPickerSelectedAccountProps) => {
if (!selectedAccount) {
return (
<Text color="textDisabled" variant="body">
<Translation id="moduleTrading.notSelected" />
</Text>
);
}

const { account, address } = selectedAccount;

if (!address) {
return (
<VStack spacing={0} paddingLeft="sp20">
<Text color="textSubdued" variant="body" textAlign="right">
{account.accountLabel}
</Text>
</VStack>
);
}

return (
<VStack spacing={0} paddingLeft="sp20">
<Text color="textSubdued" variant="body" textAlign="right">
{account.accountLabel}
</Text>
<Text color="textSubdued" variant="hint" ellipsizeMode="tail" numberOfLines={1}>
{address.address}
</Text>
</VStack>
);
};

export const ReceiveAccountPicker = ({ selectedAsset }: ReceiveAccountPickerProps) => {
const { translate } = useTranslate();

const { isSheetVisible, hideSheet, showSheet, setSelectedValue, selectedValue } =
useTradeSheetControls<ReceiveAccount>();

return (
<>
<TradingOverviewRow
title={translate('moduleTrading.tradingScreen.receiveAccount')}
onPress={showSheet}
noBottomBorder
>
<ReceiveAccountPickerRight selectedAccount={selectedValue} />
</TradingOverviewRow>
<AccountSheet
symbol={selectedAsset?.symbol ?? 'btc'}
onAccountSelect={setSelectedValue}
isVisible={isSheetVisible}
onClose={hideSheet}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Pressable } from 'react-native';

import { useFormatters } from '@suite-common/formatters';
import { NetworkSymbol } from '@suite-common/wallet-config';
import { Box, HStack, RoundedIcon, Text, VStack } from '@suite-native/atoms';
import { Icon } from '@suite-native/icons';
import { useTranslate } from '@suite-native/intl';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';

import { ReceiveAccount } from '../../../types';

export type AccountListItemProps = {
symbol: NetworkSymbol;
receiveAccount: ReceiveAccount;
onPress: () => void;
};

export const ACCOUNT_LIST_ITEM_HEIGHT = 68 as const;

const labelTextStyle = prepareNativeStyle(({ colors }) => ({
color: colors.textDefault,
flex: 1,
}));

const cryptoAmountTextStyle = prepareNativeStyle(({ colors }) => ({
color: colors.textDefault,
textAlign: 'right',
flex: 0,
}));

const networkStyle = prepareNativeStyle(({ colors }) => ({
color: colors.textSubdued,
flex: 1,
}));

const fiatStyle = prepareNativeStyle(({ colors }) => ({
color: colors.textSubdued,
textAlign: 'right',
flex: 0,
}));

export const AccountListItem = ({
symbol,
receiveAccount: { account, address },
onPress,
}: AccountListItemProps) => {
const { applyStyle } = useNativeStyles();
const { translate } = useTranslate();
const { DisplaySymbolFormatter, FiatAmountFormatter, CryptoAmountFormatter } = useFormatters();

// TODO 16638 use selectAccountFiatBalance selector
const fiatValue = 987654;

const isAddressDetail = !!address;
const shouldDisplayCaret = !isAddressDetail && !!account.addresses;
const shouldDisplayBalance = !isAddressDetail || address?.balance != null;

return (
<Pressable
onPress={onPress}
accessibilityRole="radio"
accessibilityLabel={account.accountLabel}
>
<HStack alignItems="center" spacing="sp12" paddingVertical="sp12">
<Box justifyContent="center">
<RoundedIcon symbol={symbol} />
</Box>
<VStack flex={1} spacing={0}>
<HStack alignItems="center" justifyContent="space-between">
<Text
variant="body"
numberOfLines={1}
ellipsizeMode="tail"
style={applyStyle(labelTextStyle)}
>
{address?.address ?? account.accountLabel}
</Text>
{shouldDisplayBalance && (
<Text variant="body" style={applyStyle(cryptoAmountTextStyle)}>
<CryptoAmountFormatter
value={address?.balance ?? account.availableBalance}
symbol={account.symbol}
/>
</Text>
)}
</HStack>
<HStack alignItems="center" justifyContent="space-between">
<Text variant="hint" style={applyStyle(networkStyle)}>
<DisplaySymbolFormatter value={symbol} areAmountUnitsEnabled={false} />
</Text>
{shouldDisplayBalance && (
<Text variant="hint" style={applyStyle(fiatStyle)}>
<FiatAmountFormatter value={fiatValue} />
</Text>
)}
</HStack>
</VStack>
{shouldDisplayCaret && (
<Box justifyContent="center">
<Icon
name="caretCircleRight"
color="textSecondaryHighlight"
accessibilityHint={translate('moduleTrading.accountSheet.step2Hint')}
/>
</Box>
)}
</HStack>
</Pressable>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { NetworkSymbol } from '@suite-common/wallet-config';

import { useReceiveAccountsListData } from '../../../hooks/useReceiveAccountsListData';
import { useSelectedAccount } from '../../../hooks/useSelectedAccount';
import { TradingBottomSheetSectionList } from '../TradingBottomSheetSectionList';
import { ACCOUNT_LIST_ITEM_HEIGHT, AccountListItem } from './AccountListItem';
import { AccountSheetHeader } from './AccountSheetHeader';
import { AddressListEmptyComponent } from './AddressListEmptyComponent';
import { ReceiveAccount } from '../../../types';

export type AccountSheetProps = {
isVisible: boolean;
onClose: () => void;
onAccountSelect: (account: ReceiveAccount) => void;
symbol: NetworkSymbol;
};

const keyExtractor = (item: ReceiveAccount) => `${item.account}_${item.address?.address}`;

export const AccountSheet = ({
isVisible,
onClose,
onAccountSelect,
symbol,
}: AccountSheetProps) => {
const { selectedAccount, clearSelectedAccount, onItemSelect } = useSelectedAccount({
onAccountSelect,
onClose,
isVisible,
});

// TODO 16638 should we display loading state instead of empty?
const data = useReceiveAccountsListData(symbol, selectedAccount) ?? [];

return (
<TradingBottomSheetSectionList<ReceiveAccount>
isVisible={isVisible}
onClose={onClose}
handleComponent={() => (
<AccountSheetHeader
onClose={onClose}
selectedAccount={selectedAccount}
clearSelectedAccount={clearSelectedAccount}
/>
)}
ListEmptyComponent={<AddressListEmptyComponent />}
renderItem={item => (
<AccountListItem
receiveAccount={item}
onPress={() => onItemSelect(item)}
symbol={symbol}
/>
)}
data={data}
estimatedItemSize={ACCOUNT_LIST_ITEM_HEIGHT}
keyExtractor={keyExtractor}
noSingletonSectionHeader
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Account } from '@suite-common/wallet-types';
import { BottomSheetGrabber, VStack } from '@suite-native/atoms';
import { Translation, useTranslate } from '@suite-native/intl';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';

import { SearchableSheetHeader } from '../SearchableSheetHeader';
import { SheetHeaderTitle } from '../SheetHeaderTitle';

export type AccountSheetHeaderProps = {
onClose: () => void;
selectedAccount: undefined | Account;
clearSelectedAccount: () => void;
};

const wrapperStyle = prepareNativeStyle<{}>(({ spacings }) => ({
padding: spacings.sp16,
gap: spacings.sp16,
}));

export const AccountSheetHeader = ({
selectedAccount,
clearSelectedAccount,
onClose,
}: AccountSheetHeaderProps) => {
const { applyStyle } = useNativeStyles();
const { translate } = useTranslate();

if (selectedAccount) {
return (
<SearchableSheetHeader
onClose={clearSelectedAccount}
title={selectedAccount.accountLabel}
leftButtonIcon="caretLeft"
/>
);
}

return (
<VStack style={applyStyle(wrapperStyle)}>
<BottomSheetGrabber />
<SheetHeaderTitle
leftButtonIcon="x"
onLeftButtonPress={onClose}
leftButtonA11yLabel={translate('generic.buttons.close')}
>
<Translation id="moduleTrading.accountSheet.titleStep1" />
</SheetHeaderTitle>
</VStack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Translation } from '@suite-native/intl';

import { TradingEmptyComponent } from '../TradingEmptyComponent';

// TODO 16638 we probably need empty state for step 1 as well
export const AddressListEmptyComponent = () => (
<TradingEmptyComponent
title={<Translation id="moduleTrading.accountSheet.addressEmptyTitle" />}
description={<Translation id="moduleTrading.accountSheet.addressEmptyDescription" />}
/>
);
Loading

0 comments on commit 1bccaf0

Please sign in to comment.