diff --git a/electrum/gui/qml/components/Receive.qml b/electrum/gui/qml/components/Receive.qml index 5b8451756..64ef1c127 100644 --- a/electrum/gui/qml/components/Receive.qml +++ b/electrum/gui/qml/components/Receive.qml @@ -153,7 +153,7 @@ Pane { model: Daemon.currentWallet.requestModel delegate: InvoiceDelegate { onClicked: { - var dialog = requestdialog.createObject(app, {'modelItem': model}) + var dialog = requestdialog.createObject(app, {key: model.key}) dialog.open() } } @@ -204,12 +204,10 @@ Pane { Connections { target: Daemon.currentWallet - function onRequestCreateSuccess() { + function onRequestCreateSuccess(key) { message.text = '' amount.text = '' - var dialog = requestdialog.createObject(app, { - 'modelItem': delegateModel.items.get(0).model - }) + var dialog = requestdialog.createObject(app, { key: key }) dialog.open() } function onRequestCreateError(code, error) { diff --git a/electrum/gui/qml/components/RequestDialog.qml b/electrum/gui/qml/components/RequestDialog.qml index 5ee6e7636..4f55ca33c 100644 --- a/electrum/gui/qml/components/RequestDialog.qml +++ b/electrum/gui/qml/components/RequestDialog.qml @@ -11,7 +11,7 @@ ElDialog { id: dialog title: qsTr('Payment Request') - property var modelItem + property string key property string _bolt11 property string _bip21uri @@ -174,7 +174,7 @@ ElDialog { icon.source: '../../icons/delete.png' text: qsTr('Delete') onClicked: { - Daemon.currentWallet.delete_request(modelItem.key) + Daemon.currentWallet.delete_request(request.key) dialog.close() } } @@ -183,7 +183,7 @@ ElDialog { icon.color: 'transparent' text: 'Copy' onClicked: { - if (modelItem.is_lightning && rootLayout.state == 'bolt11') + if (request.isLightning && rootLayout.state == 'bolt11') AppController.textToClipboard(_bolt11) else if (rootLayout.state == 'bip21uri') AppController.textToClipboard(_bip21uri) @@ -196,7 +196,7 @@ ElDialog { text: 'Share' onClicked: { enabled = false - if (modelItem.is_lightning && rootLayout.state == 'bolt11') + if (request.isLightning && rootLayout.state == 'bolt11') AppController.doShare(_bolt11, qsTr('Payment Request')) else if (rootLayout.state == 'bip21uri') AppController.doShare(_bip21uri, qsTr('Payment Request')) @@ -212,25 +212,25 @@ ElDialog { columns: 2 Label { - visible: modelItem.message != '' + visible: request.message != '' text: qsTr('Description') } Label { - visible: modelItem.message != '' + visible: request.message != '' Layout.fillWidth: true wrapMode: Text.Wrap - text: modelItem.message + text: request.message font.pixelSize: constants.fontSizeLarge } Label { - visible: modelItem.amount.satsInt != 0 + visible: request.amount.satsInt != 0 text: qsTr('Amount') } RowLayout { - visible: modelItem.amount.satsInt != 0 + visible: request.amount.satsInt != 0 Label { - text: Config.formatSats(modelItem.amount) + text: Config.formatSats(request.amount) font.family: FixedFont font.pixelSize: constants.fontSizeLarge font.bold: true @@ -245,7 +245,7 @@ ElDialog { id: fiatValue Layout.fillWidth: true text: Daemon.fx.enabled - ? '(' + Daemon.fx.fiatValue(modelItem.amount, false) + ' ' + Daemon.fx.fiatCurrency + ')' + ? '(' + Daemon.fx.fiatValue(request.amount, false) + ' ' + Daemon.fx.fiatCurrency + ')' : '' font.pixelSize: constants.fontSizeMedium wrapMode: Text.Wrap @@ -253,17 +253,17 @@ ElDialog { } Label { - visible: modelItem.address + visible: request.address text: qsTr('Address') } Label { - visible: modelItem.address + visible: request.address Layout.fillWidth: true font.family: FixedFont font.pixelSize: constants.fontSizeLarge wrapMode: Text.WrapAnywhere - text: modelItem.address + text: request.address } Label { @@ -272,37 +272,30 @@ ElDialog { Label { Layout.fillWidth: true font.pixelSize: constants.fontSizeLarge - text: modelItem.status_str + text: request.status_str } } } } - Connections { - target: Daemon.currentWallet - function onRequestStatusChanged(key, status) { - if (key != modelItem.key) - return - modelItem = Daemon.currentWallet.get_request(key) - } - } - Component.onCompleted: { - if (!modelItem.is_lightning) { - _bip21uri = bitcoin.create_bip21_uri(modelItem.address, modelItem.amount, modelItem.message, modelItem.timestamp, modelItem.expiration - modelItem.timestamp) - _address = modelItem.address + if (!request.isLightning) { + _bip21uri = request.bip21 + _address = request.address rootLayout.state = 'bip21uri' } else { - _bolt11 = modelItem.lightning_invoice + _bolt11 = request.bolt11 rootLayout.state = 'bolt11' - if (modelItem.address != '') { - _bip21uri = bitcoin.create_bip21_uri(modelItem.address, modelItem.amount, modelItem.message, modelItem.timestamp, modelItem.expiration - modelItem.timestamp) - _address = modelItem.address + if (request.address != '') { + _bip21uri = request.bip21 + _address = request.address } } } - Bitcoin { - id: bitcoin + RequestDetails { + id: request + wallet: Daemon.currentWallet + key: dialog.key } } diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index 99918de07..630697928 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -20,6 +20,7 @@ from .qebitcoin import QEBitcoin from .qefx import QEFX from .qetxfinalizer import QETxFinalizer from .qeinvoice import QEInvoice, QEInvoiceParser, QEUserEnteredPayment +from .qerequestdetails import QERequestDetails from .qetypes import QEAmount from .qeaddressdetails import QEAddressDetails from .qetxdetails import QETxDetails @@ -156,6 +157,7 @@ class ElectrumQmlApplication(QGuiApplication): qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails') qmlRegisterType(QEChannelDetails, 'org.electrum', 1, 0, 'ChannelDetails') qmlRegisterType(QESwapHelper, 'org.electrum', 1, 0, 'SwapHelper') + qmlRegisterType(QERequestDetails, 'org.electrum', 1, 0, 'RequestDetails') qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property') diff --git a/electrum/gui/qml/qerequestdetails.py b/electrum/gui/qml/qerequestdetails.py new file mode 100644 index 000000000..30522cd58 --- /dev/null +++ b/electrum/gui/qml/qerequestdetails.py @@ -0,0 +1,161 @@ +from time import time + +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer + +from electrum.logging import get_logger +from electrum.invoices import PR_UNPAID, LN_EXPIRY_NEVER + +from .qewallet import QEWallet +from .qetypes import QEAmount + +class QERequestDetails(QObject): + _logger = get_logger(__name__) + + + _wallet = None + _key = None + _req = None + _timer = None + + _amount = None + + detailsChanged = pyqtSignal() # generic request properties changed signal + + def __init__(self, parent=None): + super().__init__(parent) + + def __del__(self): + if self._wallet: + self._wallet.requestStatusChanged.disconnect(self.updateRequestStatus) + if self._timer: + self._timer.stop() + self._timer = None + + walletChanged = pyqtSignal() + @pyqtProperty(QEWallet, notify=walletChanged) + def wallet(self): + return self._wallet + + @wallet.setter + def wallet(self, wallet: QEWallet): + if self._wallet != wallet: + if self._wallet: + self._wallet.requestStatusChanged.disconnect(self.updateRequestStatus) + self._wallet = wallet + self.walletChanged.emit() + + wallet.requestStatusChanged.connect(self.updateRequestStatus) + + self.initRequest() + + keyChanged = pyqtSignal() + @pyqtProperty(str, notify=keyChanged) + def key(self): + return self._key + + @key.setter + def key(self, key): + if self._key != key: + self._key = key + self._logger.debug(f'key={key}') + self.keyChanged.emit() + self.initRequest() + + statusChanged = pyqtSignal() + @pyqtProperty(int, notify=statusChanged) + def status(self): + return self._wallet.wallet.get_request_status(self._key) + + statusStringChanged = pyqtSignal() + @pyqtProperty(str, notify=statusStringChanged) + def status_str(self): + return self._req.get_status_str(self.status) + + + @pyqtProperty(bool, notify=detailsChanged) + def isLightning(self): + return self._req.is_lightning() + + @pyqtProperty(str, notify=detailsChanged) + def address(self): + addr = self._req.get_address() + return addr if addr else '' + + @pyqtProperty(str, notify=detailsChanged) + def message(self): + return self._req.get_message() + + @pyqtProperty(QEAmount, notify=detailsChanged) + def amount(self): + return self._amount + + @pyqtProperty(int, notify=detailsChanged) + def timestamp(self): + return self._req.get_time() + + @pyqtProperty(int, notify=detailsChanged) + def expiration(self): + return self._req.get_expiration_date() + + @pyqtProperty(str, notify=detailsChanged) + def bolt11(self): + return self._req.lightning_invoice + + @pyqtProperty(str, notify=detailsChanged) + def bip21(self): + return self._req.get_bip21_URI() + + + @pyqtSlot(str, int) + def updateRequestStatus(self, key, status): + if key == self._key: + self._logger.debug(f'request with key {key} updated status ({status})') + self.statusChanged.emit() + + + def initRequest(self): + if self._wallet is None or self._key is None: + return + + self._req = self._wallet.wallet.get_request(self._key) + + if self._req is None: + self._logger.error(f'payment request key {key} unknown in wallet {self._wallet.name}') + return + + self._amount = QEAmount(from_invoice=self._req) + + self.initStatusStringTimer() + + def initStatusStringTimer(self): + if self.status == PR_UNPAID: + if self.expiration > 0 and self.expiration != LN_EXPIRY_NEVER: + self._timer = QTimer(self) + self._timer.setSingleShot(True) + self._timer.timeout.connect(self.updateStatusString) + + # very roughly according to util.time_difference + exp_in = int(self.expiration - time()) + exp_in_min = int(exp_in/60) + + interval = 0 + if exp_in < 0: + interval = 0 + if exp_in_min < 2: + interval = 1000 + elif exp_in_min < 90: + interval = 1000 * 60 + elif exp_in_min < 1440: + interval = 1000 * 60 * 60 + + if interval > 0: + self._logger.debug(f'setting status update timer to {interval}, req expires in {exp_in} seconds') + self._timer.setInterval(interval) # msec + self._timer.start() + + + @pyqtSlot() + def updateStatusString(self): + self.statusStringChanged.emit() + self.initStatusStringTimer() + diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index 12a8564d0..14e194f80 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -51,7 +51,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener): isUptodateChanged = pyqtSignal() requestStatusChanged = pyqtSignal([str,int], arguments=['key','status']) - requestCreateSuccess = pyqtSignal() + requestCreateSuccess = pyqtSignal([str], arguments=['key']) requestCreateError = pyqtSignal([str,str], arguments=['code','error']) invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status']) invoiceCreateSuccess = pyqtSignal() @@ -526,7 +526,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener): assert key is not None self._requestModel.add_invoice(self.wallet.get_request(key)) - self.requestCreateSuccess.emit() + self.requestCreateSuccess.emit(key) @pyqtSlot(str) def delete_request(self, key: str):