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
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user