diff --git a/electrum/gui/qml/components/ConfirmTxDialog.qml b/electrum/gui/qml/components/ConfirmTxDialog.qml index 3e2ef3161..2123e2749 100644 --- a/electrum/gui/qml/components/ConfirmTxDialog.qml +++ b/electrum/gui/qml/components/ConfirmTxDialog.qml @@ -17,6 +17,9 @@ Dialog { property alias amountLabelText: amountLabel.text property alias sendButtonText: sendButton.text + signal txcancelled + signal txaccepted + title: qsTr('Confirm Transaction') // copy these to finalizer @@ -206,7 +209,10 @@ Dialog { Button { text: qsTr('Cancel') - onClicked: dialog.close() + onClicked: { + txcancelled() + dialog.close() + } } Button { @@ -214,6 +220,7 @@ Dialog { text: qsTr('Pay') enabled: finalizer.valid onClicked: { + txaccepted() finalizer.send_onchain() dialog.close() } diff --git a/electrum/gui/qml/components/Send.qml b/electrum/gui/qml/components/Send.qml index 2a32c7325..df710c0e9 100644 --- a/electrum/gui/qml/components/Send.qml +++ b/electrum/gui/qml/components/Send.qml @@ -15,6 +15,7 @@ Pane { recipient.text = '' amount.text = '' message.text = '' + is_max.checked = false } GridLayout { @@ -44,8 +45,9 @@ Pane { wrapMode: Text.Wrap placeholderText: qsTr('Paste address or invoice') onTextChanged: { - if (activeFocus) - invoice.recipient = text + //if (activeFocus) + //userEnteredPayment.recipient = text + userEnteredPayment.recipient = recipient.text } } @@ -79,7 +81,7 @@ Pane { fiatfield: amountFiat Layout.preferredWidth: parent.width /3 onTextChanged: { - invoice.create_invoice(recipient.text, is_max.checked ? MAX : Config.unitsToSats(amount.text), message.text) + userEnteredPayment.amount = is_max.checked ? MAX : Config.unitsToSats(amount.text) } } @@ -94,7 +96,7 @@ Pane { id: is_max text: qsTr('Max') onCheckedChanged: { - invoice.create_invoice(recipient.text, is_max.checked ? MAX : Config.unitsToSats(amount.text), message.text) + userEnteredPayment.amount = is_max.checked ? MAX : Config.unitsToSats(amount.text) } } } @@ -125,7 +127,7 @@ Pane { Layout.columnSpan: 2 Layout.fillWidth: true onTextChanged: { - invoice.create_invoice(recipient.text, is_max.checked ? MAX : Config.unitsToSats(amount.text), message.text) + userEnteredPayment.message = message.text } } @@ -136,29 +138,30 @@ Pane { Button { text: qsTr('Save') - enabled: invoice.canSave + enabled: userEnteredPayment.canSave icon.source: '../../icons/save.png' onClicked: { - invoice.save_invoice() - invoice.clear() + userEnteredPayment.save_invoice() + userEnteredPayment.clear() rootItem.clear() } } Button { text: qsTr('Pay now') - enabled: invoice.canPay + enabled: userEnteredPayment.canPay icon.source: '../../icons/confirmed.png' onClicked: { - invoice.save_invoice() var dialog = confirmPaymentDialog.createObject(app, { 'address': recipient.text, - 'satoshis': Config.unitsToSats(amount.text), + 'satoshis': is_max.checked ? MAX : Config.unitsToSats(amount.text), 'message': message.text }) + dialog.txaccepted.connect(function() { + userEnteredPayment.clear() + rootItem.clear() + }) dialog.open() - invoice.clear() - rootItem.clear() } } @@ -293,6 +296,26 @@ Pane { FocusScope { id: parkFocus } } + + UserEnteredPayment { + id: userEnteredPayment + wallet: Daemon.currentWallet + + //onValidationError: { + //if (recipient.activeFocus) { + //// no popups when editing + //return + //} + //var dialog = app.messageDialog.createObject(app, {'text': message }) + //dialog.open() +//// rootItem.clear() + //} + + onInvoiceSaved: { + Daemon.currentWallet.invoiceModel.init_model() + } + } + Invoice { id: invoice wallet: Daemon.currentWallet @@ -314,11 +337,12 @@ Pane { } } onValidationSuccess: { - // address only -> fill form fields + // address only -> fill form fields and clear this instance // else -> show invoice confirmation dialog - if (invoiceType == Invoice.OnchainOnlyAddress) + if (invoiceType == Invoice.OnchainOnlyAddress) { recipient.text = invoice.recipient - else { + invoice.clear() + } else { var dialog = invoiceDialog.createObject(rootItem, {'invoice': invoice}) dialog.open() } @@ -326,8 +350,8 @@ Pane { onInvoiceCreateError: console.log(code + ' ' + message) onInvoiceSaved: { - console.log('invoice got saved') Daemon.currentWallet.invoiceModel.init_model() } } + } diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index a118b705b..8c9e1d12d 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -19,7 +19,7 @@ from .qewalletdb import QEWalletDB from .qebitcoin import QEBitcoin from .qefx import QEFX from .qetxfinalizer import QETxFinalizer -from .qeinvoice import QEInvoice +from .qeinvoice import QEInvoice, QEUserEnteredPayment from .qetypes import QEAmount from .qeaddressdetails import QEAddressDetails from .qetxdetails import QETxDetails @@ -146,6 +146,8 @@ class ElectrumQmlApplication(QGuiApplication): qmlRegisterType(QEFX, 'org.electrum', 1, 0, 'FX') qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer') qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice') + qmlRegisterType(QEUserEnteredPayment, 'org.electrum', 1, 0, 'UserEnteredPayment') + qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails') qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails') qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener') diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index e74264d55..2a757a121 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -305,33 +305,154 @@ class QEInvoice(QObject): self._wallet.wallet.save_invoice(self._effectiveInvoice) self.invoiceSaved.emit() - @pyqtSlot(str, QEAmount, str) - def create_invoice(self, address: str, amount: QEAmount, message: str): - # create invoice from user entered fields - # (any other type of invoice is created from parsing recipient) - self._logger.debug('creating invoice to %s, amount=%s, message=%s' % (address, repr(amount), message)) - self.clear() +class QEUserEnteredPayment(QObject): + _logger = get_logger(__name__) + _wallet = None + _recipient = None + _message = None + _amount = QEAmount() + _key = None + _canSave = False + _canPay = False + + validationError = pyqtSignal([str,str], arguments=['code','message']) + invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message']) + invoiceSaved = pyqtSignal() + + walletChanged = pyqtSignal() + @pyqtProperty(QEWallet, notify=walletChanged) + def wallet(self): + return self._wallet + + @wallet.setter + def wallet(self, wallet: QEWallet): + if self._wallet != wallet: + self._wallet = wallet + self.walletChanged.emit() + + recipientChanged = pyqtSignal() + @pyqtProperty(str, notify=recipientChanged) + def recipient(self): + return self._recipient + + @recipient.setter + def recipient(self, recipient: str): + if self._recipient != recipient: + self._recipient = recipient + self.validate() + self.recipientChanged.emit() + + messageChanged = pyqtSignal() + @pyqtProperty(str, notify=messageChanged) + def message(self): + return self._message + + @message.setter + def message(self, message): + if self._message != message: + self._message = message + self.messageChanged.emit() + + amountChanged = pyqtSignal() + @pyqtProperty(QEAmount, notify=amountChanged) + def amount(self): + return self._amount + + @amount.setter + def amount(self, amount): + if self._amount != amount: + self._amount = amount + self.validate() + self.amountChanged.emit() + + canSaveChanged = pyqtSignal() + @pyqtProperty(bool, notify=canSaveChanged) + def canSave(self): + return self._canSave + + @canSave.setter + def canSave(self, canSave): + if self._canSave != canSave: + self._canSave = canSave + self.canSaveChanged.emit() + + canPayChanged = pyqtSignal() + @pyqtProperty(bool, notify=canPayChanged) + def canPay(self): + return self._canPay + + @canPay.setter + def canPay(self, canPay): + if self._canPay != canPay: + self._canPay = canPay + self.canPayChanged.emit() - if not address: - self.invoiceCreateError.emit('fatal', _('Recipient not specified.') + ' ' + _('Please scan a Bitcoin address or a payment request')) + keyChanged = pyqtSignal() + @pyqtProperty(bool, notify=keyChanged) + def key(self): + return self._key + + @key.setter + def key(self, key): + if self._key != key: + self._key = key + self.keyChanged.emit() + + def validate(self): + self.canPay = False + self.canSave = False + self._logger.debug('validate') + + if not self._recipient: + self.validationError.emit('recipient', _('Recipient not specified.')) return - if not bitcoin.is_address(address): - self.invoiceCreateError.emit('fatal', _('Invalid Bitcoin address')) + if not bitcoin.is_address(self._recipient): + self.validationError.emit('recipient', _('Invalid Bitcoin address')) return - if amount.isEmpty: - self.invoiceCreateError.emit('fatal', _('Invalid amount')) + if self._amount.isEmpty: + self.validationError.emit('amount', _('Invalid amount')) return - inv_amt = '!' if amount.isMax else (amount.satsInt * 1000) # FIXME msat precision from UI? + if self._amount.isMax: + self.canPay = True + else: + self.canSave = True + if self.get_max_spendable() >= self._amount.satsInt: + self.canPay = True + + def get_max_spendable(self): + c, u, x = self._wallet.wallet.get_balance() + #TODO determine real max + return c + + @pyqtSlot() + def save_invoice(self): + assert self.canSave + assert not self._amount.isMax + + self._logger.debug('saving invoice to %s, amount=%s, message=%s' % (self._recipient, repr(self._amount), self._message)) + + inv_amt = self._amount.satsInt try: - outputs = [PartialTxOutput.from_address_and_value(address, inv_amt)] - invoice = self._wallet.wallet.create_invoice(outputs=outputs, message=message, pr=None, URI=None) + outputs = [PartialTxOutput.from_address_and_value(self._recipient, inv_amt)] + self._logger.debug(repr(outputs)) + invoice = self._wallet.wallet.create_invoice(outputs=outputs, message=self._message, pr=None, URI=None) except InvoiceError as e: self.invoiceCreateError.emit('fatal', _('Error creating payment') + ':\n' + str(e)) return - self.set_effective_invoice(invoice) + self.key = self._wallet.wallet.get_key_for_outgoing_invoice(invoice) + self._wallet.wallet.save_invoice(invoice) + self.invoiceSaved.emit() + + @pyqtSlot() + def clear(self): + self._recipient = None + self._amount = QEAmount() + self._message = None + self.canSave = False + self.canPay = False