Files
pallectrum/electrum/gui/qml/components/InvoiceDialog.qml
ThomasV d7c5c40c1d Save user-entered amount in invoice. fixes #8252.
Note that this allows users to save invoices that have an empty
amount, which is not allowed by the Qt GUI. Qt will complain at
pay time about empty amount if a lightning invoice without amount
is saved. With onchain invoices, Qt will create an onchain tx with
a zero output.
2023-03-18 17:29:56 +01:00

483 lines
18 KiB
QML

import QtQuick 2.12
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.14
import QtQuick.Controls.Material 2.0
import org.electrum 1.0
import "controls"
ElDialog {
id: dialog
property Invoice invoice
property string invoice_key
signal doPay
signal invoiceAmountChanged
title: qsTr('Invoice')
iconSource: Qt.resolvedUrl('../../icons/tab_send.png')
padding: 0
modal: true
parent: Overlay.overlay
Overlay.modal: Rectangle {
color: "#aa000000"
}
property bool _canMax: invoice.invoiceType == Invoice.OnchainInvoice
ColumnLayout {
anchors.fill: parent
spacing: 0
Flickable {
Layout.preferredWidth: parent.width
Layout.fillHeight: true
leftMargin: constants.paddingLarge
rightMargin: constants.paddingLarge
contentHeight: rootLayout.height
clip:true
interactive: height < contentHeight
GridLayout {
id: rootLayout
width: parent.width
columns: 2
InfoTextArea {
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.bottomMargin: constants.paddingLarge
visible: invoice.userinfo
text: invoice.userinfo
iconStyle: InfoTextArea.IconStyle.Warn
}
Label {
text: qsTr('Type')
color: Material.accentColor
}
RowLayout {
Layout.fillWidth: true
Image {
Layout.preferredWidth: constants.iconSizeSmall
Layout.preferredHeight: constants.iconSizeSmall
source: invoice.invoiceType == Invoice.LightningInvoice
? "../../icons/lightning.png"
: "../../icons/bitcoin.png"
}
Label {
text: invoice.invoiceType == Invoice.OnchainInvoice
? qsTr('On chain')
: invoice.invoiceType == Invoice.LightningInvoice
? invoice.address
? qsTr('Lightning with on-chain fallback address')
: qsTr('Lightning')
: ''
Layout.fillWidth: true
}
}
Label {
text: qsTr('Status')
color: Material.accentColor
}
RowLayout {
Image {
Layout.preferredWidth: constants.iconSizeSmall
Layout.preferredHeight: constants.iconSizeSmall
source: invoice.status == Invoice.Expired
? '../../icons/expired.png'
: invoice.status == Invoice.Unpaid
? '../../icons/unpaid.png'
: invoice.status == Invoice.Failed || invoice.status == Invoice.Unknown
? '../../icons/warning.png'
: invoice.status == Invoice.Inflight || invoice.status == Invoice.Routing
? '../../icons/status_waiting.png'
: invoice.status == Invoice.Unconfirmed
? '../../icons/unconfirmed.png'
: invoice.status == Invoice.Paid
? '../../icons/confirmed.png'
: ''
}
Label {
text: invoice.status_str
}
}
Label {
Layout.columnSpan: 2
Layout.topMargin: constants.paddingSmall
visible: invoice.invoiceType == Invoice.OnchainInvoice
text: qsTr('Address')
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: invoice.invoiceType == Invoice.OnchainInvoice
leftPadding: constants.paddingMedium
Label {
width: parent.width
text: invoice.address
font.family: FixedFont
wrapMode: Text.Wrap
}
}
Label {
Layout.columnSpan: 2
Layout.topMargin: constants.paddingSmall
text: qsTr('Description')
visible: invoice.message
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: invoice.message
leftPadding: constants.paddingMedium
Label {
text: invoice.message
width: parent.width
font.pixelSize: constants.fontSizeXLarge
wrapMode: Text.Wrap
elide: Text.ElideRight
}
}
Label {
Layout.columnSpan: 2
Layout.topMargin: constants.paddingSmall
text: qsTr('Amount to send')
color: Material.accentColor
}
TextHighlightPane {
id: amountContainer
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
leftPadding: constants.paddingXLarge
property bool editmode: false
RowLayout {
id: amountLayout
width: parent.width
GridLayout {
visible: !amountContainer.editmode
columns: 2
Label {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: invoice.amount.isMax
font.pixelSize: constants.fontSizeXLarge
font.bold: true
text: qsTr('All on-chain funds')
}
Label {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: invoice.amount.isEmpty
font.pixelSize: constants.fontSizeXLarge
color: constants.mutedForeground
text: qsTr('not specified')
}
Label {
Layout.alignment: Qt.AlignRight
visible: !invoice.amount.isMax && !invoice.amount.isEmpty
font.pixelSize: constants.fontSizeXLarge
font.family: FixedFont
font.bold: true
text: Config.formatSats(invoice.amount, false)
}
Label {
Layout.fillWidth: true
visible: !invoice.amount.isMax && !invoice.amount.isEmpty
text: Config.baseUnit
color: Material.accentColor
font.pixelSize: constants.fontSizeXLarge
}
Label {
id: fiatValue
Layout.alignment: Qt.AlignRight
visible: Daemon.fx.enabled && !invoice.amount.isMax && !invoice.amount.isEmpty
text: Daemon.fx.fiatValue(invoice.amount, false)
font.pixelSize: constants.fontSizeMedium
color: constants.mutedForeground
}
Label {
Layout.fillWidth: true
visible: Daemon.fx.enabled && !invoice.amount.isMax && !invoice.amount.isEmpty
text: Daemon.fx.fiatCurrency
font.pixelSize: constants.fontSizeMedium
color: constants.mutedForeground
}
}
GridLayout {
Layout.fillWidth: true
visible: amountContainer.editmode
enabled: !(invoice.status == Invoice.Expired && invoice.amount.isEmpty)
columns: 3
BtcField {
id: amountBtc
fiatfield: amountFiat
enabled: !amountMax.checked
onTextAsSatsChanged: {
invoice.amountOverride = textAsSats
}
}
Label {
Layout.fillWidth: amountMax.visible ? false : true
Layout.columnSpan: amountMax.visible ? 1 : 2
text: Config.baseUnit
color: Material.accentColor
}
Switch {
id: amountMax
Layout.fillWidth: true
text: qsTr('Max')
visible: _canMax
checked: false
onCheckedChanged: {
if (activeFocus)
invoice.amountOverride.isMax = checked
}
}
FiatField {
id: amountFiat
btcfield: amountBtc
visible: Daemon.fx.enabled && !amountMax.checked
enabled: !amountMax.checked
}
Label {
Layout.columnSpan: 2
visible: Daemon.fx.enabled && !amountMax.checked
text: Daemon.fx.fiatCurrency
color: Material.accentColor
}
}
}
}
Heading {
Layout.columnSpan: 2
visible: invoice.invoiceType == Invoice.LightningInvoice
text: qsTr('Technical properties')
}
Label {
Layout.columnSpan: 2
Layout.topMargin: constants.paddingSmall
visible: invoice.invoiceType == Invoice.LightningInvoice
text: qsTr('Remote Pubkey')
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: invoice.invoiceType == Invoice.LightningInvoice
leftPadding: constants.paddingMedium
RowLayout {
width: parent.width
Label {
id: pubkeyLabel
Layout.fillWidth: true
text: 'pubkey' in invoice.lnprops ? invoice.lnprops.pubkey : ''
font.family: FixedFont
wrapMode: Text.Wrap
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: 'transparent'
enabled: pubkeyLabel.text
onClicked: {
var dialog = app.genericShareDialog.createObject(app,
{ title: qsTr('Node public key'), text: invoice.lnprops.pubkey }
)
dialog.open()
}
}
}
}
Label {
Layout.columnSpan: 2
Layout.topMargin: constants.paddingSmall
visible: invoice.invoiceType == Invoice.LightningInvoice
text: qsTr('Payment hash')
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: invoice.invoiceType == Invoice.LightningInvoice
leftPadding: constants.paddingMedium
RowLayout {
width: parent.width
Label {
id: paymenthashLabel
Layout.fillWidth: true
text: 'payment_hash' in invoice.lnprops ? invoice.lnprops.payment_hash : ''
font.family: FixedFont
wrapMode: Text.Wrap
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: 'transparent'
enabled: paymenthashLabel.text
onClicked: {
var dialog = app.genericShareDialog.createObject(app, {
title: qsTr('Payment hash'),
text: invoice.lnprops.payment_hash
})
dialog.open()
}
}
}
}
Label {
Layout.columnSpan: 2
Layout.topMargin: constants.paddingSmall
visible: 'r' in invoice.lnprops && invoice.lnprops.r.length
text: qsTr('Routing hints')
color: Material.accentColor
}
Repeater {
visible: 'r' in invoice.lnprops && invoice.lnprops.r.length
model: invoice.lnprops.r
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
RowLayout {
width: parent.width
Label {
text: modelData.scid
}
Label {
Layout.fillWidth: true
text: modelData.node
wrapMode: Text.Wrap
}
}
}
}
}
}
ButtonContainer {
Layout.fillWidth: true
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Delete')
icon.source: '../../icons/delete.png'
visible: invoice_key != ''
onClicked: {
invoice.wallet.delete_invoice(invoice_key)
dialog.close()
}
}
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Save')
icon.source: '../../icons/save.png'
visible: invoice_key == ''
enabled: invoice.canSave
onClicked: {
app.stack.push(Qt.resolvedUrl('Invoices.qml'))
if (invoice.amount.isEmpty) {
invoice.amount = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text)
}
invoice.save_invoice()
dialog.close()
}
}
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Pay')
icon.source: '../../icons/confirmed.png'
enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay
onClicked: {
if (invoice.amount.isEmpty) {
invoice.amount = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text)
if (invoice_key != '') {
// delete the existing invoice because this affects get_id()
invoice.wallet.delete_invoice(invoice_key)
invoice_key = ''
}
}
if (invoice_key == '') {
// save invoice if new or modified
invoice.save_invoice()
}
dialog.close()
doPay() // only signal here
}
}
}
}
Component.onCompleted: {
if (invoice_key != '') {
invoice.initFromKey(invoice_key)
}
if (invoice.amount.isEmpty && !invoice.status == Invoice.Expired) {
amountContainer.editmode = true
} else if (invoice.amount.isMax) {
amountMax.checked = true
}
}
}