From d4df633f223fd937d928b675db7c3fe990c27ec7 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 26 Sep 2022 13:21:53 +0200 Subject: [PATCH] move request details into separate dialog --- .../qml/components/ReceiveDetailsDialog.qml | 124 ++++++++++++++ electrum/gui/qml/components/ReceiveDialog.qml | 152 +++++------------- electrum/gui/qml/qerequestdetails.py | 7 +- electrum/gui/qml/qewallet.py | 32 +++- electrum/wallet.py | 1 + 5 files changed, 199 insertions(+), 117 deletions(-) create mode 100644 electrum/gui/qml/components/ReceiveDetailsDialog.qml diff --git a/electrum/gui/qml/components/ReceiveDetailsDialog.qml b/electrum/gui/qml/components/ReceiveDetailsDialog.qml new file mode 100644 index 000000000..f104278b0 --- /dev/null +++ b/electrum/gui/qml/components/ReceiveDetailsDialog.qml @@ -0,0 +1,124 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.14 +import QtQuick.Controls.Material 2.0 +import QtQml.Models 2.1 + +import org.electrum 1.0 + +import "controls" + +ElDialog { + id: dialog + + property alias amount: amountBtc.text + property alias description: message.text + property alias expiry: expires.currentValue + + + parent: Overlay.overlay + modal: true + standardButtons: Dialog.Close + + implicitWidth: parent.width + height: parent.height + + Overlay.modal: Rectangle { + color: "#aa000000" + } + + GridLayout { + id: form + width: parent.width + rowSpacing: constants.paddingSmall + columnSpacing: constants.paddingSmall + columns: 4 + + Label { + text: qsTr('Message') + } + + TextField { + id: message + placeholderText: qsTr('Description of payment request') + Layout.columnSpan: 3 + Layout.fillWidth: true + } + + Label { + text: qsTr('Request') + wrapMode: Text.WordWrap + Layout.rightMargin: constants.paddingXLarge + } + + BtcField { + id: amountBtc + fiatfield: amountFiat + Layout.preferredWidth: parent.width /3 + } + + Label { + text: Config.baseUnit + color: Material.accentColor + } + + Item { width: 1; height: 1; Layout.fillWidth: true } + + Item { visible: Daemon.fx.enabled; width: 1; height: 1 } + + FiatField { + id: amountFiat + btcfield: amountBtc + visible: Daemon.fx.enabled + Layout.preferredWidth: parent.width /3 + } + + Label { + visible: Daemon.fx.enabled + text: Daemon.fx.fiatCurrency + color: Material.accentColor + } + + Item { visible: Daemon.fx.enabled; width: 1; height: 1; Layout.fillWidth: true } + + Label { + text: qsTr('Expires after') + Layout.fillWidth: false + } + + ElComboBox { + id: expires + Layout.columnSpan: 2 + + textRole: 'text' + valueRole: 'value' + + model: ListModel { + id: expiresmodel + Component.onCompleted: { + // we need to fill the model like this, as ListElement can't evaluate script + expiresmodel.append({'text': qsTr('10 minutes'), 'value': 10*60}) + expiresmodel.append({'text': qsTr('1 hour'), 'value': 60*60}) + expiresmodel.append({'text': qsTr('1 day'), 'value': 24*60*60}) + expiresmodel.append({'text': qsTr('1 week'), 'value': 7*24*60*60}) + expiresmodel.append({'text': qsTr('1 month'), 'value': 31*24*60*60}) + expiresmodel.append({'text': qsTr('Never'), 'value': 0}) + expires.currentIndex = 0 + } + } + } + + Item { width: 1; height: 1; Layout.fillWidth: true } + + Button { + Layout.columnSpan: 4 + Layout.alignment: Qt.AlignHCenter + text: qsTr('Create Request') + icon.source: '../../icons/qrcode.png' + onClicked: { + accept() + } + } + } + +} diff --git a/electrum/gui/qml/components/ReceiveDialog.qml b/electrum/gui/qml/components/ReceiveDialog.qml index 2b2d56309..986becaf8 100644 --- a/electrum/gui/qml/components/ReceiveDialog.qml +++ b/electrum/gui/qml/components/ReceiveDialog.qml @@ -11,9 +11,9 @@ import "controls" ElDialog { id: dialog - property string _bolt11 - property string _bip21uri - property string _address + property string _bolt11: request.bolt11 + property string _bip21uri: request.bip21 + property string _address: request.address property bool _render_qr: false // delay qr rendering until dialog is shown @@ -151,101 +151,10 @@ ElDialog { color: Material.accentColor } -// aaaaaaaaaaaaaaaaaaaa - - GridLayout { - id: form - width: parent.width - rowSpacing: constants.paddingSmall - columnSpacing: constants.paddingSmall - columns: 4 - - Label { - text: qsTr('Message') - } - - TextField { - id: message - placeholderText: qsTr('Description of payment request') - Layout.columnSpan: 3 - Layout.fillWidth: true - } - - Label { - text: qsTr('Request') - wrapMode: Text.WordWrap - Layout.rightMargin: constants.paddingXLarge - } - - BtcField { - id: amount - fiatfield: amountFiat - Layout.preferredWidth: parent.width /3 - } - - Label { - text: Config.baseUnit - color: Material.accentColor - } - - Item { width: 1; height: 1; Layout.fillWidth: true } - - Item { visible: Daemon.fx.enabled; width: 1; height: 1 } - - FiatField { - id: amountFiat - btcfield: amount - visible: Daemon.fx.enabled - Layout.preferredWidth: parent.width /3 - } - - Label { - visible: Daemon.fx.enabled - text: Daemon.fx.fiatCurrency - color: Material.accentColor - } - - Item { visible: Daemon.fx.enabled; width: 1; height: 1; Layout.fillWidth: true } - - Label { - text: qsTr('Expires after') - Layout.fillWidth: false - } - - ElComboBox { - id: expires - Layout.columnSpan: 2 - - textRole: 'text' - valueRole: 'value' - - model: ListModel { - id: expiresmodel - Component.onCompleted: { - // we need to fill the model like this, as ListElement can't evaluate script - expiresmodel.append({'text': qsTr('10 minutes'), 'value': 10*60}) - expiresmodel.append({'text': qsTr('1 hour'), 'value': 60*60}) - expiresmodel.append({'text': qsTr('1 day'), 'value': 24*60*60}) - expiresmodel.append({'text': qsTr('1 week'), 'value': 7*24*60*60}) - expiresmodel.append({'text': qsTr('1 month'), 'value': 31*24*60*60}) - expiresmodel.append({'text': qsTr('Never'), 'value': 0}) - expires.currentIndex = 0 - } - } - } - - Item { width: 1; height: 1; Layout.fillWidth: true } - - Button { - Layout.columnSpan: 4 - Layout.alignment: Qt.AlignHCenter - text: qsTr('Create Request') - icon.source: '../../icons/qrcode.png' - onClicked: { - createRequest() - } - } + Button { + text: 'specify' + onClicked: receiveDetailsDialog.open() } } @@ -267,29 +176,31 @@ ElDialog { } function createRequest(ignoreGaplimit = false) { - var qamt = Config.unitsToSats(amount.text) + var qamt = Config.unitsToSats(receiveDetailsDialog.amount) if (qamt.satsInt > Daemon.currentWallet.lightningCanReceive.satsInt) { console.log('Creating OnChain request') - Daemon.currentWallet.create_request(qamt, message.text, expires.currentValue, false, ignoreGaplimit) + Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, false, ignoreGaplimit) } else { console.log('Creating Lightning request') - Daemon.currentWallet.create_request(qamt, message.text, expires.currentValue, true) + Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, true) } } + function createDefaultRequest(ignoreGaplimit = false) { + console.log('Creating default request') + Daemon.currentWallet.create_default_request(ignoreGaplimit) + } + Connections { target: Daemon.currentWallet function onRequestCreateSuccess(key) { - message.text = '' - amount.text = '' - var dialog = requestdialog.createObject(app, { key: key }) - dialog.open() + request.key = key } function onRequestCreateError(code, error) { if (code == 'gaplimit') { var dialog = app.messageDialog.createObject(app, {'text': error, 'yesno': true}) dialog.yesClicked.connect(function() { - createRequest(true) + createDefaultRequest(true) }) } else { console.log(error) @@ -297,14 +208,37 @@ ElDialog { } dialog.open() } - function onRequestStatusChanged(key, status) { - Daemon.currentWallet.requestModel.updateRequest(key, status) + } + + RequestDetails { + id: request + wallet: Daemon.currentWallet + key: dialog.key + onDetailsChanged: { + if (bolt11) { + rootLayout.state = 'bolt11' + } else if (bip21) { + rootLayout.state = 'bip21uri' + } else { + rootLayout.state = 'address' + } + } + } + + ReceiveDetailsDialog { + id: receiveDetailsDialog + onAccepted: { + console.log('accepted') + Daemon.currentWallet.delete_request(request.key) + createRequest() + } + onRejected: { + console.log('rejected') } } Component.onCompleted: { - _address = '1234567890' - rootLayout.state = 'address' + createDefaultRequest() } // hack. delay qr rendering until dialog is shown diff --git a/electrum/gui/qml/qerequestdetails.py b/electrum/gui/qml/qerequestdetails.py index fba61c135..8dbf9f71f 100644 --- a/electrum/gui/qml/qerequestdetails.py +++ b/electrum/gui/qml/qerequestdetails.py @@ -77,7 +77,7 @@ class QERequestDetails(QObject): @pyqtProperty(str, notify=detailsChanged) def address(self): - addr = self._req.get_address() + addr = self._req.get_address() if self._req else '' return addr if addr else '' @pyqtProperty(str, notify=detailsChanged) @@ -98,11 +98,11 @@ class QERequestDetails(QObject): @pyqtProperty(str, notify=detailsChanged) def bolt11(self): - return self._req.lightning_invoice + return self._req.lightning_invoice if self._req else '' @pyqtProperty(str, notify=detailsChanged) def bip21(self): - return self._req.get_bip21_URI() + return self._req.get_bip21_URI() if self._req else '' @pyqtSlot(str, int) @@ -124,6 +124,7 @@ class QERequestDetails(QObject): self._amount = QEAmount(from_invoice=self._req) + self.detailsChanged.emit() self.initStatusStringTimer() def initStatusStringTimer(self): diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index 9bc312805..e50aa5c67 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -526,28 +526,50 @@ class QEWallet(AuthMixin, QObject, QtEventListener): return assert key is not None - self._requestModel.add_invoice(self.wallet.get_request(key)) + self.requestModel.add_invoice(self.wallet.get_request(key)) + self.requestCreateSuccess.emit(key) + + @pyqtSlot() + @pyqtSlot(bool) + def create_default_request(self, ignore_gap: bool = False): + try: + if self.wallet.lnworker.channels: + if self.wallet.config.get('bolt11_fallback', True): + addr = self.wallet.get_unused_address() + # if addr is None, we ran out of addresses. for lightning enabled wallets, ignore for now + key = self.wallet.create_request(None, None, 3600, addr) # TODO : expiration from config + else: + key, addr = self.create_bitcoin_request(None, None, 3600, ignore_gap) + if not key: + return + # self.addressModel.init_model() + except InvoiceError as e: + self.requestCreateError.emit('fatal',_('Error creating payment request') + ':\n' + str(e)) + return + + assert key is not None + self.requestModel.add_invoice(self.wallet.get_request(key)) self.requestCreateSuccess.emit(key) @pyqtSlot(str) def delete_request(self, key: str): self._logger.debug('delete req %s' % key) self.wallet.delete_request(key) - self._requestModel.delete_invoice(key) + self.requestModel.delete_invoice(key) @pyqtSlot(str, result='QVariant') def get_request(self, key: str): - return self._requestModel.get_model_invoice(key) + return self.requestModel.get_model_invoice(key) @pyqtSlot(str) def delete_invoice(self, key: str): self._logger.debug('delete inv %s' % key) self.wallet.delete_invoice(key) - self._invoiceModel.delete_invoice(key) + self.invoiceModel.delete_invoice(key) @pyqtSlot(str, result='QVariant') def get_invoice(self, key: str): - return self._invoiceModel.get_model_invoice(key) + return self.invoiceModel.get_model_invoice(key) @pyqtSlot(str, result=bool) def verify_password(self, password): diff --git a/electrum/wallet.py b/electrum/wallet.py index c464f4ed5..b1505d010 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -2467,6 +2467,7 @@ class Abstract_Wallet(ABC, Logger, EventListener): # for receiving amount_sat = amount_sat or 0 assert isinstance(amount_sat, int), f"{amount_sat!r}" + message = message or '' address = address or None # converts "" to None exp_delay = exp_delay or 0 timestamp = int(time.time())