From 4542ca90f2123da310f550d867b39ee712383384 Mon Sep 17 00:00:00 2001 From: banklesss <105349292+banklesss@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:04:04 +0200 Subject: [PATCH 1/2] feature(wallet-mobile): token details dialog for new tx review (#3672) --- .../.storybook/storybook.requires.js | 2 +- .../MediaPreview/MediaPreview.tsx | 6 +- .../SimpleTab/SimpleTab.stories.tsx | 26 + .../SimpleTab/SimpleTab.tsx} | 4 +- .../DAppExplorerTabItem.stories.tsx | 26 - .../SelectDappFromListScreen.tsx | 6 +- .../MediaDetailsScreen/MediaDetailsScreen.tsx | 2 +- .../common/MediaGallery/MediaGallery.tsx | 2 +- .../DashboardNFTsList/DashboardNFTsList.tsx | 2 +- .../ZoomMediaImageScreen.tsx | 2 +- .../{CollapsibleSection.tsx => Accordion.tsx} | 14 +- .../src/features/ReviewTx/common/Address.tsx | 73 -- .../features/ReviewTx/common/CopiableText.tsx | 37 + .../features/ReviewTx/common/TokenDetails.tsx | 396 ++++++++ .../features/ReviewTx/common/TokenItem.tsx | 34 +- .../ReviewTx/common/hooks/useFormattedTx.tsx | 8 +- .../ReviewTx/common/hooks/useStrings.tsx | 57 +- .../src/features/ReviewTx/common/types.ts | 877 +----------------- .../ReviewTxScreen/Overview/OverviewTab.tsx | 70 +- .../ReviewTxScreen/ReviewTxScreen.tsx | 26 +- .../ReviewTxScreen/UTxOs/UTxOsTab.tsx | 60 +- .../src/kernel/i18n/locales/en-US.json | 15 +- .../messages/src/WalletNavigator.json | 96 +- .../MediaDetailsScreen.json | 44 +- .../ReviewTx/common/hooks/useStrings.json | 279 ++++-- 25 files changed, 999 insertions(+), 1165 deletions(-) rename apps/wallet-mobile/src/{features/Portfolio/common => components}/MediaPreview/MediaPreview.tsx (90%) create mode 100644 apps/wallet-mobile/src/components/SimpleTab/SimpleTab.stories.tsx rename apps/wallet-mobile/src/{features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.tsx => components/SimpleTab/SimpleTab.tsx} (89%) delete mode 100644 apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.stories.tsx rename apps/wallet-mobile/src/features/ReviewTx/common/{CollapsibleSection.tsx => Accordion.tsx} (80%) delete mode 100644 apps/wallet-mobile/src/features/ReviewTx/common/Address.tsx create mode 100644 apps/wallet-mobile/src/features/ReviewTx/common/CopiableText.tsx create mode 100644 apps/wallet-mobile/src/features/ReviewTx/common/TokenDetails.tsx diff --git a/apps/wallet-mobile/.storybook/storybook.requires.js b/apps/wallet-mobile/.storybook/storybook.requires.js index a41e78585f..c041b07b66 100644 --- a/apps/wallet-mobile/.storybook/storybook.requires.js +++ b/apps/wallet-mobile/.storybook/storybook.requires.js @@ -88,6 +88,7 @@ const getStories = () => { "./src/components/PairedBalance/PairedBalance.stories.tsx": require("../src/components/PairedBalance/PairedBalance.stories.tsx"), "./src/components/PressableIcon/PressableIcon.stories.tsx": require("../src/components/PressableIcon/PressableIcon.stories.tsx"), "./src/components/ShareQRCodeCard/ShareQRCodeCard.stories.tsx": require("../src/components/ShareQRCodeCard/ShareQRCodeCard.stories.tsx"), + "./src/components/SimpleTab/SimpleTab.stories.tsx": require("../src/components/SimpleTab/SimpleTab.stories.tsx"), "./src/components/SomethingWentWrong/SomethingWentWrong.stories.tsx": require("../src/components/SomethingWentWrong/SomethingWentWrong.stories.tsx"), "./src/components/StepperProgress/StepperProgress.stories.tsx": require("../src/components/StepperProgress/StepperProgress.stories.tsx"), "./src/components/TextInput/TextInput.stories.tsx": require("../src/components/TextInput/TextInput.stories.tsx"), @@ -121,7 +122,6 @@ const getStories = () => { "./src/features/Discover/useCases/SearchDappInBrowser/SearchDappInBrowserScreen.stories.tsx": require("../src/features/Discover/useCases/SearchDappInBrowser/SearchDappInBrowserScreen.stories.tsx"), "./src/features/Discover/useCases/SelectDappFromList/CountDAppsAvailable/CountDAppsAvailable.stories.tsx": require("../src/features/Discover/useCases/SelectDappFromList/CountDAppsAvailable/CountDAppsAvailable.stories.tsx"), "./src/features/Discover/useCases/SelectDappFromList/CountDAppsConnected/CountDAppsConnected.stories.tsx": require("../src/features/Discover/useCases/SelectDappFromList/CountDAppsConnected/CountDAppsConnected.stories.tsx"), - "./src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.stories.tsx": require("../src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.stories.tsx"), "./src/features/Discover/useCases/SelectDappFromList/DAppListItem/DAppItemSkeleton.stories.tsx": require("../src/features/Discover/useCases/SelectDappFromList/DAppListItem/DAppItemSkeleton.stories.tsx"), "./src/features/Discover/useCases/SelectDappFromList/DAppListItem/DAppListItem.stories.tsx": require("../src/features/Discover/useCases/SelectDappFromList/DAppListItem/DAppListItem.stories.tsx"), "./src/features/Discover/useCases/SelectDappFromList/DAppTypes/DAppTypes.stories.tsx": require("../src/features/Discover/useCases/SelectDappFromList/DAppTypes/DAppTypes.stories.tsx"), diff --git a/apps/wallet-mobile/src/features/Portfolio/common/MediaPreview/MediaPreview.tsx b/apps/wallet-mobile/src/components/MediaPreview/MediaPreview.tsx similarity index 90% rename from apps/wallet-mobile/src/features/Portfolio/common/MediaPreview/MediaPreview.tsx rename to apps/wallet-mobile/src/components/MediaPreview/MediaPreview.tsx index 8dacd28a7c..36f43371e1 100644 --- a/apps/wallet-mobile/src/features/Portfolio/common/MediaPreview/MediaPreview.tsx +++ b/apps/wallet-mobile/src/components/MediaPreview/MediaPreview.tsx @@ -5,9 +5,9 @@ import React from 'react' import {ImageStyle, StyleSheet, View} from 'react-native' import SkeletonPlaceholder from 'react-native-skeleton-placeholder' -import placeholderLight from '../../../../assets/img/nft-placeholder.png' -import placeholderDark from '../../../../assets/img/nft-placeholder-dark.png' -import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet' +import placeholderLight from '../../assets/img/nft-placeholder.png' +import placeholderDark from '../../assets/img/nft-placeholder-dark.png' +import {useSelectedWallet} from '../../features/WalletManager/common/hooks/useSelectedWallet' type MediaPreviewProps = { info: Portfolio.Token.Info diff --git a/apps/wallet-mobile/src/components/SimpleTab/SimpleTab.stories.tsx b/apps/wallet-mobile/src/components/SimpleTab/SimpleTab.stories.tsx new file mode 100644 index 0000000000..549ead2f95 --- /dev/null +++ b/apps/wallet-mobile/src/components/SimpleTab/SimpleTab.stories.tsx @@ -0,0 +1,26 @@ +import {action} from '@storybook/addon-actions' +import {storiesOf} from '@storybook/react-native' +import * as React from 'react' +import {View} from 'react-native' + +import {SimpleTab} from './SimpleTab' + +storiesOf('SimpleTab', module) + .add('Active', () => ) + .add('Inactive', () => ) + +const Active = () => { + return ( + + + + ) +} + +const Inactive = () => { + return ( + + + + ) +} diff --git a/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.tsx b/apps/wallet-mobile/src/components/SimpleTab/SimpleTab.tsx similarity index 89% rename from apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.tsx rename to apps/wallet-mobile/src/components/SimpleTab/SimpleTab.tsx index e140c97e43..e72787c55d 100644 --- a/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.tsx +++ b/apps/wallet-mobile/src/components/SimpleTab/SimpleTab.tsx @@ -8,7 +8,7 @@ type Props = { onPress: () => void } -export const DAppExplorerTabItem = ({name, onPress, isActive}: Props) => { +export const SimpleTab = ({name, onPress, isActive}: Props) => { const {styles} = useStyles() return ( @@ -34,5 +34,5 @@ const useStyles = () => { }, }) - return {styles} + return {styles} as const } diff --git a/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.stories.tsx b/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.stories.tsx deleted file mode 100644 index 626ac02ba0..0000000000 --- a/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/DAppExplorerTabItem/DAppExplorerTabItem.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import {action} from '@storybook/addon-actions' -import {storiesOf} from '@storybook/react-native' -import * as React from 'react' -import {View} from 'react-native' - -import {DAppExplorerTabItem} from './DAppExplorerTabItem' - -storiesOf('Discover DAppExplorerTabItem', module) - .add('connected', () => ) - .add('recommended', () => ) - -const Connected = () => { - return ( - - - - ) -} - -const Recommended = () => { - return ( - - - - ) -} diff --git a/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/SelectDappFromListScreen.tsx b/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/SelectDappFromListScreen.tsx index a21de2ae63..39a2ee4b6c 100644 --- a/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/SelectDappFromListScreen.tsx +++ b/apps/wallet-mobile/src/features/Discover/useCases/SelectDappFromList/SelectDappFromListScreen.tsx @@ -3,6 +3,7 @@ import {useTheme} from '@yoroi/theme' import * as React from 'react' import {FlatList, StyleSheet, View} from 'react-native' +import {SimpleTab} from '../../../../components/SimpleTab/SimpleTab' import {Spacer} from '../../../../components/Spacer/Spacer' import {useMetrics} from '../../../../kernel/metrics/metricsManager' import {useSearch, useSearchOnNavBar} from '../../../Search/SearchContext' @@ -13,7 +14,6 @@ import {useDAppsConnected} from '../../common/useDAppsConnected' import {useStrings} from '../../common/useStrings' import {CountDAppsAvailable} from './CountDAppsAvailable/CountDAppsAvailable' import {CountDAppsConnected} from './CountDAppsConnected/CountDAppsConnected' -import {DAppExplorerTabItem} from './DAppExplorerTabItem/DAppExplorerTabItem' import {DAppListItem} from './DAppListItem/DAppListItem' import {DAppTypes} from './DAppTypes/DAppTypes' import {WelcomeDAppModal} from './WelcomeDAppModal' @@ -153,13 +153,13 @@ const HeaderControl = ({ <> {hasConnectedDapps && ( - onTabChange(DAppTabs.connected)} /> - onTabChange(DAppTabs.recommended)} diff --git a/apps/wallet-mobile/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.tsx b/apps/wallet-mobile/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.tsx index bcd4e24f25..f2cff3c32c 100644 --- a/apps/wallet-mobile/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.tsx +++ b/apps/wallet-mobile/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.tsx @@ -20,6 +20,7 @@ import {Boundary} from '../../../../components/Boundary/Boundary' import {CopyButton} from '../../../../components/CopyButton' import {FadeIn} from '../../../../components/FadeIn' import {Hr} from '../../../../components/Hr/Hr' +import {MediaPreview} from '../../../../components/MediaPreview/MediaPreview' import {Spacer} from '../../../../components/Spacer/Spacer' import {Tab, TabPanel, TabPanels, Tabs} from '../../../../components/Tabs/Tabs' import {Text} from '../../../../components/Text' @@ -28,7 +29,6 @@ import {useMetrics} from '../../../../kernel/metrics/metricsManager' import {NftRoutes} from '../../../../kernel/navigation' import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet' import {usePortfolioImageInvalidate} from '../hooks/usePortfolioImage' -import {MediaPreview} from '../MediaPreview/MediaPreview' import {useNavigateTo} from '../navigation' export const MediaDetailsScreen = () => { diff --git a/apps/wallet-mobile/src/features/Portfolio/common/MediaGallery/MediaGallery.tsx b/apps/wallet-mobile/src/features/Portfolio/common/MediaGallery/MediaGallery.tsx index 149d062a5f..273b193caf 100644 --- a/apps/wallet-mobile/src/features/Portfolio/common/MediaGallery/MediaGallery.tsx +++ b/apps/wallet-mobile/src/features/Portfolio/common/MediaGallery/MediaGallery.tsx @@ -4,8 +4,8 @@ import {Balance, Portfolio} from '@yoroi/types' import * as React from 'react' import {StyleSheet, Text, TouchableOpacity, useWindowDimensions, View} from 'react-native' +import {MediaPreview} from '../../../../components/MediaPreview/MediaPreview' import {Spacer} from '../../../../components/Spacer/Spacer' -import {MediaPreview} from '../MediaPreview/MediaPreview' type Props = { amounts: ReadonlyArray diff --git a/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioDashboard/DashboardNFTsList/DashboardNFTsList.tsx b/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioDashboard/DashboardNFTsList/DashboardNFTsList.tsx index d60950805a..a3e6402aa3 100644 --- a/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioDashboard/DashboardNFTsList/DashboardNFTsList.tsx +++ b/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioDashboard/DashboardNFTsList/DashboardNFTsList.tsx @@ -5,12 +5,12 @@ import {FlatList, Image, StyleSheet, Text, TouchableOpacity, useWindowDimensions import placeholderLight from '../../../../../assets/img/nft-placeholder.png' import placeholderDark from '../../../../../assets/img/nft-placeholder-dark.png' import {Icon} from '../../../../../components/Icon' +import {MediaPreview} from '../../../../../components/MediaPreview/MediaPreview' import {Spacer} from '../../../../../components/Spacer/Spacer' import {useSelectedWallet} from '../../../../WalletManager/common/hooks/useSelectedWallet' import {useNavigateTo} from '../../../common/hooks/useNavigateTo' import {usePortfolioBalances} from '../../../common/hooks/usePortfolioBalances' import {useStrings} from '../../../common/hooks/useStrings' -import {MediaPreview} from '../../../common/MediaPreview/MediaPreview' export const DashboardNFTsList = () => { const {styles, cardItemWidth} = useStyles() diff --git a/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioTokensList/PortfolioWalletTokenList/ListMediaGalleryScreen/ZoomMediaImageScreen.tsx b/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioTokensList/PortfolioWalletTokenList/ListMediaGalleryScreen/ZoomMediaImageScreen.tsx index fc4fdb9071..b655b30e32 100644 --- a/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioTokensList/PortfolioWalletTokenList/ListMediaGalleryScreen/ZoomMediaImageScreen.tsx +++ b/apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioTokensList/PortfolioWalletTokenList/ListMediaGalleryScreen/ZoomMediaImageScreen.tsx @@ -6,11 +6,11 @@ import {StyleSheet, useWindowDimensions, View} from 'react-native' import ViewTransformer from 'react-native-easy-view-transformer' import {FadeIn} from '../../../../../../components/FadeIn' +import {MediaPreview} from '../../../../../../components/MediaPreview/MediaPreview' import {useMetrics} from '../../../../../../kernel/metrics/metricsManager' import {NftRoutes, useParams} from '../../../../../../kernel/navigation' import {isEmptyString} from '../../../../../../kernel/utils' import {useSelectedWallet} from '../../../../../WalletManager/common/hooks/useSelectedWallet' -import {MediaPreview} from '../../../../common/MediaPreview/MediaPreview' type Params = NftRoutes['nft-details'] diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/CollapsibleSection.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/Accordion.tsx similarity index 80% rename from apps/wallet-mobile/src/features/ReviewTx/common/CollapsibleSection.tsx rename to apps/wallet-mobile/src/features/ReviewTx/common/Accordion.tsx index 048ac41d0e..92e7bfb3ac 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/common/CollapsibleSection.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/common/Accordion.tsx @@ -4,7 +4,7 @@ import {Animated, LayoutAnimation, StyleSheet, Text, TouchableOpacity, View} fro import {Icon} from '../../../components/Icon' -export const CollapsibleSection = ({label, children}: {label: string; children: React.ReactNode}) => { +export const Accordion = ({label, children}: {label: string; children: React.ReactNode}) => { const {styles, colors} = useStyles() const [isOpen, setIsOpen] = React.useState(false) const animatedHeight = React.useRef(new Animated.Value(0)).current @@ -20,14 +20,12 @@ export const CollapsibleSection = ({label, children}: {label: string; children: } return ( - <> - + + {label} - - - - + + {children} - + ) } diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/Address.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/Address.tsx deleted file mode 100644 index 5279b07df9..0000000000 --- a/apps/wallet-mobile/src/features/ReviewTx/common/Address.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import {useTheme} from '@yoroi/theme' -import * as React from 'react' -import {StyleSheet, Text, TextStyle, TouchableOpacity, View} from 'react-native' - -import {Icon} from '../../../components/Icon' -import {Space} from '../../../components/Space/Space' -import {useCopy} from '../../../hooks/useCopy' - -export const Address = ({ - address, - index, - textStyle, - multiline = false, -}: { - address: string - index?: number - textStyle?: TextStyle - multiline?: boolean -}) => { - const {styles, colors} = useStyles() - const [, copy] = useCopy() - - return ( - - - {address} - - - {index !== undefined && ( - <> - - - {`#${index}`} - - - - )} - - copy(address)} activeOpacity={0.5}> - - - - ) -} - -const useStyles = () => { - const {atoms, color} = useTheme() - const styles = StyleSheet.create({ - address: { - ...atoms.flex_row, - ...atoms.justify_between, - }, - addressText: { - ...atoms.flex_1, - ...atoms.body_2_md_regular, - color: color.text_gray_medium, - }, - index: { - ...atoms.body_2_md_medium, - color: color.text_gray_medium, - }, - }) - - const colors = { - copy: color.gray_900, - } - - return {styles, colors} as const -} diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/CopiableText.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/CopiableText.tsx new file mode 100644 index 0000000000..b09812b5a6 --- /dev/null +++ b/apps/wallet-mobile/src/features/ReviewTx/common/CopiableText.tsx @@ -0,0 +1,37 @@ +import {useTheme} from '@yoroi/theme' +import * as React from 'react' +import {StyleSheet, TouchableOpacity, View} from 'react-native' + +import {Icon} from '../../../components/Icon' +import {useCopy} from '../../../hooks/useCopy' + +export const CopiableText = ({children, textToCopy}: {children: React.ReactNode; textToCopy: string}) => { + const {styles, colors} = useStyles() + const [, copy] = useCopy() + + return ( + + {children} + + copy(textToCopy)} activeOpacity={0.5}> + + + + ) +} + +const useStyles = () => { + const {atoms, color} = useTheme() + const styles = StyleSheet.create({ + text: { + ...atoms.flex_row, + ...atoms.justify_between, + }, + }) + + const colors = { + copy: color.gray_900, + } + + return {styles, colors} as const +} diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/TokenDetails.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/TokenDetails.tsx new file mode 100644 index 0000000000..6f590decfa --- /dev/null +++ b/apps/wallet-mobile/src/features/ReviewTx/common/TokenDetails.tsx @@ -0,0 +1,396 @@ +import {usePortfolioTokenDiscovery} from '@yoroi/portfolio' +import {useTheme} from '@yoroi/theme' +import {Portfolio} from '@yoroi/types' +import * as React from 'react' +import {Linking, ScrollView, StyleSheet, Text, TouchableOpacity, View} from 'react-native' + +import {Icon} from '../../../components/Icon' +import {MediaPreview} from '../../../components/MediaPreview/MediaPreview' +import {SimpleTab} from '../../../components/SimpleTab/SimpleTab' +import {Space} from '../../../components/Space/Space' +import {useCopy} from '../../../hooks/useCopy' +import {time} from '../../../kernel/constants' +import {isEmptyString} from '../../../kernel/utils' +import {useSelectedWallet} from '../../WalletManager/common/hooks/useSelectedWallet' +import {CopiableText} from './CopiableText' +import {useStrings} from './hooks/useStrings' + +export const TokenDetails = ({tokenInfo}: {tokenInfo: Portfolio.Token.Info}) => { + const {styles} = useStyles() + + return ( + +
+ + + + + + ) +} + +const Header = ({info}: {info: Portfolio.Token.Info}) => { + const {styles} = useStyles() + const [policyId, assetName] = info.id.split('.') + + const title = !isEmptyString(info.ticker) ? info.ticker : !isEmptyString(info.name) ? info.name : '' + + return ( + + + + + + {!isEmptyString(title) && {title}} + + {`(${assetName})`} + + + + + + + + + + ) +} + +const Info = ({info}: {info: Portfolio.Token.Info}) => { + const {styles} = useStyles() + const strings = useStrings() + const {wallet} = useSelectedWallet() + const [activeTab, setActiveTab] = React.useState<'overview' | 'json'>('overview') + + const {tokenDiscovery} = usePortfolioTokenDiscovery( + { + id: info.id, + network: wallet.networkManager.network, + getTokenDiscovery: wallet.networkManager.tokenManager.api.tokenDiscovery, + }, + { + staleTime: time.session, + }, + ) + + return ( + + + setActiveTab('overview')} + isActive={activeTab === 'overview'} + /> + + setActiveTab('json')} isActive={activeTab === 'json'} /> + + + + + {/* ↓↓↓ TABS CONTENT ↓↓↓ */} + + + + + + ) +} + +const Json = ({discovery, isActive}: {discovery?: Portfolio.Token.Discovery; isActive: boolean}) => { + const {styles, colors} = useStyles() + const strings = useStrings() + const [, copy] = useCopy() + + if (!isActive || !discovery) return null + + const stringifiedMetadata = JSON.stringify(discovery.originalMetadata, null, 2) + + return ( + + + {strings.metadata} + + copy(stringifiedMetadata)} activeOpacity={0.5}> + + + + + + + + {stringifiedMetadata} + + + ) +} + +const Overview = ({ + info, + discovery, + isActive, +}: { + info: Portfolio.Token.Info + discovery?: Portfolio.Token.Discovery + isActive: boolean +}) => { + if (!isActive) return null + + if (info.type === 'ft') { + return ( + + + + + + + + + + + + ) + } + return ( + + + + + + + + ) +} + +const PolicyId = ({policyId}: {policyId: string}) => { + const {styles} = useStyles() + const strings = useStrings() + + if (isEmptyString(policyId)) return null + + return ( + + {strings.policyId} + + + + + + {policyId} + + + + ) +} + +const Fingerprint = ({info}: {info: Portfolio.Token.Info}) => { + const {styles} = useStyles() + const strings = useStrings() + + if (isEmptyString(info.fingerprint)) return null + + return ( + + {strings.fingerprint} + + + + + + {info.fingerprint} + + + + ) +} + +const Name = ({info}: {info: Portfolio.Token.Info}) => { + const {styles} = useStyles() + const strings = useStrings() + + if (isEmptyString(info.name)) return null + + return ( + + {strings.name} + + {info.name} + + ) +} + +const TokenSupply = ({discovery}: {discovery?: Portfolio.Token.Discovery}) => { + const {styles} = useStyles() + const strings = useStrings() + + if (!discovery || isEmptyString(discovery.supply)) return null + + return ( + + + + + {strings.tokenSupply} + + {discovery.supply} + + + ) +} + +const Symbol = ({info}: {info: Portfolio.Token.Info}) => { + const {styles} = useStyles() + const strings = useStrings() + + if (isEmptyString(info.ticker)) return null + + return ( + + + + + {strings.symbol} + + {info.ticker} + + + ) +} + +const Description = ({info}: {info: Portfolio.Token.Info}) => { + const {styles} = useStyles() + const strings = useStrings() + + if (isEmptyString(info.description)) return null + + return ( + + + + {strings.description} + + {info.description} + + ) +} + +const Links = ({info}: {info: Portfolio.Token.Info}) => { + const {styles} = useStyles() + const {wallet} = useSelectedWallet() + const strings = useStrings() + + const handleOpenLink = async (direction: 'cardanoscan' | 'adaex') => { + if (info == null) return + if (direction === 'cardanoscan') { + await Linking.openURL(wallet.networkManager.explorers.cardanoscan.token(info.id)) + } else { + await Linking.openURL(wallet.networkManager.explorers.cexplorer.token(info.id)) + } + } + + return ( + + + + {strings.details} + + + handleOpenLink('cardanoscan')}> + Cardanoscan + + + handleOpenLink('adaex')}> + Adaex + + + + ) +} + +const Row = ({children}: {children: React.ReactNode}) => { + const {styles} = useStyles() + return {children} +} + +const useStyles = () => { + const {atoms, color} = useTheme() + + const styles = StyleSheet.create({ + root: { + ...atoms.flex_1, + ...atoms.px_lg, + }, + header: { + ...atoms.align_center, + }, + headerText: { + ...atoms.body_1_lg_medium, + ...atoms.text_center, + color: color.text_gray_medium, + maxWidth: 300, + }, + row: { + ...atoms.flex_row, + ...atoms.justify_between, + }, + label: { + ...atoms.body_2_md_regular, + color: color.text_gray_low, + }, + value: { + ...atoms.flex_1, + ...atoms.text_right, + ...atoms.body_2_md_regular, + color: color.text_gray_max, + }, + description: { + ...atoms.body_2_md_regular, + color: color.text_gray_max, + }, + tabs: { + ...atoms.flex_row, + }, + link: { + ...atoms.link_1_lg_underline, + color: color.text_primary_medium, + }, + linkGroup: { + ...atoms.flex_row, + ...atoms.gap_lg, + }, + copiableText: { + ...atoms.flex_1, + ...atoms.align_center, + }, + json: { + ...atoms.flex_1, + ...atoms.pt_lg, + borderRadius: 8, + backgroundColor: color.bg_color_min, + }, + jsonHeader: { + ...atoms.px_lg, + ...atoms.flex_row, + ...atoms.justify_between, + }, + jsonLabel: { + ...atoms.body_1_lg_medium, + color: color.text_gray_medium, + }, + jsonContent: { + ...atoms.px_lg, + }, + info: { + ...atoms.flex_1, + }, + metadata: { + ...atoms.body_2_md_regular, + color: color.text_gray_medium, + }, + }) + + const colors = { + copy: color.gray_900, + } + + return {styles, colors} as const +} diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/TokenItem.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/TokenItem.tsx index 751a3bbd56..fabea93b81 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/common/TokenItem.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/common/TokenItem.tsx @@ -1,31 +1,55 @@ import {useTheme} from '@yoroi/theme' +import {Portfolio} from '@yoroi/types' import * as React from 'react' -import {StyleSheet, Text, View} from 'react-native' +import {StyleSheet, Text, TouchableOpacity, useWindowDimensions} from 'react-native' + +import {useModal} from '../../../components/Modal/ModalContext' +import {useStrings} from './hooks/useStrings' +import {TokenDetails} from './TokenDetails' export const TokenItem = ({ + tokenInfo, isPrimaryToken = true, isSent = true, label, }: { + tokenInfo: Portfolio.Token.Info isPrimaryToken?: boolean isSent?: boolean label: string }) => { const {styles} = useStyles() + const strings = useStrings() + const {openModal} = useModal() + const {height: windowHeight} = useWindowDimensions() + + const handleShowTokenDetails = () => { + openModal(strings.tokenDetailsTitle, , windowHeight * 0.8) + } if (!isSent) return ( - + {label} - + ) return ( - + {label} - + ) } diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx index 08a8d1ebfe..7949d9541d 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx @@ -1,7 +1,7 @@ import {invalid, isNonNullable} from '@yoroi/common' import {infoExtractName} from '@yoroi/portfolio' import {Portfolio} from '@yoroi/types' -import * as _ from 'lodash' +import _ from 'lodash' import {useQuery} from 'react-query' import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types' @@ -19,7 +19,6 @@ import { TransactionOutputs, } from '../types' -export type FormattedTx = ReturnType export const useFormattedTx = (data: TransactionBody) => { const {wallet} = useSelectedWallet() @@ -102,6 +101,7 @@ const formatInputs = async ( coin != null ? [ { + tokenInfo: wallet.portfolioPrimaryTokenInfo, name: wallet.portfolioPrimaryTokenInfo.name, label: formatTokenWithText(coin, wallet.portfolioPrimaryTokenInfo), quantity: coin, @@ -118,6 +118,7 @@ const formatInputs = async ( const quantity = asQuantity(a.amount) return { + tokenInfo, name: infoExtractName(tokenInfo), label: formatTokenWithText(quantity, tokenInfo), quantity: quantity, @@ -151,6 +152,7 @@ const formatOutputs = async ( const primaryAssets = [ { + tokenInfo: wallet.portfolioPrimaryTokenInfo, name: wallet.portfolioPrimaryTokenInfo.name, label: formatTokenWithText(coin, wallet.portfolioPrimaryTokenInfo), quantity: coin, @@ -166,6 +168,7 @@ const formatOutputs = async ( const quantity = asQuantity(amount) return { + tokenInfo, name: infoExtractName(tokenInfo), label: formatTokenWithText(quantity, tokenInfo), quantity, @@ -191,6 +194,7 @@ export const formatFee = (wallet: YoroiWallet, data: TransactionBody): Formatted const fee = asQuantity(data?.fee ?? '0') return { + tokenInfo: wallet.portfolioPrimaryTokenInfo, name: wallet.portfolioPrimaryTokenInfo.name, label: formatTokenWithText(fee, wallet.portfolioPrimaryTokenInfo), quantity: fee, diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx index 54d8581849..9914016ec2 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx @@ -21,6 +21,17 @@ export const useStrings = () => { utxosOutputsLabel: intl.formatMessage(messages.utxosOutputsLabel), utxosYourAddressLabel: intl.formatMessage(messages.utxosYourAddressLabel), utxosForeignAddressLabel: intl.formatMessage(messages.utxosForeignAddressLabel), + overview: intl.formatMessage(messages.overview), + json: intl.formatMessage(messages.json), + metadata: intl.formatMessage(messages.metadata), + policyId: intl.formatMessage(messages.policyId), + fingerprint: intl.formatMessage(messages.fingerprint), + name: intl.formatMessage(messages.name), + tokenSupply: intl.formatMessage(messages.tokenSupply), + symbol: intl.formatMessage(messages.symbol), + description: intl.formatMessage(messages.description), + details: intl.formatMessage(messages.details), + tokenDetailsTitle: intl.formatMessage(messages.tokenDetailsTitle), } } @@ -59,7 +70,7 @@ const messages = defineMessages({ }, receiveToLabel: { id: 'txReview.overview.receiveToLabel', - defaultMessage: '!!!receiveToLabel', + defaultMessage: '!!!To', }, receiveToScriptLabel: { id: 'txReview.overview.receiveToScriptLabel', @@ -81,4 +92,48 @@ const messages = defineMessages({ id: 'txReview.utxos.utxosForeignAddressLabel', defaultMessage: '!!!Foreign address', }, + overview: { + id: 'txReview.tokenDetails.overViewTab.title', + defaultMessage: '!!!Overview', + }, + json: { + id: 'txReview.tokenDetails.jsonTab.title', + defaultMessage: '!!!JSON', + }, + metadata: { + id: 'txReview.tokenDetails.jsonTab.metadata', + defaultMessage: '!!!Metadata', + }, + policyId: { + id: 'txReview.tokenDetails.policyId.label', + defaultMessage: '!!!Policy ID', + }, + fingerprint: { + id: 'txReview.tokenDetails.fingerprint.label', + defaultMessage: '!!!Fingerprint', + }, + name: { + id: 'txReview.tokenDetails.overViewTab.name.label', + defaultMessage: '!!!Name', + }, + tokenSupply: { + id: 'txReview.tokenDetails.overViewTab.tokenSupply.label', + defaultMessage: '!!!Token Supply', + }, + symbol: { + id: 'txReview.tokenDetails.overViewTab.symbol.label', + defaultMessage: '!!!Symbol', + }, + description: { + id: 'txReview.tokenDetails.overViewTab.description.label', + defaultMessage: '!!!Description', + }, + details: { + id: 'txReview.tokenDetails.overViewTab.details.label', + defaultMessage: '!!!Details on', + }, + tokenDetailsTitle: { + id: 'txReview.tokenDetails.title', + defaultMessage: '!!!Asset Details', + }, }) diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/types.ts b/apps/wallet-mobile/src/features/ReviewTx/common/types.ts index 935f333b56..45744a4cf6 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/common/types.ts +++ b/apps/wallet-mobile/src/features/ReviewTx/common/types.ts @@ -1,863 +1,17 @@ -import {Balance} from '@yoroi/types' +import { + TransactionBodyJSON, + TransactionInputsJSON, + TransactionOutputsJSON, +} from '@emurgo/cardano-serialization-lib-nodejs' +import {Balance, Portfolio} from '@yoroi/types' -export type TransactionDetails = { - id: string - walletPlate: React.ReactNode - walletName: string - createdBy: string | null - fee: string - txBody: TransactionBody -} - -export type Address = string -export type URL = string - -export interface Anchor { - anchor_data_hash: string - anchor_url: URL -} -export type AnchorDataHash = string -export type AssetName = string -export type AssetNames = string[] -export interface Assets { - [k: string]: string -} -export type NativeScript = - | { - ScriptPubkey: ScriptPubkey - } - | { - ScriptAll: ScriptAll - } - | { - ScriptAny: ScriptAny - } - | { - ScriptNOfK: ScriptNOfK - } - | { - TimelockStart: TimelockStart - } - | { - TimelockExpiry: TimelockExpiry - } -export type NativeScripts = NativeScript[] -export type PlutusScripts = string[] - -export interface AuxiliaryData { - metadata?: { - [k: string]: string - } | null - native_scripts?: NativeScripts | null - plutus_scripts?: PlutusScripts | null - prefer_alonzo_format: boolean -} -export interface ScriptPubkey { - addr_keyhash: string -} -export interface ScriptAll { - native_scripts: NativeScripts -} -export interface ScriptAny { - native_scripts: NativeScripts -} -export interface ScriptNOfK { - n: number - native_scripts: NativeScripts -} -export interface TimelockStart { - slot: string -} -export interface TimelockExpiry { - slot: string -} -export type AuxiliaryDataHash = string -export interface AuxiliaryDataSet { - [k: string]: AuxiliaryData -} -export type BigInt = string -export type BigNum = string -export type Vkey = string -export type HeaderLeaderCertEnum = - | { - /** - * @minItems 2 - * @maxItems 2 - */ - NonceAndLeader: [VRFCert, VRFCert] - } - | { - VrfResult: VRFCert - } -export type Certificate = - | { - StakeRegistration: StakeRegistration - } - | { - StakeDeregistration: StakeDeregistration - } - | { - StakeDelegation: StakeDelegation - } - | { - PoolRegistration: PoolRegistration - } - | { - PoolRetirement: PoolRetirement - } - | { - GenesisKeyDelegation: GenesisKeyDelegation - } - | { - MoveInstantaneousRewardsCert: MoveInstantaneousRewardsCert - } - | { - CommitteeHotAuth: CommitteeHotAuth - } - | { - CommitteeColdResign: CommitteeColdResign - } - | { - DRepDeregistration: DRepDeregistration - } - | { - DRepRegistration: DRepRegistration - } - | { - DRepUpdate: DRepUpdate - } - | { - StakeAndVoteDelegation: StakeAndVoteDelegation - } - | { - StakeRegistrationAndDelegation: StakeRegistrationAndDelegation - } - | { - StakeVoteRegistrationAndDelegation: StakeVoteRegistrationAndDelegation - } - | { - VoteDelegation: VoteDelegation - } - | { - VoteRegistrationAndDelegation: VoteRegistrationAndDelegation - } -export type CredType = - | { - Key: string - } - | { - Script: string - } -export type Relay = - | { - SingleHostAddr: SingleHostAddr - } - | { - SingleHostName: SingleHostName - } - | { - MultiHostName: MultiHostName - } -/** - * @minItems 4 - * @maxItems 4 - */ -export type Ipv4 = [number, number, number, number] -/** - * @minItems 16 - * @maxItems 16 - */ -export type Ipv6 = [ - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, -] -export type DNSRecordAorAAAA = string -export type DNSRecordSRV = string -export type Relays = Relay[] -export type MIRPot = 'Reserves' | 'Treasury' -export type MIREnum = - | { - ToOtherPot: string - } - | { - ToStakeCredentials: StakeToCoin[] - } -export type DRep = - | ('AlwaysAbstain' | 'AlwaysNoConfidence') - | { - KeyHash: string - } - | { - ScriptHash: string - } -export type DataOption = - | { - DataHash: string - } - | { - Data: string - } -export type ScriptRef = - | { - NativeScript: NativeScript - } - | { - PlutusScript: string - } -export type Mint = [string, MintAssets][] -export type NetworkId = 'Testnet' | 'Mainnet' -export type TransactionOutputs = TransactionOutput[] -export type CostModel = string[] -export type Voter = - | { - ConstitutionalCommitteeHotCred: CredType - } - | { - DRep: CredType - } - | { - StakingPool: string - } -export type VoteKind = 'No' | 'Yes' | 'Abstain' -export type GovernanceAction = - | { - ParameterChangeAction: ParameterChangeAction - } - | { - HardForkInitiationAction: HardForkInitiationAction - } - | { - TreasuryWithdrawalsAction: TreasuryWithdrawalsAction - } - | { - NoConfidenceAction: NoConfidenceAction - } - | { - UpdateCommitteeAction: UpdateCommitteeAction - } - | { - NewConstitutionAction: NewConstitutionAction - } - | { - InfoAction: InfoAction - } -/** - * @minItems 0 - * @maxItems 0 - */ -export type InfoAction = [] -export type TransactionBodies = TransactionBody[] -export type RedeemerTag = 'Spend' | 'Mint' | 'Cert' | 'Reward' | 'Vote' | 'VotingProposal' -export type TransactionWitnessSets = TransactionWitnessSet[] - -export interface Block { - auxiliary_data_set: { - [k: string]: AuxiliaryData - } - header: Header - invalid_transactions: number[] - transaction_bodies: TransactionBodies - transaction_witness_sets: TransactionWitnessSets -} -export interface Header { - body_signature: string - header_body: HeaderBody -} -export interface HeaderBody { - block_body_hash: string - block_body_size: number - block_number: number - issuer_vkey: Vkey - leader_cert: HeaderLeaderCertEnum - operational_cert: OperationalCert - prev_hash?: string | null - protocol_version: ProtocolVersion - slot: string - vrf_vkey: string -} -export interface VRFCert { - output: number[] - proof: number[] -} -export interface OperationalCert { - hot_vkey: string - kes_period: number - sequence_number: number - sigma: string -} -export interface ProtocolVersion { - major: number - minor: number -} -export interface TransactionBody { - auxiliary_data_hash?: string | null - certs?: Certificate[] | null - collateral?: TransactionInput[] | null - collateral_return?: TransactionOutput | null - current_treasury_value?: string | null - donation?: string | null - fee: string - inputs: TransactionInput[] - mint?: Mint | null - network_id?: NetworkId | null - outputs: TransactionOutputs - reference_inputs?: TransactionInput[] | null - required_signers?: string[] | null - script_data_hash?: string | null - total_collateral?: string | null - ttl?: string | null - update?: Update | null - validity_start_interval?: string | null - voting_procedures?: VoterVotes[] | null - voting_proposals?: VotingProposal[] | null - withdrawals?: { - [k: string]: string - } | null -} -export interface StakeRegistration { - coin?: string | null - stake_credential: CredType -} -export interface StakeDeregistration { - coin?: string | null - stake_credential: CredType -} -export interface StakeDelegation { - pool_keyhash: string - stake_credential: CredType -} -export interface PoolRegistration { - pool_params: PoolParams -} -export interface PoolParams { - cost: string - margin: UnitInterval - operator: string - pledge: string - pool_metadata?: PoolMetadata | null - pool_owners: string[] - relays: Relays - reward_account: string - vrf_keyhash: string -} -export interface UnitInterval { - denominator: string - numerator: string -} -export interface PoolMetadata { - pool_metadata_hash: string - url: URL -} -export interface SingleHostAddr { - ipv4?: Ipv4 | null - ipv6?: Ipv6 | null - port?: number | null -} -export interface SingleHostName { - dns_name: DNSRecordAorAAAA - port?: number | null -} -export interface MultiHostName { - dns_name: DNSRecordSRV -} -export interface PoolRetirement { - epoch: number - pool_keyhash: string -} -export interface GenesisKeyDelegation { - genesis_delegate_hash: string - genesishash: string - vrf_keyhash: string -} -export interface MoveInstantaneousRewardsCert { - move_instantaneous_reward: MoveInstantaneousReward -} -export interface MoveInstantaneousReward { - pot: MIRPot - variant: MIREnum -} -export interface StakeToCoin { - amount: string - stake_cred: CredType -} -export interface CommitteeHotAuth { - committee_cold_credential: CredType - committee_hot_credential: CredType -} -export interface CommitteeColdResign { - anchor?: Anchor | null - committee_cold_credential: CredType -} -export interface DRepDeregistration { - coin: string - voting_credential: CredType -} -export interface DRepRegistration { - anchor?: Anchor | null - coin: string - voting_credential: CredType -} -export interface DRepUpdate { - anchor?: Anchor | null - voting_credential: CredType -} -export interface StakeAndVoteDelegation { - drep: DRep - pool_keyhash: string - stake_credential: CredType -} -export interface StakeRegistrationAndDelegation { - coin: string - pool_keyhash: string - stake_credential: CredType -} -export interface StakeVoteRegistrationAndDelegation { - coin: string - drep: DRep - pool_keyhash: string - stake_credential: CredType -} -export interface VoteDelegation { - drep: DRep - stake_credential: CredType -} -export interface VoteRegistrationAndDelegation { - coin: string - drep: DRep - stake_credential: CredType -} -export interface TransactionInput { - index: number - transaction_id: string -} -export interface TransactionOutput { - address: string - amount: Value - plutus_data?: DataOption | null - script_ref?: ScriptRef | null -} -export interface Value { - coin: string - multiasset?: MultiAsset | null -} -export interface MultiAsset { - [k: string]: Assets -} -export interface MintAssets { - [k: string]: string -} -export interface Update { - epoch: number - proposed_protocol_parameter_updates: { - [k: string]: ProtocolParamUpdate - } -} -export interface ProtocolParamUpdate { - ada_per_utxo_byte?: string | null - collateral_percentage?: number | null - committee_term_limit?: number | null - cost_models?: Costmdls | null - d?: UnitInterval | null - drep_deposit?: string | null - drep_inactivity_period?: number | null - drep_voting_thresholds?: DRepVotingThresholds | null - execution_costs?: ExUnitPrices | null - expansion_rate?: UnitInterval | null - extra_entropy?: Nonce | null - governance_action_deposit?: string | null - governance_action_validity_period?: number | null - key_deposit?: string | null - max_block_body_size?: number | null - max_block_ex_units?: ExUnits | null - max_block_header_size?: number | null - max_collateral_inputs?: number | null - max_epoch?: number | null - max_tx_ex_units?: ExUnits | null - max_tx_size?: number | null - max_value_size?: number | null - min_committee_size?: number | null - min_pool_cost?: string | null - minfee_a?: string | null - minfee_b?: string | null - n_opt?: number | null - pool_deposit?: string | null - pool_pledge_influence?: UnitInterval | null - pool_voting_thresholds?: PoolVotingThresholds | null - protocol_version?: ProtocolVersion | null - ref_script_coins_per_byte?: UnitInterval | null - treasury_growth_rate?: UnitInterval | null -} -export interface Costmdls { - [k: string]: CostModel -} -export interface DRepVotingThresholds { - committee_no_confidence: UnitInterval - committee_normal: UnitInterval - hard_fork_initiation: UnitInterval - motion_no_confidence: UnitInterval - pp_economic_group: UnitInterval - pp_governance_group: UnitInterval - pp_network_group: UnitInterval - pp_technical_group: UnitInterval - treasury_withdrawal: UnitInterval - update_constitution: UnitInterval -} -export interface ExUnitPrices { - mem_price: UnitInterval - step_price: UnitInterval -} -export interface Nonce { - /** - * @minItems 32 - * @maxItems 32 - */ - hash?: - | [ - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - ] - | null -} -export interface ExUnits { - mem: string - steps: string -} -export interface PoolVotingThresholds { - committee_no_confidence: UnitInterval - committee_normal: UnitInterval - hard_fork_initiation: UnitInterval - motion_no_confidence: UnitInterval - security_relevant_threshold: UnitInterval -} -export interface VoterVotes { - voter: Voter - votes: Vote[] -} -export interface Vote { - action_id: GovernanceActionId - voting_procedure: VotingProcedure -} -export interface GovernanceActionId { - index: number - transaction_id: string -} -export interface VotingProcedure { - anchor?: Anchor | null - vote: VoteKind -} -export interface VotingProposal { - anchor: Anchor - deposit: string - governance_action: GovernanceAction - reward_account: string -} -export interface ParameterChangeAction { - gov_action_id?: GovernanceActionId | null - policy_hash?: string | null - protocol_param_updates: ProtocolParamUpdate -} -export interface HardForkInitiationAction { - gov_action_id?: GovernanceActionId | null - protocol_version: ProtocolVersion -} -export interface TreasuryWithdrawalsAction { - policy_hash?: string | null - withdrawals: TreasuryWithdrawals -} -export interface TreasuryWithdrawals { - [k: string]: string -} -export interface NoConfidenceAction { - gov_action_id?: GovernanceActionId | null -} -export interface UpdateCommitteeAction { - committee: Committee - gov_action_id?: GovernanceActionId | null - members_to_remove: CredType[] -} -export interface Committee { - members: CommitteeMember[] - quorum_threshold: UnitInterval -} -export interface CommitteeMember { - stake_credential: CredType - term_limit: number -} -export interface NewConstitutionAction { - constitution: Constitution - gov_action_id?: GovernanceActionId | null -} -export interface Constitution { - anchor: Anchor - script_hash?: string | null -} -export interface TransactionWitnessSet { - bootstraps?: BootstrapWitness[] | null - native_scripts?: NativeScripts | null - plutus_data?: PlutusList | null - plutus_scripts?: PlutusScripts | null - redeemers?: Redeemer[] | null - vkeys?: Vkeywitness[] | null -} -export interface BootstrapWitness { - attributes: number[] - chain_code: number[] - signature: string - vkey: Vkey -} -export interface PlutusList { - definite_encoding?: boolean | null - elems: string[] -} -export interface Redeemer { - data: string - ex_units: ExUnits - index: string - tag: RedeemerTag -} -export interface Vkeywitness { - signature: string - vkey: Vkey -} -export type BlockHash = string -export type BootstrapWitnesses = BootstrapWitness[] - -export type CertificateEnum = - | { - StakeRegistration: StakeRegistration - } - | { - StakeDeregistration: StakeDeregistration - } - | { - StakeDelegation: StakeDelegation - } - | { - PoolRegistration: PoolRegistration - } - | { - PoolRetirement: PoolRetirement - } - | { - GenesisKeyDelegation: GenesisKeyDelegation - } - | { - MoveInstantaneousRewardsCert: MoveInstantaneousRewardsCert - } - | { - CommitteeHotAuth: CommitteeHotAuth - } - | { - CommitteeColdResign: CommitteeColdResign - } - | { - DRepDeregistration: DRepDeregistration - } - | { - DRepRegistration: DRepRegistration - } - | { - DRepUpdate: DRepUpdate - } - | { - StakeAndVoteDelegation: StakeAndVoteDelegation - } - | { - StakeRegistrationAndDelegation: StakeRegistrationAndDelegation - } - | { - StakeVoteRegistrationAndDelegation: StakeVoteRegistrationAndDelegation - } - | { - VoteDelegation: VoteDelegation - } - | { - VoteRegistrationAndDelegation: VoteRegistrationAndDelegation - } -export type Certificates = Certificate[] - -export type Credential = CredType -export type Credentials = CredType[] -export type DRepEnum = - | ('AlwaysAbstain' | 'AlwaysNoConfidence') - | { - KeyHash: string - } - | { - ScriptHash: string - } -export type DataHash = string -export type Ed25519KeyHash = string -export type Ed25519KeyHashes = string[] -export type Ed25519Signature = string -export interface GeneralTransactionMetadata { - [k: string]: string -} -export type GenesisDelegateHash = string -export type GenesisHash = string -export type GenesisHashes = string[] -export type GovernanceActionEnum = - | { - ParameterChangeAction: ParameterChangeAction - } - | { - HardForkInitiationAction: HardForkInitiationAction - } - | { - TreasuryWithdrawalsAction: TreasuryWithdrawalsAction - } - | { - NoConfidenceAction: NoConfidenceAction - } - | { - UpdateCommitteeAction: UpdateCommitteeAction - } - | { - NewConstitutionAction: NewConstitutionAction - } - | { - InfoAction: InfoAction - } -export type GovernanceActionIds = GovernanceActionId[] - -export type Int = string -/** - * @minItems 4 - * @maxItems 4 - */ -export type KESVKey = string -export type Language = LanguageKind -export type LanguageKind = 'PlutusV1' | 'PlutusV2' | 'PlutusV3' -export type Languages = Language[] -export type MIRToStakeCredentials = StakeToCoin[] - -export type MintsAssets = MintAssets[] - -export type NetworkIdKind = 'Testnet' | 'Mainnet' -export type PlutusScript = string -export type PoolMetadataHash = string -export interface ProposedProtocolParameterUpdates { - [k: string]: ProtocolParamUpdate -} -export type PublicKey = string -export type RedeemerTagKind = 'Spend' | 'Mint' | 'Cert' | 'Reward' | 'Vote' | 'VotingProposal' -export type Redeemers = Redeemer[] - -export type RelayEnum = - | { - SingleHostAddr: SingleHostAddr - } - | { - SingleHostName: SingleHostName - } - | { - MultiHostName: MultiHostName - } -/** - * @minItems 4 - * @maxItems 4 - */ -export type RewardAddress = string -export type RewardAddresses = string[] -export type ScriptDataHash = string -export type ScriptHash = string -export type ScriptHashes = string[] -export type ScriptRefEnum = - | { - NativeScript: NativeScript - } - | { - PlutusScript: string - } -export interface Transaction { - auxiliary_data?: AuxiliaryData | null - body: TransactionBody - is_valid: boolean - witness_set: TransactionWitnessSet -} -export type TransactionHash = string -export type TransactionInputs = TransactionInput[] - -export type TransactionMetadatum = string -export interface TransactionUnspentOutput { - input: TransactionInput - output: TransactionOutput -} -export type TransactionUnspentOutputs = TransactionUnspentOutput[] - -export type VRFKeyHash = string -export type VRFVKey = string -export interface VersionedBlock { - block: Block - era_code: number -} -export type Vkeywitnesses = Vkeywitness[] - -export type VoterEnum = - | { - ConstitutionalCommitteeHotCred: CredType - } - | { - DRep: CredType - } - | { - StakingPool: string - } -export type Voters = Voter[] -export type VotingProcedures = VoterVotes[] - -export type VotingProposals = VotingProposal[] - -export interface Withdrawals { - [k: string]: string -} +export type TransactionBody = TransactionBodyJSON +export type TransactionInputs = TransactionInputsJSON +export type TransactionOutputs = TransactionOutputsJSON export type FormattedInput = { assets: Array<{ + tokenInfo: Portfolio.Token.Info name: string label: string quantity: Balance.Quantity @@ -871,8 +25,10 @@ export type FormattedInput = { } export type FormattedInputs = Array + export type FormattedOutput = { assets: Array<{ + tokenInfo: Portfolio.Token.Info name: string label: string quantity: Balance.Quantity @@ -882,10 +38,19 @@ export type FormattedOutput = { rewardAddress: string | null ownAddress: boolean } + export type FormattedOutputs = Array + export type FormattedFee = { + tokenInfo: Portfolio.Token.Info name: string label: string quantity: Balance.Quantity isPrimary: boolean } + +export type FormattedTx = { + inputs: FormattedInputs + outputs: FormattedOutputs + fee: FormattedFee +} diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx index eed42fd24a..dd7e2e086e 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx @@ -6,20 +6,18 @@ import * as React from 'react' import {Linking, StyleSheet, Text, TouchableOpacity, View} from 'react-native' import {Icon} from '../../../../../components/Icon' -import {Info} from '../../../../../components/Info/Info' import {Space} from '../../../../../components/Space/Space' import {formatTokenWithText} from '../../../../../yoroi-wallets/utils/format' import {Quantities} from '../../../../../yoroi-wallets/utils/utils' import {useSelectedWallet} from '../../../../WalletManager/common/hooks/useSelectedWallet' import {useWalletManager} from '../../../../WalletManager/context/WalletManagerProvider' -import {Address} from '../../../common/Address' -import {CollapsibleSection} from '../../../common/CollapsibleSection' +import {Accordion} from '../../../common/Accordion' +import {CopiableText} from '../../../common/CopiableText' import {Divider} from '../../../common/Divider' import {useAddressType} from '../../../common/hooks/useAddressType' -import {FormattedTx} from '../../../common/hooks/useFormattedTx' import {useStrings} from '../../../common/hooks/useStrings' import {TokenItem} from '../../../common/TokenItem' -import {FormattedOutputs} from '../../../common/types' +import {FormattedOutputs, FormattedTx} from '../../../common/types' export const OverviewTab = ({tx}: {tx: FormattedTx}) => { const {styles} = useStyles() @@ -77,7 +75,7 @@ const FeeInfoItem = ({fee}: {fee: string}) => { {strings.feeLabel} - {fee} + {`-${fee}`} ) } @@ -92,20 +90,25 @@ const SenderSection = ({ ownedOutputs: FormattedOutputs }) => { const strings = useStrings() + const {styles} = useStyles() const address = ownedOutputs[0]?.rewardAddress ?? ownedOutputs[0]?.address return ( - + -
+ + + {address} + + {notOwnedOutputs.length === 1 && } - + ) } @@ -140,10 +143,10 @@ const SenderTokens = ({tx, notOwnedOutputs}: {tx: FormattedTx; notOwnedOutputs: - + - {notPrimaryTokenSent.map((token) => ( - + {notPrimaryTokenSent.map((token, index) => ( + ))} @@ -179,7 +182,11 @@ const ReceiverSection = ({notOwnedOutputs}: {notOwnedOutputs: FormattedOutputs}) {isScriptAddress ? strings.receiveToScriptLabel : strings.receiveToLabel}: -
+ + + {address} + + ) @@ -249,6 +256,11 @@ const useStyles = () => { receiverSectionAddress: { maxWidth: 260, }, + addressText: { + ...atoms.flex_1, + ...atoms.body_2_md_regular, + color: color.text_gray_medium, + }, }) const colors = { @@ -285,7 +297,7 @@ const CreatedByInfoItem = () => { // 🚧 TODO: WIP 🚧 // eslint-disable-next-line @typescript-eslint/no-unused-vars -const ReceiverTokensSectionMultiReceiver = () => { +/* const ReceiverTokensSectionMultiReceiver = () => { const {styles} = useStyles() return ( @@ -294,7 +306,7 @@ const ReceiverTokensSectionMultiReceiver = () => { - + @@ -311,36 +323,36 @@ const ReceiverTokensSectionMultiReceiver = () => { - + - + - + - + - + - + - + - + - + - + - + ) -} +} */ // 🚧 TODO: WIP 🚧 -const ReceiverSectionLabel = () => { +/* const ReceiverSectionLabel = () => { const {styles, colors} = useStyles() return ( @@ -352,4 +364,4 @@ const ReceiverSectionLabel = () => { Receive ) -} +} */ diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx index 6216f4c12f..3f8af27dcc 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx @@ -1,7 +1,7 @@ import {createMaterialTopTabNavigator, MaterialTopTabBarProps} from '@react-navigation/material-top-tabs' import {useTheme} from '@yoroi/theme' import * as React from 'react' -import {FlatList, StyleSheet, Text, TouchableOpacity, TouchableOpacityProps, View} from 'react-native' +import {FlatList, ScrollView, StyleSheet, Text, TouchableOpacity, TouchableOpacityProps, View} from 'react-native' import {Button} from '../../../../components/Button/Button' import {SafeArea} from '../../../../components/SafeArea' @@ -37,9 +37,23 @@ export const ReviewTxScreen = () => { return ( - - - + + {() => ( + /* TODO: make scrollview general to use button border */ + + + + )} + + + + {() => ( + /* TODO: make scrollview general to use button border */ + + + + )} + @@ -59,11 +73,11 @@ const TabBar = ({navigation, state}: MaterialTopTabBarProps) => { [strings.overviewTab, 'overview'], [strings.utxosTab, 'utxos'], ]} - style={styles.tabBar} - showsHorizontalScrollIndicator={false} renderItem={({item: [label, key], index}) => ( navigation.navigate(key)} /> )} + style={styles.tabBar} + showsHorizontalScrollIndicator={false} bounces={false} horizontal /> diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/UTxOs/UTxOsTab.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/UTxOs/UTxOsTab.tsx index bbf23f8cec..83861af94f 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/UTxOs/UTxOsTab.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/UTxOs/UTxOsTab.tsx @@ -3,13 +3,12 @@ import * as React from 'react' import {StyleSheet, Text, View} from 'react-native' import {Space} from '../../../../../components/Space/Space' -import {Address} from '../../../common/Address' -import {CollapsibleSection} from '../../../common/CollapsibleSection' +import {Accordion} from '../../../common/Accordion' +import {CopiableText} from '../../../common/CopiableText' import {Divider} from '../../../common/Divider' -import {FormattedTx} from '../../../common/hooks/useFormattedTx' import {useStrings} from '../../../common/hooks/useStrings' import {TokenItem} from '../../../common/TokenItem' -import {FormattedInput, FormattedInputs, FormattedOutput, FormattedOutputs} from '../../../common/types' +import {FormattedInput, FormattedInputs, FormattedOutput, FormattedOutputs, FormattedTx} from '../../../common/types' export const UTxOsTab = ({tx}: {tx: FormattedTx}) => { const {styles} = useStyles() @@ -19,21 +18,21 @@ export const UTxOsTab = ({tx}: {tx: FormattedTx}) => { - + - + - + - + ) } const Inputs = ({inputs}: {inputs: FormattedInputs}) => { - return inputs.map((input) => ) + return inputs.map((input, index) => ) } const Input = ({input}: {input: FormattedInput}) => { @@ -49,25 +48,35 @@ const Input = ({input}: {input: FormattedInput}) => { -
+ + {input.address} + -
+ + {input.txHash} + + + + {`#${input.txIndex}`} + + + - {input.assets.map((asset) => ( - + {input.assets.map((asset, index) => ( + ))} ) } const Outputs = ({outputs}: {outputs: FormattedOutputs}) => { - return outputs.map((output) => ) + return outputs.map((output, index) => ) } const Output = ({output}: {output: FormattedOutput}) => { @@ -83,14 +92,22 @@ const Output = ({output}: {output: FormattedOutput}) => { -
+ + {output.address} + - {output.assets.map((asset) => ( - + {output.assets.map((asset, index) => ( + ))} @@ -179,6 +196,15 @@ const useStyles = () => { ...atoms.body_2_md_regular, color: color.text_gray_medium, }, + addressText: { + ...atoms.flex_1, + ...atoms.body_2_md_regular, + color: color.text_gray_medium, + }, + index: { + ...atoms.body_2_md_medium, + color: color.text_gray_medium, + }, }) return {styles} as const diff --git a/apps/wallet-mobile/src/kernel/i18n/locales/en-US.json b/apps/wallet-mobile/src/kernel/i18n/locales/en-US.json index 81cea9275e..28753180fa 100644 --- a/apps/wallet-mobile/src/kernel/i18n/locales/en-US.json +++ b/apps/wallet-mobile/src/kernel/i18n/locales/en-US.json @@ -1227,12 +1227,23 @@ "txReview.fee": "Fee", "txReview.overview.myWalletLabel": "Your Wallet", "txReview.overview.sendLabel": "Send", - "txReview.overview.receiveToLabel": "receiveToLabel", + "txReview.overview.receiveToLabel": "To", "txReview.overview.receiveToScriptLabel": "To script", "txReview.utxos.utxosInputsLabel": "Inputs", "txReview.utxos.utxosOutputsLabel": "Outputs", "txReview.utxos.utxosYourAddressLabel": "Your address", "txReview.utxos.utxosForeignAddressLabel": "Foreign address", "txReview.title": "Transaction Review", - "txReview.confirm": "Confirm" + "txReview.confirm": "Confirm", + "txReview.tokenDetails.overViewTab.title": "Overview", + "txReview.tokenDetails.jsonTab.title": "JSON", + "txReview.tokenDetails.jsonTab.metadata": "Metadata", + "txReview.tokenDetails.policyId.label": "Policy ID", + "txReview.tokenDetails.fingerprint.label": "Fingerprint", + "txReview.tokenDetails.overViewTab.name.label": "Name", + "txReview.tokenDetails.overViewTab.tokenSupply.label": "Token supply", + "txReview.tokenDetails.overViewTab.symbol.label": "Symbol", + "txReview.tokenDetails.overViewTab.description.label": "Description", + "txReview.tokenDetails.overViewTab.details.label": "Details on", + "txReview.tokenDetails.title": "Asset Details" } diff --git a/apps/wallet-mobile/translations/messages/src/WalletNavigator.json b/apps/wallet-mobile/translations/messages/src/WalletNavigator.json index f8e4bc086c..ce9df7a0e8 100644 --- a/apps/wallet-mobile/translations/messages/src/WalletNavigator.json +++ b/apps/wallet-mobile/translations/messages/src/WalletNavigator.json @@ -4,14 +4,14 @@ "defaultMessage": "!!!Transactions", "file": "src/WalletNavigator.tsx", "start": { - "line": 309, + "line": 306, "column": 22, - "index": 10838 + "index": 10668 }, "end": { - "line": 312, + "line": 309, "column": 3, - "index": 10941 + "index": 10771 } }, { @@ -19,14 +19,14 @@ "defaultMessage": "!!!Send", "file": "src/WalletNavigator.tsx", "start": { - "line": 313, + "line": 310, "column": 14, - "index": 10957 + "index": 10787 }, "end": { - "line": 316, + "line": 313, "column": 3, - "index": 11056 + "index": 10886 } }, { @@ -34,14 +34,14 @@ "defaultMessage": "!!!Receive", "file": "src/WalletNavigator.tsx", "start": { - "line": 317, + "line": 314, "column": 17, - "index": 11075 + "index": 10905 }, "end": { - "line": 320, + "line": 317, "column": 3, - "index": 11180 + "index": 11010 } }, { @@ -49,14 +49,14 @@ "defaultMessage": "!!!Dashboard", "file": "src/WalletNavigator.tsx", "start": { - "line": 321, + "line": 318, "column": 19, - "index": 11201 + "index": 11031 }, "end": { - "line": 324, + "line": 321, "column": 3, - "index": 11298 + "index": 11128 } }, { @@ -64,14 +64,14 @@ "defaultMessage": "!!!Delegate", "file": "src/WalletNavigator.tsx", "start": { - "line": 325, + "line": 322, "column": 18, - "index": 11318 + "index": 11148 }, "end": { - "line": 328, + "line": 325, "column": 3, - "index": 11413 + "index": 11243 } }, { @@ -79,14 +79,14 @@ "defaultMessage": "!!!Wallet", "file": "src/WalletNavigator.tsx", "start": { - "line": 329, + "line": 326, "column": 16, - "index": 11431 + "index": 11261 }, "end": { - "line": 332, + "line": 329, "column": 3, - "index": 11529 + "index": 11359 } }, { @@ -94,14 +94,14 @@ "defaultMessage": "!!!Staking", "file": "src/WalletNavigator.tsx", "start": { - "line": 333, + "line": 330, "column": 17, - "index": 11548 + "index": 11378 }, "end": { - "line": 336, + "line": 333, "column": 3, - "index": 11613 + "index": 11443 } }, { @@ -109,14 +109,14 @@ "defaultMessage": "!!!NFT Gallery", "file": "src/WalletNavigator.tsx", "start": { - "line": 337, + "line": 334, "column": 14, - "index": 11629 + "index": 11459 }, "end": { - "line": 340, + "line": 337, "column": 3, - "index": 11723 + "index": 11553 } }, { @@ -124,14 +124,14 @@ "defaultMessage": "!!!Menu", "file": "src/WalletNavigator.tsx", "start": { - "line": 341, + "line": 338, "column": 14, - "index": 11739 + "index": 11569 }, "end": { - "line": 344, + "line": 341, "column": 3, - "index": 11791 + "index": 11621 } }, { @@ -139,14 +139,14 @@ "defaultMessage": "!!!Discover", "file": "src/WalletNavigator.tsx", "start": { - "line": 345, + "line": 342, "column": 18, - "index": 11811 + "index": 11641 }, "end": { - "line": 348, + "line": 345, "column": 3, - "index": 11900 + "index": 11730 } }, { @@ -154,14 +154,14 @@ "defaultMessage": "!!!My wallets", "file": "src/WalletNavigator.tsx", "start": { - "line": 349, + "line": 346, "column": 31, - "index": 11933 + "index": 11763 }, "end": { - "line": 352, + "line": 349, "column": 3, - "index": 12042 + "index": 11872 } }, { @@ -169,14 +169,14 @@ "defaultMessage": "!!!Portfolio", "file": "src/WalletNavigator.tsx", "start": { - "line": 353, + "line": 350, "column": 19, - "index": 12063 + "index": 11893 }, "end": { - "line": 356, + "line": 353, "column": 3, - "index": 12132 + "index": 11962 } } ] \ No newline at end of file diff --git a/apps/wallet-mobile/translations/messages/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.json b/apps/wallet-mobile/translations/messages/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.json index 3938849601..38ab260df5 100644 --- a/apps/wallet-mobile/translations/messages/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.json +++ b/apps/wallet-mobile/translations/messages/src/features/Portfolio/common/MediaDetailsScreen/MediaDetailsScreen.json @@ -6,12 +6,12 @@ "start": { "line": 378, "column": 9, - "index": 10888 + "index": 10908 }, "end": { "line": 381, "column": 3, - "index": 10959 + "index": 10979 } }, { @@ -21,12 +21,12 @@ "start": { "line": 382, "column": 12, - "index": 10973 + "index": 10993 }, "end": { "line": 385, "column": 3, - "index": 11044 + "index": 11064 } }, { @@ -36,12 +36,12 @@ "start": { "line": 386, "column": 12, - "index": 11058 + "index": 11078 }, "end": { "line": 389, "column": 3, - "index": 11129 + "index": 11149 } }, { @@ -51,12 +51,12 @@ "start": { "line": 390, "column": 11, - "index": 11142 + "index": 11162 }, "end": { "line": 393, "column": 3, - "index": 11212 + "index": 11232 } }, { @@ -66,12 +66,12 @@ "start": { "line": 394, "column": 13, - "index": 11227 + "index": 11247 }, "end": { "line": 397, "column": 3, - "index": 11298 + "index": 11318 } }, { @@ -81,12 +81,12 @@ "start": { "line": 398, "column": 15, - "index": 11315 + "index": 11335 }, "end": { "line": 401, "column": 3, - "index": 11392 + "index": 11412 } }, { @@ -96,12 +96,12 @@ "start": { "line": 402, "column": 10, - "index": 11404 + "index": 11424 }, "end": { "line": 405, "column": 3, - "index": 11471 + "index": 11491 } }, { @@ -111,12 +111,12 @@ "start": { "line": 406, "column": 15, - "index": 11488 + "index": 11508 }, "end": { "line": 409, "column": 3, - "index": 11565 + "index": 11585 } }, { @@ -126,12 +126,12 @@ "start": { "line": 410, "column": 12, - "index": 11579 + "index": 11599 }, "end": { "line": 413, "column": 3, - "index": 11651 + "index": 11671 } }, { @@ -141,12 +141,12 @@ "start": { "line": 414, "column": 16, - "index": 11669 + "index": 11689 }, "end": { "line": 417, "column": 3, - "index": 11746 + "index": 11766 } }, { @@ -156,12 +156,12 @@ "start": { "line": 418, "column": 16, - "index": 11764 + "index": 11784 }, "end": { "line": 421, "column": 3, - "index": 11844 + "index": 11864 } } ] \ No newline at end of file diff --git a/apps/wallet-mobile/translations/messages/src/features/ReviewTx/common/hooks/useStrings.json b/apps/wallet-mobile/translations/messages/src/features/ReviewTx/common/hooks/useStrings.json index 77bbb70459..f7ec21070a 100644 --- a/apps/wallet-mobile/translations/messages/src/features/ReviewTx/common/hooks/useStrings.json +++ b/apps/wallet-mobile/translations/messages/src/features/ReviewTx/common/hooks/useStrings.json @@ -4,14 +4,14 @@ "defaultMessage": "!!!Confirm", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 28, + "line": 39, "column": 11, - "index": 1188 + "index": 1785 }, "end": { - "line": 31, + "line": 42, "column": 3, - "index": 1255 + "index": 1852 } }, { @@ -19,14 +19,14 @@ "defaultMessage": "!!!UTxOs", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 32, + "line": 43, "column": 9, - "index": 1266 + "index": 1863 }, "end": { - "line": 35, + "line": 46, "column": 3, - "index": 1329 + "index": 1926 } }, { @@ -34,14 +34,14 @@ "defaultMessage": "!!!UTxOs", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 36, + "line": 47, "column": 12, - "index": 1343 + "index": 1940 }, "end": { - "line": 39, + "line": 50, "column": 3, - "index": 1415 + "index": 2012 } }, { @@ -49,14 +49,14 @@ "defaultMessage": "!!!Overview", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 40, + "line": 51, "column": 15, - "index": 1432 + "index": 2029 }, "end": { - "line": 43, + "line": 54, "column": 3, - "index": 1510 + "index": 2107 } }, { @@ -64,14 +64,14 @@ "defaultMessage": "!!!Wallet", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 44, + "line": 55, "column": 15, - "index": 1527 + "index": 2124 }, "end": { - "line": 47, + "line": 58, "column": 3, - "index": 1601 + "index": 2198 } }, { @@ -79,14 +79,14 @@ "defaultMessage": "!!!Fee", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 48, + "line": 59, "column": 12, - "index": 1615 + "index": 2212 }, "end": { - "line": 51, + "line": 62, "column": 3, - "index": 1674 + "index": 2271 } }, { @@ -94,14 +94,14 @@ "defaultMessage": "!!!Your Wallet", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 52, + "line": 63, "column": 17, - "index": 1693 + "index": 2290 }, "end": { - "line": 55, + "line": 66, "column": 3, - "index": 1779 + "index": 2376 } }, { @@ -109,29 +109,29 @@ "defaultMessage": "!!!Send", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 56, + "line": 67, "column": 13, - "index": 1794 + "index": 2391 }, "end": { - "line": 59, + "line": 70, "column": 3, - "index": 1869 + "index": 2466 } }, { "id": "txReview.overview.receiveToLabel", - "defaultMessage": "!!!receiveToLabel", + "defaultMessage": "!!!To", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 60, + "line": 71, "column": 18, - "index": 1889 + "index": 2486 }, "end": { - "line": 63, + "line": 74, "column": 3, - "index": 1979 + "index": 2564 } }, { @@ -139,14 +139,14 @@ "defaultMessage": "!!!To script", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 64, + "line": 75, "column": 24, - "index": 2005 + "index": 2590 }, "end": { - "line": 67, + "line": 78, "column": 3, - "index": 2096 + "index": 2681 } }, { @@ -154,14 +154,14 @@ "defaultMessage": "!!!Inputs", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 68, + "line": 79, "column": 20, - "index": 2118 + "index": 2703 }, "end": { - "line": 71, + "line": 82, "column": 3, - "index": 2199 + "index": 2784 } }, { @@ -169,14 +169,14 @@ "defaultMessage": "!!!Outputs", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 72, + "line": 83, "column": 21, - "index": 2222 + "index": 2807 }, "end": { - "line": 75, + "line": 86, "column": 3, - "index": 2305 + "index": 2890 } }, { @@ -184,14 +184,14 @@ "defaultMessage": "!!!Your address", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 76, + "line": 87, "column": 25, - "index": 2332 + "index": 2917 }, "end": { - "line": 79, + "line": 90, "column": 3, - "index": 2424 + "index": 3009 } }, { @@ -199,14 +199,179 @@ "defaultMessage": "!!!Foreign address", "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", "start": { - "line": 80, + "line": 91, "column": 28, - "index": 2454 + "index": 3039 }, "end": { - "line": 83, + "line": 94, + "column": 3, + "index": 3137 + } + }, + { + "id": "txReview.tokenDetails.overViewTab.title", + "defaultMessage": "!!!Overview", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 95, + "column": 12, + "index": 3151 + }, + "end": { + "line": 98, + "column": 3, + "index": 3242 + } + }, + { + "id": "txReview.tokenDetails.jsonTab.title", + "defaultMessage": "!!!JSON", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 99, + "column": 8, + "index": 3252 + }, + "end": { + "line": 102, + "column": 3, + "index": 3335 + } + }, + { + "id": "txReview.tokenDetails.jsonTab.metadata", + "defaultMessage": "!!!Metadata", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 103, + "column": 12, + "index": 3349 + }, + "end": { + "line": 106, + "column": 3, + "index": 3439 + } + }, + { + "id": "txReview.tokenDetails.policyId.label", + "defaultMessage": "!!!Policy ID", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 107, + "column": 12, + "index": 3453 + }, + "end": { + "line": 110, + "column": 3, + "index": 3542 + } + }, + { + "id": "txReview.tokenDetails.fingerprint.label", + "defaultMessage": "!!!Fingerprint", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 111, + "column": 15, + "index": 3559 + }, + "end": { + "line": 114, + "column": 3, + "index": 3653 + } + }, + { + "id": "txReview.tokenDetails.overViewTab.name.label", + "defaultMessage": "!!!Name", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 115, + "column": 8, + "index": 3663 + }, + "end": { + "line": 118, + "column": 3, + "index": 3755 + } + }, + { + "id": "txReview.tokenDetails.overViewTab.tokenSupply.label", + "defaultMessage": "!!!Token Supply", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 119, + "column": 15, + "index": 3772 + }, + "end": { + "line": 122, + "column": 3, + "index": 3879 + } + }, + { + "id": "txReview.tokenDetails.overViewTab.symbol.label", + "defaultMessage": "!!!Symbol", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 123, + "column": 10, + "index": 3891 + }, + "end": { + "line": 126, + "column": 3, + "index": 3987 + } + }, + { + "id": "txReview.tokenDetails.overViewTab.description.label", + "defaultMessage": "!!!Description", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 127, + "column": 15, + "index": 4004 + }, + "end": { + "line": 130, + "column": 3, + "index": 4110 + } + }, + { + "id": "txReview.tokenDetails.overViewTab.details.label", + "defaultMessage": "!!!Details on", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 131, + "column": 11, + "index": 4123 + }, + "end": { + "line": 134, + "column": 3, + "index": 4224 + } + }, + { + "id": "txReview.tokenDetails.title", + "defaultMessage": "!!!Asset Details", + "file": "src/features/ReviewTx/common/hooks/useStrings.tsx", + "start": { + "line": 135, + "column": 21, + "index": 4247 + }, + "end": { + "line": 138, "column": 3, - "index": 2552 + "index": 4331 } } ] \ No newline at end of file From 7c937379d48447321a8395e35b30f68a6910586f Mon Sep 17 00:00:00 2001 From: banklesss <105349292+banklesss@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:11:13 +0200 Subject: [PATCH 2/2] feature(wallet-mobile): new tx review for collateral (#3683) --- .../.storybook/storybook.requires.js | 1 - apps/wallet-mobile/src/YoroiApp.tsx | 5 +- .../ReviewTx/common/ReviewTxProvider.tsx | 141 ++++++ .../ReviewTx/common/hooks/useOnConfirm.tsx | 16 +- .../ReviewTxScreen/Overview/OverviewTab.tsx | 31 +- .../ReviewTxScreen/ReviewTxScreen.tsx | 17 +- .../ListAmountsToSendScreen.tsx | 17 +- .../ManageCollateral/CollateralInfoModal.tsx | 41 ++ .../ConfirmTx/ConfirmTxScreen.stories.tsx | 18 - .../ConfirmTx/ConfirmTxScreen.tsx | 150 ------ .../ConfirmTx/Summary/BalanceAfter.tsx | 36 -- .../ConfirmTx/Summary/Fees.tsx | 29 -- .../ConfirmTx/Summary/PrimaryTotal.tsx | 48 -- .../ConfirmTx/Summary/SecondaryTotals.tsx | 59 --- .../ManageCollateral/ConfirmTx/index.ts | 1 - .../InitialCollateralInfoModal.tsx | 52 +++ .../ManageCollateralScreen.tsx | 110 +++-- .../illustrations/InfoModalIllustration.tsx | 430 ++++++++++++++++++ .../Settings/ManageCollateral/strings.ts | 32 ++ .../Settings/SettingsScreenNavigator.tsx | 9 - .../src/kernel/i18n/locales/en-US.json | 6 + apps/wallet-mobile/src/kernel/navigation.tsx | 12 +- .../ListAmountsToSendScreen.json | 8 +- .../Settings/ManageCollateral/strings.json | 146 ++++-- .../WalletSettings/WalletSettingsScreen.json | 168 +++---- 25 files changed, 1045 insertions(+), 538 deletions(-) create mode 100644 apps/wallet-mobile/src/features/ReviewTx/common/ReviewTxProvider.tsx create mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/CollateralInfoModal.tsx delete mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.stories.tsx delete mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.tsx delete mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/BalanceAfter.tsx delete mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/Fees.tsx delete mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/PrimaryTotal.tsx delete mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/SecondaryTotals.tsx delete mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/index.ts create mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/InitialCollateralInfoModal.tsx create mode 100644 apps/wallet-mobile/src/features/Settings/ManageCollateral/illustrations/InfoModalIllustration.tsx diff --git a/apps/wallet-mobile/.storybook/storybook.requires.js b/apps/wallet-mobile/.storybook/storybook.requires.js index c041b07b66..21011eaba6 100644 --- a/apps/wallet-mobile/.storybook/storybook.requires.js +++ b/apps/wallet-mobile/.storybook/storybook.requires.js @@ -194,7 +194,6 @@ const getStories = () => { "./src/features/Settings/Currency/ChangeCurrencyScreen.stories.tsx": require("../src/features/Settings/Currency/ChangeCurrencyScreen.stories.tsx"), "./src/features/Settings/EasyConfirmation/EasyConfirmationScreen.stories.tsx": require("../src/features/Settings/EasyConfirmation/EasyConfirmationScreen.stories.tsx"), "./src/features/Settings/EnableLoginWithOs/EnableLoginWithOsScreen.stories.tsx": require("../src/features/Settings/EnableLoginWithOs/EnableLoginWithOsScreen.stories.tsx"), - "./src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.stories.tsx": require("../src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.stories.tsx"), "./src/features/Settings/ManageCollateral/ConfirmTx/FailedTx/FailedTxScreen.stories.tsx": require("../src/features/Settings/ManageCollateral/ConfirmTx/FailedTx/FailedTxScreen.stories.tsx"), "./src/features/Settings/ManageCollateral/ConfirmTx/SubmittedTx/SubmittedTxScreen.stories.tsx": require("../src/features/Settings/ManageCollateral/ConfirmTx/SubmittedTx/SubmittedTxScreen.stories.tsx"), "./src/features/Settings/ManageCollateral/ManageCollateralScreen.stories.tsx": require("../src/features/Settings/ManageCollateral/ManageCollateralScreen.stories.tsx"), diff --git a/apps/wallet-mobile/src/YoroiApp.tsx b/apps/wallet-mobile/src/YoroiApp.tsx index fc98586531..655676b9c4 100644 --- a/apps/wallet-mobile/src/YoroiApp.tsx +++ b/apps/wallet-mobile/src/YoroiApp.tsx @@ -15,6 +15,7 @@ import {ErrorBoundary} from './components/ErrorBoundary/ErrorBoundary' import {AuthProvider} from './features/Auth/AuthProvider' import {BrowserProvider} from './features/Discover/common/BrowserProvider' import {PortfolioTokenActivityProvider} from './features/Portfolio/common/PortfolioTokenActivityProvider' +import {ReviewTxProvider} from './features/ReviewTx/common/ReviewTxProvider' import {CurrencyProvider} from './features/Settings/Currency/CurrencyContext' import {AutomaticWalletOpenerProvider} from './features/WalletManager/context/AutomaticWalletOpeningProvider' import {WalletManagerProvider} from './features/WalletManager/context/WalletManagerProvider' @@ -65,7 +66,9 @@ const Yoroi = () => { - + + + diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/ReviewTxProvider.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/ReviewTxProvider.tsx new file mode 100644 index 0000000000..05dd6ec924 --- /dev/null +++ b/apps/wallet-mobile/src/features/ReviewTx/common/ReviewTxProvider.tsx @@ -0,0 +1,141 @@ +import {castDraft, produce} from 'immer' +import _ from 'lodash' +import React from 'react' + +import {YoroiSignedTx, YoroiUnsignedTx} from '../../../yoroi-wallets/types/yoroi' + +export const useReviewTx = () => React.useContext(ReviewTxContext) + +export const ReviewTxProvider = ({ + children, + initialState, +}: { + children: React.ReactNode + initialState?: Partial +}) => { + const [state, dispatch] = React.useReducer(reviewTxReducer, { + ...defaultState, + ...initialState, + }) + + const actions = React.useRef({ + unsignedTxChanged: (unsignedTx: ReviewTxState['unsignedTx']) => + dispatch({type: ReviewTxActionType.UnsignedTxChanged, unsignedTx}), + cborChanged: (cbor: ReviewTxState['cbor']) => dispatch({type: ReviewTxActionType.CborChanged, cbor}), + operationsChanged: (operations: ReviewTxState['operations']) => + dispatch({type: ReviewTxActionType.OperationsChanged, operations}), + onSuccessChanged: (onSuccess: ReviewTxState['onSuccess']) => + dispatch({type: ReviewTxActionType.OnSuccessChanged, onSuccess}), + onErrorChanged: (onError: ReviewTxState['onError']) => dispatch({type: ReviewTxActionType.OnErrorChanged, onError}), + }).current + + const context = React.useMemo( + () => ({ + ...state, + ...actions, + }), + [state, actions], + ) + + return {children} +} + +const reviewTxReducer = (state: ReviewTxState, action: ReviewTxAction) => { + return produce(state, (draft) => { + switch (action.type) { + case ReviewTxActionType.UnsignedTxChanged: + draft.unsignedTx = castDraft(action.unsignedTx) + break + + case ReviewTxActionType.CborChanged: + draft.cbor = action.cbor + break + + case ReviewTxActionType.OperationsChanged: + draft.operations = action.operations + break + + case ReviewTxActionType.OnSuccessChanged: + draft.onSuccess = action.onSuccess + break + + case ReviewTxActionType.OnErrorChanged: + draft.onError = action.onError + break + + default: + throw new Error('[ReviewTxContext] invalid action') + } + }) +} + +type ReviewTxAction = + | { + type: ReviewTxActionType.UnsignedTxChanged + unsignedTx: ReviewTxState['unsignedTx'] + } + | { + type: ReviewTxActionType.CborChanged + cbor: ReviewTxState['cbor'] + } + | { + type: ReviewTxActionType.OperationsChanged + operations: ReviewTxState['operations'] + } + | { + type: ReviewTxActionType.OnSuccessChanged + onSuccess: ReviewTxState['onSuccess'] + } + | { + type: ReviewTxActionType.OnErrorChanged + onError: ReviewTxState['onError'] + } + +export type ReviewTxState = { + unsignedTx: YoroiUnsignedTx | null + cbor: string | null + operations: Array | null + onSuccess: ((signedTx: YoroiSignedTx) => void) | null + onError: (() => void) | null +} + +type ReviewTxActions = { + unsignedTxChanged: (unsignedTx: ReviewTxState['unsignedTx']) => void + cborChanged: (cbor: ReviewTxState['cbor']) => void + operationsChanged: (operations: ReviewTxState['operations']) => void + onSuccessChanged: (onSuccess: ReviewTxState['onSuccess']) => void + onErrorChanged: (onError: ReviewTxState['onError']) => void +} + +const defaultState: ReviewTxState = Object.freeze({ + unsignedTx: null, + cbor: null, + operations: null, + onSuccess: null, + onError: null, +}) + +function missingInit() { + console.error('[ReviewTxContext] missing initialization') +} + +const initialReviewTxContext: ReviewTxContext = { + ...defaultState, + unsignedTxChanged: missingInit, + cborChanged: missingInit, + operationsChanged: missingInit, + onSuccessChanged: missingInit, + onErrorChanged: missingInit, +} + +enum ReviewTxActionType { + UnsignedTxChanged = 'unsignedTxChanged', + CborChanged = 'cborChanged', + OperationsChanged = 'operationsChanged', + OnSuccessChanged = 'onSuccessChanged', + OnErrorChanged = 'onErrorChanged', +} + +type ReviewTxContext = ReviewTxState & ReviewTxActions + +const ReviewTxContext = React.createContext(initialReviewTxContext) diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useOnConfirm.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useOnConfirm.tsx index dfb076eab5..38188b6e93 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useOnConfirm.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useOnConfirm.tsx @@ -15,8 +15,8 @@ export const useOnConfirm = ({ onError, onNotSupportedCIP1694, }: { - onSuccess: (txId: YoroiSignedTx) => void - onError: () => void + onSuccess?: ((txId: YoroiSignedTx) => void) | null + onError?: (() => void) | null cbor?: string unsignedTx?: YoroiUnsignedTx onNotSupportedCIP1694?: () => void @@ -34,7 +34,7 @@ export const useOnConfirm = ({ onSuccess(signedTx)} + onSuccess={(signedTx) => onSuccess?.(signedTx)} onNotSupportedCIP1694={onNotSupportedCIP1694} />, 400, @@ -47,8 +47,8 @@ export const useOnConfirm = ({ strings.signTransaction, onSuccess(signedTx)} - onError={onError} + onSuccess={(signedTx) => onSuccess?.(signedTx)} + onError={onError ?? undefined} />, ) return @@ -59,13 +59,13 @@ export const useOnConfirm = ({ strings.signTransaction, onSuccess(signedTx)} - onError={onError} + onSuccess={(signedTx) => onSuccess?.(signedTx)} + onError={onError ?? undefined} />, ) return } } - return {onConfirm} + return {onConfirm} as const } diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx index dd7e2e086e..92278d03ba 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx @@ -16,10 +16,11 @@ import {CopiableText} from '../../../common/CopiableText' import {Divider} from '../../../common/Divider' import {useAddressType} from '../../../common/hooks/useAddressType' import {useStrings} from '../../../common/hooks/useStrings' +import {ReviewTxState} from '../../../common/ReviewTxProvider' import {TokenItem} from '../../../common/TokenItem' import {FormattedOutputs, FormattedTx} from '../../../common/types' -export const OverviewTab = ({tx}: {tx: FormattedTx}) => { +export const OverviewTab = ({tx, operations}: {tx: FormattedTx; operations: ReviewTxState['operations']}) => { const {styles} = useStyles() const notOwnedOutputs = React.useMemo(() => tx.outputs.filter((output) => !output.ownAddress), [tx.outputs]) @@ -34,6 +35,8 @@ export const OverviewTab = ({tx}: {tx: FormattedTx}) => { + + ) } @@ -192,6 +195,32 @@ const ReceiverSection = ({notOwnedOutputs}: {notOwnedOutputs: FormattedOutputs}) ) } +const OperationsSection = ({operations}: {operations: ReviewTxState['operations']}) => { + if (operations === null || (Array.isArray(operations) && operations.length === 0)) return null + + return ( + + + + + + + {operations.map((operation, index) => { + if (index === 0) return operation + + return ( + <> + + + {operation} + + ) + })} + + + ) +} + const useStyles = () => { const {atoms, color} = useTheme() const styles = StyleSheet.create({ diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx index 3f8af27dcc..c1d728dbe5 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx @@ -5,11 +5,11 @@ import {FlatList, ScrollView, StyleSheet, Text, TouchableOpacity, TouchableOpaci import {Button} from '../../../../components/Button/Button' import {SafeArea} from '../../../../components/SafeArea' -import {ReviewTxRoutes, useUnsafeParams} from '../../../../kernel/navigation' import {useFormattedTx} from '../../common/hooks/useFormattedTx' import {useOnConfirm} from '../../common/hooks/useOnConfirm' import {useStrings} from '../../common/hooks/useStrings' import {useTxBody} from '../../common/hooks/useTxBody' +import {useReviewTx} from '../../common/ReviewTxProvider' import {OverviewTab} from './Overview/OverviewTab' import {UTxOsTab} from './UTxOs/UTxOsTab' @@ -18,20 +18,21 @@ const MaterialTab = createMaterialTopTabNavigator() export const ReviewTxScreen = () => { const {styles} = useStyles() const strings = useStrings() + const {unsignedTx, operations, onSuccess, onError} = useReviewTx() + + if (unsignedTx === null) throw new Error('ReviewTxScreen: missing unsignedTx') - // TODO: move this to a context - const params = useUnsafeParams() const {onConfirm} = useOnConfirm({ - unsignedTx: params.unsignedTx, - onSuccess: params.onSuccess, - onError: params.onError, + unsignedTx, + onSuccess, + onError, }) // TODO: add cbor arguments - const txBody = useTxBody({unsignedTx: params.unsignedTx}) + const txBody = useTxBody({unsignedTx}) const formatedTx = useFormattedTx(txBody) - const OverViewTabMemo = React.memo(() => ) + const OverViewTabMemo = React.memo(() => ) const UTxOsTabMemo = React.memo(() => ) return ( diff --git a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx index ee534acc81..aa4bfac370 100644 --- a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx +++ b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx @@ -22,6 +22,7 @@ import {useWalletNavigation} from '../../../../kernel/navigation' import {useSaveMemo} from '../../../../yoroi-wallets/hooks' import {YoroiEntry, YoroiSignedTx} from '../../../../yoroi-wallets/types/yoroi' import {TokenAmountItem} from '../../../Portfolio/common/TokenAmountItem/TokenAmountItem' +import {useReviewTx} from '../../../ReviewTx/common/ReviewTxProvider' import {useSearch} from '../../../Search/SearchContext' import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet' import {useNavigateTo, useOverridePreviousSendTxRoute} from '../../common/navigation' @@ -38,6 +39,7 @@ export const ListAmountsToSendScreen = () => { const navigation = useNavigation() const {track} = useMetrics() const {wallet} = useSelectedWallet() + const {unsignedTxChanged, onSuccessChanged, onErrorChanged} = useReviewTx() useOverridePreviousSendTxRoute('send-start-tx') @@ -45,14 +47,7 @@ export const ListAmountsToSendScreen = () => { navigation.setOptions({headerLeft: () => }) }, [navigation]) - const { - memo, - targets, - selectedTargetIndex, - tokenSelectedChanged, - amountRemoved, - unsignedTxChanged: yoroiUnsignedTxChanged, - } = useTransfer() + const {memo, targets, selectedTargetIndex, tokenSelectedChanged, amountRemoved} = useTransfer() const {saveMemo} = useSaveMemo({wallet}) const {amounts} = targets[selectedTargetIndex].entry const selectedTokensCounter = Object.keys(amounts).length @@ -108,8 +103,10 @@ export const ListAmountsToSendScreen = () => { // NOTE: update on multi target support createUnsignedTx([toYoroiEntry(targets[selectedTargetIndex].entry)], { onSuccess: (yoroiUnsignedTx) => { - yoroiUnsignedTxChanged(yoroiUnsignedTx) - navigateToTxReview({unsignedTx: yoroiUnsignedTx, onSuccess, onError}) + unsignedTxChanged(yoroiUnsignedTx) + onSuccessChanged(onSuccess) + onErrorChanged(onError) + navigateToTxReview() }, }) } diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/CollateralInfoModal.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/CollateralInfoModal.tsx new file mode 100644 index 0000000000..98a14c7025 --- /dev/null +++ b/apps/wallet-mobile/src/features/Settings/ManageCollateral/CollateralInfoModal.tsx @@ -0,0 +1,41 @@ +import {useTheme} from '@yoroi/theme' +import * as React from 'react' +import {StyleSheet, Text, View} from 'react-native' + +import {InfoModalIllustration} from './illustrations/InfoModalIllustration' +import {useStrings} from './strings' + +export const CollateralInfoModal = () => { + const {styles} = useStyles() + const strings = useStrings() + + return ( + + + + {strings.collateralInfoModalText} + + ) +} + +const useStyles = () => { + const {color, atoms} = useTheme() + const styles = StyleSheet.create({ + modal: { + ...atoms.flex_1, + ...atoms.px_lg, + ...atoms.align_center, + }, + modalText: { + ...atoms.text_center, + ...atoms.body_1_lg_regular, + color: color.text_gray_medium, + }, + }) + + const colors = { + iconColor: color.gray_900, + } + + return {styles, colors} as const +} diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.stories.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.stories.tsx deleted file mode 100644 index a62d55cffe..0000000000 --- a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import {storiesOf} from '@storybook/react-native' -import {TransferProvider} from '@yoroi/transfer' -import React from 'react' - -import {mocks as walletMocks} from '../../../../yoroi-wallets/mocks/wallet' -import {WalletManagerProviderMock} from '../../../../yoroi-wallets/mocks/WalletManagerProviderMock' -import {mocks as sendMocks} from '../../../Send/common/mocks' -import {ConfirmTxScreen} from './ConfirmTxScreen' - -storiesOf('Confirm Tx', module).add('initial', () => { - return ( - - - - - - ) -}) diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.tsx deleted file mode 100644 index a81b7b750c..0000000000 --- a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/ConfirmTxScreen.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import {useTheme} from '@yoroi/theme' -import {useTransfer} from '@yoroi/transfer' -import React, {useEffect} from 'react' -import {useIntl} from 'react-intl' -import {ScrollView, StyleSheet, View, ViewProps} from 'react-native' -import {SafeAreaView} from 'react-native-safe-area-context' - -import {ConfirmTx} from '../../../../components/ConfirmTx/ConfirmTx' -import {KeyboardAvoidingView} from '../../../../components/KeyboardAvoidingView/KeyboardAvoidingView' -import {Spacer} from '../../../../components/Spacer/Spacer' -import {ValidatedTextInput} from '../../../../components/ValidatedTextInput' -import {debugWalletInfo, features} from '../../../../kernel/features' -import globalMessages, {confirmationMessages, errorMessages, txLabels} from '../../../../kernel/i18n/global-messages' -import {useSetCollateralId} from '../../../../yoroi-wallets/cardano/utxoManager/useSetCollateralId' -import {useSaveMemo} from '../../../../yoroi-wallets/hooks' -import {YoroiSignedTx} from '../../../../yoroi-wallets/types/yoroi' -import {CurrentBalance} from '../../../Send/useCases/ConfirmTx/Summary/CurrentBalance' -import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet' -import {useNavigateTo} from '../navigation' -import {BalanceAfter} from './Summary/BalanceAfter' -import {Fees} from './Summary/Fees' -import {PrimaryTotal} from './Summary/PrimaryTotal' -import {SecondaryTotals} from './Summary/SecondaryTotals' - -export const ConfirmTxScreen = () => { - const strings = useStrings() - const styles = useStyles() - const {wallet, meta} = useSelectedWallet() - const navigateTo = useNavigateTo() - const [password, setPassword] = React.useState('') - const [useUSB, setUseUSB] = React.useState(false) - const {setCollateralId} = useSetCollateralId(wallet) - - const {memo, unsignedTx: yoroiUnsignedTx} = useTransfer() - - const {saveMemo} = useSaveMemo({wallet}) - - useEffect(() => { - if (features.prefillWalletInfo && __DEV__) { - setPassword(debugWalletInfo.PASSWORD) - } - }, []) - - const onSuccess = (signedTx: YoroiSignedTx) => { - navigateTo.submittedTx() - const collateralId = `${signedTx.signedTx.id}:0` - setCollateralId(collateralId) - if (memo.length > 0) { - saveMemo({txId: signedTx.signedTx.id, memo: memo.trim()}) - } - } - - const onError = () => { - navigateTo.failedTx() - } - - if (yoroiUnsignedTx === undefined) throw new Error('Missing yoroiUnsignedTx') - - return ( - - - - - - - - - - - - - - - - - - - - - - {!meta.isEasyConfirmationEnabled && !meta.isHW && ( - - )} - - - - - - - - ) -} - -const Actions = (props: ViewProps) => { - const styles = useStyles() - return -} - -const useStyles = () => { - const {color} = useTheme() - const styles = StyleSheet.create({ - root: { - backgroundColor: color.bg_color_max, - flex: 1, - }, - container: { - flex: 1, - paddingHorizontal: 16, - }, - actions: { - paddingTop: 16, - paddingHorizontal: 16, - }, - }) - return styles -} - -const useStrings = () => { - const intl = useIntl() - - return { - availableFunds: intl.formatMessage(globalMessages.availableFunds), - balanceAfterTx: intl.formatMessage(txLabels.balanceAfterTx), - total: intl.formatMessage(globalMessages.total), - password: intl.formatMessage(txLabels.password), - confirmButton: intl.formatMessage(confirmationMessages.commonButtons.confirmButton), - submittingTx: intl.formatMessage(txLabels.submittingTx), - pleaseWait: intl.formatMessage(globalMessages.pleaseWait), - generalTxError: { - title: intl.formatMessage(errorMessages.generalTxError.title), - message: intl.formatMessage(errorMessages.generalTxError.message), - }, - } -} diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/BalanceAfter.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/BalanceAfter.tsx deleted file mode 100644 index 44a25cc607..0000000000 --- a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/BalanceAfter.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from 'react' -import {useIntl} from 'react-intl' - -import {Text} from '../../../../../components/Text' -import {txLabels} from '../../../../../kernel/i18n/global-messages' -import {useBalances} from '../../../../../yoroi-wallets/hooks' -import {YoroiUnsignedTx} from '../../../../../yoroi-wallets/types/yoroi' -import {formatTokenWithSymbol} from '../../../../../yoroi-wallets/utils/format' -import {Amounts} from '../../../../../yoroi-wallets/utils/utils' -import {useSelectedWallet} from '../../../../WalletManager/common/hooks/useSelectedWallet' - -export const BalanceAfter = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx}) => { - const strings = useStrings() - const {wallet} = useSelectedWallet() - const balances = useBalances(wallet) - - const balancesAfter = Amounts.diff(balances, yoroiUnsignedTx.fee) - const primaryAmountAfter = Amounts.getAmount(balancesAfter, wallet.portfolioPrimaryTokenInfo.id) - - return ( - - {`${strings.balanceAfterTx}: ${formatTokenWithSymbol( - primaryAmountAfter.quantity, - wallet.portfolioPrimaryTokenInfo, - )}`} - - ) -} - -const useStrings = () => { - const intl = useIntl() - - return { - balanceAfterTx: intl.formatMessage(txLabels.balanceAfterTx), - } -} diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/Fees.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/Fees.tsx deleted file mode 100644 index 05ad1a135f..0000000000 --- a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/Fees.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react' -import {useIntl} from 'react-intl' - -import {Text} from '../../../../../components/Text' -import {txLabels} from '../../../../../kernel/i18n/global-messages' -import {YoroiUnsignedTx} from '../../../../../yoroi-wallets/types/yoroi' -import {formatTokenWithSymbol} from '../../../../../yoroi-wallets/utils/format' -import {Amounts} from '../../../../../yoroi-wallets/utils/utils' -import {useSelectedWallet} from '../../../../WalletManager/common/hooks/useSelectedWallet' - -export const Fees = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx}) => { - const strings = useStrings() - const {wallet} = useSelectedWallet() - const feeAmount = Amounts.getAmount(yoroiUnsignedTx.fee, wallet.portfolioPrimaryTokenInfo.id) - - return ( - - {`${strings.fees}: ${formatTokenWithSymbol(feeAmount.quantity, wallet.portfolioPrimaryTokenInfo)}`} - - ) -} - -const useStrings = () => { - const intl = useIntl() - - return { - fees: intl.formatMessage(txLabels.fees), - } -} diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/PrimaryTotal.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/PrimaryTotal.tsx deleted file mode 100644 index a34bfdebf5..0000000000 --- a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/PrimaryTotal.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import {useTheme} from '@yoroi/theme' -import * as React from 'react' -import {useIntl} from 'react-intl' -import {StyleSheet, View} from 'react-native' - -import {Text} from '../../../../../components/Text' -import globalMessages from '../../../../../kernel/i18n/global-messages' -import {YoroiUnsignedTx} from '../../../../../yoroi-wallets/types/yoroi' -import {formatTokenWithSymbol} from '../../../../../yoroi-wallets/utils/format' -import {Amounts} from '../../../../../yoroi-wallets/utils/utils' -import {useSelectedWallet} from '../../../../WalletManager/common/hooks/useSelectedWallet' - -export const PrimaryTotal = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx}) => { - const strings = useStrings() - const styles = useStyles() - const {wallet} = useSelectedWallet() - const primaryAmount = Amounts.getAmount( - Amounts.getAmountsFromEntries(yoroiUnsignedTx.entries), - wallet.portfolioPrimaryTokenInfo.id, - ) - - return ( - - {strings.total} - - - {formatTokenWithSymbol(primaryAmount.quantity, wallet.portfolioPrimaryTokenInfo)} - - - ) -} - -const useStrings = () => { - const intl = useIntl() - - return { - total: intl.formatMessage(globalMessages.total), - } -} -const useStyles = () => { - const {color} = useTheme() - const styles = StyleSheet.create({ - amount: { - color: color.secondary_600, - }, - }) - return styles -} diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/SecondaryTotals.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/SecondaryTotals.tsx deleted file mode 100644 index 318160016a..0000000000 --- a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/Summary/SecondaryTotals.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import {createUnknownTokenInfo, usePortfolioTokenInfo} from '@yoroi/portfolio' -import {useTheme} from '@yoroi/theme' -import {Balance, Portfolio} from '@yoroi/types' -import * as React from 'react' -import {StyleSheet, View} from 'react-native' - -import {Boundary} from '../../../../../components/Boundary/Boundary' -import {Text} from '../../../../../components/Text' -import {YoroiWallet} from '../../../../../yoroi-wallets/cardano/types' -import {YoroiUnsignedTx} from '../../../../../yoroi-wallets/types/yoroi' -import {formatTokenWithText} from '../../../../../yoroi-wallets/utils/format' -import {Amounts, Quantities} from '../../../../../yoroi-wallets/utils/utils' -import {useSelectedWallet} from '../../../../WalletManager/common/hooks/useSelectedWallet' - -export const SecondaryTotals = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx}) => { - const {wallet} = useSelectedWallet() - const secondaryAmounts = Amounts.remove(Amounts.getAmountsFromEntries(yoroiUnsignedTx.entries), [ - wallet.portfolioPrimaryTokenInfo.id, - ]) - const sortedAmounts = Amounts.toArray(secondaryAmounts).sort((a, b) => - Quantities.isGreaterThan(a.quantity, b.quantity) ? -1 : 1, - ) - - return ( - - {sortedAmounts.map((amount) => ( - - - - ))} - - ) -} - -const Amount = ({amount, wallet}: {amount: Balance.Amount; wallet: YoroiWallet}) => { - const styles = useStyles() - const {tokenInfo} = usePortfolioTokenInfo({ - network: wallet.networkManager.network, - id: amount.tokenId as Portfolio.Token.Id, - getTokenInfo: wallet.networkManager.tokenManager.api.tokenInfo, - primaryTokenInfo: wallet.portfolioPrimaryTokenInfo, - }) - - const info = - tokenInfo ?? - createUnknownTokenInfo({id: amount.tokenId as Portfolio.Token.Id, name: `Unknown token ${amount.tokenId}`}) - - return {formatTokenWithText(amount.quantity, info)} -} - -const useStyles = () => { - const {color} = useTheme() - const styles = StyleSheet.create({ - amount: { - color: color.secondary_600, - }, - }) - return styles -} diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/index.ts b/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/index.ts deleted file mode 100644 index 8e77dd2083..0000000000 --- a/apps/wallet-mobile/src/features/Settings/ManageCollateral/ConfirmTx/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ConfirmTxScreen' diff --git a/apps/wallet-mobile/src/features/Settings/ManageCollateral/InitialCollateralInfoModal.tsx b/apps/wallet-mobile/src/features/Settings/ManageCollateral/InitialCollateralInfoModal.tsx new file mode 100644 index 0000000000..589cd07a1f --- /dev/null +++ b/apps/wallet-mobile/src/features/Settings/ManageCollateral/InitialCollateralInfoModal.tsx @@ -0,0 +1,52 @@ +import {useTheme} from '@yoroi/theme' +import * as React from 'react' +import {StyleSheet, Text, View} from 'react-native' + +import {Button} from '../../../components/Button/Button' +import {Space} from '../../../components/Space/Space' +import {InfoModalIllustration} from './illustrations/InfoModalIllustration' +import {useStrings} from './strings' + +export const InitialCollateralInfoModal = ({onConfirm}: {onConfirm: () => void}) => { + const {styles} = useStyles() + const strings = useStrings() + + return ( + + + + {strings.collateralInfoModalText} + + + + +