Merge pull request '1.0.0' (#394) from tobtoht/feather:1.0.0 into master

Reviewed-on: https://git.featherwallet.org/feather/feather/pulls/394
This commit is contained in:
tobtoht 2021-10-28 18:04:39 +00:00
commit 465a10b983
135 changed files with 2014 additions and 1195 deletions

View file

@ -3,10 +3,10 @@ project(feather)
message(STATUS "Initiating compile using CMake ${CMAKE_VERSION}") message(STATUS "Initiating compile using CMake ${CMAKE_VERSION}")
set(VERSION_MAJOR "0") set(VERSION_MAJOR "1")
set(VERSION_MINOR "1") set(VERSION_MINOR "0")
set(VERSION_REVISION "0") set(VERSION_REVISION "0")
set(VERSION "beta-9") set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}")
option(STATIC "Link libraries statically, requires static Qt") option(STATIC "Link libraries statically, requires static Qt")
@ -32,7 +32,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE ON)
endif() endif()
set(MONERO_HEAD "d4257af2e7503fc6dc09fc704606230d353a0a02") set(MONERO_HEAD "bdd284b35d2e2c9c6ac733b4bc5ce8bd3b1162dd")
set(BUILD_GUI_DEPS ON) set(BUILD_GUI_DEPS ON)
option(ARCH "Target architecture" "x86-64") option(ARCH "Target architecture" "x86-64")
set(BUILD_64 ON) set(BUILD_64 ON)

View file

@ -11,7 +11,12 @@ CMAKEFLAGS = \
release: release:
mkdir -p build/release && \ mkdir -p build/release && \
cd build/release && \ cd build/release && \
cmake -D CMAKE_BUILD_TYPE=Release $(CMAKEFLAGS) ../.. && \ cmake \
-DARCH=x86-64 \
-D BUILD_TAG="linux-x64" \
-D CMAKE_BUILD_TYPE=Release \
$(CMAKEFLAGS) \
../.. && \
$(MAKE) $(MAKE)
release-static: release-static:

View file

@ -2,7 +2,7 @@
# Contributor: wowario <wowario[at]protonmail[dot]com> # Contributor: wowario <wowario[at]protonmail[dot]com>
pkgname='monero-feather-git' pkgname='monero-feather-git'
pkgver=0.8.0.d3791cbd26 pkgver=1.0.0
pkgrel=1 pkgrel=1
pkgdesc='A free Monero desktop wallet' pkgdesc='A free Monero desktop wallet'
license=('BSD') license=('BSD')

View file

@ -0,0 +1,28 @@
Copyright (c) 2020-2021, The Monero Project
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View file

@ -0,0 +1,67 @@
; Feather Wallet Installer for Windows
; Copyright (c) 2021-2021, The Monero Project
#define AppName "Feather Wallet"
#define AppVersion "1.0.2"
#define AppPublisher "Feather Wallet"
#define AppURL "https://featherwallet.org"
#define AppExeName "feather.exe"
[Setup]
AppId={{E3C599C7-4DF1-49F2-9C35-918A288677A4}
AppName={#AppName}
AppVersion={#AppVersion}
;AppVerName={#AppName} {#AppVersion}
AppPublisher={#AppPublisher}
AppPublisherURL={#AppURL}
AppSupportURL={#AppURL}
AppUpdatesURL={#AppURL}
DefaultDirName={autopf}\{#AppName}
DisableDirPage=yes
DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputBaseFilename=FeatherWalletSetup
SetupIconFile=appicon.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern
ArchitecturesInstallIn64BitMode=x64
ArchitecturesAllowed=x64
DisableReadyPage=yes
WizardSmallImageFile=compiler:WizClassicSmallImage.bmp
WizardImageFile=compiler:WizClassicImage.bmp
UninstallDisplayIcon={app}\{#AppExeName}
[Messages]
SetupWindowTitle=Setup - Feather Wallet {#AppVersion}
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}";
[Files]
Source: "bin\{#AppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion
;Source: "C:\Users\dev\Desktop\feather setup\finishbanner.bmp"; Flags: dontcopy
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Code]
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectTasks then
WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall)
end;
[Icons]
Name: "{autoprograms}\{#AppName}"; Filename: "{app}\{#AppExeName}"
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

2
monero

@ -1 +1 @@
Subproject commit d4257af2e7503fc6dc09fc704606230d353a0a02 Subproject commit bdd284b35d2e2c9c6ac733b4bc5ce8bd3b1162dd

View file

@ -23,13 +23,12 @@ CalcWidget::CalcWidget(QWidget *parent)
ui->imageExchange->setFixedSize(26, 26); ui->imageExchange->setFixedSize(26, 26);
// validator/locale for input // validator/locale for input
QLocale lo(QLocale::C); QString amount_rx = R"(^\d{0,8}[\.]\d{0,12}$)";
lo.setNumberOptions(QLocale::RejectGroupSeparator); QRegExp rx;
auto dv = new QDoubleValidator(0.0, 2147483647, 10, this); // [0, 32bit max], 10 decimals of precision rx.setPattern(amount_rx);
dv->setNotation(QDoubleValidator::StandardNotation); QValidator *validator = new QRegExpValidator(rx, this);
dv->setLocale(lo); ui->lineFrom->setValidator(validator);
ui->lineFrom->setValidator(dv); ui->lineTo->setValidator(validator);
ui->lineTo->setValidator(dv);
connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::onPricesReceived); connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::onPricesReceived);
connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::onPricesReceived); connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::onPricesReceived);

View file

@ -26,8 +26,7 @@ CoinsWidget::CoinsWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
connect(ui->coins->header(), &QHeaderView::customContextMenuRequested, this, &CoinsWidget::showHeaderMenu); connect(ui->coins->header(), &QHeaderView::customContextMenuRequested, this, &CoinsWidget::showHeaderMenu);
// copy menu // copy menu
m_copyMenu->setIcon(icons()->icon("copy.png")); m_copyMenu->addAction("Public Key", this, [this]{copy(copyField::PubKey);});
m_copyMenu->addAction("Public key", this, [this]{copy(copyField::PubKey);});
m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);}); m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);});
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
m_copyMenu->addAction("Address", this, [this]{copy(copyField::Address);}); m_copyMenu->addAction("Address", this, [this]{copy(copyField::Address);});
@ -47,12 +46,12 @@ CoinsWidget::CoinsWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
m_freezeAllSelectedAction = new QAction("Freeze selected", this); m_freezeAllSelectedAction = new QAction("Freeze selected", this);
m_thawAllSelectedAction = new QAction("Thaw selected", this); m_thawAllSelectedAction = new QAction("Thaw selected", this);
m_viewOutputAction = new QAction(icons()->icon("info2.svg"), "Details", this); m_viewOutputAction = new QAction("Details", this);
m_sweepOutputAction = new QAction("Sweep output", this); m_sweepOutputAction = new QAction("Sweep output", this);
m_sweepOutputsAction = new QAction("Sweep selected outputs", this); m_sweepOutputsAction = new QAction("Sweep selected outputs", this);
connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeOutput); connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeAllSelected);
connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawOutput); connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawAllSelected);
connect(m_viewOutputAction, &QAction::triggered, this, &CoinsWidget::viewOutput); connect(m_viewOutputAction, &QAction::triggered, this, &CoinsWidget::viewOutput);
connect(m_sweepOutputAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs); connect(m_sweepOutputAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs);
connect(m_sweepOutputsAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs); connect(m_sweepOutputsAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs);
@ -155,36 +154,24 @@ void CoinsWidget::setSearchFilter(const QString &filter) {
m_proxyModel->setSearchFilter(filter); m_proxyModel->setSearchFilter(filter);
} }
void CoinsWidget::freezeOutput() { QStringList CoinsWidget::selectedPubkeys() {
QModelIndex index = ui->coins->currentIndex(); QModelIndexList list = ui->coins->selectionModel()->selectedRows();
QVector<int> indexes = {m_proxyModel->mapToSource(index).row()};
this->freezeCoins(indexes); QStringList pubkeys;
for (QModelIndex index: list) {
pubkeys << m_model->entryFromIndex(m_proxyModel->mapToSource(index))->pubKey();
}
return pubkeys;
} }
void CoinsWidget::freezeAllSelected() { void CoinsWidget::freezeAllSelected() {
QModelIndexList list = ui->coins->selectionModel()->selectedRows(); QStringList pubkeys = this->selectedPubkeys();
this->freezeCoins(pubkeys);
QVector<int> indexes;
for (QModelIndex index: list) {
indexes.push_back(m_proxyModel->mapToSource(index).row()); // todo: will segfault if index get invalidated
}
this->freezeCoins(indexes);
}
void CoinsWidget::thawOutput() {
QModelIndex index = ui->coins->currentIndex();
QVector<int> indexes = {m_proxyModel->mapToSource(index).row()};
this->thawCoins(indexes);
} }
void CoinsWidget::thawAllSelected() { void CoinsWidget::thawAllSelected() {
QModelIndexList list = ui->coins->selectionModel()->selectedRows(); QStringList pubkeys = this->selectedPubkeys();
this->thawCoins(pubkeys);
QVector<int> indexes;
for (QModelIndex index: list) {
indexes.push_back(m_proxyModel->mapToSource(index).row());
}
this->thawCoins(indexes);
} }
void CoinsWidget::viewOutput() { void CoinsWidget::viewOutput() {
@ -291,17 +278,17 @@ QVector<CoinsInfo*> CoinsWidget::currentEntries() {
return selectedCoins; return selectedCoins;
} }
void CoinsWidget::freezeCoins(const QVector<int>& indexes) { void CoinsWidget::freezeCoins(QStringList &pubkeys) {
for (int i : indexes) { for (auto &pubkey : pubkeys) {
m_ctx->wallet->coins()->freeze(i); m_ctx->wallet->coins()->freeze(pubkey);
} }
m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount());
m_ctx->updateBalance(); m_ctx->updateBalance();
} }
void CoinsWidget::thawCoins(const QVector<int> &indexes) { void CoinsWidget::thawCoins(QStringList &pubkeys) {
for (int i : indexes) { for (auto &pubkey : pubkeys) {
m_ctx->wallet->coins()->thaw(i); m_ctx->wallet->coins()->thaw(pubkey);
} }
m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount());
m_ctx->updateBalance(); m_ctx->updateBalance();

View file

@ -33,9 +33,7 @@ public slots:
private slots: private slots:
void showHeaderMenu(const QPoint& position); void showHeaderMenu(const QPoint& position);
void setShowSpent(bool show); void setShowSpent(bool show);
void freezeOutput();
void freezeAllSelected(); void freezeAllSelected();
void thawOutput();
void thawAllSelected(); void thawAllSelected();
void viewOutput(); void viewOutput();
void onSweepOutputs(); void onSweepOutputs();
@ -43,8 +41,8 @@ private slots:
void editLabel(); void editLabel();
private: private:
void freezeCoins(const QVector<int>& indexes); void freezeCoins(QStringList &pubkeys);
void thawCoins(const QVector<int>& indexes); void thawCoins(QStringList &pubkeys);
enum copyField { enum copyField {
PubKey = 0, PubKey = 0,
@ -79,6 +77,7 @@ private:
void copy(copyField field); void copy(copyField field);
CoinsInfo* currentEntry(); CoinsInfo* currentEntry();
QVector<CoinsInfo*> currentEntries(); QVector<CoinsInfo*> currentEntries();
QStringList selectedPubkeys();
}; };

View file

@ -18,6 +18,11 @@ ContactsWidget::ContactsWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
m_btn_addContact = new QPushButton(this);
m_btn_addContact->setIcon(icons()->icon("localMonero_register.svg"));
ui->searchLayout->addWidget(m_btn_addContact, 0, Qt::AlignRight);
connect(m_btn_addContact, &QPushButton::clicked, [this]{this->newContact();});
m_model = m_ctx->wallet->addressBookModel(); m_model = m_ctx->wallet->addressBookModel();
m_proxyModel = new AddressBookProxyModel; m_proxyModel = new AddressBookProxyModel;
m_proxyModel->setSourceModel(m_model); m_proxyModel->setSourceModel(m_model);
@ -45,7 +50,7 @@ ContactsWidget::ContactsWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
// context menu // context menu
ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu); ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu);
m_contextMenu = new QMenu(ui->contacts); m_contextMenu = new QMenu(ui->contacts);
m_contextMenu->addAction(icons()->icon("person.svg"), "New contact", [this]{ m_contextMenu->addAction("New contact", [this]{
this->newContact(); this->newContact();
}); });

View file

