From 38f51a3b136283b3fb43d3b00fe6de11cca34823 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 12 May 2025 13:42:41 +0200 Subject: [PATCH] qml: add terms of use to setup wizard --- .../gui/qml/components/TermsOfUseWizard.qml | 24 ++++++ electrum/gui/qml/components/main.qml | 73 +++++++++++++------ .../components/wizard/WCTermsOfUseRequest.qml | 48 ++++++++++++ .../gui/qml/components/wizard/WCWelcome.qml | 43 +++++------ electrum/gui/qml/qeapp.py | 3 +- electrum/gui/qml/qeconfig.py | 13 ++++ electrum/gui/qml/qedaemon.py | 10 ++- electrum/gui/qml/qewizard.py | 19 ++++- 8 files changed, 184 insertions(+), 49 deletions(-) create mode 100644 electrum/gui/qml/components/TermsOfUseWizard.qml create mode 100644 electrum/gui/qml/components/wizard/WCTermsOfUseRequest.qml diff --git a/electrum/gui/qml/components/TermsOfUseWizard.qml b/electrum/gui/qml/components/TermsOfUseWizard.qml new file mode 100644 index 000000000..54d25edbb --- /dev/null +++ b/electrum/gui/qml/components/TermsOfUseWizard.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import "wizard" + +Wizard { + id: termsofusewizard + + wizardTitle: "" + iconSource: "" + header: null + + enter: null // disable transition + + wiz: Daemon.termsOfUseWizard + finishButtonText: qsTr('Next') + + Component.onCompleted: { + var view = wiz.startWizard() + _loadNextComponent(view) + } +} + diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index 203bf20dc..c3e3d4aa1 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -360,6 +360,14 @@ ApplicationWindow } } + property alias termsOfUseWizard: _termsOfUseWizard + Component { + id: _termsOfUseWizard + TermsOfUseWizard { + onClosed: destroy() + } + } + property alias serverConnectWizard: _serverConnectWizard Component { id: _serverConnectWizard @@ -517,38 +525,57 @@ ApplicationWindow app.scanDialog = _qtScanDialog } - if (!Config.autoConnectDefined) { - var dialog = serverConnectWizard.createObject(app) - // without completed serverConnectWizard we can't start + function continueWithServerConnection() { + if (!Config.autoConnectDefined) { + var dialog = serverConnectWizard.createObject(app) + // without completed serverConnectWizard we can't start + dialog.rejected.connect(function() { + app.visible = false + AppController.wantClose = true + Qt.callLater(Qt.quit) + }) + dialog.accepted.connect(function() { + Daemon.startNetwork() + var newww = app.newWalletWizard.createObject(app) + newww.walletCreated.connect(function() { + Daemon.availableWallets.reload() + // and load the new wallet + Daemon.loadWallet(newww.path, newww.wizard_data['password']) + }) + newww.open() + }) + dialog.open() + } else { + Daemon.startNetwork() + if (Daemon.availableWallets.rowCount() > 0) { + Daemon.loadWallet() + } else { + var newww = app.newWalletWizard.createObject(app) + newww.walletCreated.connect(function() { + Daemon.availableWallets.reload() + // and load the new wallet + Daemon.loadWallet(newww.path, newww.wizard_data['password']) + }) + newww.open() + } + } + } + + if (!Config.termsOfUseAccepted) { + var dialog = termsOfUseWizard.createObject(app) + dialog.rejected.connect(function() { app.visible = false AppController.wantClose = true Qt.callLater(Qt.quit) }) dialog.accepted.connect(function() { - Daemon.startNetwork() - var newww = app.newWalletWizard.createObject(app) - newww.walletCreated.connect(function() { - Daemon.availableWallets.reload() - // and load the new wallet - Daemon.loadWallet(newww.path, newww.wizard_data['password']) - }) - newww.open() + Config.termsOfUseAccepted = true + continueWithServerConnection() }) dialog.open() } else { - Daemon.startNetwork() - if (Daemon.availableWallets.rowCount() > 0) { - Daemon.loadWallet() - } else { - var newww = app.newWalletWizard.createObject(app) - newww.walletCreated.connect(function() { - Daemon.availableWallets.reload() - // and load the new wallet - Daemon.loadWallet(newww.path, newww.wizard_data['password']) - }) - newww.open() - } + continueWithServerConnection() } } diff --git a/electrum/gui/qml/components/wizard/WCTermsOfUseRequest.qml b/electrum/gui/qml/components/wizard/WCTermsOfUseRequest.qml new file mode 100644 index 000000000..aa3b6a37c --- /dev/null +++ b/electrum/gui/qml/components/wizard/WCTermsOfUseRequest.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import "../controls" + +WizardComponent { + valid: true + last: true + + Flickable { + anchors.fill: parent + contentHeight: mainLayout.height + clip: true + interactive: height < contentHeight + + ColumnLayout { + id: mainLayout + width: parent.width + spacing: constants.paddingLarge + + Image { + Layout.fillWidth: true + fillMode: Image.PreserveAspectFit + source: Qt.resolvedUrl('../../../icons/electrum_presplash.png') + // reduce spacing a bit + Layout.topMargin: -100 + Layout.bottomMargin: -200 + } + + Label { + Layout.fillWidth: true + text: qsTr("Terms of Use") + font.pixelSize: constants.fontSizeLarge + font.bold: true + horizontalAlignment: Text.AlignHCenter + } + + Label { + Layout.fillWidth: true + text: wiz.termsOfUseText + wrapMode: Text.WordWrap + font.pixelSize: constants.fontSizeMedium + padding: constants.paddingSmall + } + } + } +} diff --git a/electrum/gui/qml/components/wizard/WCWelcome.qml b/electrum/gui/qml/components/wizard/WCWelcome.qml index 3a8654275..d1757b6da 100644 --- a/electrum/gui/qml/components/wizard/WCWelcome.qml +++ b/electrum/gui/qml/components/wizard/WCWelcome.qml @@ -6,41 +6,29 @@ import "../controls" WizardComponent { valid: true - wizard_title: qsTr('Electrum Bitcoin Wallet') + wizard_title: qsTr('Network Configuration') function apply() { - wizard_data['use_defaults'] = !config_advanced.checked - wizard_data['want_proxy'] = config_advanced.checked && config_proxy.checked - wizard_data['autoconnect'] = !config_server.checked || !config_advanced.checked + wizard_data['use_defaults'] = !config_proxy.checked && !config_server.checked + wizard_data['want_proxy'] = config_proxy.checked + wizard_data['autoconnect'] = !config_server.checked } ColumnLayout { width: parent.width - Image { - Layout.fillWidth: true - fillMode: Image.PreserveAspectFit - source: Qt.resolvedUrl('../../../icons/electrum_presplash.png') - // reduce spacing a bit - Layout.topMargin: -50 - Layout.bottomMargin: -120 - } - - CheckBox { - id: config_advanced + Label { Layout.alignment: Qt.AlignHCenter - text: qsTr('Advanced network settings') - checked: false - onCheckedChanged: checkIsLast() + Layout.preferredWidth: parent.width + text: qsTr("Optional settings to customize your network connection") + ":" + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHLeft + font.pixelSize: constants.fontSizeLarge } ColumnLayout { Layout.alignment: Qt.AlignHCenter - - opacity: config_advanced.checked ? 1 : 0 - Behavior on opacity { - NumberAnimation { duration: 300 } - } + Layout.topMargin: 2*constants.paddingXLarge; Layout.bottomMargin: 2*constants.paddingXLarge CheckBox { id: config_proxy @@ -55,5 +43,14 @@ WizardComponent { onCheckedChanged: checkIsLast() } } + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: parent.width + text: qsTr("If you are unsure what this is, leave them unchecked and Electrum will automatically select servers.") + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHLeft + font.pixelSize: constants.fontSizeMedium + } } } diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index 3efc496d2..c8e44bfb4 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -41,7 +41,7 @@ from .qechannelopener import QEChannelOpener from .qelnpaymentdetails import QELnPaymentDetails from .qechanneldetails import QEChannelDetails from .qeswaphelper import QESwapHelper -from .qewizard import QENewWalletWizard, QEServerConnectWizard +from .qewizard import QENewWalletWizard, QEServerConnectWizard, QETermsOfUseWizard from .qemodelfilter import QEFilterProxyModel from .qebip39recovery import QEBip39RecoveryListModel @@ -428,6 +428,7 @@ class ElectrumQmlApplication(QGuiApplication): # TODO QT6 order of declaration is important now? qmlRegisterType(QEAmount, 'org.electrum', 1, 0, 'Amount') qmlRegisterType(QENewWalletWizard, 'org.electrum', 1, 0, 'QNewWalletWizard') + qmlRegisterType(QETermsOfUseWizard, 'org.electrum', 1, 0, 'QTermsOfUseWizard') qmlRegisterType(QEServerConnectWizard, 'org.electrum', 1, 0, 'QServerConnectWizard') qmlRegisterType(QEFilterProxyModel, 'org.electrum', 1, 0, 'FilterProxyModel') qmlRegisterType(QSortFilterProxyModel, 'org.electrum', 1, 0, 'QSortFilterProxyModel') diff --git a/electrum/gui/qml/qeconfig.py b/electrum/gui/qml/qeconfig.py index 61aac644d..16a014692 100644 --- a/electrum/gui/qml/qeconfig.py +++ b/electrum/gui/qml/qeconfig.py @@ -66,6 +66,19 @@ class QEConfig(AuthMixin, QObject): langs_sorted.insert(0, {'value': '', 'text': default}) return langs_sorted + termsOfUseChanged = pyqtSignal() + @pyqtProperty(bool, notify=termsOfUseChanged) + def termsOfUseAccepted(self) -> bool: + return self.config.TERMS_OF_USE_ACCEPTED >= messages.TERMS_OF_USE_LATEST_VERSION + + @termsOfUseAccepted.setter + def termsOfUseAccepted(self, accepted: bool) -> None: + if accepted: + self.config.TERMS_OF_USE_ACCEPTED = messages.TERMS_OF_USE_LATEST_VERSION + else: + self.config.TERMS_OF_USE_ACCEPTED = 0 + self.termsOfUseChanged.emit() + autoConnectChanged = pyqtSignal() @pyqtProperty(bool, notify=autoConnectChanged) def autoConnect(self): diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index d1e236ad4..5791f31de 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -18,7 +18,7 @@ from electrum.storage import StorageReadWriteError from .auth import AuthMixin, auth_protect from .qefx import QEFX from .qewallet import QEWallet -from .qewizard import QENewWalletWizard, QEServerConnectWizard +from .qewizard import QENewWalletWizard, QEServerConnectWizard, QETermsOfUseWizard if TYPE_CHECKING: from electrum.daemon import Daemon @@ -128,6 +128,7 @@ class QEDaemon(AuthMixin, QObject): _available_wallets = None _current_wallet = None _new_wallet_wizard = None + _terms_of_use_wizard = None _server_connect_wizard = None _path = None _name = None @@ -140,6 +141,7 @@ class QEDaemon(AuthMixin, QObject): availableWalletsChanged = pyqtSignal() fxChanged = pyqtSignal() newWalletWizardChanged = pyqtSignal() + termsOfUseWizardChanged = pyqtSignal() serverConnectWizardChanged = pyqtSignal() loadingChanged = pyqtSignal() requestNewPassword = pyqtSignal() @@ -365,6 +367,12 @@ class QEDaemon(AuthMixin, QObject): return self._server_connect_wizard + @pyqtProperty(QETermsOfUseWizard, notify=termsOfUseWizardChanged) + def termsOfUseWizard(self): + if not self._terms_of_use_wizard: + self._terms_of_use_wizard = QETermsOfUseWizard(self) + return self._terms_of_use_wizard + @pyqtSlot() def startNetwork(self): self.daemon.start_network() diff --git a/electrum/gui/qml/qewizard.py b/electrum/gui/qml/qewizard.py index 7ca8841ff..0a9d51fa1 100644 --- a/electrum/gui/qml/qewizard.py +++ b/electrum/gui/qml/qewizard.py @@ -5,9 +5,10 @@ from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum import mnemonic -from electrum.wizard import NewWalletWizard, ServerConnectWizard +from electrum.wizard import NewWalletWizard, ServerConnectWizard, TermsOfUseWizard from electrum.storage import WalletStorage, StorageReadWriteError from electrum.util import WalletFileException +from electrum.gui import messages if TYPE_CHECKING: from electrum.gui.qml.qedaemon import QEDaemon @@ -183,3 +184,19 @@ class QEServerConnectWizard(ServerConnectWizard, QEAbstractWizard): 'proxy_config': {'gui': 'WCProxyConfig'}, 'server_config': {'gui': 'WCServerConfig'}, }) + + +class QETermsOfUseWizard(TermsOfUseWizard, QEAbstractWizard): + def __init__(self, daemon: 'QEDaemon', parent=None): + TermsOfUseWizard.__init__(self, daemon.daemon.config) + QEAbstractWizard.__init__(self, parent) + + # attach gui classes + self.navmap_merge({ + 'terms_of_use': {'gui': 'WCTermsOfUseRequest'}, + }) + + termsOfUseChanged = pyqtSignal() + @pyqtProperty(str, notify=termsOfUseChanged) + def termsOfUseText(self): + return messages.MSG_TERMS_OF_USE