From 3fd33169f54636274b794cd721a636b44a93b860 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 3 Jun 2022 15:19:24 +0200 Subject: [PATCH] frontend improvements, refactor qeinvoicelistmodel --- electrum/gui/qml/components/Receive.qml | 4 +- electrum/gui/qml/components/RequestDialog.qml | 9 +++- electrum/gui/qml/components/Send.qml | 12 +++-- .../gui/qml/components/controls/FiatField.qml | 4 +- .../components/controls/InvoiceDelegate.qml | 26 +++++++--- electrum/gui/qml/qebitcoin.py | 2 +- electrum/gui/qml/qeinvoicelistmodel.py | 51 +++++++++++-------- electrum/gui/qml/qetypes.py | 15 +++++- 8 files changed, 83 insertions(+), 40 deletions(-) diff --git a/electrum/gui/qml/components/Receive.qml b/electrum/gui/qml/components/Receive.qml index 92507eccb..44cbf0f1c 100644 --- a/electrum/gui/qml/components/Receive.qml +++ b/electrum/gui/qml/components/Receive.qml @@ -39,7 +39,7 @@ Pane { BtcField { id: amount fiatfield: amountFiat - Layout.preferredWidth: parent.width /2 + Layout.preferredWidth: parent.width /3 } Label { @@ -55,7 +55,7 @@ Pane { id: amountFiat btcfield: amount visible: Daemon.fx.enabled - Layout.preferredWidth: parent.width /2 + Layout.preferredWidth: parent.width /3 } Label { diff --git a/electrum/gui/qml/components/RequestDialog.qml b/electrum/gui/qml/components/RequestDialog.qml index 5f21f70b8..437602077 100644 --- a/electrum/gui/qml/components/RequestDialog.qml +++ b/electrum/gui/qml/components/RequestDialog.qml @@ -164,10 +164,12 @@ Dialog { Label { text: qsTr('Address') + visible: !modelItem.is_lightning } Label { Layout.fillWidth: true Layout.columnSpan: 3 + visible: !modelItem.is_lightning font.family: FixedFont font.pixelSize: constants.fontSizeLarge wrapMode: Text.WrapAnywhere @@ -175,6 +177,7 @@ Dialog { } ToolButton { icon.source: '../../icons/copy_bw.png' + visible: !modelItem.is_lightning onClicked: { AppController.textToClipboard(modelItem.address) } @@ -203,8 +206,10 @@ Dialog { } Component.onCompleted: { - _bip21uri = bitcoin.create_uri(modelItem.address, modelItem.amount, modelItem.message, modelItem.timestamp, modelItem.expiration - modelItem.timestamp) - qr.source = 'image://qrgen/' + _bip21uri + if (!modelItem.is_lightning) { + _bip21uri = bitcoin.create_bip21_uri(modelItem.address, modelItem.amount, modelItem.message, modelItem.timestamp, modelItem.expiration - modelItem.timestamp) + qr.source = 'image://qrgen/' + _bip21uri + } } Bitcoin { diff --git a/electrum/gui/qml/components/Send.qml b/electrum/gui/qml/components/Send.qml index 32422390f..02deca968 100644 --- a/electrum/gui/qml/components/Send.qml +++ b/electrum/gui/qml/components/Send.qml @@ -74,7 +74,7 @@ Pane { BtcField { id: amount fiatfield: amountFiat - Layout.preferredWidth: parent.width /2 + Layout.preferredWidth: parent.width /3 } Label { @@ -91,7 +91,7 @@ Pane { id: amountFiat btcfield: amount visible: Daemon.fx.enabled - Layout.preferredWidth: parent.width /2 + Layout.preferredWidth: parent.width /3 } Label { @@ -123,6 +123,7 @@ Pane { Button { text: qsTr('Save') enabled: invoice.invoiceType != Invoice.Invalid + icon.source: '../../icons/save.png' onClicked: { Daemon.currentWallet.create_invoice(recipient.text, amount.text, message.text) } @@ -131,6 +132,7 @@ Pane { Button { text: qsTr('Pay now') enabled: invoice.invoiceType != Invoice.Invalid // TODO && has funds + icon.source: '../../icons/confirmed.png' onClicked: { var f_amount = parseFloat(amount.text) if (isNaN(f_amount)) @@ -193,7 +195,7 @@ Pane { model: Daemon.currentWallet.invoiceModel delegate: InvoiceDelegate { onClicked: { - var dialog = confirmInvoiceDialog.createObject(app, {'invoice' : invoice, 'invoice_key': model.key}) + var dialog = invoiceDialog.createObject(app, {'invoice' : invoice, 'invoice_key': model.key}) dialog.open() } } @@ -227,7 +229,7 @@ Pane { } Component { - id: confirmInvoiceDialog + id: invoiceDialog InvoiceDialog { onDoPay: { if (invoice.invoiceType == Invoice.OnchainInvoice) { @@ -285,7 +287,7 @@ Pane { if (invoiceType == Invoice.OnchainOnlyAddress) recipient.text = invoice.recipient else { - var dialog = confirmInvoiceDialog.createObject(rootItem, {'invoice': invoice}) + var dialog = invoiceDialog.createObject(rootItem, {'invoice': invoice}) dialog.open() } } diff --git a/electrum/gui/qml/components/controls/FiatField.qml b/electrum/gui/qml/components/controls/FiatField.qml index 59cb0f180..3c2ed376c 100644 --- a/electrum/gui/qml/components/controls/FiatField.qml +++ b/electrum/gui/qml/components/controls/FiatField.qml @@ -13,7 +13,9 @@ TextField { inputMethodHints: Qt.ImhPreferNumbers onTextChanged: { if (amountFiat.activeFocus) - btcfield.text = text == '' ? '' : Config.satsToUnits(Daemon.fx.satoshiValue(amountFiat.text)) + btcfield.text = text == '' + ? '' + : Config.satsToUnits(Daemon.fx.satoshiValue(amountFiat.text)) } Connections { diff --git a/electrum/gui/qml/components/controls/InvoiceDelegate.qml b/electrum/gui/qml/components/controls/InvoiceDelegate.qml index 67e9c0239..4e6cd40f0 100644 --- a/electrum/gui/qml/components/controls/InvoiceDelegate.qml +++ b/electrum/gui/qml/components/controls/InvoiceDelegate.qml @@ -36,6 +36,18 @@ ItemDelegate { source: model.is_lightning ? "../../../icons/lightning.png" : "../../../icons/bitcoin.png" + + Image { + visible: model.onchain_fallback + z: -1 + source: "../../../icons/bitcoin.png" + anchors { + right: parent.right + bottom: parent.bottom + } + width: parent.width /2 + height: parent.height /2 + } } RowLayout { @@ -55,13 +67,13 @@ ItemDelegate { Label { id: amount - text: model.amount == 0 ? '' : Config.formatSats(model.amount) + text: model.amount.isEmpty ? '' : Config.formatSats(model.amount) font.pixelSize: constants.fontSizeMedium font.family: FixedFont } Label { - text: model.amount == 0 ? '' : Config.baseUnit + text: model.amount.isEmpty ? '' : Config.baseUnit font.pixelSize: constants.fontSizeMedium color: Material.accentColor } @@ -95,14 +107,14 @@ ItemDelegate { id: fiatValue visible: Daemon.fx.enabled Layout.alignment: Qt.AlignRight - text: model.amount == 0 ? '' : Daemon.fx.fiatValue(model.amount, false) + text: model.amount.isEmpty ? '' : Daemon.fx.fiatValue(model.amount, false) font.family: FixedFont font.pixelSize: constants.fontSizeSmall } Label { visible: Daemon.fx.enabled Layout.alignment: Qt.AlignRight - text: model.amount == 0 ? '' : Daemon.fx.fiatCurrency + text: model.amount.isEmpty ? '' : Daemon.fx.fiatCurrency font.pixelSize: constants.fontSizeSmall color: Material.accentColor } @@ -119,16 +131,16 @@ ItemDelegate { Connections { target: Config function onBaseUnitChanged() { - amount.text = model.amount == 0 ? '' : Config.formatSats(model.amount) + amount.text = model.amount.isEmpty ? '' : Config.formatSats(model.amount) } function onThousandsSeparatorChanged() { - amount.text = model.amount == 0 ? '' : Config.formatSats(model.amount) + amount.text = model.amount.isEmpty ? '' : Config.formatSats(model.amount) } } Connections { target: Daemon.fx function onQuotesUpdated() { - fiatValue.text = model.amount == 0 ? '' : Daemon.fx.fiatValue(model.amount, false) + fiatValue.text = model.amount.isEmpty ? '' : Daemon.fx.fiatValue(model.amount, false) } } diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index 2dbfaf34e..eb9d1625f 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -123,7 +123,7 @@ class QEBitcoin(QObject): return { 'error': str(e) } @pyqtSlot(str, QEAmount, str, int, int, result=str) - def create_uri(self, address, satoshis, message, timestamp, expiry): + def create_bip21_uri(self, address, satoshis, message, timestamp, expiry): extra_params = {} if expiry: extra_params['time'] = str(timestamp) diff --git a/electrum/gui/qml/qeinvoicelistmodel.py b/electrum/gui/qml/qeinvoicelistmodel.py index ef041c96b..8fa464e9c 100644 --- a/electrum/gui/qml/qeinvoicelistmodel.py +++ b/electrum/gui/qml/qeinvoicelistmodel.py @@ -18,7 +18,8 @@ class QEAbstractInvoiceListModel(QAbstractListModel): self.init_model() # define listmodel rolemap - _ROLE_NAMES=('key','is_lightning','timestamp','date','message','amount','status','status_str','address','expiration','type') + _ROLE_NAMES=('key', 'is_lightning', 'timestamp', 'date', 'message', 'amount', + 'status', 'status_str', 'address', 'expiration', 'type', 'onchain_fallback') _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) _ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) @@ -96,6 +97,19 @@ class QEAbstractInvoiceListModel(QAbstractListModel): return i = i + 1 + def invoice_to_model(self, invoice: Invoice): + item = self.get_invoice_as_dict(invoice) + item['key'] = invoice.get_id() + item['is_lightning'] = invoice.is_lightning() + if invoice.is_lightning() and 'address' not in item: + item['address'] = '' + item['date'] = format_time(item['timestamp']) + item['amount'] = QEAmount(from_invoice=invoice) + item['onchain_fallback'] = invoice.is_lightning() and invoice._lnaddr.get_fallback_address() + item['type'] = 'invoice' + + return item + @abstractmethod def get_invoice_for_key(self, key: str): raise Exception('provide impl') @@ -105,55 +119,52 @@ class QEAbstractInvoiceListModel(QAbstractListModel): raise Exception('provide impl') @abstractmethod - def invoice_to_model(self, invoice: Invoice): + def get_invoice_as_dict(self, invoice: Invoice): raise Exception('provide impl') + class QEInvoiceListModel(QEAbstractInvoiceListModel): def __init__(self, wallet, parent=None): super().__init__(wallet, parent) _logger = get_logger(__name__) - def get_invoice_list(self): - return self.wallet.get_unpaid_invoices() - def invoice_to_model(self, invoice: Invoice): - item = self.wallet.export_invoice(invoice) - item['is_lightning'] = invoice.is_lightning() - item['date'] = format_time(item['timestamp']) - item['amount'] = QEAmount(amount_sat=invoice.get_amount_sat()) - item['key'] = invoice.get_id() - + item = super().invoice_to_model(invoice) item['type'] = 'invoice' return item + def get_invoice_list(self): + return self.wallet.get_unpaid_invoices() + def get_invoice_for_key(self, key: str): return self.wallet.get_invoice(key) + def get_invoice_as_dict(self, invoice: Invoice): + return self.wallet.export_invoice(invoice) + class QERequestListModel(QEAbstractInvoiceListModel): def __init__(self, wallet, parent=None): super().__init__(wallet, parent) _logger = get_logger(__name__) - def get_invoice_list(self): - return self.wallet.get_unpaid_requests() - def invoice_to_model(self, req: Invoice): - item = self.wallet.export_request(req) - item['key'] = req.get_rhash() if req.is_lightning() else req.get_address() - item['is_lightning'] = req.is_lightning() - item['date'] = format_time(item['timestamp']) - item['amount'] = QEAmount(amount_sat=req.get_amount_sat()) - + item = super().invoice_to_model(req) item['type'] = 'request' return item + def get_invoice_list(self): + return self.wallet.get_unpaid_requests() + def get_invoice_for_key(self, key: str): return self.wallet.get_request(key) + def get_invoice_as_dict(self, req: Invoice): + return self.wallet.export_request(req) + @pyqtSlot(str, int) def updateRequest(self, key, status): self.updateInvoice(key, status) diff --git a/electrum/gui/qml/qetypes.py b/electrum/gui/qml/qetypes.py index baba6e219..641f9e03d 100644 --- a/electrum/gui/qml/qetypes.py +++ b/electrum/gui/qml/qetypes.py @@ -15,11 +15,18 @@ from electrum.util import profiler class QEAmount(QObject): _logger = get_logger(__name__) - def __init__(self, *, amount_sat: int = 0, amount_msat: int = 0, is_max: bool = False, parent=None): + def __init__(self, *, amount_sat: int = 0, amount_msat: int = 0, is_max: bool = False, from_invoice = None, parent=None): super().__init__(parent) self._amount_sat = amount_sat self._amount_msat = amount_msat self._is_max = is_max + if from_invoice: + inv_amt = from_invoice.get_amount_msat() + if inv_amt == '!': + self._is_max = True + elif inv_amt is not None: + self._amount_msat = inv_amt + self._amount_sat = from_invoice.get_amount_sat() valueChanged = pyqtSignal() @@ -43,6 +50,10 @@ class QEAmount(QObject): def isMax(self): return self._is_max + @pyqtProperty(bool, notify=valueChanged) + def isEmpty(self): + return not(self._is_max or self._amount_sat or self._amount_msat) + def __eq__(self, other): if isinstance(other, QEAmount): return self._amount_sat == other._amount_sat and self._amount_msat == other._amount_msat and self._is_max == other._is_max @@ -60,4 +71,4 @@ class QEAmount(QObject): return '%s(sats=%d, msats=%d)' % (s, self._amount_sat, self._amount_msat) def __repr__(self): - return f"" + return f""