@ -4,6 +4,7 @@
#ifndef FEATHER_CONTACTSWIDGET_H #ifndef FEATHER_CONTACTSWIDGET_H
#define FEATHER_CONTACTSWIDGET_H #define FEATHER_CONTACTSWIDGET_H
#include <QPushButton>
#include <QWidget> #include <QWidget>
#include <QMenu> #include <QMenu>
@ -51,6 +52,7 @@ private:
QMenu *m_headerMenu; QMenu *m_headerMenu;
AddressBookModel * m_model; AddressBookModel * m_model;
AddressBookProxyModel * m_proxyModel; AddressBookProxyModel * m_proxyModel;
QPushButton *m_btn_addContact;
}; };
#endif // FEATHER_CONTACTSWIDGET_H #endif // FEATHER_CONTACTSWIDGET_H

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>589</width> <width>914</width>
<height>416</height> <height>763</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -30,14 +30,24 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QLineEdit" name="search"> <layout class="QHBoxLayout" name="searchLayout">
<property name="placeholderText"> <item>
<string>Search contacts...</string> <widget class="QLineEdit" name="search">
</property> <property name="placeholderText">
</widget> <string>Search contacts...</string>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QTreeView" name="contacts"> <widget class="QTreeView" name="contacts">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="rootIsDecorated"> <property name="rootIsDecorated">
<bool>false</bool> <bool>false</bool>
</property> </property>

View file

@ -11,6 +11,7 @@
#include "dialog/TxProofDialog.h" #include "dialog/TxProofDialog.h"
#include "utils/config.h" #include "utils/config.h"
#include "utils/Icons.h" #include "utils/Icons.h"
#include "WebsocketNotifier.h"
HistoryWidget::HistoryWidget(QSharedPointer<AppContext> ctx, QWidget *parent) HistoryWidget::HistoryWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@ -26,10 +27,9 @@ HistoryWidget::HistoryWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
// copy menu // copy menu
m_copyMenu->setIcon(icons()->icon("copy.png"));
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);});
m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);}); m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);});
m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);});
m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);}); m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);});
ui->history->setContextMenuPolicy(Qt::CustomContextMenu); ui->history->setContextMenuPolicy(Qt::CustomContextMenu);
@ -53,7 +53,10 @@ HistoryWidget::HistoryWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
ui->syncNotice->setVisible(config()->get(Config::showHistorySyncNotice).toBool()); ui->syncNotice->setVisible(config()->get(Config::showHistorySyncNotice).toBool());
ui->history->setHistoryModel(m_model); ui->history->setHistoryModel(m_model);
m_ctx->wallet->transactionHistoryModel()->amountPrecision = config()->get(Config::amountPrecision).toInt();
connect(websocketNotifier(), &WebsocketNotifier::FiatRatesReceived, [this]{
ui->history->update();
});
// Load view state // Load view state
QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()); QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray());
@ -88,9 +91,9 @@ void HistoryWidget::showContextMenu(const QPoint &point) {
} }
menu.addMenu(m_copyMenu); menu.addMenu(m_copyMenu);
menu.addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails); menu.addAction("Show details", this, &HistoryWidget::showTxDetails);
menu.addAction(icons()->icon("network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); menu.addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof); menu.addAction("Create Tx Proof", this, &HistoryWidget::createTxProof);
menu.exec(ui->history->viewport()->mapToGlobal(point)); menu.exec(ui->history->viewport()->mapToGlobal(point));
} }
@ -121,6 +124,7 @@ void HistoryWidget::showTxDetails() {
emit resendTransaction(txid); emit resendTransaction(txid);
}); });
dialog->show(); dialog->show();
dialog->setAttribute(Qt::WA_DeleteOnClose);
} }
void HistoryWidget::onViewOnBlockExplorer() { void HistoryWidget::onViewOnBlockExplorer() {
@ -157,10 +161,13 @@ void HistoryWidget::copy(copyField field) {
switch(field) { switch(field) {
case copyField::TxID: case copyField::TxID:
return tx->hash(); return tx->hash();
case copyField::Description:
return tx->description();
case copyField::Date: case copyField::Date:
return tx->timestamp().toString("yyyy-MM-dd HH:mm"); return tx->timestamp().toString(QString("%1 %2").arg(config()->get(Config::dateFormat).toString(),
config()->get(Config::timeFormat).toString()));
case copyField::Amount: case copyField::Amount:
return tx->displayAmount(); return WalletManager::displayAmount(tx->balanceDelta());
default: default:
return QString(""); return QString("");
} }

View file

@ -47,7 +47,6 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
// Ensure the destructor is called after closeEvent() // Ensure the destructor is called after closeEvent()
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
m_windowSettings = new Settings(m_ctx, this);
m_windowCalc = new CalcWindow(this); m_windowCalc = new CalcWindow(this);
m_splashDialog = new SplashDialog(this); m_splashDialog = new SplashDialog(this);
@ -68,21 +67,12 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
#endif #endif
websocketNotifier()->emitCache(); // Get cached data websocketNotifier()->emitCache(); // Get cached data
// Settings
for (const auto &widget: m_priceTickerWidgets)
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::updateDisplay);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx.get(), &AppContext::onPreferredFiatCurrencyChanged);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged));
connect(m_windowSettings, &Settings::amountPrecisionChanged, m_ctx.get(), &AppContext::onAmountPrecisionChanged);
connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged);
QTimer::singleShot(1, [this]{this->updateWidgetIcons();});
connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged); connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged);
connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged); connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged);
this->onTorConnectionStateChanged(torManager()->torConnected); this->onTorConnectionStateChanged(torManager()->torConnected);
ColorScheme::updateFromWidget(this); ColorScheme::updateFromWidget(this);
QTimer::singleShot(1, [this]{this->updateWidgetIcons();});
// Timers // Timers
connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats); connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats);
@ -105,6 +95,10 @@ void MainWindow::initStatusBar() {
this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}"); this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}");
#endif #endif
#if defined(Q_OS_MACOS)
this->patchStylesheetMac();
#endif
this->statusBar()->setFixedHeight(30); this->statusBar()->setFixedHeight(30);
m_statusLabelStatus = new QLabel("Idle", this); m_statusLabelStatus = new QLabel("Idle", this);
@ -327,6 +321,7 @@ void MainWindow::initMenu() {
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
connect(ui->actionOfficialWebsite, &QAction::triggered, [this](){Utils::externalLinkWarning(this, "https://featherwallet.org");}); connect(ui->actionOfficialWebsite, &QAction::triggered, [this](){Utils::externalLinkWarning(this, "https://featherwallet.org");});
connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked); connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked);
connect(ui->actionDocumentation, &QAction::triggered, this, &MainWindow::onShowDocumentaton);
connect(ui->actionReport_bug, &QAction::triggered, this, &MainWindow::onReportBug); connect(ui->actionReport_bug, &QAction::triggered, this, &MainWindow::onReportBug);
connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo); connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo);
@ -341,6 +336,7 @@ void MainWindow::initMenu() {
ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S")); ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S"));
ui->actionUpdate_balance->setShortcut(QKeySequence("Ctrl+U")); ui->actionUpdate_balance->setShortcut(QKeySequence("Ctrl+U"));
ui->actionShow_Searchbar->setShortcut(QKeySequence("Ctrl+F")); ui->actionShow_Searchbar->setShortcut(QKeySequence("Ctrl+F"));
ui->actionDocumentation->setShortcut(QKeySequence("F1"));
} }
void MainWindow::initHome() { void MainWindow::initHome() {
@ -626,9 +622,9 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
m_ctx->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0)); m_ctx->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0));
// Show advanced dialog on multi-destination transactions // Show advanced dialog on multi-destination transactions
if (address.size() > 1) { if (address.size() > 1 || m_ctx->wallet->viewOnly()) {
TxConfAdvDialog dialog_adv{m_ctx, m_ctx->tmpTxDescription, this}; TxConfAdvDialog dialog_adv{m_ctx, m_ctx->tmpTxDescription, this};
dialog_adv.setTransaction(tx); dialog_adv.setTransaction(tx, !m_ctx->wallet->viewOnly());
dialog_adv.exec(); dialog_adv.exec();
return; return;
} }
@ -642,7 +638,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
break; break;
} }
case QDialog::Accepted: case QDialog::Accepted:
m_ctx->commitTransaction(tx); m_ctx->commitTransaction(tx, m_ctx->tmpTxDescription);
break; break;
} }
@ -666,9 +662,10 @@ void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, con
if (msgBox.clickedButton() == showDetailsButton) { if (msgBox.clickedButton() == showDetailsButton) {
this->showHistoryTab(); this->showHistoryTab();
TransactionInfo *txInfo = m_ctx->wallet->history()->transaction(txid.first()); TransactionInfo *txInfo = m_ctx->wallet->history()->transaction(txid.first());
TxInfoDialog dialog{m_ctx, txInfo, this}; auto *dialog = new TxInfoDialog(m_ctx, txInfo, this);
connect(&dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction); connect(dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction);
dialog.exec(); dialog->show();
dialog->setAttribute(Qt::WA_DeleteOnClose);
} }
m_sendWidget->clearFields(); m_sendWidget->clearFields();
@ -832,9 +829,14 @@ void MainWindow::menuAboutClicked() {
} }
void MainWindow::menuSettingsClicked() { void MainWindow::menuSettingsClicked() {
m_windowSettings->raise(); Settings settings{m_ctx, this};
m_windowSettings->show(); for (const auto &widget: m_priceTickerWidgets) {
m_windowSettings->activateWindow(); connect(&settings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::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);
settings.exec();
} }
void MainWindow::menuSignVerifyClicked() { void MainWindow::menuSignVerifyClicked() {
@ -851,6 +853,10 @@ void MainWindow::skinChanged(const QString &skinName) {
m_windowManager->changeSkin(skinName); m_windowManager->changeSkin(skinName);
ColorScheme::updateFromWidget(this); ColorScheme::updateFromWidget(this);
this->updateWidgetIcons(); this->updateWidgetIcons();
#if defined(Q_OS_MACOS)
this->patchStylesheetMac();
#endif
} }
void MainWindow::updateWidgetIcons() { void MainWindow::updateWidgetIcons() {
@ -1042,7 +1048,7 @@ void MainWindow::showWSNodeExhaustedMessage() {
} }
void MainWindow::exportKeyImages() { void MainWindow::exportKeyImages() {
QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QDir::homePath(), "Key Images (*_keyImages)"); 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; if (fn.isEmpty()) return;
if (!fn.endsWith("_keyImages")) fn += "_keyImages"; if (!fn.endsWith("_keyImages")) fn += "_keyImages";
m_ctx->wallet->exportKeyImages(fn, true); m_ctx->wallet->exportKeyImages(fn, true);
@ -1068,7 +1074,7 @@ void MainWindow::importKeyImages() {
} }
void MainWindow::exportOutputs() { void MainWindow::exportOutputs() {
QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QDir::homePath(), "Outputs (*_outputs)"); QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Outputs (*_outputs)");
if (fn.isEmpty()) return; if (fn.isEmpty()) return;
if (!fn.endsWith("_outputs")) fn += "_outputs"; if (!fn.endsWith("_outputs")) fn += "_outputs";
m_ctx->wallet->exportOutputs(fn, true); m_ctx->wallet->exportOutputs(fn, true);
@ -1464,14 +1470,12 @@ void MainWindow::onCreateDesktopEntry(bool checked) {
QMessageBox::information(this, "Desktop entry", msg); QMessageBox::information(this, "Desktop entry", msg);
} }
void MainWindow::onShowDocumentaton() {
Utils::externalLinkWarning(this, "https://docs.featherwallet.org");
}
void MainWindow::onReportBug(bool checked) { void MainWindow::onReportBug(bool checked) {
QMessageBox::information(this, "Reporting Bugs", Utils::externalLinkWarning(this, "https://docs.featherwallet.org/guides/report-an-issue");
"<body>Please report any bugs as issues on our git repo:<br>\n"
"<a href=\"https://git.featherwallet.org/feather/feather/issues\" style=\"color: #33A4DF\">https://git.featherwallet.org/feather/feather/issues</a><br/><br/>"
"\n"
"Before reporting a bug, upgrade to the most recent version of Feather "
"(latest release or git HEAD), and include the version number in your report. "
"Try to explain not only what the bug is, but how it occurs.</body>");
} }
QString MainWindow::getPlatformTag() { QString MainWindow::getPlatformTag() {
@ -1590,6 +1594,14 @@ bool MainWindow::verifyPassword() {
return true; 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::toggleSearchbar(bool visible) { void MainWindow::toggleSearchbar(bool visible) {
config()->set(Config::showSearchbar, visible); config()->set(Config::showSearchbar, visible);

View file

@ -117,6 +117,7 @@ private slots:
void onExportHistoryCSV(bool checked); void onExportHistoryCSV(bool checked);
void onExportContactsCSV(bool checked); void onExportContactsCSV(bool checked);
void onCreateDesktopEntry(bool checked); void onCreateDesktopEntry(bool checked);
void onShowDocumentaton();
void onReportBug(bool checked); void onReportBug(bool checked);
// offline tx signing // offline tx signing
@ -214,6 +215,7 @@ private:
void updateRecentlyOpenedMenu(); void updateRecentlyOpenedMenu();
void updateWidgetIcons(); void updateWidgetIcons();
bool verifyPassword(); bool verifyPassword();
void patchStylesheetMac();
QIcon hardwareDevicePairedIcon(); QIcon hardwareDevicePairedIcon();
QIcon hardwareDeviceUnpairedIcon(); QIcon hardwareDeviceUnpairedIcon();
@ -222,7 +224,6 @@ private:
WindowManager *m_windowManager; WindowManager *m_windowManager;
QSharedPointer<AppContext> m_ctx; QSharedPointer<AppContext> m_ctx;
Settings *m_windowSettings = nullptr;
CalcWindow *m_windowCalc = nullptr; CalcWindow *m_windowCalc = nullptr;
SplashDialog *m_splashDialog = nullptr; SplashDialog *m_splashDialog = nullptr;

View file

@ -465,10 +465,12 @@
</property> </property>
<addaction name="actionAbout"/> <addaction name="actionAbout"/>
<addaction name="actionOfficialWebsite"/> <addaction name="actionOfficialWebsite"/>
<addaction name="actionDonate_to_Feather"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionDocumentation"/>
<addaction name="actionReport_bug"/> <addaction name="actionReport_bug"/>
<addaction name="actionShow_debug_info"/> <addaction name="actionShow_debug_info"/>
<addaction name="separator"/>
<addaction name="actionDonate_to_Feather"/>
</widget> </widget>
<widget class="QMenu" name="menuView"> <widget class="QMenu" name="menuView">
<property name="title"> <property name="title">
@ -803,6 +805,11 @@
<string>Address checker</string> <string>Address checker</string>
</property> </property>
</action> </action>
<action name="actionDocumentation">
<property name="text">
<string>Documentation</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

View file

@ -7,6 +7,7 @@
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include "dialog/PaymentRequestDialog.h"
#include "dialog/QrCodeDialog.h" #include "dialog/QrCodeDialog.h"
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
#include "utils/Icons.h" #include "utils/Icons.h"
@ -43,8 +44,6 @@ ReceiveWidget::ReceiveWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
m_headerMenu = new QMenu(this); m_headerMenu = new QMenu(this);
m_showFullAddressesAction = m_headerMenu->addAction("Show full addresses", this, &ReceiveWidget::setShowFullAddresses); m_showFullAddressesAction = m_headerMenu->addAction("Show full addresses", this, &ReceiveWidget::setShowFullAddresses);
m_showFullAddressesAction->setCheckable(true); m_showFullAddressesAction->setCheckable(true);
m_showUsedAddressesAction = m_headerMenu->addAction("Show used addresses", this, &ReceiveWidget::setShowUsedAddresses);
m_showUsedAddressesAction->setCheckable(true);
connect(ui->addresses->header(), &QHeaderView::customContextMenuRequested, this, &ReceiveWidget::showHeaderMenu); connect(ui->addresses->header(), &QHeaderView::customContextMenuRequested, this, &ReceiveWidget::showHeaderMenu);
// context menu // context menu
@ -60,6 +59,8 @@ ReceiveWidget::ReceiveWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
connect(ui->check_showUsed, &QCheckBox::clicked, this, &ReceiveWidget::setShowUsedAddresses); connect(ui->check_showUsed, &QCheckBox::clicked, this, &ReceiveWidget::setShowUsedAddresses);
connect(ui->check_showHidden, &QCheckBox::clicked, this, &ReceiveWidget::setShowHiddenAddresses); connect(ui->check_showHidden, &QCheckBox::clicked, this, &ReceiveWidget::setShowHiddenAddresses);
connect(ui->btn_createPaymentRequest, &QPushButton::clicked, this, &ReceiveWidget::createPaymentRequest);
} }
void ReceiveWidget::setSearchbarVisible(bool visible) { void ReceiveWidget::setSearchbarVisible(bool visible) {
@ -96,9 +97,9 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
auto *menu = new QMenu(ui->addresses); auto *menu = new QMenu(ui->addresses);
menu->addAction(icons()->icon("copy.png"), "Copy address", this, &ReceiveWidget::copyAddress); menu->addAction("Copy address", this, &ReceiveWidget::copyAddress);
menu->addAction(icons()->icon("copy.png"), "Copy label", this, &ReceiveWidget::copyLabel); menu->addAction("Copy label", this, &ReceiveWidget::copyLabel);
menu->addAction(icons()->icon("edit.png"), "Edit label", this, &ReceiveWidget::editLabel); menu->addAction("Edit label", this, &ReceiveWidget::editLabel);
if (isUsed) { if (isUsed) {
menu->addAction(m_showTransactionsAction); menu->addAction(m_showTransactionsAction);
@ -106,7 +107,7 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
QStringList hiddenAddresses = this->getHiddenAddresses(); QStringList hiddenAddresses = this->getHiddenAddresses();
if (hiddenAddresses.contains(address)) { if (hiddenAddresses.contains(address)) {
menu->addAction("Show address", this, &ReceiveWidget::showAddress); menu->addAction("Unhide address", this, &ReceiveWidget::showAddress);
} else { } else {
menu->addAction("Hide address", this, &ReceiveWidget::hideAddress); menu->addAction("Hide address", this, &ReceiveWidget::hideAddress);
} }
@ -118,6 +119,18 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
menu->popup(ui->addresses->viewport()->mapToGlobal(point)); menu->popup(ui->addresses->viewport()->mapToGlobal(point));
} }
void ReceiveWidget::createPaymentRequest() {
QModelIndex index = ui->addresses->currentIndex();
if (!index.isValid()) {
return;
}
QString address = index.model()->data(index.siblingAtColumn(SubaddressModel::Address), Qt::UserRole).toString();
PaymentRequestDialog dialog{this, m_ctx, address};
dialog.exec();
}
void ReceiveWidget::onShowTransactions() { void ReceiveWidget::onShowTransactions() {
QModelIndex index = ui->addresses->currentIndex(); QModelIndex index = ui->addresses->currentIndex();
if (!index.isValid()) { if (!index.isValid()) {
@ -190,6 +203,7 @@ void ReceiveWidget::updateQrCode(){
QModelIndex index = ui->addresses->currentIndex(); QModelIndex index = ui->addresses->currentIndex();
if (!index.isValid()) { if (!index.isValid()) {
ui->qrCode->clear(); ui->qrCode->clear();
ui->btn_createPaymentRequest->hide();
return; return;
} }
@ -199,6 +213,7 @@ void ReceiveWidget::updateQrCode(){
int width = ui->qrCode->width() - 4; int width = ui->qrCode->width() - 4;
if (qrc.isValid()) { if (qrc.isValid()) {
ui->qrCode->setPixmap(qrc.toPixmap(1).scaled(width, width, Qt::KeepAspectRatio)); ui->qrCode->setPixmap(qrc.toPixmap(1).scaled(width, width, Qt::KeepAspectRatio));
ui->btn_createPaymentRequest->show();
} }
} }

View file

@ -39,6 +39,7 @@ public slots:
void setShowHiddenAddresses(bool show); void setShowHiddenAddresses(bool show);
void setSearchFilter(const QString &filter); void setSearchFilter(const QString &filter);
void onShowTransactions(); void onShowTransactions();
void createPaymentRequest();
signals: signals:
void showTransactions(const QString& address); void showTransactions(const QString& address);

View file

@ -69,6 +69,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="btn_createPaymentRequest">
<property name="text">
<string>Payment Request</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer_4"> <spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">

View file

@ -26,13 +26,13 @@ SendWidget::SendWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)"; QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)";
QRegExp rx; QRegExp rx;
rx.setPattern(amount_rx); rx.setPattern(amount_rx);
QValidator *validator = new QRegExpValidator(rx, this); QValidator *validator = new QRegExpValidator(rx, this);
ui->lineAmount->setValidator(validator); ui->lineAmount->setValidator(validator);
connect(m_ctx.get(), &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction); connect(m_ctx.get(), &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction);
connect(m_ctx.get(), &AppContext::endTransaction, this, &SendWidget::onEndTransaction); connect(m_ctx.get(), &AppContext::endTransaction, this, &SendWidget::onEndTransaction);
connect(m_ctx.get(), &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved);
connect(m_ctx.get(), &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError); connect(WalletManager::instance(), &WalletManager::openAliasResolved, this, &SendWidget::onOpenAliasResolved);
connect(ui->btnScan, &QPushButton::clicked, this, &SendWidget::scanClicked); connect(ui->btnScan, &QPushButton::clicked, this, &SendWidget::scanClicked);
connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked); connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked);
@ -139,8 +139,8 @@ void SendWidget::sendClicked() {
QString currency = ui->comboCurrencySelection->currentText(); QString currency = ui->comboCurrencySelection->currentText();
QString recipient = ui->lineAddress->text().simplified().remove(' '); QString recipient = ui->lineAddress->text().simplified().remove(' ');
QString description = ui->lineDescription->text(); QString description = ui->lineDescription->text();
if(recipient.isEmpty()) { if (recipient.isEmpty()) {
QMessageBox::warning(this, "Malformed recipient", "The recipient address was not correct"); QMessageBox::warning(this, "Malformed recipient", "No destination address was entered.");
return; return;
} }
@ -178,7 +178,7 @@ void SendWidget::sendClicked() {
amount = this->amount(); amount = this->amount();
bool sendAll = (ui->lineAmount->text() == "all"); bool sendAll = (ui->lineAmount->text() == "all");
if (amount == 0 && !sendAll) { if (amount == 0 && !sendAll) {
QMessageBox::warning(this, "Amount error", "Invalid amount specified."); QMessageBox::warning(this, "Amount error", "No amount was entered.");
return; return;
} }
m_ctx->onCreateTransaction(recipient, amount, description, sendAll); m_ctx->onCreateTransaction(recipient, amount, description, sendAll);
@ -193,8 +193,9 @@ void SendWidget::sendClicked() {
} }
void SendWidget::aliasClicked() { void SendWidget::aliasClicked() {
auto address = ui->lineAddress->text(); ui->btn_openAlias->setEnabled(false);
m_ctx->onOpenAliasResolve(address); auto alias = ui->lineAddress->text();
WalletManager::instance()->resolveOpenAliasAsync(alias);
} }
void SendWidget::clearClicked() { void SendWidget::clearClicked() {
@ -254,7 +255,26 @@ double SendWidget::amountDouble() {
return amount / constants::cdiv; return amount / constants::cdiv;
} }
void SendWidget::onOpenAliasResolved(const QString &address, const QString &openAlias) { void SendWidget::onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid) {
ui->btn_openAlias->setEnabled(true);
if (address.isEmpty()) {
this->onOpenAliasResolveError("Could not resolve OpenAlias.");
return;
}
if (!dnssecValid) {
this->onOpenAliasResolveError("Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed.");
return;
}
bool valid = WalletManager::addressValid(address, constants::networkType);
if (!valid) {
this->onOpenAliasResolveError(QString("Address validation error. Perhaps it is of the wrong network type.\n\n"
"OpenAlias: %1\nAddress: %2").arg(openAlias, address));
return;
}
this->fill(address, openAlias); this->fill(address, openAlias);
ui->btn_openAlias->hide(); ui->btn_openAlias->hide();
} }

View file

@ -38,7 +38,7 @@ public slots:
void fillAddress(const QString &address); void fillAddress(const QString &address);
void updateConversionLabel(); void updateConversionLabel();
void onOpenAliasResolveError(const QString &err); void onOpenAliasResolveError(const QString &err);
void onOpenAliasResolved(const QString &address, const QString &openAlias); void onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid);
void onPreferredFiatCurrencyChanged(); void onPreferredFiatCurrencyChanged();
void onInitiateTransaction(); void onInitiateTransaction();

View file

@ -79,8 +79,9 @@ Settings::Settings(QSharedPointer<AppContext> ctx, QWidget *parent)
// Preferred fiat currency combobox // Preferred fiat currency combobox
QStringList fiatCurrencies; QStringList fiatCurrencies;
for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) {
fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index); fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index);
}
auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString();
if(!preferredFiatCurrency.isEmpty()) if(!preferredFiatCurrency.isEmpty())

View file

@ -102,14 +102,6 @@ void WindowManager::startupWarning() {
this->showWarningMessageBox("Warning", worthlessWarning.arg("testnet")); this->showWarningMessageBox("Warning", worthlessWarning.arg("testnet"));
config()->set(Config::warnOnTestnet, false); config()->set(Config::warnOnTestnet, false);
} }
// Beta
if (config()->get(Config::warnOnAlpha).toBool()) {
QString warning = "Feather Wallet is currently in beta.\n\nPlease report any bugs "
"you encounter on our Git repository, IRC or on /r/FeatherWallet.";
this->showWarningMessageBox("Beta warning", warning);
config()->set(Config::warnOnAlpha, false);
}
} }
void WindowManager::showWarningMessageBox(const QString &title, const QString &message) { void WindowManager::showWarningMessageBox(const QString &title, const QString &message) {
@ -460,8 +452,6 @@ void WindowManager::initTor() {
torManager()->init(); torManager()->init();
torManager()->start(); torManager()->start();
connect(torManager(), &TorManager::connectionStateChanged, &websocketNotifier()->websocketClient, &WebsocketClient::onToggleConnect);
this->onTorSettingsChanged(); this->onTorSettingsChanged();
} }

View file

@ -55,9 +55,6 @@ AppContext::AppContext(Wallet *wallet)
this->updateBalance(); this->updateBalance();
// force trigger preferredFiat signal for history model
this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString());
connect(this->wallet->history(), &TransactionHistory::txNoteChanged, [this]{ connect(this->wallet->history(), &TransactionHistory::txNoteChanged, [this]{
this->wallet->history()->refresh(this->wallet->currentSubaddressAccount()); this->wallet->history()->refresh(this->wallet->currentSubaddressAccount());
}); });
@ -75,7 +72,8 @@ void AppContext::onCreateTransaction(const QString &address, quint64 amount, con
quint64 unlocked_balance = this->wallet->unlockedBalance(); quint64 unlocked_balance = this->wallet->unlockedBalance();
if (!all && amount > unlocked_balance) { if (!all && amount > unlocked_balance) {
emit createTransactionError("Not enough money to spend"); emit createTransactionError(QString("Not enough money to spend.\n\n"
"Spendable balance: %1").arg(WalletManager::displayAmount(unlocked_balance)));
return; return;
} else if (unlocked_balance == 0) { } else if (unlocked_balance == 0) {
emit createTransactionError("No money to spend"); emit createTransactionError("No money to spend");
@ -132,14 +130,14 @@ void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector<QStri
this->wallet->disposeTransaction(tx); this->wallet->disposeTransaction(tx);
} }
void AppContext::commitTransaction(PendingTransaction *tx) { void AppContext::commitTransaction(PendingTransaction *tx, const QString &description) {
// Nodes - even well-connected, properly configured ones - consistently fail to relay transactions // Nodes - even well-connected, properly configured ones - consistently fail to relay transactions
// To mitigate transactions failing we just send the transaction to every node we know about over Tor // To mitigate transactions failing we just send the transaction to every node we know about over Tor
if (config()->get(Config::multiBroadcast).toBool()) { if (config()->get(Config::multiBroadcast).toBool()) {
this->onMultiBroadcast(tx); this->onMultiBroadcast(tx);
} }
this->wallet->commitTransactionAsync(tx); this->wallet->commitTransactionAsync(tx, description);
} }
void AppContext::onMultiBroadcast(PendingTransaction *tx) { void AppContext::onMultiBroadcast(PendingTransaction *tx) {
@ -167,21 +165,6 @@ QString AppContext::getCacheTransaction(const QString &txid) const {
return txHex; return txHex;
} }
// ################## Models ##################
void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) {
auto *model = this->wallet->transactionHistoryModel();
if (model != nullptr) {
model->preferredFiatSymbol = symbol;
}
}
void AppContext::onAmountPrecisionChanged(int precision) {
auto *model = this->wallet->transactionHistoryModel();
if (!model) return;
model->amountPrecision = precision;
}
// ################## Device ################## // ################## Device ##################
void AppContext::onDeviceButtonRequest(quint64 code) { void AppContext::onDeviceButtonRequest(quint64 code) {
@ -228,48 +211,6 @@ void AppContext::onSetRestoreHeight(quint64 height){
emit customRestoreHeightSet(height); emit customRestoreHeightSet(height);
} }
void AppContext::onOpenAliasResolve(const QString &openAlias) {
// @TODO: calling this freezes for about 1-2 seconds :/
const auto result = WalletManager::instance()->resolveOpenAlias(openAlias);; // TODO: async call
const auto spl = result.split("|");
auto msg = QString("");
if(spl.count() != 2) {
msg = "Internal error";
emit openAliasResolveError(msg);
return;
}
const auto &status = spl.at(0);
const auto &address = spl.at(1);
const auto valid = WalletManager::addressValid(address, constants::networkType);
if(status == "false"){
if(valid){
msg = "Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed";
emit openAliasResolveError(msg);
return;
} else {
msg = "No valid address found at this OpenAlias address, but the DNSSEC signatures could not be verified, so this may be spoofed";
emit openAliasResolveError(msg);
return;
}
} else if(status != "true") {
msg = "Internal error";
emit openAliasResolveError(msg);
return;
}
if(valid){
emit openAliasResolved(address, openAlias);
return;
}
msg = QString("Address validation error.");
if(!address.isEmpty())
msg += QString(" Perhaps it is of the wrong network type."
"\n\nOpenAlias: %1\nAddress: %2").arg(openAlias).arg(address);
emit openAliasResolveError(msg);
}
void AppContext::stopTimers() { void AppContext::stopTimers() {
m_storeTimer.stop(); m_storeTimer.stop();
} }
@ -365,13 +306,6 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QStr
} }
void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){ void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){
if (status) {
for (const auto &entry: txid) {
this->wallet->setUserNote(entry, this->tmpTxDescription);
}
this->tmpTxDescription = "";
}
// Store wallet immediately so we don't risk losing tx key if wallet crashes // Store wallet immediately so we don't risk losing tx key if wallet crashes
this->wallet->store(); this->wallet->store();

View file

@ -9,7 +9,6 @@
#include "utils/os/whonix.h" #include "utils/os/whonix.h"
#include "utils/networking.h" #include "utils/networking.h"
#include "utils/wsclient.h"
#include "utils/FeatherSeed.h" #include "utils/FeatherSeed.h"
#include "utils/daemonrpc.h" #include "utils/daemonrpc.h"
#include "utils/RestoreHeightLookup.h" #include "utils/RestoreHeightLookup.h"
@ -38,7 +37,7 @@ public:
// libwalletqt // libwalletqt
bool refreshed = false; bool refreshed = false;
void commitTransaction(PendingTransaction *tx); void commitTransaction(PendingTransaction *tx, const QString &description="");
void syncStatusUpdated(quint64 height, quint64 target); void syncStatusUpdated(quint64 height, quint64 target);
void updateBalance(); void updateBalance();
void refreshModels(); void refreshModels();
@ -56,10 +55,7 @@ public slots:
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address); void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
void onSweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs); void onSweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs);
void onCreateTransactionError(const QString &msg); void onCreateTransactionError(const QString &msg);
void onOpenAliasResolve(const QString &openAlias);
void onSetRestoreHeight(quint64 height); void onSetRestoreHeight(quint64 height);
void onPreferredFiatCurrencyChanged(const QString &symbol);
void onAmountPrecisionChanged(int precision);
void onMultiBroadcast(PendingTransaction *tx); void onMultiBroadcast(PendingTransaction *tx);
void onDeviceButtonRequest(quint64 code); void onDeviceButtonRequest(quint64 code);
void onDeviceButtonPressed(); void onDeviceButtonPressed();
@ -89,8 +85,6 @@ signals:
void createTransactionError(QString message); void createTransactionError(QString message);
void createTransactionCancelled(const QVector<QString> &address, quint64 amount); void createTransactionCancelled(const QVector<QString> &address, quint64 amount);
void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address); void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void openAliasResolveError(const QString &msg);
void openAliasResolved(const QString &address, const QString &openAlias);
void setRestoreHeightError(const QString &msg); void setRestoreHeightError(const QString &msg);
void customRestoreHeightSet(int height); void customRestoreHeightSet(int height);
void initiateTransaction(); void initiateTransaction();

