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.
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Normal file
161
electrum/gui/qml/qerequestdetails.py
Normal file
@@ -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()
|
||||||
|
|
||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user