Browse Source

replace swipeview, send & receive now dialogs

send mostly working, though no user entered payment yet
patch-4
Sander van Grieken 2 years ago
parent
commit
15c76114c6
  1. 2
      electrum/gui/qml/components/Constants.qml
  2. 5
      electrum/gui/qml/components/History.qml
  3. 320
      electrum/gui/qml/components/ReceiveDialog.qml
  4. 58
      electrum/gui/qml/components/SendDialog.qml
  5. 172
      electrum/gui/qml/components/WalletMainView.qml
  6. 24
      electrum/gui/qml/components/controls/FlatButton.qml
  7. 10
      electrum/gui/qml/components/controls/HistoryItemDelegate.qml
  8. 21
      electrum/gui/qml/qeinvoice.py

2
electrum/gui/qml/components/Constants.qml

@ -27,6 +27,8 @@ Item {
property color colorCredit: "#ff80ff80"
property color colorDebit: "#ffff8080"
property color mutedForeground: 'gray' //Qt.lighter(Material.background, 2)
property color darkerBackground: Qt.darker(Material.background, 1.20)
property color lighterBackground: Qt.lighter(Material.background, 1.10)
property color colorMine: "yellow"
property color colorError: '#ffff8080'
property color colorLightningLocal: "blue"

5
electrum/gui/qml/components/History.qml

@ -14,10 +14,15 @@ Pane {
padding: 0
clip: true
background: Rectangle {
color: constants.darkerBackground
}
ListView {
id: listview
width: parent.width
height: parent.height
boundsBehavior: Flickable.StopAtBounds
model: visualModel

320
electrum/gui/qml/components/ReceiveDialog.qml

@ -0,0 +1,320 @@
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 string _bolt11
property string _bip21uri
property string _address
property bool _render_qr: false // delay qr rendering until dialog is shown
parent: Overlay.overlay
modal: true
standardButtons: Dialog.Close
width: parent.width
height: parent.height
Overlay.modal: Rectangle {
color: "#aa000000"
}
ColumnLayout {
id: rootLayout
width: parent.width
spacing: constants.paddingMedium
states: [
State {
name: 'bolt11'
PropertyChanges { target: qrloader; sourceComponent: qri_bolt11 }
PropertyChanges { target: bolt11label; font.bold: true }
},
State {
name: 'bip21uri'
PropertyChanges { target: qrloader; sourceComponent: qri_bip21uri }
PropertyChanges { target: bip21label; font.bold: true }
},
State {
name: 'address'
PropertyChanges { target: qrloader; sourceComponent: qri_address }
PropertyChanges { target: addresslabel; font.bold: true }
}
]
Rectangle {
height: 1
Layout.fillWidth: true
color: Material.accentColor
}
Item {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: constants.paddingSmall
Layout.bottomMargin: constants.paddingSmall
Layout.preferredWidth: qrloader.width
Layout.preferredHeight: qrloader.height
Loader {
id: qrloader
Component {
id: qri_bolt11
QRImage {
qrdata: _bolt11
render: _render_qr
}
}
Component {
id: qri_bip21uri
QRImage {
qrdata: _bip21uri
render: _render_qr
}
}
Component {
id: qri_address
QRImage {
qrdata: _address
render: _render_qr
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (rootLayout.state == 'bolt11') {
if (_bip21uri != '')
rootLayout.state = 'bip21uri'
else if (_address != '')
rootLayout.state = 'address'
} else if (rootLayout.state == 'bip21uri') {
if (_address != '')
rootLayout.state = 'address'
else if (_bolt11 != '')
rootLayout.state = 'bolt11'
} else if (rootLayout.state == 'address') {
if (_bolt11 != '')
rootLayout.state = 'bolt11'
else if (_bip21uri != '')
rootLayout.state = 'bip21uri'
}
}
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: constants.paddingLarge
Label {
id: bolt11label
text: qsTr('BOLT11')
color: _bolt11 ? Material.foreground : constants.mutedForeground
}
Rectangle {
Layout.preferredWidth: constants.paddingXXSmall
Layout.preferredHeight: constants.paddingXXSmall
radius: constants.paddingXXSmall / 2
color: Material.accentColor
}
Label {
id: bip21label
text: qsTr('BIP21')
color: _bip21uri ? Material.foreground : constants.mutedForeground
}
Rectangle {
Layout.preferredWidth: constants.paddingXXSmall
Layout.preferredHeight: constants.paddingXXSmall
radius: constants.paddingXXSmall / 2
color: Material.accentColor
}
Label {
id: addresslabel
text: qsTr('ADDRESS')
color: _address ? Material.foreground : constants.mutedForeground
}
}
Rectangle {
height: 1
Layout.fillWidth: true
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()
}
}
}
}
// make clicking the dialog background move the scope away from textedit fields
// so the keyboard goes away
MouseArea {
anchors.fill: parent
z: -1000
onClicked: parkFocus.focus = true
FocusScope { id: parkFocus }
}
Component {
id: requestdialog
RequestDialog {
onClosed: destroy()
}
}
function createRequest(ignoreGaplimit = false) {
var qamt = Config.unitsToSats(amount.text)
if (qamt.satsInt > Daemon.currentWallet.lightningCanReceive.satsInt) {
console.log('Creating OnChain request')
Daemon.currentWallet.create_request(qamt, message.text, expires.currentValue, false, ignoreGaplimit)
} else {
console.log('Creating Lightning request')
Daemon.currentWallet.create_request(qamt, message.text, expires.currentValue, true)
}
}
Connections {
target: Daemon.currentWallet
function onRequestCreateSuccess(key) {
message.text = ''
amount.text = ''
var dialog = requestdialog.createObject(app, { key: key })
dialog.open()
}
function onRequestCreateError(code, error) {
if (code == 'gaplimit') {
var dialog = app.messageDialog.createObject(app, {'text': error, 'yesno': true})
dialog.yesClicked.connect(function() {
createRequest(true)
})
} else {
console.log(error)
var dialog = app.messageDialog.createObject(app, {'text': error})
}
dialog.open()
}
function onRequestStatusChanged(key, status) {
Daemon.currentWallet.requestModel.updateRequest(key, status)
}
}
Component.onCompleted: {
_address = '1234567890'
rootLayout.state = 'address'
}
// hack. delay qr rendering until dialog is shown
Connections {
target: dialog.enter
function onRunningChanged() {
if (!dialog.enter.running) {
dialog._render_qr = true
}
}
}
}

58
electrum/gui/qml/components/SendDialog.qml

@ -0,0 +1,58 @@
import QtQuick 2.6
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.0
import QtQuick.Controls.Material 2.0
import QtQml.Models 2.1
import org.electrum 1.0
import "controls"
ElDialog {
id: dialog
property InvoiceParser invoiceParser
signal manualInput
parent: Overlay.overlay
modal: true
standardButtons: Dialog.Close
width: parent.width
height: parent.height
Overlay.modal: Rectangle {
color: "#aa000000"
}
padding: 0
onClosed: destroy()
ColumnLayout {
anchors.fill: parent
QRScan {
Layout.preferredWidth: parent.width
Layout.fillHeight: true
onFound: invoiceParser.recipient = scanData
}
FlatButton {
Layout.fillWidth: true
text: qsTr('Manual input')
onClicked: {
manualInput()
}
}
FlatButton {
Layout.fillWidth: true
text: qsTr('Paste from clipboard')
onClicked: invoiceParser.recipient = AppController.clipboardToText()
}
}
}

172
electrum/gui/qml/components/WalletMainView.qml

@ -3,8 +3,12 @@ import QtQuick.Controls 2.3
import QtQuick.Layouts 1.0
import QtQml 2.6
import org.electrum 1.0
import "controls"
Item {
id: rootItem
id: mainView
property string title: Daemon.currentWallet ? Daemon.currentWallet.name : ''
@ -69,6 +73,8 @@ Item {
}
}
property var _sendDialog
ColumnLayout {
anchors.centerIn: parent
width: parent.width
@ -94,71 +100,137 @@ Item {
anchors.fill: parent
visible: Daemon.currentWallet
SwipeView {
id: swipeview
History {
id: history
Layout.preferredWidth: parent.width
Layout.fillHeight: true
Layout.fillWidth: true
currentIndex: tabbar.currentIndex
Item {
Loader {
anchors.fill: parent
Receive {
id: receive
anchors.fill: parent
}
}
RowLayout {
spacing: 0
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Send')
onClicked: {
console.log('send')
var comp = Qt.createComponent(Qt.resolvedUrl('SendDialog.qml'))
if (comp.status == Component.Error)
console.log(comp.errorString())
_sendDialog = comp.createObject(mainView, { invoiceParser: invoiceParser } )
// dialog.
_sendDialog.open()
}
}
Item {
Loader {
anchors.fill: parent
History {
id: history
anchors.fill: parent
}
Rectangle {
Layout.fillWidth: false
Layout.preferredWidth: 2
Layout.preferredHeight: parent.height * 2/3
Layout.alignment: Qt.AlignVCenter
color: constants.darkerBackground
}
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Receive')
onClicked: {
var comp = Qt.createComponent(Qt.resolvedUrl('ReceiveDialog.qml'))
var dialog = comp.createObject(mainView)
dialog.open()
}
}
}
}
InvoiceParser {
id: invoiceParser
wallet: Daemon.currentWallet
onValidationError: {
var dialog = app.messageDialog.createObject(app, {'text': message })
dialog.open()
}
onValidationWarning: {
if (code == 'no_channels') {
var dialog = app.messageDialog.createObject(app, {'text': message })
dialog.open()
// TODO: ask user to open a channel, if funds allow
// and maybe store invoice if expiry allows
}
}
onValidationSuccess: {
_sendDialog.close()
// address only -> fill form fields and clear this instance
// else -> show invoice confirmation dialog
if (invoiceType == Invoice.OnchainOnlyAddress) {
recipient.text = invoice.recipient
invoiceParser.clear()
} else {
var dialog = invoiceDialog.createObject(app, {'invoice': invoiceParser})
// dialog.invoice = invoiceParser
dialog.open()
}
}
onInvoiceCreateError: console.log(code + ' ' + message)
Item {
Loader {
anchors.fill: parent
Send {
anchors.fill: parent
onInvoiceSaved: {
Daemon.currentWallet.invoiceModel.init_model()
}
}
Component {
id: invoiceDialog
InvoiceDialog {
onDoPay: {
if (invoice.invoiceType == Invoice.OnchainInvoice) {
var dialog = confirmPaymentDialog.createObject(mainView, {
'address': invoice.address,
'satoshis': invoice.amount,
'message': invoice.message
})
var wo = Daemon.currentWallet.isWatchOnly
dialog.txaccepted.connect(function() {
if (wo) {
showUnsignedTx(dialog.finalizer.serializedTx(false), dialog.finalizer.serializedTx(true))
} else {
dialog.finalizer.send_onchain()
}
})
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(mainView, {
invoice_key: invoice.key
})
dialog.open()
Daemon.currentWallet.pay_lightning_invoice(invoice.key)
}
close()
}
// onClosed: destroy()
}
}
TabBar {
id: tabbar
position: TabBar.Footer
Layout.fillWidth: true
currentIndex: swipeview.currentIndex
TabButton {
text: qsTr('Receive')
font.pixelSize: constants.fontSizeLarge
}
TabButton {
text: qsTr('History')
font.pixelSize: constants.fontSizeLarge
Component {
id: confirmPaymentDialog
ConfirmTxDialog {
title: qsTr('Confirm Payment')
finalizer: TxFinalizer {
wallet: Daemon.currentWallet
canRbf: true
}
TabButton {
text: qsTr('Send')
font.pixelSize: constants.fontSizeLarge
}
Component.onCompleted: tabbar.setCurrentIndex(1)
onClosed: destroy()
}
}
Connections {
target: Daemon
function onWalletLoaded() {
tabbar.setCurrentIndex(1)
Component {
id: lightningPaymentProgressDialog
LightningPaymentProgressDialog {
onClosed: destroy()
}
}

24
electrum/gui/qml/components/controls/FlatButton.qml

@ -0,0 +1,24 @@
import QtQuick 2.6
import QtQuick.Controls 2.15
Item {
id: root
property alias text: buttonLabel.text
property alias font: buttonLabel.font
signal clicked
implicitWidth: buttonLabel.width + constants.paddingXXLarge
implicitHeight: buttonLabel.height + constants.paddingXXLarge
Label {
id: buttonLabel
anchors.centerIn: parent
}
MouseArea {
anchors.fill: root
onClicked: root.clicked()
}
}

10
electrum/gui/qml/components/controls/HistoryItemDelegate.qml

@ -45,7 +45,7 @@ Item {
x: constants.paddingSmall
width: delegate.width - 2*constants.paddingSmall
Item { Layout.columnSpan: 3; Layout.preferredWidth: 1; Layout.preferredHeight: 1}
Item { Layout.columnSpan: 3; Layout.preferredWidth: 1; Layout.preferredHeight: constants.paddingSmall }
Image {
readonly property variant tx_icons : [
@ -113,15 +113,17 @@ Item {
}
Component.onCompleted: updateText()
}
Item { Layout.columnSpan: 3; Layout.preferredWidth: 1; Layout.preferredHeight: 1 }
Item { Layout.columnSpan: 3; Layout.preferredWidth: 1; Layout.preferredHeight: constants.paddingSmall }
}
}
Rectangle {
visible: delegate.ListView.section == delegate.ListView.nextSection
Layout.fillWidth: true
// Layout.fillWidth: true
Layout.preferredWidth: parent.width * 2/3
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: constants.paddingTiny
color: Qt.rgba(0,0,0,0.10)
color: Material.background //Qt.rgba(0,0,0,0.10)
}
}

21
electrum/gui/qml/qeinvoice.py

@ -146,6 +146,7 @@ class QEInvoiceParser(QEInvoice):
@recipient.setter
def recipient(self, recipient: str):
#if self._recipient != recipient:
self.canPay = False
self._recipient = recipient
if recipient:
self.validateRecipient(recipient)
@ -249,24 +250,24 @@ class QEInvoiceParser(QEInvoice):
self.userinfo = _('Can\'t pay, insufficient balance')
else:
self.userinfo = {
PR_EXPIRED: _('Can\'t pay, invoice is expired'),
PR_PAID: _('Can\'t pay, invoice is already paid'),
PR_INFLIGHT: _('Can\'t pay, invoice is already being paid'),
PR_ROUTING: _('Can\'t pay, invoice is already being paid'),
PR_UNKNOWN: _('Can\'t pay, invoice has unknown status'),
PR_EXPIRED: _('Invoice is expired'),
PR_PAID: _('Invoice is already paid'),
PR_INFLIGHT: _('Invoice is already being paid'),
PR_ROUTING: _('Invoice is already being paid'),
PR_UNKNOWN: _('Invoice has unknown status'),
}[self.status]
elif self.invoiceType == QEInvoice.Type.OnchainInvoice:
if self.status in [PR_UNPAID, PR_FAILED]:
if self.get_max_spendable_onchain() >= self.amount.satsInt:
self.canPay = True
else:
self.userinfo = _('Can\'t pay, insufficient balance')
self.userinfo = _('Insufficient balance')
else:
self.userinfo = {
PR_EXPIRED: _('Can\'t pay, invoice is expired'),
PR_PAID: _('Can\'t pay, invoice is already paid'),
PR_UNCONFIRMED: _('Can\'t pay, invoice is already paid'),
PR_UNKNOWN: _('Can\'t pay, invoice has unknown status'),
PR_EXPIRED: _('Invoice is expired'),
PR_PAID: _('Invoice is already paid'),
PR_UNCONFIRMED: _('Invoice is already paid'),
PR_UNKNOWN: _('Invoice has unknown status'),
}[self.status]

Loading…
Cancel
Save