diff --git a/CMakeLists.txt b/CMakeLists.txt index 6023cfd..fa962f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ option(WITH_PLUGIN_BOUNTIES "Include Bounties Home plugin" ON) option(WITH_PLUGIN_REVUO "Include Revuo Home plugin" ON) option(WITH_PLUGIN_CALC "Include Calc tab plugin" ON) option(WITH_PLUGIN_XMRIG "Include XMRig plugin" ON) +option(WITH_PLUGIN_ATOMIC "Include Atomic plugin" ON) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake") include(CheckCCompilerFlag) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ae06ddb..4938446 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -225,7 +225,7 @@ if(STATIC) endif() if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - target_compile_definitions(feather PRIVATE QT_NO_DEBUG=1) + target_compile_definitions(feather PRIVATE QT_NO_DEBUG=0) endif() target_compile_definitions(feather diff --git a/src/plugins/atomic/AtomicConfigDialog.cpp b/src/plugins/atomic/AtomicConfigDialog.cpp new file mode 100644 index 0000000..1cd7d53 --- /dev/null +++ b/src/plugins/atomic/AtomicConfigDialog.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#include "AtomicConfigDialog.h" +#include "ui_AtomicConfigDialog.h" + +#include "utils/AppData.h" +#include "utils/config.h" + +AtomicConfigDialog::AtomicConfigDialog(QWidget *parent) + : WindowModalDialog(parent) + , ui(new Ui::AtomicConfigDialog) +{ + ui->setupUi(this); + + this->fillListWidgets(); + + connect(ui->btn_selectAll, &QPushButton::clicked, this, &AtomicConfigDialog::selectAll); + connect(ui->btn_deselectAll, &QPushButton::clicked, this, &AtomicConfigDialog::deselectAll); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, [this]{ + conf()->set(Config::fiatSymbols, this->checkedFiat()); + conf()->set(Config::cryptoSymbols, this->checkedCrypto()); + this->accept(); + }); + + this->adjustSize(); +} + +QStringList AtomicConfigDialog::checkedFiat() { + return this->getChecked(ui->list_fiat); +} + +QStringList AtomicConfigDialog::checkedCrypto() { + return this->getChecked(ui->list_crypto); +} + +void AtomicConfigDialog::selectAll() { + this->setCheckState(this->getVisibleListWidget(), Qt::Checked); +} + +void AtomicConfigDialog::deselectAll() { + this->setCheckState(this->getVisibleListWidget(), Qt::Unchecked); +} + +void AtomicConfigDialog::setCheckState(QListWidget *widget, Qt::CheckState checkState) { + QListWidgetItem *item; + for (int i=0; i < widget->count(); i++) { + item = widget->item(i); + item->setCheckState(checkState); + } +} + +QStringList AtomicConfigDialog::getChecked(QListWidget *widget) { + QStringList checked; + QListWidgetItem *item; + for (int i=0; i < widget->count(); i++) { + item = widget->item(i); + if (item->checkState() == Qt::Checked) { + checked.append(item->text()); + } + } + return checked; +} + +QListWidget* AtomicConfigDialog::getVisibleListWidget() { + if (ui->tabWidget->currentIndex() == 0) { + return ui->list_fiat; + } else { + return ui->list_crypto; + } +} + +void AtomicConfigDialog::fillListWidgets() { + QStringList cryptoCurrencies = appData()->prices.markets.keys(); + QStringList fiatCurrencies = appData()->prices.rates.keys(); + + QStringList checkedCryptoCurrencies = conf()->get(Config::cryptoSymbols).toStringList(); + QStringList checkedFiatCurrencies = conf()->get(Config::fiatSymbols).toStringList(); + + ui->list_crypto->addItems(cryptoCurrencies); + ui->list_fiat->addItems(fiatCurrencies); + + auto setChecked = [](QListWidget *widget, const QStringList &checked){ + QListWidgetItem *item; + for (int i=0; i < widget->count(); i++) { + item = widget->item(i); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + if (checked.contains(item->text())) { + item->setCheckState(Qt::Checked); + } else { + item->setCheckState(Qt::Unchecked); + } + } + }; + + setChecked(ui->list_crypto, checkedCryptoCurrencies); + setChecked(ui->list_fiat, checkedFiatCurrencies); +} + +AtomicConfigDialog::~AtomicConfigDialog() = default; \ No newline at end of file diff --git a/src/plugins/atomic/AtomicConfigDialog.h b/src/plugins/atomic/AtomicConfigDialog.h new file mode 100644 index 0000000..a51d28d --- /dev/null +++ b/src/plugins/atomic/AtomicConfigDialog.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef FEATHER_ATOMICCONFIGDIALOG_H +#define FEATHER_ATOMICCONFIGDIALOG_H + +#include +#include + +#include "components.h" + +namespace Ui { + class AtomicConfigDialog; +} + +class AtomicConfigDialog : public WindowModalDialog +{ +Q_OBJECT + +public: + explicit AtomicConfigDialog(QWidget *parent = nullptr); + ~AtomicConfigDialog() override; + + QStringList checkedFiat(); + QStringList checkedCrypto(); + +private slots: + void selectAll(); + void deselectAll(); + +private: + void setCheckState(QListWidget *widget, Qt::CheckState checkState); + QStringList getChecked(QListWidget *widget); + void fillListWidgets(); + QListWidget* getVisibleListWidget(); + + QScopedPointer ui; +}; + + +#endif //FEATHER_ATOMICCONFIGDIALOG_H diff --git a/src/plugins/atomic/AtomicConfigDialog.ui b/src/plugins/atomic/AtomicConfigDialog.ui new file mode 100644 index 0000000..aa13d92 --- /dev/null +++ b/src/plugins/atomic/AtomicConfigDialog.ui @@ -0,0 +1,153 @@ + + + AtomicConfigDialog + + + + 0 + 0 + 509 + 574 + + + + Atomic config + + + + + + 0 + + + + Fiat + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + Crypto + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Vertical + + + + + + + + + Select all + + + + + + + Deselect all + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + AtomicConfigDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AtomicConfigDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/atomic/AtomicPlugin.cpp b/src/plugins/atomic/AtomicPlugin.cpp new file mode 100644 index 0000000..cd2bdb4 --- /dev/null +++ b/src/plugins/atomic/AtomicPlugin.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#include "AtomicPlugin.h" +#include "AtomicConfigDialog.h" + +#include "plugins/PluginRegistry.h" + +AtomicPlugin::AtomicPlugin() +{ +} + +void AtomicPlugin::initialize(Wallet *wallet, QObject *parent) { + this->setParent(parent); + m_tab = new AtomicWidget(nullptr); +} + +QString AtomicPlugin::id() { + return "atomic"; +} + +int AtomicPlugin::idx() const { + return 61; +} + +QString AtomicPlugin::parent() { + return {}; +} + +QString AtomicPlugin::displayName() { + return "Atomic"; +} + +QString AtomicPlugin::description() { + return {}; +} + +QString AtomicPlugin::icon() { + return "gnome-calc.png"; +} + +QStringList AtomicPlugin::socketData() { + return {}; +} + +Plugin::PluginType AtomicPlugin::type() { + return Plugin::PluginType::TAB; +} + +QWidget* AtomicPlugin::tab() { + return m_tab; +} + +bool AtomicPlugin::configurable() { + return true; +} + +QDialog* AtomicPlugin::configDialog(QWidget *parent) { + return new AtomicConfigDialog{parent}; +} + +void AtomicPlugin::skinChanged() { + m_tab->skinChanged(); +} + +const bool AtomicPlugin::registered = [] { + PluginRegistry::registerPlugin(AtomicPlugin::create()); + PluginRegistry::getInstance().registerPluginCreator(&AtomicPlugin::create); + return true; +}(); diff --git a/src/plugins/atomic/AtomicPlugin.h b/src/plugins/atomic/AtomicPlugin.h new file mode 100644 index 0000000..abdcd2b --- /dev/null +++ b/src/plugins/atomic/AtomicPlugin.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef ATOMICPLUGIN_H +#define ATOMICPLUGIN_H + +#include "plugins/Plugin.h" +#include "AtomicWidget.h" + +class AtomicPlugin : public Plugin { +Q_OBJECT + +public: + explicit AtomicPlugin(); + + QString id() override; + int idx() const override; + QString parent() override; + QString displayName() override; + QString description() override; + QString icon() override; + QStringList socketData() override; + PluginType type() override; + QWidget* tab() override; + bool configurable() override; + QDialog* configDialog(QWidget *parent) override; + + void initialize(Wallet *wallet, QObject *parent) override; + + static AtomicPlugin* create() { return new AtomicPlugin(); } + +public slots: + void skinChanged() override; + +private: + AtomicWidget* m_tab = nullptr; + static const bool registered; +}; + + +#endif //ATOMICPLUGIN_H diff --git a/src/plugins/atomic/AtomicWidget.cpp b/src/plugins/atomic/AtomicWidget.cpp new file mode 100644 index 0000000..e4c5323 --- /dev/null +++ b/src/plugins/atomic/AtomicWidget.cpp @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#include "AtomicWidget.h" +#include "ui_AtomicWidget.h" + +#include + +#include "AtomicConfigDialog.h" +#include "utils/AppData.h" +#include "utils/ColorScheme.h" +#include "utils/config.h" +#include "utils/WebsocketNotifier.h" + +AtomicWidget::AtomicWidget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::AtomicWidget) +{ + ui->setupUi(this); + + ui->imageExchange->setBackgroundRole(QPalette::Base); + ui->imageExchange->setAssets(":/assets/images/exchange.png", ":/assets/images/exchange_white.png"); + ui->imageExchange->setScaledContents(true); + ui->imageExchange->setFixedSize(26, 26); + + // validator/locale for input + QString amount_rx = R"(^\d{0,8}[\.]\d{0,12}$)"; + QRegularExpression rx; + rx.setPattern(amount_rx); + QValidator *validator = new QRegularExpressionValidator(rx, this); + ui->lineFrom->setValidator(validator); + ui->lineTo->setValidator(validator); + + connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &AtomicWidget::onPricesReceived); + connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &AtomicWidget::onPricesReceived); + + connect(ui->lineFrom, &QLineEdit::textEdited, this, [this]{this->convert(false);}); + connect(ui->lineTo, &QLineEdit::textEdited, this, [this]{this->convert(true);}); + + connect(ui->comboAtomicFrom, QOverload::of(&QComboBox::currentIndexChanged), [this]{this->convert(false);}); + connect(ui->comboAtomicTo, QOverload::of(&QComboBox::currentIndexChanged), [this]{this->convert(false);}); + + connect(ui->btn_configure, &QPushButton::clicked, this, &AtomicWidget::showAtomicConfigureDialog); + + QTimer::singleShot(1, [this]{ + this->skinChanged(); + }); + + m_statusTimer.start(5000); + connect(&m_statusTimer, &QTimer::timeout, this, &AtomicWidget::updateStatus); + QPixmap warningIcon = QPixmap(":/assets/images/warning.png"); + ui->icon_warning->setPixmap(warningIcon.scaledToWidth(32, Qt::SmoothTransformation)); + + this->updateStatus(); +} + +void AtomicWidget::convert(bool reverse) { + if (!m_comboBoxInit) + return; + + auto lineFrom = reverse ? ui->lineTo : ui->lineFrom; + auto lineTo = reverse ? ui->lineFrom : ui->lineTo; + + auto comboFrom = reverse ? ui->comboAtomicTo : ui->comboAtomicFrom; + auto comboTo = reverse ? ui->comboAtomicFrom : ui->comboAtomicTo; + + QString symbolFrom = comboFrom->itemText(comboFrom->currentIndex()); + QString symbolTo = comboTo->itemText(comboTo->currentIndex()); + + if (symbolFrom == symbolTo) { + lineTo->setText(lineFrom->text()); + } + + QString amountStr = lineFrom->text(); + double amount = amountStr.toDouble(); + double result = appData()->prices.convert(symbolFrom, symbolTo, amount); + + int precision = 10; + if (appData()->prices.rates.contains(symbolTo)) + precision = 2; + + lineTo->setText(QString::number(result, 'f', precision)); +} + +void AtomicWidget::onPricesReceived() { + if (m_comboBoxInit) + return; + + QList cryptoKeys = appData()->prices.markets.keys(); + QList fiatKeys = appData()->prices.rates.keys(); + if (cryptoKeys.empty() || fiatKeys.empty()) + return; + + ui->btn_configure->setEnabled(true); + this->initComboBox(); + m_comboBoxInit = true; + this->updateStatus(); +} + +void AtomicWidget::initComboBox() { + QList cryptoKeys = appData()->prices.markets.keys(); + QList fiatKeys = appData()->prices.rates.keys(); + + QStringList enabledCrypto = conf()->get(Config::cryptoSymbols).toStringList(); + QStringList filteredCryptoKeys; + for (const auto& symbol : cryptoKeys) { + if (enabledCrypto.contains(symbol)) { + filteredCryptoKeys.append(symbol); + } + } + + QStringList enabledFiat = conf()->get(Config::fiatSymbols).toStringList(); + auto preferredFiat = conf()->get(Config::preferredFiatCurrency).toString(); + if (!enabledFiat.contains(preferredFiat) && fiatKeys.contains(preferredFiat)) { + enabledFiat.append(preferredFiat); + conf()->set(Config::fiatSymbols, enabledFiat); + } + QStringList filteredFiatKeys; + for (const auto &symbol : fiatKeys) { + if (enabledFiat.contains(symbol)) { + filteredFiatKeys.append(symbol); + } + } + + this->setupComboBox(ui->comboAtomicFrom, filteredCryptoKeys, filteredFiatKeys); + this->setupComboBox(ui->comboAtomicTo, filteredCryptoKeys, filteredFiatKeys); + + ui->comboAtomicFrom->setCurrentIndex(ui->comboAtomicFrom->findText("XMR")); + + if (!preferredFiat.isEmpty()) { + ui->comboAtomicTo->setCurrentIndex(ui->comboAtomicTo->findText(preferredFiat)); + } else { + ui->comboAtomicTo->setCurrentIndex(ui->comboAtomicTo->findText("USD")); + } +} + +void AtomicWidget::skinChanged() { + ui->imageExchange->setMode(ColorScheme::hasDarkBackground(this)); +} + +void AtomicWidget::showAtomicConfigureDialog() { + AtomicConfigDialog dialog{this}; + + if (dialog.exec() == QDialog::Accepted) { + this->initComboBox(); + } +} + +void AtomicWidget::setupComboBox(QComboBox *comboBox, const QStringList &crypto, const QStringList &fiat) { + comboBox->clear(); + comboBox->addItems(crypto); + comboBox->insertSeparator(comboBox->count()); + comboBox->addItems(fiat); +} + +void AtomicWidget::updateStatus() { + if (!m_comboBoxInit) { + ui->label_warning->setText("Waiting on exchange data."); + ui->frame_warning->show(); + } + else if (websocketNotifier()->stale(10)) { + ui->label_warning->setText("No new exchange rates received for over 10 minutes."); + ui->frame_warning->show(); + } + else { + ui->frame_warning->hide(); + } +} + +AtomicWidget::~AtomicWidget() = default; \ No newline at end of file diff --git a/src/plugins/atomic/AtomicWidget.h b/src/plugins/atomic/AtomicWidget.h new file mode 100644 index 0000000..da65c29 --- /dev/null +++ b/src/plugins/atomic/AtomicWidget.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef FEATHER_ATOMICWIDGET_H +#define FEATHER_ATOMICWIDGET_H + +#include +#include +#include + +namespace Ui { + class AtomicWidget; +} + +class AtomicWidget : public QWidget +{ +Q_OBJECT + +public: + explicit AtomicWidget(QWidget *parent = nullptr); + ~AtomicWidget() override; + +public slots: + void skinChanged(); + +private slots: + void initComboBox(); + void showAtomicConfigureDialog(); + void onPricesReceived(); + +private: + void convert(bool reverse); + void setupComboBox(QComboBox *comboBox, const QStringList &crypto, const QStringList &fiat); + void updateStatus(); + + QScopedPointer ui; + bool m_comboBoxInit = false; + QTimer m_statusTimer; +}; + +#endif // FEATHER_ATOMICWIDGET_H diff --git a/src/plugins/atomic/AtomicWidget.ui b/src/plugins/atomic/AtomicWidget.ui new file mode 100644 index 0000000..2f3236f --- /dev/null +++ b/src/plugins/atomic/AtomicWidget.ui @@ -0,0 +1,187 @@ + + + AtomicWidget + + + + 0 + 0 + 800 + 366 + + + + MainWindow + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + icon + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 10 + 20 + + + + + + + + Warning text + + + + + + + + + + 18 + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + From... + + + + + + + + + + + + exchange image + + + + + + + 0 + + + + + + 0 + 0 + + + + false + + + To... + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Configure + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + DoublePixmapLabel + QLabel +
components.h
+
+
+ + + + fromChanged(QString) + toChanged(QString) + toComboChanged(QString) + +
diff --git a/src/plugins/atomic/AtomicWindow.cpp b/src/plugins/atomic/AtomicWindow.cpp new file mode 100644 index 0000000..8ee497a --- /dev/null +++ b/src/plugins/atomic/AtomicWindow.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#include "AtomicWindow.h" +#include "ui_AtomicWindow.h" + +#include "utils/AppData.h" +#include "utils/Icons.h" + +AtomicWindow::AtomicWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::AtomicWindow) +{ + Qt::WindowFlags flags = this->windowFlags(); + this->setWindowFlags(flags|Qt::WindowStaysOnTopHint); // on top + + ui->setupUi(this); + this->setWindowIcon(icons()->icon("gnome-Atomic.png")); +} + +AtomicWindow::~AtomicWindow() = default; \ No newline at end of file diff --git a/src/plugins/atomic/AtomicWindow.h b/src/plugins/atomic/AtomicWindow.h new file mode 100644 index 0000000..acde94c --- /dev/null +++ b/src/plugins/atomic/AtomicWindow.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef FEATHER_ATOMICWINDOW_H +#define FEATHER_ATOMICWINDOW_H + +#include + +namespace Ui { + class AtomicWindow; +} + +class AtomicWindow : public QMainWindow +{ +Q_OBJECT + +public: + explicit AtomicWindow(QWidget *parent = nullptr); + ~AtomicWindow() override; + +private: + QScopedPointer ui; +}; + +#endif // FEATHER_ATOMICWINDOW_H + diff --git a/src/plugins/atomic/AtomicWindow.ui b/src/plugins/atomic/AtomicWindow.ui new file mode 100644 index 0000000..74762e9 --- /dev/null +++ b/src/plugins/atomic/AtomicWindow.ui @@ -0,0 +1,49 @@ + + + AtomicWindow + + + + 0 + 0 + 520 + 108 + + + + Atomic Swap + + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + + + + + AtomicWidget + QWidget +
plugins/atomic/AtomicWidget.h
+ 1 +
+
+ + + + stayOnTop(int) + +
diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 722eb55..0544a39 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -43,7 +43,7 @@ static const QHash configStrings = { {Config::useOnionNodes,{QS("useOnionNodes"), false}}, // Tabs - {Config::enabledTabs, {QS("enabledTabs"), QStringList{"Home", "History", "Send", "Receive", "Calc"}}}, + {Config::enabledTabs, {QS("enabledTabs"), QStringList{"Home", "History", "Send", "Receive", "Calc", "Atomic"}}}, {Config::showSearchbar,{QS("showSearchbar"), true}}, // History @@ -129,7 +129,7 @@ static const QHash configStrings = { {Config::useLocalTor, {QS("useLocalTor"), false}}, {Config::initSyncThreshold, {QS("initSyncThreshold"), 360}}, - {Config::enabledPlugins, {QS("enabledPlugins"), QStringList{"tickers", "crowdfunding", "bounties", "revuo", "calc", "xmrig"}}}, + {Config::enabledPlugins, {QS("enabledPlugins"), QStringList{"tickers", "crowdfunding", "bounties", "revuo", "calc", "xmrig", "atomic"}}}, {Config::restartRequired, {QS("restartRequired"), false}}, {Config::tickers, {QS("tickers"), QStringList{"XMR", "BTC", "XMR/BTC"}}}, diff --git a/src/wizard/WalletWizard.cpp b/src/wizard/WalletWizard.cpp index e60bfa8..a7b9b02 100644 --- a/src/wizard/WalletWizard.cpp +++ b/src/wizard/WalletWizard.cpp @@ -40,6 +40,8 @@ WalletWizard::WalletWizard(QWidget *parent) auto networkWebsocketPage = new PageNetworkWebsocket(this); auto menuPage = new PageMenu(&m_wizardFields, m_walletKeysFilesModel, this); auto openWalletPage = new PageOpenWallet(m_walletKeysFilesModel, this); + + auto createWallet = new PageWalletFile(&m_wizardFields , this); auto createWalletSeed = new PageWalletSeed(&m_wizardFields, this); auto walletSetPasswordPage = new PageSetPassword(&m_wizardFields, this); diff --git a/src/wizard/WalletWizard.h b/src/wizard/WalletWizard.h index 92cd77d..eef1a0a 100644 --- a/src/wizard/WalletWizard.h +++ b/src/wizard/WalletWizard.h @@ -84,7 +84,8 @@ public: Page_HardwareDevice, Page_NetworkProxy, Page_NetworkWebsocket, - Page_Plugins + Page_Plugins, + Page_Atomic }; explicit WalletWizard(QWidget *parent = nullptr);