Browse Source

wip

patch-4
Sander van Grieken 3 years ago
parent
commit
8f8a1fc8cf
  1. BIN
      electrum/gui/icons/save.png
  2. 27
      electrum/gui/qml/components/History.qml
  3. 7
      electrum/gui/qml/components/InvoiceDialog.qml
  4. 261
      electrum/gui/qml/components/LightningPaymentDetails.qml
  5. 127
      electrum/gui/qml/components/LightningPaymentProgressDialog.qml
  6. 20
      electrum/gui/qml/components/Send.qml
  7. 69
      electrum/gui/qml/components/TxDetails.qml
  8. 2
      electrum/gui/qml/qeapp.py
  9. 19
      electrum/gui/qml/qeconfig.py
  10. 21
      electrum/gui/qml/qeinvoice.py
  11. 2
      electrum/gui/qml/qeinvoicelistmodel.py
  12. 114
      electrum/gui/qml/qelnpaymentdetails.py
  13. 35
      electrum/gui/qml/qetransactionlistmodel.py
  14. 2
      electrum/gui/qml/qetxdetails.py
  15. 44
      electrum/gui/qml/qewallet.py

BIN
electrum/gui/icons/save.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

27
electrum/gui/qml/components/History.qml

@ -67,11 +67,19 @@ Pane {
Layout.preferredHeight: txinfo.height
onClicked: {
var page = app.stack.push(Qt.resolvedUrl('TxDetails.qml'), {'txid': model.txid})
page.txDetailsChanged.connect(function() {
// update listmodel when details change
visualModel.model.update_tx_label(model.txid, page.label)
})
if (model.lightning) {
var page = app.stack.push(Qt.resolvedUrl('LightningPaymentDetails.qml'), {'key': model.key})
page.detailsChanged.connect(function() {
// update listmodel when details change
visualModel.model.update_tx_label(model.key, page.label)
})
} else {
var page = app.stack.push(Qt.resolvedUrl('TxDetails.qml'), {'txid': model.key})
page.detailsChanged.connect(function() {
// update listmodel when details change
visualModel.model.update_tx_label(model.key, page.label)
})
}
}
GridLayout {
@ -82,6 +90,7 @@ Pane {
width: delegate.width - 2*constants.paddingSmall
Item { Layout.columnSpan: 3; Layout.preferredWidth: 1; Layout.preferredHeight: 1}
Image {
readonly property variant tx_icons : [
"../../../gui/icons/unconfirmed.png",
@ -97,7 +106,7 @@ Pane {
Layout.preferredHeight: constants.iconSizeLarge
Layout.alignment: Qt.AlignVCenter
Layout.rowSpan: 2
source: tx_icons[Math.min(6,model.confirmations)]
source: model.lightning ? "../../../gui/icons/lightning.png" : tx_icons[Math.min(6,model.confirmations)]
}
Label {
@ -118,7 +127,7 @@ Pane {
color: model.incoming ? constants.colorCredit : constants.colorDebit
function updateText() {
text = Config.formatSats(model.bc_value)
text = Config.formatSats(model.value)
}
Component.onCompleted: updateText()
}
@ -137,9 +146,9 @@ Pane {
if (!Daemon.fx.enabled) {
text = ''
} else if (Daemon.fx.historicRates) {
text = Daemon.fx.fiatValueHistoric(model.bc_value, model.timestamp) + ' ' + Daemon.fx.fiatCurrency
text = Daemon.fx.fiatValueHistoric(model.value, model.timestamp) + ' ' + Daemon.fx.fiatCurrency
} else {
text = Daemon.fx.fiatValue(model.bc_value, false) + ' ' + Daemon.fx.fiatCurrency
text = Daemon.fx.fiatValue(model.value, false) + ' ' + Daemon.fx.fiatCurrency
}
}
Component.onCompleted: updateText()

7
electrum/gui/qml/components/InvoiceDialog.qml

@ -126,7 +126,7 @@ Dialog {
text: qsTr('Save')
icon.source: '../../icons/save.png'
visible: invoice_key == ''
enabled: invoice.invoiceType == Invoice.OnchainInvoice
enabled: invoice.canSave
onClicked: {
invoice.save_invoice()
dialog.close()
@ -138,10 +138,13 @@ Dialog {
icon.source: '../../icons/confirmed.png'
enabled: invoice.invoiceType != Invoice.Invalid // TODO && has funds
onClicked: {
invoice.save_invoice()
if (invoice_key == '')
invoice.save_invoice()
dialog.close()
if (invoice.invoiceType == Invoice.OnchainInvoice) {
doPay() // only signal here
} else if (invoice.invoiceType == Invoice.LightningInvoice) {
doPay() // only signal here
}
}
}

261
electrum/gui/qml/components/LightningPaymentDetails.qml

@ -0,0 +1,261 @@
import QtQuick 2.6
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.3
import QtQuick.Controls.Material 2.0
import org.electrum 1.0
import "controls"
Pane {
id: root
width: parent.width
height: parent.height
property string title: qsTr("Lightning payment details")
property string key
property alias label: lnpaymentdetails.label
signal detailsChanged
Flickable {
anchors.fill: parent
contentHeight: rootLayout.height
clip: true
interactive: height < contentHeight
GridLayout {
id: rootLayout
width: parent.width
columns: 2
Label {
text: qsTr('Status')
color: Material.accentColor
}
Label {
text: lnpaymentdetails.status
}
Label {
text: qsTr('Date')
color: Material.accentColor
}
Label {
text: lnpaymentdetails.date
}
Label {
text: lnpaymentdetails.amount.msatsInt > 0
? qsTr('Amount received')
: qsTr('Amount sent')
color: Material.accentColor
}
RowLayout {
Label {
text: Config.formatMilliSats(lnpaymentdetails.amount)
}
Label {
text: Config.baseUnit
color: Material.accentColor
}
}
Label {
visible: lnpaymentdetails.amount.msatsInt < 0
text: qsTr('Transaction fee')
color: Material.accentColor
}
RowLayout {
visible: lnpaymentdetails.amount.msatsInt < 0
Label {
text: Config.formatMilliSats(lnpaymentdetails.fee)
}
Label {
text: Config.baseUnit
color: Material.accentColor
}
}
Label {
text: qsTr('Label')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
id: labelContent
property bool editmode: false
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
visible: !labelContent.editmode
text: lnpaymentdetails.label
wrapMode: Text.Wrap
Layout.fillWidth: true
font.pixelSize: constants.fontSizeLarge
}
ToolButton {
visible: !labelContent.editmode
icon.source: '../../icons/pen.png'
icon.color: 'transparent'
onClicked: {
labelEdit.text = lnpaymentdetails.label
labelContent.editmode = true
labelEdit.focus = true
}
}
TextField {
id: labelEdit
visible: labelContent.editmode
text: lnpaymentdetails.label
font.pixelSize: constants.fontSizeLarge
Layout.fillWidth: true
}
ToolButton {
visible: labelContent.editmode
icon.source: '../../icons/confirmed.png'
icon.color: 'transparent'
onClicked: {
labelContent.editmode = false
lnpaymentdetails.set_label(labelEdit.text)
}
}
ToolButton {
visible: labelContent.editmode
icon.source: '../../icons/delete.png'
icon.color: 'transparent'
onClicked: labelContent.editmode = false
}
}
}
Label {
text: qsTr('Payment hash')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
text: lnpaymentdetails.payment_hash
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
Layout.fillWidth: true
wrapMode: Text.Wrap
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: 'transparent'
onClicked: {
var dialog = share.createObject(root, { 'title': qsTr('Payment hash'), 'text': lnpaymentdetails.payment_hash })
dialog.open()
}
}
}
}
Label {
text: qsTr('Preimage')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
text: lnpaymentdetails.preimage
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
Layout.fillWidth: true
wrapMode: Text.Wrap
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: 'transparent'
onClicked: {
var dialog = share.createObject(root, { 'title': qsTr('Preimage'), 'text': lnpaymentdetails.preimage })
dialog.open()
}
}
}
}
Label {
text: qsTr('Lightning invoice')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
Layout.fillWidth: true
text: lnpaymentdetails.invoice
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
wrapMode: Text.Wrap
maximumLineCount: 3
elide: Text.ElideRight
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: enabled ? 'transparent' : constants.mutedForeground
enabled: lnpaymentdetails.invoice != ''
onClicked: {
var dialog = share.createObject(root, { 'title': qsTr('Lightning Invoice'), 'text': lnpaymentdetails.invoice })
dialog.open()
}
}
}
}
}
}
LnPaymentDetails {
id: lnpaymentdetails
wallet: Daemon.currentWallet
key: root.key
onLabelChanged: root.detailsChanged()
}
Component {
id: share
GenericShareDialog {}
}
}

