diff --git a/apps/wallet-mobile/.storybook/storybook.requires.js b/apps/wallet-mobile/.storybook/storybook.requires.js index a41e78585f..21011eaba6 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"), @@ -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 99a3c8c92c..7b35f285ea 100644 --- a/apps/wallet-mobile/src/YoroiApp.tsx +++ b/apps/wallet-mobile/src/YoroiApp.tsx @@ -17,6 +17,7 @@ import {AuthProvider} from './features/Auth/AuthProvider' import {BrowserProvider} from './features/Discover/common/BrowserProvider' import {notificationManager} from './features/Notifications/useCases/common/notification-manager' 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' @@ -67,9 +68,11 @@ const Yoroi = () => { - - - + + + + + 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/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/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/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/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..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 @@ -6,22 +6,21 @@ 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 {ReviewTxState} from '../../../common/ReviewTxProvider' import {TokenItem} from '../../../common/TokenItem' -import {FormattedOutputs} from '../../../common/types' +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]) @@ -36,6 +35,8 @@ export const OverviewTab = ({tx}: {tx: FormattedTx}) => { + + ) } @@ -77,7 +78,7 @@ const FeeInfoItem = ({fee}: {fee: string}) => { {strings.feeLabel} - {fee} + {`-${fee}`} ) } @@ -92,20 +93,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 +146,10 @@ const SenderTokens = ({tx, notOwnedOutputs}: {tx: FormattedTx; notOwnedOutputs: - + - {notPrimaryTokenSent.map((token) => ( - + {notPrimaryTokenSent.map((token, index) => ( + ))} @@ -179,12 +185,42 @@ const ReceiverSection = ({notOwnedOutputs}: {notOwnedOutputs: FormattedOutputs}) {isScriptAddress ? strings.receiveToScriptLabel : strings.receiveToLabel}: -
+ + + {address} + + ) } +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({ @@ -249,6 +285,11 @@ const useStyles = () => { receiverSectionAddress: { maxWidth: 260, }, + addressText: { + ...atoms.flex_1, + ...atoms.body_2_md_regular, + color: color.text_gray_medium, + }, }) const colors = { @@ -285,7 +326,7 @@ const CreatedByInfoItem = () => { // 🚧 TODO: WIP 🚧 // eslint-disable-next-line @typescript-eslint/no-unused-vars -const ReceiverTokensSectionMultiReceiver = () => { +/* const ReceiverTokensSectionMultiReceiver = () => { const {styles} = useStyles() return ( @@ -294,7 +335,7 @@ const ReceiverTokensSectionMultiReceiver = () => { - + @@ -311,36 +352,36 @@ const ReceiverTokensSectionMultiReceiver = () => { - + - + - + - + - + - + - + - + - + - + - + ) -} +} */ // 🚧 TODO: WIP 🚧 -const ReceiverSectionLabel = () => { +/* const ReceiverSectionLabel = () => { const {styles, colors} = useStyles() return ( @@ -352,4 +393,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..c1d728dbe5 100644 --- a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx +++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx @@ -1,15 +1,15 @@ 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' -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,28 +18,43 @@ 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 ( - - - + + {() => ( + /* TODO: make scrollview general to use button border */ + + + + )} + + + + {() => ( + /* TODO: make scrollview general to use button border */ + + + + )} + @@ -59,11 +74,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/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} + + + + +