add listmodel for send queue/invoices
generalize request and invoice list models into abstract base
This commit is contained in:
@@ -2,6 +2,7 @@ import QtQuick 2.6
|
|||||||
import QtQuick.Controls 2.0
|
import QtQuick.Controls 2.0
|
||||||
import QtQuick.Layouts 1.0
|
import QtQuick.Layouts 1.0
|
||||||
import QtQuick.Controls.Material 2.0
|
import QtQuick.Controls.Material 2.0
|
||||||
|
import QtQml.Models 2.1
|
||||||
|
|
||||||
import "controls"
|
import "controls"
|
||||||
|
|
||||||
@@ -200,15 +201,154 @@ Pane {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
clip: 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 {
|
Connections {
|
||||||
target: Daemon.fx
|
target: Daemon.fx
|
||||||
function onQuotesUpdated() {
|
function onQuotesUpdated() {
|
||||||
|
|||||||
148
electrum/gui/qml/qeinvoicelistmodel.py
Normal file
148
electrum/gui/qml/qeinvoicelistmodel.py
Normal file
@@ -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)
|
||||||
@@ -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
|
|
||||||
@@ -13,7 +13,7 @@ from electrum.transaction import PartialTxOutput
|
|||||||
from electrum.invoices import (Invoice, InvoiceError, PR_TYPE_ONCHAIN, PR_TYPE_LN,
|
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)
|
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 .qetransactionlistmodel import QETransactionListModel
|
||||||
from .qeaddresslistmodel import QEAddressListModel
|
from .qeaddresslistmodel import QEAddressListModel
|
||||||
|
|
||||||
@@ -54,9 +54,11 @@ class QEWallet(QObject):
|
|||||||
self._historyModel = QETransactionListModel(wallet)
|
self._historyModel = QETransactionListModel(wallet)
|
||||||
self._addressModel = QEAddressListModel(wallet)
|
self._addressModel = QEAddressListModel(wallet)
|
||||||
self._requestModel = QERequestListModel(wallet)
|
self._requestModel = QERequestListModel(wallet)
|
||||||
|
self._invoiceModel = QEInvoiceListModel(wallet)
|
||||||
|
|
||||||
self._historyModel.init_model()
|
self._historyModel.init_model()
|
||||||
self._requestModel.init_model()
|
self._requestModel.init_model()
|
||||||
|
self._invoiceModel.init_model()
|
||||||
|
|
||||||
self.tx_notification_queue = queue.Queue()
|
self.tx_notification_queue = queue.Queue()
|
||||||
self.tx_notification_last_time = 0
|
self.tx_notification_last_time = 0
|
||||||
@@ -175,6 +177,11 @@ class QEWallet(QObject):
|
|||||||
def requestModel(self):
|
def requestModel(self):
|
||||||
return self._requestModel
|
return self._requestModel
|
||||||
|
|
||||||
|
invoiceModelChanged = pyqtSignal()
|
||||||
|
@pyqtProperty(QEInvoiceListModel, notify=invoiceModelChanged)
|
||||||
|
def invoiceModel(self):
|
||||||
|
return self._invoiceModel
|
||||||
|
|
||||||
nameChanged = pyqtSignal()
|
nameChanged = pyqtSignal()
|
||||||
@pyqtProperty('QString', notify=nameChanged)
|
@pyqtProperty('QString', notify=nameChanged)
|
||||||
def name(self):
|
def name(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user