127
electrum/gui/qml/components/LightningPaymentProgressDialog.qml

@ -0,0 +1,127 @@
import QtQuick 2.6
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.14
import QtQuick.Controls.Material 2.0
import org.electrum 1.0
import "controls"
Dialog {
id: dialog
required property string invoice_key
width: parent.width
height: parent.height
title: qsTr('Paying Lightning Invoice...')
standardButtons: Dialog.Cancel
modal: true
parent: Overlay.overlay
Overlay.modal: Rectangle {
color: "#aa000000"
}
Item {
id: s
state: ''
states: [
State {
name: ''
},
State {
name: 'success'
PropertyChanges { target: spinner; running: false }
PropertyChanges { target: helpText; text: qsTr('Paid!') }
PropertyChanges { target: dialog; standardButtons: Dialog.Ok }
PropertyChanges { target: icon; source: '../../icons/confirmed.png' }
},
State {
name: 'failed'
PropertyChanges { target: spinner; running: false }
PropertyChanges { target: helpText; text: qsTr('Payment failed') }
PropertyChanges { target: dialog; standardButtons: Dialog.Ok }
PropertyChanges { target: errorText; visible: true }
PropertyChanges { target: icon; source: '../../icons/warning.png' }
}
]
transitions: [
Transition {
from: ''
to: 'success'
PropertyAnimation { target: helpText; properties: 'text'; duration: 0}
NumberAnimation { target: icon; properties: 'opacity'; from: 0; to: 1; duration: 200 }
NumberAnimation { target: icon; properties: 'scale'; from: 0; to: 1; duration: 500
easing.type: Easing.OutBack
easing.overshoot: 10
}
},
Transition {
from: ''
to: 'failed'
PropertyAnimation { target: helpText; properties: 'text'; duration: 0}
NumberAnimation { target: icon; properties: 'opacity'; from: 0; to: 1; duration: 500 }
}
]
}
ColumnLayout {
id: content
anchors.centerIn: parent
Item {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: constants.iconSizeXXLarge
Layout.preferredHeight: constants.iconSizeXXLarge
BusyIndicator {
id: spinner
visible: s.state == ''
width: constants.iconSizeXXLarge
height: constants.iconSizeXXLarge
}
Image {
id: icon
width: constants.iconSizeXXLarge
height: constants.iconSizeXXLarge
}
}
Label {
id: helpText
text: qsTr('Paying...')
font.pixelSize: constants.fontSizeXXLarge
Layout.alignment: Qt.AlignHCenter
}
Label {
id: errorText
font.pixelSize: constants.fontSizeLarge
Layout.alignment: Qt.AlignHCenter
}
}
Connections {
target: Daemon.currentWallet
function onPaymentSucceeded(key) {
if (key != invoice_key) {
console.log('wrong invoice ' + key + ' != ' + invoice_key)
return
}
console.log('payment succeeded!')
s.state = 'success'
}
function onPaymentFailed(key, reason) {
if (key != invoice_key) {
console.log('wrong invoice ' + key + ' != ' + invoice_key)
return
}
console.log('payment failed: ' + reason)
s.state = 'failed'
errorText.text = reason
}
}
}

