Browse Source

add QERequestListModel and hook up the gui in Receive tab

patch-4
Sander van Grieken 3 years ago
parent
commit
cd6d2f8a69
  1. 186
      electrum/gui/qml/components/Receive.qml
  2. 76
      electrum/gui/qml/qerequestlistmodel.py
  3. 84
      electrum/gui/qml/qewallet.py

186
electrum/gui/qml/components/Receive.qml

@ -9,24 +9,196 @@ Pane {
id: rootItem id: rootItem
visible: Daemon.currentWallet !== undefined visible: Daemon.currentWallet !== undefined
ColumnLayout { GridLayout {
id: form
width: parent.width width: parent.width
spacing: 20 rowSpacing: 10
columnSpacing: 10
columns: 3
Image { Label {
id: img text: qsTr('Message')
} }
TextField { TextField {
id: text id: message
onTextChanged: img.source = 'image://qrgen/' + text
Layout.columnSpan: 2
Layout.fillWidth: true
}
Label {
text: qsTr('Requested Amount')
wrapMode: Text.WordWrap
Layout.preferredWidth: 50 // trigger wordwrap
}
TextField {
id: amount
}
Item {
Layout.rowSpan: 3
width: img.width
height: img.height
Image {
id: img
cache: false
anchors {
top: parent.top
left: parent.left
}
source: 'image://qrgen/test'
}
}
Label {
text: qsTr('Expires after')
Layout.fillWidth: false
}
ComboBox {
id: expires
textRole: 'text'
valueRole: 'value'
model: ListModel {
id: expiresmodel
Component.onCompleted: {
// we need to fill the model like this, as ListElement can't evaluate script
expiresmodel.append({'text': qsTr('Never'), 'value': 0})
expiresmodel.append({'text': qsTr('10 minutes'), 'value': 10*60})
expiresmodel.append({'text': qsTr('1 hour'), 'value': 60*60})
expiresmodel.append({'text': qsTr('1 day'), 'value': 24*60*60})
expiresmodel.append({'text': qsTr('1 week'), 'value': 7*24*60*60})
expires.currentIndex = 0
}
}
} }
Button { Button {
text: 'generate' Layout.columnSpan: 2
text: qsTr('Create Request')
onClicked: { onClicked: {
img.source = 'image://qrgen/' + text.text var a = parseFloat(amount.text)
Daemon.currentWallet.create_invoice(a, message.text, expires.currentValue)
} }
} }
} }
Frame {
clip: true
verticalPadding: 0
horizontalPadding: 0
anchors {
top: form.bottom
topMargin: constants.paddingXLarge
left: parent.left
right: parent.right
bottom: parent.bottom
}
background: Rectangle {
color: Qt.darker(Material.background, 1.25)
}
ListView {
anchors.fill: parent
model: Daemon.currentWallet.requestModel
headerPositioning: ListView.OverlayHeader
header: Item {
z: 1
height: hitem.height
width: ListView.view.width
Rectangle {
anchors.fill: hitem
color: Qt.lighter(Material.background, 1.25)
}
RowLayout {
id: hitem
width: parent.width
Label {
text: qsTr('Receive queue')
font.pixelSize: constants.fontSizeXLarge
}
}
}
delegate: Item {
z: -1
height: item.height
width: ListView.view.width
GridLayout {
id: item
columns: 5
Image {
Layout.rowSpan: 2
Layout.preferredWidth: 32
Layout.preferredHeight: 32
source: model.type == 0 ? "../../icons/bitcoin.png" : "../../icons/lightning.png"
}
Label {
Layout.fillWidth: true
Layout.columnSpan: 2
text: model.message
font.pixelSize: constants.fontSizeLarge
}
Label {
text: qsTr('Amount: ')
font.pixelSize: constants.fontSizeSmall
}
Label {
text: model.amount
font.pixelSize: constants.fontSizeSmall
}
Label {
text: qsTr('Timestamp: ')
font.pixelSize: constants.fontSizeSmall
}
Label {
text: model.timestamp
font.pixelSize: constants.fontSizeSmall
}
Label {
text: qsTr('Status: ')
font.pixelSize: constants.fontSizeSmall
}
Label {
text: model.status
font.pixelSize: constants.fontSizeSmall
}
}
}
add: Transition {
NumberAnimation { properties: 'y'; from: -50; duration: 300 }
NumberAnimation { properties: 'opacity'; from: 0; to: 1.0; duration: 700 }
}
addDisplaced: Transition {
NumberAnimation { properties: 'y'; duration: 100 }
}
}
}
Connections {
target: Daemon.currentWallet
function onRequestCreateSuccess() {
message.text = ''
amount.text = ''
}
function onRequestCreateError(error) {
console.log(error)
var dialog = app.messageDialog.createObject(app, {'text': error})
dialog.open()
}
}
} }

76
electrum/gui/qml/qerequestlistmodel.py

@ -0,0 +1,76 @@
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=('type','timestamp','message','amount','status')
_ROLE_KEYS = range(Qt.UserRole + 1, Qt.UserRole + 1 + len(_ROLE_NAMES))
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
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 + 1)
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 = {}
key = self.wallet.get_key_for_receive_request(req) # (verified) address for onchain, rhash for LN
status = self.wallet.get_request_status(key)
item['status'] = req.get_status_str(status)
item['type'] = req.type # 0=onchain, 2=LN
timestamp = req.time
item['timestamp'] = format_time(timestamp)
item['amount'] = req.get_amount_sat()
item['message'] = req.message
#amount_str = self.parent.format_amount(amount) if amount else ""
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()

