From d23c5f6d6e95f63bdaca5039b489fdbf70b96357 Mon Sep 17 00:00:00 2001 From: Ivan Vershigora Date: Wed, 28 Jun 2023 13:34:24 +0100 Subject: [PATCH] feat: lnurl withdraw specify amount --- .../bottom-sheet/LNURLWithdrawNavigation.tsx | 69 ++++++ src/navigation/root/RootNavigator.tsx | 2 + src/navigation/types/index.ts | 4 + src/screens/Wallets/LNURLWithdraw/Amount.tsx | 219 ++++++++++++++++++ src/screens/Wallets/LNURLWithdraw/Confirm.tsx | 101 ++++++++ .../Wallets/LNURLWithdraw/LNURLWNumberpad.tsx | 60 +++++ src/store/shapes/ui.ts | 1 + src/store/types/ui.ts | 3 + src/utils/i18n/locales/en/wallet.json | 8 +- src/utils/lnurl.ts | 28 +-- src/utils/scanner.ts | 37 ++- 11 files changed, 499 insertions(+), 33 deletions(-) create mode 100644 src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx create mode 100644 src/screens/Wallets/LNURLWithdraw/Amount.tsx create mode 100644 src/screens/Wallets/LNURLWithdraw/Confirm.tsx create mode 100644 src/screens/Wallets/LNURLWithdraw/LNURLWNumberpad.tsx diff --git a/src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx b/src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx new file mode 100644 index 000000000..d6917d334 --- /dev/null +++ b/src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx @@ -0,0 +1,69 @@ +import React, { ReactElement, memo } from 'react'; +import { useSelector } from 'react-redux'; +import { + NativeStackNavigationOptions, + NativeStackNavigationProp, + createNativeStackNavigator, +} from '@react-navigation/native-stack'; +import { LNURLWithdrawParams } from 'js-lnurl'; + +import BottomSheetWrapper from '../../components/BottomSheetWrapper'; +import { __E2E__ } from '../../constants/env'; +import { useSnapPoints } from '../../hooks/bottomSheet'; +import Amount from '../../screens/Wallets/LNURLWithdraw/Amount'; +import Confirm from '../../screens/Wallets/LNURLWithdraw/Confirm'; +import { viewControllerSelector } from '../../store/reselect/ui'; +import { NavigationContainer } from '../../styles/components'; + +export type LNURLWithdrawNavigationProp = + NativeStackNavigationProp; + +export type LNURLWithdrawStackParamList = { + Amount: { wParams: LNURLWithdrawParams }; + Confirm: { amount: number; wParams: LNURLWithdrawParams }; +}; + +const Stack = createNativeStackNavigator(); + +const screenOptions: NativeStackNavigationOptions = { + headerShown: false, + ...(__E2E__ ? { animationDuration: 0 } : {}), +}; + +const LNURLWithdrawNavigation = (): ReactElement => { + const snapPoints = useSnapPoints('large'); + const { isOpen, wParams } = useSelector((state) => { + return viewControllerSelector(state, 'lnurlWithdraw'); + }); + + if (!wParams) { + return <>; + } + + // if max === min withdrawable amount, skip the Amount screen + const initialRouteName = + wParams.minWithdrawable === wParams.maxWithdrawable ? 'Confirm' : 'Amount'; + + return ( + + + + + + + + + ); +}; + +export default memo(LNURLWithdrawNavigation); diff --git a/src/navigation/root/RootNavigator.tsx b/src/navigation/root/RootNavigator.tsx index 07d288c3c..d837b992e 100644 --- a/src/navigation/root/RootNavigator.tsx +++ b/src/navigation/root/RootNavigator.tsx @@ -58,6 +58,7 @@ import BackupNavigation from '../bottom-sheet/BackupNavigation'; import PINNavigation from '../bottom-sheet/PINNavigation'; import ForceTransfer from '../bottom-sheet/ForceTransfer'; import CloseChannelSuccess from '../bottom-sheet/CloseChannelSuccess'; +import LNURLWithdrawNavigation from '../bottom-sheet/LNURLWithdrawNavigation'; import { __E2E__ } from '../../constants/env'; import type { RootStackParamList } from '../types'; @@ -244,6 +245,7 @@ const RootNavigator = (): ReactElement => { + = export type SendScreenProps = NativeStackScreenProps; + +export type LNURLWithdrawProps = + NativeStackScreenProps; diff --git a/src/screens/Wallets/LNURLWithdraw/Amount.tsx b/src/screens/Wallets/LNURLWithdraw/Amount.tsx new file mode 100644 index 000000000..9be8f7c5f --- /dev/null +++ b/src/screens/Wallets/LNURLWithdraw/Amount.tsx @@ -0,0 +1,219 @@ +import React, { + ReactElement, + memo, + useCallback, + useMemo, + useState, + useEffect, +} from 'react'; +import { StyleSheet, View } from 'react-native'; +import { useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; + +import LNURLWNumberpad from './LNURLWNumberpad'; +import { TouchableOpacity } from '../../../styles/components'; +import { Caption13Up, Text02B } from '../../../styles/text'; +import { SwitchIcon } from '../../../styles/icons'; +import { IColors } from '../../../styles/colors'; +import GradientView from '../../../components/GradientView'; +import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader'; +import SafeAreaInset from '../../../components/SafeAreaInset'; +import Money from '../../../components/Money'; +import NumberPadTextField from '../../../components/NumberPadTextField'; +import Button from '../../../components/Button'; +import { EBalanceUnit } from '../../../store/types/wallet'; +import { sendMax } from '../../../utils/wallet/transactions'; +import { + selectedNetworkSelector, + selectedWalletSelector, +} from '../../../store/reselect/wallet'; +import { balanceUnitSelector } from '../../../store/reselect/settings'; +import { useSwitchUnit } from '../../../hooks/wallet'; +import { useCurrency } from '../../../hooks/displayValues'; +import { getNumberPadText } from '../../../utils/numberpad'; +import { convertToSats } from '../../../utils/conversion'; +import type { LNURLWithdrawProps } from '../../../navigation/types'; + +const Amount = ({ + navigation, + route, +}: LNURLWithdrawProps<'Amount'>): ReactElement => { + const { t } = useTranslation('wallet'); + const { wParams } = route.params; + const { minWithdrawable, maxWithdrawable } = wParams; + const { fiatTicker } = useCurrency(); + const [nextUnit, switchUnit] = useSwitchUnit(); + const selectedWallet = useSelector(selectedWalletSelector); + const selectedNetwork = useSelector(selectedNetworkSelector); + const unit = useSelector(balanceUnitSelector); + const [text, setText] = useState(''); + const [error, setError] = useState(false); + + // Set initial text for NumberPadTextField + useEffect(() => { + const result = getNumberPadText(minWithdrawable, unit); + setText(result); + }, [selectedWallet, selectedNetwork, minWithdrawable, unit]); + + const amount = useMemo((): number => { + return convertToSats(text, unit); + }, [text, unit]); + + const maxWithdrawableProps = { + ...(unit !== EBalanceUnit.fiat ? { symbol: true } : { showFiat: true }), + ...(error && { color: 'brand' as keyof IColors }), + }; + + const isMaxSendAmount = amount === maxWithdrawable; + + const onChangeUnit = (): void => { + const result = getNumberPadText(amount, nextUnit); + setText(result); + switchUnit(); + }; + + const onMaxAmount = useCallback((): void => { + const result = getNumberPadText(maxWithdrawable, unit); + setText(result); + sendMax({ selectedWallet, selectedNetwork }); + }, [maxWithdrawable, unit, selectedWallet, selectedNetwork]); + + const onError = (): void => { + setError(true); + setTimeout(() => setError(false), 500); + }; + + const isValid = amount >= minWithdrawable && amount <= maxWithdrawable; + + return ( + + + + + + + + + + {t('lnurl_w_max')} + + + + + + + + {t('send_max')} + + + + + + + + + {nextUnit === 'BTC' && 'BTC'} + {nextUnit === 'satoshi' && 'sats'} + {nextUnit === 'fiat' && fiatTicker} + + + + + + + + + + +