diff --git a/electrum/gui/qml/components/InvoiceDialog.qml b/electrum/gui/qml/components/InvoiceDialog.qml index b0a012147..cd4fb415b 100644 --- a/electrum/gui/qml/components/InvoiceDialog.qml +++ b/electrum/gui/qml/components/InvoiceDialog.qml @@ -214,15 +214,10 @@ ElDialog { } ToolButton { - visible: !amountContainer.editmode && invoice.canPay + visible: !amountContainer.editmode icon.source: '../../icons/pen.png' icon.color: 'transparent' - onClicked: { - amountBtc.text = invoice.amount.satsInt == 0 ? '' : Config.formatSats(invoice.amount) - amountMax.checked = invoice.amount.isMax - amountContainer.editmode = true - amountBtc.focus = true - } + onClicked: enterAmountEdit() } GridLayout { visible: amountContainer.editmode @@ -232,6 +227,9 @@ ElDialog { id: amountBtc fiatfield: amountFiat enabled: !amountMax.checked + onTextAsSatsChanged: { + invoice.amountOverride = textAsSats + } } Label { @@ -250,7 +248,7 @@ ElDialog { checked: false onCheckedChanged: { if (activeFocus) - invoice.amount.isMax = checked + invoice.amountOverride.isMax = checked } } @@ -269,22 +267,18 @@ ElDialog { } } ToolButton { - visible: amountContainer.editmode Layout.fillWidth: false + visible: amountContainer.editmode icon.source: '../../icons/confirmed.png' icon.color: 'transparent' - onClicked: { - amountContainer.editmode = false - invoice.amount = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text) - invoiceAmountChanged() - } + onClicked: applyAmountEdit() } ToolButton { - visible: amountContainer.editmode Layout.fillWidth: false + visible: amountContainer.editmode icon.source: '../../icons/closebutton.png' icon.color: 'transparent' - onClicked: amountContainer.editmode = false + onClicked: cancelAmountEdit() } } @@ -438,8 +432,10 @@ ElDialog { Layout.preferredWidth: 1 text: qsTr('Pay') icon.source: '../../icons/confirmed.png' - enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay && !amountContainer.editmode + enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay onClicked: { + if (amountContainer.editmode) + applyAmountEdit() if (invoice_key == '') // save invoice if not retrieved from key invoice.save_invoice() dialog.close() @@ -450,6 +446,24 @@ ElDialog { } + function enterAmountEdit() { + amountBtc.text = invoice.amount.satsInt == 0 ? '' : Config.formatSats(invoice.amount) + amountMax.checked = invoice.amount.isMax + amountContainer.editmode = true + amountBtc.focus = true + } + + function applyAmountEdit() { + amountContainer.editmode = false + invoice.amount = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text) + invoiceAmountChanged() + } + + function cancelAmountEdit() { + amountContainer.editmode = false + invoice.amountOverride.clear() + } + Component.onCompleted: { if (invoice_key != '') { invoice.initFromKey(invoice_key) diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index 259f91131..e568247b3 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -135,6 +135,8 @@ class QEInvoiceParser(QEInvoice): lnurlRetrieved = pyqtSignal() lnurlError = pyqtSignal([str,str], arguments=['code', 'message']) + amountOverrideChanged = pyqtSignal() + _bip70PrResolvedSignal = pyqtSignal([PaymentRequest], arguments=['pr']) def __init__(self, parent=None): @@ -151,6 +153,9 @@ class QEInvoiceParser(QEInvoice): self._timer.setSingleShot(True) self._timer.timeout.connect(self.updateStatusString) + self._amountOverride = QEAmount() + self._amountOverride.valueChanged.connect(self._on_amountoverride_value_changed) + self._bip70PrResolvedSignal.connect(self._bip70_payment_request_resolved) self.clear() @@ -203,6 +208,22 @@ class QEInvoiceParser(QEInvoice): self.determine_can_pay() self.invoiceChanged.emit() + @pyqtProperty(QEAmount, notify=amountOverrideChanged) + def amountOverride(self): + return self._amountOverride + + @amountOverride.setter + def amountOverride(self, new_amount): + self._logger.debug(f'set new override amount {repr(new_amount)}') + self._amountOverride.copyFrom(new_amount) + + self.determine_can_pay() + self.amountOverrideChanged.emit() + + @pyqtSlot() + def _on_amountoverride_value_changed(self): + self.determine_can_pay() + @pyqtProperty('quint64', notify=invoiceChanged) def time(self): return self._effectiveInvoice.time if self._effectiveInvoice else 0 @@ -316,18 +337,23 @@ class QEInvoiceParser(QEInvoice): self.canPay = False self.userinfo = '' - if self.amount.isEmpty: # unspecified amount + if not self.amountOverride.isEmpty: + amount = self.amountOverride + else: + amount = self.amount + + if amount.isEmpty: # unspecified amount return if self.invoiceType == QEInvoice.Type.LightningInvoice: if self.status in [PR_UNPAID, PR_FAILED]: - if self.get_max_spendable_lightning() >= self.amount.satsInt: + if self.get_max_spendable_lightning() >= amount.satsInt: lnaddr = self._effectiveInvoice._lnaddr - if lnaddr.amount and self.amount.satsInt < lnaddr.amount * COIN: + if lnaddr.amount and amount.satsInt < lnaddr.amount * COIN: self.userinfo = _('Cannot pay less than the amount specified in the invoice') else: self.canPay = True - elif self.address and self.get_max_spendable_onchain() > self.amount.satsInt: + elif self.address and self.get_max_spendable_onchain() > amount.satsInt: # TODO: validate address? # TODO: subtract fee? self.canPay = True @@ -343,10 +369,10 @@ class QEInvoiceParser(QEInvoice): }[self.status] elif self.invoiceType == QEInvoice.Type.OnchainInvoice: if self.status in [PR_UNPAID, PR_FAILED]: - if self.amount.isMax and self.get_max_spendable_onchain() > 0: + if amount.isMax and self.get_max_spendable_onchain() > 0: # TODO: dust limit? self.canPay = True - elif self.get_max_spendable_onchain() >= self.amount.satsInt: + elif self.get_max_spendable_onchain() >= amount.satsInt: # TODO: subtract fee? self.canPay = True else: diff --git a/electrum/gui/qml/qetypes.py b/electrum/gui/qml/qetypes.py index 895b686d3..d41a5cd8a 100644 --- a/electrum/gui/qml/qetypes.py +++ b/electrum/gui/qml/qetypes.py @@ -77,10 +77,12 @@ class QEAmount(QObject): def isEmpty(self): return not(self._is_max or self._amount_sat or self._amount_msat) + @pyqtSlot() def clear(self): - self.satsInt = 0 - self.msatsInt = 0 - self.isMax = False + self._amount_sat = 0 + self._amount_msat = 0 + self._is_max = False + self.valueChanged.emit() def copyFrom(self, amount): if not amount: