]> Nutra Git (v2) - gamesguru/feather.git/commitdiff
settings rework, proxy support
authortobtoht <tob@featherwallet.org>
Sat, 11 Feb 2023 17:11:21 +0000 (18:11 +0100)
committertobtoht <tob@featherwallet.org>
Sat, 11 Feb 2023 17:11:21 +0000 (18:11 +0100)
77 files changed:
monero
src/MainWindow.cpp
src/MainWindow.h
src/SettingsDialog.cpp
src/SettingsNewDialog.cpp [new file with mode: 0644]
src/SettingsNewDialog.h [new file with mode: 0644]
src/SettingsNewDialog.ui [new file with mode: 0644]
src/WindowManager.cpp
src/WindowManager.h
src/api/LocalMoneroApi.cpp
src/api/LocalMoneroApi.h
src/appcontext.cpp
src/appcontext.h
src/assets.qrc
src/assets/images/chipset_32px.png [new file with mode: 0644]
src/assets/images/file_manager_32px.png [new file with mode: 0644]
src/assets/images/hd_32px.png [new file with mode: 0644]
src/assets/images/i2p.png [new file with mode: 0644]
src/assets/images/interface_32px.png [new file with mode: 0644]
src/assets/images/nw_32px.png [new file with mode: 0644]
src/assets/images/settings_disabled_32px.png [new file with mode: 0644]
src/assets/images/status_offline.svg [new file with mode: 0644]
src/assets/images/vrdp_32px.png [new file with mode: 0644]
src/assets/nodes.json
src/components.h
src/constants.h
src/dialog/DebugInfoDialog.ui
src/dialog/TorInfoDialog.cpp
src/dialog/TorInfoDialog.h
src/dialog/TorInfoDialog.ui
src/dialog/TxBroadcastDialog.cpp
src/dialog/UpdateDialog.cpp
src/dialog/UpdateDialog.h
src/main.cpp
src/model/BountiesModel.cpp
src/model/NodeModel.cpp
src/model/RedditModel.cpp
src/utils/NetworkManager.cpp
src/utils/NetworkManager.h
src/utils/TorManager.cpp
src/utils/TorManager.h
src/utils/Updater.cpp
src/utils/Updater.h
src/utils/Utils.cpp
src/utils/Utils.h
src/utils/WebsocketClient.cpp
src/utils/WebsocketClient.h
src/utils/config.cpp
src/utils/config.h
src/utils/daemonrpc.cpp
src/utils/daemonrpc.h
src/utils/networking.cpp
src/utils/networking.h
src/utils/nodes.cpp
src/utils/nodes.h
src/utils/os/Prestium.cpp [new file with mode: 0644]
src/utils/os/Prestium.h [new file with mode: 0644]
src/widgets/LocalMoneroWidget.cpp
src/widgets/NetworkProxyWidget.cpp [new file with mode: 0644]
src/widgets/NetworkProxyWidget.h [new file with mode: 0644]
src/widgets/NetworkProxyWidget.ui [new file with mode: 0644]
src/widgets/NodeWidget.cpp
src/widgets/NodeWidget.h
src/widgets/NodeWidget.ui
src/wizard/PageMenu.cpp
src/wizard/PageMenu.h
src/wizard/PageMenu.ui
src/wizard/PageNetwork.cpp
src/wizard/PageNetwork.ui
src/wizard/PageNetworkProxy.cpp [new file with mode: 0644]
src/wizard/PageNetworkProxy.h [moved from src/wizard/PageNetworkTor.h with 56% similarity]
src/wizard/PageNetworkProxy.ui [new file with mode: 0644]
src/wizard/PageNetworkTor.cpp [deleted file]
src/wizard/PageNetworkTor.ui [deleted file]
src/wizard/PageNetworkWebsocket.ui
src/wizard/WalletWizard.cpp
src/wizard/WalletWizard.h

diff --git a/monero b/monero
index 9a6dad7cf4958f795b41517badf93f1c6c80b313..f7705c2c6740699a3fa47895473f79c006624559 160000 (submodule)
--- a/monero
+++ b/monero
@@ -1 +1 @@
-Subproject commit 9a6dad7cf4958f795b41517badf93f1c6c80b313
+Subproject commit f7705c2c6740699a3fa47895473f79c006624559
index a513b056e0ea7ad351c542a5884cf7d5100449d6..84babb175f6f602fa9f404423b02fe08fe7c8d1a 100644 (file)
@@ -37,6 +37,8 @@
 #include "utils/Updater.h"
 #include "utils/WebsocketNotifier.h"
 
+//#include "misc_log_ex.h"
+
 MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent)
     : QMainWindow(parent)
     , ui(new Ui::MainWindow)
@@ -46,6 +48,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
     ui->setupUi(this);
 
     qDebug() << "Platform tag: " << this->getPlatformTag();
+//    MCWARNING("feather", "Platform tag: " << this->getPlatformTag().toStdString());
 
     // Ensure the destructor is called after closeEvent()
     setAttribute(Qt::WA_DeleteOnClose);
@@ -78,7 +81,19 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
     connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged);
     this->onWebsocketStatusChanged(!config()->get(Config::disableWebsocket).toBool());
 
-    connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged);
+    connect(m_windowManager, &WindowManager::proxySettingsChanged, [this]{
+        m_ctx->onProxySettingsChanged();
+        this->onProxySettingsChanged();
+    });
+    connect(m_windowManager, &WindowManager::updateBalance, m_ctx.data(), &AppContext::updateBalance);
+    connect(m_windowManager, &WindowManager::offlineMode, [this](bool offline){
+       if (!m_ctx->wallet) {
+           return;
+       }
+       m_ctx->wallet->setOffline(offline);
+       this->onConnectionStatusChanged(Wallet::ConnectionStatus_Disconnected);
+    });
+
     connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged);
     this->onTorConnectionStateChanged(torManager()->torConnected);
 
@@ -113,10 +128,6 @@ void MainWindow::initStatusBar() {
     this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}");
 #endif
 
-#if defined(Q_OS_MACOS)
-    this->patchStylesheetMac();
-#endif
-
     this->statusBar()->setFixedHeight(30);
 
     m_statusLabelStatus = new QLabel("Idle", this);
@@ -143,9 +154,10 @@ void MainWindow::initStatusBar() {
 
     m_statusBtnConnectionStatusIndicator = new StatusBarButton(icons()->icon("status_disconnected.svg"), "Connection status", this);
     connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, [this](){
-        this->onShowSettingsPage(2);
+        this->onShowSettingsPage(SettingsNew::Pages::NETWORK);
     });
     this->statusBar()->addPermanentWidget(m_statusBtnConnectionStatusIndicator);
+    this->onConnectionStatusChanged(Wallet::ConnectionStatus_Disconnected);
 
     m_statusAccountSwitcher = new StatusBarButton(icons()->icon("change_account.png"), "Account switcher", this);
     connect(m_statusAccountSwitcher, &StatusBarButton::clicked, this, &MainWindow::showAccountSwitcherDialog);
@@ -163,9 +175,10 @@ void MainWindow::initStatusBar() {
     connect(m_statusBtnSeed, &StatusBarButton::clicked, this, &MainWindow::showSeedDialog);
     this->statusBar()->addPermanentWidget(m_statusBtnSeed);
 
-    m_statusBtnTor = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Tor settings", this);
-    connect(m_statusBtnTor, &StatusBarButton::clicked, this, &MainWindow::menuTorClicked);
-    this->statusBar()->addPermanentWidget(m_statusBtnTor);
+    m_statusBtnProxySettings = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Proxy settings", this);
+    connect(m_statusBtnProxySettings, &StatusBarButton::clicked, this, &MainWindow::menuProxySettingsClicked);
+    this->statusBar()->addPermanentWidget(m_statusBtnProxySettings);
+    this->onProxySettingsChanged();
 
     m_statusBtnHwDevice = new StatusBarButton(this->hardwareDevicePairedIcon(), this->getHardwareDevice(), this);
     connect(m_statusBtnHwDevice, &StatusBarButton::clicked, this, &MainWindow::menuHwDeviceClicked);
@@ -417,12 +430,12 @@ void MainWindow::initWalletContext() {
     connect(m_ctx.get(), &AppContext::keysCorrupted,            this, &MainWindow::onKeysCorrupted);
     connect(m_ctx.get(), &AppContext::selectedInputsChanged,    this, &MainWindow::onSelectedInputsChanged);
 
-    // Nodes
-    connect(m_ctx->nodes, &Nodes::nodeExhausted,   this, &MainWindow::showNodeExhaustedMessage);
-    connect(m_ctx->nodes, &Nodes::WSNodeExhausted, this, &MainWindow::showWSNodeExhaustedMessage);
-
     // Wallet
-    connect(m_ctx->wallet, &Wallet::connectionStatusChanged, this, &MainWindow::onConnectionStatusChanged);
+    connect(m_ctx->wallet, &Wallet::connectionStatusChanged, [this](int status){
+        // Order is important, first inform UI about a potential disconnect, then reconnect
+        this->onConnectionStatusChanged(status);
+        this->m_ctx->nodes->autoConnect();
+    });
     connect(m_ctx->wallet, &Wallet::currentSubaddressAccountChanged, this, &MainWindow::updateTitle);
     connect(m_ctx->wallet, &Wallet::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded);
 }
@@ -521,7 +534,9 @@ void MainWindow::onWalletOpened() {
     m_ctx->nodes->connectToNode();
     m_updateBytes.start(250);
 
-    this->addToRecentlyOpened(m_ctx->wallet->cachePath());
+    if (config()->get(Config::writeRecentlyOpenedWallets).toBool()) {
+        this->addToRecentlyOpened(m_ctx->wallet->cachePath());
+    }
 }
 
 void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
@@ -593,6 +608,24 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
 #endif
 }
 
