diff --git a/suite-common/icons/generateIconFont.ts b/suite-common/icons/generateIconFont.ts
index 3d109e628a9..a38144410f5 100644
--- a/suite-common/icons/generateIconFont.ts
+++ b/suite-common/icons/generateIconFont.ts
@@ -94,6 +94,8 @@ const usedIcons = [
'shieldWarning',
'shuffle',
'stack',
+ 'star',
+ 'starFilled',
'swap',
'trashSimple',
'treeStructure',
diff --git a/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.json b/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.json
index 5bc84d79bd8..7608cfa8ccb 100644
--- a/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.json
+++ b/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.json
@@ -20,84 +20,86 @@
"treeStructure": 61715,
"trashSimple": 61716,
"swap": 61717,
- "stack": 61718,
- "shuffle": 61719,
- "shieldWarning": 61720,
- "shieldCheck": 61721,
- "shareNetwork": 61722,
- "question": 61723,
- "qrCode": 61724,
- "prohibit": 61725,
- "plusCircle": 61726,
- "plus": 61727,
- "plugs": 61728,
- "piggyBankFilled": 61729,
- "piggyBank": 61730,
- "pictureFrame": 61731,
- "pencilSimpleLine": 61732,
- "pencilSimple": 61733,
- "pencil": 61734,
- "password": 61735,
- "palette": 61736,
- "magnifyingGlass": 61737,
- "lock": 61738,
- "link": 61739,
- "lightbulb": 61740,
- "lifebuoy": 61741,
- "info": 61742,
- "image": 61743,
- "houseFilled": 61744,
- "house": 61745,
- "handPalm": 61746,
- "githubLogo": 61747,
- "gearFilled": 61748,
- "gear": 61749,
- "flagCheckered": 61750,
- "flag": 61751,
- "fingerprintSimple": 61752,
- "fingerprint": 61753,
- "filePdf": 61754,
- "facebookLogo": 61755,
- "eyeSlash": 61756,
- "eye": 61757,
- "discoverFilled": 61758,
- "discover": 61759,
- "detective": 61760,
- "database": 61761,
- "cpu": 61762,
- "copy": 61763,
- "coins": 61764,
- "coinVerticalCheck": 61765,
- "code": 61766,
- "clockClockwise": 61767,
- "circleDashed": 61768,
- "checks": 61769,
- "checkCircleFilled": 61770,
- "checkCircle": 61771,
- "check": 61772,
- "chatCircle": 61773,
- "change": 61774,
- "caretUpFilled": 61775,
- "caretUpDown": 61776,
- "caretUp": 61777,
- "caretRight": 61778,
- "caretLeft": 61779,
- "caretDownFilled": 61780,
- "caretDown": 61781,
- "caretCircleRight": 61782,
- "calendar": 61783,
- "bugBeetle": 61784,
- "bookmarkSimple": 61785,
- "backspace": 61786,
- "arrowsLeftRight": 61787,
- "arrowsCounterClockwise": 61788,
- "arrowUpRight": 61789,
- "arrowUp": 61790,
- "arrowURightDown": 61791,
- "arrowSquareOut": 61792,
- "arrowRight": 61793,
- "arrowLineUpRight": 61794,
- "arrowLineUp": 61795,
- "arrowLineDown": 61796,
- "arrowDown": 61797
+ "starFilled": 61718,
+ "star": 61719,
+ "stack": 61720,
+ "shuffle": 61721,
+ "shieldWarning": 61722,
+ "shieldCheck": 61723,
+ "shareNetwork": 61724,
+ "question": 61725,
+ "qrCode": 61726,
+ "prohibit": 61727,
+ "plusCircle": 61728,
+ "plus": 61729,
+ "plugs": 61730,
+ "piggyBankFilled": 61731,
+ "piggyBank": 61732,
+ "pictureFrame": 61733,
+ "pencilSimpleLine": 61734,
+ "pencilSimple": 61735,
+ "pencil": 61736,
+ "password": 61737,
+ "palette": 61738,
+ "magnifyingGlass": 61739,
+ "lock": 61740,
+ "link": 61741,
+ "lightbulb": 61742,
+ "lifebuoy": 61743,
+ "info": 61744,
+ "image": 61745,
+ "houseFilled": 61746,
+ "house": 61747,
+ "handPalm": 61748,
+ "githubLogo": 61749,
+ "gearFilled": 61750,
+ "gear": 61751,
+ "flagCheckered": 61752,
+ "flag": 61753,
+ "fingerprintSimple": 61754,
+ "fingerprint": 61755,
+ "filePdf": 61756,
+ "facebookLogo": 61757,
+ "eyeSlash": 61758,
+ "eye": 61759,
+ "discoverFilled": 61760,
+ "discover": 61761,
+ "detective": 61762,
+ "database": 61763,
+ "cpu": 61764,
+ "copy": 61765,
+ "coins": 61766,
+ "coinVerticalCheck": 61767,
+ "code": 61768,
+ "clockClockwise": 61769,
+ "circleDashed": 61770,
+ "checks": 61771,
+ "checkCircleFilled": 61772,
+ "checkCircle": 61773,
+ "check": 61774,
+ "chatCircle": 61775,
+ "change": 61776,
+ "caretUpFilled": 61777,
+ "caretUpDown": 61778,
+ "caretUp": 61779,
+ "caretRight": 61780,
+ "caretLeft": 61781,
+ "caretDownFilled": 61782,
+ "caretDown": 61783,
+ "caretCircleRight": 61784,
+ "calendar": 61785,
+ "bugBeetle": 61786,
+ "bookmarkSimple": 61787,
+ "backspace": 61788,
+ "arrowsLeftRight": 61789,
+ "arrowsCounterClockwise": 61790,
+ "arrowUpRight": 61791,
+ "arrowUp": 61792,
+ "arrowURightDown": 61793,
+ "arrowSquareOut": 61794,
+ "arrowRight": 61795,
+ "arrowLineUpRight": 61796,
+ "arrowLineUp": 61797,
+ "arrowLineDown": 61798,
+ "arrowDown": 61799
}
diff --git a/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.ttf b/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.ttf
index 8002f2c04e4..2d6f9f53c18 100644
Binary files a/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.ttf and b/suite-common/icons/iconFontsMobile/TrezorSuiteIcons.ttf differ
diff --git a/suite-native/intl/src/en.ts b/suite-native/intl/src/en.ts
index 825192413a8..923b4b55483 100644
--- a/suite-native/intl/src/en.ts
+++ b/suite-native/intl/src/en.ts
@@ -1195,6 +1195,19 @@ export const en = {
description: 'We currently support staking as view-only in Trezor Suite Lite.',
},
},
+ moduleTrading: {
+ selectCoin: {
+ buttonTitle: 'Select coin',
+ },
+ tradeableAssetsSheet: {
+ title: 'Coins',
+ popularTitle: 'Favourites',
+ assetsTitle: 'All assets',
+ favouritesAdd: 'Add to favourites',
+ favouritesRemove: 'Remove from favourites',
+ },
+ defaultSearchLabel: 'Search',
+ },
};
export type Translations = typeof en;
diff --git a/suite-native/module-trading/jest.config.js b/suite-native/module-trading/jest.config.js
new file mode 100644
index 00000000000..4299995bffa
--- /dev/null
+++ b/suite-native/module-trading/jest.config.js
@@ -0,0 +1,5 @@
+const { ...baseConfig } = require('../../jest.config.native');
+
+module.exports = {
+ ...baseConfig,
+};
diff --git a/suite-native/module-trading/package.json b/suite-native/module-trading/package.json
index 25182f5e1e3..5796fe90519 100644
--- a/suite-native/module-trading/package.json
+++ b/suite-native/module-trading/package.json
@@ -8,13 +8,14 @@
"scripts": {
"depcheck": "yarn g:depcheck",
"type-check": "yarn g:tsc --build",
- "test:unit": "yarn g:jest -c ../../jest.config.native.js"
+ "test:unit": "yarn g:jest"
},
"dependencies": {
"@react-navigation/native-stack": "6.11.0",
"@reduxjs/toolkit": "1.9.5",
"@suite-native/navigation": "workspace:*",
"@suite-native/test-utils": "workspace:*",
+ "expo-linear-gradient": "^14.0.1",
"react": "18.2.0",
"react-native": "0.76.1"
}
diff --git a/suite-native/module-trading/src/components/TradeableAssetsSheet/FavouriteIcon.tsx b/suite-native/module-trading/src/components/TradeableAssetsSheet/FavouriteIcon.tsx
new file mode 100644
index 00000000000..fc69cd576a1
--- /dev/null
+++ b/suite-native/module-trading/src/components/TradeableAssetsSheet/FavouriteIcon.tsx
@@ -0,0 +1,31 @@
+import { TouchableWithoutFeedback } from 'react-native';
+
+import { Icon, IconColor, IconName } from '@suite-native/icons';
+import { useTranslate } from '@suite-native/intl';
+
+export type FavouriteIconProps = {
+ isFavourite: boolean;
+ onPress: () => void;
+};
+
+export const FavouriteIcon = ({ isFavourite, onPress }: FavouriteIconProps) => {
+ const { translate } = useTranslate();
+
+ const hint: string = isFavourite
+ ? translate('moduleTrading.tradeableAssetsSheet.favouritesRemove')
+ : translate('moduleTrading.tradeableAssetsSheet.favouritesAdd');
+ const iconName: IconName = isFavourite ? 'starFilled' : 'star';
+ // TODO 16600 - do I really want to use backgroundAlertYellowBold here?
+ // TODO outline?
+ const iconColor: IconColor = isFavourite ? 'backgroundAlertYellowBold' : 'textSubdued';
+
+ return (
+
+
+
+ );
+};
diff --git a/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetListItem.tsx b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetListItem.tsx
new file mode 100644
index 00000000000..a34f463cc53
--- /dev/null
+++ b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetListItem.tsx
@@ -0,0 +1,66 @@
+import { ReactNode } from 'react';
+import { Pressable } from 'react-native';
+
+import { HStack, VStack, Text, Badge } from '@suite-native/atoms';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+
+import { FavouriteIcon } from './FavouriteIcon';
+
+export type AssetListItemProps = {
+ assetName: ReactNode;
+ displaySymbol: ReactNode;
+ fiatRate: ReactNode;
+
+ icon: ReactNode;
+ priceChange?: ReactNode;
+ onPress: () => void;
+ isFavourite?: boolean;
+ onFavouritePress: () => void;
+};
+
+const vStackStyle = prepareNativeStyle(utils => ({
+ height: 68,
+ paddingVertical: utils.spacings.sp8,
+ flex: 1,
+ spacing: 0,
+}));
+
+export const TradeableAssetListItem = ({
+ assetName,
+ icon,
+ displaySymbol,
+ fiatRate,
+ priceChange,
+ onPress,
+ onFavouritePress,
+ isFavourite = false,
+}: AssetListItemProps) => {
+ const { applyStyle } = useNativeStyles();
+
+ return (
+
+
+ {icon}
+
+
+
+ {assetName}
+
+
+ {fiatRate}
+
+
+
+
+ {displaySymbol}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetsList.tsx b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetsList.tsx
new file mode 100644
index 00000000000..82c271e4ad5
--- /dev/null
+++ b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetsList.tsx
@@ -0,0 +1,29 @@
+import { NetworkSymbol } from '@suite-common/wallet-config';
+import { Card, Text, VStack } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+
+import { TradeableNetworkListItem } from './TradeableNetworkListItem';
+
+export type TradeableAssetsListProps = {
+ onItemSelected: (item: NetworkSymbol) => void;
+};
+
+export const TradeableAssetsList = ({ onItemSelected }: TradeableAssetsListProps) => (
+
+
+
+
+
+ onItemSelected('btc')}
+ onFavouritePress={() => {}}
+ />
+ onItemSelected('ada')}
+ onFavouritePress={() => {}}
+ />
+
+
+);
diff --git a/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetsSheet.tsx b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetsSheet.tsx
new file mode 100644
index 00000000000..842e1862a99
--- /dev/null
+++ b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableAssetsSheet.tsx
@@ -0,0 +1,39 @@
+import { NetworkSymbol } from '@suite-common/wallet-config';
+import { BottomSheet, VStack } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+
+import { PickerCloseButton } from '../general/PickerCloseButton';
+import { PickerHeader } from '../general/PickerHeader';
+import { TradeableAssetsList } from './TradeableAssetsList';
+
+export type TradeableAssetsSheetProps = {
+ isVisible: boolean;
+ onClose: () => void;
+ onAssetSelect: (symbol: NetworkSymbol) => void;
+};
+
+export const TradeableAssetsSheet = ({
+ isVisible,
+ onClose,
+ onAssetSelect,
+}: TradeableAssetsSheetProps) => {
+ const onAssetSelectCallback = (symbol: NetworkSymbol) => {
+ onAssetSelect(symbol);
+ onClose();
+ };
+
+ return (
+
+
+ }
+ withSearch
+ onSearchInputChange={() => {}}
+ withBackButton
+ />
+
+
+
+
+ );
+};
diff --git a/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableNetworkListItem.tsx b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableNetworkListItem.tsx
new file mode 100644
index 00000000000..5bb853cce2b
--- /dev/null
+++ b/suite-native/module-trading/src/components/TradeableAssetsSheet/TradeableNetworkListItem.tsx
@@ -0,0 +1,31 @@
+import { useFormatters } from '@suite-common/formatters';
+import { NetworkSymbol } from '@suite-common/wallet-config';
+import { RoundedIcon } from '@suite-native/atoms';
+
+import { TradeableAssetListItem } from './TradeableAssetListItem';
+
+export type TradeableNetworkListItemProps = {
+ symbol: NetworkSymbol;
+ onPress: () => void;
+ onFavouritePress: () => void;
+};
+
+export const TradeableNetworkListItem = ({
+ symbol,
+ onPress,
+ onFavouritePress,
+}: TradeableNetworkListItemProps) => {
+ const { DisplaySymbolFormatter, NetworkNameFormatter } = useFormatters();
+
+ return (
+ }
+ displaySymbol={}
+ priceChange="1.23%"
+ fiatRate="$ 123.45"
+ icon={}
+ onPress={onPress}
+ onFavouritePress={onFavouritePress}
+ />
+ );
+};
diff --git a/suite-native/module-trading/src/components/TradeableAssetsSheet/__tests__/FavouriteIcon.comp.test.tsx b/suite-native/module-trading/src/components/TradeableAssetsSheet/__tests__/FavouriteIcon.comp.test.tsx
new file mode 100644
index 00000000000..6e9147ebd34
--- /dev/null
+++ b/suite-native/module-trading/src/components/TradeableAssetsSheet/__tests__/FavouriteIcon.comp.test.tsx
@@ -0,0 +1,25 @@
+import { render, fireEvent } from '@suite-native/test-utils';
+
+import { FavouriteIcon } from '../FavouriteIcon';
+
+describe('FavouriteIcon', () => {
+ it('should has correct hint when marked as favourite', () => {
+ const { getByA11yHint } = render();
+ expect(getByA11yHint('Remove from favourites')).toBeDefined();
+ });
+
+ it('should has correct hint when not marked as favourite', () => {
+ const { getByA11yHint } = render();
+ expect(getByA11yHint('Add to favourites')).toBeDefined();
+ });
+
+ it('should call onPress callback', () => {
+ const pressSpy = jest.fn();
+ const { getByA11yHint } = render();
+
+ const button = getByA11yHint('Add to favourites');
+ fireEvent.press(button);
+
+ expect(pressSpy).toHaveBeenCalledWith();
+ });
+});
diff --git a/suite-native/module-trading/src/components/TradeableAssetsSheet/__tests__/TradeableNetworkListItem.comp.test.tsx b/suite-native/module-trading/src/components/TradeableAssetsSheet/__tests__/TradeableNetworkListItem.comp.test.tsx
new file mode 100644
index 00000000000..d39c7fd68d9
--- /dev/null
+++ b/suite-native/module-trading/src/components/TradeableAssetsSheet/__tests__/TradeableNetworkListItem.comp.test.tsx
@@ -0,0 +1,48 @@
+import { render, fireEvent } from '@suite-native/test-utils';
+
+import { TradeableNetworkListItem } from '../TradeableNetworkListItem';
+
+describe('TradeableNetworkListItem', () => {
+ it('should render with correct labels', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ expect(getByText('Bitcoin')).toBeDefined();
+ expect(getByText('BTC')).toBeDefined();
+ });
+
+ it('should call onPress callback when clicked', () => {
+ const onPress = jest.fn();
+ const { getByText } = render(
+ ,
+ );
+
+ fireEvent.press(getByText('BTC'));
+
+ expect(onPress).toHaveBeenCalledWith();
+ });
+
+ it('should call onFavouritePress when star is clicked', () => {
+ const onFavouritePress = jest.fn();
+ const { getByAccessibilityHint } = render(
+ ,
+ );
+
+ fireEvent.press(getByAccessibilityHint('Add to favourites'));
+
+ expect(onFavouritePress).toHaveBeenCalledWith();
+ });
+});
diff --git a/suite-native/module-trading/src/components/buy/AmountCard.tsx b/suite-native/module-trading/src/components/buy/AmountCard.tsx
new file mode 100644
index 00000000000..1cbb3a77928
--- /dev/null
+++ b/suite-native/module-trading/src/components/buy/AmountCard.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+
+import { Card, HStack } from '@suite-native/atoms';
+
+import { SelectTradeableAssetButton } from '../general/SelectTradeableAssetButton';
+import { TradeableAssetsSheet } from '../TradeableAssetsSheet/TradeableAssetsSheet';
+import { useTradeableAssetsSheetControls } from '../../hooks/useTradeableAssetsSheetControls';
+
+export const AmountCard = () => {
+ const {
+ isTradeableAssetsSheetVisible,
+ showTradeableAssetsSheet,
+ hideTradeableAssetsSheet,
+ selectedTradeableAsset,
+ setSelectedTradeableAsset,
+ } = useTradeableAssetsSheetControls();
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/suite-native/module-trading/src/components/buy/__tests__/AmountCard.comp.test.tsx b/suite-native/module-trading/src/components/buy/__tests__/AmountCard.comp.test.tsx
new file mode 100644
index 00000000000..d8fd822018e
--- /dev/null
+++ b/suite-native/module-trading/src/components/buy/__tests__/AmountCard.comp.test.tsx
@@ -0,0 +1,30 @@
+import { render, fireEvent } from '@suite-native/test-utils';
+
+import { AmountCard } from '../AmountCard';
+
+describe('AmountCard', () => {
+ it('should display Select coin button', () => {
+ const { getByText, queryByText } = render();
+
+ expect(getByText('Select coin')).toBeDefined();
+ expect(queryByText('Coins')).toBeNull();
+ });
+
+ it('should display AssetsSheet after button click', () => {
+ const { getByText } = render();
+
+ fireEvent.press(getByText('Select coin'));
+
+ expect(getByText('Coins')).toBeDefined();
+ });
+
+ it('should display selected network from AssetsSheet', () => {
+ const { getByText, queryByText } = render();
+
+ fireEvent.press(getByText('Select coin'));
+ fireEvent.press(getByText('BTC'));
+
+ expect(queryByText('Coins')).toBeNull();
+ expect(getByText('BTC')).toBeDefined();
+ });
+});
diff --git a/suite-native/module-trading/src/components/general/PickerHeader.tsx b/suite-native/module-trading/src/components/general/PickerHeader.tsx
index bfd2ca6ef14..133b2afea03 100644
--- a/suite-native/module-trading/src/components/general/PickerHeader.tsx
+++ b/suite-native/module-trading/src/components/general/PickerHeader.tsx
@@ -1,6 +1,7 @@
import { ReactNode } from 'react';
-import { HStack, SearchInput, Text, VStack } from '@suite-native/atoms';
+import { SearchInput, Text, VStack } from '@suite-native/atoms';
+import { useTranslate } from '@suite-native/intl';
type PickerHeaderSearchInputProps = {
onSearchInputChange: (value: string) => void;
@@ -11,40 +12,47 @@ type PickerHeaderSearchInputProps = {
export type PickerHeaderProps = {
title: ReactNode;
- children?: ReactNode;
-} & (
- | { isSearchInputVisible?: false }
- | ({ isSearchInputVisible: true } & PickerHeaderSearchInputProps)
-);
+ withBackButton?: boolean;
+ onBackButtonPress?: () => void;
+} & ({ withSearch?: false } | ({ withSearch: true } & PickerHeaderSearchInputProps));
const PickerHeaderSearchInput = ({
onSearchInputChange,
searchInputPlaceholder,
isSearchInputDisabled,
maxSearchInputLength,
-}: PickerHeaderSearchInputProps) => (
-
-);
+}: PickerHeaderSearchInputProps) => {
+ const { translate } = useTranslate();
+ let placeholder = searchInputPlaceholder;
+
+ if (!placeholder) {
+ placeholder = translate('moduleTrading.defaultSearchLabel');
+ }
+ return (
+
+ );
+};
+
+// TODO 16600 - drop general component and create TradableAssetsSheetHeader
export const PickerHeader = ({
title,
- isSearchInputVisible,
- children,
+ withSearch,
+ withBackButton,
+ onBackButtonPress,
...searchInputProps
}: PickerHeaderProps) => (
-
-
- {title}
-
- {children}
-
- {isSearchInputVisible && (
+
+ {title}
+
+
+ {withSearch && (
)}
diff --git a/suite-native/module-trading/src/components/general/SelectTradeableAssetButton.tsx b/suite-native/module-trading/src/components/general/SelectTradeableAssetButton.tsx
new file mode 100644
index 00000000000..d66a6f9f0a7
--- /dev/null
+++ b/suite-native/module-trading/src/components/general/SelectTradeableAssetButton.tsx
@@ -0,0 +1,29 @@
+import { NetworkSymbol } from '@suite-common/wallet-config';
+import { Button, buttonSchemeToColorsMap } from '@suite-native/atoms';
+import { Translation } from '@suite-native/intl';
+import { Icon } from '@suite-native/icons';
+
+import { TradeableNetworkButton } from './TradeableNetworkButton';
+
+export type SelectAssetButtonProps = {
+ onPress: () => void;
+ selectedAsset: NetworkSymbol | undefined;
+};
+
+export const SelectTradeableAssetButton = ({ onPress, selectedAsset }: SelectAssetButtonProps) => {
+ const { iconColor } = buttonSchemeToColorsMap.primary;
+
+ if (selectedAsset) {
+ return ;
+ }
+
+ return (
+ }
+ size="small"
+ >
+
+
+ );
+};
diff --git a/suite-native/module-trading/src/components/general/TradeableAssetButton.tsx b/suite-native/module-trading/src/components/general/TradeableAssetButton.tsx
new file mode 100644
index 00000000000..d5af09acdd2
--- /dev/null
+++ b/suite-native/module-trading/src/components/general/TradeableAssetButton.tsx
@@ -0,0 +1,63 @@
+import { ReactNode } from 'react';
+import { Pressable, StyleSheet } from 'react-native';
+
+import { LinearGradient } from 'expo-linear-gradient';
+
+import { hexToRgba } from '@suite-common/suite-utils';
+import { Text } from '@suite-native/atoms';
+import { Icon } from '@suite-native/icons';
+import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
+import { nativeSpacings } from '@trezor/theme';
+
+export type TradeableAssetButtonProps = {
+ icon: ReactNode;
+ children: ReactNode;
+ bgBaseColor: string;
+ caret?: boolean;
+ onPress: () => void;
+};
+
+const styles = StyleSheet.create({
+ button: {
+ height: 36,
+ padding: nativeSpacings.sp4,
+ paddingRight: nativeSpacings.sp12,
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ gap: nativeSpacings.sp8,
+ },
+});
+
+const gradientBackgroundStyle = prepareNativeStyle(({ borders }) => ({
+ borderRadius: borders.radii.round,
+ borderWidth: borders.widths.small,
+ borderColor: 'rgba(0, 0, 0, 0.06)',
+}));
+
+export const TradeableAssetButton = ({
+ bgBaseColor,
+ caret,
+ icon,
+ children,
+ onPress,
+}: TradeableAssetButtonProps) => {
+ const { applyStyle } = useNativeStyles();
+
+ return (
+
+
+ {icon}
+
+ {children}
+
+ {caret && }
+
+
+ );
+};
diff --git a/suite-native/module-trading/src/components/general/TradeableNetworkButton.tsx b/suite-native/module-trading/src/components/general/TradeableNetworkButton.tsx
new file mode 100644
index 00000000000..f95142e812e
--- /dev/null
+++ b/suite-native/module-trading/src/components/general/TradeableNetworkButton.tsx
@@ -0,0 +1,29 @@
+import { useFormatters } from '@suite-common/formatters';
+import { NetworkSymbol } from '@suite-common/wallet-config';
+import { CryptoIcon } from '@suite-native/icons';
+import { useNativeStyles } from '@trezor/styles';
+
+import { TradeableAssetButton } from './TradeableAssetButton';
+
+export type TradeableNetworkButtonProps = {
+ symbol: NetworkSymbol;
+ onPress: () => void;
+ caret?: boolean;
+};
+
+export const TradeableNetworkButton = ({ symbol, onPress, caret }: TradeableNetworkButtonProps) => {
+ const { DisplaySymbolFormatter } = useFormatters();
+ const { utils } = useNativeStyles();
+ const baseSymbolColor = utils.coinsColors[symbol];
+
+ return (
+ }
+ >
+
+
+ );
+};
diff --git a/suite-native/module-trading/src/components/general/__tests__/PickerHeader.comp.test.tsx b/suite-native/module-trading/src/components/general/__tests__/PickerHeader.comp.test.tsx
index 0b53d7524b5..51b0ddc538d 100644
--- a/suite-native/module-trading/src/components/general/__tests__/PickerHeader.comp.test.tsx
+++ b/suite-native/module-trading/src/components/general/__tests__/PickerHeader.comp.test.tsx
@@ -1,30 +1,19 @@
import { render, fireEvent } from '@suite-native/test-utils';
-import { Text } from '@suite-native/atoms';
import { PickerHeader } from '../PickerHeader';
describe('PickerHeader', () => {
- it('should render without children', () => {
+ it('should render with minimum props', () => {
const { getByText } = render();
expect(getByText('Title')).toBeDefined();
});
- it('should render with children', () => {
- const { getByText } = render(
-
- Child
- ,
- );
- expect(getByText('Title')).toBeDefined();
- expect(getByText('Child')).toBeDefined();
- });
-
- it('should render search input when `isSearchInputVisible`', () => {
+ it('should render search input when `withSearch` is specified', () => {
const searchInputSpy = jest.fn();
const { getByPlaceholderText } = render(
,
@@ -35,4 +24,12 @@ describe('PickerHeader', () => {
expect(searchInputSpy).toHaveBeenCalledWith('search');
});
+
+ it('should render search input with `Search` placeholder by default', () => {
+ const { getByPlaceholderText } = render(
+ ,
+ );
+
+ expect(getByPlaceholderText('Search')).toBeDefined();
+ });
});
diff --git a/suite-native/module-trading/src/components/general/__tests__/SelectTradeableAssetButton.comp.test.tsx b/suite-native/module-trading/src/components/general/__tests__/SelectTradeableAssetButton.comp.test.tsx
new file mode 100644
index 00000000000..d9c1787f556
--- /dev/null
+++ b/suite-native/module-trading/src/components/general/__tests__/SelectTradeableAssetButton.comp.test.tsx
@@ -0,0 +1,22 @@
+import { render } from '@suite-native/test-utils';
+
+import { SelectTradeableAssetButton } from '../SelectTradeableAssetButton';
+
+describe('SelectTradeableAssetButton', () => {
+ it('should render "Select coin" when no network is selected', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ expect(getByText('Select coin')).toBeDefined();
+ });
+
+ it('should render TradeableAssetButton when network is selected', () => {
+ const { queryByText } = render(
+ ,
+ );
+
+ expect(queryByText('Select coin')).toBeNull();
+ expect(queryByText('ADA')).toBeDefined();
+ });
+});
diff --git a/suite-native/module-trading/src/components/general/__tests__/TradeableNetworkButton.comp.test.tsx b/suite-native/module-trading/src/components/general/__tests__/TradeableNetworkButton.comp.test.tsx
new file mode 100644
index 00000000000..18b4b21f34a
--- /dev/null
+++ b/suite-native/module-trading/src/components/general/__tests__/TradeableNetworkButton.comp.test.tsx
@@ -0,0 +1,21 @@
+import { render, fireEvent } from '@suite-native/test-utils';
+
+import { TradeableNetworkButton } from '../TradeableNetworkButton';
+
+describe('TradeableNetworkButton', () => {
+ it('should render display name of given symbol', () => {
+ const { getByText } = render();
+
+ expect(getByText('BTC')).toBeDefined();
+ });
+
+ it('should call onPress callback', () => {
+ const pressSpy = jest.fn();
+ const { getByText } = render();
+
+ const button = getByText('BTC');
+ fireEvent.press(button);
+
+ expect(pressSpy).toHaveBeenCalledWith();
+ });
+});
diff --git a/suite-native/module-trading/src/hooks/__tests__/useAssetsSheetControls.test.ts b/suite-native/module-trading/src/hooks/__tests__/useAssetsSheetControls.test.ts
new file mode 100644
index 00000000000..b3959e53ea5
--- /dev/null
+++ b/suite-native/module-trading/src/hooks/__tests__/useAssetsSheetControls.test.ts
@@ -0,0 +1,52 @@
+import { renderHook, act } from '@suite-native/test-utils';
+
+import { useTradeableAssetsSheetControls } from '../useTradeableAssetsSheetControls';
+
+describe('useTradeableAssetsSheetControls', () => {
+ describe('isTokensSheetVisible', () => {
+ it('should be false by default', () => {
+ const { result } = renderHook(() => useTradeableAssetsSheetControls());
+
+ expect(result.current.isTradeableAssetsSheetVisible).toBe(false);
+ });
+
+ it('should be true after showTokensSheet call', () => {
+ const { result } = renderHook(() => useTradeableAssetsSheetControls());
+
+ act(() => {
+ result.current.showTradeableAssetsSheet();
+ });
+
+ expect(result.current.isTradeableAssetsSheetVisible).toBe(true);
+ });
+
+ it('should be false after hideTokensSheet call', () => {
+ const { result } = renderHook(() => useTradeableAssetsSheetControls());
+
+ act(() => {
+ result.current.showTradeableAssetsSheet();
+ result.current.hideTradeableAssetsSheet();
+ });
+
+ expect(result.current.isTradeableAssetsSheetVisible).toBe(false);
+ });
+ });
+
+ describe('selectedAsset', () => {
+ it('should be undefined by default', () => {
+ const { result } = renderHook(() => useTradeableAssetsSheetControls());
+
+ expect(result.current.selectedTradeableAsset).toBeUndefined();
+ });
+
+ it('should be set after setSelectedNetwork call', () => {
+ const { result } = renderHook(() => useTradeableAssetsSheetControls());
+
+ act(() => {
+ result.current.setSelectedTradeableAsset('btc');
+ });
+
+ expect(result.current.selectedTradeableAsset).toBe('btc');
+ });
+ });
+});
diff --git a/suite-native/module-trading/src/hooks/useTradeableAssetsSheetControls.ts b/suite-native/module-trading/src/hooks/useTradeableAssetsSheetControls.ts
new file mode 100644
index 00000000000..be659d5b326
--- /dev/null
+++ b/suite-native/module-trading/src/hooks/useTradeableAssetsSheetControls.ts
@@ -0,0 +1,26 @@
+import { useState } from 'react';
+
+import { NetworkSymbol } from '@suite-common/wallet-config';
+
+export const useTradeableAssetsSheetControls = () => {
+ const [isTradeableAssetsSheetVisible, setIsTradeableAssetsSheetVisible] = useState(false);
+ const [selectedTradeableAsset, setSelectedTradeableAsset] = useState<
+ undefined | NetworkSymbol
+ >();
+
+ const showTradeableAssetsSheet = () => {
+ setIsTradeableAssetsSheetVisible(true);
+ };
+
+ const hideTradeableAssetsSheet = () => {
+ setIsTradeableAssetsSheetVisible(false);
+ };
+
+ return {
+ isTradeableAssetsSheetVisible,
+ showTradeableAssetsSheet,
+ hideTradeableAssetsSheet,
+ selectedTradeableAsset,
+ setSelectedTradeableAsset,
+ };
+};
diff --git a/suite-native/module-trading/src/screens/TradingScreen.tsx b/suite-native/module-trading/src/screens/TradingScreen.tsx
index 7c6c8996fec..de0b73a7914 100644
--- a/suite-native/module-trading/src/screens/TradingScreen.tsx
+++ b/suite-native/module-trading/src/screens/TradingScreen.tsx
@@ -1,13 +1,14 @@
import React from 'react';
import { Screen } from '@suite-native/navigation';
-import { Card, Text } from '@suite-native/atoms';
+import { Text } from '@suite-native/atoms';
import { DeviceManagerScreenHeader } from '@suite-native/device-manager';
+import { AmountCard } from '../components/buy/AmountCard';
+
export const TradingScreen = () => (
}>
-
- Trading placeholder
-
+ Trading placeholder
+
);
diff --git a/suite-native/test-utils/src/BasicProvider.tsx b/suite-native/test-utils/src/BasicProvider.tsx
index 981f6dd9643..c664608fa5c 100644
--- a/suite-native/test-utils/src/BasicProvider.tsx
+++ b/suite-native/test-utils/src/BasicProvider.tsx
@@ -1,8 +1,9 @@
-import { ReactNode } from 'react';
+import { ReactNode, useMemo } from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { NavigationContainer } from '@react-navigation/native';
+import { FormatterProvider } from '@suite-common/formatters';
import { createRenderer, StylesProvider } from '@trezor/styles';
import { prepareNativeTheme } from '@trezor/theme';
import { IntlProvider } from '@suite-native/intl';
@@ -14,12 +15,26 @@ type ProviderProps = {
const renderer = createRenderer();
const theme = prepareNativeTheme({ colorVariant: 'standard' });
-export const BasicProvider = ({ children }: ProviderProps) => (
-
-
-
- {children}
-
-
-
-);
+export const BasicProvider = ({ children }: ProviderProps) => {
+ const formattersConfig = useMemo(
+ () => ({
+ locale: 'en' as const,
+ fiatCurrency: 'usd' as const,
+ bitcoinAmountUnit: 0,
+ is24HourFormat: true,
+ }),
+ [],
+ );
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+};
diff --git a/yarn.lock b/yarn.lock
index f25cccbb642..af55e4ed21a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10941,6 +10941,7 @@ __metadata:
"@reduxjs/toolkit": "npm:1.9.5"
"@suite-native/navigation": "workspace:*"
"@suite-native/test-utils": "workspace:*"
+ expo-linear-gradient: "npm:^14.0.1"
react: "npm:18.2.0"
react-native: "npm:0.76.1"
languageName: unknown