Browse Source

qml: add QERequestDetails component.

Auto binds to wallet request status update signal so qml doesn't have to.
implements timer to update status string when near expiry.
patch-4
Sander van Grieken 2 years ago
parent
commit
f05ff0c9b8
  1. 8
      electrum/gui/qml/components/Receive.qml
  2. 59
      electrum/gui/qml/components/RequestDialog.qml
  3. 2
      electrum/gui/qml/qeapp.py
  4. 161
      electrum/gui/qml/qerequestdetails.py
  5. 4
      electrum/gui/qml/qewallet.py

8
electrum/gui/qml/components/Receive.qml

@ -153,7 +153,7 @@ Pane {
model: Daemon.currentWallet.requestModel model: Daemon.currentWallet.requestModel
delegate: InvoiceDelegate { delegate: InvoiceDelegate {
onClicked: { onClicked: {
var dialog = requestdialog.createObject(app, {'modelItem': model}) var dialog = requestdialog.createObject(app, {key: model.key})
dialog.open() dialog.open()
} }
} }
@ -204,12 +204,10 @@ Pane {
Connections { Connections {
target: Daemon.currentWallet target: Daemon.currentWallet
function onRequestCreateSuccess() { function onRequestCreateSuccess(key) {
message.text = '' message.text = ''
amount.text = '' amount.text = ''
var dialog = requestdialog.createObject(app, { var dialog = requestdialog.createObject(app, { key: key })
'modelItem': delegateModel.items.get(0).model
})
dialog.open() dialog.open()
} }
function onRequestCreateError(code, error) { function onRequestCreateError(code, error) {

59
electrum/gui/qml/components/RequestDialog.qml

@ -11,7 +11,7 @@ ElDialog {
id: dialog id: dialog
title: qsTr('Payment Request') title: qsTr('Payment Request')
property var modelItem property string key
property string _bolt11 property string _bolt11
property string _bip21uri property string _bip21uri
@ -174,7 +174,7 @@ ElDialog {
icon.source: '../../icons/delete.png' icon.source: '../../icons/delete.png'
text: qsTr('Delete') text: qsTr('Delete')
onClicked: { onClicked: {
Daemon.currentWallet.delete_request(modelItem.key) Daemon.currentWallet.delete_request(request.key)
dialog.close() dialog.close()
} }
} }
@ -183,7 +183,7 @@ ElDialog {
icon.color: 'transparent' icon.color: 'transparent'
text: 'Copy' text: 'Copy'
onClicked: { onClicked: {
if (modelItem.is_lightning && rootLayout.state == 'bolt11') if (request.isLightning && rootLayout.state == 'bolt11')
AppController.textToClipboard(_bolt11) AppController.textToClipboard(_bolt11)
else if (rootLayout.state == 'bip21uri') else if (rootLayout.state == 'bip21uri')
AppController.textToClipboard(_bip21uri) AppController.textToClipboard(_bip21uri)
@ -196,7 +196,7 @@ ElDialog {
text: 'Share' text: 'Share'
onClicked: { onClicked: {
enabled = false enabled = false
if (modelItem.is_lightning && rootLayout.state == 'bolt11') if (request.isLightning && rootLayout.state == 'bolt11')
AppController.doShare(_bolt11, qsTr('Payment Request')) AppController.doShare(_bolt11, qsTr('Payment Request'))
else if (rootLayout.state == 'bip21uri') else if (rootLayout.state == 'bip21uri')
AppController.doShare(_bip21uri, qsTr('Payment Request')) AppController.doShare(_bip21uri, qsTr('Payment Request'))
@ -212,25 +212,25 @@ ElDialog {
columns: 2 columns: 2
Label { Label {
visible: modelItem.message != '' visible: request.message != ''
text: qsTr('Description') text: qsTr('Description')
} }
Label { Label {
visible: modelItem.message != '' visible: request.message != ''
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
text: modelItem.message text: request.message
font.pixelSize: constants.fontSizeLarge font.pixelSize: constants.fontSizeLarge
} }
Label { Label {
visible: modelItem.amount.satsInt != 0 visible: request.amount.satsInt != 0
text: qsTr('Amount') text: qsTr('Amount')
} }
RowLayout { RowLayout {
visible: modelItem.amount.satsInt != 0 visible: request.amount.satsInt != 0
Label { Label {
text: Config.formatSats(modelItem.amount) text: Config.formatSats(request.amount)
font.family: FixedFont font.family: FixedFont
font.pixelSize: constants.fontSizeLarge font.pixelSize: constants.fontSizeLarge
font.bold: true font.bold: true
@ -245,7 +245,7 @@ ElDialog {
id: fiatValue id: fiatValue
Layout.fillWidth: true Layout.fillWidth: true
text: Daemon.fx.enabled 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 font.pixelSize: constants.fontSizeMedium
wrapMode: Text.Wrap wrapMode: Text.Wrap
@ -253,17 +253,17 @@ ElDialog {
} }
Label { Label {
visible: modelItem.address visible: request.address
text: qsTr('Address') text: qsTr('Address')
} }
Label { Label {
visible: modelItem.address visible: request.address
Layout.fillWidth: true Layout.fillWidth: true
font.family: FixedFont font.family: FixedFont
font.pixelSize: constants.fontSizeLarge font.pixelSize: constants.fontSizeLarge
wrapMode: Text.WrapAnywhere wrapMode: Text.WrapAnywhere
text: modelItem.address text: request.address
} }
Label { Label {
@ -272,37 +272,30 @@ ElDialog {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
font.pixelSize: constants.fontSizeLarge 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: { Component.onCompleted: {
if (!modelItem.is_lightning) { if (!request.isLightning) {
_bip21uri = bitcoin.create_bip21_uri(modelItem.address, modelItem.amount, modelItem.message, modelItem.timestamp, modelItem.expiration - modelItem.timestamp) _bip21uri = request.bip21
_address = modelItem.address _address = request.address
rootLayout.state = 'bip21uri' rootLayout.state = 'bip21uri'
} else { } else {
_bolt11 = modelItem.lightning_invoice _bolt11 = request.bolt11
rootLayout.state = 'bolt11' rootLayout.state = 'bolt11'
if (modelItem.address != '') { if (request.address != '') {
_bip21uri = bitcoin.create_bip21_uri(modelItem.address, modelItem.amount, modelItem.message, modelItem.timestamp, modelItem.expiration - modelItem.timestamp) _bip21uri = request.bip21
_address = modelItem.address _address = request.address
} }
} }
} }
Bitcoin { RequestDetails {
id: bitcoin id: request
wallet: Daemon.currentWallet
key: dialog.key
} }
} }

2
electrum/gui/qml/qeapp.py

@ -20,6 +20,7 @@ from .qebitcoin import QEBitcoin
from .qefx import QEFX from .qefx import QEFX
from .qetxfinalizer import QETxFinalizer from .qetxfinalizer import QETxFinalizer
from .qeinvoice import QEInvoice, QEInvoiceParser, QEUserEnteredPayment from .qeinvoice import QEInvoice, QEInvoiceParser, QEUserEnteredPayment
from .qerequestdetails import QERequestDetails
from .qetypes import QEAmount from .qetypes import QEAmount
from .qeaddressdetails import QEAddressDetails from .qeaddressdetails import QEAddressDetails
from .qetxdetails import QETxDetails from .qetxdetails import QETxDetails
@ -156,6 +157,7 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails') qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails')
qmlRegisterType(QEChannelDetails, 'org.electrum', 1, 0, 'ChannelDetails') qmlRegisterType(QEChannelDetails, 'org.electrum', 1, 0, 'ChannelDetails')
qmlRegisterType(QESwapHelper, 'org.electrum', 1, 0, 'SwapHelper') 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') qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')

161
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()

4
electrum/gui/qml/qewallet.py

@ -51,7 +51,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
isUptodateChanged = pyqtSignal() isUptodateChanged = pyqtSignal()
requestStatusChanged = pyqtSignal([str,int], arguments=['key','status']) requestStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
requestCreateSuccess = pyqtSignal() requestCreateSuccess = pyqtSignal([str], arguments=['key'])
requestCreateError = pyqtSignal([str,str], arguments=['code','error']) requestCreateError = pyqtSignal([str,str], arguments=['code','error'])
invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status']) invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
invoiceCreateSuccess = pyqtSignal() invoiceCreateSuccess = pyqtSignal()
@ -526,7 +526,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
assert key is not None 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() self.requestCreateSuccess.emit(key)
@pyqtSlot(str) @pyqtSlot(str)
def delete_request(self, key: str): def delete_request(self, key: str):

Loading…
Cancel
Save