20
electrum/gui/qml/components/Send.qml

@ -161,6 +161,7 @@ Pane {
rootItem.clear()
}
}
}
}
@ -244,6 +245,11 @@ Pane {
}
}
Component {
id: lightningPaymentProgressDialog
LightningPaymentProgressDialog {}
}
Component {
id: invoiceDialog
InvoiceDialog {
@ -255,6 +261,17 @@ Pane {
'message': invoice.message
})
dialog.open()
} else if (invoice.invoiceType == Invoice.LightningInvoice) {
console.log('About to pay lightning invoice')
if (invoice.key == '') {
console.log('No invoice key, aborting')
return
}
var dialog = lightningPaymentProgressDialog.createObject(rootItem, {
invoice_key: invoice.key
})
dialog.open()
Daemon.currentWallet.pay_lightning_invoice(invoice.key)
}
}
}
@ -263,8 +280,7 @@ Pane {
Connections {
target: Daemon.currentWallet
function onInvoiceStatusChanged(key, status) {
// TODO: status from?
//Daemon.currentWallet.invoiceModel.updateInvoice(key, status)
Daemon.currentWallet.invoiceModel.updateInvoice(key, status)
}
}

69
electrum/gui/qml/components/TxDetails.qml

@ -18,7 +18,7 @@ Pane {
property alias label: txdetails.label
signal txDetailsChanged
signal detailsChanged
property QtObject menu: Menu {
id: menu
@ -97,11 +97,13 @@ Pane {
}
Label {
visible: txdetails.amount.satsInt < 0
text: qsTr('Transaction fee')
color: Material.accentColor
}
RowLayout {
visible: txdetails.amount.satsInt < 0
Label {
text: Config.formatSats(txdetails.fee)
}
@ -111,38 +113,6 @@ Pane {
}
}
Label {
text: qsTr('Transaction ID')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
text: root.txid
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
Layout.fillWidth: true
wrapMode: Text.Wrap
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: 'transparent'
onClicked: {
var dialog = share.createObject(root, { 'title': qsTr('Transaction ID'), 'text': root.txid })
dialog.open()
}
}
}
}
Label {
text: qsTr('Label')
Layout.columnSpan: 2
@ -203,6 +173,37 @@ Pane {
}
}
Label {
text: qsTr('Transaction ID')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
text: root.txid
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
Layout.fillWidth: true
wrapMode: Text.Wrap
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: 'transparent'
onClicked: {
var dialog = share.createObject(root, { 'title': qsTr('Transaction ID'), 'text': root.txid })
dialog.open()
}
}
}
}
Label {
text: qsTr('Outputs')
@ -247,7 +248,7 @@ Pane {
id: txdetails
wallet: Daemon.currentWallet
txid: root.txid
onLabelChanged: txDetailsChanged()
onLabelChanged: root.detailsChanged()
}
Component {

2
electrum/gui/qml/qeapp.py

@ -24,6 +24,7 @@ from .qetypes import QEAmount
from .qeaddressdetails import QEAddressDetails
from .qetxdetails import QETxDetails
from .qechannelopener import QEChannelOpener
from .qelnpaymentdetails import QELnPaymentDetails
notification = None
@ -145,6 +146,7 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails')
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')

19
electrum/gui/qml/qeconfig.py

@ -3,7 +3,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from decimal import Decimal
from electrum.logging import get_logger
from electrum.util import DECIMAL_POINT_DEFAULT
from electrum.util import DECIMAL_POINT_DEFAULT, format_satoshis
from .qetypes import QEAmount
@ -92,6 +92,23 @@ class QEConfig(QObject):
else:
return self.config.format_amount(satoshis)
@pyqtSlot(QEAmount, result=str)
@pyqtSlot(QEAmount, bool, result=str)
def formatMilliSats(self, amount, with_unit=False):
if isinstance(amount, QEAmount):
msats = amount.msatsInt
else:
return '---'
s = format_satoshis(msats/1000,
decimal_point=self.decimal_point(),
precision=3)
return s
#if with_unit:
#return self.config.format_amount_and_units(msats)
#else:
#return self.config.format_amount(satoshis)
# TODO delegate all this to config.py/util.py
def decimal_point(self):
return self.config.get('decimal_point', DECIMAL_POINT_DEFAULT)

21
electrum/gui/qml/qeinvoice.py

@ -46,6 +46,7 @@ class QEInvoice(QObject):
_effectiveInvoice = None
_canSave = False
_canPay = False
_key = ''
invoiceChanged = pyqtSignal()
invoiceSaved = pyqtSignal()
@ -128,6 +129,17 @@ class QEInvoice(QObject):
status = self._wallet.wallet.get_invoice_status(self._effectiveInvoice)
return self._effectiveInvoice.get_status_str(status)
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.keyChanged.emit()
# single address only, TODO: n outputs
@pyqtProperty(str, notify=invoiceChanged)
def address(self):
@ -170,6 +182,7 @@ class QEInvoice(QObject):
self._logger.debug(repr(invoice))
if invoice:
self.set_effective_invoice(invoice)
self.key = key
def set_effective_invoice(self, invoice: Invoice):
self._effectiveInvoice = invoice
@ -264,9 +277,12 @@ class QEInvoice(QObject):
else:
self._logger.debug('flow with LN but not LN enabled AND having bip21 uri')
self.setValidOnchainInvoice(self._bip21['address'])
elif not self._wallet.wallet.lnworker.channels:
self.validationWarning.emit('no_channels',_('Detected valid Lightning invoice, but there are no open channels'))
else:
self.setValidLightningInvoice(lninvoice)
if not self._wallet.wallet.lnworker.channels:
self.validationWarning.emit('no_channels',_('Detected valid Lightning invoice, but there are no open channels'))
else:
self.validationSuccess.emit()
else:
self._logger.debug('flow without LN but having bip21 uri')
if 'amount' not in self._bip21: #TODO can we have amount-less invoices?
@ -286,6 +302,7 @@ class QEInvoice(QObject):
if not self._effectiveInvoice:
return
# TODO detect duplicate?
self.key = self._wallet.wallet.get_key_for_outgoing_invoice(self._effectiveInvoice)
self._wallet.wallet.save_invoice(self._effectiveInvoice)
self.invoiceSaved.emit()

2
electrum/gui/qml/qeinvoicelistmodel.py

@ -51,7 +51,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
invoices = []
for invoice in self.get_invoice_list():
item = self.invoice_to_model(invoice)
self._logger.debug(str(item))
#self._logger.debug(str(item))
invoices.append(item)
self.clear()

114
electrum/gui/qml/qelnpaymentdetails.py

@ -0,0 +1,114 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from electrum.logging import get_logger
from electrum.util import format_time, bfh, format_time
from .qewallet import QEWallet
from .qetypes import QEAmount
class QELnPaymentDetails(QObject):
def __init__(self, parent=None):
super().__init__(parent)
_logger = get_logger(__name__)
_wallet = None
_key = None
_date = None
detailsChanged = pyqtSignal()
walletChanged = pyqtSignal()
@pyqtProperty(QEWallet, notify=walletChanged)
def wallet(self):
return self._wallet
@wallet.setter
def wallet(self, wallet: QEWallet):
if self._wallet != wallet:
self._wallet = wallet
self.walletChanged.emit()
keyChanged = pyqtSignal()
@pyqtProperty(str, notify=keyChanged)
def key(self):
return self._key
@key.setter
def key(self, key: str):
if self._key != key:
self._logger.debug('key set -> %s' % key)
self._key = key
self.keyChanged.emit()
self.update()
labelChanged = pyqtSignal()
@pyqtProperty(str, notify=labelChanged)
def label(self):
return self._label
@pyqtSlot(str)
def set_label(self, label: str):
if label != self._label:
self._wallet.wallet.set_label(self._key, label)
self._label = label
self.labelChanged.emit()
@pyqtProperty(str, notify=detailsChanged)
def status(self):
return self._status
@pyqtProperty(str, notify=detailsChanged)
def date(self):
return self._date
@pyqtProperty(str, notify=detailsChanged)
def payment_hash(self):
return self._phash
@pyqtProperty(str, notify=detailsChanged)
def preimage(self):
return self._preimage
@pyqtProperty(str, notify=detailsChanged)
def invoice(self):
return self._invoice
@pyqtProperty(QEAmount, notify=detailsChanged)
def amount(self):
return self._amount
@pyqtProperty(QEAmount, notify=detailsChanged)
def fee(self):
return self._fee
def update(self):
if self._wallet is None:
self._logger.error('wallet undefined')
return
if self._key not in self._wallet.wallet.lnworker.payments:
self._logger.error('payment_hash not found')
return
# TODO this is horribly inefficient. need a payment getter/query method
tx = self._wallet.wallet.lnworker.get_lightning_history()[bfh(self._key)]
self._logger.debug(str(tx))
self._fee = QEAmount() if not tx['fee_msat'] else QEAmount(amount_msat=tx['fee_msat'])
self._amount = QEAmount(amount_msat=tx['amount_msat'])
self._label = tx['label']
self._date = format_time(tx['timestamp'])
self._status = 'settled' # TODO: other states? get_lightning_history is deciding the filter for us :(
self._phash = tx['payment_hash']
self._preimage = tx['preimage']
invoice = (self._wallet.wallet.get_invoice(self._key)
or self._wallet.wallet.get_request(self._key))
self._logger.debug(str(invoice))
if invoice:
self._invoice = invoice.lightning_invoice or ''
else:
self._invoice = ''
self.detailsChanged.emit()

35
electrum/gui/qml/qetransactionlistmodel.py

@ -18,8 +18,8 @@ class QETransactionListModel(QAbstractListModel):
# define listmodel rolemap
_ROLE_NAMES=('txid','fee_sat','height','confirmations','timestamp','monotonic_timestamp',
'incoming','bc_value','bc_balance','date','label','txpos_in_block','fee',
'inputs','outputs','section')
'incoming','value','balance','date','label','txpos_in_block','fee',
'inputs','outputs','section','type','lightning','payment_hash','key')
_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))
@ -46,12 +46,23 @@ class QETransactionListModel(QAbstractListModel):
self.endResetModel()
def tx_to_model(self, tx):
#self._logger.debug(str(tx))
item = tx
for output in item['outputs']:
output['value'] = output['value'].value
item['bc_value'] = QEAmount(amount_sat=item['bc_value'].value)
item['bc_balance'] = QEAmount(amount_sat=item['bc_balance'].value)
item['key'] = item['txid'] if 'txid' in item else item['payment_hash']
if not 'lightning' in item:
item['lightning'] = False
if item['lightning']:
item['value'] = QEAmount(amount_sat=item['value'].value, amount_msat=item['amount_msat'])
item['balance'] = QEAmount(amount_sat=item['balance'].value, amount_msat=item['amount_msat'])
if item['type'] == 'payment':
item['incoming'] = True if item['direction'] == 'received' else False
item['confirmations'] = 0
else:
item['value'] = QEAmount(amount_sat=item['value'].value)
item['balance'] = QEAmount(amount_sat=item['balance'].value)
# newly arriving txs have no (block) timestamp
# TODO?
@ -90,9 +101,9 @@ class QETransactionListModel(QAbstractListModel):
# initial model data
def init_model(self):
history = self.wallet.get_detailed_history(show_addresses = True)
history = self.wallet.get_full_history()
txs = []
for tx in history['transactions']:
for key, tx in history.items():
txs.append(self.tx_to_model(tx))
self.clear()
@ -104,7 +115,7 @@ class QETransactionListModel(QAbstractListModel):
def update_tx(self, txid, info):
i = 0
for tx in self.tx_history:
if tx['txid'] == txid:
if 'txid' in tx and tx['txid'] == txid:
tx['height'] = info.height
tx['confirmations'] = info.conf
tx['timestamp'] = info.timestamp
@ -116,10 +127,10 @@ class QETransactionListModel(QAbstractListModel):
i = i + 1
@pyqtSlot(str, str)
def update_tx_label(self, txid, label):
def update_tx_label(self, key, label):
i = 0
for tx in self.tx_history:
if tx['txid'] == txid:
if tx['key'] == key:
tx['label'] = label
index = self.index(i,0)
self.dataChanged.emit(index, index, [self._ROLE_RMAP['label']])
@ -131,7 +142,7 @@ class QETransactionListModel(QAbstractListModel):
self._logger.debug('updating height to %d' % height)
i = 0
for tx in self.tx_history:
if tx['height'] > 0:
if 'height' in tx and tx['height'] > 0:
tx['confirmations'] = height - tx['height'] + 1
index = self.index(i,0)
roles = [self._ROLE_RMAP['confirmations']]

