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 ( + + ); +}; 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