wip
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
Normal file
261
electrum/gui/qml/components/LightningPaymentDetails.qml
Normal file
@@ -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
Normal file
127
electrum/gui/qml/components/LightningPaymentProgressDialog.qml
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
Normal file
114
electrum/gui/qml/qelnpaymentdetails.py
Normal file
@@ -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()
|
||||
@@ -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']]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user