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

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

@ -126,7 +126,7 @@ Dialog {
text: qsTr('Save') text: qsTr('Save')
icon.source: '../../icons/save.png' icon.source: '../../icons/save.png'
visible: invoice_key == '' visible: invoice_key == ''
enabled: invoice.invoiceType == Invoice.OnchainInvoice enabled: invoice.canSave
onClicked: { onClicked: {
invoice.save_invoice() invoice.save_invoice()
dialog.close() dialog.close()
@ -138,10 +138,13 @@ Dialog {
icon.source: '../../icons/confirmed.png' icon.source: '../../icons/confirmed.png'
enabled: invoice.invoiceType != Invoice.Invalid // TODO && has funds enabled: invoice.invoiceType != Invoice.Invalid // TODO && has funds
onClicked: { onClicked: {
invoice.save_invoice() if (invoice_key == '')
invoice.save_invoice()
dialog.close() dialog.close()
if (invoice.invoiceType == Invoice.OnchainInvoice) { if (invoice.invoiceType == Invoice.OnchainInvoice) {
doPay() // only signal here 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() rootItem.clear()
} }
} }
} }
} }
@ -244,6 +245,11 @@ Pane {
} }
} }
Component {
id: lightningPaymentProgressDialog
LightningPaymentProgressDialog {}
}
Component { Component {
id: invoiceDialog id: invoiceDialog
InvoiceDialog { InvoiceDialog {
@ -255,6 +261,17 @@ Pane {
'message': invoice.message 'message': invoice.message
}) })
dialog.open() 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 { Connections {
target: Daemon.currentWallet target: Daemon.currentWallet
function onInvoiceStatusChanged(key, status) { 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 property alias label: txdetails.label
signal txDetailsChanged signal detailsChanged
property QtObject menu: Menu { property QtObject menu: Menu {
id: menu id: menu
@ -97,11 +97,13 @@ Pane {
} }
Label { Label {
visible: txdetails.amount.satsInt < 0
text: qsTr('Transaction fee') text: qsTr('Transaction fee')
color: Material.accentColor color: Material.accentColor
} }
RowLayout { RowLayout {
visible: txdetails.amount.satsInt < 0
Label { Label {
text: Config.formatSats(txdetails.fee) 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 { Label {
text: qsTr('Label') text: qsTr('Label')
Layout.columnSpan: 2 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 { Label {
text: qsTr('Outputs') text: qsTr('Outputs')
@ -247,7 +248,7 @@ Pane {
id: txdetails id: txdetails
wallet: Daemon.currentWallet wallet: Daemon.currentWallet
txid: root.txid txid: root.txid
onLabelChanged: txDetailsChanged() onLabelChanged: root.detailsChanged()
} }
Component { Component {

2
electrum/gui/qml/qeapp.py

@ -24,6 +24,7 @@ from .qetypes import QEAmount
from .qeaddressdetails import QEAddressDetails from .qeaddressdetails import QEAddressDetails
from .qetxdetails import QETxDetails from .qetxdetails import QETxDetails
from .qechannelopener import QEChannelOpener from .qechannelopener import QEChannelOpener
from .qelnpaymentdetails import QELnPaymentDetails
notification = None notification = None
@ -145,6 +146,7 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails') qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails') qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener') 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') 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 decimal import Decimal
from electrum.logging import get_logger 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 from .qetypes import QEAmount
@ -92,6 +92,23 @@ class QEConfig(QObject):
else: else:
return self.config.format_amount(satoshis) 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 # TODO delegate all this to config.py/util.py
def decimal_point(self): def decimal_point(self):
return self.config.get('decimal_point', DECIMAL_POINT_DEFAULT) return self.config.get('decimal_point', DECIMAL_POINT_DEFAULT)

21
electrum/gui/qml/qeinvoice.py

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

2
electrum/gui/qml/qeinvoicelistmodel.py

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

2
electrum/gui/qml/qetxdetails.py

@ -1,7 +1,5 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
#from decimal import Decimal
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.util import format_time 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 from typing import Optional, TYPE_CHECKING, Sequence, List, Union
import queue import queue
import time import time
import asyncio
import threading
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QTimer from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QTimer
@ -47,9 +49,11 @@ class QEWallet(QObject):
requestStatusChanged = pyqtSignal([str,int], arguments=['key','status']) requestStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
requestCreateSuccess = pyqtSignal() requestCreateSuccess = pyqtSignal()
requestCreateError = pyqtSignal([str,str], arguments=['code','error']) requestCreateError = pyqtSignal([str,str], arguments=['code','error'])
invoiceStatusChanged = pyqtSignal([str], arguments=['key']) invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
invoiceCreateSuccess = pyqtSignal() invoiceCreateSuccess = pyqtSignal()
invoiceCreateError = pyqtSignal([str,str], arguments=['code','error']) invoiceCreateError = pyqtSignal([str,str], arguments=['code','error'])
paymentSucceeded = pyqtSignal([str], arguments=['key'])
paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
_network_signal = pyqtSignal(str, object) _network_signal = pyqtSignal(str, object)
@ -95,6 +99,10 @@ class QEWallet(QObject):
def on_network_qt(self, event, args=None): def on_network_qt(self, event, args=None):
# note: we get events from all wallets! args are heterogenous so we can't # note: we get events from all wallets! args are heterogenous so we can't
# shortcut here # shortcut here
if event != 'status':
wallet = args[0]
if wallet == self.wallet:
self._logger.debug('event %s' % event)
if event == 'status': if event == 'status':
self.isUptodateChanged.emit() self.isUptodateChanged.emit()
elif event == 'request_status': elif event == 'request_status':
@ -105,8 +113,11 @@ class QEWallet(QObject):
elif event == 'invoice_status': elif event == 'invoice_status':
wallet, key = args wallet, key = args
if wallet == self.wallet: if wallet == self.wallet:
self._logger.debug('invoice status %d for key %s' % (c, key)) self._logger.debug('invoice status update for key %s' % key)
self.invoiceStatusChanged.emit(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': elif event == 'new_transaction':
wallet, tx = args wallet, tx = args
if wallet == self.wallet: if wallet == self.wallet:
@ -129,6 +140,15 @@ class QEWallet(QObject):
wallet, = args wallet, = args
if wallet == self.wallet: if wallet == self.wallet:
self.balanceChanged.emit() 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: else:
self._logger.debug('unhandled event: %s %s' % (event, str(args))) self._logger.debug('unhandled event: %s %s' % (event, str(args)))
@ -346,6 +366,24 @@ class QEWallet(QObject):
return 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]: def create_bitcoin_request(self, amount: int, message: str, expiration: int, ignore_gap: bool) -> Optional[str]:
addr = self.wallet.get_unused_address() addr = self.wallet.get_unused_address()
if addr is None: if addr is None:

Loading…
Cancel
Save