refactor: move Swaps + RefundSwap scanners to their own views
This commit is contained in:
@@ -65,6 +65,8 @@ import LnurlAuth from './views/LnurlAuth';
|
||||
import Receive from './views/Receive';
|
||||
import PaymentRequest from './views/PaymentRequest';
|
||||
import HandleAnythingQRScanner from './views/HandleAnythingQRScanner';
|
||||
import SwapsQRScanner from './views/SwapsQRScanner';
|
||||
import RefundSwapQRScanner from './views/RefundSwapQRScanner';
|
||||
import NodeQRScanner from './views/NodeQRScanner';
|
||||
import OpenChannel from './views/OpenChannel';
|
||||
import SendingOnChain from './views/SendingOnChain';
|
||||
@@ -743,6 +745,18 @@ export default class App extends React.PureComponent {
|
||||
HandleAnythingQRScanner
|
||||
}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="SwapsQRScanner" // @ts-ignore:next-line
|
||||
component={
|
||||
SwapsQRScanner
|
||||
}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="RefundSwapQRScanner" // @ts-ignore:next-line
|
||||
component={
|
||||
RefundSwapQRScanner
|
||||
}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="NodeQRCodeScanner" // @ts-ignore:next-line
|
||||
component={
|
||||
|
||||
@@ -6,32 +6,19 @@ import { Header } from '@rneui/themed';
|
||||
import { observer } from 'mobx-react';
|
||||
import { URDecoder } from '@ngraveio/bc-ur';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { Route } from '@react-navigation/native';
|
||||
import { Bytes, CryptoAccount, CryptoPSBT } from '@keystonehq/bc-ur-registry';
|
||||
|
||||
import LoadingIndicator from '../components/LoadingIndicator';
|
||||
import QRCodeScanner from '../components/QRCodeScanner';
|
||||
|
||||
import { nodeInfoStore } from '../stores/Stores';
|
||||
|
||||
import Invoice from '../models/Invoice';
|
||||
|
||||
import handleAnything from '../utils/handleAnything';
|
||||
import Base64Utils from '../utils/Base64Utils';
|
||||
import { joinQRs } from '../utils/BbqrUtils';
|
||||
import { localeString } from '../utils/LocaleUtils';
|
||||
import { themeColor } from '../utils/ThemeUtils';
|
||||
import AddressUtils from '../utils/AddressUtils';
|
||||
import BackendUtils from '../utils/BackendUtils';
|
||||
|
||||
interface HandleAnythingQRProps {
|
||||
navigation: StackNavigationProp<any, any>;
|
||||
route: Route<
|
||||
'HandleAnythingQRScanner',
|
||||
{
|
||||
view?: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
interface HandleAnythingQRState {
|
||||
@@ -68,129 +55,7 @@ export default class HandleAnythingQRScanner extends React.Component<
|
||||
};
|
||||
|
||||
handleAnythingScanned = async (data: string) => {
|
||||
const { navigation, route } = this.props;
|
||||
const view = route.params?.view;
|
||||
|
||||
if (view === 'Swaps') {
|
||||
if (this.isProcessing) return;
|
||||
this.isProcessing = true;
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const { value, satAmount } = AddressUtils.processBIP21Uri(data);
|
||||
|
||||
const { nodeInfo } = nodeInfoStore;
|
||||
const { isTestNet, isRegTest, isSigNet } = nodeInfo;
|
||||
|
||||
// Reverse Swap
|
||||
if (
|
||||
AddressUtils.isValidBitcoinAddress(
|
||||
value,
|
||||
isTestNet || isRegTest || isSigNet
|
||||
)
|
||||
) {
|
||||
navigation.goBack();
|
||||
const navigationParams: any = {
|
||||
initialInvoice: value,
|
||||
initialReverse: true
|
||||
};
|
||||
if (satAmount) {
|
||||
navigationParams.initialAmountSats = satAmount;
|
||||
}
|
||||
navigation.navigate('Swaps', navigationParams);
|
||||
return;
|
||||
}
|
||||
|
||||
// Submarine Swap
|
||||
if (AddressUtils.isValidLightningPaymentRequest(value)) {
|
||||
const decodedInvoice =
|
||||
await BackendUtils.decodePaymentRequest([value]);
|
||||
|
||||
if (!decodedInvoice) {
|
||||
throw new Error(
|
||||
localeString('views.Invoice.couldNotDecode')
|
||||
);
|
||||
}
|
||||
|
||||
const invoiceModel = new Invoice(decodedInvoice);
|
||||
const amount = invoiceModel.getRequestAmount;
|
||||
|
||||
navigation.goBack();
|
||||
navigation.navigate('Swaps', {
|
||||
initialInvoice: value,
|
||||
initialAmountSats: amount,
|
||||
initialReverse: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
localeString('components.QRCodeScanner.notRecognized')
|
||||
);
|
||||
} catch (err: any) {
|
||||
this.isProcessing = false;
|
||||
console.error(err.message);
|
||||
Alert.alert(
|
||||
localeString('general.error'),
|
||||
(err as Error).message ||
|
||||
localeString('utils.handleAnything.notValid'),
|
||||
[
|
||||
{
|
||||
text: localeString('general.ok'),
|
||||
onPress: () => void 0
|
||||
}
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
this.setState({ loading: false });
|
||||
navigation.goBack();
|
||||
}
|
||||
return;
|
||||
} else if (view === 'RefundSwap') {
|
||||
if (this.isProcessing) return;
|
||||
this.isProcessing = true;
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const { value } = AddressUtils.processBIP21Uri(data);
|
||||
|
||||
const { nodeInfo } = nodeInfoStore;
|
||||
const { isTestNet, isRegTest, isSigNet } = nodeInfo;
|
||||
|
||||
if (
|
||||
AddressUtils.isValidBitcoinAddress(
|
||||
value,
|
||||
isTestNet || isRegTest || isSigNet
|
||||
)
|
||||
) {
|
||||
navigation.goBack();
|
||||
navigation.navigate('RefundSwap', {
|
||||
scannedAddress: value
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
localeString('components.QRCodeScanner.notRecognized')
|
||||
);
|
||||
} catch (err: any) {
|
||||
this.isProcessing = false;
|
||||
console.error(err.message);
|
||||
Alert.alert(
|
||||
localeString('general.error'),
|
||||
(err as Error).message ||
|
||||
localeString('utils.handleAnything.notValid'),
|
||||
[
|
||||
{
|
||||
text: localeString('general.ok'),
|
||||
onPress: () => void 0
|
||||
}
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
this.setState({ loading: false });
|
||||
navigation.goBack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const { navigation } = this.props;
|
||||
|
||||
let handleData;
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@ interface NodeQRProps {
|
||||
|
||||
function NodeQRScanner(props: NodeQRProps) {
|
||||
const { navigation } = props;
|
||||
const isProcessing = React.useRef(false);
|
||||
|
||||
const handleNodeScanned = (data: string) => {
|
||||
if (isProcessing.current) return;
|
||||
isProcessing.current = true;
|
||||
|
||||
if (NodeUriUtils.isValidNodeUri(data)) {
|
||||
const { pubkey, host } = NodeUriUtils.processNodeUri(data);
|
||||
navigation.popTo('OpenChannel', {
|
||||
@@ -22,6 +26,7 @@ function NodeQRScanner(props: NodeQRProps) {
|
||||
host
|
||||
});
|
||||
} else {
|
||||
isProcessing.current = false;
|
||||
Alert.alert(
|
||||
localeString('general.error'),
|
||||
localeString('views.NodeQRScanner.error'),
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import * as React from 'react';
|
||||
import { Alert, View } from 'react-native';
|
||||
import { Header } from '@rneui/themed';
|
||||
import { observer } from 'mobx-react';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import LoadingIndicator from '../../components/LoadingIndicator';
|
||||
import QRCodeScanner from '../../components/QRCodeScanner';
|
||||
|
||||
import { localeString } from '../../utils/LocaleUtils';
|
||||
import { themeColor } from '../../utils/ThemeUtils';
|
||||
|
||||
export interface BaseQRScannerProps {
|
||||
navigation: StackNavigationProp<any, any>;
|
||||
}
|
||||
|
||||
export interface BaseQRScannerState {
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
@observer
|
||||
export default abstract class BaseQRScanner<
|
||||
P extends BaseQRScannerProps = BaseQRScannerProps,
|
||||
S extends BaseQRScannerState = BaseQRScannerState
|
||||
> extends React.Component<P, S> {
|
||||
isProcessing: boolean = false;
|
||||
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false
|
||||
} as S;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the scanned QR data. Implementations should:
|
||||
* - Validate the data
|
||||
* - Navigate to the appropriate screen on success
|
||||
* - Throw an error if the data is invalid
|
||||
*/
|
||||
protected abstract processQRData(data: string): Promise<void>;
|
||||
|
||||
handleQRScanned = async (data: string) => {
|
||||
const { navigation } = this.props;
|
||||
|
||||
if (this.isProcessing) return;
|
||||
this.isProcessing = true;
|
||||
this.setState({ loading: true } as Partial<S> as S);
|
||||
|
||||
try {
|
||||
await this.processQRData(data);
|
||||
} catch (err: any) {
|
||||
this.isProcessing = false;
|
||||
console.error(err.message);
|
||||
Alert.alert(
|
||||
localeString('general.error'),
|
||||
(err as Error).message ||
|
||||
localeString('utils.handleAnything.notValid'),
|
||||
[
|
||||
{
|
||||
text: localeString('general.ok'),
|
||||
onPress: () => void 0
|
||||
}
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
this.setState({ loading: false } as Partial<S> as S);
|
||||
navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { loading } = this.state;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: themeColor('background')
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
centerComponent={{
|
||||
text: localeString('general.loading'),
|
||||
style: {
|
||||
color: themeColor('text'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}
|
||||
}}
|
||||
backgroundColor={themeColor('background')}
|
||||
containerStyle={{
|
||||
borderBottomWidth: 0
|
||||
}}
|
||||
/>
|
||||
<View style={{ top: 40 }}>
|
||||
<LoadingIndicator />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<QRCodeScanner
|
||||
handleQRScanned={this.handleQRScanned}
|
||||
goBack={() => navigation.goBack()}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import BaseQRScanner from './QRScanner/BaseQRScanner';
|
||||
|
||||
import { nodeInfoStore } from '../stores/Stores';
|
||||
|
||||
import { localeString } from '../utils/LocaleUtils';
|
||||
import AddressUtils from '../utils/AddressUtils';
|
||||
|
||||
@observer
|
||||
export default class RefundSwapQRScanner extends BaseQRScanner {
|
||||
protected async processQRData(data: string): Promise<void> {
|
||||
const { navigation } = this.props;
|
||||
const { value } = AddressUtils.processBIP21Uri(data);
|
||||
|
||||
const { nodeInfo } = nodeInfoStore;
|
||||
const { isTestNet, isRegTest, isSigNet } = nodeInfo;
|
||||
|
||||
if (
|
||||
AddressUtils.isValidBitcoinAddress(
|
||||
value,
|
||||
isTestNet || isRegTest || isSigNet
|
||||
)
|
||||
) {
|
||||
navigation.goBack();
|
||||
navigation.navigate('RefundSwap', {
|
||||
scannedAddress: value
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(localeString('components.QRCodeScanner.notRecognized'));
|
||||
}
|
||||
}
|
||||
@@ -215,10 +215,7 @@ export default class RefundSwap extends React.Component<
|
||||
}}
|
||||
onScan={() =>
|
||||
this.props.navigation.navigate(
|
||||
'HandleAnythingQRScanner',
|
||||
{
|
||||
view: 'RefundSwap'
|
||||
}
|
||||
'RefundSwapQRScanner'
|
||||
)
|
||||
}
|
||||
placeholder={fetchingAddress ? '' : 'bc1...'}
|
||||
|
||||
@@ -2018,8 +2018,7 @@ export default class Swap extends React.PureComponent<SwapProps, SwapState> {
|
||||
onScan={() => {
|
||||
this.isNavigatingAway = true;
|
||||
navigation.navigate(
|
||||
'HandleAnythingQRScanner',
|
||||
{ view: 'Swaps' }
|
||||
'SwapsQRScanner'
|
||||
);
|
||||
}}
|
||||
placeholder={
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import BaseQRScanner from './QRScanner/BaseQRScanner';
|
||||
|
||||
import { nodeInfoStore } from '../stores/Stores';
|
||||
|
||||
import Invoice from '../models/Invoice';
|
||||
|
||||
import { localeString } from '../utils/LocaleUtils';
|
||||
import AddressUtils from '../utils/AddressUtils';
|
||||
import BackendUtils from '../utils/BackendUtils';
|
||||
|
||||
@observer
|
||||
export default class SwapsQRScanner extends BaseQRScanner {
|
||||
protected async processQRData(data: string): Promise<void> {
|
||||
const { navigation } = this.props;
|
||||
const { value, satAmount } = AddressUtils.processBIP21Uri(data);
|
||||
|
||||
const { nodeInfo } = nodeInfoStore;
|
||||
const { isTestNet, isRegTest, isSigNet } = nodeInfo;
|
||||
|
||||
// Reverse Swap - Bitcoin address
|
||||
if (
|
||||
AddressUtils.isValidBitcoinAddress(
|
||||
value,
|
||||
isTestNet || isRegTest || isSigNet
|
||||
)
|
||||
) {
|
||||
navigation.goBack();
|
||||
navigation.navigate('Swaps', {
|
||||
initialInvoice: value,
|
||||
initialAmountSats: satAmount,
|
||||
initialReverse: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Submarine Swap - Lightning invoice
|
||||
if (AddressUtils.isValidLightningPaymentRequest(value)) {
|
||||
const decodedInvoice = await BackendUtils.decodePaymentRequest([
|
||||
value
|
||||
]);
|
||||
|
||||
if (!decodedInvoice) {
|
||||
throw new Error(localeString('views.Invoice.couldNotDecode'));
|
||||
}
|
||||
|
||||
const invoiceModel = new Invoice(decodedInvoice);
|
||||
const amount = invoiceModel.getRequestAmount;
|
||||
|
||||
navigation.goBack();
|
||||
navigation.navigate('Swaps', {
|
||||
initialInvoice: value,
|
||||
initialAmountSats: amount,
|
||||
initialReverse: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(localeString('components.QRCodeScanner.notRecognized'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user