+void MainWindow::onProxySettingsChanged() {
+    int proxy = config()->get(Config::proxy).toInt();
+
+    if (proxy == Config::Proxy::Tor) {
+        this->onTorConnectionStateChanged(torManager()->torConnected);
+        m_statusBtnProxySettings->show();
+        return;
+    }
+
+    if (proxy == Config::Proxy::i2p) {
+        m_statusBtnProxySettings->setIcon(icons()->icon("i2p.png"));
+        m_statusBtnProxySettings->show();
+        return;
+    }
+
+    m_statusBtnProxySettings->hide();
+}
+
 void MainWindow::onSynchronized() {
     this->updateNetStats();
     this->setStatusText("Synchronized");
@@ -617,28 +650,33 @@ void MainWindow::onConnectionStatusChanged(int status)
     // Update connection info in status bar.
 
     QIcon icon;
-    switch(status){
-        case Wallet::ConnectionStatus_Disconnected:
-            icon = icons()->icon("status_disconnected.svg");
-            this->setStatusText("Disconnected");
-            break;
-        case Wallet::ConnectionStatus_Connecting:
-            icon = icons()->icon("status_lagging.svg");
-            this->setStatusText("Connecting to node");
-            break;
-        case Wallet::ConnectionStatus_WrongVersion:
-            icon = icons()->icon("status_disconnected.svg");
-            this->setStatusText("Incompatible node");
-            break;
-        case Wallet::ConnectionStatus_Synchronizing:
-            icon = icons()->icon("status_waiting.svg");
-            break;
-        case Wallet::ConnectionStatus_Synchronized:
-            icon = icons()->icon("status_connected.svg");
-            break;
-        default:
-            icon = icons()->icon("status_disconnected.svg");
-            break;
+    if (config()->get(Config::offlineMode).toBool()) {
+        icon = icons()->icon("status_offline.svg");
+        this->setStatusText("Offline");
+    } else {
+        switch(status){
+            case Wallet::ConnectionStatus_Disconnected:
+                icon = icons()->icon("status_disconnected.svg");
+                this->setStatusText("Disconnected");
+                break;
+            case Wallet::ConnectionStatus_Connecting:
+                icon = icons()->icon("status_lagging.svg");
+                this->setStatusText("Connecting to node");
+                break;
+            case Wallet::ConnectionStatus_WrongVersion:
+                icon = icons()->icon("status_disconnected.svg");
+                this->setStatusText("Incompatible node");
+                break;
+            case Wallet::ConnectionStatus_Synchronizing:
+                icon = icons()->icon("status_waiting.svg");
+                break;
+            case Wallet::ConnectionStatus_Synchronized:
+                icon = icons()->icon("status_connected.svg");
+                break;
+            default:
+                icon = icons()->icon("status_disconnected.svg");
+                break;
+        }
     }
 
     m_statusBtnConnectionStatusIndicator->setIcon(icon);
@@ -826,13 +864,6 @@ void MainWindow::showViewOnlyDialog() {
     dialog.exec();
 }
 
-void MainWindow::menuTorClicked() {
-    auto *dialog = new TorInfoDialog(m_ctx, this);
-    connect(dialog, &TorInfoDialog::torSettingsChanged, m_windowManager, &WindowManager::onTorSettingsChanged);
-    dialog->exec();
-    dialog->deleteLater();
-}
-
 void MainWindow::menuHwDeviceClicked() {
     QMessageBox::information(this, "Hardware Device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice()));
 }
@@ -854,21 +885,17 @@ void MainWindow::menuWalletCloseClicked() {
     this->close();
 }
 
+void MainWindow::menuProxySettingsClicked() {
+    this->menuSettingsClicked(true);
+}
+
 void MainWindow::menuAboutClicked() {
     AboutDialog dialog{this};
     dialog.exec();
 }
 
-void MainWindow::menuSettingsClicked() {
-    Settings settings{m_ctx, this};
-    for (const auto &widget: m_tickerWidgets) {
-        connect(&settings, &Settings::preferredFiatCurrencyChanged, widget, &TickerWidgetBase::updateDisplay);
-    }
-    connect(&settings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay);
-    connect(&settings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged));
-    connect(&settings, &Settings::skinChanged, this, &MainWindow::skinChanged);
-    connect(&settings, &Settings::websocketStatusChanged, m_windowManager, &WindowManager::onWebsocketStatusChanged);
-    settings.exec();
+void MainWindow::menuSettingsClicked(bool showProxyTab) {
+    m_windowManager->showSettings(m_ctx, this, showProxyTab);
 }
 
 void MainWindow::menuSignVerifyClicked() {
@@ -887,13 +914,8 @@ void MainWindow::onShowSettingsPage(int page) {
 }
 
 void MainWindow::skinChanged(const QString &skinName) {
-    m_windowManager->changeSkin(skinName);
     ColorScheme::updateFromWidget(this);
     this->updateWidgetIcons();
-
-#if defined(Q_OS_MACOS)
-    this->patchStylesheetMac();
-#endif
 }
 
 void MainWindow::updateWidgetIcons() {
@@ -949,6 +971,17 @@ void MainWindow::closeEvent(QCloseEvent *event) {
     event->accept();
 }
 
+void MainWindow::changeEvent(QEvent* event)
+{
+    if ((event->type() == QEvent::WindowStateChange) && this->isMinimized()) {
+        if (config()->get(Config::lockOnMinimize).toBool()) {
+            this->lockWallet();
+        }
+    } else {
+        QMainWindow::changeEvent(event);
+    }
+}
+
 void MainWindow::donateButtonClicked() {
     m_sendWidget->fill(constants::donationAddress, "Donation to the Feather development team");
     ui->tabWidget->setCurrentIndex(Tabs::SEND);
@@ -1075,22 +1108,6 @@ void MainWindow::showAddressChecker() {
     }
 }
 
-void MainWindow::showNodeExhaustedMessage() {
-    // Spawning dialogs inside a lambda can cause system freezes on linux so we have to do it this way Â¯\_(ツ)_/¯
-
-    auto msg = "Feather is in 'custom node connection mode' but could not "
-               "find an eligible node to connect to. Please go to Settings->Node "
-               "and enter a node manually.";
-    QMessageBox::warning(this, "Could not connect to a node", msg);
-}
-
-void MainWindow::showWSNodeExhaustedMessage() {
-    auto msg = "Feather is in 'automatic node connection mode' but the "
-               "websocket server returned no available nodes. Please go to Settings->Node "
-               "and enter a node manually.";
-    QMessageBox::warning(this, "Could not connect to a node", msg);
-}
-
 void MainWindow::exportKeyImages() {
     QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Key Images (*_keyImages)");
     if (fn.isEmpty()) return;
@@ -1343,14 +1360,39 @@ void MainWindow::bringToFront() {
     activateWindow();
 }
 
+void MainWindow::onPreferredFiatCurrencyChanged() {
+    for (const auto &widget : m_tickerWidgets) {
+        widget->updateDisplay();
+    }
+    m_balanceTickerWidget->updateDisplay();
+    m_sendWidget->onPreferredFiatCurrencyChanged();
+}
+
+void MainWindow::onHideUpdateNotifications(bool hidden) {
+    if (hidden) {
+        m_statusUpdateAvailable->hide();
+    }
+    else if (m_updater->state == Updater::State::UPDATE_AVAILABLE) {
+        m_statusUpdateAvailable->show();
+    }
+}
+
 void MainWindow::onTorConnectionStateChanged(bool connected) {
+    if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
+        return;
+    }
+
     if (connected)
-        m_statusBtnTor->setIcon(icons()->icon("tor_logo.png"));
+        m_statusBtnProxySettings->setIcon(icons()->icon("tor_logo.png"));
     else
-        m_statusBtnTor->setIcon(icons()->icon("tor_logo_disabled.png"));
+        m_statusBtnProxySettings->setIcon(icons()->icon("tor_logo_disabled.png"));
 }
 
 void MainWindow::showUpdateNotification() {
+    if (config()->get(Config::hideUpdateNotifications).toBool()) {
+        return;
+    }
+
     QString versionDisplay{m_updater->version};
     versionDisplay.replace("beta", "Beta");
     QString updateText = QString("Update to Feather %1 is available").arg(versionDisplay);
@@ -1602,14 +1644,6 @@ bool MainWindow::verifyPassword(bool sensitive) {
     return true;
 }
 
-void MainWindow::patchStylesheetMac() {
-    auto patch = Utils::fileOpenQRC(":assets/macStylesheet.patch");
-    auto patch_text = Utils::barrayToString(patch);
-
-    QString styleSheet = qApp->styleSheet() + patch_text;
-    qApp->setStyleSheet(styleSheet);
-}
-
 void MainWindow::userActivity() {
     m_userLastActive = QDateTime::currentSecsSinceEpoch();
 }
index 34a380e92986c292601a20cdfe6bcb8ce3c7739a..709152aba5f0814993eeafbc24b9f30d6f4b5c57 100644 (file)
@@ -12,6 +12,7 @@
 #include "components.h"
 #include "CalcWindow.h"
 #include "SettingsDialog.h"
+#include "SettingsNewDialog.h"
 
 #include "dialog/AboutDialog.h"
 #include "dialog/AccountSwitcherDialog.h"
@@ -102,21 +103,28 @@ public:
     void showOrHide();
     void bringToFront();
 
+public slots:
+    void onPreferredFiatCurrencyChanged();
+    void onHideUpdateNotifications(bool hidden);
+
 signals:
     void closed();
 
+protected:
+    void changeEvent(QEvent* event) override;
+
 private slots:
     // TODO: use a consistent naming convention for slots
     // Menu
     void menuOpenClicked();
     void menuNewRestoreClicked();
     void menuQuitClicked();
-    void menuSettingsClicked();
+    void menuSettingsClicked(bool showProxytab = false);
     void menuAboutClicked();
     void menuSignVerifyClicked();
     void menuVerifyTxProof();
     void menuWalletCloseClicked();
-    void menuTorClicked();
+    void menuProxySettingsClicked();
     void menuToggleTabVisible(const QString &key);
     void menuClearHistoryClicked();
     void onExportHistoryCSV(bool checked);
@@ -184,6 +192,7 @@ private slots:
     void tryStoreWallet();
     void onWebsocketStatusChanged(bool enabled);
     void showUpdateNotification();
+    void onProxySettingsChanged();
 
 private:
     friend WindowManager;
@@ -199,8 +208,6 @@ private:
     void saveGeo();
     void restoreGeo();
     void showDebugInfo();
-    void showNodeExhaustedMessage();
-    void showWSNodeExhaustedMessage();
     void createUnsignedTxDialog(UnsignedTransaction *tx);
     void updatePasswordIcon();
     void updateNetStats();
@@ -217,7 +224,6 @@ private:
     void updateRecentlyOpenedMenu();
     void updateWidgetIcons();
     bool verifyPassword(bool sensitive = true);
-    void patchStylesheetMac();
     void fillSendTab(const QString &address, const QString &description);
     void userActivity();
     void checkUserActivity();
@@ -264,7 +270,7 @@ private:
     StatusBarButton *m_statusBtnPassword;
     StatusBarButton *m_statusBtnPreferences;
     StatusBarButton *m_statusBtnSeed;
-    StatusBarButton *m_statusBtnTor;
+    StatusBarButton *m_statusBtnProxySettings;
     StatusBarButton *m_statusBtnHwDevice;
 
     QSignalMapper *m_tabShowHideSignalMapper;
index 147e69b5e4569f3621d9af5d6fb7b810b85f1524..ccd8695331c66228a2f24715a414aefe6c8c0611 100644 (file)
@@ -160,7 +160,7 @@ void Settings::setupPrivacyTab() {
 }
 
 void Settings::setupNodeTab() {
-    ui->nodeWidget->setupUI(m_ctx);
+//    ui->nodeWidget->setupUI(m_ctx);
     connect(ui->nodeWidget, &NodeWidget::nodeSourceChanged, m_ctx->nodes, &Nodes::onNodeSourceChanged);
     connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode));
 }
diff --git a/src/SettingsNewDialog.cpp b/src/SettingsNewDialog.cpp
new file mode 100644 (file)
index 0000000..6f28c28
--- /dev/null
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "SettingsNewDialog.h"
+#include "ui_SettingsNewDialog.h"
+
+#include <QCloseEvent>
+#include <QDesktopServices>
+#include <QFileDialog>
+#include <QMessageBox>
+
+#include "utils/Icons.h"
+#include "utils/WebsocketNotifier.h"
+#include "widgets/NetworkProxyWidget.h"
+
+SettingsNew::SettingsNew(QSharedPointer<AppContext> ctx, QWidget *parent)
+        : QDialog(parent)
+        , ui(new Ui::SettingsNew)
+        , m_ctx(std::move(ctx))
+{
+    ui->setupUi(this);
+
+    this->setupAppearanceTab();
+    this->setupNetworkTab();
+    this->setupStorageTab();
+    this->setupDisplayTab();
+    this->setupMemoryTab();
+    this->setupTransactionsTab();
+    this->setupMiscTab();
+
+    connect(ui->selector, &QListWidget::currentItemChanged, [this](QListWidgetItem *current, QListWidgetItem *previous){
+        ui->stackedWidget->setCurrentIndex(current->type());
+    });
+
+    ui->selector->setSelectionMode(QAbstractItemView::SingleSelection);
+    ui->selector->setSelectionBehavior(QAbstractItemView::SelectRows);
+//    ui->selector->setCurrentRow(config()->get(Config::lastSettingsPage).toInt());
+
+    new QListWidgetItem(icons()->icon("interface_32px.png"), "Appearance", ui->selector, Pages::APPEARANCE);
+    new QListWidgetItem(icons()->icon("nw_32px.png"), "Network", ui->selector, Pages::NETWORK);
+    new QListWidgetItem(icons()->icon("hd_32px.png"), "Storage", ui->selector, Pages::STORAGE);
+    new QListWidgetItem(icons()->icon("vrdp_32px.png"), "Display", ui->selector, Pages::DISPLAY);
+//  new QListWidgetItem(icons()->icon("chipset_32px.png"), "Memory", ui->selector, Pages::MEMORY);
+    new QListWidgetItem(icons()->icon("file_manager_32px.png"), "Transactions", ui->selector, Pages::TRANSACTIONS);
+    new QListWidgetItem(icons()->icon("settings_disabled_32px.png"), "Misc", ui->selector, Pages::MISC);
+
+    ui->selector->setFixedWidth(ui->selector->sizeHintForColumn(0) + ui->selector->frameWidth() + 5);
+
+//    for (int i = 0; i < ui->selector->count(); ++i) {
+//        QListWidgetItem *item = ui->selector->item(i);
+//        item->setSizeHint(QSize(item->sizeHint().width(), ui->selector->iconSize().height() + 8));
+//    }
+
+    connect(ui->closeButton, &QDialogButtonBox::accepted, [this]{
+        // Save Proxy settings
+        bool isProxySettingChanged = ui->proxyWidget->isProxySettingsChanged();
+        ui->proxyWidget->setProxySettings();
+        if (isProxySettingChanged) {
+            emit proxySettingsChanged();
+        }
+
+        config()->set(Config::lastSettingsPage, ui->selector->currentRow());
+        this->close();
+    });
+
+    this->setSelection(config()->get(Config::lastSettingsPage).toInt());
+
+    this->adjustSize();
+}
+
+void SettingsNew::setupAppearanceTab() {
+    // [Theme]
+    this->setupThemeComboBox();
+    auto settingsTheme = config()->get(Config::skin).toString();
+    if (m_themes.contains(settingsTheme)) {
+        ui->comboBox_theme->setCurrentIndex(m_themes.indexOf(settingsTheme));
+    }
+    connect(ui->comboBox_theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
+        emit skinChanged(m_themes.at(pos));
+    });
+
+    // [Amount precision]
+    for (int i = 0; i <= 12; i++) {
+        ui->comboBox_amountPrecision->addItem(QString::number(i));
+    }
+    ui->comboBox_amountPrecision->setCurrentIndex(config()->get(Config::amountPrecision).toInt());
+
+    connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
+        config()->set(Config::amountPrecision, pos);
+        emit updateBalance();
+    });
+
+    // [Date format]
+    QDateTime now = QDateTime::currentDateTime();
+    for (const auto & format : m_dateFormats) {
+        ui->comboBox_dateFormat->addItem(now.toString(format));
+    }
+    QString dateFormatSetting = config()->get(Config::dateFormat).toString();
+    if (m_dateFormats.contains(dateFormatSetting)) {
+        ui->comboBox_dateFormat->setCurrentIndex(m_dateFormats.indexOf(dateFormatSetting));
+    }
+
+    connect(ui->comboBox_dateFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
+        config()->set(Config::dateFormat, m_dateFormats.at(pos));
+    });
+
+    // [Time format]
+    for (const auto & format : m_timeFormats) {
+        ui->comboBox_timeFormat->addItem(now.toString(format));
+    }
+    QString timeFormatSetting = config()->get(Config::timeFormat).toString();
+    if (m_timeFormats.contains(timeFormatSetting)) {
+        ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting));
+    }
+
+    connect(ui->comboBox_timeFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
+        config()->set(Config::timeFormat, m_timeFormats.at(pos));
+    });
+
+
+    // [Balance display]
+    ui->comboBox_balanceDisplay->setCurrentIndex(config()->get(Config::balanceDisplay).toInt());
+    connect(ui->comboBox_balanceDisplay, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
+        config()->set(Config::balanceDisplay, pos);
+        emit updateBalance();
+    });
+
+    // [Preferred fiat currency]
+    QStringList availableFiatCurrencies = appData()->prices.rates.keys();
+    for (const auto &currency : availableFiatCurrencies) {
+        ui->comboBox_fiatCurrency->addItem(currency);
+    }
+
+    QStringList fiatCurrencies;
+    for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) {
+        fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index);
+    }
+
+    auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString();
+    if (!preferredFiatCurrency.isEmpty() && fiatCurrencies.contains(preferredFiatCurrency)) {
+        ui->comboBox_fiatCurrency->setCurrentText(preferredFiatCurrency);
+    }
+
+    connect(ui->comboBox_fiatCurrency, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
+        QString selection = ui->comboBox_fiatCurrency->itemText(index);
+        config()->set(Config::preferredFiatCurrency, selection);
+        emit preferredFiatCurrencyChanged(selection);
+    });
+}
+
+void SettingsNew::setupNetworkTab() {
+    // Node
+    if (m_ctx) {
+        ui->nodeWidget->setupUI(m_ctx->nodes);
+        connect(ui->nodeWidget, &NodeWidget::nodeSourceChanged, m_ctx->nodes, &Nodes::onNodeSourceChanged);
+        connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode));
+    } else {
+        m_nodes = new Nodes(this);
+        ui->nodeWidget->setupUI(m_nodes);
+        ui->nodeWidget->setCanConnect(false);
+    }
+
+    // Proxy
+    connect(ui->proxyWidget, &NetworkProxyWidget::proxySettingsChanged, this, &SettingsNew::onProxySettingsChanged);
+
+    // Websocket
+    // [Obtain third-party data]
+    ui->checkBox_enableWebsocket->setChecked(!config()->get(Config::disableWebsocket).toBool());
+    connect(ui->checkBox_enableWebsocket, &QCheckBox::toggled, [this](bool checked){
+        config()->set(Config::disableWebsocket, !checked);
+        this->enableWebsocket(checked);
+    });
+
+    // Overview
+    ui->checkBox_offlineMode->setChecked(config()->get(Config::offlineMode).toBool());
+    connect(ui->checkBox_offlineMode, &QCheckBox::toggled, [this](bool checked){
+        config()->set(Config::offlineMode, checked);
+        emit offlineMode(checked);
+        this->enableWebsocket(!checked);
+    });
+}
+
+void SettingsNew::setupStorageTab() {
+    // Paths
+    ui->lineEdit_defaultWalletDir->setText(config()->get(Config::walletDirectory).toString());
+    ui->lineEdit_configDir->setText(Config::defaultConfigDir().path());
+    ui->lineEdit_applicationDir->setText(Utils::applicationPath());
+
+    bool portableMode = Utils::isPortableMode();
+    if (portableMode) {
+        ui->btn_browseDefaultWalletDir->setDisabled(true);
+        ui->btn_browseDefaultWalletDir->setToolTip("Portable Mode enabled");
+    }
+
+    connect(ui->btn_browseDefaultWalletDir, &QPushButton::clicked, [this]{
+        QString walletDirOld = config()->get(Config::walletDirectory).toString();
+        QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", walletDirOld, QFileDialog::ShowDirsOnly);
+        if (walletDir.isEmpty())
+            return;
+        config()->set(Config::walletDirectory, walletDir);
+        ui->lineEdit_defaultWalletDir->setText(walletDir);
+    });
+
+    connect(ui->btn_openWalletDir, &QPushButton::clicked, []{
+        QDesktopServices::openUrl(QUrl::fromLocalFile(config()->get(Config::walletDirectory).toString()));
+    });
+
+    connect(ui->btn_openConfigDir, &QPushButton::clicked, []{
+        QDesktopServices::openUrl(QUrl::fromLocalFile(Config::defaultConfigDir().path()));
+    });
+
+    ui->frame_portableMode->setVisible(portableMode);
+
+    // Logging
+    // [Write log files to disk]
+    ui->checkBox_enableLogging->setChecked(!config()->get(Config::disableLogging).toBool());
+    connect(ui->checkBox_enableLogging, &QCheckBox::toggled, [](bool toggled){
+        config()->set(Config::disableLogging, !toggled);
+        WalletManager::instance()->setLogLevel(toggled ? config()->get(Config::logLevel).toInt() : -1);
+    });
+
+    // [Log level]
+    ui->comboBox_logLevel->setCurrentIndex(config()->get(Config::logLevel).toInt());
+    connect(ui->comboBox_logLevel, &QComboBox::currentIndexChanged, [](int index){
+       config()->set(Config::logLevel, index);
+       if (!config()->get(Config::disableLogging).toBool()) {
+           WalletManager::instance()->setLogLevel(index);
+       }
+    });
+
+    // [Write stack trace to disk on crash]
+    ui->checkBox_writeStackTraceToDisk->setChecked(config()->get(Config::writeStackTraceToDisk).toBool());
+    connect(ui->checkBox_writeStackTraceToDisk, &QCheckBox::toggled, [](bool toggled){
+       config()->set(Config::writeStackTraceToDisk, toggled);
+    });
+
+    // [Open log file]
+    connect(ui->btn_openLogFile, &QPushButton::clicked, []{
+        QDesktopServices::openUrl(QUrl::fromLocalFile(Config::defaultConfigDir().path() + "/libwallet.log"));
+    });
+
+    // Misc
+    // [Save recently opened wallet to config file]
+    ui->checkBox_writeRecentlyOpened->setChecked(config()->get(Config::writeRecentlyOpenedWallets).toBool());
+    connect(ui->checkBox_writeRecentlyOpened, &QCheckBox::toggled, [](bool toggled){
+       config()->set(Config::writeRecentlyOpenedWallets, toggled);
+       if (!toggled) {
+           config()->set(Config::recentlyOpenedWallets, {});
+       }
+    });
+}
+
+void SettingsNew::setupDisplayTab() {
+    // [Hide balance]
+    ui->checkBox_hideBalance->setChecked(config()->get(Config::hideBalance).toBool());
+    connect(ui->checkBox_hideBalance, &QCheckBox::toggled, [this](bool toggled){
+        config()->set(Config::hideBalance, toggled);
+        emit updateBalance();
+    });
+
+    // [Hide update notifications]
+    ui->checkBox_hideUpdateNotifications->setChecked(config()->get(Config::hideUpdateNotifications).toBool());
+    connect(ui->checkBox_hideUpdateNotifications, &QCheckBox::toggled, [this](bool toggled){
+        config()->set(Config::hideUpdateNotifications, toggled);
+        emit hideUpdateNotifications(toggled);
+    });
+
+    // [Hide transaction notifications]
+    ui->checkBox_hideTransactionNotifications->setChecked(config()->get(Config::hideNotifications).toBool());
+    connect(ui->checkBox_hideTransactionNotifications, &QCheckBox::toggled, [](bool toggled){
+        config()->set(Config::hideNotifications, toggled);
+    });
+
+    // [Warn before opening external link]
+    ui->checkBox_warnOnExternalLink->setChecked(config()->get(Config::warnOnExternalLink).toBool());
+    connect(ui->checkBox_warnOnExternalLink, &QCheckBox::clicked, this, [this]{
+        bool state = ui->checkBox_warnOnExternalLink->isChecked();
+        config()->set(Config::warnOnExternalLink, state);
+    });
+
+    // [Lock wallet on inactivity]
+    ui->checkBox_lockOnInactivity->setChecked(config()->get(Config::inactivityLockEnabled).toBool());
+    ui->spinBox_lockOnInactivity->setValue(config()->get(Config::inactivityLockTimeout).toInt());
+    connect(ui->checkBox_lockOnInactivity, &QCheckBox::toggled, [](bool toggled){
+        config()->set(Config::inactivityLockEnabled, toggled);
+    });
+    connect(ui->spinBox_lockOnInactivity, QOverload<int>::of(&QSpinBox::valueChanged), [](int value){
+        config()->set(Config::inactivityLockTimeout, value);
+    });
+
+    // [Lock wallet on minimize]
+    ui->checkBox_lockOnMinimize->setChecked(config()->get(Config::lockOnMinimize).toBool());
+    connect(ui->checkBox_lockOnMinimize, &QCheckBox::toggled, [](bool toggled){
+        config()->set(Config::lockOnMinimize, toggled);
+    });
+}
+
+void SettingsNew::setupMemoryTab() {
+    // Nothing here, yet
+}
+
+void SettingsNew::setupTransactionsTab() {
+    // [Multibroadcast outgoing transactions]
+    ui->checkBox_multibroadcast->setChecked(config()->get(Config::multiBroadcast).toBool());
+    connect(ui->checkBox_multibroadcast, &QCheckBox::toggled, [](bool toggled){
+        config()->set(Config::multiBroadcast, toggled);
+    });
+    connect(ui->btn_multibroadcast, &QPushButton::clicked, [this]{
+        QMessageBox::information(this, "Multibroadcasting", "Multibroadcasting relays outgoing transactions to all nodes in your selected node list. This may improve transaction relay speed and reduces the chance of your transaction failing.");
+    });
+
+    // Hide unimplemented settings
+    ui->checkBox_alwaysOpenAdvancedTxDialog->hide();
+    ui->checkBox_requirePasswordToSpend->hide();
+}
+
+void SettingsNew::setupMiscTab() {
+    // [Block explorer]
+    ui->comboBox_blockExplorer->setCurrentIndex(ui->comboBox_blockExplorer->findText(config()->get(Config::blockExplorer).toString()));
+    connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
+        QString blockExplorer = ui->comboBox_blockExplorer->currentText();
+        config()->set(Config::blockExplorer, blockExplorer);
+        emit blockExplorerChanged(blockExplorer);
+    });
+
+    // [Reddit frontend]
+    ui->comboBox_redditFrontend->setCurrentIndex(ui->comboBox_redditFrontend->findText(config()->get(Config::redditFrontend).toString()));
+    connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
+        QString redditFrontend = ui->comboBox_redditFrontend->currentText();
+        config()->set(Config::redditFrontend, redditFrontend);
+    });
+
+    // [LocalMonero frontend]
+    ui->comboBox_localMoneroFrontend->addItem("localmonero.co", "https://localmonero.co");
+    ui->comboBox_localMoneroFrontend->addItem("localmonero.co/nojs", "https://localmonero.co/nojs");
+    ui->comboBox_localMoneroFrontend->addItem("nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion",
+                                              "http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion");
+    ui->comboBox_localMoneroFrontend->addItem("yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p",
+                                              "http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p");
+    ui->comboBox_localMoneroFrontend->setCurrentIndex(ui->comboBox_localMoneroFrontend->findData(config()->get(Config::localMoneroFrontend).toString()));
+    connect(ui->comboBox_localMoneroFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
+        QString localMoneroFrontend = ui->comboBox_localMoneroFrontend->currentData().toString();
+        config()->set(Config::localMoneroFrontend, localMoneroFrontend);
+    });
+}
+
+void SettingsNew::onProxySettingsChanged() {
+    ui->closeButton->addButton(QDialogButtonBox::Apply);
+    connect(ui->closeButton->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, [this](){
+        ui->proxyWidget->setProxySettings();
+        emit proxySettingsChanged();
+        ui->closeButton->removeButton(ui->closeButton->button(QDialogButtonBox::Apply));
+    });
+}
+
+void SettingsNew::showNetworkProxyTab() {
+    this->setSelection(SettingsNew::Pages::NETWORK);
+    ui->tabWidget_network->setCurrentIndex(1);
+}
+
+void SettingsNew::setupThemeComboBox() {
+#if defined(Q_OS_WIN)
+    m_themes.removeOne("Breeze/Dark");
+    m_themes.removeOne("Breeze/Light");
+#elif defined(Q_OS_MAC)
+    m_themes.removeOne("QDarkStyle");
+#endif
+
+    ui->comboBox_theme->insertItems(0, m_themes);
+}
+
+void SettingsNew::setSelection(int index) {
+    // You'd really think there is a better way
+    QListWidgetItem *item = ui->selector->item(index);
+    QModelIndex idx = ui->selector->indexFromItem(item);
+    ui->selector->setCurrentRow(index);
+    ui->selector->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
+}
+
+void SettingsNew::enableWebsocket(bool enabled) {
+    if (enabled && !config()->get(Config::offlineMode).toBool() && !config()->get(Config::disableWebsocket).toBool()) {
+        websocketNotifier()->websocketClient.restart();
+    } else {
+        websocketNotifier()->websocketClient.stop();
+    }
+    ui->nodeWidget->onWebsocketStatusChanged();
+    emit websocketStatusChanged(enabled);
+}
+
+SettingsNew::~SettingsNew() = default;
\ No newline at end of file
diff --git a/src/SettingsNewDialog.h b/src/SettingsNewDialog.h
new file mode 100644 (file)
index 0000000..f6dc487
--- /dev/null
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef FEATHER_SETTINGSNEWDIALOG_H
+#define FEATHER_SETTINGSNEWDIALOG_H
+
+
+#include <QAbstractButton>
+#include <QDialog>
+#include <QSettings>
+
+#include "appcontext.h"
+#include "widgets/NodeWidget.h"
+
+namespace Ui {
+    class SettingsNew;
+}
+
+class SettingsNew : public QDialog
+{
+Q_OBJECT
+
+public:
+    explicit SettingsNew(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
+    ~SettingsNew() override;
+
+    void showNetworkProxyTab();
+
+    enum Pages {
+        APPEARANCE = 0,
+        NETWORK,
+        STORAGE,
+        DISPLAY,
+        MEMORY,
+        TRANSACTIONS,
+        MISC
+    };
+
+signals:
+    void preferredFiatCurrencyChanged(QString currency);
+    void skinChanged(QString skinName);
+    void blockExplorerChanged(QString blockExplorer);
+    void hideUpdateNotifications(bool hidden);
+    void websocketStatusChanged(bool enabled);
+    void proxySettingsChanged();
+    void updateBalance();
+    void offlineMode(bool offline);
+
+public slots:
+//    void checkboxExternalLinkWarn();
+//    void fiatCurrencySelected(int index);
+
+private slots:
+    void onProxySettingsChanged();
+
+private:
+    void setupAppearanceTab();
+    void setupNetworkTab();
+    void setupStorageTab();
+    void setupDisplayTab();
+    void setupMemoryTab();
+    void setupTransactionsTab();
+    void setupMiscTab();
+
+    void setupThemeComboBox();
+    void setSelection(int index);
+    void enableWebsocket(bool enabled);
+
+    QScopedPointer<Ui::SettingsNew> ui;
+    QSharedPointer<AppContext> m_ctx;
+    Nodes *m_nodes = nullptr;
+
+    QStringList m_themes{"Native", "QDarkStyle", "Breeze/Dark", "Breeze/Light"};
+    QStringList m_dateFormats{"yyyy-MM-dd", "MM-dd-yyyy", "dd-MM-yyyy"};
+    QStringList m_timeFormats{"hh:mm", "hh:mm ap"};
+};
+
+#endif //FEATHER_SETTINGSNEWDIALOG_H
diff --git a/src/SettingsNewDialog.ui b/src/SettingsNewDialog.ui
new file mode 100644 (file)
index 0000000..94002e5
--- /dev/null
@@ -0,0 +1,1120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SettingsNew</class>
+ <widget class="QDialog" name="SettingsNew">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>841</width>
+    <height>454</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Settings</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QListWidget" name="selector">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QVBoxLayout" name="verticalLayout_23"/>
+     </item>
+     <item>
+      <widget class="QStackedWidget" name="stackedWidget">
+       <property name="currentIndex">
+        <number>1</number>
+       </property>
+       <widget class="QWidget" name="page_appearance">
+        <layout class="QVBoxLayout" name="verticalLayout_6">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Appearance&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QFrame" name="frame_2">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Raised</enum>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_7">
+            <item>
+             <layout class="QFormLayout" name="formLayout">
+              <item row="0" column="0">
+               <widget class="QLabel" name="label_12">
+                <property name="text">
+                 <string>Theme:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="0" column="1">
+               <widget class="QComboBox" name="comboBox_theme"/>
+              </item>
+              <item row="1" column="0">
+               <widget class="QLabel" name="label_13">
+                <property name="text">
+                 <string>Amount precision:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="1" column="1">
+               <widget class="QComboBox" name="comboBox_amountPrecision"/>
+              </item>
+              <item row="2" column="0">
+               <widget class="QLabel" name="label_14">
+                <property name="text">
+                 <string>Date format:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="2" column="1">
+               <widget class="QComboBox" name="comboBox_dateFormat"/>
+              </item>
+              <item row="3" column="0">
+               <widget class="QLabel" name="label_15">
+                <property name="text">
+                 <string>Time format:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="3" column="1">
+               <widget class="QComboBox" name="comboBox_timeFormat"/>
+              </item>
+              <item row="4" column="0">
+               <widget class="QLabel" name="label_16">
+                <property name="text">
+                 <string>Balance display:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="4" column="1">
+               <widget class="QComboBox" name="comboBox_balanceDisplay">
+                <item>
+                 <property name="text">
+                  <string>Total balance</string>
+                 </property>
+                </item>
+                <item>
+                 <property name="text">
+                  <string>Spendable balance (+ unconfirmed balance)</string>
+                 </property>
+                </item>
+                <item>
+                 <property name="text">
+                  <string>Spendable balance</string>
+                 </property>
+                </item>
+               </widget>
+              </item>
+              <item row="5" column="0">
+               <widget class="QLabel" name="label_17">
+                <property name="text">
+                 <string>Fiat currency:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="5" column="1">
+               <widget class="QComboBox" name="comboBox_fiatCurrency"/>
+              </item>
+              <item row="6" column="0">
+               <spacer name="verticalSpacer_3">
+                <property name="orientation">
+                 <enum>Qt::Vertical</enum>
+                </property>
+                <property name="sizeHint" stdset="0">
+                 <size>
+                  <width>20</width>
+                  <height>40</height>
+                 </size>
+                </property>
+               </spacer>
+              </item>
+             </layout>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="page_network">
+        <layout class="QVBoxLayout" name="verticalLayout_8">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Network&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QTabWidget" name="tabWidget_network">
+           <property name="currentIndex">
+            <number>0</number>
+           </property>
+           <widget class="QWidget" name="Node">
+            <attribute name="title">
+             <string>Node</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_7">
+             <property name="leftMargin">
+              <number>0</number>
+             </property>
+             <property name="topMargin">
+              <number>0</number>
+             </property>
+             <property name="rightMargin">
+              <number>0</number>
+             </property>
+             <property name="bottomMargin">
+              <number>0</number>
+             </property>
+             <item>
+              <widget class="NodeWidget" name="nodeWidget" native="true">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </widget>
+           <widget class="QWidget" name="tab_proxy">
+            <attribute name="title">
+             <string>Proxy</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_15">
+             <property name="leftMargin">
+              <number>0</number>
+             </property>
+             <property name="topMargin">
+              <number>0</number>
+             </property>
+             <property name="rightMargin">
+              <number>0</number>
+             </property>
+             <property name="bottomMargin">
+              <number>0</number>
+             </property>
+             <item>
+              <widget class="NetworkProxyWidget" name="proxyWidget" native="true"/>
+             </item>
+            </layout>
+           </widget>
+           <widget class="QWidget" name="tab_websocket">
+            <attribute name="title">
+             <string>Websocket</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_16">
+             <item>
+              <widget class="QLabel" name="label_35">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feather can connect to a service hosted by the developers to obtain pricing information, a curated list of remote nodes, Home feeds, the latest version of Feather Wallet and more.&lt;/p&gt;&lt;p&gt;This service is only used to fetch information. The wallet does not send information about its state or your transactions to the server. It is not used for any telemetry or crash reports.&lt;/p&gt;&lt;p&gt;If you disable this connection some functionality will be unavailable.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="wordWrap">
+                <bool>true</bool>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <spacer name="verticalSpacer_11">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeType">
+                <enum>QSizePolicy::Maximum</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>0</width>
+                 <height>10</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item>
+              <widget class="QCheckBox" name="checkBox_enableWebsocket">
+               <property name="text">
+                <string>Enable websocket connection</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <spacer name="verticalSpacer_10">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>0</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </widget>
+           <widget class="QWidget" name="tab_offline">
+            <attribute name="title">
+             <string>Offline</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_20">
+             <item>
+              <widget class="QCheckBox" name="checkBox_offlineMode">
+               <property name="text">
+                <string>Disable all network connections (offline mode)</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <spacer name="verticalSpacer_9">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </widget>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="page_storage">
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_3">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Storage&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QTabWidget" name="tabWidget_2">
+           <property name="currentIndex">
+            <number>0</number>
+           </property>
+           <widget class="QWidget" name="tab_5">
+            <attribute name="title">
+             <string>Paths</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_9">
+             <item>
+              <widget class="QLabel" name="label_4">
+               <property name="text">
+                <string>This application uses the following paths:</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <layout class="QFormLayout" name="formLayout_2">
+               <item row="0" column="0">
+                <widget class="QLabel" name="label_22">
+                 <property name="text">
+                  <string>Wallet directory:</string>
+                 </property>
+                </widget>
+               </item>
+               <item row="0" column="1">
+                <layout class="QHBoxLayout" name="horizontalLayout_10">
+                 <item>
+                  <widget class="QLineEdit" name="lineEdit_defaultWalletDir">
+                   <property name="readOnly">
+                    <bool>true</bool>
+                   </property>
+                  </widget>
+                 </item>
+                 <item>
+                  <widget class="QPushButton" name="btn_browseDefaultWalletDir">
+                   <property name="text">
+                    <string>Change</string>
+                   </property>
+                  </widget>
+                 </item>
+                 <item>
+                  <widget class="QPushButton" name="btn_openWalletDir">
+                   <property name="text">
+                    <string>Open</string>
+                   </property>
+                  </widget>
+                 </item>
+                </layout>
+               </item>
+               <item row="1" column="0">
+                <widget class="QLabel" name="label_23">
+                 <property name="text">
+                  <string>Config directory:</string>
+                 </property>
+                </widget>
+               </item>
+               <item row="1" column="1">
+                <layout class="QHBoxLayout" name="horizontalLayout_3">
+                 <item>
+                  <widget class="QLineEdit" name="lineEdit_configDir">
+                   <property name="readOnly">
+                    <bool>true</bool>
+                   </property>
+                  </widget>
+                 </item>
+                 <item>
+                  <widget class="QPushButton" name="btn_openConfigDir">
+                   <property name="text">
+                    <string>Open</string>
+                   </property>
+                  </widget>
+                 </item>
+                </layout>
+               </item>
+               <item row="2" column="0">
+                <widget class="QLabel" name="label_24">
+                 <property name="text">
+                  <string>Application directory:</string>
+                 </property>
+                </widget>
+               </item>
+               <item row="2" column="1">
+                <widget class="QLineEdit" name="lineEdit_applicationDir">
+                 <property name="readOnly">
+                  <bool>true</bool>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <widget class="QFrame" name="frame_portableMode">
+               <property name="frameShape">
+                <enum>QFrame::NoFrame</enum>
+               </property>
+               <property name="frameShadow">
+                <enum>QFrame::Raised</enum>
+               </property>
+               <layout class="QHBoxLayout" name="horizontalLayout_6">
+                <property name="leftMargin">
+                 <number>0</number>
+                </property>
+                <property name="topMargin">
+                 <number>0</number>
+                </property>
+                <property name="rightMargin">
+                 <number>0</number>
+                </property>
+                <property name="bottomMargin">
+                 <number>0</number>
+                </property>
+                <item>
+                 <widget class="QLabel" name="label_10">
+                  <property name="text">
+                   <string>Portable mode is enabled.</string>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </widget>
+             </item>
+             <item>
+              <spacer name="verticalSpacer_4">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </widget>
+           <widget class="QWidget" name="tab_4">
+            <attribute name="title">
+             <string>Logging</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_2">
+             <item>
+              <widget class="QCheckBox" name="checkBox_writeStackTraceToDisk">
+               <property name="text">
+                <string>Write stack trace to file on crash (Linux-only)</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QCheckBox" name="checkBox_enableLogging">
+               <property name="text">
+                <string>Write log files to disk</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout_4">
+               <item>
+                <widget class="QLabel" name="label_9">
+                 <property name="sizePolicy">
+                  <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+                   <horstretch>0</horstretch>
+                   <verstretch>0</verstretch>
+                  </sizepolicy>
+                 </property>
+                 <property name="text">
+                  <string>Log level:</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <widget class="QComboBox" name="comboBox_logLevel">
+                 <item>
+                  <property name="text">
+                   <string>Warning</string>
+                  </property>
+                 </item>
+                 <item>
+                  <property name="text">
+                   <string>Info</string>
+                  </property>
+                 </item>
+                 <item>
+                  <property name="text">
+                   <string>Debug</string>
+                  </property>
+                 </item>
+                 <item>
+                  <property name="text">
+                   <string>Trace</string>
+                  </property>
+                 </item>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <spacer name="verticalSpacer">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>10</width>
+                 <height>0</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout_5">
+               <item>
+                <widget class="QPushButton" name="btn_openLogFile">
+                 <property name="text">
+                  <string>Open log file</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <spacer name="horizontalSpacer">
+                 <property name="orientation">
+                  <enum>Qt::Horizontal</enum>
+                 </property>
+                 <property name="sizeHint" stdset="0">
+                  <size>
+                   <width>40</width>
+                   <height>20</height>
+                  </size>
+                 </property>
+                </spacer>
+               </item>
+              </layout>
+             </item>
+            </layout>
+           </widget>
+           <widget class="QWidget" name="tab_6">
+            <attribute name="title">
+             <string>Misc</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_10">
+             <item>
+              <widget class="QCheckBox" name="checkBox_writeRecentlyOpened">
+               <property name="text">
+                <string>Write recently opened wallets to config file</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <spacer name="verticalSpacer_5">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </widget>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="page_display">
+        <layout class="QVBoxLayout" name="verticalLayout_12">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_5">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Display&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QFrame" name="frame_3">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Raised</enum>
+           </property>
+           <layout class="QVBoxLayout" name="verticalLayout_11">
+            <item>
+             <widget class="QCheckBox" name="checkBox_hideBalance">
+              <property name="text">
+               <string>Hide balance</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_hideUpdateNotifications">
+              <property name="text">
+               <string>Hide update notifications</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_hideTransactionNotifications">
+              <property name="text">
+               <string>Hide transaction notifications</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_warnOnExternalLink">
+              <property name="text">
+               <string>Warn before opening external link</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_11">
+              <item>
+               <widget class="QCheckBox" name="checkBox_lockOnInactivity">
+                <property name="text">
+                 <string>Lock wallet after inactivity of</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QSpinBox" name="spinBox_lockOnInactivity">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="minimum">
+                 <number>1</number>
+                </property>
+                <property name="maximum">
+                 <number>120</number>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_25">
+                <property name="text">
+                 <string>minutes</string>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_lockOnMinimize">
+              <property name="text">
+               <string>Lock wallet after minimizing the window</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="verticalSpacer_6">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>40</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="page_memory">
+        <layout class="QVBoxLayout" name="verticalLayout_13">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_6">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Memory&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QFrame" name="frame_4">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Raised</enum>
+           </property>
+           <layout class="QVBoxLayout" name="verticalLayout_17">
+            <item>
+             <widget class="QCheckBox" name="checkBox_17">
+              <property name="text">
+               <string>Encrypt private spendkey on lock</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="verticalSpacer_8">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>263</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="page_transactions">
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_7">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Transactions&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QFrame" name="frame">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Raised</enum>
+           </property>
+           <layout class="QVBoxLayout" name="verticalLayout_5">
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_2">
+              <item>
+               <widget class="QCheckBox" name="checkBox_multibroadcast">
+                <property name="text">
+                 <string>Multibroadcast outgoing transactions</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QPushButton" name="btn_multibroadcast">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>?</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <spacer name="horizontalSpacer_4">
+                <property name="orientation">
+                 <enum>Qt::Horizontal</enum>
+                </property>
+                <property name="sizeHint" stdset="0">
+                 <size>
+                  <width>0</width>
+                  <height>20</height>
+                 </size>
+                </property>
+               </spacer>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_alwaysOpenAdvancedTxDialog">
+              <property name="text">
+               <string>Always open advanced transaction dialog</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_requirePasswordToSpend">
+              <property name="text">
+               <string>Require password to spend</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="verticalSpacer_2">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>40</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="page_misc">
+        <layout class="QVBoxLayout" name="verticalLayout_18">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_8">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Misc&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QTabWidget" name="tabWidget_3">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="currentIndex">
+            <number>0</number>
+           </property>
+           <widget class="QWidget" name="tab_7">
+            <attribute name="title">
+             <string>Links</string>
+            </attribute>
+            <layout class="QVBoxLayout" name="verticalLayout_14">
+             <item>
+              <widget class="QLabel" name="label_11">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string>Block explorer:</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QComboBox" name="comboBox_blockExplorer">
+               <item>
+                <property name="text">
+                 <string>exploremonero.com</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>xmrchain.net</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>melo.tools</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>moneroblocks.info</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>blockchair.info</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>127.0.0.1:31312</string>
+                </property>
+               </item>
+              </widget>
+             </item>
+             <item>
+              <widget class="QLabel" name="label_18">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string>Reddit frontend:</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QComboBox" name="comboBox_redditFrontend">
+               <item>
+                <property name="text">
+                 <string>old.reddit.com</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>old.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>reddit.com</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>teddit.net</string>
+                </property>
+               </item>
+              </widget>
+             </item>
+             <item>
+              <widget class="QLabel" name="label_19">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string>LocalMonero frontend:</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QComboBox" name="comboBox_localMoneroFrontend"/>
+             </item>
+             <item>
+              <spacer name="verticalSpacer_7">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </widget>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="closeButton">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>NodeWidget</class>
+   <extends>QWidget</extends>
+   <header>widgets/NodeWidget.h</header>
+   <container>1</container>
+  </customwidget>
+  <customwidget>
+   <class>NetworkProxyWidget</class>
+   <extends>QWidget</extends>
+   <header>widgets/NetworkProxyWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>closeButton</sender>
+   <signal>accepted()</signal>
+   <receiver>SettingsNew</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>closeButton</sender>
+   <signal>rejected()</signal>
+   <receiver>SettingsNew</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
index 7ee8f45f703f4c4250001f31bde1143224191e3e..7bc0e2857d3e9a5f430b81c8439cb73a5fdfeb90 100644 (file)
@@ -38,6 +38,7 @@ WindowManager::WindowManager(EventFilter *eventFilter)
     m_tray->show();
 
     this->initSkins();
+    this->patchMacStylesheet();
 
     this->showCrashLogs();
 
@@ -135,6 +136,34 @@ void WindowManager::raise() {
     }
 }
 