84
electrum/gui/qml/qewallet.py

@ -1,12 +1,18 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex, QByteArray from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex, QByteArray
from electrum.util import register_callback, Satoshis from typing import Optional, TYPE_CHECKING, Sequence, List, Union
from electrum.i18n import _
from electrum.util import register_callback, Satoshis, format_time
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.wallet import Wallet, Abstract_Wallet from electrum.wallet import Wallet, Abstract_Wallet
from electrum import bitcoin from electrum import bitcoin
from electrum.transaction import Transaction, tx_from_any, PartialTransaction, PartialTxOutput from electrum.transaction import Transaction, tx_from_any, PartialTransaction, PartialTxOutput
from electrum.invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, 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)
from .qerequestlistmodel import QERequestListModel
class QETransactionListModel(QAbstractListModel): class QETransactionListModel(QAbstractListModel):
def __init__(self, wallet, parent=None): def __init__(self, wallet, parent=None):
@ -130,7 +136,11 @@ class QEWallet(QObject):
self.wallet = wallet self.wallet = wallet
self._historyModel = QETransactionListModel(wallet) self._historyModel = QETransactionListModel(wallet)
self._addressModel = QEAddressListModel(wallet) self._addressModel = QEAddressListModel(wallet)
self._requestModel = QERequestListModel(wallet)
self._historyModel.init_model() self._historyModel.init_model()
self._requestModel.init_model()
register_callback(self.on_request_status, ['request_status']) register_callback(self.on_request_status, ['request_status'])
register_callback(self.on_status, ['status']) register_callback(self.on_status, ['status'])
@ -138,6 +148,9 @@ class QEWallet(QObject):
dataChanged = pyqtSignal() # dummy to silence warnings dataChanged = pyqtSignal() # dummy to silence warnings
requestCreateSuccess = pyqtSignal()
requestCreateError = pyqtSignal([str], arguments=['error'])
requestStatus = pyqtSignal() requestStatus = pyqtSignal()
def on_request_status(self, event, *args): def on_request_status(self, event, *args):
self._logger.debug(str(event)) self._logger.debug(str(event))
@ -153,6 +166,11 @@ class QEWallet(QObject):
def addressModel(self): def addressModel(self):
return self._addressModel return self._addressModel
requestModelChanged = pyqtSignal()
@pyqtProperty(QERequestListModel, notify=requestModelChanged)
def requestModel(self):
return self._requestModel
@pyqtProperty('QString', notify=dataChanged) @pyqtProperty('QString', notify=dataChanged)
def txinType(self): def txinType(self):
return self.wallet.get_txin_type(self.wallet.dummy_address()) return self.wallet.get_txin_type(self.wallet.dummy_address())
@ -218,3 +236,65 @@ class QEWallet(QObject):
outputs = [PartialTxOutput.from_address_and_value(address, amount)] outputs = [PartialTxOutput.from_address_and_value(address, amount)]
tx = self.wallet.make_unsigned_transaction(coins=coins,outputs=outputs) tx = self.wallet.make_unsigned_transaction(coins=coins,outputs=outputs)
return True return True
def create_bitcoin_request(self, amount: int, message: str, expiration: int) -> Optional[str]:
addr = self.wallet.get_unused_address()
if addr is None:
# TODO implement
return
#if not self.wallet.is_deterministic(): # imported wallet
#msg = [
#_('No more addresses in your wallet.'), ' ',
#_('You are using a non-deterministic wallet, which cannot create new addresses.'), ' ',
#_('If you want to create new addresses, use a deterministic wallet instead.'), '\n\n',
#_('Creating a new payment request will reuse one of your addresses and overwrite an existing request. Continue anyway?'),
#]
#if not self.question(''.join(msg)):
#return
#addr = self.wallet.get_receiving_address()
#else: # deterministic wallet
#if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")):
#return
#addr = self.wallet.create_new_address(False)
req = self.wallet.make_payment_request(addr, amount, message, expiration)
try:
self.wallet.add_payment_request(req)
except Exception as e:
self.logger.exception('Error adding payment request')
self.requestCreateError.emit(_('Error adding payment request') + ':\n' + repr(e))
else:
# TODO: check this flow. Only if alias is defined in config. OpenAlias?
pass
#self.sign_payment_request(addr)
self._requestModel.add_request(req)
return addr
@pyqtSlot(int, 'QString', int)
def create_invoice(self, amount: int, message: str, expiration: int, is_lightning: bool = False):
expiry = expiration #TODO: self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
try:
if is_lightning:
if not self.wallet.lnworker.channels:
#self.show_error(_("You need to open a Lightning channel first."))
self.requestCreateError.emit(_("You need to open a Lightning channel first."))
return
# TODO maybe show a warning if amount exceeds lnworker.num_sats_can_receive (as in kivy)
key = self.wallet.lnworker.add_request(amount, message, expiry)
else:
key = self.create_bitcoin_request(amount, message, expiry)
if not key:
return
#self.address_list.update()
self._addressModel.init_model()
except InvoiceError as e:
self.requestCreateError.emit(_('Error creating payment request') + ':\n' + str(e))
return
assert key is not None
self.requestCreateSuccess.emit()
# TODO:copy to clipboard
#r = self.wallet.get_request(key)
#content = r.invoice if r.is_lightning() else r.get_address()
#title = _('Invoice') if is_lightning else _('Address')
#self.do_copy(content, title=title)

Loading…
Cancel
Save