View file

@ -2,8 +2,8 @@
<qresource prefix="/"> <qresource prefix="/">
<file>assets/about.txt</file> <file>assets/about.txt</file>
<file>assets/ack.txt</file> <file>assets/ack.txt</file>
<file>assets/contributors.txt</file>
<file>assets/feather.desktop</file> <file>assets/feather.desktop</file>
<file>assets/macStylesheet.patch</file>
<file>assets/nodes.json</file> <file>assets/nodes.json</file>
<file>assets/gpg_keys/featherwallet.asc</file> <file>assets/gpg_keys/featherwallet.asc</file>
<file>assets/images/appicons/32x32.png</file> <file>assets/images/appicons/32x32.png</file>
@ -35,6 +35,7 @@
<file>assets/images/copy.png</file> <file>assets/images/copy.png</file>
<file>assets/images/cutexmrfox.png</file> <file>assets/images/cutexmrfox.png</file>
<file>assets/images/edit.png</file> <file>assets/images/edit.png</file>
<file>assets/images/external-link.svg</file>
<file>assets/images/exchange.png</file> <file>assets/images/exchange.png</file>
<file>assets/images/exchange_white.png</file> <file>assets/images/exchange_white.png</file>
<file>assets/images/expired.png</file> <file>assets/images/expired.png</file>

View file

@ -1,12 +1,5 @@
Feather <feather_version> (<feather_git_head>) Feather <feather_version> (<feather_git_head>)
Website: https://featherwallet.org
E-mail: dev@featherwallet.org
Created by dsc, tobtoht, and contributors.
Uses icons from the Icons8 icon pack (icons8.com).
Copyright (c) 2020-<current_year>, The Monero Project Copyright (c) 2020-<current_year>, The Monero Project
All rights reserved. All rights reserved.

View file

@ -1,12 +1,82 @@
The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin. The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin.
Feather uses monero-seed written by Tevador, for 14 word mnemonic seeds. Feather uses the monero-seed libary written by Tevador for 14 word mnemonic seeds.
Wizard banner art was adapted from a paper wallet design by the themonera (themonera.art). Wizard banner art was adapted from a paper wallet design by the themonera (themonera.art).
Initial CMake support for the Monero GUI was coded by TheCharlatan/xiphon. • Uses icons from the Icons8 icon pack (icons8.com).
Huge thanks to nioc, fluffypony, wowario, thrmo for help during development. • Initial CMake support for the Monero GUI was coded by TheCharlatan/xiphon.
Some more shoutouts for people for hosting nodes and/or having good ideas: Shoutouts:
dnale0r, dEBRUYNE, binaryFate, lza_menace, jwinterm, kico, wowario
Alex_LocalMonero
anhdres
authentic
BigNastyHammer
binaryFate
bits-of-change
blasty
boldsuck
boogerlad
bruh
btsfav
csd
CzarekNakamoto
deanguss
dEBRUYNE
dnale0r
fluffypony
geonic
GhostintheQubes
GriftKilla
jberman
john_r365
jwinterm
kayabaNerve
kico
kinghat
lozbek
Lyza
lza_menace
Matt Smith
MoneroArbo
moneromooo
mrdeveloper
Nekun
netrik182
nikg83
nioc
noobmaximus
n-peugnet
onionltd
Paris
phoenix1213
qvqc
rating89us
rbrunner
rehrar
rottenwheel
samsunggalaxyplayer
samwhiskey
scoobybejesus
selsta
sethsimmons
Shakybeats
SmashTR
SovereignStreetArt
spoontechtips
stickyf00t
strace
rottenwheel
tevador
thrmo
tinkerwithtor
Vespco
viperperidot
witchman05
wowario
xiphon
xmrscott
xubuntu
yanmaani

View file

@ -1,5 +0,0 @@
dsc
tobtoht
selsta
Diego Salazar
Matt Smith

View file

@ -0,0 +1 @@
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px"><path d="M 5 3 C 3.9069372 3 3 3.9069372 3 5 L 3 19 C 3 20.093063 3.9069372 21 5 21 L 19 21 C 20.093063 21 21 20.093063 21 19 L 21 12 L 19 12 L 19 19 L 5 19 L 5 5 L 12 5 L 12 3 L 5 3 z M 14 3 L 14 5 L 17.585938 5 L 8.2929688 14.292969 L 9.7070312 15.707031 L 19 6.4140625 L 19 10 L 21 10 L 21 3 L 14 3 z"/></svg>

After

Width:  |  Height:  |  Size: 415 B

View file

@ -0,0 +1,31 @@
/* From electrum/gui/qt/stylesheet_patcher.py */
StatusBarButton {
background-color: transparent;
border: 1px solid transparent;
border-radius: 4px;
margin: 0px;
padding: 2px;
}
StatusBarButton:checked {
background-color: transparent;
border: 1px solid #1464A0;
}
StatusBarButton:checked:disabled {
border: 1px solid #14506E;
}
StatusBarButton:pressed {
margin: 1px;
background-color: transparent;
border: 1px solid #1464A0;
}
StatusBarButton:disabled {
border: none;
}
StatusBarButton:hover {
border: 1px solid #148CD2;
}

View file

