From 3cab3b86b4581f8caa6c70328e85c21eff440940 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 14 Apr 2023 14:32:23 +0200 Subject: [PATCH] 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 --- electrum/gui/qml/components/SwapDialog.qml | 32 ++-- .../gui/qml/components/SwapProgressDialog.qml | 140 ------------------ electrum/gui/qml/components/main.qml | 12 -- electrum/gui/qml/qeswaphelper.py | 34 ++++- 4 files changed, 52 insertions(+), 166 deletions(-) delete mode 100644 electrum/gui/qml/components/SwapProgressDialog.qml diff --git a/electrum/gui/qml/components/SwapDialog.qml b/electrum/gui/qml/components/SwapDialog.qml index ba24db9cc..de0aaeb67 100644 --- a/electrum/gui/qml/components/SwapDialog.qml +++ b/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 } diff --git a/electrum/gui/qml/components/SwapProgressDialog.qml b/electrum/gui/qml/components/SwapProgressDialog.qml deleted file mode 100644 index c404d1bf4..000000000 --- a/electrum/gui/qml/components/SwapProgressDialog.qml +++ /dev/null @@ -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 - } - } - -} diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index bb20cd909..3a86199fb 100644 --- a/electrum/gui/qml/components/main.qml +++ b/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 diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index 1363eb725..e6ab36721 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/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: