move request details into separate dialog
This commit is contained in:
124
electrum/gui/qml/components/ReceiveDetailsDialog.qml
Normal file
124
electrum/gui/qml/components/ReceiveDetailsDialog.qml
Normal file
@@ -0,0 +1,124 @@
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import QtQml.Models 2.1
|
||||
|
||||
import org.electrum 1.0
|
||||
|
||||
import "controls"
|
||||
|
||||
ElDialog {
|
||||
id: dialog
|
||||
|
||||
property alias amount: amountBtc.text
|
||||
property alias description: message.text
|
||||
property alias expiry: expires.currentValue
|
||||
|
||||
|
||||
parent: Overlay.overlay
|
||||
modal: true
|
||||
standardButtons: Dialog.Close
|
||||
|
||||
implicitWidth: parent.width
|
||||
height: parent.height
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: "#aa000000"
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: form
|
||||
width: parent.width
|
||||
rowSpacing: constants.paddingSmall
|
||||
columnSpacing: constants.paddingSmall
|
||||
columns: 4
|
||||
|
||||
Label {
|
||||
text: qsTr('Message')
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: message
|
||||
placeholderText: qsTr('Description of payment request')
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Request')
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.rightMargin: constants.paddingXLarge
|
||||
}
|
||||
|
||||
BtcField {
|
||||
id: amountBtc
|
||||
fiatfield: amountFiat
|
||||
Layout.preferredWidth: parent.width /3
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Item { width: 1; height: 1; Layout.fillWidth: true }
|
||||
|
||||
Item { visible: Daemon.fx.enabled; width: 1; height: 1 }
|
||||
|
||||
FiatField {
|
||||
id: amountFiat
|
||||
btcfield: amountBtc
|
||||
visible: Daemon.fx.enabled
|
||||
Layout.preferredWidth: parent.width /3
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: Daemon.fx.enabled
|
||||
text: Daemon.fx.fiatCurrency
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Item { visible: Daemon.fx.enabled; width: 1; height: 1; Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: qsTr('Expires after')
|
||||
Layout.fillWidth: false
|
||||
}
|
||||
|
||||
ElComboBox {
|
||||
id: expires
|
||||
Layout.columnSpan: 2
|
||||
|
||||
textRole: 'text'
|
||||
valueRole: 'value'
|
||||
|
||||
model: ListModel {
|
||||
id: expiresmodel
|
||||
Component.onCompleted: {
|
||||
// we need to fill the model like this, as ListElement can't evaluate script
|
||||
expiresmodel.append({'text': qsTr('10 minutes'), 'value': 10*60})
|
||||
expiresmodel.append({'text': qsTr('1 hour'), 'value': 60*60})
|
||||
expiresmodel.append({'text': qsTr('1 day'), 'value': 24*60*60})
|
||||
expiresmodel.append({'text': qsTr('1 week'), 'value': 7*24*60*60})
|
||||
expiresmodel.append({'text': qsTr('1 month'), 'value': 31*24*60*60})
|
||||
expiresmodel.append({'text': qsTr('Never'), 'value': 0})
|
||||
expires.currentIndex = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: 1; height: 1; Layout.fillWidth: true }
|
||||
|
||||
Button {
|
||||
Layout.columnSpan: 4
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr('Create Request')
|
||||
icon.source: '../../icons/qrcode.png'
|
||||
onClicked: {
|
||||
accept()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,9 +11,9 @@ import "controls"
|
||||
ElDialog {
|
||||
id: dialog
|
||||
|
||||
property string _bolt11
|
||||
property string _bip21uri
|
||||
property string _address
|
||||
property string _bolt11: request.bolt11
|
||||
property string _bip21uri: request.bip21
|
||||
property string _address: request.address
|
||||
|
||||
property bool _render_qr: false // delay qr rendering until dialog is shown
|
||||
|
||||
@@ -151,101 +151,10 @@ ElDialog {
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
// aaaaaaaaaaaaaaaaaaaa
|
||||
|
||||
|
||||
GridLayout {
|
||||
id: form
|
||||
width: parent.width
|
||||
rowSpacing: constants.paddingSmall
|
||||
columnSpacing: constants.paddingSmall
|
||||
columns: 4
|
||||
|
||||
Label {
|
||||
text: qsTr('Message')
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: message
|
||||
placeholderText: qsTr('Description of payment request')
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Request')
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.rightMargin: constants.paddingXLarge
|
||||
}
|
||||
|
||||
BtcField {
|
||||
id: amount
|
||||
fiatfield: amountFiat
|
||||
Layout.preferredWidth: parent.width /3
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Item { width: 1; height: 1; Layout.fillWidth: true }
|
||||
|
||||
Item { visible: Daemon.fx.enabled; width: 1; height: 1 }
|
||||
|
||||
FiatField {
|
||||
id: amountFiat
|
||||
btcfield: amount
|
||||
visible: Daemon.fx.enabled
|
||||
Layout.preferredWidth: parent.width /3
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: Daemon.fx.enabled
|
||||
text: Daemon.fx.fiatCurrency
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Item { visible: Daemon.fx.enabled; width: 1; height: 1; Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: qsTr('Expires after')
|
||||
Layout.fillWidth: false
|
||||
}
|
||||
|
||||
ElComboBox {
|
||||
id: expires
|
||||
Layout.columnSpan: 2
|
||||
|
||||
textRole: 'text'
|
||||
valueRole: 'value'
|
||||
|
||||
model: ListModel {
|
||||
id: expiresmodel
|
||||
Component.onCompleted: {
|
||||
// we need to fill the model like this, as ListElement can't evaluate script
|
||||
expiresmodel.append({'text': qsTr('10 minutes'), 'value': 10*60})
|
||||
expiresmodel.append({'text': qsTr('1 hour'), 'value': 60*60})
|
||||
expiresmodel.append({'text': qsTr('1 day'), 'value': 24*60*60})
|
||||
expiresmodel.append({'text': qsTr('1 week'), 'value': 7*24*60*60})
|
||||
expiresmodel.append({'text': qsTr('1 month'), 'value': 31*24*60*60})
|
||||
expiresmodel.append({'text': qsTr('Never'), 'value': 0})
|
||||
expires.currentIndex = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: 1; height: 1; Layout.fillWidth: true }
|
||||
|
||||
Button {
|
||||
Layout.columnSpan: 4
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr('Create Request')
|
||||
icon.source: '../../icons/qrcode.png'
|
||||
onClicked: {
|
||||
createRequest()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: 'specify'
|
||||
onClicked: receiveDetailsDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,29 +176,31 @@ ElDialog {
|
||||
}
|
||||
|
||||
function createRequest(ignoreGaplimit = false) {
|
||||
var qamt = Config.unitsToSats(amount.text)
|
||||
var qamt = Config.unitsToSats(receiveDetailsDialog.amount)
|
||||
if (qamt.satsInt > Daemon.currentWallet.lightningCanReceive.satsInt) {
|
||||
console.log('Creating OnChain request')
|
||||
Daemon.currentWallet.create_request(qamt, message.text, expires.currentValue, false, ignoreGaplimit)
|
||||
Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, false, ignoreGaplimit)
|
||||
} else {
|
||||
console.log('Creating Lightning request')
|
||||
Daemon.currentWallet.create_request(qamt, message.text, expires.currentValue, true)
|
||||
Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, true)
|
||||
}
|
||||
}
|
||||
|
||||
function createDefaultRequest(ignoreGaplimit = false) {
|
||||
console.log('Creating default request')
|
||||
Daemon.currentWallet.create_default_request(ignoreGaplimit)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Daemon.currentWallet
|
||||
function onRequestCreateSuccess(key) {
|
||||
message.text = ''
|
||||
amount.text = ''
|
||||
var dialog = requestdialog.createObject(app, { key: key })
|
||||
dialog.open()
|
||||
request.key = key
|
||||
}
|
||||
function onRequestCreateError(code, error) {
|
||||
if (code == 'gaplimit') {
|
||||
var dialog = app.messageDialog.createObject(app, {'text': error, 'yesno': true})
|
||||
dialog.yesClicked.connect(function() {
|
||||
createRequest(true)
|
||||
createDefaultRequest(true)
|
||||
})
|
||||
} else {
|
||||
console.log(error)
|
||||
@@ -297,14 +208,37 @@ ElDialog {
|
||||
}
|
||||
dialog.open()
|
||||
}
|
||||
function onRequestStatusChanged(key, status) {
|
||||
Daemon.currentWallet.requestModel.updateRequest(key, status)
|
||||
}
|
||||
|
||||
RequestDetails {
|
||||
id: request
|
||||
wallet: Daemon.currentWallet
|
||||
key: dialog.key
|
||||
onDetailsChanged: {
|
||||
if (bolt11) {
|
||||
rootLayout.state = 'bolt11'
|
||||
} else if (bip21) {
|
||||
rootLayout.state = 'bip21uri'
|
||||
} else {
|
||||
rootLayout.state = 'address'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReceiveDetailsDialog {
|
||||
id: receiveDetailsDialog
|
||||
onAccepted: {
|
||||
console.log('accepted')
|
||||
Daemon.currentWallet.delete_request(request.key)
|
||||
createRequest()
|
||||
}
|
||||
onRejected: {
|
||||
console.log('rejected')
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
_address = '1234567890'
|
||||
rootLayout.state = 'address'
|
||||
createDefaultRequest()
|
||||
}
|
||||
|
||||
// hack. delay qr rendering until dialog is shown
|
||||
|
||||
@@ -77,7 +77,7 @@ class QERequestDetails(QObject):
|
||||
|
||||
@pyqtProperty(str, notify=detailsChanged)
|
||||
def address(self):
|
||||
addr = self._req.get_address()
|
||||
addr = self._req.get_address() if self._req else ''
|
||||
return addr if addr else ''
|
||||
|
||||
@pyqtProperty(str, notify=detailsChanged)
|
||||
@@ -98,11 +98,11 @@ class QERequestDetails(QObject):
|
||||
|
||||
@pyqtProperty(str, notify=detailsChanged)
|
||||
def bolt11(self):
|
||||
return self._req.lightning_invoice
|
||||
return self._req.lightning_invoice if self._req else ''
|
||||
|
||||
@pyqtProperty(str, notify=detailsChanged)
|
||||
def bip21(self):
|
||||
return self._req.get_bip21_URI()
|
||||
return self._req.get_bip21_URI() if self._req else ''
|
||||
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
@@ -124,6 +124,7 @@ class QERequestDetails(QObject):
|
||||
|
||||
self._amount = QEAmount(from_invoice=self._req)
|
||||
|
||||
self.detailsChanged.emit()
|
||||
self.initStatusStringTimer()
|
||||
|
||||
def initStatusStringTimer(self):
|
||||
|
||||
@@ -526,28 +526,50 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
|
||||
return
|
||||
|
||||
assert key is not None
|
||||
self._requestModel.add_invoice(self.wallet.get_request(key))
|
||||
self.requestModel.add_invoice(self.wallet.get_request(key))
|
||||
self.requestCreateSuccess.emit(key)
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(bool)
|
||||
def create_default_request(self, ignore_gap: bool = False):
|
||||
try:
|
||||
if self.wallet.lnworker.channels:
|
||||
if self.wallet.config.get('bolt11_fallback', True):
|
||||
addr = self.wallet.get_unused_address()
|
||||
# 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
|
||||
else:
|
||||
key, addr = self.create_bitcoin_request(None, None, 3600, ignore_gap)
|
||||
if not key:
|
||||
return
|
||||
# self.addressModel.init_model()
|
||||
except InvoiceError as e:
|
||||
self.requestCreateError.emit('fatal',_('Error creating payment request') + ':\n' + str(e))
|
||||
return
|
||||
|
||||
assert key is not None
|
||||
self.requestModel.add_invoice(self.wallet.get_request(key))
|
||||
self.requestCreateSuccess.emit(key)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def delete_request(self, key: str):
|
||||
self._logger.debug('delete req %s' % key)
|
||||
self.wallet.delete_request(key)
|
||||
self._requestModel.delete_invoice(key)
|
||||
self.requestModel.delete_invoice(key)
|
||||
|
||||
@pyqtSlot(str, result='QVariant')
|
||||
def get_request(self, key: str):
|
||||
return self._requestModel.get_model_invoice(key)
|
||||
return self.requestModel.get_model_invoice(key)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def delete_invoice(self, key: str):
|
||||
self._logger.debug('delete inv %s' % key)
|
||||
self.wallet.delete_invoice(key)
|
||||
self._invoiceModel.delete_invoice(key)
|
||||
self.invoiceModel.delete_invoice(key)
|
||||
|
||||
@pyqtSlot(str, result='QVariant')
|
||||
def get_invoice(self, key: str):
|
||||
return self._invoiceModel.get_model_invoice(key)
|
||||
return self.invoiceModel.get_model_invoice(key)
|
||||
|
||||
@pyqtSlot(str, result=bool)
|
||||
def verify_password(self, password):
|
||||
|
||||
@@ -2467,6 +2467,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
# for receiving
|
||||
amount_sat = amount_sat or 0
|
||||
assert isinstance(amount_sat, int), f"{amount_sat!r}"
|
||||
message = message or ''
|
||||
address = address or None # converts "" to None
|
||||
exp_delay = exp_delay or 0
|
||||
timestamp = int(time.time())
|
||||
|
||||
Reference in New Issue
Block a user