import React, { Component } from 'react'; import { View, Text, Dimensions, StatusBar, ScrollView, BackHandler, TouchableOpacity, StyleSheet } from 'react-native'; import Share from 'react-native-share'; import { BlueLoading, BlueText, SafeBlueArea, BlueButton, SecondButton, BlueCopyTextToClipboard, BlueNavigationStyle, BlueSpacing20, BlueBigCheckmark, } from '../../BlueComponents'; import PropTypes from 'prop-types'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import { Icon } from 'react-native-elements'; import QRCode from 'react-native-qrcode-svg'; import loc from '../../loc'; import { BlueCurrentTheme } from '../../components/themes'; import { BlueStorageContext } from '../../blue_modules/storage-context'; const { width, height } = Dimensions.get('window'); const styles = StyleSheet.create({ root: { flex: 1, backgroundColor: BlueCurrentTheme.colors.background, }, center: { flex: 1, justifyContent: 'center', alignItems: 'center', }, qrCodeContainer: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' }, valueRoot: { flex: 2, flexDirection: 'column', justifyContent: 'center', }, valueAmount: { flexDirection: 'row', justifyContent: 'center', paddingBottom: 8, }, valueText: { color: BlueCurrentTheme.colors.alternativeTextColor2, fontSize: 32, fontWeight: '600', }, valueSats: { color: BlueCurrentTheme.colors.alternativeTextColor2, fontSize: 16, marginHorizontal: 4, paddingBottom: 3, fontWeight: '600', alignSelf: 'flex-end', }, memo: { color: '#9aa0aa', fontSize: 14, marginHorizontal: 4, paddingBottom: 6, fontWeight: '400', alignSelf: 'center', }, paid: { flex: 3, alignItems: 'center', justifyContent: 'center', }, paidMark: { marginTop: -100, marginBottom: 16, backgroundColor: BlueCurrentTheme.colors.success, }, detailsRoot: { flex: 1, justifyContent: 'flex-end', marginBottom: 24, alignItems: 'center', }, detailsTouch: { flexDirection: 'row', alignItems: 'center', }, detailsText: { color: BlueCurrentTheme.colors.alternativeTextColor, fontSize: 14, marginRight: 8, }, expired: { backgroundColor: BlueCurrentTheme.colors.success, width: 120, height: 120, borderRadius: 60, alignSelf: 'center', justifyContent: 'center', marginTop: -100, marginBottom: 30, }, activeRoot: { flex: 1, alignItems: 'center', marginTop: 8, justifyContent: 'space-between', }, activeQrcode: { flex: 1, alignItems: 'center', justifyContent: 'center', marginHorizontal: 16, borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF', }, additionalInfo: { backgroundColor: BlueCurrentTheme.colors.brandingColor, }, }); export default class LNDViewInvoice extends Component { static contextType = BlueStorageContext; constructor(props) { super(props); const invoice = props.route.params.invoice; const fromWallet = props.route.params.fromWallet; this.state = { invoice, fromWallet, isLoading: typeof invoice === 'string', addressText: typeof invoice === 'object' && 'payment_request' in invoice ? invoice.payment_request : invoice, isFetchingInvoices: true, qrCodeHeight: height > width ? width - 20 : width / 2, }; this.fetchInvoiceInterval = undefined; BackHandler.addEventListener('hardwareBackPress', this.handleBackButton); } componentDidMount() { console.log('LNDViewInvoice - componentDidMount'); this.fetchInvoiceInterval = setInterval(async () => { if (this.state.isFetchingInvoices) { try { const userInvoices = await this.state.fromWallet.getUserInvoices(20); // fetching only last 20 invoices // for invoice that was created just now - that should be enough (it is basically the last one, so limit=1 would be sufficient) // but that might not work as intended IF user creates 21 invoices, and then tries to check the status of invoice #0, it just wont be updated const updatedUserInvoice = userInvoices.filter(invoice => typeof this.state.invoice === 'object' ? invoice.payment_request === this.state.invoice.payment_request : invoice.payment_request === this.state.invoice, )[0]; if (typeof updatedUserInvoice !== 'undefined') { this.setState({ invoice: updatedUserInvoice, isLoading: false, addressText: updatedUserInvoice.payment_request }); if (updatedUserInvoice.ispaid) { // we fetched the invoice, and it is paid :-) this.setState({ isFetchingInvoices: false }); ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); clearInterval(this.fetchInvoiceInterval); this.fetchInvoiceInterval = undefined; this.context.fetchAndSaveWalletTransactions(this.state.fromWallet.getID()); } else { const currentDate = new Date(); const now = (currentDate.getTime() / 1000) | 0; const invoiceExpiration = updatedUserInvoice.timestamp + updatedUserInvoice.expire_time; if (invoiceExpiration < now && !updatedUserInvoice.ispaid) { // invoice expired :-( this.context.fetchAndSaveWalletTransactions(this.state.fromWallet.getID()); this.setState({ isFetchingInvoices: false }); ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); clearInterval(this.fetchInvoiceInterval); this.fetchInvoiceInterval = undefined; } } } } catch (error) { console.log(error); } } }, 3000); } async componentWillUnmount() { clearInterval(this.fetchInvoiceInterval); this.fetchInvoiceInterval = undefined; BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton); } handleBackButton = () => { this.props.navigation.goBack(null); return true; }; onLayout = () => { const { height } = Dimensions.get('window'); this.setState({ qrCodeHeight: height > width ? width - 20 : width / 2 }); }; render() { if (this.state.isLoading) { return ( ); } const { invoice } = this.state; if (typeof invoice === 'object') { const currentDate = new Date(); const now = (currentDate.getTime() / 1000) | 0; const invoiceExpiration = invoice.timestamp + invoice.expire_time; if (this.state.showPreimageQr) { return ( {loc.lndViewInvoice.preimage}: ); } if (invoice.ispaid || invoice.type === 'paid_invoice') { return ( {invoice.type === 'paid_invoice' && invoice.value && ( {invoice.value} {loc.lndViewInvoice.sats} )} {invoice.type === 'user_invoice' && invoice.amt && ( {invoice.amt} {loc.lndViewInvoice.sats} )} {!invoice.ispaid && invoice.memo && invoice.memo.length > 0 && {invoice.memo}} {loc.lndViewInvoice.has_been_paid} {invoice.payment_preimage && typeof invoice.payment_preimage === 'string' ? ( this.setState({ showPreimageQr: true })}> {loc.send.create_details} ) : ( )} ); } if (invoiceExpiration < now && !invoice.ispaid) { return ( {loc.lndViewInvoice.wasnt_paid_and_expired} ); } else if (invoiceExpiration > now && invoice.ispaid) { if (invoice.ispaid) { return ( {loc.lndViewInvoice.has_been_paid} ); } } } // Invoice has not expired, nor has it been paid for. return ( {loc.lndViewInvoice.please_pay} {invoice.amt} {loc.lndViewInvoice.sats} {invoice && 'description' in invoice && invoice.description.length > 0 && ( {loc.lndViewInvoice.for} {invoice.description} )} { Share.open({ message: `lightning:${invoice.payment_request}` }).catch(error => console.log(error)); }} title={loc.receive.details_share} /> this.props.navigation.navigate('LNDViewAdditionalInvoiceInformation', { fromWallet: this.state.fromWallet })} title={loc.lndViewInvoice.additional_info} /> ); } } LNDViewInvoice.propTypes = { navigation: PropTypes.shape({ goBack: PropTypes.func, navigate: PropTypes.func, popToTop: PropTypes.func, }), route: PropTypes.shape({ params: PropTypes.object, }), }; LNDViewInvoice.navigationOptions = ({ navigation, route }) => route.params.isModal === true ? { ...BlueNavigationStyle(navigation, true, () => navigation.dangerouslyGetParent().pop()), title: 'Lightning Invoice', headerLeft: null, headerStyle: { backgroundColor: BlueCurrentTheme.colors.customHeader, }, } : { ...BlueNavigationStyle(), title: 'Lightning Invoice', headerStyle: { backgroundColor: BlueCurrentTheme.colors.customHeader, }, };