Browse Source

qml: remove SwapProgressDialog, introduce qeswaphelper.state and enable dialog elements

depending on qeswaphelper.state

TODO: we can now retrieve the pairs from the service asynchronously, which should eliminate
the startup delay when showing the SwapDialog
master
Sander van Grieken 3 years ago
parent
commit
3cab3b86b4
  1. 32
      electrum/gui/qml/components/SwapDialog.qml
  2. 140
      electrum/gui/qml/components/SwapProgressDialog.qml
  3. 12
      electrum/gui/qml/components/main.qml
  4. 34
      electrum/gui/qml/qeswaphelper.py

32
electrum/gui/qml/components/SwapDialog.qml

@ -26,12 +26,20 @@ ElDialog {
spacing: constants.paddingLarge
InfoTextArea {
id: userinfoText
Layout.leftMargin: constants.paddingXXLarge
Layout.rightMargin: constants.paddingXXLarge
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
visible: swaphelper.userinfo != ''
text: swaphelper.userinfo
iconStyle: swaphelper.state == SwapHelper.Started
? InfoTextArea.IconStyle.Spinner
: swaphelper.state == SwapHelper.Failed
? InfoTextArea.IconStyle.Error
: swaphelper.state == SwapHelper.Success
? InfoTextArea.IconStyle.Done
: InfoTextArea.IconStyle.Info
}
GridLayout {
@ -155,12 +163,16 @@ ElDialog {
Slider {
id: swapslider
Layout.fillWidth: true
Layout.topMargin: constants.paddingLarge
Layout.bottomMargin: constants.paddingLarge
Layout.leftMargin: constants.paddingXXLarge + (parent.width - 2 * constants.paddingXXLarge) * swaphelper.leftVoid
Layout.rightMargin: constants.paddingXXLarge + (parent.width - 2 * constants.paddingXXLarge) * swaphelper.rightVoid
Layout.fillWidth: true
property real scenter: -swapslider.from/(swapslider.to-swapslider.from)
enabled: swaphelper.state == SwapHelper.ServiceReady || swaphelper.state == SwapHelper.Failed
background: Rectangle {
x: swapslider.leftPadding
@ -170,7 +182,9 @@ ElDialog {
width: swapslider.availableWidth
height: implicitHeight
radius: 2
color: Material.accentColor
color: enabled
? Material.accentColor
: Material.sliderDisabledColor
// full width somehow misaligns with handle, define rangeWidth
property int rangeWidth: width - swapslider.leftPadding
@ -183,7 +197,9 @@ ElDialog {
? (swapslider.visualPosition-swapslider.scenter) * parent.rangeWidth
: (swapslider.scenter-swapslider.visualPosition) * parent.rangeWidth
height: parent.height
color: Material.accentColor
color: enabled
? Material.accentColor
: Material.sliderDisabledColor
radius: 2
}
@ -205,8 +221,6 @@ ElDialog {
}
}
property real scenter: -swapslider.from/(swapslider.to-swapslider.from)
from: swaphelper.rangeMin
to: swaphelper.rangeMax
@ -242,9 +256,9 @@ ElDialog {
Layout.fillWidth: true
text: qsTr('Ok')
icon.source: Qt.resolvedUrl('../../icons/confirmed.png')
enabled: swaphelper.valid
enabled: swaphelper.valid && (swaphelper.state == SwapHelper.ServiceReady || swaphelper.state == SwapHelper.Failed)
onClicked: {
console.log('Swap triggered from dialog ' + this + ' using swaphelper ' + swaphelper)
swaphelper.executeSwap()
}
}
@ -255,13 +269,9 @@ ElDialog {
function onSliderPosChanged() {
swapslider.value = swaphelper.sliderPos
}
function onSwapSuccess() {
root.close()
}
}
Component.onCompleted: {
console.log('Created SwapDialog ' + this)
swapslider.value = swaphelper.sliderPos
}

140
electrum/gui/qml/components/SwapProgressDialog.qml

@ -1,140 +0,0 @@
import QtQuick 2.15
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
required property QtObject swaphelper
width: parent.width
height: parent.height
resizeWithKeyboard: false
iconSource: Qt.resolvedUrl('../../icons/update.png')
title: swaphelper.isReverse
? qsTr('Reverse swap...')
: qsTr('Swap...')
Item {
id: s
state: ''
states: [
State {
name: ''
},
State {
name: 'success'
PropertyChanges { target: spinner; visible: false }
PropertyChanges { target: helpText; text: qsTr('Success') }
PropertyChanges { target: icon; source: '../../icons/confirmed.png' }
},
State {
name: 'failed'
PropertyChanges { target: spinner; visible: false }
PropertyChanges { target: helpText; text: qsTr('Failed') }
PropertyChanges { target: errorText; visible: true }
PropertyChanges { target: icon; source: '../../icons/warning.png' }
}
]
transitions: [
Transition {
from: ''
to: 'success'
PropertyAnimation { target: helpText; properties: 'text'; duration: 0}
NumberAnimation { target: icon; properties: 'opacity'; from: 0; to: 1; duration: 200 }
NumberAnimation { target: icon; properties: 'scale'; from: 0; to: 1; duration: 500
easing.type: Easing.OutBack
easing.overshoot: 10
}
},
Transition {
from: ''
to: 'failed'
PropertyAnimation { target: helpText; properties: 'text'; duration: 0}
NumberAnimation { target: icon; properties: 'opacity'; from: 0; to: 1; duration: 500 }
}
]
}
ColumnLayout {
id: content
anchors.centerIn: parent
width: parent.width
Item {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: constants.iconSizeXXLarge
Layout.preferredHeight: constants.iconSizeXXLarge
Item {
id: spinner
property real rot: 0
RotationAnimation on rot {
duration: 2000
loops: Animation.Infinite
from: 0
to: 360
running: spinner.visible
easing.type: Easing.InOutQuint
}
Image {
x: constants.iconSizeXLarge/2 * Math.cos(spinner.rot*2*Math.PI/360)
y: constants.iconSizeXLarge/2 * Math.sin(spinner.rot*2*Math.PI/360)
width: constants.iconSizeXLarge
height: constants.iconSizeXLarge
source: swaphelper.isReverse ? '../../icons/bitcoin.png' : '../../icons/lightning.png'
}
Image {
x: constants.iconSizeXLarge/2 * Math.cos(Math.PI + spinner.rot*2*Math.PI/360)
y: constants.iconSizeXLarge/2 * Math.sin(Math.PI + spinner.rot*2*Math.PI/360)
width: constants.iconSizeXLarge
height: constants.iconSizeXLarge
source: swaphelper.isReverse ? '../../icons/lightning.png' : '../../icons/bitcoin.png'
}
}
Image {
id: icon
width: constants.iconSizeXXLarge
height: constants.iconSizeXXLarge
}
}
Label {
id: helpText
Layout.alignment: Qt.AlignHCenter
text: qsTr('Performing swap...')
font.pixelSize: constants.fontSizeXXLarge
}
Label {
id: errorText
Layout.preferredWidth: parent.width
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
font.pixelSize: constants.fontSizeLarge
}
}
Connections {
target: swaphelper
function onSwapSuccess() {
console.log('swap succeeded!')
s.state = 'success'
}
function onSwapFailed(message) {
console.log('swap failed: ' + message)
s.state = 'failed'
if (message)
errorText.text = message
}
}
}

12
electrum/gui/qml/components/main.qml

@ -382,22 +382,10 @@ ApplicationWindow
var dialog = app.messageDialog.createObject(app, { text: message })
dialog.open()
}
onSwapStarted: {
var progressdialog = swapProgressDialog.createObject(app, { swaphelper: _swaphelper })
progressdialog.open()
}
}
}
}
Component {
id: swapProgressDialog
SwapProgressDialog {
onClosed: destroy()
}
}
NotificationPopup {
id: notificationPopup
width: parent.width

34
electrum/gui/qml/qeswaphelper.py

@ -3,7 +3,7 @@ import threading
import math
from typing import Union
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, Q_ENUMS
from electrum.i18n import _
from electrum.lnutil import ln_dummy_address
@ -19,6 +19,15 @@ from .util import QtEventListener, qt_event_listener
class QESwapHelper(AuthMixin, QObject, QtEventListener):
_logger = get_logger(__name__)
class State:
Initialized = 0
ServiceReady = 1
Started = 2
Failed = 3
Success = 4
Q_ENUMS(State)
confirm = pyqtSignal([str], arguments=['message'])
error = pyqtSignal([str], arguments=['message'])
swapStarted = pyqtSignal()
@ -34,6 +43,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
self._rangeMax = 0
self._tx = None
self._valid = False
self._state = QESwapHelper.State.Initialized
self._userinfo = ' '.join([
_('Move the slider to set the amount and direction of the swap.'),
_('Swapping lightning funds for onchain funds will increase your capacity to receive lightning payments.'),
@ -130,6 +140,17 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
self._valid = valid
self.validChanged.emit()
stateChanged = pyqtSignal()
@pyqtProperty(int, notify=stateChanged)
def state(self):
return self._state
@state.setter
def state(self, state):
if self._state != state:
self._state = state
self.stateChanged.emit()
userinfoChanged = pyqtSignal()
@pyqtProperty(str, notify=userinfoChanged)
def userinfo(self):
@ -215,7 +236,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
swap_manager = lnworker.swap_manager
try:
asyncio.run(swap_manager.get_pairs())
self._service_available = True
self.state = QESwapHelper.State.ServiceReady
except Exception as e:
self.error.emit(_('Swap service unavailable'))
self._logger.error(f'could not get pairs for swap: {repr(e)}')
@ -284,7 +305,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
self.swap_slider_moved()
def swap_slider_moved(self):
if not self._service_available:
if self._state == QESwapHelper.State.Initialized:
return
position = int(self._sliderPos)
@ -339,15 +360,18 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
try:
fut = asyncio.run_coroutine_threadsafe(coro, loop)
self.userinfo = _('Performing swap...')
self.state = QESwapHelper.State.Started
self.swapStarted.emit()
txid = fut.result()
try: # swaphelper might be destroyed at this point
self.userinfo = _('Swap successful!')
self.state = QESwapHelper.State.Success
self.swapSuccess.emit()
except RuntimeError:
pass
except Exception as e:
try: # swaphelper might be destroyed at this point
self.state = QESwapHelper.State.Failed
self._logger.error(str(e))
self.swapFailed.emit(str(e))
except RuntimeError:
@ -369,20 +393,24 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
try:
fut = asyncio.run_coroutine_threadsafe(coro, loop)
self.userinfo = _('Performing swap...')
self.state = QESwapHelper.State.Started
self.swapStarted.emit()
success = fut.result()
try: # swaphelper might be destroyed at this point
if success:
self.userinfo = _('Swap successful!')
self.state = QESwapHelper.State.Success
self.swapSuccess.emit()
else:
self.userinfo = _('Swap failed!')
self.state = QESwapHelper.State.Failed
self.swapFailed.emit('')
except RuntimeError:
pass
except Exception as e:
try: # swaphelper might be destroyed at this point
self.userinfo = _('Swap failed!')
self.state = QESwapHelper.State.Failed
self._logger.error(str(e))
self.swapFailed.emit(str(e))
except RuntimeError:

Loading…
Cancel
Save