diff --git a/electrum/gui/qml/components/Send.qml b/electrum/gui/qml/components/Send.qml index 7a81cf085..f97e048eb 100644 --- a/electrum/gui/qml/components/Send.qml +++ b/electrum/gui/qml/components/Send.qml @@ -2,6 +2,7 @@ import QtQuick 2.6 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 import QtQuick.Controls.Material 2.0 +import QtQml.Models 2.1 import "controls" @@ -200,15 +201,154 @@ Pane { Layout.fillHeight: true Layout.fillWidth: true clip: true + + model: DelegateModel { + id: delegateModel + model: Daemon.currentWallet.invoiceModel + + delegate: ItemDelegate { + id: root + height: item.height + width: ListView.view.width + + font.pixelSize: constants.fontSizeSmall // set default font size for child controls + + GridLayout { + id: item + + anchors { + left: parent.left + right: parent.right + leftMargin: constants.paddingSmall + rightMargin: constants.paddingSmall + } + + columns: 2 + + Rectangle { + Layout.columnSpan: 2 + Layout.fillWidth: true + Layout.preferredHeight: constants.paddingTiny + color: 'transparent' + } + + Image { + Layout.rowSpan: 2 + Layout.preferredWidth: constants.iconSizeLarge + Layout.preferredHeight: constants.iconSizeLarge + source: model.type == 0 ? "../../icons/bitcoin.png" : "../../icons/lightning.png" + } + + RowLayout { + Layout.fillWidth: true + Label { + Layout.fillWidth: true + text: model.message ? model.message : model.address + elide: Text.ElideRight + wrapMode: Text.Wrap + maximumLineCount: 2 + font.pixelSize: model.message ? constants.fontSizeMedium : constants.fontSizeSmall + } + + Label { + id: amount + text: model.amount == 0 ? '' : Config.formatSats(model.amount) + font.pixelSize: constants.fontSizeMedium + font.family: FixedFont + } + + Label { + text: model.amount == 0 ? '' : Config.baseUnit + font.pixelSize: constants.fontSizeMedium + color: Material.accentColor + } + } + + RowLayout { + Layout.fillWidth: true + Label { + text: model.status_str + color: Material.accentColor + } + Item { + Layout.fillWidth: true + Layout.preferredHeight: status_icon.height + Image { + id: status_icon + source: model.status == 0 + ? '../../icons/unpaid.png' + : model.status == 1 + ? '../../icons/expired.png' + : model.status == 3 + ? '../../icons/confirmed.png' + : model.status == 7 + ? '../../icons/unconfirmed.png' + : '' + width: constants.iconSizeSmall + height: constants.iconSizeSmall + } + } + Label { + id: fiatValue + visible: Daemon.fx.enabled + Layout.alignment: Qt.AlignRight + text: model.amount == 0 ? '' : 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 + font.pixelSize: constants.fontSizeSmall + color: Material.accentColor + } + } + + Rectangle { + Layout.columnSpan: 2 + Layout.fillWidth: true + Layout.preferredHeight: constants.paddingTiny + color: 'transparent' + } + } + + Connections { + target: Config + function onBaseUnitChanged() { + amount.text = model.amount == 0 ? '' : Config.formatSats(model.amount) + } + function onThousandsSeparatorChanged() { + amount.text = model.amount == 0 ? '' : Config.formatSats(model.amount) + } + } + Connections { + target: Daemon.fx + function onQuotesUpdated() { + fiatValue.text = model.amount == 0 ? '' : Daemon.fx.fiatValue(model.amount, false) + } + } + + } + + } + + remove: Transition { + NumberAnimation { properties: 'scale'; to: 0.75; duration: 300 } + NumberAnimation { properties: 'opacity'; to: 0; duration: 300 } + } + removeDisplaced: Transition { + SequentialAnimation { + PauseAnimation { duration: 200 } + SpringAnimation { properties: 'y'; duration: 100; spring: 5; damping: 0.5; mass: 2 } + } + } + + ScrollIndicator.vertical: ScrollIndicator { } } } } - Component { - id: confirmPaymentDialog - ConfirmPaymentDialog {} - } - Connections { target: Daemon.fx function onQuotesUpdated() { diff --git a/electrum/gui/qml/qeinvoicelistmodel.py b/electrum/gui/qml/qeinvoicelistmodel.py new file mode 100644 index 000000000..596949894 --- /dev/null +++ b/electrum/gui/qml/qeinvoicelistmodel.py @@ -0,0 +1,148 @@ +from abc import abstractmethod + +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex + +from electrum.logging import get_logger +from electrum.util import Satoshis, format_time +from electrum.invoices import Invoice + +class QEAbstractInvoiceListModel(QAbstractListModel): + _logger = get_logger(__name__) + + def __init__(self, wallet, parent=None): + super().__init__(parent) + self.wallet = wallet + self.invoices = [] + + # define listmodel rolemap + _ROLE_NAMES=('key','type','timestamp','date','message','amount','status','status_str','address','expiration') + _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)) + + def rowCount(self, index): + return len(self.invoices) + + def roleNames(self): + return self._ROLE_MAP + + def data(self, index, role): + invoice = self.invoices[index.row()] + role_index = role - Qt.UserRole + value = invoice[self._ROLE_NAMES[role_index]] + if isinstance(value, bool) or isinstance(value, list) or isinstance(value, int) or value is None: + return value + if isinstance(value, Satoshis): + return value.value + return str(value) + + def clear(self): + self.beginResetModel() + self.invoices = [] + self.endResetModel() + + @pyqtSlot() + def init_model(self): + invoices = [] + for invoice in self.get_invoice_list(): + item = self.invoice_to_model(invoice) + self._logger.debug(str(item)) + invoices.append(item) + + self.clear() + self.beginInsertRows(QModelIndex(), 0, len(self.invoices) - 1) + self.invoices = invoices + self.endInsertRows() + + def add_invoice(self, invoice: Invoice): + item = self.invoice_to_model(invoice) + self._logger.debug(str(item)) + + self.beginInsertRows(QModelIndex(), 0, 0) + self.invoices.insert(0, item) + self.endInsertRows() + + def delete_invoice(self, key: str): + i = 0 + for invoice in self.invoices: + if invoice['key'] == key: + self.beginRemoveRows(QModelIndex(), i, i) + self.invoices.pop(i) + self.endRemoveRows() + break + i = i + 1 + + @pyqtSlot(str, int) + def updateInvoice(self, key, status): + self._logger.debug('updating invoice for %s to %d' % (key,status)) + i = 0 + for item in self.invoices: + if item['key'] == key: + invoice = self.get_invoice_for_key(key) #self.wallet.get_invoice(key) + item['status'] = status + item['status_str'] = invoice.get_status_str(status) + index = self.index(i,0) + self.dataChanged.emit(index, index, [self._ROLE_RMAP['status'], self._ROLE_RMAP['status_str']]) + i = i + 1 + + @abstractmethod + def get_invoice_for_key(self, key: str): + raise Exception('provide impl') + + @abstractmethod + def get_invoice_list(self): + raise Exception('provide impl') + + @abstractmethod + def invoice_to_model(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['type'] = invoice.type # 0=onchain, 2=LN + item['date'] = format_time(item['timestamp']) + item['amount'] = invoice.get_amount_sat() + if invoice.type == 0: + item['key'] = invoice.id + elif invoice.type == 2: + item['key'] = invoice.rhash + + return item + + def get_invoice_for_key(self, key: str): + return self.wallet.get_invoice(key) + +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'] = self.wallet.get_key_for_receive_request(req) + item['type'] = req.type # 0=onchain, 2=LN + item['date'] = format_time(item['timestamp']) + item['amount'] = req.get_amount_sat() + + return item + + def get_invoice_for_key(self, key: str): + return self.wallet.get_request(key) + + @pyqtSlot(str, int) + def updateRequest(self, key, status): + self.updateInvoice(key, status) diff --git a/electrum/gui/qml/qerequestlistmodel.py b/electrum/gui/qml/qerequestlistmodel.py deleted file mode 100644 index a664a106c..000000000 --- a/electrum/gui/qml/qerequestlistmodel.py +++ /dev/null @@ -1,94 +0,0 @@ -from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject -from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex - -from electrum.logging import get_logger -from electrum.util import Satoshis, format_time -from electrum.invoices import Invoice - -class QERequestListModel(QAbstractListModel): - def __init__(self, wallet, parent=None): - super().__init__(parent) - self.wallet = wallet - self.requests = [] - - _logger = get_logger(__name__) - - # define listmodel rolemap - _ROLE_NAMES=('key','type','timestamp','date','message','amount','status','status_str','address','expiration') - _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)) - - def rowCount(self, index): - return len(self.requests) - - def roleNames(self): - return self._ROLE_MAP - - def data(self, index, role): - request = self.requests[index.row()] - role_index = role - Qt.UserRole - value = request[self._ROLE_NAMES[role_index]] - if isinstance(value, bool) or isinstance(value, list) or isinstance(value, int) or value is None: - return value - if isinstance(value, Satoshis): - return value.value - return str(value) - - def clear(self): - self.beginResetModel() - self.requests = [] - self.endResetModel() - - def request_to_model(self, req: Invoice): - item = self.wallet.export_request(req) - item['key'] = self.wallet.get_key_for_receive_request(req) - item['type'] = req.type # 0=onchain, 2=LN - item['date'] = format_time(item['timestamp']) - item['amount'] = req.get_amount_sat() - - return item - - @pyqtSlot() - def init_model(self): - requests = [] - for req in self.wallet.get_unpaid_requests(): - item = self.request_to_model(req) - self._logger.debug(str(item)) - requests.append(item) - - self.clear() - self.beginInsertRows(QModelIndex(), 0, len(self.requests) - 1) - self.requests = requests - self.endInsertRows() - - def add_request(self, request: Invoice): - item = self.request_to_model(request) - self._logger.debug(str(item)) - - self.beginInsertRows(QModelIndex(), 0, 0) - self.requests.insert(0, item) - self.endInsertRows() - - def delete_request(self, key: str): - i = 0 - for request in self.requests: - if request['key'] == key: - self.beginRemoveRows(QModelIndex(), i, i) - self.requests.pop(i) - self.endRemoveRows() - break - i = i + 1 - - @pyqtSlot(str, int) - def updateRequest(self, key, status): - self._logger.debug('updating request for %s to %d' % (key,status)) - i = 0 - for item in self.requests: - if item['key'] == key: - req = self.wallet.get_request(key) - item['status'] = status - item['status_str'] = req.get_status_str(status) - index = self.index(i,0) - self.dataChanged.emit(index, index, [self._ROLE_RMAP['status'], self._ROLE_RMAP['status_str']]) - i = i + 1 diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index 73541df6d..e77e2c726 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -13,7 +13,7 @@ from electrum.transaction import PartialTxOutput from electrum.invoices import (Invoice, InvoiceError, PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, PR_TYPE_ONCHAIN, PR_TYPE_LN) -from .qerequestlistmodel import QERequestListModel +from .qeinvoicelistmodel import QEInvoiceListModel, QERequestListModel from .qetransactionlistmodel import QETransactionListModel from .qeaddresslistmodel import QEAddressListModel @@ -54,9 +54,11 @@ class QEWallet(QObject): self._historyModel = QETransactionListModel(wallet) self._addressModel = QEAddressListModel(wallet) self._requestModel = QERequestListModel(wallet) + self._invoiceModel = QEInvoiceListModel(wallet) self._historyModel.init_model() self._requestModel.init_model() + self._invoiceModel.init_model() self.tx_notification_queue = queue.Queue() self.tx_notification_last_time = 0 @@ -175,6 +177,11 @@ class QEWallet(QObject): def requestModel(self): return self._requestModel + invoiceModelChanged = pyqtSignal() + @pyqtProperty(QEInvoiceListModel, notify=invoiceModelChanged) + def invoiceModel(self): + return self._invoiceModel + nameChanged = pyqtSignal() @pyqtProperty('QString', notify=nameChanged) def name(self):