Browse Source

allow zero amount invoices, add edit amount option for invoices

patch-4
Sander van Grieken 2 years ago
parent
commit
fb68931a8d
  1. 216
      electrum/gui/qml/components/InvoiceDialog.qml
  2. 6
      electrum/gui/qml/components/ReceiveDialog.qml
  3. 5
      electrum/gui/qml/components/SendDialog.qml
  4. 8
      electrum/gui/qml/components/WalletMainView.qml
  5. 6
      electrum/gui/qml/components/controls/QRScan.qml
  6. 4
      electrum/gui/qml/qeconfig.py
  7. 23
      electrum/gui/qml/qeinvoice.py
  8. 12
      electrum/gui/qml/qewallet.py

216
electrum/gui/qml/components/InvoiceDialog.qml

@ -15,9 +15,6 @@ ElDialog {
signal doPay signal doPay
width: parent.width
height: parent.height
title: qsTr('Invoice') title: qsTr('Invoice')
standardButtons: invoice_key != '' ? Dialog.Close : Dialog.Cancel standardButtons: invoice_key != '' ? Dialog.Close : Dialog.Cancel
@ -40,8 +37,151 @@ ElDialog {
color: Material.accentColor color: Material.accentColor
} }
Label {
text: qsTr('Amount to send')
color: Material.accentColor
Layout.columnSpan: 2
}
TextHighlightPane {
id: amountContainer
Layout.columnSpan: 2
Layout.preferredWidth: parent.width //* 0.75
Layout.alignment: Qt.AlignHCenter
padding: 0
leftPadding: constants.paddingXXLarge
property bool editmode: false
RowLayout {
id: amountLayout
width: parent.width
GridLayout {
visible: !amountContainer.editmode
columns: 2
Label {
font.pixelSize: constants.fontSizeXLarge
font.family: FixedFont
font.bold: true
text: Config.formatSats(invoice.amount, false)
}
Label {
Layout.fillWidth: true
text: Config.baseUnit
color: Material.accentColor
font.pixelSize: constants.fontSizeXLarge
}
Label {
id: fiatValue
visible: Daemon.fx.enabled
text: Daemon.fx.fiatValue(invoice.amount, false)
font.pixelSize: constants.fontSizeMedium
color: constants.mutedForeground
}
Label {
visible: Daemon.fx.enabled
Layout.fillWidth: true
text: Daemon.fx.fiatCurrency
font.pixelSize: constants.fontSizeMedium
color: constants.mutedForeground
}
}
ToolButton {
visible: !amountContainer.editmode
icon.source: '../../icons/pen.png'
icon.color: 'transparent'
onClicked: {
amountBtc.text = invoice.amount.satsInt == 0 ? '' : Config.formatSats(invoice.amount)
amountContainer.editmode = true
amountBtc.focus = true
}
}
GridLayout {
visible: amountContainer.editmode
Layout.fillWidth: true
columns: 2
BtcField {
id: amountBtc
fiatfield: amountFiat
}
Label {
text: Config.baseUnit
color: Material.accentColor
Layout.fillWidth: true
}
FiatField {
id: amountFiat
btcfield: amountBtc
visible: Daemon.fx.enabled
}
Label {
visible: Daemon.fx.enabled
text: Daemon.fx.fiatCurrency
color: Material.accentColor
}
}
ToolButton {
visible: amountContainer.editmode
Layout.fillWidth: false
icon.source: '../../icons/confirmed.png'
icon.color: 'transparent'
onClicked: {
amountContainer.editmode = false
invoice.amount = Config.unitsToSats(amountBtc.text)
}
}
ToolButton {
visible: amountContainer.editmode
Layout.fillWidth: false
icon.source: '../../icons/closebutton.png'
icon.color: 'transparent'
onClicked: amountContainer.editmode = false
}
}
}
Label {
text: qsTr('Description')
visible: invoice.message
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
visible: invoice.message
Layout.columnSpan: 2
Layout.preferredWidth: parent.width
Layout.alignment: Qt.AlignHCenter
padding: 0
leftPadding: constants.paddingMedium
Label {
text: invoice.message
Layout.fillWidth: true
font.pixelSize: constants.fontSizeXLarge
wrapMode: Text.Wrap
elide: Text.ElideRight
}
}
Label { Label {
text: qsTr('Type') text: qsTr('Type')
color: Material.accentColor
} }
RowLayout { RowLayout {
@ -64,48 +204,10 @@ ElDialog {
} }
} }
Label {
text: qsTr('Amount to send')
}
RowLayout {
Layout.fillWidth: true
Label {
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
font.bold: true
text: Config.formatSats(invoice.amount, false)
}
Label {
text: Config.baseUnit
color: Material.accentColor
}
Label {
id: fiatValue
Layout.fillWidth: true
text: Daemon.fx.enabled
? '(' + Daemon.fx.fiatValue(invoice.amount, false) + ' ' + Daemon.fx.fiatCurrency + ')'
: ''
font.pixelSize: constants.fontSizeMedium
}
}
Label {
text: qsTr('Description')
}
Label {
text: invoice.message
Layout.fillWidth: true
wrapMode: Text.Wrap
elide: Text.ElideRight
}
Label { Label {
visible: invoice.invoiceType == Invoice.OnchainInvoice visible: invoice.invoiceType == Invoice.OnchainInvoice
text: qsTr('Address') text: qsTr('Address')
color: Material.accentColor
} }
Label { Label {
@ -119,6 +221,7 @@ ElDialog {
Label { Label {
visible: invoice.invoiceType == Invoice.LightningInvoice visible: invoice.invoiceType == Invoice.LightningInvoice
text: qsTr('Remote Pubkey') text: qsTr('Remote Pubkey')
color: Material.accentColor
} }
Label { Label {
@ -132,6 +235,7 @@ ElDialog {
Label { Label {
visible: invoice.invoiceType == Invoice.LightningInvoice visible: invoice.invoiceType == Invoice.LightningInvoice
text: qsTr('Route via (t)') text: qsTr('Route via (t)')
color: Material.accentColor
} }
Label { Label {
@ -145,6 +249,7 @@ ElDialog {
Label { Label {
visible: invoice.invoiceType == Invoice.LightningInvoice visible: invoice.invoiceType == Invoice.LightningInvoice
text: qsTr('Route via (r)') text: qsTr('Route via (r)')
color: Material.accentColor
} }
Label { Label {
@ -157,6 +262,7 @@ ElDialog {
Label { Label {
text: qsTr('Status') text: qsTr('Status')
color: Material.accentColor
} }
Label { Label {
@ -194,30 +300,20 @@ ElDialog {
} }
} }
Button { FlatButton {
text: qsTr('Save') text: qsTr('Pay')
icon.source: '../../icons/save.png'
visible: invoice_key == ''
enabled: invoice.canSave
onClicked: {
invoice.save_invoice()
dialog.close()
}
}
Button {
text: qsTr('Pay now')
icon.source: '../../icons/confirmed.png' icon.source: '../../icons/confirmed.png'
enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay
onClicked: { onClicked: {
if (invoice_key == '') // save invoice if not retrieved from key if (invoice_key == '') // save invoice if not retrieved from key
invoice.save_invoice() invoice.save_invoice()
dialog.close() dialog.close()
if (invoice.invoiceType == Invoice.OnchainInvoice) { doPay() // only signal here
doPay() // only signal here // if (invoice.invoiceType == Invoice.OnchainInvoice) {
} else if (invoice.invoiceType == Invoice.LightningInvoice) { // doPay() // only signal here
doPay() // only signal here // } else if (invoice.invoiceType == Invoice.LightningInvoice) {
} // doPay() // only signal here
// }
} }
} }
} }

6
electrum/gui/qml/components/ReceiveDialog.qml

@ -212,16 +212,16 @@ ElDialog {
var qamt = Config.unitsToSats(receiveDetailsDialog.amount) var qamt = Config.unitsToSats(receiveDetailsDialog.amount)
if (qamt.satsInt > Daemon.currentWallet.lightningCanReceive.satsInt) { if (qamt.satsInt > Daemon.currentWallet.lightningCanReceive.satsInt) {
console.log('Creating OnChain request') console.log('Creating OnChain request')
Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, false, ignoreGaplimit) Daemon.currentWallet.createRequest(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, false, ignoreGaplimit)
} else { } else {
console.log('Creating Lightning request') console.log('Creating Lightning request')
Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, true) Daemon.currentWallet.createRequest(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, true)
} }
} }
function createDefaultRequest(ignoreGaplimit = false) { function createDefaultRequest(ignoreGaplimit = false) {
console.log('Creating default request') console.log('Creating default request')
Daemon.currentWallet.create_default_request(ignoreGaplimit) Daemon.currentWallet.createDefaultRequest(ignoreGaplimit)
} }
Connections { Connections {

5
electrum/gui/qml/components/SendDialog.qml

@ -22,10 +22,15 @@ ElDialog {
padding: 0 padding: 0
function restart() {
qrscan.restart()
}
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
QRScan { QRScan {
id: qrscan
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
Layout.fillHeight: true Layout.fillHeight: true

8
electrum/gui/qml/components/WalletMainView.qml

@ -143,6 +143,9 @@ Item {
wallet: Daemon.currentWallet wallet: Daemon.currentWallet
onValidationError: { onValidationError: {
var dialog = app.messageDialog.createObject(app, {'text': message }) var dialog = app.messageDialog.createObject(app, {'text': message })
dialog.closed.connect(function() {
_sendDialog.restart()
})
dialog.open() dialog.open()
} }
onValidationWarning: { onValidationWarning: {
@ -176,6 +179,9 @@ Item {
Component { Component {
id: invoiceDialog id: invoiceDialog
InvoiceDialog { InvoiceDialog {
width: parent.width
height: parent.height
onDoPay: { onDoPay: {
if (invoice.invoiceType == Invoice.OnchainInvoice) { if (invoice.invoiceType == Invoice.OnchainInvoice) {
var dialog = confirmPaymentDialog.createObject(mainView, { var dialog = confirmPaymentDialog.createObject(mainView, {
@ -206,7 +212,7 @@ Item {
} }
close() close()
} }
// onClosed: destroy() onClosed: destroy()
} }
} }

6
electrum/gui/qml/components/controls/QRScan.qml

@ -15,6 +15,12 @@ Item {
signal found signal found
function restart() {
still.source = ''
_pointsVisible = false
active = true
}
VideoOutput { VideoOutput {
id: vo id: vo
anchors.fill: parent anchors.fill: parent

4
electrum/gui/qml/qeconfig.py

@ -85,9 +85,7 @@ class QEConfig(AuthMixin, QObject):
requestExpiryChanged = pyqtSignal() requestExpiryChanged = pyqtSignal()
@pyqtProperty(int, notify=requestExpiryChanged) @pyqtProperty(int, notify=requestExpiryChanged)
def requestExpiry(self): def requestExpiry(self):
a = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING) return self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
self._logger.debug(f'request expiry {a}')
return a
@requestExpiry.setter @requestExpiry.setter
def requestExpiry(self, expiry): def requestExpiry(self, expiry):

23
electrum/gui/qml/qeinvoice.py

@ -165,6 +165,16 @@ class QEInvoiceParser(QEInvoice):
self._amount = QEAmount(from_invoice=self._effectiveInvoice) self._amount = QEAmount(from_invoice=self._effectiveInvoice)
return self._amount return self._amount
@amount.setter
def amount(self, new_amount):
self._logger.debug('set amount')
if self._effectiveInvoice:
self._effectiveInvoice.amount_msat = int(new_amount.satsInt * 1000)
# TODO: side effects?
# TODO: recalc outputs for onchain
self.determine_can_pay()
self.invoiceChanged.emit()
@pyqtProperty('quint64', notify=invoiceChanged) @pyqtProperty('quint64', notify=invoiceChanged)
def expiration(self): def expiration(self):
return self._effectiveInvoice.exp if self._effectiveInvoice else 0 return self._effectiveInvoice.exp if self._effectiveInvoice else 0
@ -242,6 +252,10 @@ class QEInvoiceParser(QEInvoice):
self.statusChanged.emit() self.statusChanged.emit()
def determine_can_pay(self): def determine_can_pay(self):
if self.amount.satsInt == 0:
self.canPay = False
return
if self.invoiceType == QEInvoice.Type.LightningInvoice: if self.invoiceType == QEInvoice.Type.LightningInvoice:
if self.status in [PR_UNPAID, PR_FAILED]: if self.status in [PR_UNPAID, PR_FAILED]:
if self.get_max_spendable_lightning() >= self.amount.satsInt: if self.get_max_spendable_lightning() >= self.amount.satsInt:
@ -374,9 +388,12 @@ class QEInvoiceParser(QEInvoice):
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?
self.validationError.emit('no_amount', 'no amount in uri') # self.validationError.emit('no_amount', 'no amount in uri')
return # return
outputs = [PartialTxOutput.from_address_and_value(self._bip21['address'], self._bip21['amount'])] amount = 0
else:
amount = self._bip21['amount']
outputs = [PartialTxOutput.from_address_and_value(self._bip21['address'], amount)]
self._logger.debug(outputs) self._logger.debug(outputs)
message = self._bip21['message'] if 'message' in self._bip21 else '' message = self._bip21['message'] if 'message' in self._bip21 else ''
invoice = self.create_onchain_invoice(outputs, message, None, self._bip21) invoice = self.create_onchain_invoice(outputs, message, None, self._bip21)

12
electrum/gui/qml/qewallet.py

@ -8,7 +8,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
from electrum import bitcoin from electrum import bitcoin
from electrum.i18n import _ from electrum.i18n import _
from electrum.invoices import (InvoiceError) from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.network import TxBroadcastError, BestEffortRequestFailed from electrum.network import TxBroadcastError, BestEffortRequestFailed
from electrum.transaction import PartialTxOutput from electrum.transaction import PartialTxOutput
@ -505,7 +505,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
@pyqtSlot(QEAmount, str, int) @pyqtSlot(QEAmount, str, int)
@pyqtSlot(QEAmount, str, int, bool) @pyqtSlot(QEAmount, str, int, bool)
@pyqtSlot(QEAmount, str, int, bool, bool) @pyqtSlot(QEAmount, str, int, bool, bool)
def create_request(self, amount: QEAmount, message: str, expiration: int, is_lightning: bool = False, ignore_gap: bool = False): def createRequest(self, amount: QEAmount, message: str, expiration: int, is_lightning: bool = False, ignore_gap: bool = False):
# TODO: unify this method and create_bitcoin_request # TODO: unify this method and create_bitcoin_request
try: try:
if is_lightning: if is_lightning:
@ -531,15 +531,17 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
@pyqtSlot() @pyqtSlot()
@pyqtSlot(bool) @pyqtSlot(bool)
def create_default_request(self, ignore_gap: bool = False): def createDefaultRequest(self, ignore_gap: bool = False):
try: try:
default_expiry = self.wallet.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
if self.wallet.lnworker.channels: if self.wallet.lnworker.channels:
addr = None
if self.wallet.config.get('bolt11_fallback', True): if self.wallet.config.get('bolt11_fallback', True):
addr = self.wallet.get_unused_address() addr = self.wallet.get_unused_address()
# if addr is None, we ran out of addresses. for lightning enabled wallets, ignore for now # if addr is None, we ran out of addresses. for lightning enabled wallets, ignore for now
key = self.wallet.create_request(None, None, 3600, addr) # TODO : expiration from config key = self.wallet.create_request(None, None, default_expiry, addr)
else: else:
key, addr = self.create_bitcoin_request(None, None, 3600, ignore_gap) key, addr = self.create_bitcoin_request(None, None, default_expiry, ignore_gap)
if not key: if not key:
return return
# self.addressModel.init_model() # self.addressModel.init_model()

Loading…
Cancel
Save