@ -1,16 +1,22 @@
{ {
"mainnet": { "mainnet": {
"tor": [ "tor": [
"sfprpc5klzs5vyitq2mrooicgk2wcs5ho2nm3niqduvzn5o6ylaslaqd.onion:18089",
"sfprpc2fws6ltnq4hyr7lvpul3nank5layd7q7tyc5h4gy4h77gtabad.onion:18089",
"mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081", "mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081",
"moneroxmrxw44lku6qniyarpwgznpcwml4drq7vb24ppatlcg4kmxpqd.onion:18089", "moneroxmrxw44lku6qniyarpwgznpcwml4drq7vb24ppatlcg4kmxpqd.onion:18089",
"rbpgdckle3h3vi4wwwrh75usqtoc5r3alohy7yyx57isynvay63nacyd.onion:18089",
"6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081", "6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081",
"56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089", "56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089",
"melo7jwjspngus3xhbt5kxeqc4njhokyvh55jfmehplglgmb7a6rb6yd.onion:18081" "melo7jwjspngus3xhbt5kxeqc4njhokyvh55jfmehplglgmb7a6rb6yd.onion:18081",
"ip4zpbps7unk6xhlanqtw24f75akfbl3upeckfjqjks7ftfnk4i73oid.onion:18081",
"ghziyspoobhmp5oun2xcomrmetqiwbvuaegmte3s47nnqv7hkaa64sid.onion:18089",
"xmrnodesarnt4w35aqmu66aart3o324yw6qbnv6pglpof6uqaydzk5id.onion:18081",
"usexmr2eeexmlwpuvsfe6tyjmdqliplb2b7uxju6yrrziq3n7fksnxyd.onion:18081"
], ],
"clearnet": [ "clearnet": [
"node.melo.tools:18081", "node.melo.tools:18081",
"node-1.sethsimmons.me:18089", "node.sethforprivacy.com:18089",
"node2.sethforprivacy.com:18089",
"selsta1.featherwallet.net:18081", "selsta1.featherwallet.net:18081",
"selsta2.featherwallet.net:18081", "selsta2.featherwallet.net:18081",
"node.monerooutreach.org:18081", "node.monerooutreach.org:18081",

View file

@ -114,3 +114,11 @@ ClickableLabel::~ClickableLabel() = default;
void ClickableLabel::mousePressEvent(QMouseEvent* event) { void ClickableLabel::mousePressEvent(QMouseEvent* event) {
emit clicked(); emit clicked();
} }
WindowModalDialog::WindowModalDialog(QWidget *parent)
: QDialog(parent)
{
#ifndef Q_OS_MACOS
this->setWindowModality(Qt::WindowModal);
#endif
}

View file

@ -121,4 +121,11 @@ protected:
}; };
class WindowModalDialog : public QDialog {
Q_OBJECT
public:
explicit WindowModalDialog(QWidget *parent);
};
#endif //FEATHER_COMPONENTS_H #endif //FEATHER_COMPONENTS_H

View file

@ -8,12 +8,11 @@
#include "utils/Utils.h" #include "utils/Utils.h"
AboutDialog::AboutDialog(QWidget *parent) AboutDialog::AboutDialog(QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::AboutDialog) , ui(new Ui::AboutDialog)
, m_model(new QStringListModel(this))
{ {
ui->setupUi(this); ui->setupUi(this);
this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png"));
// cute fox (c) Diego "rehrar" Salazar :-D // cute fox (c) Diego "rehrar" Salazar :-D
QPixmap p(":assets/images/cutexmrfox.png"); QPixmap p(":assets/images/cutexmrfox.png");
ui->aboutImage->setPixmap(p.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)); ui->aboutImage->setPixmap(p.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation));
@ -28,14 +27,6 @@ AboutDialog::AboutDialog(QWidget *parent)
auto ack_text = Utils::barrayToString(ack); auto ack_text = Utils::barrayToString(ack);
ui->ackText->setText(ack_text); ui->ackText->setText(ack_text);
QString contributors = Utils::barrayToString(Utils::fileOpenQRC(":assets/contributors.txt"));
QStringList contributor_list = contributors.split("\n");
m_model->setStringList(contributor_list);
ui->authorView->setHeaderHidden(true);
ui->authorView->setModel(this->m_model);
ui->authorView->header()->setSectionResizeMode(QHeaderView::Stretch);
this->adjustSize(); this->adjustSize();
} }

View file

@ -7,11 +7,13 @@
#include <QDialog> #include <QDialog>
#include <QStringListModel> #include <QStringListModel>
#include "components.h"
namespace Ui { namespace Ui {
class AboutDialog; class AboutDialog;
} }
class AboutDialog : public QDialog class AboutDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT
@ -21,7 +23,6 @@ public:
private: private:
QScopedPointer<Ui::AboutDialog> ui; QScopedPointer<Ui::AboutDialog> ui;
QStringListModel *m_model;
}; };
#endif // FEATHER_ABOUT_H #endif // FEATHER_ABOUT_H

View file

@ -43,7 +43,7 @@
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>a free, open-source Monero wallet</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Feather&lt;/span&gt;: a free, open-source Monero wallet&lt;/p&gt;&lt;p&gt;Created by dsc, tobtoht and contributors&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>
@ -71,18 +71,144 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="Authors"> <widget class="QWidget" name="tab">
<attribute name="title"> <attribute name="title">
<string>Authors</string> <string>Info</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QFormLayout" name="formLayout">
<item> <item row="0" column="0">
<widget class="QTreeView" name="authorView"> <widget class="QLabel" name="label_2">
<property name="editTriggers"> <property name="text">
<set>QAbstractItemView::NoEditTriggers</set> <string>Website:</string>
</property> </property>
<property name="rootIsDecorated"> </widget>
<bool>false</bool> </item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Docs:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Git:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Reddit:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>IRC:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Matrix:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Email:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_8">
<property name="text">
<string>featherwallet.org</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_10">
<property name="text">
<string>docs.featherwallet.org</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_11">
<property name="text">
<string>git.featherwallet.org</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_12">
<property name="text">
<string>/r/FeatherWallet</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_13">
<property name="text">
<string>#feather on OFTC</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_14">
<property name="text">
<string>#_oftc_#feather:matrix.org</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_15">
<property name="text">
<string>dev@featherwallet.org</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Twitter:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="label_17">
<property name="text">
<string>@featherwallet</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>

View file

@ -11,7 +11,7 @@
#include "utils/Icons.h" #include "utils/Icons.h"
AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer<AppContext> ctx, QWidget *parent) AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::AccountSwitcherDialog) , ui(new Ui::AccountSwitcherDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
, m_model(m_ctx->wallet->subaddressAccountModel()) , m_model(m_ctx->wallet->subaddressAccountModel())
@ -25,6 +25,8 @@ AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer<AppContext> ctx, QWi
ui->label_totalBalance->setFont(ModelUtils::getMonospaceFont()); ui->label_totalBalance->setFont(ModelUtils::getMonospaceFont());
ui->label_totalBalance->setText(WalletManager::displayAmount(m_ctx->wallet->balanceAll())); ui->label_totalBalance->setText(WalletManager::displayAmount(m_ctx->wallet->balanceAll()));
this->setWindowModality(Qt::WindowModal);
ui->accounts->setModel(m_proxyModel); ui->accounts->setModel(m_proxyModel);
ui->accounts->setContextMenuPolicy(Qt::CustomContextMenu); ui->accounts->setContextMenuPolicy(Qt::CustomContextMenu);
ui->accounts->setSelectionMode(QAbstractItemView::SingleSelection); ui->accounts->setSelectionMode(QAbstractItemView::SingleSelection);

View file

@ -7,13 +7,14 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
#include "model/SubaddressAccountModel.h" #include "model/SubaddressAccountModel.h"
namespace Ui { namespace Ui {
class AccountSwitcherDialog; class AccountSwitcherDialog;
} }
class AccountSwitcherDialog : public QDialog class AccountSwitcherDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -8,7 +8,7 @@
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet) BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::BalanceDialog) , ui(new Ui::BalanceDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -6,13 +6,14 @@
#include <QDialog> #include <QDialog>
#include "components.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
namespace Ui { namespace Ui {
class BalanceDialog; class BalanceDialog;
} }
class BalanceDialog : public QDialog class BalanceDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -8,7 +8,7 @@
#include "utils/config.h" #include "utils/config.h"
CalcConfigDialog::CalcConfigDialog(QWidget *parent) CalcConfigDialog::CalcConfigDialog(QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::CalcConfigDialog) , ui(new Ui::CalcConfigDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -7,11 +7,13 @@
#include <QDialog> #include <QDialog>
#include <QListWidget> #include <QListWidget>
#include "components.h"
namespace Ui { namespace Ui {
class CalcConfigDialog; class CalcConfigDialog;
} }
class CalcConfigDialog : public QDialog class CalcConfigDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -5,7 +5,7 @@
#include "ContactsDialog.h" #include "ContactsDialog.h"
ContactsDialog::ContactsDialog(QWidget *parent, const QString &address, const QString &name) ContactsDialog::ContactsDialog(QWidget *parent, const QString &address, const QString &name)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::ContactsDialog) , ui(new Ui::ContactsDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -6,11 +6,13 @@
#include <QDialog> #include <QDialog>
#include "components.h"
namespace Ui { namespace Ui {
class ContactsDialog; class ContactsDialog;
} }
class ContactsDialog : public QDialog class ContactsDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -11,7 +11,7 @@
#include "utils/WebsocketNotifier.h" #include "utils/WebsocketNotifier.h"
DebugInfoDialog::DebugInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent) DebugInfoDialog::DebugInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::DebugInfoDialog) , ui(new Ui::DebugInfoDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
{ {
@ -139,7 +139,7 @@ void DebugInfoDialog::copyToClipboad() {
text += QString("Seed type: %1 \n").arg(ui->label_seedType->text()); text += QString("Seed type: %1 \n").arg(ui->label_seedType->text());
text += QString("Device type: %1 \n").arg(ui->label_deviceType->text()); text += QString("Device type: %1 \n").arg(ui->label_deviceType->text());
text += QString("View only: %1 \n").arg(ui->label_viewOnly->text()); text += QString("View only: %1 \n").arg(ui->label_viewOnly->text());
text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text()); text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text());
text += QString("Operating system: %1 \n").arg(ui->label_OS->text()); text += QString("Operating system: %1 \n").arg(ui->label_OS->text());
text += QString("Timestamp: %1 \n").arg(ui->label_timestamp->text()); text += QString("Timestamp: %1 \n").arg(ui->label_timestamp->text());

View file

@ -7,13 +7,14 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
namespace Ui { namespace Ui {
class DebugInfoDialog; class DebugInfoDialog;
} }
class DebugInfoDialog : public QDialog class DebugInfoDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -5,7 +5,7 @@
#include "ui_InfoDialog.h" #include "ui_InfoDialog.h"
InfoDialog::InfoDialog(QWidget *parent, const QString &title, const QString &infoData) InfoDialog::InfoDialog(QWidget *parent, const QString &title, const QString &infoData)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::InfoDialog) , ui(new Ui::InfoDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -6,11 +6,13 @@
#include <QDialog> #include <QDialog>
#include "components.h"
namespace Ui { namespace Ui {
class InfoDialog; class InfoDialog;
} }
class InfoDialog : public QDialog class InfoDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -5,7 +5,7 @@
#include "ui_KeysDialog.h" #include "ui_KeysDialog.h"
KeysDialog::KeysDialog(QSharedPointer<AppContext> ctx, QWidget *parent) KeysDialog::KeysDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::KeysDialog) , ui(new Ui::KeysDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -7,12 +7,13 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
namespace Ui { namespace Ui {
class KeysDialog; class KeysDialog;
} }
class KeysDialog : public QDialog class KeysDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -8,7 +8,7 @@
#include "utils/Utils.h" #include "utils/Utils.h"
LocalMoneroInfoDialog::LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row) LocalMoneroInfoDialog::LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::LocalMoneroInfoDialog) , ui(new Ui::LocalMoneroInfoDialog)
, m_model(model) , m_model(model)
, m_row(row) , m_row(row)

View file