+// ######################## SETTINGS ########################
+
+void WindowManager::showSettings(QSharedPointer<AppContext> ctx, QWidget *parent, bool showProxyTab) {
+    SettingsNew settings{ctx, parent};
+
+    connect(&settings, &SettingsNew::preferredFiatCurrencyChanged, [this]{
+        for (const auto &window : m_windows) {
+            window->onPreferredFiatCurrencyChanged();
+        }
+    });
+    connect(&settings, &SettingsNew::skinChanged, this, &WindowManager::onChangeTheme);
+    connect(&settings, &SettingsNew::updateBalance, this, &WindowManager::updateBalance);
+    connect(&settings, &SettingsNew::proxySettingsChanged, this, &WindowManager::onProxySettingsChanged);
+    connect(&settings, &SettingsNew::websocketStatusChanged, this, &WindowManager::onWebsocketStatusChanged);
+    connect(&settings, &SettingsNew::offlineMode, this, &WindowManager::offlineMode);
+    connect(&settings, &SettingsNew::hideUpdateNotifications, [this](bool hidden){
+        for (const auto &window : m_windows) {
+            window->onHideUpdateNotifications(hidden);
+        }
+    });
+
+    if (showProxyTab) {
+        settings.showNetworkProxyTab();
+    }
+
+    settings.exec();
+}
+
 // ######################## WALLET OPEN ########################
 
 void WindowManager::tryOpenWallet(const QString &path, const QString &password) {
@@ -544,60 +573,56 @@ void WindowManager::onInitialNetworkConfigured() {
         m_initialNetworkConfigured = true;
         appData();
 
-        this->initTor();
-        this->initWS();
+        this->onProxySettingsChanged();
     }
 }
 
-void WindowManager::initTor() {
+void WindowManager::onProxySettingsChanged() {
+    if (Utils::isTorsocks()) {
+        return;
+    }
+
+    // Will kill the process if necessary
     torManager()->init();
     torManager()->start();
 
-    this->onTorSettingsChanged();
-}
+    QNetworkProxy proxy{QNetworkProxy::NoProxy};
+    if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
+        QString host = config()->get(Config::socks5Host).toString();
+        quint16 port = config()->get(Config::socks5Port).toString().toUShort();
 
-void WindowManager::onTorSettingsChanged() {
-    if (Utils::isTorsocks()) {
-        return;
-    }
+        if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
+            host = torManager()->featherTorHost;
+            port = torManager()->featherTorPort;
+        }
 
-    // use local tor -> bundled tor
-    QString host = config()->get(Config::socks5Host).toString();
-    quint16 port = config()->get(Config::socks5Port).toString().toUShort();
-    if (!torManager()->isLocalTor()) {
-        host = torManager()->featherTorHost;
-        port = torManager()->featherTorPort;
+        proxy = QNetworkProxy{QNetworkProxy::Socks5Proxy, host, port};
+        getNetworkSocks5()->setProxy(proxy);
     }
 
-    QNetworkProxy proxy{QNetworkProxy::Socks5Proxy, host, port};
-    getNetworkTor()->setProxy(proxy);
+    qWarning() << "Proxy: " << proxy.hostName() << " " << proxy.port();
+
+    // Switch websocket to new proxy and update URL
+    websocketNotifier()->websocketClient.stop();
     websocketNotifier()->websocketClient.webSocket.setProxy(proxy);
+    websocketNotifier()->websocketClient.nextWebsocketUrl();
+    websocketNotifier()->websocketClient.restart();
 
-    emit torSettingsChanged();
+    emit proxySettingsChanged();
 }
 
 void WindowManager::onWebsocketStatusChanged(bool enabled) {
     emit websocketStatusChanged(enabled);
 }
 
-void WindowManager::initWS() {
-    if (config()->get(Config::offlineMode).toBool()) {
-        return;
-    }
-
-    if (config()->get(Config::disableWebsocket).toBool()) {
-        return;
-    }
-
-    websocketNotifier()->websocketClient.start();
-}
-
 // ######################## WIZARD ########################
 
-WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) const {
+WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) {
     auto *wizard = new WalletWizard;
     connect(wizard, &WalletWizard::initialNetworkConfigured, this, &WindowManager::onInitialNetworkConfigured);
-    connect(wizard, &WalletWizard::skinChanged, this, &WindowManager::changeSkin);
+    connect(wizard, &WalletWizard::showSettings, [this, wizard]{
+        this->showSettings(nullptr, wizard);
+    });
     connect(wizard, &WalletWizard::openWallet, this, &WindowManager::tryOpenWallet);
     connect(wizard, &WalletWizard::createWallet, this, &WindowManager::tryCreateWallet);
     connect(wizard, &WalletWizard::createWalletFromKeys, this, &WindowManager::tryCreateWalletFromKeys);
@@ -667,13 +692,32 @@ QString WindowManager::loadStylesheet(const QString &resource) {
     return data;
 }
 
-void WindowManager::changeSkin(const QString &skinName) {
+void WindowManager::onChangeTheme(const QString &skinName) {
     if (!m_skins.contains(skinName)) {
         qWarning() << QString("No such skin %1").arg(skinName);
         return;
     }
 
     config()->set(Config::skin, skinName);
+
     qApp->setStyleSheet(m_skins[skinName]);
     qDebug() << QString("Skin changed to %1").arg(skinName);
+
+    this->patchMacStylesheet();
+
+    for (const auto &window : m_windows) {
+        window->skinChanged(skinName);
+    }
 }
+
+void WindowManager::patchMacStylesheet() {
+#if defined(Q_OS_MACOS)
+    QString styleSheet = qApp->styleSheet();
+
+    auto patch = Utils::fileOpenQRC(":assets/macStylesheet.patch");
+    auto patch_text = Utils::barrayToString(patch);
+    styleSheet += patch_text;
+
+    qApp->setStyleSheet(styleSheet);
+#endif
+}
\ No newline at end of file
index 4d3c7c02bd2c1a0d30e53aad753b8a6a8005acba..9c5fbd25d6d0e7c95a130763673dd4f28983c728 100644 (file)
@@ -24,18 +24,21 @@ public:
     void close();
     void closeWindow(MainWindow *window);
     void showWizard(WalletWizard::Page startPage);
-    void changeSkin(const QString &skinName);
     void restartApplication(const QString &binaryFilename);
     void raise();
 
+    void showSettings(QSharedPointer<AppContext> ctx, QWidget *parent, bool showProxyTab = false);
+
     EventFilter *eventFilter;
 
 signals:
-    void torSettingsChanged();
+    void proxySettingsChanged();
     void websocketStatusChanged(bool enabled);
+    void updateBalance();
+    void offlineMode(bool offline);
 
 public slots:
-    void onTorSettingsChanged();
+    void onProxySettingsChanged();
     void onWebsocketStatusChanged(bool enabled);
     void tryOpenWallet(const QString &path, const QString &password);
 
@@ -48,6 +51,7 @@ private slots:
     void onDeviceButtonPressed();
     void onDeviceError(const QString &errorMessage);
     void onWalletPassphraseNeeded(bool on_device);
+    void onChangeTheme(const QString &themeName);
 
 private:
     void tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage, const QString &seedOffset, const QString &subaddressLookahead);
@@ -57,15 +61,15 @@ private:
     bool autoOpenWallet();
 
     void initWizard();
-    WalletWizard* createWizard(WalletWizard::Page startPage) const;
+    WalletWizard* createWizard(WalletWizard::Page startPage);
 
     void handleWalletError(const QString &message);
     void displayWalletErrorMessage(const QString &message);
 
-    void initTor();
-    void initWS();
     void initSkins();
     QString loadStylesheet(const QString &resource);
+    void patchMacStylesheet();
+
     void buildTrayMenu();
     void startupWarning();
     void showWarningMessageBox(const QString &title, const QString &message);
index ad61b3b74bd58ea17c4de73afd376eeb9db20aae..2d4cac3c1cca273b672de5a359272e6a3ff1def8 100644 (file)
@@ -3,21 +3,22 @@
 
 #include "LocalMoneroApi.h"
 
-LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl)
+#include "utils/config.h"
+
+LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network)
     : QObject(parent)
     , m_network(network)
-    , m_baseUrl(baseUrl)
 {
 }
 
 void LocalMoneroApi::countryCodes() {
-    QString url = QString("%1/countrycodes").arg(m_baseUrl);
+    QString url = QString("%1/countrycodes").arg(this->getBaseUrl());
     QNetworkReply *reply = m_network->getJson(url);
     connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::COUNTRY_CODES));
 }
 
 void LocalMoneroApi::currencies() {
-    QString url = QString("%1/currencies").arg(m_baseUrl);
+    QString url = QString("%1/currencies").arg(this->getBaseUrl());
     QNetworkReply *reply = m_network->getJson(url);
     connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::CURRENCIES));
 }
@@ -25,9 +26,9 @@ void LocalMoneroApi::currencies() {
 void LocalMoneroApi::paymentMethods(const QString &countryCode) {
     QString url;
     if (countryCode.isEmpty()) {
-        url = QString("%1/payment_methods").arg(m_baseUrl);
+        url = QString("%1/payment_methods").arg(this->getBaseUrl());
     } else {
-        url = QString("%1/payment_methods/%2").arg(m_baseUrl, countryCode);
+        url = QString("%1/payment_methods/%2").arg(this->getBaseUrl(), countryCode);
     }
     QNetworkReply *reply = m_network->getJson(url);
     connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::PAYMENT_METHODS));
@@ -50,7 +51,7 @@ void LocalMoneroApi::sellMoneroOnline(const QString &currencyCode, const QString
 }
 
 void LocalMoneroApi::accountInfo(const QString &username) {
-    QString url = QString("%1/account_info/%2").arg(m_baseUrl, username);
+    QString url = QString("%1/account_info/%2").arg(this->getBaseUrl(), username);
     QNetworkReply *reply = m_network->getJson(url);
     connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::ACCOUNT_INFO));
 }
@@ -88,7 +89,7 @@ void LocalMoneroApi::onResponse(QNetworkReply *reply, LocalMoneroApi::Endpoint e
 QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode,
                                       const QString &paymentMethod, const QString &amount, int page)
 {
-    QString url = QString("%1/%2-monero-online/%3").arg(m_baseUrl, buy ? "buy" : "sell", currencyCode);
+    QString url = QString("%1/%2-monero-online/%3").arg(this->getBaseUrl(), buy ? "buy" : "sell", currencyCode);
     if (!countryCode.isEmpty() && paymentMethod.isEmpty())
         url += QString("/%1").arg(countryCode);
     else if (countryCode.isEmpty() && !paymentMethod.isEmpty())
@@ -103,4 +104,16 @@ QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, con
         query.addQueryItem("page", QString::number(page));
     url += "?" + query.toString();
     return url;
+}
+
+QString LocalMoneroApi::getBaseUrl() {
+    if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
+        return "http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion/api/v1";
+    }
+
+    if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
+        return "http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p/api/v1";
+    }
+
+    return "https://agoradesk.com/api/v1";
 }
\ No newline at end of file
index 56a8bf0aea54130009aa11959f3331cdc2c8198d..2fe6b98b0b06debdbc142f5bdfbbd186d9a03fd7 100644 (file)
@@ -27,7 +27,7 @@ public:
         QJsonObject obj;
     };
 
-    explicit LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl = "https://agoradesk.com/api/v1");
+    explicit LocalMoneroApi(QObject *parent, UtilsNetworking *network);
 
     void countryCodes();
     void currencies();
@@ -44,9 +44,9 @@ private slots:
 
 private:
     QString getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
+    QString getBaseUrl();
 
     UtilsNetworking *m_network;
-    QString m_baseUrl;
 };
 
 
index 5959556de7021b85bd7037feda13a0b027e9a1b9..efad102434b7599d9eb809bd6d443dc42112e768 100644 (file)
@@ -22,9 +22,9 @@
 
 AppContext::AppContext(Wallet *wallet)
     : wallet(wallet)
-    , nodes(new Nodes(this, this))
+    , nodes(new Nodes(this))
     , networkType(constants::networkType)
-    , m_rpc(new DaemonRpc{this, getNetworkTor(), ""})
+    , m_rpc(new DaemonRpc{this, ""})
 {
     connect(this->wallet, &Wallet::moneySpent,               this, &AppContext::onMoneySpent);
     connect(this->wallet, &Wallet::moneyReceived,            this, &AppContext::onMoneyReceived);
@@ -38,15 +38,14 @@ AppContext::AppContext(Wallet *wallet)
     connect(this->wallet, &Wallet::deviceError,              this, &AppContext::onDeviceError);
     connect(this->wallet, &Wallet::deviceButtonRequest,      this, &AppContext::onDeviceButtonRequest);
     connect(this->wallet, &Wallet::deviceButtonPressed,      this, &AppContext::onDeviceButtonPressed);
-    connect(this->wallet, &Wallet::connectionStatusChanged, [this]{
-        this->nodes->autoConnect();
-    });
     connect(this->wallet, &Wallet::currentSubaddressAccountChanged, [this]{
         this->updateBalance();
     });
 
     connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError);
 
