diff --git a/.github/actions/wallet-mobile/action.yml b/.github/actions/wallet-mobile/action.yml index f186ba075a..bffe957b67 100644 --- a/.github/actions/wallet-mobile/action.yml +++ b/.github/actions/wallet-mobile/action.yml @@ -5,7 +5,7 @@ inputs: node-version: description: "Node.js version to use" required: true - default: "16.19.0" + default: "18.19.1" runs: using: "composite" @@ -18,7 +18,7 @@ runs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" shell: bash - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} diff --git a/.tool-versions b/.tool-versions index 0f29bd47c8..def9920653 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,4 @@ -nodejs 18.0.0 +nodejs 18.19.1 rust 1.69.0 java adoptopenjdk-17.0.10+7 ruby 3.2.2 diff --git a/apps/wallet-mobile/.storybook/storybook.requires.js b/apps/wallet-mobile/.storybook/storybook.requires.js index 9795ba0634..ede4e04f23 100644 --- a/apps/wallet-mobile/.storybook/storybook.requires.js +++ b/apps/wallet-mobile/.storybook/storybook.requires.js @@ -129,6 +129,10 @@ const getStories = () => { "./src/features/Initialization/InitialScreen/InitialScreen.stories.tsx": require("../src/features/Initialization/InitialScreen/InitialScreen.stories.tsx"), "./src/features/Initialization/LanguagePickerScreen/LanguagePickerScreen.stories.tsx": require("../src/features/Initialization/LanguagePickerScreen/LanguagePickerScreen.stories.tsx"), "./src/features/Initialization/TermsOfServiceChangedScreen/TermsOfServiceChangedScreen.stories.tsx": require("../src/features/Initialization/TermsOfServiceChangedScreen/TermsOfServiceChangedScreen.stories.tsx"), + "./src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.stories.tsx": require("../src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.stories.tsx"), + "./src/features/Links/useCases/AskToRedirect/AskToRedirectScreen.stories.tsx": require("../src/features/Links/useCases/AskToRedirect/AskToRedirectScreen.stories.tsx"), + "./src/features/Links/useCases/RequestedAdaPaymentWithLinkScreen/RequestedAdaPaymentWithLink.stories.tsx": require("../src/features/Links/useCases/RequestedAdaPaymentWithLinkScreen/RequestedAdaPaymentWithLink.stories.tsx"), + "./src/features/Links/useCases/RequestedAdaPaymentWithLinkScreen/ShowDisclaimer/ShowDisclaimer.stories.tsx": require("../src/features/Links/useCases/RequestedAdaPaymentWithLinkScreen/ShowDisclaimer/ShowDisclaimer.stories.tsx"), "./src/features/Menu/Menu.stories.tsx": require("../src/features/Menu/Menu.stories.tsx"), "./src/features/Receive/common/AddressDetailCard/AddressDetailCard.stories.tsx": require("../src/features/Receive/common/AddressDetailCard/AddressDetailCard.stories.tsx"), "./src/features/Receive/common/AddressModal/AddressModal.stories.tsx": require("../src/features/Receive/common/AddressModal/AddressModal.stories.tsx"), diff --git a/apps/wallet-mobile/.tool-versions b/apps/wallet-mobile/.tool-versions index 0f29bd47c8..def9920653 100644 --- a/apps/wallet-mobile/.tool-versions +++ b/apps/wallet-mobile/.tool-versions @@ -1,4 +1,4 @@ -nodejs 18.0.0 +nodejs 18.19.1 rust 1.69.0 java adoptopenjdk-17.0.10+7 ruby 3.2.2 diff --git a/apps/wallet-mobile/scripts/request-ada-with-link-android.sh b/apps/wallet-mobile/scripts/request-ada-with-link-android.sh new file mode 100755 index 0000000000..7bda088cf4 --- /dev/null +++ b/apps/wallet-mobile/scripts/request-ada-with-link-android.sh @@ -0,0 +1,3 @@ +#!/bin/zsh + +adb shell am start -W -a android.intent.action.VIEW -d "yoroi://yoroi-wallet.com/w1/transfer/request/ada-with-link?link=web%252Bcardano%253Aaddr_test1qzp3w455j8hdusjyv38rkr3cas9hyydfkr3tpwsz2yt8mqxr3y3kdut55a40jff00qmg74686vz44v6k363md06qkq0qgvjva0%253Famount%253D10\&isSandbox=true\&isTestnet=false\&appId=yoroi\&message=hi+there\&walletId=c4832ba5-c03e-4bd8-93ee-52536f1b1747\&authorization=ac0692d3-bf34-44e2-b57d-53e4ce47666b\&signature=4B7C8DB8B485D176D426980EE8A0D8A600B305AE2D7DC835B537657E2DAE110A6F7C0A641EAA623A8C43568BFC9DDCED5CBEA5AD6768BDE89F084EEDDBEBA36A\&redirectTo=https%253A%252F%252Fyoroi-wallet.com" diff --git a/apps/wallet-mobile/scripts/request-ada-with-link-ios.sh b/apps/wallet-mobile/scripts/request-ada-with-link-ios.sh new file mode 100755 index 0000000000..d49da4c42e --- /dev/null +++ b/apps/wallet-mobile/scripts/request-ada-with-link-ios.sh @@ -0,0 +1,4 @@ +#!/bin/zsh + +xcrun simctl openurl booted "yoroi://yoroi-wallet.com/w1/transfer/request/ada-with-link?link=web%252Bcardano%253Aaddr_test1qzp3w455j8hdusjyv38rkr3cas9hyydfkr3tpwsz2yt8mqxr3y3kdut55a40jff00qmg74686vz44v6k363md06qkq0qgvjva0%253Famount%253D10&isSandbox=true&isTestnet=false&appId=yoroi&message=hi+there&walletId=c4832ba5-c03e-4bd8-93ee-52536f1b1747&authorization=ac0692d3-bf34-44e2-b57d-53e4ce47666b&signature=4B7C8DB8B485D176D426980EE8A0D8A600B305AE2D7DC835B537657E2DAE110A6F7C0A641EAA623A8C43568BFC9DDCED5CBEA5AD6768BDE89F084EEDDBEBA36A&redirectTo=https%253A%252F%252Fyoroi-wallet.com" + diff --git a/apps/wallet-mobile/src/AppNavigator.tsx b/apps/wallet-mobile/src/AppNavigator.tsx index d34c68b5d9..f84f4543d4 100644 --- a/apps/wallet-mobile/src/AppNavigator.tsx +++ b/apps/wallet-mobile/src/AppNavigator.tsx @@ -1,6 +1,7 @@ import {NavigationContainer, NavigationContainerRef} from '@react-navigation/native' import {createStackNavigator} from '@react-navigation/stack' import {isString} from '@yoroi/common' +import {TransferProvider} from '@yoroi/transfer' import * as React from 'react' import {defineMessages, useIntl} from 'react-intl' import {Alert, AppState, AppStateStatus, InteractionManager, Platform} from 'react-native' @@ -136,7 +137,9 @@ export const AppNavigator = () => { {() => ( - + + + )} diff --git a/apps/wallet-mobile/src/SelectedWallet/WalletSelection/WalletSelectionScreen.tsx b/apps/wallet-mobile/src/SelectedWallet/WalletSelection/WalletSelectionScreen.tsx index da0c82c62d..fa3660a14e 100644 --- a/apps/wallet-mobile/src/SelectedWallet/WalletSelection/WalletSelectionScreen.tsx +++ b/apps/wallet-mobile/src/SelectedWallet/WalletSelection/WalletSelectionScreen.tsx @@ -9,6 +9,7 @@ import {useStatusBar} from '../../components/hooks/useStatusBar' import {Icon} from '../../components/Icon' import {PleaseWaitModal} from '../../components/PleaseWaitModal' import {showErrorDialog} from '../../dialogs' +import {useLinksRequestWallet} from '../../features/Links/common/useLinksRequestWallet' import globalMessages, {errorMessages} from '../../i18n/global-messages' import {isNightly} from '../../legacy/config' import {useMetrics} from '../../metrics/metricsManager' @@ -27,6 +28,7 @@ import {useSetSelectedWallet, useSetSelectedWalletMeta} from '../Context' import {WalletListItem} from './WalletListItem' export const WalletSelectionScreen = () => { + useLinksRequestWallet() const strings = useStrings() const {navigateToTxHistory} = useWalletNavigation() const walletManager = useWalletManager() @@ -48,14 +50,7 @@ export const WalletSelectionScreen = () => { onSuccess: ([wallet, walletMeta]) => { selectWalletMeta(walletMeta) selectWallet(wallet) - - // fixes modal issue - // https://github.com/facebook/react-native/issues/32329 - // https://github.com/facebook/react-native/issues/33733 - // https://github.com/facebook/react-native/issues/29319 - InteractionManager.runAfterInteractions(() => { - navigateToTxHistory() - }) + navigateToTxHistory() }, onError: (error) => { InteractionManager.runAfterInteractions(() => { diff --git a/apps/wallet-mobile/src/TxHistory/TxHistoryNavigator.tsx b/apps/wallet-mobile/src/TxHistory/TxHistoryNavigator.tsx index db4051dd6c..ad2313f006 100644 --- a/apps/wallet-mobile/src/TxHistory/TxHistoryNavigator.tsx +++ b/apps/wallet-mobile/src/TxHistory/TxHistoryNavigator.tsx @@ -13,7 +13,6 @@ import { swapStorageMaker, } from '@yoroi/swap' import {Theme, useTheme} from '@yoroi/theme' -import {TransferProvider} from '@yoroi/transfer' import {Resolver, Swap} from '@yoroi/types' import _ from 'lodash' import React from 'react' @@ -138,299 +137,296 @@ export const TxHistoryNavigator = () => { return ( - - - - - - + + + + + - + + + {() => ( + + + + )} + + + + + + + + + + {() => ( + + + + )} + + + + {() => ( + + + + )} + + + + {() => ( + + + + )} + + + + + + + + + + + + + + + + + + + + + + {() => ( + + + + )} + + + + {() => ( + + + + )} + + + + {() => ( + + + + )} + + + - - - - {() => ( - - - - )} - - - - - - - - - - {() => ( - - - - )} - - - - {() => ( - - - - )} - - - - {() => ( - - - - )} - - - - - - - - - - - - - - - - - - - - - - {() => ( - - - - )} - - - - {() => ( - - - - )} - - - - {() => ( - - - - )} - - - - {() => ( - - - - )} - - - - - - - - - , - }} - /> - - - - null}} - /> - - - - {strings.receiveInfoText} - - - - - - - + {() => ( + + + + )} + + + + + + + + + , + }} + /> + + + + null}} + /> + + + + {strings.receiveInfoText} + + + + + + ) } diff --git a/apps/wallet-mobile/src/WalletNavigator.tsx b/apps/wallet-mobile/src/WalletNavigator.tsx index fdcbb2466a..532e5f8566 100644 --- a/apps/wallet-mobile/src/WalletNavigator.tsx +++ b/apps/wallet-mobile/src/WalletNavigator.tsx @@ -1,7 +1,6 @@ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs' import {RouteProp, useFocusEffect} from '@react-navigation/native' import {createStackNavigator} from '@react-navigation/stack' -import {useLinks} from '@yoroi/links' import React from 'react' import {defineMessages, useIntl} from 'react-intl' import {Keyboard, Platform} from 'react-native' @@ -10,6 +9,8 @@ import {VotingRegistration} from './Catalyst' import {Icon, OfflineBanner} from './components' import {DashboardNavigator} from './Dashboard' import {ShowExchangeResultOrderScreen} from './features/Exchange/useCases/ShowExchangeResultOrderScreen/ShowExchangeResultOrderScreen' +import {useLinksRequestAction} from './features/Links/common/useLinksRequestAction' +import {useLinksShowActionResult} from './features/Links/common/useLinksShowActionResult' import {MenuNavigator} from './features/Menu' import {SettingsScreenNavigator} from './features/Settings' import {GovernanceNavigator} from './features/Staking/Governance' @@ -154,14 +155,14 @@ const WalletTabNavigator = () => { const Stack = createStackNavigator() export const WalletNavigator = () => { - const initialRouteName = useInitialRouteName() + const initialRoute = useLinksShowActionResult() + useLinksRequestAction() - // initialRoute doens't update the state of the navigator, only at first render + // initialRoute doesn't update the state of the navigator, only at first render // https://reactnavigation.org/docs/auth-flow/ - if (initialRouteName === 'exchange-result') { + if (initialRoute === 'exchange-result') { return ( { return ( { ) } -const useInitialRouteName = () => { - const {action} = useLinks() - const routeName: keyof WalletStackRoutes = - action?.info.useCase === 'order/show-create-result' ? 'exchange-result' : 'wallet-selection' - return routeName -} - const messages = defineMessages({ transactionsButton: { id: 'components.common.navigation.transactionsButton', diff --git a/apps/wallet-mobile/src/components/ConfirmTx/ConfirmTx.tsx b/apps/wallet-mobile/src/components/ConfirmTx/ConfirmTx.tsx index c14389343d..6c0396adb3 100644 --- a/apps/wallet-mobile/src/components/ConfirmTx/ConfirmTx.tsx +++ b/apps/wallet-mobile/src/components/ConfirmTx/ConfirmTx.tsx @@ -269,6 +269,7 @@ export const ConfirmTx = ({ {...buttonProps} disabled={isConfirmationDisabled || isProcessing || disabled} testID="confirmTxButton" + shelleyTheme /> diff --git a/apps/wallet-mobile/src/features/Exchange/useCases/CreateExchangeOrderScreen/CreateExchangeOrderScreen.tsx b/apps/wallet-mobile/src/features/Exchange/useCases/CreateExchangeOrderScreen/CreateExchangeOrderScreen.tsx index 6f6bfc9cb0..417e7a25ea 100644 --- a/apps/wallet-mobile/src/features/Exchange/useCases/CreateExchangeOrderScreen/CreateExchangeOrderScreen.tsx +++ b/apps/wallet-mobile/src/features/Exchange/useCases/CreateExchangeOrderScreen/CreateExchangeOrderScreen.tsx @@ -38,9 +38,10 @@ export const CreateExchangeOrderScreen = () => { const {orderType, canExchange, providerId, provider, amount, referralLink: managerReferralLink} = useExchange() const providers = useExchangeProvidersByOrderType({orderType, providerListByOrderType: provider.list.byOrderType}) - const providerSelected = Object.fromEntries(providers)[providerId] - const fee = providerSelected.supportedOrders?.[orderType]?.fee ?? 0 - const Logo = providerSelected.id === 'banxa' ? BanxaLogo : EncryptusLogo + const providerSelected = new Map(providers).get(providerId) + const fee = providerSelected?.supportedOrders[orderType]?.fee ?? 0 + + const Logo = providerSelected?.id === 'banxa' ? BanxaLogo : EncryptusLogo const wallet = useSelectedWallet() @@ -127,7 +128,7 @@ export const CreateExchangeOrderScreen = () => { } rightAdornment={} diff --git a/apps/wallet-mobile/src/features/Exchange/useCases/ShowExchangeResultOrderScreen/ShowExchangeResultOrderScreen.tsx b/apps/wallet-mobile/src/features/Exchange/useCases/ShowExchangeResultOrderScreen/ShowExchangeResultOrderScreen.tsx index 162b01e922..8115688553 100644 --- a/apps/wallet-mobile/src/features/Exchange/useCases/ShowExchangeResultOrderScreen/ShowExchangeResultOrderScreen.tsx +++ b/apps/wallet-mobile/src/features/Exchange/useCases/ShowExchangeResultOrderScreen/ShowExchangeResultOrderScreen.tsx @@ -1,5 +1,5 @@ import {exchangeApiMaker, exchangeManagerMaker, ExchangeProvider} from '@yoroi/exchange' -import {LinksYoroiExchangeShowCreateResultParams, useLinks} from '@yoroi/links' +import {LinksExchangeShowCreateResultParams, useLinks} from '@yoroi/links' import {useTheme} from '@yoroi/theme' import * as React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' @@ -35,7 +35,7 @@ export const ShowExchangeResultOrderScreen = () => { // NOTE: should never happen, caller should handle it if (action == null || action.info.useCase !== 'order/show-create-result') return null - const params: LinksYoroiExchangeShowCreateResultParams = action.info.params + const params: LinksExchangeShowCreateResultParams = action.info.params const handleOnClose = () => { actionFinished() @@ -121,7 +121,7 @@ const providerName = { banxa: 'Banxa', } as const -const sanitizeParams = (params: LinksYoroiExchangeShowCreateResultParams) => { +const sanitizeParams = (params: LinksExchangeShowCreateResultParams) => { const showOrderDetails = params.coin != null && params.coinAmount != null && params.fiat != null && params.fiatAmount != null diff --git a/apps/wallet-mobile/src/features/Links/common/constants.ts b/apps/wallet-mobile/src/features/Links/common/constants.ts index e3a014324c..75019f22f1 100644 --- a/apps/wallet-mobile/src/features/Links/common/constants.ts +++ b/apps/wallet-mobile/src/features/Links/common/constants.ts @@ -1,6 +1,6 @@ import {freeze} from 'immer' -export const knownApps: Readonly> = freeze( +export const trustedApps: Readonly> = freeze( new Map(__DEV__ ? [['yoroi', {name: 'Yoroi Test', vkey: 'w1'}]] : []), true, ) diff --git a/apps/wallet-mobile/src/features/Links/common/useDeepLinkWatcher.tsx b/apps/wallet-mobile/src/features/Links/common/useDeepLinkWatcher.tsx index 91e57ef44f..7ed597f6f7 100644 --- a/apps/wallet-mobile/src/features/Links/common/useDeepLinkWatcher.tsx +++ b/apps/wallet-mobile/src/features/Links/common/useDeepLinkWatcher.tsx @@ -5,28 +5,24 @@ import {Linking} from 'react-native' import {Logger} from '../../../yoroi-wallets/logging' export const useDeepLinkWatcher = () => { - const links = useLinks() + const {actionStarted} = useLinks() const processLink = React.useCallback( (url: string) => { - if (links.action !== null) { - Logger.debug('useDeepLinksWatcher :: action in progress, ignoring...') - return - } - const action = linksYoroiParser(url) - if (action == null) { + const parsedAction = linksYoroiParser(url) + if (parsedAction == null) { Logger.debug('useDeepLinksWatcher :: link is malformated, ignoring...') return } - if (action.params?.isSandbox === true && __DEV__ === false) { + if (parsedAction.params?.isSandbox === true && __DEV__ === false) { Logger.debug('useDeepLinksWatcher :: link is sandboxed, ignoring...') return } // TODO: implement isTrusted if signature was provided and doesn't match with authorization ignore it - Logger.debug('action', JSON.stringify(action, null, 2)) - links.actionStarted({info: action, isTrusted: false}) + Logger.debug('parsedAction', JSON.stringify(parsedAction, null, 2)) + actionStarted({info: parsedAction, isTrusted: false}) }, - [links], + [actionStarted], ) React.useEffect(() => { diff --git a/apps/wallet-mobile/src/features/Links/common/useLinksRequestAction.tsx b/apps/wallet-mobile/src/features/Links/common/useLinksRequestAction.tsx new file mode 100644 index 0000000000..d6340edb16 --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/common/useLinksRequestAction.tsx @@ -0,0 +1,101 @@ +import {linksCardanoModuleMaker, LinksTransferRequestAdaWithLinkParams, LinksYoroiAction, useLinks} from '@yoroi/links' +import {useTransfer} from '@yoroi/transfer' +import * as React from 'react' +import {InteractionManager, Keyboard} from 'react-native' + +import {useModal} from '../../../components/Modal/ModalContext' +import {useSelectedWalletContext} from '../../../SelectedWallet' +import {Logger} from '../../../yoroi-wallets/logging' +import {asQuantity, Quantities} from '../../../yoroi-wallets/utils/utils' +import {RequestedAdaPaymentWithLinkScreen} from '../useCases/RequestedAdaPaymentWithLinkScreen/RequestedAdaPaymentWithLinkScreen' +import {useNavigateTo} from './useNavigationTo' +import {useStrings} from './useStrings' + +const heightBreakpoint = 467 +export const useLinksRequestAction = () => { + const strings = useStrings() + const {action, actionFinished} = useLinks() + const {openModal, closeModal} = useModal() + const [wallet] = useSelectedWalletContext() + const navigateTo = useNavigateTo() + + const {memoChanged, receiverResolveChanged, amountChanged, reset, redirectToChanged} = useTransfer() + const startTransferWithLink = React.useCallback( + (action: LinksYoroiAction, decimals: number) => { + Logger.debug('startTransferWithLink', action, decimals) + if (action.info.useCase === 'request/ada-with-link') { + reset() + try { + const link = decodeURIComponent(action.info.params.link) + const parsedCardanoLink = linksCardanoModuleMaker().parse(link) + if (parsedCardanoLink) { + const redirectTo = action.info.params.redirectTo + if (redirectTo != null) redirectToChanged(decodeURIComponent(redirectTo)) + + const {address: receiver, amount, memo} = parsedCardanoLink.params + const ptAmount = Quantities.integer(asQuantity(amount ?? 0), decimals) + memoChanged(memo ?? '') + receiverResolveChanged(receiver ?? '') + amountChanged(ptAmount) + closeModal() + actionFinished() + navigateTo.startTransfer() + } + } catch (error) { + // TODO: revisit it should display an alert + closeModal() + actionFinished() + Logger.error('Error parsing Cardano link', error) + } + } + }, + [ + actionFinished, + amountChanged, + closeModal, + memoChanged, + navigateTo, + receiverResolveChanged, + redirectToChanged, + reset, + ], + ) + + const openRequestedPaymentAdaWithLink = React.useCallback( + ({params, isTrusted}: {params: LinksTransferRequestAdaWithLinkParams; isTrusted: boolean}, decimals: number) => { + Keyboard.dismiss() + const title = isTrusted ? strings.trustedPaymentRequestedTitle : strings.untrustedPaymentRequestedTitle + const handleOnContinue = () => + startTransferWithLink( + { + info: { + version: 1, + feature: 'transfer', + useCase: 'request/ada-with-link', + params, + }, + isTrusted, + }, + decimals, + ) + + const content = ( + + ) + + openModal(title, content, heightBreakpoint) + }, + [strings.trustedPaymentRequestedTitle, strings.untrustedPaymentRequestedTitle, startTransferWithLink, openModal], + ) + + React.useEffect(() => { + InteractionManager.runAfterInteractions(() => { + if (action?.info.useCase === 'request/ada-with-link' && wallet != null) { + openRequestedPaymentAdaWithLink( + {params: action.info.params, isTrusted: action.isTrusted}, + wallet.primaryTokenInfo.decimals ?? 0, + ) + } + }) + }, [action?.info.params, action?.info.useCase, action?.isTrusted, openRequestedPaymentAdaWithLink, wallet]) +} diff --git a/apps/wallet-mobile/src/features/Links/common/useLinksRequestRedirect.tsx b/apps/wallet-mobile/src/features/Links/common/useLinksRequestRedirect.tsx new file mode 100644 index 0000000000..1188df6470 --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/common/useLinksRequestRedirect.tsx @@ -0,0 +1,28 @@ +import * as React from 'react' +import {InteractionManager, Keyboard} from 'react-native' + +import {useModal} from '../../../components/Modal/ModalContext' +import {isEmptyString} from '../../../utils/utils' +import {AskToRedirectScreen} from '../useCases/AskToRedirect/AskToRedirectScreen' +import {useStrings} from './useStrings' + +const heightBreakpoint = 367 +export const useLinksRequestRedirect = (redirectTo?: string) => { + const strings = useStrings() + const {openModal} = useModal() + + const askToRedirect = React.useCallback( + (link: string) => { + Keyboard.dismiss() + const content = + openModal(strings.askToRedirectTitle, content, heightBreakpoint) + }, + [openModal, strings.askToRedirectTitle], + ) + + React.useEffect(() => { + InteractionManager.runAfterInteractions(() => { + if (!isEmptyString(redirectTo)) askToRedirect(redirectTo) + }) + }, [redirectTo, askToRedirect]) +} diff --git a/apps/wallet-mobile/src/features/Links/common/useLinksRequestWallet.tsx b/apps/wallet-mobile/src/features/Links/common/useLinksRequestWallet.tsx new file mode 100644 index 0000000000..5b095a2ffd --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/common/useLinksRequestWallet.tsx @@ -0,0 +1,29 @@ +import {useLinks} from '@yoroi/links' +import * as React from 'react' +import {InteractionManager, Keyboard} from 'react-native' + +import {useModal} from '../../../components/Modal/ModalContext' +import {useSelectedWalletContext} from '../../../SelectedWallet' +import {AskToOpenWalletScreen} from '../useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen' +import {useStrings} from './useStrings' + +const heightBreakpoint = 367 +export const useLinksRequestWallet = () => { + const strings = useStrings() + const {openModal} = useModal() + const [wallet] = useSelectedWalletContext() + const {action} = useLinks() + + const askToOpenAWallet = React.useCallback(() => { + Keyboard.dismiss() + const content = + openModal(strings.askToOpenAWalletTitle, content, heightBreakpoint) + }, [openModal, strings.askToOpenAWalletTitle]) + + React.useEffect(() => { + InteractionManager.runAfterInteractions(() => { + const isWalletRequested = action?.info.useCase === 'request/ada-with-link' && wallet == null + if (isWalletRequested) askToOpenAWallet() + }) + }, [askToOpenAWallet, action?.info.useCase, wallet]) +} diff --git a/apps/wallet-mobile/src/features/Links/common/useLinksShowActionResult.tsx b/apps/wallet-mobile/src/features/Links/common/useLinksShowActionResult.tsx new file mode 100644 index 0000000000..5c53508296 --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/common/useLinksShowActionResult.tsx @@ -0,0 +1,11 @@ +import {useLinks} from '@yoroi/links' +import * as React from 'react' + +export const useLinksShowActionResult = () => { + const {action} = useLinks() + const initialRoute = action?.info.useCase === 'order/show-create-result' ? 'exchange-result' : 'wallet-selection' + + return React.useMemo(() => { + return initialRoute + }, [initialRoute]) +} diff --git a/apps/wallet-mobile/src/features/Links/common/useNavigationTo.tsx b/apps/wallet-mobile/src/features/Links/common/useNavigationTo.tsx new file mode 100644 index 0000000000..7eeffa6c27 --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/common/useNavigationTo.tsx @@ -0,0 +1,11 @@ +import * as React from 'react' + +import {useWalletNavigation} from '../../../navigation' + +export const useNavigateTo = () => { + const walletNavigation = useWalletNavigation() + + return React.useRef({ + startTransfer: () => walletNavigation.navigateToStartTransfer(), + } as const).current +} diff --git a/apps/wallet-mobile/src/features/Links/common/useStrings.ts b/apps/wallet-mobile/src/features/Links/common/useStrings.ts new file mode 100644 index 0000000000..82faa31d35 --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/common/useStrings.ts @@ -0,0 +1,67 @@ +import * as React from 'react' +import {defineMessages, useIntl} from 'react-intl' + +import globalMessages from '../../../i18n/global-messages' + +export const useStrings = () => { + const intl = useIntl() + + return React.useRef({ + trustedPaymentRequestedTitle: intl.formatMessage(messages.trustedPaymentRequestedTitle), + trustedPaymentRequestedDescription: intl.formatMessage(messages.trustedPaymentRequestedDescription), + untrustedPaymentRequestedTitle: intl.formatMessage(messages.untrustedPaymentRequestedTitle), + untrustedPaymentRequestedDescription: intl.formatMessage(messages.untrustedPaymentRequestedDescription), + + askToOpenAWalletTitle: intl.formatMessage(messages.askToOpenAWalletTitle), + askToOpenAWalletDescription: intl.formatMessage(messages.askToOpenAWalletDescription), + askToRedirectTitle: intl.formatMessage(messages.askToRedirectTitle), + askToRedirectDescription: intl.formatMessage(messages.askToRedirectDescription), + + unknown: intl.formatMessage(globalMessages.unknown), + disclaimer: intl.formatMessage(globalMessages.disclaimer), + + ok: intl.formatMessage(globalMessages.ok), + cancel: intl.formatMessage(globalMessages.cancel), + continue: intl.formatMessage(globalMessages.continue), + } as const).current +} + +export const messages = Object.freeze( + defineMessages({ + trustedPaymentRequestedTitle: { + id: 'links.trusted.paymentRequested.title', + defaultMessage: '!!!Payment requested', + }, + trustedPaymentRequestedDescription: { + id: 'links.trusted.paymentRequested.description', + defaultMessage: '!!!A payment has been requested.', + }, + untrustedPaymentRequestedTitle: { + id: 'links.untrusted.paymentRequested.title', + defaultMessage: '!!!Payment requested', + }, + untrustedPaymentRequestedDescription: { + id: 'links.untrusted.paymentRequested.description', + defaultMessage: '!!!A payment has been requested.', + }, + + askToOpenAWalletTitle: { + id: 'links.askToOpenAWallet.title', + defaultMessage: '!!!Open a wallet', + }, + askToOpenAWalletDescription: { + id: 'links.askToOpenAWallet.description', + defaultMessage: '!!!To continue, open a wallet.', + }, + + askToRedirectTitle: { + id: 'links.askToRedirect.title', + defaultMessage: '!!!Redirect available', + }, + askToRedirectDescription: { + id: 'links.askToRedirect.description', + defaultMessage: + '!!!The caller that request this action has provided a way for Yoroi to return to their application, would you like to be redirected?', + }, + }), +) diff --git a/apps/wallet-mobile/src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.stories.tsx b/apps/wallet-mobile/src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.stories.tsx new file mode 100644 index 0000000000..1b79bd1998 --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.stories.tsx @@ -0,0 +1,21 @@ +import {storiesOf} from '@storybook/react-native' +import React from 'react' +import {StyleSheet, View} from 'react-native' + +import {ModalProvider} from '../../../../components' +import {AskToOpenWalletScreen} from './AskToOpenAWalletScreen' + +storiesOf('Links AskToOpenAWallet', module) + .addDecorator((story) => ( + + {story()} + + )) + .add('initial', () => ) + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + }, +}) diff --git a/apps/wallet-mobile/src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.tsx b/apps/wallet-mobile/src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.tsx new file mode 100644 index 0000000000..9cfcae0d2c --- /dev/null +++ b/apps/wallet-mobile/src/features/Links/useCases/AskToOpenAWalletScreen/AskToOpenAWalletScreen.tsx @@ -0,0 +1,65 @@ +import {useLinks} from '@yoroi/links' +import {useTheme} from '@yoroi/theme' +import * as React from 'react' +import {ScrollView, StyleSheet, Text, View, ViewProps} from 'react-native' +import {SafeAreaView} from 'react-native-safe-area-context' + +import {Button, Spacer, useModal} from '../../../../components' +import {useStrings} from '../../common/useStrings' + +export const AskToOpenWalletScreen = () => { + const strings = useStrings() + const {styles} = useStyles() + const {closeModal} = useModal() + const {actionFinished} = useLinks() + + const handleOnCancel = () => { + actionFinished() + closeModal() + } + + // TODO: revisit check with product size and copy + return ( + + + {strings.askToOpenAWalletDescription} + + + + + +