@ -7,13 +7,14 @@
#include <QDialog> #include <QDialog>
#include <QLabel> #include <QLabel>
#include "components.h"
#include "model/LocalMoneroModel.h" #include "model/LocalMoneroModel.h"
namespace Ui { namespace Ui {
class LocalMoneroInfoDialog; class LocalMoneroInfoDialog;
} }
class LocalMoneroInfoDialog : public QDialog class LocalMoneroInfoDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -8,7 +8,7 @@
#include "utils/Utils.h" #include "utils/Utils.h"
OutputInfoDialog::OutputInfoDialog(CoinsInfo *cInfo, QWidget *parent) OutputInfoDialog::OutputInfoDialog(CoinsInfo *cInfo, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::OutputInfoDialog) , ui(new Ui::OutputInfoDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -6,6 +6,7 @@
#include <QDialog> #include <QDialog>
#include "components.h"
#include "libwalletqt/Coins.h" #include "libwalletqt/Coins.h"
#include "libwalletqt/CoinsInfo.h" #include "libwalletqt/CoinsInfo.h"
@ -13,7 +14,7 @@ namespace Ui {
class OutputInfoDialog; class OutputInfoDialog;
} }
class OutputInfoDialog : public QDialog class OutputInfoDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -7,7 +7,7 @@
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
OutputSweepDialog::OutputSweepDialog(QWidget *parent, quint64 amount) OutputSweepDialog::OutputSweepDialog(QWidget *parent, quint64 amount)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::OutputSweepDialog) , ui(new Ui::OutputSweepDialog)
, m_amount(amount) , m_amount(amount)
{ {

View file

@ -6,13 +6,14 @@
#include <QDialog> #include <QDialog>
#include "components.h"
#include "libwalletqt/CoinsInfo.h" #include "libwalletqt/CoinsInfo.h"
namespace Ui { namespace Ui {
class OutputSweepDialog; class OutputSweepDialog;
} }
class OutputSweepDialog : public QDialog class OutputSweepDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -7,7 +7,7 @@
#include <QMessageBox> #include <QMessageBox>
PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet) PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::PasswordChangeDialog) , ui(new Ui::PasswordChangeDialog)
, m_wallet(wallet) , m_wallet(wallet)
{ {

View file

@ -6,13 +6,14 @@
#include <QDialog> #include <QDialog>
#include "components.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
namespace Ui { namespace Ui {
class PasswordChangeDialog; class PasswordChangeDialog;
} }
class PasswordChangeDialog : public QDialog class PasswordChangeDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -7,7 +7,7 @@
#include "utils/Icons.h" #include "utils/Icons.h"
PasswordDialog::PasswordDialog(const QString &walletName, bool incorrectPassword, QWidget *parent) PasswordDialog::PasswordDialog(const QString &walletName, bool incorrectPassword, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::PasswordDialog) , ui(new Ui::PasswordDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -6,11 +6,13 @@
#include <QDialog> #include <QDialog>
#include "components.h"
namespace Ui { namespace Ui {
class PasswordDialog; class PasswordDialog;
} }
class PasswordDialog : public QDialog class PasswordDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -0,0 +1,82 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "PaymentRequestDialog.h"
#include "ui_PaymentRequestDialog.h"
#include <QClipboard>
#include <QFileDialog>
#include <QMessageBox>
#include <QRegExpValidator>
#include "WalletManager.h"
PaymentRequestDialog::PaymentRequestDialog(QWidget *parent, QSharedPointer<AppContext> ctx, QString address)
: WindowModalDialog(parent)
, ui(new Ui::PaymentRequestDialog)
, m_ctx(std::move(ctx))
, m_address(std::move(address))
{
ui->setupUi(this);
QString amount_rx = R"(^\d{0,8}[\.]\d{0,12}|(all)$)";
QRegExp rx;
rx.setPattern(amount_rx);
QValidator *validator = new QRegExpValidator(rx, this);
ui->line_amountXMR->setValidator(validator);
connect(ui->line_amountXMR, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest);
connect(ui->line_description, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest);
connect(ui->line_recipient, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest);
connect(ui->btn_copyLink, &QPushButton::clicked, this, &PaymentRequestDialog::copyLink);
connect(ui->btn_copyImage, &QPushButton::clicked, this, &PaymentRequestDialog::copyImage);
connect(ui->btn_saveImage, &QPushButton::clicked, this, &PaymentRequestDialog::saveImage);
this->updatePaymentRequest();
ui->line_amountXMR->setFocus();
this->adjustSize();
}
void PaymentRequestDialog::updatePaymentRequest() {
QString description = ui->line_description->text();
QString recipient = ui->line_recipient->text();
quint64 amount = WalletManager::amountFromString(ui->line_amountXMR->text());
QString uri = m_ctx->wallet->make_uri(m_address, amount, description, recipient);
ui->line_paymentRequestUri->setText(uri);
ui->line_paymentRequestUri->setCursorPosition(0);
// TODO: memory leak, cba to refactor now
m_qrCode = new QrCode(uri, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM);
if (m_qrCode->isValid()) {
ui->qrWidget->setQrCode(m_qrCode);
}
}
void PaymentRequestDialog::copyLink() {
Utils::copyToClipboard(ui->line_paymentRequestUri->text());
QMessageBox::information(this, "Information", "Payment request link copied to clipboard.");
}
void PaymentRequestDialog::copyImage() {
QApplication::clipboard()->setPixmap(m_qrCode->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio));
QMessageBox::information(this, "Information", "QR code copied to clipboard.");
}
void PaymentRequestDialog::saveImage() {
QString filename = QFileDialog::getSaveFileName(this, "Select where to save file", QDir::current().filePath("qrcode.png"));
if (filename.isEmpty()) {
return;
}
QFile file(filename);
file.open(QIODevice::WriteOnly);
m_qrCode->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio).save(&file, "PNG");
QMessageBox::information(this, "Information", "QR code saved to file");
}
PaymentRequestDialog::~PaymentRequestDialog() = default;

View file

@ -0,0 +1,38 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_PAYMENTREQUESTDIALOG_H
#define FEATHER_PAYMENTREQUESTDIALOG_H
#include <QDialog>
#include "appcontext.h"
#include "components.h"
#include "qrcode/QrCode.h"
namespace Ui {
class PaymentRequestDialog;
}
class PaymentRequestDialog : public WindowModalDialog
{
Q_OBJECT
public:
explicit PaymentRequestDialog(QWidget *parent, QSharedPointer<AppContext> ctx, QString address);
~PaymentRequestDialog() override;
private slots:
void updatePaymentRequest();
void copyLink();
void copyImage();
void saveImage();
private:
QScopedPointer<Ui::PaymentRequestDialog> ui;
QSharedPointer<AppContext> m_ctx;
QString m_address;
QrCode *m_qrCode;
};
#endif //FEATHER_PAYMENTREQUESTDIALOG_H

View file

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PaymentRequestDialog</class>
<widget class="QDialog" name="PaymentRequestDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>658</width>
<height>761</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Payment Request</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QrCodeWidget" name="qrWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_paymentRequestUri">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Amount:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="line_amountXMR">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>XMR</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="line_description"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Your name:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="line_recipient"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btn_copyLink">
<property name="text">
<string>Copy Link</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_copyImage">
<property name="text">
<string>Copy Image</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_saveImage">
<property name="text">
<string>Save Image</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QrCodeWidget</class>
<extends>QWidget</extends>
<header>widgets/QrCodeWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PaymentRequestDialog</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>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PaymentRequestDialog</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>

View file

@ -9,7 +9,7 @@
#include <QMessageBox> #include <QMessageBox>
QrCodeDialog::QrCodeDialog(QWidget *parent, QrCode *qrCode, const QString &title) QrCodeDialog::QrCodeDialog(QWidget *parent, QrCode *qrCode, const QString &title)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::QrCodeDialog) , ui(new Ui::QrCodeDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -6,6 +6,7 @@
#include <QDialog> #include <QDialog>
#include "components.h"
#include "qrcode/QrCode.h" #include "qrcode/QrCode.h"
#include "widgets/QrCodeWidget.h" #include "widgets/QrCodeWidget.h"
@ -13,7 +14,7 @@ namespace Ui {
class QrCodeDialog; class QrCodeDialog;
} }
class QrCodeDialog : public QDialog class QrCodeDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -8,7 +8,7 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
RestoreHeightDialog::RestoreHeightDialog(QWidget *parent, quint64 currentRestoreHeight) RestoreHeightDialog::RestoreHeightDialog(QWidget *parent, quint64 currentRestoreHeight)
: QDialog(parent) : WindowModalDialog(parent)
, m_restoreHeightWidget(new RestoreHeightWidget(this)) , m_restoreHeightWidget(new RestoreHeightWidget(this))
{ {
auto *layout = new QVBoxLayout(this); auto *layout = new QVBoxLayout(this);

View file

@ -6,9 +6,10 @@
#include <QDialog> #include <QDialog>
#include "components.h"
#include "widgets/RestoreHeightWidget.h" #include "widgets/RestoreHeightWidget.h"
class RestoreHeightDialog : public QDialog class RestoreHeightDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -7,7 +7,7 @@
#include "constants.h" #include "constants.h"
SeedDialog::SeedDialog(QSharedPointer<AppContext> ctx, QWidget *parent) SeedDialog::SeedDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::SeedDialog) , ui(new Ui::SeedDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
{ {

View file

@ -7,12 +7,13 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
namespace Ui { namespace Ui {
class SeedDialog; class SeedDialog;
} }
class SeedDialog : public QDialog class SeedDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -9,7 +9,7 @@
#include "utils/Utils.h" #include "utils/Utils.h"
SignVerifyDialog::SignVerifyDialog(Wallet *wallet, QWidget *parent) SignVerifyDialog::SignVerifyDialog(Wallet *wallet, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::SignVerifyDialog) , ui(new Ui::SignVerifyDialog)
, m_wallet(wallet) , m_wallet(wallet)
{ {

View file

@ -6,13 +6,14 @@
#include <QDialog> #include <QDialog>
#include "components.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
namespace Ui { namespace Ui {
class SignVerifyDialog; class SignVerifyDialog;
} }
class SignVerifyDialog : public QDialog class SignVerifyDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -7,7 +7,7 @@
#include "utils/Icons.h" #include "utils/Icons.h"
SplashDialog::SplashDialog(QWidget *parent) SplashDialog::SplashDialog(QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::SplashDialog) , ui(new Ui::SplashDialog)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -6,11 +6,13 @@
#include <QDialog> #include <QDialog>
#include "components.h"
namespace Ui { namespace Ui {
class SplashDialog; class SplashDialog;
} }
class SplashDialog : public QDialog class SplashDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -9,7 +9,7 @@
#include "utils/NetworkManager.h" #include "utils/NetworkManager.h"
TxBroadcastDialog::TxBroadcastDialog(QWidget *parent, QSharedPointer<AppContext> ctx, const QString &transactionHex) TxBroadcastDialog::TxBroadcastDialog(QWidget *parent, QSharedPointer<AppContext> ctx, const QString &transactionHex)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::TxBroadcastDialog) , ui(new Ui::TxBroadcastDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
{ {

View file

@ -7,13 +7,14 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
#include "utils/daemonrpc.h" #include "utils/daemonrpc.h"
namespace Ui { namespace Ui {
class TxBroadcastDialog; class TxBroadcastDialog;
} }
class TxBroadcastDialog : public QDialog class TxBroadcastDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -7,6 +7,7 @@
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include "constants.h"
#include "dialog/QrCodeDialog.h" #include "dialog/QrCodeDialog.h"
#include "libwalletqt/Input.h" #include "libwalletqt/Input.h"
#include "libwalletqt/Transfer.h" #include "libwalletqt/Transfer.h"
@ -14,7 +15,7 @@
#include "qrcode/QrCode.h" #include "qrcode/QrCode.h"
TxConfAdvDialog::TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &description, QWidget *parent) TxConfAdvDialog::TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &description, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::TxConfAdvDialog) , ui(new Ui::TxConfAdvDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
, m_exportUnsignedMenu(new QMenu(this)) , m_exportUnsignedMenu(new QMenu(this))
@ -35,42 +36,51 @@ TxConfAdvDialog::TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &
m_exportTxKeyMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::txKeyCopy); m_exportTxKeyMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::txKeyCopy);
ui->btn_exportTxKey->setMenu(m_exportTxKeyMenu); ui->btn_exportTxKey->setMenu(m_exportTxKeyMenu);
if (m_ctx->wallet->viewOnly()) { ui->line_description->setText(description);
ui->btn_exportSigned->hide();
ui->btn_send->hide();
}
ui->label_description->setText(QString("Description: %1").arg(description));
connect(ui->btn_sign, &QPushButton::clicked, this, &TxConfAdvDialog::signTransaction); connect(ui->btn_sign, &QPushButton::clicked, this, &TxConfAdvDialog::signTransaction);
connect(ui->btn_send, &QPushButton::clicked, this, &TxConfAdvDialog::broadcastTransaction); connect(ui->btn_send, &QPushButton::clicked, this, &TxConfAdvDialog::broadcastTransaction);
connect(ui->btn_close, &QPushButton::clicked, this, &TxConfAdvDialog::closeDialog); connect(ui->btn_close, &QPushButton::clicked, this, &TxConfAdvDialog::closeDialog);
ui->amount->setFont(ModelUtils::getMonospaceFont());
ui->fee->setFont(ModelUtils::getMonospaceFont());
ui->total->setFont(ModelUtils::getMonospaceFont());
ui->inputs->setFont(ModelUtils::getMonospaceFont()); ui->inputs->setFont(ModelUtils::getMonospaceFont());
ui->outputs->setFont(ModelUtils::getMonospaceFont()); ui->outputs->setFont(ModelUtils::getMonospaceFont());
this->adjustSize(); this->adjustSize();
} }
void TxConfAdvDialog::setTransaction(PendingTransaction *tx) { void TxConfAdvDialog::setTransaction(PendingTransaction *tx, bool isSigned) {
ui->btn_sign->hide(); ui->btn_sign->hide();
if (!isSigned) {
ui->btn_exportSigned->hide();
ui->btn_send->hide();
}
m_tx = tx; m_tx = tx;
m_tx->refresh(); m_tx->refresh();
PendingTransactionInfo *ptx = m_tx->transaction(0); //Todo: support split transactions PendingTransactionInfo *ptx = m_tx->transaction(0); //Todo: support split transactions
ui->txid->setText(tx->txid().first()); // TODO: implement hasTxKey()
if (!m_ctx->wallet->isHwBacked() && m_tx->transaction(0)->txKey() == "0100000000000000000000000000000000000000000000000000000000000000") {
ui->btn_exportTxKey->hide();
}
ui->amount->setText(WalletManager::displayAmount(tx->amount())); m_txid = tx->txid().first();
ui->fee->setText(WalletManager::displayAmount(ptx->fee())); ui->txid->setText(m_txid);
ui->total->setText(WalletManager::displayAmount(tx->amount() + ptx->fee()));
auto size_str = [this]{ this->setAmounts(tx->amount(), tx->fee());
if (m_ctx->wallet->viewOnly()) {
return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size())); auto size_str = [this, isSigned]{
} else { if (isSigned) {
auto size = m_tx->signedTxToHex(0).size() / 2; auto size = m_tx->signedTxToHex(0).size() / 2;
return QString("Size: %1 bytes (%2 bytes unsigned)").arg(QString::number(size), QString::number(m_tx->unsignedTxToBin().size())); return QString("Size: %1 bytes (%2 bytes unsigned)").arg(QString::number(size), QString::number(m_tx->unsignedTxToBin().size()));
} else {
return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size()));
} }
}(); }();
ui->label_size->setText(size_str); ui->label_size->setText(size_str);
@ -91,14 +101,38 @@ void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) {
ui->txid->setText("n/a"); ui->txid->setText("n/a");
ui->label_size->setText("Size: n/a"); ui->label_size->setText("Size: n/a");
ui->amount->setText(WalletManager::displayAmount(utx->amount(0))); this->setAmounts(utx->amount(0), utx->fee(0));
ui->fee->setText(WalletManager::displayAmount(utx->fee(0)));
ui->total->setText(WalletManager::displayAmount(utx->amount(0) + utx->fee(0)));
ConstructionInfo *ci = m_utx->constructionInfo(0); ConstructionInfo *ci = m_utx->constructionInfo(0);
this->setupConstructionData(ci); this->setupConstructionData(ci);
} }
void TxConfAdvDialog::setAmounts(quint64 amount, quint64 fee) {
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString();
auto convert = [preferredCur](double amount){
return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2);
};
QString amount_str = WalletManager::displayAmount(amount);
QString fee_str = WalletManager::displayAmount(fee);
QString total = WalletManager::displayAmount(amount + fee);
QVector<QString> amounts = {amount_str, fee_str, total};
int maxLength = Utils::maxLength(amounts);
std::for_each(amounts.begin(), amounts.end(), [maxLength](QString& amount){amount = amount.rightJustified(maxLength, ' ');});
QString amount_fiat = convert(amount / constants::cdiv);
QString fee_fiat = convert(fee / constants::cdiv);
QString total_fiat = convert((amount + fee) / constants::cdiv);
QVector<QString> amounts_fiat = {amount_fiat, fee_fiat, total_fiat};
int maxLengthFiat = Utils::maxLength(amounts_fiat);
std::for_each(amounts_fiat.begin(), amounts_fiat.end(), [maxLengthFiat](QString& amount){amount = amount.rightJustified(maxLengthFiat, ' ');});
ui->amount->setText(QString("%1 (%2 %3)").arg(amounts[0], amounts_fiat[0], preferredCur));
ui->fee->setText(QString("%1 (%2 %3)").arg(amounts[1], amounts_fiat[1], preferredCur));
ui->total->setText(QString("%1 (%2 %3)").arg(amounts[2], amounts_fiat[2], preferredCur));
}
void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) { void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) {
QString inputs_str; QString inputs_str;
auto inputs = ci->inputs(); auto inputs = ci->inputs();
@ -122,7 +156,6 @@ void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) {
ui->label_outputs->setText(QString("Outputs (%1)").arg(QString::number(outputs.size()))); ui->label_outputs->setText(QString("Outputs (%1)").arg(QString::number(outputs.size())));
ui->label_ringSize->setText(QString("Ring size: %1").arg(QString::number(ci->minMixinCount() + 1))); ui->label_ringSize->setText(QString("Ring size: %1").arg(QString::number(ci->minMixinCount() + 1)));
ui->label_unlockTime->setText(QString("Unlock time: %1 (height)").arg(QString::number(ci->unlockTime())));
} }
void TxConfAdvDialog::signTransaction() { void TxConfAdvDialog::signTransaction() {
@ -180,7 +213,7 @@ void TxConfAdvDialog::signedQrCode() {
void TxConfAdvDialog::broadcastTransaction() { void TxConfAdvDialog::broadcastTransaction() {
if (m_tx == nullptr) return; if (m_tx == nullptr) return;
m_ctx->commitTransaction(m_tx); m_ctx->commitTransaction(m_tx, ui->line_description->text());
QDialog::accept(); QDialog::accept();
} }

View file

@ -10,13 +10,14 @@
#include <QTextCharFormat> #include <QTextCharFormat>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
#include "libwalletqt/PendingTransaction.h" #include "libwalletqt/PendingTransaction.h"
namespace Ui { namespace Ui {
class TxConfAdvDialog; class TxConfAdvDialog;
} }
class TxConfAdvDialog : public QDialog class TxConfAdvDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT
@ -24,7 +25,7 @@ public:
explicit TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &description, QWidget *parent = nullptr); explicit TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &description, QWidget *parent = nullptr);
~TxConfAdvDialog() override; ~TxConfAdvDialog() override;
void setTransaction(PendingTransaction *tx); void setTransaction(PendingTransaction *tx, bool isSigned = true); // #TODO: have libwallet return a UnsignedTransaction, this is just dumb
void setUnsignedTransaction(UnsignedTransaction *utx); void setUnsignedTransaction(UnsignedTransaction *utx);
private: private:
@ -32,6 +33,7 @@ private:
void signTransaction(); void signTransaction();
void broadcastTransaction(); void broadcastTransaction();
void closeDialog(); void closeDialog();
void setAmounts(quint64 amount, quint64 fee);
void unsignedCopy(); void unsignedCopy();
void unsignedQrCode(); void unsignedQrCode();
@ -50,6 +52,7 @@ private:
QMenu *m_exportUnsignedMenu; QMenu *m_exportUnsignedMenu;
QMenu *m_exportSignedMenu; QMenu *m_exportSignedMenu;
QMenu *m_exportTxKeyMenu; QMenu *m_exportTxKeyMenu;
QString m_txid;
}; };
#endif //FEATHER_TXCONFADVDIALOG_H #endif //FEATHER_TXCONFADVDIALOG_H