+    nodes->setContext(this);
+
     // Store the wallet every 2 minutes
     m_storeTimer.start(2 * 60 * 1000);
     connect(&m_storeTimer, &QTimer::timeout, [this](){
@@ -188,15 +187,12 @@ void AppContext::setSelectedInputs(const QStringList &selectedInputs) {
     emit selectedInputsChanged(selectedInputs);
 }
 
-void AppContext::onTorSettingsChanged() {
+void AppContext::onProxySettingsChanged() {
     if (Utils::isTorsocks()) {
         return;
     }
 
     this->nodes->connectToNode();
-
-    auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
-    qDebug() << "Changed privacyLevel to " << privacyLevel;
 }
 
 void AppContext::stopTimers() {
index 81a612a11358026bdfe149d4b7d64df9d733a844..4b1198356c32534f21c8b0efa0da25447a61ed87 100644 (file)
@@ -62,7 +62,7 @@ public slots:
     void onDeviceButtonPressed();
     void onDeviceError(const QString &message);
 
-    void onTorSettingsChanged(); // should not be here
+    void onProxySettingsChanged(); // should not be here
 
 private slots:
     void onMoneySpent(const QString &txId, quint64 amount);
index cf9e9abf636200ad0d319a255a3d834713c82b1a..97bcc93a01cddd09eb5cb9802abdf401d734a8df 100644 (file)
@@ -20,6 +20,7 @@
     <file>assets/images/camera_dark.png</file>
     <file>assets/images/camera_white.png</file>
     <file>assets/images/change_account.png</file>
+    <file>assets/images/chipset_32px.png</file>
     <file>assets/images/clock1.png</file>
     <file>assets/images/clock2.png</file>
     <file>assets/images/clock3.png</file>
     <file>assets/images/eye_blind.png</file>
     <file>assets/images/feather.png</file>
     <file>assets/images/file.png</file>
+    <file>assets/images/file_manager_32px.png</file>
     <file>assets/images/gnome-calc.png</file>
+    <file>assets/images/hd_32px.png</file>
     <file>assets/images/history.png</file>
+    <file>assets/images/i2p.png</file>
     <file>assets/images/info.png</file>
     <file>assets/images/info2.svg</file>
+    <file>assets/images/interface_32px.png</file>
     <file>assets/images/key.png</file>
     <file>assets/images/ledger.png</file>
     <file>assets/images/ledger_unpaired.png</file>
@@ -65,6 +70,7 @@
     <file>assets/images/microphone.png</file>
     <file>assets/images/mining.png</file>
     <file>assets/images/network.png</file>
+    <file>assets/images/nw_32px.png</file>
     <file>assets/images/offline_tx.png</file>
     <file>assets/images/person.svg</file>
     <file>assets/images/preferences.png</file>
@@ -81,6 +87,7 @@
     <file>assets/images/securityLevelSafestWhite.png</file>
     <file>assets/images/securityLevelStandardWhite.png</file>
     <file>assets/images/seed.png</file>
+    <file>assets/images/settings_disabled_32px.png</file>
     <file>assets/images/speaker.png</file>
     <file>assets/images/status_connected_fork.png</file>
     <file>assets/images/status_connected.png</file>
     <file>assets/images/status_lagging_fork.png</file>
     <file>assets/images/status_lagging.png</file>
     <file>assets/images/status_lagging.svg</file>
+    <file>assets/images/status_offline.svg</file>
     <file>assets/images/status_waiting.png</file>
     <file>assets/images/status_waiting.svg</file>
     <file>assets/images/tab_addresses.png</file>
     <file>assets/images/unpaid.png</file>
     <file>assets/images/update.png</file>
     <file>assets/images/warning.png</file>
+    <file>assets/images/vrdp_32px.png</file>
     <file>assets/images/xmrig.ico</file>
     <file>assets/images/xmrig.svg</file>
     <file>assets/images/zoom.png</file>
diff --git a/src/assets/images/chipset_32px.png b/src/assets/images/chipset_32px.png
new file mode 100644 (file)
index 0000000..79cb521
Binary files /dev/null and b/src/assets/images/chipset_32px.png differ
diff --git a/src/assets/images/file_manager_32px.png b/src/assets/images/file_manager_32px.png
new file mode 100644 (file)
index 0000000..35edf7b
Binary files /dev/null and b/src/assets/images/file_manager_32px.png differ
diff --git a/src/assets/images/hd_32px.png b/src/assets/images/hd_32px.png
new file mode 100644 (file)
index 0000000..a9c0c14
Binary files /dev/null and b/src/assets/images/hd_32px.png differ
diff --git a/src/assets/images/i2p.png b/src/assets/images/i2p.png
new file mode 100644 (file)
index 0000000..39cd6b4
Binary files /dev/null and b/src/assets/images/i2p.png differ
diff --git a/src/assets/images/interface_32px.png b/src/assets/images/interface_32px.png
new file mode 100644 (file)
index 0000000..1bc056c
Binary files /dev/null and b/src/assets/images/interface_32px.png differ
diff --git a/src/assets/images/nw_32px.png b/src/assets/images/nw_32px.png
new file mode 100644 (file)
index 0000000..2d03afc
Binary files /dev/null and b/src/assets/images/nw_32px.png differ
diff --git a/src/assets/images/settings_disabled_32px.png b/src/assets/images/settings_disabled_32px.png
new file mode 100644 (file)
index 0000000..bd8b5c2
Binary files /dev/null and b/src/assets/images/settings_disabled_32px.png differ
diff --git a/src/assets/images/status_offline.svg b/src/assets/images/status_offline.svg
new file mode 100644 (file)
index 0000000..c8332a8
--- /dev/null
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.0"
+   id="svg7854"
+   height="512"
+   width="512"
+   viewBox="9 9 30 30">
+  <defs
+     id="defs7856">
+    <linearGradient
+       id="linearGradient860">
+      <stop
+         id="stop856"
+         offset="0"
+         style="stop-color:#aaaaaa;stop-opacity:1" />
+      <stop
+         id="stop858"
+         offset="1"
+         style="stop-color:#999999;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient7577">
+      <stop
+         id="stop7579"
+         offset="0"
+         style="stop-color:#000000;stop-opacity:0.3137255;" />
+      <stop
+         id="stop7581"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5167">
+      <stop
+         style="stop-color:#999999;stop-opacity:1"
+         offset="0"
+         id="stop5169" />
+      <stop
+         style="stop-color:#666666;stop-opacity:1"
+         offset="1"
+         id="stop5171" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5184">
+      <stop
+         style="stop-color:white;stop-opacity:1;"
+         offset="0"
+         id="stop5186" />
+      <stop
+         style="stop-color:white;stop-opacity:0;"
+         offset="1"
+         id="stop5188" />
+    </linearGradient>
+    <linearGradient
+       gradientTransform="matrix(2.1074616,0,0,2.1078593,-9.43551,-10.006786)"
+       y2="17.024479"
+       x2="16.657505"
+       y1="10.883683"
+       x1="15.011773"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient8317"
+       xlink:href="#linearGradient7577" />
+    <radialGradient
+       gradientTransform="matrix(1.897257,0,0,1.897615,-6.10046,-6.6146433)"
+       r="7.5896134"
+       fy="20.410854"
+       fx="15.865708"
+       cy="20.410854"
+       cx="15.865708"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient8319"
+       xlink:href="#linearGradient5167" />
+    <radialGradient
+       r="5.96875"
+       fy="11.308558"
+       fx="14.05685"
+       cy="11.308558"
+       cx="14.05685"
+       gradientTransform="matrix(-4.2002315,0.5953403,0.2958442,2.0989386,75.31118,-18.732928)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient8321"
+       xlink:href="#linearGradient5184" />
+    <linearGradient
+       gradientTransform="matrix(1.7591324,0,0,1.7580929,-3.90899,-4.3562887)"
+       y2="26.431587"
+       x2="13.458839"
+       y1="2.0178134"
+       x1="8.9317284"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient8323"
+       xlink:href="#linearGradient860" />
+  </defs>
+  <metadata
+     id="metadata7859">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>Lapo Calamandrei</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:source />
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
+        <dc:title></dc:title>
+        <dc:subject>
+          <rdf:Bag>
+            <rdf:li>record</rdf:li>
+            <rdf:li>media</rdf:li>
+          </rdf:Bag>
+        </dc:subject>
+        <dc:contributor>
+          <cc:Agent>
+            <dc:title>Jakub Steiner</dc:title>
+          </cc:Agent>
+        </dc:contributor>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Reproduction" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Distribution" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/Notice" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/ShareAlike" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/SourceCode" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1">
+    <ellipse
+       ry="14.997972"
+       rx="14.995141"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.4;fill:url(#linearGradient8317);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.11079514;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+       id="path7691"
+       cx="24.00086"
+       cy="24.002029" />
+    <ellipse
+       ry="13.502028"
+       rx="13.49948"
+       cy="24.002029"
+       cx="24.000866"
+       id="path7968"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#radialGradient8319);fill-opacity:1;fill-rule:nonzero;stroke:#555555;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+    <path
+       id="path7970"
+       d="M 25.3861,13.485003 C 20.31979,12.724926 15.45183,15.857848 14,20.764516 c 1.18871,3.18039 3.90811,5.70993 7.46677,6.47724 5.29459,1.141602 10.50115,-2.027543 12.01505,-7.143895 -1.18869,-3.180413 -3.90812,-5.709952 -7.46675,-6.477239 -0.217,-0.04678 -0.41248,-0.103152 -0.62897,-0.135619 z"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.404;fill:url(#radialGradient8321);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.09465754;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+    <ellipse
+       ry="12.509292"
+       rx="12.516688"
+       cy="24.009293"
+       cx="24.000891"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.54494413;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient8323);stroke-width:1.00000215;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+       id="path7972" />
+  </g>
+</svg>
diff --git a/src/assets/images/vrdp_32px.png b/src/assets/images/vrdp_32px.png
new file mode 100644 (file)
index 0000000..505a586
Binary files /dev/null and b/src/assets/images/vrdp_32px.png differ
index e3a16c8816c2854c09e78ce003b908cfbca9be60..1c7f846df24ed35bd45b72a4d0257cb07aef145e 100644 (file)
       "rino4muoj3zk223twblr53hwt4ukqemjqw4fbiwdcrqqevcrclpuzyyd.onion:18081",
       "majesticrepik35vnngouksfl7jiwf6sj7s2doj3bvdffq27tgqoeayd.onion:18089"
     ],
+    "i2p": [
+      "ynk3hrwte23asonojqeskoulek2g2cd6tqg4neghnenfyljrvhga.b32.i2p:18081",
+      "t7lce3j7mwxt32h755u3njjp3k6og3defgueo3iecgsexxnkoezouobc.b32.i2p:18089"
+    ],
     "clearnet": [
       "node.community.rino.io:18081",
       "node.sethforprivacy.com:18089",
index 26b66df7d3c78e6db11e1c4dcb771eb3289ede87..5d05ab73664213eda5bf3977c43e9a0d3f9a9c4f 100644 (file)
@@ -10,6 +10,7 @@
 #include <QLabel>
 #include <QPlainTextEdit>
 #include <QLineEdit>
+#include <QListWidget>
 
 class DoublePixmapLabel : public QLabel
 {
index 548e0dbd154e43eb1a51e1e266b2d574022c75a3..f0dbe1084f22e08bdaa4fe4e6a3611b71d409a9b 100644 (file)
@@ -24,15 +24,6 @@ namespace constants
     // donation constants
     const QString donationAddress = "47ntfT2Z5384zku39pTM6hGcnLnvpRYW2Azm87GiAAH2bcTidtq278TL6HmwyL8yjMeERqGEBs3cqC8vvHPJd1cWQrGC65f";
     const int donationBoundary = 25;
-
-    // websocket constants
-    const QVector<QUrl> websocketUrls = {
-        QUrl(QStringLiteral("wss://ws.featherwallet.org/ws")),
-        QUrl(QStringLiteral("wss://ws.featherwallet.net/ws"))
-    };
-
-    // website constants
-    const QString websiteUrl = "https://featherwallet.org";
 }
 
 #endif //FEATHER_CONSTANTS_H
index f4ee55e50401a1049dfe06b61d7801c7bccad006..f5e030fff84501c4fa4305c7e8be678d8df5f6e1 100644 (file)
        <property name="text">
         <string>TextLabel</string>
        </property>
+       <property name="textInteractionFlags">
+        <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+       </property>
       </widget>
      </item>
      <item row="13" column="1">
index 1f2623331903f466369dbcbe7cb0cc5092bd2840..b1f88232db9d71c74cca8fe28ba0780565cefa53 100644 (file)
@@ -4,64 +4,23 @@
 #include "TorInfoDialog.h"
 #include "ui_TorInfoDialog.h"
 
-#include <QDesktopServices>
-#include <QInputDialog>
-#include <QMessageBox>
-#include <QPushButton>
-#include <QRegularExpressionValidator>
-
-#include "utils/ColorScheme.h"
-#include "utils/Icons.h"
-#include "utils/os/tails.h"
 #include "utils/TorManager.h"
 
-TorInfoDialog::TorInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
+TorInfoDialog::TorInfoDialog(QWidget *parent)
         : QDialog(parent)
         , ui(new Ui::TorInfoDialog)
-        , m_ctx(std::move(ctx))
 {
     ui->setupUi(this);
 
-    if (!torManager()->torConnected && !torManager()->errorMsg.isEmpty()) {
-        ui->message->setText(torManager()->errorMsg);
-    } else {
-        ui->message->hide();
-    }
-
-    if (torManager()->isLocalTor()) {
-        ui->frame_logs->setHidden(true);
-    } else {
-        ui->logs->setPlainText(torManager()->torLogs);
-    }
-
-    initConnectionSettings();
-    initPrivacyLevel();
-    onConnectionStatusChanged(torManager()->torConnected);
+    ui->logs->setPlainText(torManager()->torLogs);
 
-    auto *portValidator = new QRegularExpressionValidator{QRegularExpression("[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]")};
-    ui->line_port->setValidator(portValidator);
+    this->onStatusChanged(torManager()->errorMsg);
+    this->onConnectionStatusChanged(torManager()->torConnected);
 
+    connect(torManager(), &TorManager::statusChanged, this, &TorInfoDialog::onStatusChanged);
     connect(torManager(), &TorManager::connectionStateChanged, this, &TorInfoDialog::onConnectionStatusChanged);
     connect(torManager(), &TorManager::logsUpdated, this, &TorInfoDialog::onLogsUpdated);
 
-    connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &TorInfoDialog::onApplySettings);
-
-    connect(ui->line_host, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
-    connect(ui->line_port, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
-    connect(ui->check_useLocalTor, &QCheckBox::stateChanged, this, &TorInfoDialog::onSettingsChanged);
-    connect(ui->btnGroup_privacyLevel, &QButtonGroup::idToggled, this, &TorInfoDialog::onSettingsChanged);
-
-    ui->label_changes->hide();
-
-    ui->btn_configureInitSync->setIcon(icons()->icon("preferences.svg"));
-    connect(ui->btn_configureInitSync, &QPushButton::clicked, this, &TorInfoDialog::onShowInitSyncConfigDialog);
-
-#if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER)
-    ui->check_useLocalTor->setChecked(true);
-    ui->check_useLocalTor->setEnabled(false);
-    ui->check_useLocalTor->setToolTip("Feather was bundled without Tor");
-#endif
-
     this->adjustSize();
 }
 
@@ -70,104 +29,25 @@ void TorInfoDialog::onLogsUpdated() {
 }
 
 void TorInfoDialog::onConnectionStatusChanged(bool connected) {
-    if (connected) {
+    if (!torManager()->isStarted()) {
+        ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_offline.svg").scaledToWidth(16, Qt::SmoothTransformation));
+        ui->label_testConnectionStatus->setText("Not running");
+    }
+    else if (connected) {
         ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_connected.png").scaledToWidth(16, Qt::SmoothTransformation));
         ui->label_testConnectionStatus->setText("Connected");
-    } else {
-        ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation));
-        ui->label_testConnectionStatus->setText("Disconnected");
-    }
-}
-
-void TorInfoDialog::onApplySettings() {
-    config()->set(Config::socks5Host, ui->line_host->text());
-    config()->set(Config::socks5Port, ui->line_port->text());
-
-    int id = ui->btnGroup_privacyLevel->checkedId();
-    config()->set(Config::torPrivacyLevel, id);
-
-    ui->label_changes->hide();
-
-    bool useLocalTor = ui->check_useLocalTor->isChecked();
-    if (config()->get(Config::useLocalTor).toBool() && useLocalTor && torManager()->isStarted()) {
-        QMessageBox::warning(this, "Warning", "Feather is running the bundled Tor daemon, "
-                                              "but the option to never start a bundled Tor daemon was selected. "
-                                              "A restart is required to apply the setting.");
-    }
-    config()->set(Config::useLocalTor, useLocalTor);
-
-    ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_lagging.png").scaledToWidth(16, Qt::SmoothTransformation));
-    ui->label_testConnectionStatus->setText("Connecting");
-
-    emit torSettingsChanged();
-}
-
-void TorInfoDialog::onSettingsChanged() {
-    ui->label_changes->show();
-}
-
-void TorInfoDialog::initConnectionSettings() {
-    bool localTor = torManager()->isLocalTor();
-    ui->label_connectionSettingsMessage->setVisible(!localTor);
-    ui->frame_connectionSettings->setVisible(localTor);
-
-    ui->line_host->setText(config()->get(Config::socks5Host).toString());
-    ui->line_port->setText(config()->get(Config::socks5Port).toString());
-
-    ui->check_useLocalTor->setChecked(config()->get(Config::useLocalTor).toBool());
-}
-
-void TorInfoDialog::initPrivacyLevel() {
-    ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptNode,  Config::allTorExceptNode);
-    ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptInitSync, Config::allTorExceptInitSync);
-    ui->btnGroup_privacyLevel->setId(ui->radio_allTor, Config::allTor);
-
-    int privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
-    auto button = ui->btnGroup_privacyLevel->button(privacyLevel);
-    if (button) {
-        button->setChecked(true);
-    }
-
-    if (m_ctx->nodes->connection().isLocal()) {
-        ui->label_notice->setText("You are connected to a local node. Traffic to node is not routed over Tor.");
-    }
-    else if (Utils::isTorsocks()) {
-        ui->label_notice->setText("Feather was started with torsocks, all traffic is routed over Tor");
-    }
-    else if (WhonixOS::detect()) {
-        ui->label_notice->setText("Feather is running on Whonix, all traffic is routed over Tor");
-    }
-    else if (TailsOS::detect()) {
-        ui->label_notice->setText("Feather is running on Tails, all traffic is routed over Tor");
     }
     else {
-        ui->frame_notice->hide();
+        ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation));
+        ui->label_testConnectionStatus->setText("Disconnected");
     }
-
-    bool dark = ColorScheme::darkScheme;
-    QPixmap iconNoTor(dark ? ":/assets/images/securityLevelStandardWhite.png" : ":/assets/images/securityLevelStandard.png");
-    QPixmap iconNoSync(dark ? ":/assets/images/securityLevelSaferWhite.png" : ":/assets/images/securityLevelSafer.png");
-    QPixmap iconAllTor(dark ? ":/assets/images/securityLevelSafestWhite.png" : ":/assets/images/securityLevelSafest.png");
-    ui->icon_noTor->setPixmap(iconNoTor.scaledToHeight(16, Qt::SmoothTransformation));
-    ui->icon_noSync->setPixmap(iconNoSync.scaledToHeight(16, Qt::SmoothTransformation));
-    ui->icon_allTor->setPixmap(iconAllTor.scaledToHeight(16, Qt::SmoothTransformation));
 }
 
-void TorInfoDialog::onStopTor() {
-    torManager()->stop();
-}
-
-void TorInfoDialog::onShowInitSyncConfigDialog() {
-
-    int threshold = config()->get(Config::initSyncThreshold).toInt();
+void TorInfoDialog::onStatusChanged(const QString &msg) {
+    ui->message->setText(msg);
 
-    bool ok;
-    int newThreshold = QInputDialog::getInt(this, "Sync threshold",
-                                            "Synchronize over clearnet if wallet is behind more than x blocks: ",
-                                            threshold, 0, 10000, 10, &ok);
-
-    if (ok) {
-        config()->set(Config::initSyncThreshold, newThreshold);
+    if (msg.isEmpty()) {
+        ui->message->hide();
     }
 }
 
index 617ba3d655ab85c3c6d79f02d6b4efe0281ef727..5e0705847cbddacfba3bfbb9304935d7ae2f31e6 100644 (file)
@@ -6,8 +6,6 @@
 
 #include <QDialog>
 
-#include "appcontext.h"
-
 namespace Ui {
     class TorInfoDialog;
 }
@@ -17,7 +15,7 @@ class TorInfoDialog : public QDialog
     Q_OBJECT
 
 public:
-    explicit TorInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
+    explicit TorInfoDialog(QWidget *parent = nullptr);
     ~TorInfoDialog() override;
 
 public slots:
@@ -25,20 +23,10 @@ public slots:
 
 private slots:
     void onConnectionStatusChanged(bool connected);
-    void onApplySettings();
-    void onSettingsChanged();
-    void onStopTor();
-    void onShowInitSyncConfigDialog();
-
-signals:
-    void torSettingsChanged();
+    void onStatusChanged(const QString &msg = "");
 
 private:
-    void initConnectionSettings();
-    void initPrivacyLevel();
-
     QScopedPointer<Ui::TorInfoDialog> ui;
-    QSharedPointer<AppContext> m_ctx;
 };
 
 
index ffce23df8efd6e24b2691c2c36b8894c2165c640..6cfb9590af6d2bb0da278dadeb054929852dc9b0 100644 (file)
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>703</width>
-    <height>804</height>
+    <width>708</width>
+    <height>496</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Tor settings</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QGroupBox" name="group_connectionSettings">
-     <property name="title">
-      <string>Connection settings</string>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout_3">
-      <item>
-       <widget class="QFrame" name="frame_connectionSettings">
-        <property name="frameShape">
-         <enum>QFrame::NoFrame</enum>
-        </property>
-        <property name="frameShadow">
-         <enum>QFrame::Raised</enum>
-        </property>
-        <layout class="QVBoxLayout" name="verticalLayout_5">
-         <property name="leftMargin">
-          <number>0</number>
-         </property>
-         <property name="topMargin">
-          <number>0</number>
-         </property>
-         <property name="rightMargin">
-          <number>0</number>
-         </property>
-         <property name="bottomMargin">
-          <number>0</number>
-         </property>
-         <item>
-          <layout class="QHBoxLayout" name="horizontalLayout_7">
-           <item>
-            <widget class="QLabel" name="label">
-             <property name="text">
-              <string>Host</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLineEdit" name="line_host">
-             <property name="text">
-              <string>127.0.0.1</string>
-             </property>
-             <property name="placeholderText">
-              <string>127.0.0.1</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLabel" name="label_3">
-             <property name="text">
-              <string>Port</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLineEdit" name="line_port">
-             <property name="text">
-              <string>9050</string>
-             </property>
-             <property name="placeholderText">
-              <string>9050</string>
-             </property>
-            </widget>
-           </item>
-          </layout>
-         </item>
-        </layout>
-       </widget>
-      </item>
-      <item>
-       <widget class="QLabel" name="label_connectionSettingsMessage">
-        <property name="text">
-         <string>Tor daemon is managed by Feather.</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QCheckBox" name="check_useLocalTor">
-        <property name="text">
-         <string>Never start bundled Tor (requires local Tor daemon)</string>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="group_privacyLevel">
-     <property name="title">
-      <string>Privacy Level</string>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout_2">
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_4">
-        <item>
-         <widget class="QLabel" name="icon_noTor">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>icon</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QRadioButton" name="radio_allTorExceptNode">
-          <property name="text">
-           <string>Route all traffic over Tor, except traffic to node</string>
-          </property>
-          <attribute name="buttonGroup">
-           <string notr="true">btnGroup_privacyLevel</string>
-          </attribute>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_5">
-        <item>
-         <widget class="QLabel" name="icon_noSync">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>icon</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QRadioButton" name="radio_allTorExceptInitSync">
-          <property name="text">
-           <string>Route all traffic over Tor, except initial wallet synchronization</string>
-          </property>
-          <attribute name="buttonGroup">
-           <string notr="true">btnGroup_privacyLevel</string>
-          </attribute>
-         </widget>
-        </item>
-        <item>
-         <widget class="QPushButton" name="btn_configureInitSync">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string/>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_6">
-        <item>
-         <widget class="QLabel" name="icon_allTor">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>icon</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QRadioButton" name="radio_allTor">
-          <property name="text">
-           <string>Route all traffic over Tor</string>
-          </property>
-          <attribute name="buttonGroup">
-           <string notr="true">btnGroup_privacyLevel</string>
-          </attribute>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
-       <widget class="QFrame" name="frame_notice">
-        <property name="frameShape">
-         <enum>QFrame::StyledPanel</enum>
-        </property>
-        <property name="frameShadow">
-         <enum>QFrame::Raised</enum>
-        </property>
-        <layout class="QHBoxLayout" name="horizontalLayout">
-         <item>
-          <widget class="QLabel" name="label_notice">
-           <property name="text">
-            <string>notice</string>
-           </property>
-           <property name="wordWrap">
-            <bool>true</bool>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
    <item>
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
           </property>
          </widget>
         </item>
-        <item>
-         <widget class="QLabel" name="label_changes">
-          <property name="text">
-           <string>(changes not applied)</string>
-          </property>
-         </widget>
-        </item>
         <item>
          <spacer name="horizontalSpacer">
           <property name="orientation">
       </property>
       <item>
        <widget class="QGroupBox" name="groupBox_2">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
         <property name="title">
          <string>Logs</string>
         </property>
           <number>0</number>
          </property>
          <property name="topMargin">
-          <number>0</number>
+          <number>7</number>
          </property>
          <property name="rightMargin">
           <number>0</number>
      </layout>
     </widget>
    </item>
-   <item>
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>0</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
    <item>
     <widget class="QDialogButtonBox" name="buttonBox">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
      <property name="standardButtons">
-      <set>QDialogButtonBox::Apply|QDialogButtonBox::Close</set>
+      <set>QDialogButtonBox::Close</set>
      </property>
     </widget>
    </item>
index b37daa8e9e6becaaf395ca6be9064380a1af09da..9710449dd00123554b535322200433e388daa0da 100644 (file)
@@ -16,7 +16,7 @@ TxBroadcastDialog::TxBroadcastDialog(QWidget *parent, QSharedPointer<AppContext>
     ui->setupUi(this);
 
     auto node = m_ctx->nodes->connection();
-    m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress());
+    m_rpc = new DaemonRpc(this, node.toAddress());
 
     connect(ui->btn_Broadcast, &QPushButton::clicked, this, &TxBroadcastDialog::broadcastTx);
     connect(ui->btn_Close, &QPushButton::clicked, this, &TxBroadcastDialog::reject);
@@ -35,12 +35,6 @@ void TxBroadcastDialog::broadcastTx() {
 
     FeatherNode node = ui->radio_useCustom->isChecked() ? FeatherNode(ui->customNode->text()) : m_ctx->nodes->connection();
 
-    if (node.isLocal()) {
-        m_rpc->setNetwork(getNetworkClearnet());
-    } else {
-        m_rpc->setNetwork(getNetworkTor());
-    }
-
     m_rpc->setDaemonAddress(node.toURL());
     m_rpc->sendRawTransaction(tx);
 }
@@ -56,6 +50,7 @@ void TxBroadcastDialog::onApiResponse(const DaemonRpc::DaemonResponse &resp) {
     }
 
     this->accept();
+
     QMessageBox::information(this, "Transaction broadcast", "Transaction submitted successfully.\n\n"
                                                             "If the transaction belongs to this wallet it may take several minutes before it shows up in the history tab.");
 }
index 3fd9ecbbc6d7c572da24c8993039de8c71ee09df..e35dad7b84e6ea5124b0267fd5b3c06401849182 100644 (file)
@@ -51,16 +51,22 @@ UpdateDialog::UpdateDialog(QWidget *parent, QSharedPointer<Updater> updater)
 }
 
 void UpdateDialog::checkForUpdates() {
-    ui->label_header->setText("Checking for updates...");
-    ui->label_body->setText("...");
+    ui->label_header->setText("Checking for updates");
+    ui->label_body->setText("..");
+    connect(&m_waitingTimer, &QTimer::timeout, [this]{
+       ui->label_body->setText(ui->label_body->text() + ".");
+    });
+    m_waitingTimer.start(500);
     m_updater->checkForUpdates();
 }
 
 void UpdateDialog::noUpdateAvailable() {
+    m_waitingTimer.stop();
     this->setStatus("Feather is up-to-date.", true);
 }
 
 void UpdateDialog::updateAvailable() {
+    m_waitingTimer.stop();
     ui->frame->show();
     ui->btn_installUpdate->hide();
     ui->btn_restart->hide();
@@ -70,6 +76,7 @@ void UpdateDialog::updateAvailable() {
 }
 
 void UpdateDialog::onUpdateCheckFailed(const QString &errorMsg) {
+    m_waitingTimer.stop();
     this->setStatus(QString("Failed to check for updates: %1").arg(errorMsg), false);
 }
 
@@ -78,7 +85,7 @@ void UpdateDialog::onDownloadClicked() {
     ui->btn_download->hide();
     ui->progressBar->show();
 
-    UtilsNetworking network{getNetworkTor()};
+    UtilsNetworking network{this};
 
     m_reply = network.get(m_updater->downloadUrl);
     connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress);
index 53cb07b7b0dba867128fa820f9d363087980a134..37791313a3d26706ed3538fe995ee9d412038162 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <QDialog>
 #include <QNetworkReply>
+#include <QTimer>
 
 #include "utils/Updater.h"
 
@@ -45,11 +46,12 @@ private:
     QSharedPointer<Updater> m_updater;
 
     QString m_downloadUrl;
-
     QString m_updatePath;
 
     std::string m_updateZipArchive;
 
+    QTimer m_waitingTimer;
+
     QNetworkReply *m_reply = nullptr;
 };
 
