From 29b0d9d3502f701f3b02f86e11359d345d20faa2 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Thu, 5 Oct 2023 11:48:04 +0200 Subject: [PATCH] feat(send): update Send flow --- e2e/lightning.e2e.js | 7 +- e2e/onchain.e2e.js | 8 +- src/components/AuthWidget.tsx | 2 +- src/components/ContactImage.tsx | 21 ++ src/components/NavigationHeader.tsx | 8 +- src/components/ProfileImage.tsx | 9 +- src/components/SlashtagsProvider2.tsx | 2 +- .../bottom-sheet/SendNavigation.tsx | 11 +- src/screens/Scanner/MainScanner.tsx | 2 +- src/screens/Scanner/ScannerComponent.tsx | 22 +- src/screens/Settings/AddressViewer/index.tsx | 2 +- src/screens/Wallets/Send/Address.tsx | 147 ++++++++ src/screens/Wallets/Send/Amount.tsx | 10 +- src/screens/Wallets/Send/Contacts.tsx | 3 +- src/screens/Wallets/Send/Recipient.tsx | 314 +++++++----------- src/screens/Wallets/Send/ReviewAndSend.tsx | 8 +- src/screens/Wallets/Send/Scanner.tsx | 16 +- src/store/actions/actions.ts | 1 + src/store/actions/slashtags.ts | 4 + src/store/index.ts | 2 +- src/store/migrations/index.ts | 14 +- src/store/reducers/slashtags.ts | 24 ++ src/store/reselect/slashtags.ts | 5 + src/store/shapes/slashtags.ts | 1 + src/store/types/slashtags.ts | 1 + src/utils/i18n/locales/en/other.json | 3 +- src/utils/i18n/locales/en/slashtags.json | 2 +- src/utils/i18n/locales/en/wallet.json | 6 +- src/utils/lnurl.ts | 10 +- src/utils/scanner.ts | 36 +- 30 files changed, 437 insertions(+), 264 deletions(-) create mode 100644 src/components/ContactImage.tsx create mode 100644 src/screens/Wallets/Send/Address.tsx diff --git a/e2e/lightning.e2e.js b/e2e/lightning.e2e.js index af2a731ee..2d29ba62c 100644 --- a/e2e/lightning.e2e.js +++ b/e2e/lightning.e2e.js @@ -200,9 +200,10 @@ d('Lightning', () => { memo: note2, }); await element(by.id('Send')).tap(); + await element(by.id('RecipientManual')).tap(); await element(by.id('RecipientInput')).replaceText(invoice3); await element(by.id('RecipientInput')).tapReturnKey(); - await element(by.id('ContinueRecipient')).tap(); + await element(by.id('AddressContinue')).tap(); await element( by.id('N1').withAncestor(by.id('SendAmountNumberPad')), ).multiTap(3); @@ -223,10 +224,10 @@ d('Lightning', () => { value: '1000', }); await element(by.id('Send')).tap(); + await element(by.id('RecipientManual')).tap(); await element(by.id('RecipientInput')).replaceText(invoice4); await element(by.id('RecipientInput')).tapReturnKey(); - await element(by.id('ContinueRecipient')).tap(); - await element(by.id('ContinueAmount')).tap(); // FIXME: this should not be needed + await element(by.id('AddressContinue')).tap(); // Review & Send await expect(element(by.id('TagsAddSend'))).toBeVisible(); diff --git a/e2e/onchain.e2e.js b/e2e/onchain.e2e.js index 576ffdf1f..30fc50e61 100644 --- a/e2e/onchain.e2e.js +++ b/e2e/onchain.e2e.js @@ -97,10 +97,10 @@ d('Onchain', () => { const coreAddress = await rpc.getNewAddress(); await element(by.id('Send')).tap(); - await sleep(1000); // animation + await element(by.id('RecipientManual')).tap(); await element(by.id('RecipientInput')).replaceText(coreAddress); await element(by.id('RecipientInput')).tapReturnKey(); - await element(by.id('ContinueRecipient')).tap(); + await element(by.id('AddressContinue')).tap(); // Amount / NumberPad await element(by.id('SendNumberPadMax')).tap(); @@ -261,10 +261,10 @@ d('Onchain', () => { await element(by.id('NavigationClose')).tap(); await element(by.id('Send')).tap(); - await sleep(1000); // animation + await element(by.id('RecipientManual')).tap(); await element(by.id('RecipientInput')).replaceText(coreAddress); await element(by.id('RecipientInput')).tapReturnKey(); - await element(by.id('ContinueRecipient')).tap(); + await element(by.id('AddressContinue')).tap(); // enter amount that would leave dust let { label: amount } = await element( diff --git a/src/components/AuthWidget.tsx b/src/components/AuthWidget.tsx index 84a49d570..b3c211152 100644 --- a/src/components/AuthWidget.tsx +++ b/src/components/AuthWidget.tsx @@ -51,7 +51,7 @@ const AuthWidget = ({ const message = e.message === 'channel closed' ? t('auth_error_peer') - : `An error occured: ${e.message}`; + : `An error occurred: ${e.message}`; showToast({ type: 'error', diff --git a/src/components/ContactImage.tsx b/src/components/ContactImage.tsx new file mode 100644 index 000000000..dbf4a02b0 --- /dev/null +++ b/src/components/ContactImage.tsx @@ -0,0 +1,21 @@ +import React, { ReactElement } from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import { useProfile2 } from '../hooks/slashtags2'; +import ProfileImage from './ProfileImage'; + +const ContactImage = ({ + url, + size = 24, + style, +}: { + url: string; + size?: number; + style?: StyleProp; +}): ReactElement => { + const { profile } = useProfile2(url); + return ( + + ); +}; + +export default ContactImage; diff --git a/src/components/NavigationHeader.tsx b/src/components/NavigationHeader.tsx index c1d03631d..ca545ce9f 100644 --- a/src/components/NavigationHeader.tsx +++ b/src/components/NavigationHeader.tsx @@ -87,14 +87,14 @@ const NavigationHeader = ({ const showBack = Boolean(displayBackButton && navigation.canGoBack()); const numberOfActions = useMemo(() => { - if (onActionPress && onClosePress) { + if (actionIcon && onClosePress) { return 2; - } else if (showBack || onActionPress || onClosePress) { + } else if (showBack || actionIcon || onClosePress) { return 1; } else { return 0; } - }, [onActionPress, onClosePress, showBack]); + }, [actionIcon, onClosePress, showBack]); const actionColumn = useMemo( () => [ @@ -123,7 +123,7 @@ const NavigationHeader = ({ - {onActionPress && ( + {actionIcon && ( ; size: number; }): JSX.Element => { const { gray5 } = useColors(); @@ -38,13 +38,12 @@ const ProfileImage = ({ overflow: 'hidden', height: size, width: size, - ...style, }), - [xml, size, style, gray5], + [xml, gray5, size], ); return ( - + {xml ? ( ) : image ? ( diff --git a/src/components/SlashtagsProvider2.tsx b/src/components/SlashtagsProvider2.tsx index c0f1f0f1e..9e99984f2 100644 --- a/src/components/SlashtagsProvider2.tsx +++ b/src/components/SlashtagsProvider2.tsx @@ -129,7 +129,7 @@ export const SlashtagsProvider2 = ({ type: 'error', title: 'Data Connection Issue', description: - 'An error occured: Could not load primary key from keychain.', + 'An error occurred: Could not load primary key from keychain.', }); return; } diff --git a/src/navigation/bottom-sheet/SendNavigation.tsx b/src/navigation/bottom-sheet/SendNavigation.tsx index 7653e45eb..3d475ef9c 100644 --- a/src/navigation/bottom-sheet/SendNavigation.tsx +++ b/src/navigation/bottom-sheet/SendNavigation.tsx @@ -17,8 +17,9 @@ import Tags from '../../screens/Wallets/Send/Tags'; import AutoRebalance from '../../screens/Wallets/Send/AutoRebalance'; import PinCheck from '../../screens/Wallets/Send/PinCheck'; import Result from '../../screens/Wallets/Send/Result'; -import Scanner from '../../screens/Wallets/Send/Scanner'; import Contacts from '../../screens/Wallets/Send/Contacts'; +import Address from '../../screens/Wallets/Send/Address'; +import Scanner from '../../screens/Wallets/Send/Scanner'; import CoinSelection from '../../screens/Wallets/Send/CoinSelection'; import { NavigationContainer } from '../../styles/components'; import { TProcessedData } from '../../utils/scanner'; @@ -42,9 +43,10 @@ export type SendNavigationProp = NativeStackNavigationProp; export type SendStackParamList = { PinCheck: { onSuccess: () => void }; Recipient: undefined; - Amount: undefined; - Scanner: { onScan: (data: TProcessedData) => void } | undefined; Contacts: undefined; + Address: undefined; + Scanner: { onScan: (data: TProcessedData) => void } | undefined; + Amount: undefined; CoinSelection: undefined; FeeRate: undefined; FeeCustom: undefined; @@ -127,8 +129,9 @@ const SendNavigation = (): ReactElement => { initialRouteName={initialRouteName} screenOptions={screenOptions}> - + + diff --git a/src/screens/Scanner/MainScanner.tsx b/src/screens/Scanner/MainScanner.tsx index 2337b9015..0d96a0a8f 100644 --- a/src/screens/Scanner/MainScanner.tsx +++ b/src/screens/Scanner/MainScanner.tsx @@ -65,7 +65,7 @@ const ScannerScreen = ({ diff --git a/src/screens/Scanner/ScannerComponent.tsx b/src/screens/Scanner/ScannerComponent.tsx index 3052eafc7..13c5e0832 100644 --- a/src/screens/Scanner/ScannerComponent.tsx +++ b/src/screens/Scanner/ScannerComponent.tsx @@ -100,7 +100,9 @@ const ScannerComponent = ({ } } catch (err) { console.error('Failed to open image file: ', err); - showError('Sorry. An error occured when trying to open this image file.'); + showError( + 'Sorry. An error occurred when trying to open this image file.', + ); } finally { setIsChoosingFile(false); } @@ -114,6 +116,7 @@ const ScannerComponent = ({ const TopBackground = bottomSheet ? GradientView : BlurView; const Background = bottomSheet ? View : BlurView; + const size = dimensions.width - 16 * 2; return ( - + + {bottomSheet && } ): ReactElement => { + const colors = useColors(); + const sdk = useSlashtagsSDK(); + const { t } = useTranslation('wallet'); + const { keyboardShown } = useKeyboard(); + const [textFieldValue, setTextFieldValue] = useState(''); + const [isValid, setIsValid] = useState(false); + const selectedWallet = useSelector(selectedWalletSelector); + const selectedNetwork = useSelector(selectedNetworkSelector); + + const onChangeText = async (text: string): Promise => { + const diff = Math.abs(text.length - textFieldValue.length); + const hasPasted = diff > 1; + + setTextFieldValue(text); + + const result = await validateInputData({ + data: text, + source: 'send', + sdk, + showErrors: hasPasted, + }); + if (result.isErr()) { + setIsValid(false); + } else { + setIsValid(true); + } + }; + + const onContinue = async (): Promise => { + await Keyboard.dismiss(); + + await processInputData({ + data: textFieldValue, + source: 'send', + sdk, + selectedNetwork, + selectedWallet, + }); + }; + + return ( + + + + + {t('send_to')} + + + + + + + +