View file

@ -21,15 +21,15 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QFormLayout" name="formLayout_2">
<item> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Transaction ID:</string> <string>Transaction ID:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="0" column="1">
<widget class="QLineEdit" name="txid"> <widget class="QLineEdit" name="txid">
<property name="text"> <property name="text">
<string>txid</string> <string>txid</string>
@ -41,15 +41,56 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_description">
<property name="text">
<string>Description:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_description"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_size">
<property name="text">
<string>Size: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_ringSize">
<property name="text">
<string>Ringsize:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item> <item>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<item row="0" column="0"> <item row="0" column="0">
@ -112,57 +153,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_description">
<property name="text">
<string>Description:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_size">
<property name="text">
<string>Size: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_unlockTime">
<property name="text">
<string>Unlock time: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_ringSize">
<property name="text">
<string>Ringsize:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View file

@ -13,7 +13,7 @@
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
TxConfDialog::TxConfDialog(QSharedPointer<AppContext> ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent) TxConfDialog::TxConfDialog(QSharedPointer<AppContext> ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::TxConfDialog) , ui(new Ui::TxConfDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
, m_tx(tx) , m_tx(tx)

View file

@ -7,6 +7,7 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
#include "libwalletqt/PendingTransaction.h" #include "libwalletqt/PendingTransaction.h"
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
@ -14,7 +15,7 @@ namespace Ui {
class TxConfDialog; class TxConfDialog;
} }
class TxConfDialog : public QDialog class TxConfDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -9,7 +9,7 @@
#include "utils/NetworkManager.h" #include "utils/NetworkManager.h"
TxImportDialog::TxImportDialog(QWidget *parent, QSharedPointer<AppContext> ctx) TxImportDialog::TxImportDialog(QWidget *parent, QSharedPointer<AppContext> ctx)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::TxImportDialog) , ui(new Ui::TxImportDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
, m_loadTimer(new QTimer(this)) , m_loadTimer(new QTimer(this))

View file

@ -7,13 +7,14 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
#include "utils/daemonrpc.h" #include "utils/daemonrpc.h"
namespace Ui { namespace Ui {
class TxImportDialog; class TxImportDialog;
} }
class TxImportDialog : public QDialog class TxImportDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -9,6 +9,7 @@
#include "appcontext.h" #include "appcontext.h"
#include "config.h" #include "config.h"
#include "constants.h"
#include "libwalletqt/Coins.h" #include "libwalletqt/Coins.h"
#include "libwalletqt/CoinsInfo.h" #include "libwalletqt/CoinsInfo.h"
#include "libwalletqt/TransactionHistory.h" #include "libwalletqt/TransactionHistory.h"
@ -16,6 +17,7 @@
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
#include "Utils.h" #include "Utils.h"
#include "utils/Icons.h"
TxInfoDialog::TxInfoDialog(QSharedPointer<AppContext> ctx, TransactionInfo *txInfo, QWidget *parent) TxInfoDialog::TxInfoDialog(QSharedPointer<AppContext> ctx, TransactionInfo *txInfo, QWidget *parent)
: QDialog(parent) : QDialog(parent)
@ -26,11 +28,16 @@ TxInfoDialog::TxInfoDialog(QSharedPointer<AppContext> ctx, TransactionInfo *txIn
{ {
ui->setupUi(this); ui->setupUi(this);
ui->btn_viewOnBlockExplorer->setIcon(icons()->icon("external-link.svg"));
ui->btn_viewOnBlockExplorer->setToolTip("View on block explorer");
connect(ui->btn_viewOnBlockExplorer, &QPushButton::clicked, this, &TxInfoDialog::viewOnBlockExplorer);
m_txid = txInfo->hash(); m_txid = txInfo->hash();
ui->label_txid->setText(m_txid); ui->label_txid->setText(m_txid);
connect(ui->btn_CopyTxKey, &QPushButton::pressed, this, &TxInfoDialog::copyTxKey); connect(ui->btn_copyTxID, &QPushButton::clicked, this, &TxInfoDialog::copyTxID);
connect(ui->btn_createTxProof, &QPushButton::pressed, this, &TxInfoDialog::createTxProof); connect(ui->btn_CopyTxKey, &QPushButton::clicked, this, &TxInfoDialog::copyTxKey);
connect(ui->btn_createTxProof, &QPushButton::clicked, this, &TxInfoDialog::createTxProof);
connect(m_ctx->wallet, &Wallet::newBlock, this, &TxInfoDialog::updateData); connect(m_ctx->wallet, &Wallet::newBlock, this, &TxInfoDialog::updateData);
@ -43,6 +50,12 @@ TxInfoDialog::TxInfoDialog(QSharedPointer<AppContext> ctx, TransactionInfo *txIn
} else { } else {
ui->btn_rebroadcastTx->hide(); ui->btn_rebroadcastTx->hide();
} }
if (txInfo->direction() == TransactionInfo::Direction_In) {
ui->btn_CopyTxKey->setDisabled(true);
ui->btn_CopyTxKey->setToolTip("No tx secret key available for incoming transactions.");
}
// //
// if (txInfo->direction() == TransactionInfo::Direction_Out) { // if (txInfo->direction() == TransactionInfo::Direction_Out) {
// // TODO: this will not properly represent coinjoin-like transactions. // // TODO: this will not properly represent coinjoin-like transactions.
@ -78,6 +91,9 @@ TxInfoDialog::TxInfoDialog(QSharedPointer<AppContext> ctx, TransactionInfo *txIn
} }
this->adjustSize(); this->adjustSize();
// Don't autofocus any of the buttons. There is probably a better way for this.
ui->label_txid->setFocus();
} }
void TxInfoDialog::adjustHeight(QTextEdit *textEdit, qreal docHeight) { void TxInfoDialog::adjustHeight(QTextEdit *textEdit, qreal docHeight) {
@ -140,6 +156,10 @@ void TxInfoDialog::updateData() {
this->setData(tx); this->setData(tx);
} }
void TxInfoDialog::copyTxID() {
Utils::copyToClipboard(m_txid);
}
void TxInfoDialog::copyTxKey() { void TxInfoDialog::copyTxKey() {
m_ctx->wallet->getTxKeyAsync(m_txid, [this](QVariantMap map){ m_ctx->wallet->getTxKeyAsync(m_txid, [this](QVariantMap map){
QString txKey = map.value("tx_key").toString(); QString txKey = map.value("tx_key").toString();
@ -157,4 +177,8 @@ void TxInfoDialog::createTxProof() {
m_txProofDialog->getTxKey(); m_txProofDialog->getTxKey();
} }
void TxInfoDialog::viewOnBlockExplorer() {
Utils::externalLinkWarning(this, Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), constants::networkType, m_txid));
}
TxInfoDialog::~TxInfoDialog() = default; TxInfoDialog::~TxInfoDialog() = default;

View file

@ -28,11 +28,13 @@ signals:
void resendTranscation(const QString &txid); void resendTranscation(const QString &txid);
private: private:
void copyTxID();
void copyTxKey(); void copyTxKey();
void createTxProof(); void createTxProof();
void setData(TransactionInfo *tx); void setData(TransactionInfo *tx);
void updateData(); void updateData();
void adjustHeight(QTextEdit *textEdit, qreal docHeight); void adjustHeight(QTextEdit *textEdit, qreal docHeight);
void viewOnBlockExplorer();
QScopedPointer<Ui::TxInfoDialog> ui; QScopedPointer<Ui::TxInfoDialog> ui;
QSharedPointer<AppContext> m_ctx; QSharedPointer<AppContext> m_ctx;

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>929</width> <width>929</width>
<height>631</height> <height>715</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -19,7 +19,7 @@
<property name="title"> <property name="title">
<string>Transaction ID:</string> <string>Transaction ID:</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="label_txid"> <widget class="QLabel" name="label_txid">
<property name="text"> <property name="text">
@ -30,6 +30,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="btn_viewOnBlockExplorer">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -205,17 +218,24 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="btn_copyTxID">
<property name="text">
<string>Copy Tx ID</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="btn_CopyTxKey"> <widget class="QPushButton" name="btn_CopyTxKey">
<property name="text"> <property name="text">
<string>Copy Transaction Key</string> <string>Copy Tx Secret Key</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="btn_createTxProof"> <widget class="QPushButton" name="btn_createTxProof">
<property name="text"> <property name="text">
<string>Create Transaction Proof</string> <string>Create Tx Proof</string>
</property> </property>
</widget> </widget>
</item> </item>

View file

@ -11,7 +11,7 @@
#include "utils/Utils.h" #include "utils/Utils.h"
TxProofDialog::TxProofDialog(QWidget *parent, QSharedPointer<AppContext> ctx, TransactionInfo *txInfo) TxProofDialog::TxProofDialog(QWidget *parent, QSharedPointer<AppContext> ctx, TransactionInfo *txInfo)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::TxProofDialog) , ui(new Ui::TxProofDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
{ {
@ -88,13 +88,13 @@ void TxProofDialog::selectOutProof() {
m_mode = Mode::OutProof; m_mode = Mode::OutProof;
this->resetFrames(); this->resetFrames();
if (m_OutDestinations.empty()) { if (m_txKey.isEmpty()) {
this->showWarning("This transaction did not spend any outputs owned by this wallet. Creating an OutProof is not possible."); this->showWarning("No transaction key stored for this transaction. Creating an OutProof is not possible.");
return; return;
} }
if (m_txKey.isEmpty()) { if (m_OutDestinations.empty()) {
this->showWarning("No transaction key stored for this transaction. Creating an OutProof is not possible."); this->showWarning("This transaction did not send funds to any known addresses. Creating an OutProof is not possible.");
return; return;
} }
@ -107,6 +107,11 @@ void TxProofDialog::selectInProof() {
m_mode = Mode::InProof; m_mode = Mode::InProof;
this->resetFrames(); this->resetFrames();
if (m_direction == TransactionInfo::Direction_Out) {
this->showWarning("Can't create InProofs for outgoing transactions.");
return;
}
if (m_InDestinations.empty()) { if (m_InDestinations.empty()) {
this->showWarning("Your wallet did not receive any outputs in this transaction."); this->showWarning("Your wallet did not receive any outputs in this transaction.");
return; return;

View file

@ -7,13 +7,14 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
#include "libwalletqt/TransactionInfo.h" #include "libwalletqt/TransactionInfo.h"
namespace Ui { namespace Ui {
class TxProofDialog; class TxProofDialog;
} }
class TxProofDialog : public QDialog class TxProofDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -117,6 +117,11 @@ void UpdateDialog::onInstallUpdate() {
ui->btn_installUpdate->hide(); ui->btn_installUpdate->hide();
this->setStatus("Unzipping archive..."); this->setStatus("Unzipping archive...");
#ifdef Q_OS_MACOS
this->installUpdateMac();
return;
#endif
zip_error_t err; zip_error_t err;
zip_error_init(&err); zip_error_init(&err);
@ -172,14 +177,7 @@ void UpdateDialog::onInstallUpdate() {
zip_fclose(zf); zip_fclose(zf);
zip_close(zip_archive); zip_close(zip_archive);
QString applicationPath = qgetenv("APPIMAGE"); QDir applicationDir(Utils::applicationPath());
if (!applicationPath.isEmpty()) {
applicationPath = QFileInfo(applicationPath).absoluteDir().path();
} else {
applicationPath = QCoreApplication::applicationDirPath();
}
QDir applicationDir(applicationPath);
QString filePath = applicationDir.filePath(name); QString filePath = applicationDir.filePath(name);
m_updatePath = filePath; m_updatePath = filePath;
@ -206,6 +204,41 @@ void UpdateDialog::onInstallUpdate() {
ui->btn_restart->show(); ui->btn_restart->show();
} }
void UpdateDialog::installUpdateMac() {
QString appPath = Utils::applicationPath();
QDir appDir(appPath);
if (appPath.endsWith("Contents/MacOS")) {
appDir.cd("../../..");
}
QString appName = QString("feather-%1").arg(m_version);
QString zipName = QString("%1.zip").arg(appName);
QString fPath = appDir.filePath(zipName);
QFile file(fPath);
qDebug() << "Writing zip file to " << fPath;
if (!file.open(QIODevice::WriteOnly))
{
this->onInstallError(QString("Error: Could not write to application path: %1").arg(fPath));
return;
}
if (static_cast<size_t>(file.write(&m_updateZipArchive[0], m_updateZipArchive.size())) != m_updateZipArchive.size()) {
this->onInstallError("Error: Unable to write file");
return;
}
QProcess unzip;
unzip.start("/usr/bin/unzip", {"-n", fPath, "-d", appDir.path()});
unzip.waitForFinished();
m_updatePath = QString("%1.app/Contents/MacOS/feather").arg(appDir.filePath(appName));
file.remove();
this->setStatus("Installation successful. Do you want to restart Feather now?");
ui->btn_restart->show();
}
void UpdateDialog::onInstallError(const QString &errMsg) { void UpdateDialog::onInstallError(const QString &errMsg) {
this->setStatus(errMsg); this->setStatus(errMsg);
} }

View file

@ -33,6 +33,7 @@ signals:
private: private:
void setStatus(const QString &msg, bool success = false); void setStatus(const QString &msg, bool success = false);
void installUpdateMac();
QScopedPointer<Ui::UpdateDialog> ui; QScopedPointer<Ui::UpdateDialog> ui;

View file

@ -11,7 +11,7 @@
#include "utils/Utils.h" #include "utils/Utils.h"
VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent) VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::VerifyProofDialog) , ui(new Ui::VerifyProofDialog)
, m_wallet(wallet) , m_wallet(wallet)
{ {
@ -50,6 +50,9 @@ VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent)
} }
}); });
connect(m_wallet, &Wallet::transactionProofVerified, this, &VerifyProofDialog::onTxProofVerified);
connect(m_wallet, &Wallet::spendProofVerified, this, &VerifyProofDialog::onSpendProofVerified);
ui->input_formattedProof->setFont(ModelUtils::getMonospaceFont()); ui->input_formattedProof->setFont(ModelUtils::getMonospaceFont());
} }
@ -69,31 +72,15 @@ void VerifyProofDialog::checkProof() {
void VerifyProofDialog::checkTxProof(const QString &txId, const QString &address, const QString &message, void VerifyProofDialog::checkTxProof(const QString &txId, const QString &address, const QString &message,
const QString &signature) { const QString &signature) {
TxProofResult r = m_wallet->checkTxProof(txId, address, message, signature); ui->btn_verifyFormattedProof->setEnabled(false);
ui->btn_verify->setEnabled(false);
if (!r.success) { m_wallet->checkTxProofAsync(txId, address, message, signature);
this->proofStatus(false, m_wallet->errorString());
return;
}
if (!r.good) {
this->proofStatus(false, "Proof is invalid");
return;
}
this->proofStatus(true, QString("Proof is valid.\n\nThis address received %1 XMR, with %2 confirmation(s)").arg(WalletManager::displayAmount(r.received), QString::number(r.confirmations)));
} }
void VerifyProofDialog::checkSpendProof(const QString &txId, const QString &message, const QString &signature) { void VerifyProofDialog::checkSpendProof(const QString &txId, const QString &message, const QString &signature) {
auto r = m_wallet->checkSpendProof(txId, message, signature); ui->btn_verifyFormattedProof->setEnabled(false);
ui->btn_verify->setEnabled(false);
if (!r.first) { m_wallet->checkSpendProofAsync(txId, message, signature);
this->proofStatus(false, m_wallet->errorString());
return;
}
r.second ? this->proofStatus(true, "Proof is valid")
: this->proofStatus(false, "Proof is invalid");
} }
void VerifyProofDialog::checkOutProof() { void VerifyProofDialog::checkOutProof() {
@ -163,11 +150,37 @@ void VerifyProofDialog::proofStatus(bool success, const QString &message) {
ui->frame_status->show(); ui->frame_status->show();
ui->label_icon->setPixmap(success ? m_success : m_failure); ui->label_icon->setPixmap(success ? m_success : m_failure);
ui->label_status->setText(message); ui->label_status->setText(message);
ui->btn_verifyFormattedProof->setEnabled(true);
} }
else { else {
ui->btn_verify->setEnabled(true);
success ? QMessageBox::information(this, "Information", message) success ? QMessageBox::information(this, "Information", message)
: QMessageBox::warning(this, "Warning", message); : QMessageBox::warning(this, "Warning", message);
} }
} }
void VerifyProofDialog::onTxProofVerified(TxProofResult r) {
if (!r.success) {
this->proofStatus(false, m_wallet->errorString());
return;
}
if (!r.good) {
this->proofStatus(false, "Proof is invalid");
return;
}
this->proofStatus(true, QString("Proof is valid.\n\nThis address received %1 XMR, with %2 confirmation(s)").arg(WalletManager::displayAmount(r.received), QString::number(r.confirmations)));
}
void VerifyProofDialog::onSpendProofVerified(QPair<bool, bool> r) {
if (!r.first) {
this->proofStatus(false, m_wallet->errorString());
return;
}
r.second ? this->proofStatus(true, "Proof is valid")
: this->proofStatus(false, "Proof is invalid");
}
VerifyProofDialog::~VerifyProofDialog() = default; VerifyProofDialog::~VerifyProofDialog() = default;

View file

@ -7,13 +7,14 @@
#include <QDialog> #include <QDialog>
#include <QIcon> #include <QIcon>
#include "components.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
namespace Ui { namespace Ui {
class VerifyProofDialog; class VerifyProofDialog;
} }
class VerifyProofDialog : public QDialog class VerifyProofDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT
@ -31,6 +32,8 @@ private:
void checkInProof(); void checkInProof();
void checkFormattedProof(); void checkFormattedProof();
void proofStatus(bool success, const QString &message); void proofStatus(bool success, const QString &message);
void onTxProofVerified(TxProofResult result);
void onSpendProofVerified(QPair<bool, bool> result);
QScopedPointer<Ui::VerifyProofDialog> ui; QScopedPointer<Ui::VerifyProofDialog> ui;
Wallet *m_wallet; Wallet *m_wallet;

View file

@ -9,7 +9,7 @@
#include <QMessageBox> #include <QMessageBox>
ViewOnlyDialog::ViewOnlyDialog(QSharedPointer<AppContext> ctx, QWidget *parent) ViewOnlyDialog::ViewOnlyDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::ViewOnlyDialog) , ui(new Ui::ViewOnlyDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
{ {

View file

@ -7,12 +7,13 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
namespace Ui { namespace Ui {
class ViewOnlyDialog; class ViewOnlyDialog;
} }
class ViewOnlyDialog : public QDialog class ViewOnlyDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -9,7 +9,7 @@
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
WalletCacheDebugDialog::WalletCacheDebugDialog(QSharedPointer<AppContext> ctx, QWidget *parent) WalletCacheDebugDialog::WalletCacheDebugDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::WalletCacheDebugDialog) , ui(new Ui::WalletCacheDebugDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
{ {

View file

@ -7,12 +7,13 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
namespace Ui { namespace Ui {
class WalletCacheDebugDialog; class WalletCacheDebugDialog;
} }
class WalletCacheDebugDialog : public QDialog class WalletCacheDebugDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -7,7 +7,7 @@
#include <QDesktopServices> #include <QDesktopServices>
WalletInfoDialog::WalletInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent) WalletInfoDialog::WalletInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent) : WindowModalDialog(parent)
, ui(new Ui::WalletInfoDialog) , ui(new Ui::WalletInfoDialog)
, m_ctx(std::move(ctx)) , m_ctx(std::move(ctx))
{ {

View file

@ -7,12 +7,13 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h" #include "appcontext.h"
#include "components.h"
namespace Ui { namespace Ui {
class WalletInfoDialog; class WalletInfoDialog;
} }
class WalletInfoDialog : public QDialog class WalletInfoDialog : public WindowModalDialog
{ {
Q_OBJECT Q_OBJECT

View file

@ -71,15 +71,15 @@ quint64 Coins::count() const
return m_tinfo.count(); return m_tinfo.count();
} }
void Coins::freeze(int index) const void Coins::freeze(QString &publicKey) const
{ {
m_pimpl->setFrozen(index); m_pimpl->setFrozen(publicKey.toStdString());
emit coinFrozen(); emit coinFrozen();
} }
void Coins::thaw(int index) const void Coins::thaw(QString &publicKey) const
{ {
m_pimpl->thaw(index); m_pimpl->thaw(publicKey.toStdString());
emit coinThawed(); emit coinThawed();
} }
@ -96,9 +96,9 @@ QVector<CoinsInfo*> Coins::coins_from_txid(const QString &txid)
return coins; return coins;
} }
void Coins::setDescription(int index, quint32 accountIndex, const QString &description) void Coins::setDescription(const QString &publicKey, quint32 accountIndex, const QString &description)
{ {
m_pimpl->setDescription(index, description.toStdString()); m_pimpl->setDescription(publicKey.toStdString(), description.toStdString());
this->refresh(accountIndex); this->refresh(accountIndex);
emit descriptionChanged(); emit descriptionChanged();
} }

Some files were not shown because too many files have changed in this diff Show more