index 367b3f53f3718e72d20abcc5c1bd217c9bbc11bd..8f39957a94a740a51a708c23d63005f8b02f7b83 100644 (file)
@@ -11,6 +11,7 @@
 #include "constants.h"
 #include "MainWindow.h"
 #include "utils/EventFilter.h"
+#include "utils/os/Prestium.h"
 #include "WindowManager.h"
 #include "config.h"
 
@@ -40,10 +41,12 @@ void signal_handler(int signum) {
     std::cout << keyStream.str();
 
     // Write stack trace to disk
-    QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
-    std::ofstream out(crashLogPath.toStdString());
-    out << keyStream.str();
-    out.close();
+    if (config()->get(Config::writeStackTraceToDisk).toBool()) {
+        QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
+        std::ofstream out(crashLogPath.toStdString());
+        out << keyStream.str();
+        out.close();
+    }
 
     // Make a last ditch attempt to restart the application
     QProcess::startDetached(qApp->arguments()[0], qApp->arguments());
@@ -91,12 +94,6 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
     QCommandLineOption useLocalTorOption(QStringList() << "use-local-tor", "Use system wide installed Tor instead of the bundled.");
     parser.addOption(useLocalTorOption);
 
-    QCommandLineOption torHostOption(QStringList() << "tor-host", "Address of running Tor instance.", "torHost");
-    parser.addOption(torHostOption);
-
-    QCommandLineOption torPortOption(QStringList() << "tor-port", "Port of running Tor instance.", "torPort");
-    parser.addOption(torPortOption);
-
     QCommandLineOption quietModeOption(QStringList() << "quiet", "Limit console output");
     parser.addOption(quietModeOption);
 
@@ -164,16 +161,17 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
 
     bool logLevelFromEnv;
     int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv);