2
electrum/gui/qml/qetxdetails.py

@ -1,7 +1,5 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
#from decimal import Decimal
from electrum.logging import get_logger
from electrum.util import format_time

44
electrum/gui/qml/qewallet.py

@ -1,6 +1,8 @@
from typing import Optional, TYPE_CHECKING, Sequence, List, Union
import queue
import time
import asyncio
import threading
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QTimer
@ -47,9 +49,11 @@ class QEWallet(QObject):
requestStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
requestCreateSuccess = pyqtSignal()
requestCreateError = pyqtSignal([str,str], arguments=['code','error'])
invoiceStatusChanged = pyqtSignal([str], arguments=['key'])
invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
invoiceCreateSuccess = pyqtSignal()
invoiceCreateError = pyqtSignal([str,str], arguments=['code','error'])
paymentSucceeded = pyqtSignal([str], arguments=['key'])
paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
_network_signal = pyqtSignal(str, object)
@ -95,6 +99,10 @@ class QEWallet(QObject):
def on_network_qt(self, event, args=None):
# note: we get events from all wallets! args are heterogenous so we can't
# shortcut here
if event != 'status':
wallet = args[0]
if wallet == self.wallet:
self._logger.debug('event %s' % event)
if event == 'status':
self.isUptodateChanged.emit()
elif event == 'request_status':
@ -105,8 +113,11 @@ class QEWallet(QObject):
elif event == 'invoice_status':
wallet, key = args
if wallet == self.wallet:
self._logger.debug('invoice status %d for key %s' % (c, key))
self.invoiceStatusChanged.emit(key)
self._logger.debug('invoice status update for key %s' % key)
# FIXME event doesn't pass the new status, so we need to retrieve
invoice = self.wallet.get_invoice(key)
status = self.wallet.get_invoice_status(invoice)
self.invoiceStatusChanged.emit(key, status)
elif event == 'new_transaction':
wallet, tx = args
if wallet == self.wallet:
@ -129,6 +140,15 @@ class QEWallet(QObject):
wallet, = args
if wallet == self.wallet:
self.balanceChanged.emit()
elif event == 'payment_succeeded':
wallet, key = args
if wallet == self.wallet:
self.paymentSucceeded.emit(key)
self._historyModel.init_model() # TODO: be less dramatic
elif event == 'payment_failed':
wallet, key, reason = args
if wallet == self.wallet:
self.paymentFailed.emit(key, reason)
else:
self._logger.debug('unhandled event: %s %s' % (event, str(args)))
@ -346,6 +366,24 @@ class QEWallet(QObject):
return
@pyqtSlot(str)
def pay_lightning_invoice(self, invoice_key):
self._logger.debug('about to pay LN')
invoice = self.wallet.get_invoice(invoice_key)
assert(invoice)
assert(invoice.lightning_invoice)
amount_msat = invoice.get_amount_msat()
def pay_thread():
try:
coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat)
fut = asyncio.run_coroutine_threadsafe(coro, self.wallet.network.asyncio_loop)
fut.result()
except Exception as e:
self.userNotify(repr(e))
#self.app.show_error(repr(e))
#self.save_invoice(invoice)
threading.Thread(target=pay_thread).start()
def create_bitcoin_request(self, amount: int, message: str, expiration: int, ignore_gap: bool) -> Optional[str]:
addr = self.wallet.get_unused_address()
if addr is None:

Loading…
Cancel
Save