/* global alert */
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
View,
TextInput,
KeyboardAvoidingView,
Keyboard,
StatusBar,
TouchableWithoutFeedback,
TouchableOpacity,
Text,
StyleSheet,
Image,
} from 'react-native';
import {
BlueNavigationStyle,
BlueButton,
BlueBitcoinAmount,
BlueDismissKeyboardInputAccessory,
BlueAlertWalletExportReminder,
} from '../../BlueComponents';
import * as NavigationService from '../../NavigationService';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements';
import loc, { formatBalanceWithoutSuffix, formatBalancePlain } from '../../loc';
import Lnurl from '../../class/lnurl';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import Notifications from '../../blue_modules/notifications';
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
const currency = require('../../blue_modules/currency');
const LNDCreateInvoice = () => {
const { wallets, saveToDisk, setSelectedWallet } = useContext(BlueStorageContext);
const { walletID, uri } = useRoute().params;
const wallet = useRef(
wallets.find(item => item.getID() === walletID) || wallets.find(item => item.type === LightningCustodianWallet.type),
);
const { name } = useRoute();
const { colors } = useTheme();
const { navigate, dangerouslyGetParent, goBack, pop, setParams } = useNavigation();
const [unit, setUnit] = useState(wallet.current.getPreferredBalanceUnit());
const [amount, setAmount] = useState();
const [renderWalletSelectionButtonHidden, setRenderWalletSelectionButtonHidden] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [description, setDescription] = useState('');
const [lnurlParams, setLNURLParams] = useState();
const styleHooks = StyleSheet.create({
scanRoot: {
backgroundColor: colors.scanLabel,
},
scanClick: {
color: colors.inverseForegroundColor,
},
walletNameText: {
color: colors.buttonAlternativeTextColor,
},
walletNameBalance: {
color: colors.buttonAlternativeTextColor,
},
walletNameSats: {
color: colors.buttonAlternativeTextColor,
},
root: {
backgroundColor: colors.elevated,
},
amount: {
backgroundColor: colors.elevated,
},
fiat: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
});
useEffect(() => {
// console.log(params)
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.removeListener('keyboardDidShow', _keyboardDidShow);
Keyboard.removeListener('keyboardDidHide', _keyboardDidHide);
};
}, []);
const renderReceiveDetails = async () => {
try {
wallet.current.setUserHasSavedExport(true);
await saveToDisk();
if (uri) {
processLnurl(uri);
}
} catch (e) {
console.log(e);
}
setIsLoading(false);
};
useEffect(() => {
if (wallet.current && wallet.current.getID() !== walletID) {
const newWallet = wallets.find(w => w.getID() === walletID);
if (newWallet) {
wallet.current = newWallet;
setSelectedWallet(newWallet.getID());
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletID]);
useFocusEffect(
useCallback(() => {
if (wallet.current) {
setSelectedWallet(walletID);
if (wallet.current.getUserHasSavedExport()) {
renderReceiveDetails();
} else {
BlueAlertWalletExportReminder({
onSuccess: () => renderReceiveDetails(),
onFailure: () => {
dangerouslyGetParent().pop();
NavigationService.navigate('WalletExportRoot', {
screen: 'WalletExport',
params: {
walletID,
},
});
},
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet]),
[],
);
const _keyboardDidShow = () => {
setRenderWalletSelectionButtonHidden(true);
};
const _keyboardDidHide = () => {
setRenderWalletSelectionButtonHidden(false);
};
const createInvoice = async () => {
setIsLoading(true);
try {
let invoiceAmount = amount;
switch (unit) {
case BitcoinUnit.SATS:
invoiceAmount = parseInt(invoiceAmount); // basically nop
break;
case BitcoinUnit.BTC:
invoiceAmount = currency.btcToSatoshi(invoiceAmount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
// trying to fetch cached sat equivalent for this fiat amount
invoiceAmount = BlueBitcoinAmount.getCachedSatoshis(invoiceAmount) || currency.btcToSatoshi(currency.fiatToBTC(invoiceAmount));
break;
}
const invoiceRequest = await wallet.current.addInvoice(invoiceAmount, description);
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
// lets decode payreq and subscribe groundcontrol so we can receive push notification when our invoice is paid
/** @type LightningCustodianWallet */
const decoded = await wallet.current.decodeInvoice(invoiceRequest);
await Notifications.tryToObtainPermissions();
Notifications.majorTomToGroundControl([], [decoded.payment_hash], []);
// send to lnurl-withdraw callback url if that exists
if (lnurlParams) {
const { callback, k1 } = lnurlParams;
const callbackUrl = callback + (callback.indexOf('?') !== -1 ? '&' : '?') + 'k1=' + k1 + '&pr=' + invoiceRequest;
const resp = await fetch(callbackUrl, { method: 'GET' });
if (resp.status >= 300) {
const text = await resp.text();
throw new Error(text);
}
const reply = await resp.json();
if (reply.status === 'ERROR') {
throw new Error('Reply from server: ' + reply.reason);
}
}
setTimeout(async () => {
// wallet object doesnt have this fresh invoice in its internals, so we refetch it and only then save
await wallet.current.fetchUserInvoices(1);
await saveToDisk();
}, 1000);
navigate('LNDViewInvoice', {
invoice: invoiceRequest,
walletID,
isModal: true,
});
} catch (Err) {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
setIsLoading(false);
alert(Err.message);
}
};
const processLnurl = async data => {
setIsLoading(true);
if (!wallet) {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
return goBack();
}
// decoding the lnurl
const url = Lnurl.getUrlFromLnurl(data);
// calling the url
try {
const resp = await fetch(url, { method: 'GET' });
if (resp.status >= 300) {
throw new Error('Bad response from server');
}
const reply = await resp.json();
if (reply.status === 'ERROR') {
throw new Error('Reply from server: ' + reply.reason);
}
if (reply.tag === Lnurl.TAG_PAY_REQUEST) {
// we are here by mistake. user wants to SEND to lnurl-pay, but he is on a screen that creates
// invoices (including through lnurl-withdraw)
navigate('ScanLndInvoiceRoot', {
screen: 'LnurlPay',
params: {
lnurl: data,
fromWalletID: walletID,
},
});
return;
}
if (reply.tag !== Lnurl.TAG_WITHDRAW_REQUEST) {
throw new Error('Unsupported lnurl');
}
// amount that comes from lnurl is always in sats
let amount = (reply.maxWithdrawable / 1000).toString();
const sats = amount;
switch (unit) {
case BitcoinUnit.SATS:
// nop
break;
case BitcoinUnit.BTC:
amount = currency.satoshiToBTC(amount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
amount = formatBalancePlain(amount, BitcoinUnit.LOCAL_CURRENCY);
BlueBitcoinAmount.setCachedSatoshis(amount, sats);
break;
}
// setting the invoice creating screen with the parameters
setLNURLParams({
k1: reply.k1,
callback: reply.callback,
fixed: reply.minWithdrawable === reply.maxWithdrawable,
min: (reply.minWithdrawable || 0) / 1000,
max: reply.maxWithdrawable / 1000,
});
setAmount(amount);
setDescription(reply.defaultDescription);
setIsLoading(false);
} catch (Err) {
Keyboard.dismiss();
setIsLoading(false);
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert(Err.message);
}
};
const renderCreateButton = () => {
return (
{isLoading ? (
) : (
0)} onPress={createInvoice} title={loc.send.details_create} />
)}
);
};
const navigateToScanQRCode = () => {
NavigationService.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
onBarScanned: processLnurl,
launchedBy: name,
},
});
Keyboard.dismiss();
};
const renderScanClickable = () => {
return (
{loc.send.details_scan}
);
};
const navigateToSelectWallet = () => {
navigate('SelectWallet', { onWalletSelect: onWalletSelect, chainType: Chain.OFFCHAIN });
};
const renderWalletSelectionButton = () => {
if (renderWalletSelectionButtonHidden) return;
return (
{!isLoading && (
{loc.wallets.select_wallet.toLowerCase()}
)}
{wallet.current.getLabel()}
{formatBalanceWithoutSuffix(wallet.current.getBalance(), BitcoinUnit.SATS, false)}
{BitcoinUnit.SATS}
);
};
const onWalletSelect = selectedWallet => {
setParams({ walletID: selectedWallet.getID() });
pop();
};
if (wallet.current === undefined || !walletID) {
return (
System error: Source wallet not found (this should never happen)
);
}
return (
{
if (lnurlParams) {
// in this case we prevent the user from changing the amount to < min or > max
const { min, max } = lnurlParams;
const nextAmount = parseInt(text);
if (nextAmount < min) {
text = min.toString();
} else if (nextAmount > max) {
text = max.toString();
}
}
setAmount(text);
}}
disabled={isLoading || (lnurlParams && lnurlParams.fixed)}
unit={unit}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
{lnurlParams ? null : renderScanClickable()}
{renderCreateButton()}
{renderWalletSelectionButton()}
);
};
const styles = StyleSheet.create({
createButton: {
marginHorizontal: 16,
marginVertical: 16,
minHeight: 45,
},
scanRoot: {
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
},
scanClick: {
marginLeft: 4,
},
walletRoot: {
marginBottom: 16,
alignItems: 'center',
justifyContent: 'center',
},
walletChooseWrap: {
flexDirection: 'row',
alignItems: 'center',
},
walletChooseText: {
color: '#9aa0aa',
fontSize: 14,
marginRight: 8,
},
walletNameWrap: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 4,
},
walletNameTouch: {
flexDirection: 'row',
alignItems: 'center',
},
walletNameText: {
fontSize: 14,
},
walletNameBalance: {
fontSize: 14,
fontWeight: '600',
marginLeft: 8,
marginRight: 4,
},
walletNameSats: {
fontSize: 11,
fontWeight: '600',
textAlignVertical: 'bottom',
marginTop: 2,
},
error: {
flex: 1,
paddingTop: 20,
},
root: {
flex: 1,
justifyContent: 'space-between',
},
amount: {
flex: 1,
},
fiat: {
flexDirection: 'row',
borderWidth: 1.0,
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
},
fiat2: {
flex: 1,
marginHorizontal: 8,
minHeight: 33,
color: '#81868e',
},
});
export default LNDCreateInvoice;
LNDCreateInvoice.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
headerTitle: loc.receive.header,
headerLeft: null,
});