-    if (!logLevelFromEnv) {
-        logLevel = 0;
+    if (logLevelFromEnv) {
+        config()->set(Config::logLevel, logLevel);
+    } else {
+        logLevel = config()->get(Config::logLevel).toInt();
     }
-    config()->set(Config::logLevel, logLevel);
 
     if (parser.isSet("quiet") || config()->get(Config::disableLogging).toBool()) {
         qWarning() << "Logging is disabled";
         WalletManager::instance()->setLogLevel(-1);
     }
-    else if (logLevelFromEnv && logLevel >= 0 && logLevel <= Monero::WalletManagerFactory::LogLevel_Max) {
+    else if (logLevel >= 0 && logLevel <= Monero::WalletManagerFactory::LogLevel_Max) {
         Monero::WalletManagerFactory::setLogLevel(logLevel);
     }
 
@@ -186,11 +184,12 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
     if (!QDir().mkpath(walletDir))
         qCritical() << "Unable to create dir: " << walletDir;
 
-    // Setup Tor config
-    if (parser.isSet("tor-host"))
-        config()->set(Config::socks5Host, parser.value("tor-host"));
-    if (parser.isSet("tor-port"))
-        config()->set(Config::socks5Port, parser.value("tor-port"));
+    // Prestium initial config
+    if (config()->get(Config::firstRun).toBool() && Prestium::detect()) {
+        config()->set(Config::proxy, Config::Proxy::i2p);
+        config()->set(Config::socks5Port, 4448);
+    }
+
     if (parser.isSet("use-local-tor"))
         config()->set(Config::useLocalTor, true);
 
index f0cb5ae5eef81d74672e8379cfbc8b03bbf0c9b1..7d084a6ad046ee7211682121fd6f9e3939a769d6 100644 (file)
@@ -90,13 +90,13 @@ QVariant BountiesModel::headerData(int section, Qt::Orientation orientation, int
     {
         switch(section) {
             case Votes:
-                return QString("🡅");
+                return QString(" ðŸ¡… ");
             case Title:
                 return QString("Title");
             case Status:
-                return QString("Status");
+                return QString(" Status ");
             case Bounty:
-                return QString("Bounty");
+                return QString(" Bounty ");
             default:
                 return QVariant();
         }
index 6428cacd6226f2a0a16c955c9168fe1e96a67faa..1526444f1049893886d2025d34711c4ea4b760af 100644 (file)
@@ -9,8 +9,8 @@
 NodeModel::NodeModel(int nodeSource, QObject *parent)
         : QAbstractTableModel(parent)
         , m_nodeSource(nodeSource)
-        , m_offline(icons()->icon("expired_icon.png"))
-        , m_online(icons()->icon("confirmed_icon.png"))
+        , m_offline(icons()->icon("status_offline.svg"))
+        , m_online(icons()->icon("status_connected.svg"))
 {
 }
 
@@ -60,8 +60,9 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
     else if (role == Qt::DecorationRole) {
         switch (index.column()) {
             case NodeModel::URL: {
-                if(m_nodeSource == NodeSource::websocket)
+                if (m_nodeSource == NodeSource::websocket && !config()->get(Config::offlineMode).toBool()) {
                     return QVariant(node.online ? m_online : m_offline);
+                }
                 return QVariant();
             }
             default: {
@@ -72,6 +73,8 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
     else if (role == Qt::ToolTipRole) {
         switch (index.column()) {
             case NodeModel::URL: {
+                if (node.isConnecting)
+                    return QString("Feather is connecting to this node.");
                 if (node.isActive)
                     return QString("Feather is connected to this node.");
             }
@@ -95,7 +98,7 @@ QVariant NodeModel::headerData(int section, Qt::Orientation orientation, int rol
             case NodeModel::URL:
                 return QString("Node");
             case NodeModel::Height:
-                return QString("Height");
+                return QString("Height ");
             default:
                 return QVariant();
         }
index 5326c568699e1b1a92ac0f89c936fbe31c3e5944..036547752cbdb0d8fac5c50c5c4a67760234b86a 100644 (file)
@@ -86,7 +86,7 @@ QVariant RedditModel::headerData(int section, Qt::Orientation orientation, int r
             case Author:
                 return QString("Author");
             case Comments:
-                return QString("Comments");
+                return QString(" Comments ");
             default:
                 return QVariant();
         }
index 1018727133fcfe166862b7322b3b93c32522256f..26315b10dc1736437b00059eef77aac7090a4ae7 100644 (file)
@@ -5,21 +5,26 @@
 
 #include <QCoreApplication>
 #include <QNetworkProxy>
+#include <QRegularExpression>
+#include <QUrl>
 
-QNetworkAccessManager *g_networkManagerTor = nullptr;
+#include "utils/config.h"
+#include "utils/Utils.h"
+
+QNetworkAccessManager *g_networkManagerSocks5 = nullptr;
 QNetworkAccessManager *g_networkManagerClearnet = nullptr;
 
-QNetworkAccessManager* getNetworkTor()
+QNetworkAccessManager* getNetworkSocks5()
 {
-    if (!g_networkManagerTor) {
-        g_networkManagerTor = new QNetworkAccessManager(QCoreApplication::instance());
+    if (!g_networkManagerSocks5) {
+        g_networkManagerSocks5 = new QNetworkAccessManager(QCoreApplication::instance());
         QNetworkProxy proxy;
         proxy.setType(QNetworkProxy::Socks5Proxy);
         proxy.setHostName("127.0.0.1");
         proxy.setPort(9050);
-        g_networkManagerTor->setProxy(proxy);
+        g_networkManagerSocks5->setProxy(proxy);
     }
-    return g_networkManagerTor;
+    return g_networkManagerSocks5;
 }
 
 QNetworkAccessManager* getNetworkClearnet()
@@ -30,8 +35,17 @@ QNetworkAccessManager* getNetworkClearnet()
     return g_networkManagerClearnet;
 }
 
-//void setTorProxy(const QNetworkProxy &proxy)
-//{
-//    QNetworkAccessManager *network = getNetworkTor();
-//    network->setProxy(proxy);
-//}
\ No newline at end of file
+
+QNetworkAccessManager* getNetwork(const QString &address)
+{
+    if (config()->get(Config::proxy).toInt() == Config::Proxy::None) {
+        return getNetworkClearnet();
+    }
+
+    // Ignore proxy rules for local addresses
+    if (!address.isEmpty() && Utils::isLocalUrl(QUrl(address))) {
+        return getNetworkClearnet();
+    }
+
+    return getNetworkSocks5();
+}
\ No newline at end of file
index 7bed7faea7c13a6eb8cc0c4bb544941088ed8cb1..be285abc805b2632a6e987e3c63809ea08725f0c 100644 (file)
@@ -6,9 +6,9 @@
 
 #include <QNetworkAccessManager>
 
-QNetworkAccessManager* getNetworkTor();
+QNetworkAccessManager* getNetworkSocks5();
 QNetworkAccessManager* getNetworkClearnet();
 
-//void setTorProxy(const QNetworkProxy &proxy);
+QNetworkAccessManager* getNetwork(const QString &address = "");
 
 #endif //FEATHER_NETWORKMANAGER_H
index 9ebbef59e8e483c9f19a4a57bf03442c862d23a6..830dbd6beb37a186fd0de90bc2439f45ea2d9451 100644 (file)
@@ -42,6 +42,8 @@ void TorManager::init() {
     if (m_localTor && (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)) {
         m_process.kill();
     }
+
+    featherTorPort = config()->get(Config::torManagedPort).toString().toUShort();
 }
 
 void TorManager::stop() {
@@ -58,12 +60,12 @@ void TorManager::start() {
 
     auto state = m_process.state();
     if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting) {
-        this->errorMsg = "Can't start Tor, already running or starting";
+        this->setErrorMessage("Can't start Tor, already running or starting");
         return;
     }
 
     if (Utils::portOpen(featherTorHost, featherTorPort)) {
-        this->errorMsg = QString("Unable to start Tor on %1:%2. Port already in use.").arg(featherTorHost, QString::number(featherTorPort));
+        this->setErrorMessage(QString("Unable to start Tor on %1:%2. Port already in use.").arg(featherTorHost, QString::number(featherTorPort)));
         return;
     }
 
@@ -71,7 +73,7 @@ void TorManager::start() {
 
     m_restarts += 1;
     if (m_restarts > 4) {
-        this->errorMsg = "Tor failed to start: maximum retries exceeded";
+        this->setErrorMessage("Tor failed to start: maximum retries exceeded");
         return;
     }
 
@@ -107,6 +109,10 @@ void TorManager::checkConnection() {
         this->setConnectionState(code == 0);
     }
 
+    else if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
+        this->setConnectionState(false);
+    }
+
     else if (m_localTor) {
         QString host = config()->get(Config::socks5Host).toString();
         quint16 port = config()->get(Config::socks5Port).toString().toUShort();
@@ -125,6 +131,7 @@ void TorManager::setConnectionState(bool connected) {
 
 void TorManager::stateChanged(QProcess::ProcessState state) {
     if (state == QProcess::ProcessState::Running) {
+        this->setErrorMessage("");
         qWarning() << "Tor started, awaiting bootstrap";
     }
     else if (state == QProcess::ProcessState::NotRunning) {
@@ -155,7 +162,7 @@ void TorManager::handleProcessError(QProcess::ProcessError error) {
     if (error == QProcess::ProcessError::Crashed)
         qWarning() << "Tor crashed or killed";
     else if (error == QProcess::ProcessError::FailedToStart) {
-        this->errorMsg = "Tor binary failed to start: " + this->torPath;
+        this->setErrorMessage("Tor binary failed to start: " + this->torPath);
         this->m_stopRetries = true;
     }
 }
@@ -236,6 +243,11 @@ bool TorManager::shouldStartTorDaemon() {
     return false;
 #endif
 
+    // Don't start a Tor daemon if our proxy config isn't set to Tor
+    if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
+        return false;
+    }
+
     // Don't start a Tor daemon if --use-local-tor is specified
     if (config()->get(Config::useLocalTor).toBool()) {
         return false;
@@ -250,7 +262,7 @@ bool TorManager::shouldStartTorDaemon() {
     if (!unpacked) {
         // Don't try to start a Tor daemon if unpacking failed
         qWarning() << "Error unpacking embedded Tor. Assuming --use-local-tor";
-        this->errorMsg = "Error unpacking embedded Tor. Assuming --use-local-tor";
+        this->setErrorMessage("Error unpacking embedded Tor. Assuming --use-local-tor");
         return false;
     }
 
@@ -280,6 +292,11 @@ SemanticVersion TorManager::getVersion(const QString &fileName) {
     return SemanticVersion::fromString(output);
 }
 
+void TorManager::setErrorMessage(const QString &msg) {
+    this->errorMsg = msg;
+    emit statusChanged(msg);
+}
+
 TorManager* TorManager::instance()
 {
     if (!m_instance) {
index 038ba9fc09e8f140a5d4f2c5e8a53130e92df987..16bf76c0f305674c0d844405039dd36482e7bfb2 100644 (file)
@@ -43,7 +43,7 @@ public:
 
 signals:
     void connectionStateChanged(bool connected);
-    void startupFailure(QString reason);
+    void statusChanged(QString reason);
     void logsUpdated();
 
 private slots:
@@ -55,6 +55,7 @@ private slots:
 private:
     bool shouldStartTorDaemon();
     void setConnectionState(bool connected);
+    void setErrorMessage(const QString &msg);
 
     static QPointer<TorManager> m_instance;
 
index 4e12c0d303dabe9305e1308f6ba00b4f5b6eb4d4..faf3732780cd8749e4a2716cacd22a52acfe1e27 100644 (file)
@@ -4,10 +4,11 @@
 #include "Updater.h"
 
 #include <common/util.h>
+#undef config
 #include <openpgp/hash.h>
 
+#include "utils/config.h"
 #include "config-feather.h"
-#include "constants.h"
 #include "Utils.h"
 #include "utils/AsyncTask.h"
 #include "utils/networking.h"
@@ -22,8 +23,12 @@ Updater::Updater(QObject *parent) :
 }
 
 void Updater::checkForUpdates() {
-    UtilsNetworking network{getNetworkTor()};
-    QNetworkReply *reply = network.getJson("https://featherwallet.org/updates.json");
+    UtilsNetworking network{this};
+    QNetworkReply *reply = network.getJson(QString("%1/updates.json").arg(this->getWebsiteUrl()));
+    if (!reply) {
+        emit updateCheckFailed("Can't check for websites: offline mode enabled");
+        return;
+    }
 
     connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onUpdateCheckResponse, this, reply));
 }
@@ -77,9 +82,10 @@ void Updater::wsUpdatesReceived(const QJsonObject &updates) {
 
     // Hooray! New update available
 
-    QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(constants::websiteUrl, newVersion);
+    QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(this->getWebsiteUrl(), newVersion);
+    qDebug() << hashesUrl;
 
-    UtilsNetworking network{getNetworkTor()};
+    UtilsNetworking network{this};
     QNetworkReply *reply = network.get(hashesUrl);
 
     connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onSignedHashesReceived, this, reply, platformTag, newVersion));
@@ -115,7 +121,7 @@ void Updater::onSignedHashesReceived(QNetworkReply *reply, const QString &platfo
     this->state = Updater::State::UPDATE_AVAILABLE;
     this->version = version;
     this->binaryFilename = binaryFilename;
-    this->downloadUrl = QString("https://featherwallet.org/files/releases/%1/%2").arg(platformTag, binaryFilename);
+    this->downloadUrl = QString("%1/files/releases/%2/%3").arg(this->getWebsiteUrl(), platformTag, binaryFilename);
     this->hash = hash;
     this->signer = signer;
     this->platformTag = platformTag;
@@ -154,6 +160,18 @@ QString Updater::getPlatformTag() {
     return "";
 }
 
+QString Updater::getWebsiteUrl() {
+        if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
+            return "http://featherdvtpi7ckdbkb2yxjfwx3oyvr3xjz3oo4rszylfzjdg6pbm3id.onion";
+        }
+        else if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
+            return "http://rwzulgcql2y3n6os2jhmhg6un2m33rylazfnzhf56likav47aylq.b32.i2p";
+        }
+        else {
+            return "https://featherwallet.org";
+        }
+}
+
 QByteArray Updater::verifyParseSignedHashes(
         const QByteArray &armoredSignedHashes,
         const QString &binaryFilename,
index d280520ff99a1e92b181fd5be5c295ea49b3f5e1..634ccc6c13a7bf098fc70ac92ceda8af8d1f3029 100644 (file)
@@ -50,6 +50,7 @@ private:
     QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const;
     void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version);
     QString getPlatformTag();
+    QString getWebsiteUrl();
 
 private:
     std::vector<openpgp::public_key_block> m_maintainers;
index 6555d121f2a24a5250a2abed8668bdfa966645b7..b7f454ae01d81c4bbf8f5d77f9519c9948b82540 100644 (file)
@@ -464,9 +464,6 @@ void externalLinkWarning(QWidget *parent, const QString &url){
 
     QString body = QString("You are about to open the following link:\n\n%1").arg(url);
 
-    if (!(TailsOS::detect() || WhonixOS::detect()))
-        body += "\n\nYou will NOT be using Tor.";
-
     QMessageBox linkWarning(parent);
     linkWarning.setWindowTitle("External link warning");
     linkWarning.setText(body);
@@ -574,4 +571,9 @@ QFont relativeFont(int delta) {
     font.setPointSize(font.pointSize() + delta);
     return font;
 }
+
+bool isLocalUrl(const QUrl &url) {
+    QRegularExpression localNetwork(R"((^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.))");
+    return (localNetwork.match(url.host()).hasMatch() || url.host() == "localhost");
+}
 }
index 0739bf4ef5d568d2d98cb263646f153543232c65..dfbb50e0c349e3f00aa14d4487c2e300d32d0ca3 100644 (file)
@@ -66,6 +66,8 @@ namespace Utils
     QString getAccountName();
     QFont relativeFont(int delta);
 
+    bool isLocalUrl(const QUrl &url);
+
     template<typename QEnum>
     QString QtEnumToString (QEnum value) {
         return QString::fromStdString(std::string(QMetaEnum::fromType<QEnum>().valueToKey(value)));
index aadf34a7445bb2d3a29d1a8c1c7b607e9a2f12f1..39142ed76afbc1ad3d2d6a661c2af962360f94b5 100644 (file)
@@ -6,6 +6,8 @@
 #include <QCoreApplication>
 #include "utils/Utils.h"
 
+#include "utils/config.h"
+
 WebsocketClient::WebsocketClient(QObject *parent)
     : QObject(parent)
 {
@@ -27,7 +29,7 @@ WebsocketClient::WebsocketClient(QObject *parent)
 
     connect(&m_connectionTimeout, &QTimer::timeout, this, &WebsocketClient::onConnectionTimeout);
 
-    m_websocketUrlIndex = QRandomGenerator::global()->bounded(constants::websocketUrls.length());
+    m_websocketUrlIndex = QRandomGenerator::global()->bounded(m_websocketUrls[this->networkType()].length());
     this->nextWebsocketUrl();
 }
 
@@ -42,10 +44,18 @@ void WebsocketClient::start() {
         return;
     }
 
+    if (config()->get(Config::offlineMode).toBool()) {
+        return;
+    }
+
+    if (config()->get(Config::disableWebsocket).toBool()) {
+        return;
+    }
+
     // connect & reconnect on errors/close
-    qDebug() << "WebSocket connect:" << m_url.url();
     auto state = webSocket.state();
     if (state != QAbstractSocket::ConnectedState && state != QAbstractSocket::ConnectingState) {
+        qDebug() << "WebSocket connect:" << m_url.url();
         webSocket.open(m_url);
     }
 }
@@ -90,8 +100,22 @@ void WebsocketClient::onError(QAbstractSocket::SocketError error) {
 }
 
 void WebsocketClient::nextWebsocketUrl() {
-    m_url = constants::websocketUrls[m_websocketUrlIndex];
-    m_websocketUrlIndex = (m_websocketUrlIndex+1)%constants::websocketUrls.length();
+    Config::Proxy networkType = this->networkType();
+    m_websocketUrlIndex = (m_websocketUrlIndex+1)%m_websocketUrls[networkType].length();
+    m_url = m_websocketUrls[networkType][m_websocketUrlIndex];
+}
+
+Config::Proxy WebsocketClient::networkType() {
+    if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
+        // Websocket performance with onion services is abysmal, connect to clearnet server unless instructed otherwise
+        return Config::Proxy::Tor;
+    }
+    else if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
+        return Config::Proxy::i2p;
+    }
+    else {
+        return Config::Proxy::None;
+    }
 }
 
 void WebsocketClient::onConnectionTimeout() {
index 1b88151c0c84f6be4f31c738502e50130f0f81f8..2368253c4daa1f9a9aa2f6903c69028119e7a96e 100644 (file)
@@ -9,6 +9,7 @@
 #include <QTimer>
 #include <QPointer>
 #include "constants.h"
+#include "utils/config.h"
 
 class WebsocketClient : public QObject {
     Q_OBJECT
@@ -20,6 +21,7 @@ public:
     void restart();
     void stop();
     void sendMsg(const QByteArray &data);
+    void nextWebsocketUrl();
 
     QWebSocket webSocket;
 
@@ -33,10 +35,25 @@ private slots:
     void onStateChanged(QAbstractSocket::SocketState state);
     void onbinaryMessageReceived(const QByteArray &message);
     void onError(QAbstractSocket::SocketError error);
-    void nextWebsocketUrl();
     void onConnectionTimeout();
 
 private:
+    Config::Proxy networkType();
+    const QHash<Config::Proxy, QVector<QUrl>> m_websocketUrls = {
+            {Config::Proxy::None, {
+                        QUrl(QStringLiteral("wss://ws.featherwallet.org/ws")),
+                        QUrl(QStringLiteral("wss://ws.featherwallet.net/ws"))
+                }},
+            {Config::Proxy::Tor, {
+                        QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws")),
+                        QUrl(QStringLiteral("ws://an5ecwgzyujqe7jverkp42d22zhvjes2mrhvol6tpqcgfkzwseqrafqd.onion/ws"))
+                }},
+            {Config::Proxy::i2p, {
+                        QUrl(QStringLiteral("ws://hk5smvnkifjcm5346bs6cmnczwbiupr4jyiw3gz5z7ybaigp72fa.b32.i2p/ws")),
+                        QUrl(QStringLiteral("ws://tr7iahturgfii64txw7cjhrfunmpg35w2lmmqmsa6i4jxwi7vplq.b32.i2p/ws"))
+                }}
+    };
+
     QUrl m_url;
     QTimer m_pingTimer;
     QTimer m_connectionTimeout;
index 800957421e1a48536f824f904a5bb98b1f558e03..47c5a1c32be59c1629571de5c8f4d29d1f42d1df 100644 (file)
@@ -68,14 +68,18 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
         {Config::balanceDisplay, {QS("balanceDisplay"), Config::BalanceDisplay::spendablePlusUnconfirmed}},
         {Config::inactivityLockEnabled, {QS("inactivityLockEnabled"), false}},
         {Config::inactivityLockTimeout, {QS("inactivityLockTimeout"), 10}},
+        {Config::lockOnMinimize, {QS("lockOnMinimize"), false}},
         {Config::disableWebsocket, {QS("disableWebsocket"), false}},
         {Config::offlineMode, {QS("offlineMode"), false}},
 
         {Config::multiBroadcast, {QS("multiBroadcast"), true}},
         {Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}},
         {Config::hideBalance, {QS("hideBalance"), false}},
-         {Config::hideNotifications, {QS("hideNotifications"), false}},
-        {Config::disableLogging, {QS("disableLogging"), false}},
+        {Config::hideNotifications, {QS("hideNotifications"), false}},
+        {Config::hideUpdateNotifications, {QS("hideUpdateNotifications"), false}},
+        {Config::disableLogging, {QS("disableLogging"), true}},
+        {Config::writeStackTraceToDisk, {QS("writeStackTraceToDisk"), true}},
+        {Config::writeRecentlyOpenedWallets, {QS("writeRecentlyOpenedWallets"), true}},
 
         {Config::blockExplorer,{QS("blockExplorer"), "exploremonero.com"}},
         {Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}},
@@ -86,11 +90,14 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
         {Config::cryptoSymbols, {QS("cryptoSymbols"), QStringList{"BTC", "ETH", "LTC", "XMR", "ZEC"}}},
 
         // Tor
+        {Config::proxy, {QS("proxy"), Config::Proxy::Tor}},
         {Config::torPrivacyLevel, {QS("torPrivacyLevel"), 1}},
+        {Config::torOnlyAllowOnion, {QS("torOnlyAllowOnion"), false}},
         {Config::socks5Host, {QS("socks5Host"), "127.0.0.1"}},
         {Config::socks5Port, {QS("socks5Port"), "9050"}},
         {Config::socks5User, {QS("socks5User"), ""}}, // Unused
         {Config::socks5Pass, {QS("socks5Pass"), ""}}, // Unused
+        {Config::torManagedPort, {QS("torManagedPort"), "19450"}},
         {Config::useLocalTor, {QS("useLocalTor"), false}},
         {Config::initSyncThreshold, {QS("initSyncThreshold"), 360}}
 };
index baa7f9d2f85ce46e4b5f24439a5ff360bb0ff581..ff7d208f5ef555cfdc4bf1a5067dcbbadde5cbc1 100644 (file)
@@ -24,7 +24,6 @@ public:
         firstRun,
         warnOnStagenet,
         warnOnTestnet,
-        logLevel,
 
         homeWidget,
         donateBeg,
@@ -64,39 +63,61 @@ public:
 
         // Settings
         lastSettingsPage,
-        preferredFiatCurrency,
+
+        // Appearance
         skin,
         amountPrecision,
         dateFormat,
         timeFormat,
         balanceDisplay,
-        inactivityLockEnabled,
-        inactivityLockTimeout,
+        preferredFiatCurrency,
+
+        // Network -> Proxy
+        proxy,
+        socks5Host,
+        socks5Port,
+        socks5User,
+        socks5Pass,
+        useLocalTor, // Prevents Feather from starting bundled Tor daemon
+        torOnlyAllowOnion,
+        torPrivacyLevel, // Tor node network traffic strategy
+        torManagedPort, // Port for managed Tor daemon
+        initSyncThreshold, // Switch to Tor after initial sync threshold blocks
+
+        // Network -> Websocket
         disableWebsocket,
+
+        // Network -> Offline
         offlineMode,
 
-        multiBroadcast,
-        warnOnExternalLink,
-        hideBalance,
+        // Storage -> Logging
+        writeStackTraceToDisk,
         disableLogging,
+        logLevel,
+
+        // Storage -> Misc
+        writeRecentlyOpenedWallets,
+
+        // Display
+        hideBalance,
+        hideUpdateNotifications,
         hideNotifications,
+        warnOnExternalLink,
+        inactivityLockEnabled,
+        inactivityLockTimeout,
+        lockOnMinimize,
+
+        // Transactions
+        multiBroadcast,
 
+        // Misc
         blockExplorer,
         redditFrontend,
         localMoneroFrontend,
-        bountiesFrontend,
+        bountiesFrontend, // unused
 
         fiatSymbols,
         cryptoSymbols,
-
-        // Tor
-        torPrivacyLevel,
-        socks5Host,
-        socks5Port,
-        socks5User,
-        socks5Pass,
-        useLocalTor, // Prevents Feather from starting bundled Tor daemon
-        initSyncThreshold
     };
 
     enum PrivacyLevel {
@@ -116,6 +137,13 @@ public:
         Solo
     };
 
+    enum Proxy {
+        None = 0,
+        Tor,
+        i2p,
+        socks5
+    };
+
     ~Config() override;
     QVariant get(ConfigKey key);
     QString getFileName();
index b80aed9a8df14d49338fb9385d3274ee6d4203f9..8414563499af8f3c496d74899bbbdd0628d5152a 100644 (file)
@@ -3,9 +3,9 @@
 
 #include "daemonrpc.h"
 
-DaemonRpc::DaemonRpc(QObject *parent, QNetworkAccessManager *network, QString daemonAddress)
+DaemonRpc::DaemonRpc(QObject *parent, QString daemonAddress)
         : QObject(parent)
-        , m_network(new UtilsNetworking(network, this))
+        , m_network(new UtilsNetworking(this))
         , m_daemonAddress(std::move(daemonAddress))
 {
 }
@@ -18,7 +18,9 @@ void DaemonRpc::sendRawTransaction(const QString &tx_as_hex, bool do_not_relay,
 
     QString url = QString("%1/send_raw_transaction").arg(m_daemonAddress);
     QNetworkReply *reply = m_network->postJson(url, req);
-    connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::SEND_RAW_TRANSACTION));
+    connect(reply, &QNetworkReply::finished, [this, reply]{
+        onResponse(reply, Endpoint::SEND_RAW_TRANSACTION);
+    });
 }
 
 void DaemonRpc::getTransactions(const QStringList &txs_hashes, bool decode_as_json, bool prune) {
@@ -29,12 +31,19 @@ void DaemonRpc::getTransactions(const QStringList &txs_hashes, bool decode_as_js
 
     QString url = QString("%1/get_transactions").arg(m_daemonAddress);
     QNetworkReply *reply = m_network->postJson(url, req);
-    connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::GET_TRANSACTIONS));
+    connect(reply, &QNetworkReply::finished, [this, reply]{
+        onResponse(reply, Endpoint::GET_TRANSACTIONS);
+    });
 }
 
 void DaemonRpc::onResponse(QNetworkReply *reply, Endpoint endpoint) {
-    const auto ok = reply->error() == QNetworkReply::NoError;
-    const auto err = reply->errorString();
+    if (!reply) {
+        emit ApiResponse(DaemonResponse(false, endpoint, "Offline mode"));
+        return;
+    }
+
+    bool ok = reply->error() == QNetworkReply::NoError;
+    QString err = reply->errorString();
 
     QByteArray data = reply->readAll();
     reply->deleteLater();
@@ -93,7 +102,3 @@ QString DaemonRpc::onSendRawTransactionFailed(const QJsonObject &obj) {
 void DaemonRpc::setDaemonAddress(const QString &daemonAddress) {
     m_daemonAddress = daemonAddress;
 }
-
-void DaemonRpc::setNetwork(QNetworkAccessManager *network) {
-    m_network = new UtilsNetworking(network, this);
-}
\ No newline at end of file
index 01f1a62c1a5c169e9d13770e9a1fd9b6b6f1c9ae..496ce68a7438313303745285b79bbff37bcb2856 100644 (file)
@@ -27,13 +27,12 @@ public:
         QJsonObject obj;
     };
 
-    explicit DaemonRpc(QObject *parent, QNetworkAccessManager *network, QString daemonAddress);
+    explicit DaemonRpc(QObject *parent, QString daemonAddress);
 
     void sendRawTransaction(const QString &tx_as_hex, bool do_not_relay = false, bool do_sanity_checks = true);
     void getTransactions(const QStringList &txs_hashes, bool decode_as_json = false, bool prune = false);
 
     void setDaemonAddress(const QString &daemonAddress);
-    void setNetwork(QNetworkAccessManager *network);
 
 signals:
     void ApiResponse(DaemonResponse resp);
index e2a5cda22e99c693194d77e5b57aa02dd41ba619..4c7293bf4017478b1d4417f981fa8afe79265f84 100644 (file)
@@ -6,11 +6,11 @@
 
 #include "utils/Utils.h"
 #include "utils/networking.h"
+#include "utils/NetworkManager.h"
 #include "config.h"
 
-UtilsNetworking::UtilsNetworking(QNetworkAccessManager *networkAccessManager, QObject *parent)
-    : QObject(parent)
-    , m_networkAccessManager(networkAccessManager) {}
+UtilsNetworking::UtilsNetworking(QObject *parent)
+    : QObject(parent) {}
 
 void UtilsNetworking::setUserAgent(const QString &userAgent) {
     this->m_userAgent = userAgent;
@@ -21,6 +21,8 @@ QNetworkReply* UtilsNetworking::get(const QString &url) {
         return nullptr;
     }
 
+    m_networkAccessManager = getNetwork(url);
+
     QNetworkRequest request;
     request.setUrl(QUrl(url));
     request.setRawHeader("User-Agent", m_userAgent.toUtf8());
@@ -33,6 +35,8 @@ QNetworkReply* UtilsNetworking::getJson(const QString &url) {
         return nullptr;
     }
 
+    m_networkAccessManager = getNetwork(url);
+
     QNetworkRequest request;
     request.setUrl(QUrl(url));
     request.setRawHeader("User-Agent", m_userAgent.toUtf8());
@@ -46,6 +50,8 @@ QNetworkReply* UtilsNetworking::postJson(const QString &url, const QJsonObject &
         return nullptr;
     }
 
+    m_networkAccessManager = getNetwork(url);
+
     QNetworkRequest request;
     request.setUrl(QUrl(url));
     request.setRawHeader("User-Agent", m_userAgent.toUtf8());
index c140cd0b8864e90480c54582ddf6e08ddc3bc683..bc33a0cb04d25d01be74f430f0c05fa148e7f406 100644 (file)
@@ -16,7 +16,7 @@ class UtilsNetworking : public QObject
 Q_OBJECT
 
 public:
-    explicit UtilsNetworking(QNetworkAccessManager *networkAccessManager, QObject *parent = nullptr);
+    explicit UtilsNetworking(QObject *parent = nullptr);
 
     QNetworkReply* get(const QString &url);
     QNetworkReply* getJson(const QString &url);
@@ -24,7 +24,7 @@ public:
     void setUserAgent(const QString &userAgent);
 
 private:
-    QString m_userAgent = "Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0";
+    QString m_userAgent = "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0";
     QNetworkAccessManager *m_networkAccessManager;
 };
 
index a31fb856c2ca6b9e028e166623697184c16fe0db..b1f77278423eecf7cd4531fea31a2f40f85d5414 100644 (file)
@@ -94,18 +94,23 @@ void NodeList::ensureStructure(QJsonObject &obj, NetworkType::Type networkType)
     obj[networkTypeStr] = netTypeObj;
 }
 
-Nodes::Nodes(AppContext *ctx, QObject *parent)
+Nodes::Nodes(QObject *parent)
     : QObject(parent)
     , modelWebsocket(new NodeModel(NodeSource::websocket, this))
     , modelCustom(new NodeModel(NodeSource::custom, this))
-    , m_ctx(ctx)
     , m_connection(FeatherNode())
 {
+    // TODO: This class is in desperate need of refactoring
+
     this->loadConfig();
-    connect(m_ctx, &AppContext::walletRefreshed, this, &Nodes::onWalletRefreshed);
     connect(websocketNotifier(), &WebsocketNotifier::NodesReceived, this, &Nodes::onWSNodesReceived);
 }
 
+void Nodes::setContext(AppContext *ctx) {
+    m_ctx = ctx;
+    connect(m_ctx, &AppContext::walletRefreshed, this, &Nodes::onWalletRefreshed);
+}
+
 void Nodes::loadConfig() {
     QStringList customNodes = m_nodes.getNodes(constants::networkType, NodeList::custom);
     for (const auto &node : customNodes) {
@@ -165,6 +170,9 @@ void Nodes::loadConfig() {
             for (const auto &node : nodes_json[netKey].toObject()["clearnet"].toArray()) {
                 nodes_list.append(node);
             }
+            for (const auto &node : nodes_json[netKey].toObject()["i2p"].toArray()) {
+                nodes_list.append(node);
+            }
 
             for (auto node: nodes_list) {
                 FeatherNode wsNode(node.toString());
@@ -183,30 +191,40 @@ void Nodes::loadConfig() {
 
 void Nodes::connectToNode() {
     // auto connect
-    m_wsExhaustedWarningEmitted = false;
-    m_customExhaustedWarningEmitted = false;
     this->autoConnect(true);
 }
 
 void Nodes::connectToNode(const FeatherNode &node) {
-    if (!node.isValid())
+    if (!m_ctx) {
         return;
+    }
+
+    if (!node.isValid()) {
+        return;
+    }
 
     if (config()->get(Config::offlineMode).toBool()) {
         return;
     }
 
-    qInfo() << QString("Attempting to connect to %1 (%2)").arg(node.toAddress()).arg(node.custom ? "custom" : "ws");
+    if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
+        if (!node.isOnion()) {
+            return;
+        }
+    }
+
+    qInfo() << QString("Attempting to connect to %1 (%2)").arg(node.toAddress(), node.custom ? "custom" : "ws");
 
-    if (!node.url.userName().isEmpty() && !node.url.password().isEmpty())
+    if (!node.url.userName().isEmpty() && !node.url.password().isEmpty()) {
         m_ctx->wallet->setDaemonLogin(node.url.userName(), node.url.password());
+    }
 
-    // Don't use SSL over Tor
-    m_ctx->wallet->setUseSSL(!node.isOnion());
+    // Don't use SSL over Tor/i2p
+    m_ctx->wallet->setUseSSL(!node.isAnonymityNetwork());
 
     QString proxyAddress;
-    if (useTorProxy(node)) {
-        if (!torManager()->isLocalTor()) {
+    if (useSocks5Proxy(node)) {
+        if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
             proxyAddress = QString("%1:%2").arg(torManager()->featherTorHost, QString::number(torManager()->featherTorPort));
         } else {
             proxyAddress = QString("%1:%2").arg(config()->get(Config::socks5Host).toString(),
@@ -225,6 +243,10 @@ void Nodes::connectToNode(const FeatherNode &node) {
 }
 
 void Nodes::autoConnect(bool forceReconnect) {
+    if (!m_ctx) {
+        return;
+    }
+
     // this function is responsible for automatically connecting to a daemon.
     if (m_ctx->wallet == nullptr || !m_enableAutoconnect) {
         return;
@@ -234,7 +256,7 @@ void Nodes::autoConnect(bool forceReconnect) {
     bool wsMode = (this->source() == NodeSource::websocket);
 
     if (wsMode && !m_wsNodesReceived && websocketNodes().count() == 0) {
-        // this situation should rarely occur due to the usage of the websocket node cache on startup.
+        // this situation should rarely onneccur due to the usage of the websocket node cache on startup.
         qInfo() << "Feather is in websocket connection mode but was not able to receive any nodes (yet).";
         return;
     }
@@ -244,7 +266,7 @@ void Nodes::autoConnect(bool forceReconnect) {
             m_recentFailures << m_connection.toAddress();
         }
 
-        // try connect
+        // try connect
         FeatherNode node = this->pickEligibleNode();
         this->connectToNode(node);
         return;
@@ -257,8 +279,6 @@ void Nodes::autoConnect(bool forceReconnect) {
         m_connection.isActive = true;
 
         // reset node exhaustion state
-        m_wsExhaustedWarningEmitted = false;
-        m_customExhaustedWarningEmitted = false;
         m_recentFailures.clear();
     }
 
@@ -374,7 +394,7 @@ void Nodes::setCustomNodes(const QList<FeatherNode> &nodes) {
 }
 
 void Nodes::onWalletRefreshed() {
-    if (config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptInitSync) {
+    if (config()->get(Config::proxy) == Config::Proxy::Tor && config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptInitSync) {
         // Don't reconnect if we're connected to a local node (traffic will not be routed through Tor)
         if (m_connection.isLocal())
             return;
@@ -388,20 +408,29 @@ void Nodes::onWalletRefreshed() {
 }
 
 bool Nodes::useOnionNodes() {
+    if (config()->get(Config::proxy) != Config::Proxy::Tor) {
+        return false;
+    }
+
+    if (config()->get(Config::torOnlyAllowOnion).toBool()) {
+        return true;
+    }
+
     auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
     if (privacyLevel == Config::allTor) {
         return true;
     }
 
     if (privacyLevel == Config::allTorExceptInitSync) {
-        if (m_ctx->refreshed)
+        if (m_ctx && m_ctx->refreshed) {
             return true;
+        }
 
         if (appData()->heights.contains(constants::networkType)) {
             int initSyncThreshold = config()->get(Config::initSyncThreshold).toInt();
             int networkHeight = appData()->heights[constants::networkType];
 
-            if (m_ctx->wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) {
+            if (m_ctx && m_ctx->wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) {
                 return true;
             }
         }
@@ -410,7 +439,15 @@ bool Nodes::useOnionNodes() {
     return false;
 }
 
-bool Nodes::useTorProxy(const FeatherNode &node) {
+bool Nodes::useI2PNodes() {
+    if (config()->get(Config::proxy) == Config::Proxy::i2p) {
+        return true;
+    }
+
+    return false;
+}
+
+bool Nodes::useSocks5Proxy(const FeatherNode &node) {
     if (node.isLocal()) {
         return false;
     }
@@ -423,7 +460,15 @@ bool Nodes::useTorProxy(const FeatherNode &node) {
         return true;
     }
 
-    return this->useOnionNodes();
+    if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor) {
+        return this->useOnionNodes();
+    }
+
+    if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
+        return true;
+    }
+
+    return false;
 }
 
 void Nodes::updateModels() {
@@ -450,28 +495,7 @@ void Nodes::resetLocalState() {
 }
 
 void Nodes::exhausted() {
-    if (this->source() == NodeSource::websocket)
-        this->WSNodeExhaustedWarning();
-    else
-        this->nodeExhaustedWarning();
-}
-
-void Nodes::nodeExhaustedWarning(){
-    if (m_customExhaustedWarningEmitted)
-        return;
-
-    emit nodeExhausted();
-    qWarning() << "Could not find an eligible custom node to connect to.";
-    m_customExhaustedWarningEmitted = true;
-}
-
-void Nodes::WSNodeExhaustedWarning() {
-    if (m_wsExhaustedWarningEmitted)
-        return;
-
-    emit WSNodeExhausted();
-    qWarning() << "Could not find an eligible websocket node to connect to.";
-    m_wsExhaustedWarningEmitted = true;
+    // Do nothing
 }
 
 QList<FeatherNode> Nodes::nodes() {
@@ -485,6 +509,7 @@ QList<FeatherNode> Nodes::customNodes() {
 
 QList<FeatherNode> Nodes::websocketNodes() {
     bool onionNode = this->useOnionNodes();
+    bool i2pNode = this->useI2PNodes();
 
     QList<FeatherNode> nodes;
     for (const auto &node : m_websocketNodes) {
@@ -492,20 +517,24 @@ QList<FeatherNode> Nodes::websocketNodes() {
             continue;
         }
 
+        if (i2pNode && !node.isI2P()) {
+            continue;
+        }
+
         if (!onionNode && node.isOnion()) {
             continue;
         }
 
+        if (!i2pNode && node.isI2P()) {
+            continue;
+        }
+
         nodes.push_back(node);
     }
 
     return nodes;
 }
 
-void Nodes::onTorSettingsChanged() {
-    this->autoConnect(true);
-}
-
 FeatherNode Nodes::connection() {
     return m_connection;
 }
index ebad101b0609bdb628bf950516a8894178be4d2f..ed261232387abbf0fecaf53950fc940284eee35c 100644 (file)
@@ -8,7 +8,6 @@
 #include <QRegularExpression>
 #include <QApplication>
 #include <QtNetwork>
-#include <QNetworkAccessManager>
 #include <QNetworkReply>
 
 #include "model/NodeModel.h"
@@ -83,6 +82,14 @@ struct FeatherNode {
         return url.host().endsWith(".onion");
     }
 
+    bool isI2P() const {
+        return url.host().endsWith(".i2p");
+    }
+
+    bool isAnonymityNetwork() const {
+        return isOnion() || isI2P();
+    };
+
     QString toAddress() const {
         return QString("%1:%2").arg(url.host(), QString::number(url.port()));
     }
@@ -111,7 +118,8 @@ class Nodes : public QObject {
     Q_OBJECT
 
 public:
-    explicit Nodes(AppContext *ctx, QObject *parent = nullptr);
+    explicit Nodes(QObject *parent = nullptr);
+    void setContext(AppContext *ctx);
     void loadConfig();
 
     NodeSource source();
@@ -132,17 +140,11 @@ public slots:
     void setCustomNodes(const QList<FeatherNode>& nodes);
     void autoConnect(bool forceReconnect = false);
 
-    void onTorSettingsChanged();
-
-signals:
-    void WSNodeExhausted();
-    void nodeExhausted();
-
 private slots:
     void onWalletRefreshed();
 
 private:
-    AppContext *m_ctx;
+    AppContext *m_ctx = nullptr;
     QJsonObject m_configJson;
 
     NodeList m_nodes;
@@ -155,20 +157,17 @@ private:
     FeatherNode m_connection;  // current active connection, if any
 
     bool m_wsNodesReceived = false;
-    bool m_wsExhaustedWarningEmitted = true;
-    bool m_customExhaustedWarningEmitted = true;
     bool m_enableAutoconnect = true;
 
     FeatherNode pickEligibleNode();
 
     bool useOnionNodes();
-    bool useTorProxy(const FeatherNode &node);
+    bool useI2PNodes();
+    bool useSocks5Proxy(const FeatherNode &node);
 
     void updateModels();
     void resetLocalState();
     void exhausted();
-    void WSNodeExhaustedWarning();
-    void nodeExhaustedWarning();
     int modeHeight(const QList<FeatherNode> &nodes);
 };
 
diff --git a/src/utils/os/Prestium.cpp b/src/utils/os/Prestium.cpp
new file mode 100644 (file)
index 0000000..f1ef791
--- /dev/null
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "Prestium.h"
+
+#include <QString>
+#include <QSysInfo>
+
+#include "utils/Utils.h"
+
+bool Prestium::detect()
+{
+    // Temporary detect
+    if (Utils::fileExists("/etc/hostname")) {
+        QByteArray data = Utils::fileOpen("/etc/hostname");
+        if (data == "prestium\n") {
+            return true;
+        }
+    }
+
+    return QSysInfo::prettyProductName().contains("Prestium");
+}
\ No newline at end of file
diff --git a/src/utils/os/Prestium.h b/src/utils/os/Prestium.h
new file mode 100644 (file)
index 0000000..879c9a3
--- /dev/null
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef FEATHER_PRESTIUM_H
+#define FEATHER_PRESTIUM_H
+
+
+class Prestium {
+public:
+    static bool detect();
+};
+
+#endif //FEATHER_PRESTIUM_H
index d593879569024db512fc51cb106a427c29177291..6865b13b2c6482a040eabdc1503f3fd82a96b17a 100644 (file)
@@ -25,7 +25,7 @@ LocalMoneroWidget::LocalMoneroWidget(QWidget *parent, QSharedPointer<AppContext>
 
     ui->combo_currency->addItem(config()->get(Config::preferredFiatCurrency).toString());
 
-    m_network = new UtilsNetworking(getNetworkTor(), this);
+    m_network = new UtilsNetworking(this);
     m_api = new LocalMoneroApi(this, m_network);
 
     m_model = new LocalMoneroModel(this);
diff --git a/src/widgets/NetworkProxyWidget.cpp b/src/widgets/NetworkProxyWidget.cpp
new file mode 100644 (file)
index 0000000..9a354e8
--- /dev/null
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "NetworkProxyWidget.h"
+#include "ui_NetworkProxyWidget.h"
+
+#include <QCheckBox>
+#include <QComboBox>
+#include <QWidget>
+
+#include "utils/config.h"
+#include "utils/os/Prestium.h"
+
+NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
+        : QWidget(parent)
+        , ui(new Ui::NetworkProxyWidget)
+        , m_torInfoDialog(new TorInfoDialog(this))
+{
+    ui->setupUi(this);
+
+    ui->comboBox_proxy->setCurrentIndex(config()->get(Config::proxy).toInt());
+    connect(ui->comboBox_proxy, &QComboBox::currentIndexChanged, [this](int index){
+        this->onProxySettingsChanged();
+        ui->frame_proxy->setVisible(index != Config::Proxy::None);
+        ui->groupBox_proxySettings->setTitle(QString("%1 settings").arg(ui->comboBox_proxy->currentText()));
+        ui->frame_tor->setVisible(index == Config::Proxy::Tor);
+        this->updatePort();
+    });
+
+    int proxy = config()->get(Config::proxy).toInt();
+    ui->frame_proxy->setVisible(proxy != Config::Proxy::None);
+    ui->frame_tor->setVisible(proxy == Config::Proxy::Tor);
+    ui->groupBox_proxySettings->setTitle(QString("%1 settings").arg(ui->comboBox_proxy->currentText()));
+
+    // [Host]
+    connect(ui->line_host, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
+
+    // [Port]
+    auto *portValidator = new QRegularExpressionValidator{QRegularExpression("[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]")};
+    ui->line_port->setValidator(portValidator);
+    ui->line_port->setText(config()->get(Config::socks5Port).toString());
+    connect(ui->line_port, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
+
+    // [Tor settings]
+    // [Let Feather start and manage a Tor daemon]
+#if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER)
+    ui->checkBox_torManaged->setChecked(false);
+    ui->checkBox_torManaged->setEnabled(false);
+    ui->checkBox_torManaged->setToolTip("Feather was bundled without Tor");
+#else
+    ui->checkBox_torManaged->setChecked(!config()->get(Config::useLocalTor).toBool());
+    connect(ui->checkBox_torManaged, &QCheckBox::toggled, [this](bool toggled){
+        this->updatePort();
+        this->onProxySettingsChanged();
+        if (!m_disableTorLogs) {
+            ui->frame_torShowLogs->setVisible(toggled);
+        }
+    });
+#endif
+
+    // [Only allow connections to onion services]
+    ui->checkBox_torOnlyAllowOnion->setChecked(config()->get(Config::torOnlyAllowOnion).toBool());
+    connect(ui->checkBox_torOnlyAllowOnion, &QCheckBox::toggled, this, &NetworkProxyWidget::onProxySettingsChanged);
+
+    // [Node traffic]
+    ui->comboBox_torNodeTraffic->setCurrentIndex(config()->get(Config::torPrivacyLevel).toInt());
+    connect(ui->comboBox_torNodeTraffic, &QComboBox::currentIndexChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
+
+    // [Show Tor logs]
+    ui->frame_torShowLogs->setVisible(!config()->get(Config::useLocalTor).toBool());
+#if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER)
+    ui->frame_torShowLogs->setVisible(false);
+#endif
+    connect(ui->btn_torShowLogs, &QPushButton::clicked, [this]{
+        m_torInfoDialog->show();
+    });
+
+    ui->frame_notice->hide();
+}
+
+void NetworkProxyWidget::onProxySettingsChanged() {
+    if (!m_proxySettingsChanged) {
+        emit proxySettingsChanged();
+    }
+
+    m_proxySettingsChanged = true;
+}
+
+void NetworkProxyWidget::updatePort() {
+    int socks5port;
+    switch (ui->comboBox_proxy->currentIndex()) {
+        case Config::Proxy::Tor: {
+            socks5port = 9050;
+            break;
+        }
+        case Config::Proxy::i2p: {
+            if (Prestium::detect()) {
+                socks5port = 4448;
+            } else {
+                socks5port = 4447;
+            }
+            break;
+        }
+        default: {
+            socks5port = 9050;
+        }
+    }
+    ui->line_port->setText(QString::number(socks5port));
+}
+
+void NetworkProxyWidget::setProxySettings() {
+    config()->set(Config::proxy, ui->comboBox_proxy->currentIndex());
+    config()->set(Config::socks5Host, ui->line_host->text());
+    config()->set(Config::socks5Port, ui->line_port->text());
+    config()->set(Config::useLocalTor, !ui->checkBox_torManaged->isChecked());
+    config()->set(Config::torOnlyAllowOnion, ui->checkBox_torOnlyAllowOnion->isChecked());
+    config()->set(Config::torPrivacyLevel, ui->comboBox_torNodeTraffic->currentIndex());
+    m_proxySettingsChanged = false;
+}
+
+void NetworkProxyWidget::setDisableTorLogs() {
+    m_disableTorLogs = true;
+    ui->frame_torShowLogs->hide();
+}
+
+NetworkProxyWidget::~NetworkProxyWidget() = default;
diff --git a/src/widgets/NetworkProxyWidget.h b/src/widgets/NetworkProxyWidget.h
new file mode 100644 (file)
index 0000000..4b1b479
--- /dev/null
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#ifndef FEATHER_NETWORKPROXYWIDGET_H
+#define FEATHER_NETWORKPROXYWIDGET_H
+
+#include <QWidget>
+#include <QTextEdit>
+
+#include "dialog/TorInfoDialog.h"
+
+namespace Ui {
+    class NetworkProxyWidget;
+}
+
+class NetworkProxyWidget : public QWidget
+{
+Q_OBJECT
+
+public:
+    explicit NetworkProxyWidget(QWidget *parent = nullptr);
+    ~NetworkProxyWidget() override;
+
+    void setProxySettings();
+    bool isProxySettingsChanged() {
+        return m_proxySettingsChanged;
+    };
+    void setDisableTorLogs();
+
+signals:
+    void proxySettingsChanged();
+
+private:
+    void onProxySettingsChanged();
+    void updatePort();
+
+    QScopedPointer<Ui::NetworkProxyWidget> ui;
+    TorInfoDialog *m_torInfoDialog;
+
+    bool m_disableTorLogs = false;
+    bool m_proxySettingsChanged = false;
+};
+
+
+#endif //FEATHER_NETWORKPROXYWIDGET_H
diff --git a/src/widgets/NetworkProxyWidget.ui b/src/widgets/NetworkProxyWidget.ui
new file mode 100644 (file)
index 0000000..def52da
--- /dev/null
@@ -0,0 +1,293 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NetworkProxyWidget</class>
+ <widget class="QWidget" name="NetworkProxyWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>649</width>
+    <height>382</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_8">
+     <item>
+      <widget class="QLabel" name="label_21">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Proxy:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="comboBox_proxy">
+       <item>
+        <property name="text">
+         <string>None</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Tor</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>i2p</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>socks5</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QFrame" name="frame_proxy">
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_19">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QGroupBox" name="groupBox_proxySettings">
+        <property name="title">
+         <string>Tor settings</string>
+        </property>
+        <layout class="QVBoxLayout" name="verticalLayout_21">
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_13">
+           <item>
+            <widget class="QLabel" name="label_36">
+             <property name="text">
+              <string>Socks5 Host:</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLineEdit" name="line_host">
+             <property name="text">
+              <string>127.0.0.1</string>
+             </property>
+             <property name="placeholderText">
+              <string>127.0.0.1</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="label_37">
+             <property name="text">
+              <string>Socks5 Port:</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLineEdit" name="line_port">
+             <property name="text">
+              <string>9050</string>
+             </property>
+             <property name="placeholderText">
+              <string>9050</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <widget class="QFrame" name="frame_tor">
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Raised</enum>
+           </property>
+           <layout class="QVBoxLayout" name="verticalLayout_22">
+            <property name="leftMargin">
+             <number>0</number>
+            </property>
+            <property name="topMargin">
+             <number>0</number>
+            </property>
+            <property name="rightMargin">
+             <number>0</number>
+            </property>
+            <property name="bottomMargin">
+             <number>0</number>
+            </property>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_2">
+              <item>
+               <widget class="QCheckBox" name="checkBox_torManaged">
+                <property name="text">
+                 <string>Let Feather start and manage a Tor daemon</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QFrame" name="frame_torShowLogs">
+                <property name="frameShape">
+                 <enum>QFrame::NoFrame</enum>
+                </property>
+                <property name="frameShadow">
+                 <enum>QFrame::Raised</enum>
+                </property>
+                <layout class="QHBoxLayout" name="horizontalLayout_9">
+                 <property name="leftMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="topMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="rightMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="bottomMargin">
+                  <number>0</number>
+                 </property>
+                 <item>
+                  <spacer name="horizontalSpacer_3">
+                   <property name="orientation">
+                    <enum>Qt::Horizontal</enum>
+                   </property>
+                   <property name="sizeHint" stdset="0">
+                    <size>
+                     <width>0</width>
+                     <height>0</height>
+                    </size>
+                   </property>
+                  </spacer>
+                 </item>
+                 <item>
+                  <widget class="QPushButton" name="btn_torShowLogs">
+                   <property name="text">
+                    <string>Show status</string>
+                   </property>
+                  </widget>
+                 </item>
+                </layout>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_torOnlyAllowOnion">
+              <property name="text">
+               <string>Only allow connections to onion services</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_12">
+              <item>
+               <widget class="QLabel" name="label_32">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Node traffic:</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QComboBox" name="comboBox_torNodeTraffic">
+                <item>
+                 <property name="text">
+                  <string>Never over Tor</string>
+                 </property>
+                </item>
+                <item>
+                 <property name="text">
+                  <string>Switch to Tor after initial synchronization</string>
+                 </property>
+                </item>
+                <item>
+                 <property name="text">
+                  <string>Always over Tor</string>
+                 </property>
+                </item>
+               </widget>
+              </item>
+             </layout>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QFrame" name="frame_notice">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QLabel" name="label_notice">
+        <property name="text">
+         <string>Feather is connected to a local node. Proxy settings ignored for node traffic.</string>
+        </property>
+        <property name="wordWrap">
+         <bool>true</bool>
+        </property>
+        <property name="textInteractionFlags">
+         <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>0</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
index 374353c95252f7d55690d13c25fe31d481c15d23..ab4d8c83e715190f0ad07edfdb0e45c6d46c000d 100644 (file)
@@ -8,7 +8,6 @@
 #include <QDesktopServices>
 #include <QInputDialog>
 #include <QMenu>
-#include <QMessageBox>
 #include <QTableWidget>
 
 #include "model/NodeModel.h"
@@ -20,39 +19,43 @@ NodeWidget::NodeWidget(QWidget *parent)
 {
     ui->setupUi(this);
 
-    connect(ui->btn_add_custom, &QPushButton::clicked, this, &NodeWidget::onCustomAddClicked);
+    connect(ui->btn_addCustomNodes, &QPushButton::clicked, this, &NodeWidget::onCustomAddClicked);
 
-    ui->nodeBtnGroup->setId(ui->radioButton_websocket, NodeSource::websocket);
-    ui->nodeBtnGroup->setId(ui->radioButton_custom, NodeSource::custom);
-
-    connect(ui->nodeBtnGroup, &QButtonGroup::idClicked, [this](int id){
-        config()->set(Config::nodeSource, id);
-        emit nodeSourceChanged(static_cast<NodeSource>(id));
+    connect(ui->checkBox_websocketList, &QCheckBox::stateChanged, [this](int id){
+        bool custom = (id == 0);
+        ui->stackedWidget->setCurrentIndex(custom);
+        ui->frame_addCustomNodes->setVisible(custom);
+        config()->set(Config::nodeSource, custom);
+        emit nodeSourceChanged(static_cast<NodeSource>(custom));
     });
 
     m_contextActionRemove = new QAction("Remove", this);
-    m_contextActionConnect = new QAction(icons()->icon("connect.svg"), "Connect to node", this);
-    m_contextActionOpenStatusURL = new QAction(icons()->icon("network.png"), "Visit status page", this);
-    m_contextActionCopy = new QAction(icons()->icon("copy.png"), "Copy", this);
+    m_contextActionConnect = new QAction("Connect to node", this);
+    m_contextActionOpenStatusURL = new QAction("Visit status page", this);
+    m_contextActionCopy = new QAction("Copy", this);
     connect(m_contextActionConnect, &QAction::triggered, this, &NodeWidget::onContextConnect);
     connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
     connect(m_contextActionOpenStatusURL, &QAction::triggered, this, &NodeWidget::onContextStatusURL);
     connect(m_contextActionCopy, &QAction::triggered, this, &NodeWidget::onContextNodeCopy);
     connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
 
-    ui->wsView->setContextMenuPolicy(Qt::CustomContextMenu);
-    ui->customView->setContextMenuPolicy(Qt::CustomContextMenu);
-    connect(ui->wsView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowWSContextMenu);
-    connect(ui->customView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowCustomContextMenu);
+    ui->treeView_websocket->setContextMenuPolicy(Qt::CustomContextMenu);
+    ui->treeView_custom->setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(ui->treeView_websocket, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowWSContextMenu);
+    connect(ui->treeView_custom, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowCustomContextMenu);
+
+    connect(ui->treeView_websocket, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
+    connect(ui->treeView_custom, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
 
-    connect(ui->customView, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
-    connect(ui->wsView, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
+    int index = config()->get(Config::nodeSource).toInt();
+    ui->stackedWidget->setCurrentIndex(config()->get(Config::nodeSource).toInt());
+    ui->frame_addCustomNodes->setVisible(index);
 
     this->onWebsocketStatusChanged();
 }
 
 void NodeWidget::onShowWSContextMenu(const QPoint &pos) {
-    m_activeView = ui->wsView;
+    m_activeView = ui->treeView_websocket;
     FeatherNode node = this->selectedNode();
     if (node.toAddress().isEmpty()) return;
 
@@ -60,7 +63,7 @@ void NodeWidget::onShowWSContextMenu(const QPoint &pos) {
 }
 
 void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
-    m_activeView = ui->customView;
+    m_activeView = ui->treeView_custom;
     FeatherNode node = this->selectedNode();
     if (node.toAddress().isEmpty()) return;
 
@@ -69,33 +72,32 @@ void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
 
 void NodeWidget::onWebsocketStatusChanged() {
     bool disabled = config()->get(Config::disableWebsocket).toBool() || config()->get(Config::offlineMode).toBool();
-    QString labelText = disabled ? "From cached list" : "From websocket (recommended)";
-    ui->radioButton_websocket->setText(labelText);
-    ui->wsView->setColumnHidden(1, disabled);
+    ui->treeView_websocket->setColumnHidden(1, disabled);
 }
 
 void NodeWidget::showContextMenu(const QPoint &pos, const FeatherNode &node) {
     QMenu menu(this);
 
-    if (!node.isActive) {
+    if (m_canConnect && !node.isActive) {
         menu.addAction(m_contextActionConnect);
     }
 
     menu.addAction(m_contextActionOpenStatusURL);
     menu.addAction(m_contextActionCopy);
 
-    if (m_activeView == ui->customView)
+    if (m_activeView == ui->treeView_custom) {
         menu.addAction(m_contextActionRemove);
+    }
 
     menu.exec(m_activeView->viewport()->mapToGlobal(pos));
 }
 
 void NodeWidget::onContextConnect() {
     QObject *obj = sender();
-    if (obj == ui->customView)
-        m_activeView = ui->customView;
+    if (obj == ui->treeView_custom)
+        m_activeView = ui->treeView_custom;
     else
-        m_activeView = ui->wsView;
+        m_activeView = ui->treeView_websocket;
 
     FeatherNode node = this->selectedNode();
     if (!node.toAddress().isEmpty())
@@ -118,7 +120,7 @@ FeatherNode NodeWidget::selectedNode() {
     if (!index.isValid()) return FeatherNode();
 
     FeatherNode node;
-    if (m_activeView == ui->customView) {
+    if (m_activeView == ui->treeView_custom) {
         node = m_customModel->node(index.row());
     } else {
         node = m_wsModel->node(index.row());
@@ -127,21 +129,23 @@ FeatherNode NodeWidget::selectedNode() {
 }
 
 void NodeWidget::onContextCustomNodeRemove() {
-    QModelIndex index = ui->customView->currentIndex();
-    if (!index.isValid()) return;
+    QModelIndex index = ui->treeView_custom->currentIndex();
+    if (!index.isValid()) {
+        return;
+    }
     FeatherNode node = m_customModel->node(index.row());
 
-    auto nodes = m_ctx->nodes->customNodes();
+    auto nodes = m_nodes->customNodes();
     QMutableListIterator<FeatherNode> i(nodes);
     while (i.hasNext())
         if (i.next() == node)
             i.remove();
 
-    m_ctx->nodes->setCustomNodes(nodes);
+    m_nodes->setCustomNodes(nodes);
 }
 
 void NodeWidget::onCustomAddClicked(){
-    auto currentNodes = m_ctx->nodes->customNodes();
+    auto currentNodes = m_nodes->customNodes();
     QString currentNodesText;
 
     for (auto &entry: currentNodes) {
@@ -149,51 +153,52 @@ void NodeWidget::onCustomAddClicked(){
     }
 
     bool ok;
-    QString text = QInputDialog::getMultiLineText(this, "Add custom node(s).", "E.g: user:password@127.0.0.1:18081", currentNodesText, &ok);
-    if (!ok || text.isEmpty())
+    QString text = QInputDialog::getMultiLineText(this, "Add custom node(s).", "One node per line\nE.g: user:password@127.0.0.1:18081", currentNodesText, &ok);
+    if (!ok || text.isEmpty()) {
         return;
+    }
 
     QList<FeatherNode> nodesList;
     auto newNodesList = text.split("\n");
     for (auto &newNodeText: newNodesList) {
         newNodeText = newNodeText.replace("\r", "").trimmed();
-        if (newNodeText.isEmpty())
+        if (newNodeText.isEmpty()) {
             continue;
+        }
 
         auto node = FeatherNode(newNodeText);
         node.custom = true;
         nodesList.append(node);
     }
 
-    m_ctx->nodes->setCustomNodes(nodesList);
+    m_nodes->setCustomNodes(nodesList);
 }
 
-void NodeWidget::setupUI(QSharedPointer<AppContext> ctx) {
-    m_ctx = ctx;
-
-    auto nodeSource = m_ctx->nodes->source();
+void NodeWidget::setupUI(Nodes *nodes) {
+    m_nodes = nodes;
+    
+    auto nodeSource = m_nodes->source();
+    ui->checkBox_websocketList->setChecked(nodeSource == NodeSource::websocket);
 
-    if(nodeSource == NodeSource::websocket){
-        ui->radioButton_websocket->setChecked(true);
-    } else if(nodeSource == NodeSource::custom) {
-        ui->radioButton_custom->setChecked(true);
-    }
+    this->setWSModel(m_nodes->modelWebsocket);
+    this->setCustomModel(m_nodes->modelCustom);
+}
 
-    this->setWSModel(m_ctx->nodes->modelWebsocket);
-    this->setCustomModel(m_ctx->nodes->modelCustom);
+void NodeWidget::setCanConnect(bool canConnect) {
+    m_canConnect = canConnect;
 }
 
 void NodeWidget::setWSModel(NodeModel *model) {
     m_wsModel = model;
-    ui->wsView->setModel(m_wsModel);
-    ui->wsView->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
-    ui->wsView->header()->setSectionResizeMode(NodeModel::Height, QHeaderView::ResizeToContents);
+    ui->treeView_websocket->setModel(m_wsModel);
+    ui->treeView_websocket->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
+    ui->treeView_websocket->header()->setSectionResizeMode(NodeModel::Height, QHeaderView::ResizeToContents);
 }
 
 void NodeWidget::setCustomModel(NodeModel *model) {
     m_customModel = model;
-    ui->customView->setModel(m_customModel);
-    ui->customView->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
+    ui->treeView_custom->setModel(m_customModel);
+    ui->treeView_custom->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
 }
 
 NodeModel* NodeWidget::model() {
index a35efa3549046e8614ee2b0e689c251ee4a3765f..1ef0299b90a7780ea8c7f2d3affcec0db17c3747 100644 (file)
@@ -26,7 +26,8 @@ public:
     ~NodeWidget();
     void setWSModel(NodeModel *model);
     void setCustomModel(NodeModel *model);
-    void setupUI(QSharedPointer<AppContext> ctx);
+    void setupUI(Nodes *nodes);
+    void setCanConnect(bool canConnect);
     NodeModel* model();
 
 public slots:
@@ -50,9 +51,10 @@ private:
     FeatherNode selectedNode();
 
     QScopedPointer<Ui::NodeWidget> ui;
-    QSharedPointer<AppContext> m_ctx;
+    Nodes *m_nodes;
     NodeModel  *m_customModel;
     NodeModel *m_wsModel;
+    bool m_canConnect = true;
 
     QTreeView *m_activeView;
 
index e107d327225138460b28c22343a30abac29a66e0..f72c8ad4b0edd7da91d9e2b4ee626ddf1a0151e0 100644 (file)
@@ -6,14 +6,14 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>604</width>
-    <height>271</height>
+    <width>724</width>
+    <height>451</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QGridLayout" name="gridLayout">
+  <layout class="QVBoxLayout" name="verticalLayout">
    <property name="leftMargin">
     <number>0</number>
    </property>
    <property name="bottomMargin">
     <number>0</number>
    </property>
-   <item row="0" column="0">
-    <layout class="QVBoxLayout" name="verticalLayout">
-     <property name="spacing">
-      <number>6</number>
+   <item>
+    <widget class="QStackedWidget" name="stackedWidget">
+     <property name="currentIndex">
+      <number>0</number>
      </property>
-     <item>
-      <layout class="QHBoxLayout" name="horizontalLayout">
+     <widget class="QWidget" name="page_websocket">
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
        <item>
-        <layout class="QVBoxLayout" name="verticalLayout_2">
-         <item>
-          <widget class="QRadioButton" name="radioButton_websocket">
-           <property name="text">
-            <string>From websocket (recommended)</string>
-           </property>
-           <property name="checked">
-            <bool>true</bool>
-           </property>
-           <attribute name="buttonGroup">
-            <string notr="true">nodeBtnGroup</string>
-           </attribute>
-          </widget>
-         </item>
-         <item>
-          <widget class="QTreeView" name="wsView">
-           <property name="rootIsDecorated">
-            <bool>false</bool>
-           </property>
-           <attribute name="headerStretchLastSection">
-            <bool>false</bool>
-           </attribute>
-          </widget>
-         </item>
-        </layout>
+        <widget class="QTreeView" name="treeView_websocket">
+         <property name="rootIsDecorated">
+          <bool>false</bool>
+         </property>
+         <attribute name="headerStretchLastSection">
+          <bool>false</bool>
+         </attribute>
+        </widget>
        </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="page_custom">
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
        <item>
-        <layout class="QVBoxLayout" name="verticalLayout_3">
-         <item>
-          <widget class="QRadioButton" name="radioButton_custom">
-           <property name="text">
-            <string>From custom list</string>
-           </property>
-           <attribute name="buttonGroup">
-            <string notr="true">nodeBtnGroup</string>
-           </attribute>
-          </widget>
-         </item>
-         <item>
-          <widget class="QTreeView" name="customView">
-           <property name="rootIsDecorated">
-            <bool>false</bool>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QPushButton" name="btn_add_custom">
-           <property name="text">
-            <string>Add custom node(s)</string>
-           </property>
-          </widget>
-         </item>
-        </layout>
+        <widget class="QTreeView" name="treeView_custom">
+         <property name="rootIsDecorated">
+          <bool>false</bool>
+         </property>
+        </widget>
        </item>
       </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QCheckBox" name="checkBox_websocketList">
+       <property name="text">
+        <string>Let Feather manage this list</string>
+       </property>
+      </widget>
      </item>
      <item>
-      <layout class="QHBoxLayout" name="horizontalLayout_9">
-       <property name="bottomMargin">
-        <number>0</number>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
        </property>
-      </layout>
+       <property name="sizeType">
+        <enum>QSizePolicy::MinimumExpanding</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>25</width>
+         <height>0</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QFrame" name="frame_addCustomNodes">
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
+       </property>
+       <property name="frameShadow">
+        <enum>QFrame::Raised</enum>
+       </property>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <widget class="QPushButton" name="btn_addCustomNodes">
+          <property name="text">
+           <string>Add custom node(s)</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
      </item>
     </layout>
    </item>
  </widget>
  <resources/>
  <connections/>
- <buttongroups>
-  <buttongroup name="nodeBtnGroup"/>
- </buttongroups>
 </ui>
index 45c9ee8b6c05ee77d1701ddb971d07df84d6be5b..7dd137f2ed1045e3fc4979d91753fcbd05a1fb8b 100644 (file)
@@ -7,6 +7,8 @@
 
 #include <QFileDialog>
 
+#include "SettingsNewDialog.h"
+
 PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent)
         : QWizardPage(parent)
         , ui(new Ui::PageMenu)
@@ -16,14 +18,9 @@ PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget
     ui->setupUi(this);
     this->setButtonText(QWizard::FinishButton, "Open recent wallet");
 
-#if defined(Q_OS_MAC)
-    ui->check_darkMode->setVisible(false);
-#endif
-
     QString settingsSkin = config()->get(Config::skin).toString();
-    ui->check_darkMode->setChecked(settingsSkin == "QDarkStyle");
 
-    connect(ui->check_darkMode, &QCheckBox::toggled, this, &PageMenu::enableDarkMode);
+    connect(ui->btn_openSettings, &QPushButton::clicked, this, &PageMenu::showSettings);
 }
 
 void PageMenu::initializePage() {
index ba81f34c156ea43f2f4f72ddfc45d8752c7dfb26..6710dd4cabbc25e3f06a25828b25706cbab2a958 100644 (file)
@@ -25,7 +25,7 @@ public:
     int nextId() const override;
 
 signals:
-    void enableDarkMode(bool enable);
+    void showSettings();
 
 private:
     Ui::PageMenu *ui;
index 9f7b74f19fc03917e9a2d7ef5884a4bfbc8a762b..510e84a5285e14174a2158a44acbbf508abb73ca 100644 (file)
     </spacer>
    </item>
    <item>
-    <widget class="QCheckBox" name="check_darkMode">
-     <property name="focusPolicy">
-      <enum>Qt::NoFocus</enum>
-     </property>
-     <property name="text">
-      <string>Dark mode</string>
-     </property>
-    </widget>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QPushButton" name="btn_openSettings">
+       <property name="text">
+        <string>Settings</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
index 701ada844b68d254d8e063f2b5db3579f35ded92..2391299131af3fed3e72df20627d5f4a36d16746 100644 (file)
@@ -7,6 +7,7 @@
 #include <QtConcurrent/QtConcurrent>
 
 #include "constants.h"
+#include "utils/os/Prestium.h"
 #include "Utils.h"
 #include "WalletWizard.h"
 
@@ -55,7 +56,11 @@ PageNetwork::PageNetwork(QWidget *parent)
 }
 
 int PageNetwork::nextId() const {
-    return WalletWizard::Page_NetworkTor;
+    if (Prestium::detect()) {
+        return WalletWizard::Page_NetworkWebsocket;
+    }
+
+    return WalletWizard::Page_NetworkProxy;
 }
 
 bool PageNetwork::validatePage() {
index a151c99dd59346ce2afd9c2b3edc67b55f0b4f9a..cc6a1929593e5ec5c6f94cc70c98969d4f9bc4dc 100644 (file)
@@ -51,7 +51,7 @@
      <item>
       <widget class="QLabel" name="label_5">
        <property name="text">
-        <string>In most cases you want to let Feather pick one at random. Nodes are hosted by the Feather team and trusted members of the Monero community. </string>
+        <string>In most cases you want to let Feather pick one at random. Nodes are hosted by the developers and trusted members of the Monero community. </string>
        </property>
        <property name="wordWrap">
         <bool>true</bool>
         <item row="0" column="0">
          <widget class="QLabel" name="label_3">
           <property name="text">
-           <string>Custom node:</string>
+           <string>Node:</string>
           </property>
          </widget>
         </item>
diff --git a/src/wizard/PageNetworkProxy.cpp b/src/wizard/PageNetworkProxy.cpp
new file mode 100644 (file)
index 0000000..5e01b59
--- /dev/null
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2023 The Monero Project
+
+#include "PageNetworkProxy.h"
+#include "ui_PageNetworkProxy.h"
+#include "WalletWizard.h"
+
+#include <QSysInfo>
+
+PageNetworkProxy::PageNetworkProxy(QWidget *parent)
+    : QWizardPage(parent)
+    , ui(new Ui::PageNetworkProxy)
+{
+    ui->setupUi(this);
+
+    connect(ui->radio_configureManually, &QRadioButton::toggled, [this](bool checked){
+        ui->frame_privacyLevel->setVisible(checked);
+        this->adjustSize();
+        this->updateGeometry();
+    });
+
+    ui->proxyWidget->setDisableTorLogs();
+}
+
+void PageNetworkProxy::initializePage() {
+    // Fuck you Qt. No squish.
+    QTimer::singleShot(1, [this]{
+        ui->frame_privacyLevel->setVisible(false);
+    });
+}
+
+int PageNetworkProxy::nextId() const {
+    return WalletWizard::Page_NetworkWebsocket;
+}
+
+bool PageNetworkProxy::validatePage() {
+    if (ui->proxyWidget->isProxySettingsChanged()) {
+        ui->proxyWidget->setProxySettings();
+    }
+
+    emit initialNetworkConfigured();
+    return true;
+}
\ No newline at end of file
similarity index 56%
rename from src/wizard/PageNetworkTor.h
rename to src/wizard/PageNetworkProxy.h
index 63a486ad35e7b56d91263b80748d1844bab65b34..ba6ba6232310f531f57434eeafd9129cb831be56 100644 (file)
@@ -1,23 +1,23 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // SPDX-FileCopyrightText: 2020-2023 The Monero Project
 
-#ifndef FEATHER_PAGENETWORKTOR_H
-#define FEATHER_PAGENETWORKTOR_H
+#ifndef FEATHER_PageNetworkProxy_H
+#define FEATHER_PageNetworkProxy_H
 
 #include <QWizardPage>
 
 #include "appcontext.h"
 
 namespace Ui {
-    class PageNetworkTor;
+    class PageNetworkProxy;
 }
 
-class PageNetworkTor : public QWizardPage
+class PageNetworkProxy : public QWizardPage
 {
 Q_OBJECT
 
 public:
-    explicit PageNetworkTor(QWidget *parent = nullptr);
+    explicit PageNetworkProxy(QWidget *parent = nullptr);
     void initializePage() override;
     bool validatePage() override;
     int nextId() const override;
@@ -26,7 +26,7 @@ signals:
     void initialNetworkConfigured();
 
 private:
-    Ui::PageNetworkTor *ui;
+    Ui::PageNetworkProxy *ui;
 };
 
-#endif //FEATHER_PAGENETWORKTOR_H
+#endif //FEATHER_PageNetworkProxy_H
diff --git a/src/wizard/PageNetworkProxy.ui b/src/wizard/PageNetworkProxy.ui
new file mode 100644 (file)
index 0000000..68c4cb7
--- /dev/null
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PageNetworkProxy</class>
+ <widget class="QWizardPage" name="PageNetworkProxy">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>671</width>
+    <height>450</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>WizardPage</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>How should Feather route its network traffic?</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>By default, Feather routes most traffic over Tor. </string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>An exception is made for the initial wallet synchronization after opening a wallet. Synchronization requires a lot of data transfer and is therefore very slow over Tor. </string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_5">
+     <property name="text">
+      <string>Connections to local nodes are never routed over Tor.</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QRadioButton" name="radio_useDefaultSettings">
+     <property name="text">
+      <string>Use default settings</string>
+     </property>
+     <property name="checked">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QRadioButton" name="radio_configureManually">
+     <property name="text">
+      <string>Change proxy settings</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QFrame" name="frame_privacyLevel">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="NetworkProxyWidget" name="proxyWidget" native="true"/>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>0</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>NetworkProxyWidget</class>
+   <extends>QWidget</extends>
+   <header>widgets/NetworkProxyWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/wizard/PageNetworkTor.cpp b/src/wizard/PageNetworkTor.cpp
deleted file mode 100644 (file)
index cdf014e..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-// SPDX-License-Identifier: BSD-3-Clause
-// SPDX-FileCopyrightText: 2020-2023 The Monero Project
-
-#include "PageNetworkTor.h"
-#include "ui_PageNetworkTor.h"
-#include "WalletWizard.h"
-
-PageNetworkTor::PageNetworkTor(QWidget *parent)
-    : QWizardPage(parent)
-    , ui(new Ui::PageNetworkTor)
-{
-    ui->setupUi(this);
-
-    QPixmap iconAllTorExceptNode(":/assets/images/securityLevelStandard.png");
-    QPixmap iconAllTorExceptInitSync(":/assets/images/securityLevelSafer.png");
-    QPixmap iconAllTor(":/assets/images/securityLevelSafest.png");
-    ui->icon_allTorExceptNode->setPixmap(iconAllTorExceptNode.scaledToHeight(16, Qt::SmoothTransformation));
-    ui->icon_allTorExceptInitSync->setPixmap(iconAllTorExceptInitSync.scaledToHeight(16, Qt::SmoothTransformation));
-    ui->icon_allTor->setPixmap(iconAllTor.scaledToHeight(16, Qt::SmoothTransformation));
-
-    connect(ui->radio_configureManually, &QRadioButton::toggled, [this](bool checked){
-        ui->frame_privacyLevel->setVisible(checked);
-        this->adjustSize();
-        this->updateGeometry();
-    });
-
-    ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptNode, Config::allTorExceptNode);
-    ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptInitSync, Config::allTorExceptInitSync);
-    ui->btnGroup_privacyLevel->setId(ui->radio_allTor, Config::allTor);
-
-    int privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
-    auto button = ui->btnGroup_privacyLevel->button(privacyLevel);
-    if (button) {
-        button->setChecked(true);
-    }
-}
-
-void PageNetworkTor::initializePage() {
-    // Fuck you Qt. No squish.
-    QTimer::singleShot(1, [this]{
-        ui->frame_privacyLevel->setVisible(false);
-    });
-}
-
-int PageNetworkTor::nextId() const {
-    return WalletWizard::Page_NetworkWebsocket;
-}
-
-bool PageNetworkTor::validatePage() {
-    int id = ui->btnGroup_privacyLevel->checkedId();
-    config()->set(Config::torPrivacyLevel, id);
-
-    emit initialNetworkConfigured();
-
-    return true;
-}
\ No newline at end of file
diff --git a/src/wizard/PageNetworkTor.ui b/src/wizard/PageNetworkTor.ui
deleted file mode 100644 (file)
index 8cabfc8..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>PageNetworkTor</class>
- <widget class="QWizardPage" name="PageNetworkTor">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>618</width>
-    <height>438</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>WizardPage</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QLabel" name="label">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
-     </property>
-     <property name="text">
-      <string>How should Feather route its network traffic?</string>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QLabel" name="label_2">
-     <property name="text">
-      <string>By default, Feather routes most traffic over Tor. </string>
-     </property>
-     <property name="wordWrap">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QLabel" name="label_3">
-     <property name="text">
-      <string>An exception is made for the initial wallet synchronization after opening a wallet. Synchronization requires a lot of data transfer and is therefore very slow over Tor. </string>
-     </property>
-     <property name="wordWrap">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QLabel" name="label_4">
-     <property name="text">
-      <string>On Tails, Whonix, or when Feather is started with Torsocks, all traffic is routed through Tor regardless of application configuration.</string>
-     </property>
-     <property name="wordWrap">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QLabel" name="label_5">
-     <property name="text">
-      <string>Connections to local nodes are never routed over Tor.</string>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QRadioButton" name="radio_useDefaultSettings">
-     <property name="text">
-      <string>Use default settings (recommended)</string>
-     </property>
-     <property name="checked">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QRadioButton" name="radio_configureManually">
-     <property name="text">
-      <string>Configure manually</string>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QFrame" name="frame_privacyLevel">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout_2">
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout">
-        <item>
-         <widget class="QLabel" name="icon_allTorExceptNode">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>icon</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QRadioButton" name="radio_allTorExceptNode">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>Route all traffic over Tor, except traffic to node</string>
-          </property>
-          <attribute name="buttonGroup">
-           <string notr="true">btnGroup_privacyLevel</string>
-          </attribute>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_2">
-        <item>
-         <widget class="QLabel" name="icon_allTorExceptInitSync">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>icon</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QRadioButton" name="radio_allTorExceptInitSync">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>Route all traffic over Tor, except initial wallet sync</string>
-          </property>
-          <attribute name="buttonGroup">
-           <string notr="true">btnGroup_privacyLevel</string>
-          </attribute>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_3">
-        <item>
-         <widget class="QLabel" name="icon_allTor">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>icon</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QRadioButton" name="radio_allTor">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>Route all traffic over Tor</string>
-          </property>
-          <attribute name="buttonGroup">
-           <string notr="true">btnGroup_privacyLevel</string>
-          </attribute>
-         </widget>
-        </item>
-       </layout>
-      </item>
-     </layout>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
- <buttongroups>
-  <buttongroup name="btnGroup_privacyLevel"/>
- </buttongroups>
-</ui>
index f4bebb1c9d9cd400916d1f5f2a67a153b72f0f97..ebf28c4475286b95be06cbfd946d12d16f30c2ef 100644 (file)
@@ -30,7 +30,7 @@
    <item>
     <widget class="QLabel" name="label_2">
      <property name="text">
-      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feather can connect to an onion service hosted by the Feather developers to obtain pricing information, a curated list of remote nodes, Home feeds, the latest version of Feather Wallet and more.&lt;/p&gt;&lt;p&gt;This service is only used to fetch information and can only be reached over Tor. The wallet does not send information about its state or your transactions to the server. It is not used for any telemetry or crash reports.&lt;/p&gt;&lt;p&gt;If you opt to disable this connection some wallet functionality will be disabled. You can re-enable it at any time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feather can connect to a service hosted by the developers to obtain pricing information, a curated list of remote nodes, Home feeds, the latest version of Feather Wallet and more.&lt;/p&gt;&lt;p&gt;This service is only used to fetch information. The wallet does not send information about its state or your transactions to the server. It is not used for any telemetry or crash reports.&lt;/p&gt;&lt;p&gt;If you disable this connection some wallet functionality will be unavailable. You can re-enable it at any time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
      </property>
      <property name="wordWrap">
       <bool>true</bool>
index 91e689351338cad2345f38603112b21225bd0390..7d39777bb6514e1f9301cc63c883509fc2bd871c 100644 (file)
 #include "PageSetSeedPassphrase.h"
 #include "PageSetSubaddressLookahead.h"
 #include "PageHardwareDevice.h"
-#include "PageNetworkTor.h"
+#include "PageNetworkProxy.h"
 #include "PageNetworkWebsocket.h"
 #include "constants.h"
+#include "SettingsNewDialog.h"
 
 #include <QLineEdit>
 #include <QVBoxLayout>
@@ -34,7 +35,7 @@ WalletWizard::WalletWizard(QWidget *parent)
     m_walletKeysFilesModel->refresh();
 
     auto networkPage = new PageNetwork(this);
-    auto networkTorPage = new PageNetworkTor(this);
+    auto networkProxyPage = new PageNetworkProxy(this);
     auto networkWebsocketPage = new PageNetworkWebsocket(this);
     auto menuPage = new PageMenu(&m_wizardFields, m_walletKeysFilesModel, this);
     auto openWalletPage = new PageOpenWallet(m_walletKeysFilesModel, this);
@@ -49,7 +50,7 @@ WalletWizard::WalletWizard(QWidget *parent)
     setPage(Page_CreateWalletSeed, createWalletSeed);
     setPage(Page_SetPasswordPage, walletSetPasswordPage);
     setPage(Page_Network, networkPage);
-    setPage(Page_NetworkTor, networkTorPage);
+    setPage(Page_NetworkProxy, networkProxyPage);
     setPage(Page_NetworkWebsocket, networkWebsocketPage);
     setPage(Page_WalletRestoreSeed, new PageWalletRestoreSeed(&m_wizardFields, this));
     setPage(Page_WalletRestoreKeys, new PageWalletRestoreKeys(&m_wizardFields, this));
@@ -69,12 +70,7 @@ WalletWizard::WalletWizard(QWidget *parent)
         emit initialNetworkConfigured();
     });
 
-    connect(menuPage, &PageMenu::enableDarkMode, [this](bool enable){
-        if (enable)
-            emit skinChanged("QDarkStyle");
-        else
-            emit skinChanged("Native");
-    });
+    connect(menuPage, &PageMenu::showSettings, this, &WalletWizard::showSettings);
 
     connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet);
 
index 04d31a6dc15300e307877560dd126b0191a0ee6f..b423a6e359844dd082150477429b623a5188ce32 100644 (file)
@@ -82,7 +82,7 @@ public:
         Page_WalletRestoreKeys,
         Page_SetRestoreHeight,
         Page_HardwareDevice,
-        Page_NetworkTor,
+        Page_NetworkProxy,
         Page_NetworkWebsocket
     };
 
@@ -91,7 +91,7 @@ public:
 
 signals:
     void initialNetworkConfigured();
-    void skinChanged(const QString &skin);
+    void showSettings();
     void openWallet(QString path, QString password);
     void defaultWalletDirChanged(QString walletDir);