This commit is contained in:
tobtoht 2021-05-02 20:22:38 +02:00
parent e6f512788b
commit d4cfa05157
No known key found for this signature in database
GPG key ID: 1CADD27F41F45C3C
152 changed files with 6060 additions and 1880 deletions

1
.gitignore vendored
View file

@ -14,3 +14,4 @@ src/tor/*
!src/tor/.gitkeep
src/config-feather.h
src/assets/exec/*
feather.AppDir/*

View file

@ -22,7 +22,7 @@ git clone --branch master --recursive https://git.featherwallet.org/feather/feat
cd feather
```
Replace `master` with the desired version tag (e.g. `beta-4`) to build the release binary.
Replace `master` with the desired version tag (e.g. `beta-6`) to build the release binary.
#### 2. Base image
@ -37,7 +37,7 @@ Building the base image takes a while. You only need to build the base image onc
##### Standalone binary
```bash
docker run --rm -it -v $PWD:/feather -w /feather feather:linux sh -c 'make release-static -j4'
docker run --rm -it -v $PWD:/feather -w /feather feather:linux sh -c 'CHECK_UPDATES=On make release-static -j4'
```
If you're re-running a build make sure to `rm -rf build/` first.
@ -75,7 +75,7 @@ Building the base image takes a while. You only need to build the base image onc
#### 3. Build
```bash
docker run --rm -it -v $PWD:/feather -w /feather feather:win sh -c 'make depends root=/depends target=x86_64-w64-mingw32 tag=win-x64 -j4'
docker run --rm -it -v $PWD:/feather -w /feather feather:win sh -c 'CHECK_UPDATES=On make depends root=/depends target=x86_64-w64-mingw32 tag=win-x64 -j4'
```
If you're re-running a build make sure to `rm -rf build/` first.

View file

@ -7,11 +7,13 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
set(VERSION_MAJOR "0")
set(VERSION_MINOR "1")
set(VERSION_REVISION "0")
set(VERSION "beta-5")
set(VERSION "beta-6")
option(FETCH_DEPS "Download dependencies if they are not found" ON)
option(LOCALMONERO "Include LocalMonero module" ON)
option(XMRIG "Include XMRig module" ON)
option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF)
option(CHECK_UPDATES "Enable checking for application updates" OFF)
option(STATIC "Link libraries statically, requires static Qt")
option(USE_DEVICE_TREZOR "Trezor support compilation" OFF)
@ -29,7 +31,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON)
endif()
set(MONERO_HEAD "41327974116dedccc2f9709d8ad3a8a1f591faed")
set(MONERO_HEAD "626f1b4ba1c1a490ca97fe265230aea84eac0ed2")
set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64")
set(BUILD_64 ON)
@ -139,6 +141,11 @@ if(NOT monero-seed_FOUND)
endif()
endif()
# libzip
find_package(zlib CONFIG)
find_path(LIBZIP_INCLUDE_DIRS zip.h)
find_library(LIBZIP_LIBRARIES zip)
# Boost
if(DEBUG)
set(Boost_DEBUG ON)

View file

@ -41,9 +41,11 @@ via the `CMAKE_PREFIX_PATH` definition. For me this is:
There are some Monero/Feather related options/definitions that you may pass:
- `-DLOCALMONERO=OFF` - disable LocalMonero feature
- `-DXMRIG=OFF` - disable XMRig feature
- `-DTOR_BIN=/path/to/tor` - Embed a Tor executable inside Feather
- `-DDONATE_BEG=OFF` - disable the dreaded donate requests
- `-DCHECK_UPDATES=ON` - enable checking for updates, only for standalone binaries
And:

View file

@ -30,7 +30,9 @@ CMAKEFLAGS = \
-DARCH=x86_64 \
-DBUILD_64=On \
-DBUILD_TESTS=Off \
-DLOCALMONERO=On \
-DXMRIG=On \
-DCHECK_UPDATES=Off \
-DTOR_BIN=Off \
-DCMAKE_CXX_STANDARD=11 \
-DCMAKE_VERBOSE_MAKEFILE=On \
@ -42,6 +44,7 @@ CMAKEFLAGS = \
release-static: CMAKEFLAGS += -DBUILD_TAG="linux-x64"
release-static: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
release-static: CMAKEFLAGS += -DCHECK_UPDATES=$(or ${CHECK_UPDATES}, Off)
release-static: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
release-static: CMAKEFLAGS += -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF)
release-static:
@ -50,10 +53,11 @@ release-static:
depends:
mkdir -p build/$(target)/release
cd build/$(target)/release && cmake -D STATIC=ON -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF) -DTOR_VERSION=$(or ${TOR_VERSION}, OFF) -DTOR_BIN=$(or ${TOR_BIN},OFF) -D DEV_MODE=$(or ${DEV_MODE},OFF) -D BUILD_TAG=$(tag) -D CMAKE_BUILD_TYPE=Release -D CMAKE_TOOLCHAIN_FILE=$(root)/$(target)/share/toolchain.cmake ../../.. && $(MAKE)
cd build/$(target)/release && cmake -D STATIC=ON -DREPRODUCIBLE=$(or ${SOURCE_DATE_EPOCH},OFF) -DTOR_VERSION=$(or ${TOR_VERSION}, OFF) -DTOR_BIN=$(or ${TOR_BIN},OFF) -DCHECK_UPDATES=$(or ${CHECK_UPDATES}, OFF) -D DEV_MODE=$(or ${DEV_MODE},OFF) -D BUILD_TAG=$(tag) -D CMAKE_BUILD_TYPE=Release -D CMAKE_TOOLCHAIN_FILE=$(root)/$(target)/share/toolchain.cmake ../../.. && $(MAKE)
mac-release: CMAKEFLAGS += -DSTATIC=Off
mac-release: CMAKEFLAGS += -DTOR_BIN=$(or ${TOR_BIN},OFF)
mac-release: CMAKEFLAGS += -DCHECK_UPDATES=$(or ${CHECK_UPDATES}, Off)
mac-release: CMAKEFLAGS += -DBUILD_TAG="mac-x64"
mac-release: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
mac-release:

View file

@ -2,24 +2,19 @@
# Contributor: wowario <wowario[at]protonmail[dot]com>
pkgname='monero-feather-git'
pkgver=0.1.0.925ef5683
pkgver=0.5.0.d1bb4c143f
pkgrel=1
pkgdesc='a free Monero desktop wallet'
license=('BSD')
arch=('x86_64')
url="https://featherwallet.org"
depends=('boost-libs' 'libunwind' 'openssl' 'readline' 'pcsclite' 'hidapi' 'protobuf' 'miniupnpc' 'libgcrypt' 'qrencode' 'libsodium' 'libpgm' 'expat' 'qt5-base' 'qt5-websockets' 'tor')
depends=('boost-libs' 'libunwind' 'openssl' 'readline' 'zeromq' 'pcsclite' 'hidapi' 'protobuf' 'libusb' 'libudev.so' 'miniupnpc' 'libgcrypt' 'qrencode' 'libsodium' 'libpgm' 'expat' 'qt5-base' 'qt5-websockets' 'qt5-svg' 'tor' 'libzip')
makedepends=('git' 'cmake' 'boost')
source=("${pkgname}"::"git+https://git.featherwallet.org/feather/feather")
sha256sums=('SKIP')
pkgver() {
cd "${srcdir}/${pkgname}"
printf "%s.%s" "$(git describe --tags --abbrev=0)" "$(git rev-parse --short=9 HEAD)"
}
build() {
cd "${srcdir}/${pkgname}"
git submodule update --init --recursive
@ -32,4 +27,6 @@ build() {
package_monero-feather-git() {
install -Dm644 "${srcdir}/${pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
install -Dm755 "${srcdir}/${pkgname}/build/bin/feather" "${pkgdir}/usr/bin/feather"
install -Dm644 "${srcdir}/${pkgname}/src/assets/feather.desktop" "${pkgdir}/usr/share/applications/feather.desktop"
install -Dm644 "${srcdir}/${pkgname}/src/assets/images/feather.png" "${pkgdir}/usr/share/pixmaps/feather.png"
}

View file

@ -0,0 +1,60 @@
# - try to find HIDAPI library
# from http://www.signal11.us/oss/hidapi/
#
# Cache Variables: (probably not for direct use in your scripts)
# HIDAPI_INCLUDE_DIR
# HIDAPI_LIBRARY
#
# Non-cache variables you might use in your CMakeLists.txt:
# HIDAPI_FOUND
# HIDAPI_INCLUDE_DIRS
# HIDAPI_LIBRARIES
#
# Requires these CMake modules:
# FindPackageHandleStandardArgs (known included with CMake >=2.6.2)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
find_library(HIDAPI_LIBRARY
NAMES hidapi hidapi-libusb)
find_path(HIDAPI_INCLUDE_DIR
NAMES hidapi.h
PATH_SUFFIXES
hidapi)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HIDAPI
DEFAULT_MSG
HIDAPI_LIBRARY
HIDAPI_INCLUDE_DIR)
if(HIDAPI_FOUND)
set(HIDAPI_LIBRARIES "${HIDAPI_LIBRARY}")
if((STATIC AND UNIX AND NOT APPLE) OR (DEPENDS AND CMAKE_SYSTEM_NAME STREQUAL "Linux"))
find_library(LIBUSB-1.0_LIBRARY usb-1.0)
find_library(LIBUDEV_LIBRARY udev)
if(LIBUSB-1.0_LIBRARY)
set(HIDAPI_LIBRARIES "${HIDAPI_LIBRARIES};${LIBUSB-1.0_LIBRARY}")
if(LIBUDEV_LIBRARY)
set(HIDAPI_LIBRARIES "${HIDAPI_LIBRARIES};${LIBUDEV_LIBRARY}")
else()
message(WARNING "libudev library not found, binaries may fail to link.")
endif()
else()
message(WARNING "libusb-1.0 library not found, binaries may fail to link.")
endif()
endif()
set(HIDAPI_INCLUDE_DIRS "${HIDAPI_INCLUDE_DIR}")
endif()
mark_as_advanced(HIDAPI_INCLUDE_DIR HIDAPI_LIBRARY)

2
monero

@ -1 +1 @@
Subproject commit 41327974116dedccc2f9709d8ad3a8a1f591faed
Subproject commit 626f1b4ba1c1a490ca97fe265230aea84eac0ed2

View file

@ -18,6 +18,8 @@ qt5_add_resources(RESOURCES assets.qrc)
file(GLOB SOURCE_FILES
"*.h"
"*.cpp"
"api/*.h"
"api/*.cpp"
"utils/*.h"
"utils/*.cpp"
"libwalletqt/*.h"
@ -116,12 +118,22 @@ target_include_directories(feather PUBLIC
${Qt5Svg_INCLUDE_DIRS}
${Qt5Xml_INCLUDE_DIRS}
${Qt5WebSockets_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS}
${LIBZIP_INCLUDE_DIRS}
)
if(DONATE_BEG)
target_compile_definitions(feather PRIVATE DONATE_BEG=1)
endif()
if (CHECK_UPDATES)
target_compile_definitions(feather PRIVATE CHECK_UPDATES=1)
endif()
if(LOCALMONERO)
target_compile_definitions(feather PRIVATE HAS_LOCALMONERO=1)
endif()
if(TOR_BIN)
target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1)
endif()
@ -130,6 +142,9 @@ if(XMRIG)
target_compile_definitions(feather PRIVATE HAS_XMRIG=1)
endif()
# TODO: PLACEHOLDER
target_compile_definitions(feather PRIVATE HAS_WEBSOCKET=1)
if(HAVE_SYS_PRCTL_H)
target_compile_definitions(feather PRIVATE HAVE_SYS_PRCTL_H=1)
endif()
@ -191,6 +206,8 @@ target_link_libraries(feather
openpgp
Threads::Threads
${QRENCODE_LIBRARY}
${ZLIB_LIBRARIES}
${LIBZIP_LIBRARIES}
)
if(APPLE)

121
src/api/LocalMoneroApi.cpp Normal file
View file

@ -0,0 +1,121 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "LocalMoneroApi.h"
LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl)
: QObject(parent)
, m_network(network)
, m_baseUrl(baseUrl)
{
}
void LocalMoneroApi::countryCodes() {
QString url = QString("%1/countrycodes").arg(m_baseUrl);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::COUNTRY_CODES));
}
void LocalMoneroApi::currencies() {
QString url = QString("%1/currencies").arg(m_baseUrl);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::CURRENCIES));
}
void LocalMoneroApi::paymentMethods(const QString &countryCode) {
QString url;
if (countryCode.isEmpty()) {
url = QString("%1/payment_methods").arg(m_baseUrl);
} else {
url = QString("%1/payment_methods/%2").arg(m_baseUrl, countryCode);
}
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::PAYMENT_METHODS));
}
void LocalMoneroApi::buyMoneroOnline(const QString &currencyCode, const QString &countryCode,
const QString &paymentMethod, const QString &amount, int page)
{
QString url = this->getBuySellUrl(true, currencyCode, countryCode, paymentMethod, amount, page);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::BUY_MONERO_ONLINE));
}
void LocalMoneroApi::sellMoneroOnline(const QString &currencyCode, const QString &countryCode,
const QString &paymentMethod, const QString &amount, int page)
{
QString url = this->getBuySellUrl(false, currencyCode, countryCode, paymentMethod, amount, page);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::SELL_MONERO_ONLINE));
}
void LocalMoneroApi::accountInfo(const QString &username) {
QString url = QString("%1/account_info/%2").arg(m_baseUrl, username);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::ACCOUNT_INFO));
}
void LocalMoneroApi::onResponse(QNetworkReply *reply, LocalMoneroApi::Endpoint endpoint) {
const bool ok = reply->error() == QNetworkReply::NoError;
const QString err = reply->errorString();
QByteArray data = reply->readAll();
qDebug() << "Response";
qDebug() << data;
for (const auto header : reply->rawHeaderList()) {
qDebug() << header << ": " << reply->rawHeader(header);
}
qDebug() << reply->rawHeaderPairs();
qDebug() << "Request";
for (const auto header : reply->request().rawHeaderList()) {
qDebug() << "header: " << header << ": " << reply->request().rawHeader(header);
}
qDebug() << reply->request().url();
reply->deleteLater();
QJsonObject obj;
if (!data.isEmpty() && Utils::validateJSON(data)) {
auto doc = QJsonDocument::fromJson(data);
obj = doc.object();
}
else if (!ok) {
emit ApiResponse(LocalMoneroResponse{false, endpoint, err, {}});
return;
}
else {
emit ApiResponse(LocalMoneroResponse{false, endpoint, "Invalid response from LocalMonero", {}});
return;
}
if (obj.contains("error")) {
QString errorStr = QJsonDocument(obj["error"].toObject()).toJson(QJsonDocument::Compact);
emit ApiResponse(LocalMoneroResponse{false, endpoint, errorStr, obj});
return;
}
emit ApiResponse(LocalMoneroResponse{true, endpoint, "", obj});
}
QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode,
const QString &paymentMethod, const QString &amount, int page)
{
QString url = QString("%1/%2-monero-online/%3").arg(m_baseUrl, buy ? "buy" : "sell", currencyCode);
if (!countryCode.isEmpty() && paymentMethod.isEmpty())
url += QString("/%1").arg(countryCode);
else if (countryCode.isEmpty() && !paymentMethod.isEmpty())
url += QString("/%1").arg(paymentMethod);
else if (!countryCode.isEmpty() && !paymentMethod.isEmpty())
url += QString("/%1/%2").arg(countryCode, paymentMethod);
QUrlQuery query;
if (!amount.isEmpty())
query.addQueryItem("amount", amount);
if (page > 0)
query.addQueryItem("page", QString::number(page));
url += "?" + query.toString();
return url;
}

53
src/api/LocalMoneroApi.h Normal file
View file

@ -0,0 +1,53 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_LOCALMONEROAPI_H
#define FEATHER_LOCALMONEROAPI_H
#include <QObject>
#include "utils/networking.h"
class LocalMoneroApi : public QObject {
Q_OBJECT
public:
enum Endpoint {
COUNTRY_CODES,
CURRENCIES,
PAYMENT_METHODS,
BUY_MONERO_ONLINE,
SELL_MONERO_ONLINE,
ACCOUNT_INFO
};
struct LocalMoneroResponse {
bool ok;
Endpoint endpoint;
QString message;
QJsonObject obj;
};
explicit LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl = "https://agoradesk.com/api/v1");
void countryCodes();
void currencies();
void paymentMethods(const QString &countryCode = "");
void buyMoneroOnline(const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
void sellMoneroOnline(const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
void accountInfo(const QString &username);
signals:
void ApiResponse(LocalMoneroResponse resp);
private slots:
void onResponse(QNetworkReply *reply, Endpoint endpoint);
private:
QString getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
QString m_baseUrl;
UtilsNetworking *m_network;
};
#endif //FEATHER_LOCALMONEROAPI_H

View file

@ -13,20 +13,16 @@
#include "libwalletqt/Coins.h"
#include "model/TransactionHistoryModel.h"
#include "model/SubaddressModel.h"
#include "utils/NetworkManager.h"
#include "utils/WebsocketClient.h"
#include "utils/WebsocketNotifier.h"
Prices *AppContext::prices = nullptr;
WalletKeysFilesModel *AppContext::wallets = nullptr;
TxFiatHistory *AppContext::txFiatHistory = nullptr;
double AppContext::balance = 0;
QMap<QString, QString> AppContext::txCache;
AppContext::AppContext(QCommandLineParser *cmdargs) {
this->network = new QNetworkAccessManager();
this->networkClearnet = new QNetworkAccessManager();
this->cmdargs = cmdargs;
this->isTorSocks = Utils::isTorsocks();
this->isTails = TailsOS::detect();
this->isWhonix = WhonixOS::detect();
@ -45,21 +41,20 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
// ----------------- Network Type -----------------
if (this->cmdargs->isSet("stagenet"))
if (this->cmdargs->isSet("stagenet")) {
this->networkType = NetworkType::STAGENET;
else if (this->cmdargs->isSet("testnet"))
config()->set(Config::networkType, NetworkType::STAGENET);
}
else if (this->cmdargs->isSet("testnet")) {
this->networkType = NetworkType::TESTNET;
else
config()->set(Config::networkType, NetworkType::TESTNET);
}
else {
this->networkType = NetworkType::MAINNET;
config()->set(Config::networkType, NetworkType::MAINNET);
}
this->nodes = new Nodes(this, this->networkClearnet);
connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged);
connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes);
// Tor & socks proxy
this->ws = new WSClient(this, globals::websocketUrl);
connect(this->ws, &WSClient::WSMessage, this, &AppContext::onWSMessage);
this->nodes = new Nodes(this, this);
// Store the wallet every 2 minutes
m_storeTimer.start(2 * 60 * 1000);
@ -67,31 +62,6 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
this->storeWallet();
});
// restore height lookup
this->initRestoreHeights();
// price history lookup
auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey();
AppContext::txFiatHistory = new TxFiatHistory(genesis_timestamp, configDir);
connect(this->ws, &WSClient::connectionEstablished, AppContext::txFiatHistory, &TxFiatHistory::onUpdateDatabase);
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYear, [=](int year){
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1}})").arg(year).toUtf8();
this->ws->sendMsg(data);
});
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYearMonth, [=](int year, int month) {
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8();
this->ws->sendMsg(data);
});
// fiat/crypto lookup
AppContext::prices = new Prices();
// XMRig
#ifdef HAS_XMRIG
this->XMRig = new XmRig(configDir, this);
this->XMRig->prepare();
#endif
this->walletManager = WalletManager::instance();
QString logPath = QString("%1/daemon.log").arg(configDir);
Monero::Utils::onStartup();
@ -108,23 +78,32 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
// libwallet connects
connect(this->walletManager, &WalletManager::walletOpened, this, &AppContext::onWalletOpened);
connect(this->walletManager, &WalletManager::walletCreated, this, &AppContext::onWalletCreated);
connect(this->walletManager, &WalletManager::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest);
connect(this->walletManager, &WalletManager::deviceError, this, &AppContext::onDeviceError);
// TODO: move me
connect(websocketNotifier(), &WebsocketNotifier::NodesReceived, this->nodes, &Nodes::onWSNodesReceived);
}
void AppContext::initTor() {
this->tor = new Tor(this, this);
this->tor->start();
if (this->cmdargs->isSet("tor-host"))
config()->set(Config::socks5Host, this->cmdargs->value("tor-host"));
if (this->cmdargs->isSet("tor-port"))
config()->set(Config::socks5Port, this->cmdargs->value("tor-port"));
if (this->cmdargs->isSet("use-local-tor"))
config()->set(Config::useLocalTor, true);
if (!(isWhonix) && !(isTorSocks)) {
this->networkProxy = new QNetworkProxy(QNetworkProxy::Socks5Proxy, Tor::torHost, Tor::torPort);
this->network->setProxy(*networkProxy);
if (globals::websocketUrl.host().endsWith(".onion")) {
this->ws->webSocket.setProxy(*networkProxy);
}
}
torManager()->init();
torManager()->start();
connect(torManager(), &TorManager::connectionStateChanged, &websocketNotifier()->websocketClient, &WebsocketClient::onToggleConnect);
this->onTorSettingsChanged();
}
void AppContext::initWS() {
this->ws->start();
websocketNotifier()->websocketClient.start();
}
void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address) {
@ -249,6 +228,17 @@ void AppContext::onOpenWallet(const QString &path, const QString &password){
this->walletManager->openWalletAsync(path, password, this->networkType, 1);
}
void AppContext::onWalletCreated(Wallet * wallet) {
// Currently only called when a wallet is created from device.
auto state = wallet->status();
if (state != Wallet::Status_Ok) {
emit walletCreatedError(wallet->errorString());
return;
}
this->onWalletOpened(wallet);
}
void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) {
if(this->currentWallet) {
auto *model = this->currentWallet->transactionHistoryModel();
@ -276,8 +266,7 @@ void AppContext::commitTransaction(PendingTransaction *tx) {
}
void AppContext::onMultiBroadcast(PendingTransaction *tx) {
UtilsNetworking *net = new UtilsNetworking(this->network, this);
DaemonRpc *rpc = new DaemonRpc(this, net, "");
DaemonRpc rpc{this, getNetworkTor(), ""};
int count = tx->txCount();
for (int i = 0; i < count; i++) {
@ -286,40 +275,81 @@ void AppContext::onMultiBroadcast(PendingTransaction *tx) {
for (const auto& node: this->nodes->websocketNodes()) {
if (!node.online) continue;
QString address = node.as_url();
QString address = node.toURL();
qDebug() << QString("Relaying %1 to: %2").arg(tx->txid()[i], address);
rpc->setDaemonAddress(address);
rpc->sendRawTransaction(txData);
rpc.setDaemonAddress(address);
rpc.sendRawTransaction(txData);
}
}
}
void AppContext::onDeviceButtonRequest(quint64 code) {
emit deviceButtonRequest(code);
}
void AppContext::onDeviceError(const QString &message) {
qCritical() << "Device error: " << message;
emit deviceError(message);
}
void AppContext::onTorSettingsChanged() {
if (WhonixOS::detect() || Utils::isTorsocks()) {
return;
}
// use local tor -> bundled tor
QString host = config()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort();
if (!torManager()->isLocalTor()) {
host = torManager()->featherTorHost;
port = torManager()->featherTorPort;
}
QNetworkProxy proxy{QNetworkProxy::Socks5Proxy, host, port};
getNetworkTor()->setProxy(proxy);
websocketNotifier()->websocketClient.webSocket.setProxy(proxy);
this->nodes->connectToNode();
auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
qDebug() << "Changed privacyLevel to " << privacyLevel;
}
void AppContext::onInitialNetworkConfigured() {
this->initTor();
this->initWS();
}
void AppContext::onWalletOpened(Wallet *wallet) {
auto state = wallet->status();
if (state != Wallet::Status_Ok) {
auto errMsg = wallet->errorString();
if(errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) {
if (state == Wallet::Status_BadPassword) {
this->closeWallet(false);
// Don't show incorrect password when we try with empty password for the first time
bool showIncorrectPassword = m_openWalletTriedOnce;
m_openWalletTriedOnce = true;
emit walletOpenPasswordNeeded(showIncorrectPassword, wallet->path());
}
else if (errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) {
qCritical() << errMsg;
this->walletManager->clearWalletCache(this->walletPath);
errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart Feather.").arg(errMsg);
this->closeWallet(false);
emit walletOpenedError(errMsg);
} else if(errMsg.contains("wallet cannot be opened as")) {
this->closeWallet(false);
emit walletOpenedError(errMsg);
} else if(errMsg.contains("is opened by another wallet program")) {
this->closeWallet(false);
emit walletOpenedError(errMsg);
} else {
this->closeWallet(false);
emit walletOpenPasswordNeeded(!this->walletPassword.isEmpty(), wallet->path());
emit walletOpenedError(errMsg);
}
return;
}
m_openWalletTriedOnce = false;
this->refreshed = false;
this->currentWallet = wallet;
this->walletPath = this->currentWallet->path() + ".keys";
this->walletPassword = this->currentWallet->getPassword();
config()->set(Config::walletPath, this->walletPath);
connect(this->currentWallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent);
@ -331,6 +361,7 @@ void AppContext::onWalletOpened(Wallet *wallet) {
connect(this->currentWallet, &Wallet::transactionCommitted, this, &AppContext::onTransactionCommitted);
connect(this->currentWallet, &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed);
connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated);
connect(this->currentWallet, &Wallet::deviceError, this, &AppContext::onDeviceError);
emit walletOpened();
@ -346,142 +377,6 @@ void AppContext::onWalletOpened(Wallet *wallet) {
// force trigger preferredFiat signal for history model
this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString());
this->setWindowTitle();
}
void AppContext::setWindowTitle(bool mining) {
QFileInfo fileInfo(this->walletPath);
auto title = QString("Feather - [%1]").arg(fileInfo.fileName());
if(this->currentWallet && this->currentWallet->viewOnly())
title += " [view-only]";
if(mining)
title += " [mining]";
emit setTitle(title);
}
void AppContext::onWSMessage(const QJsonObject &msg) {
QString cmd = msg.value("cmd").toString();
if (cmd == "blockheights") {
QJsonObject data = msg.value("data").toObject();
int mainnet = data.value("mainnet").toInt();
int stagenet = data.value("stagenet").toInt();
this->heights[NetworkType::MAINNET] = mainnet;
this->heights[NetworkType::STAGENET] = stagenet;
}
else if(cmd == "nodes") {
this->onWSNodes(msg.value("data").toArray());
}
#if defined(HAS_XMRIG)
else if(cmd == "xmrig") {
this->XMRigDownloads(msg.value("data").toObject());
}
#endif
else if(cmd == "crypto_rates") {
QJsonArray crypto_rates = msg.value("data").toArray();
AppContext::prices->cryptoPricesReceived(crypto_rates);
}
else if(cmd == "fiat_rates") {
QJsonObject fiat_rates = msg.value("data").toObject();
AppContext::prices->fiatPricesReceived(fiat_rates);
}
else if(cmd == "reddit") {
QJsonArray reddit_data = msg.value("data").toArray();
this->onWSReddit(reddit_data);
}
else if(cmd == "ccs") {
auto ccs_data = msg.value("data").toArray();
this->onWSCCS(ccs_data);
}
else if(cmd == "txFiatHistory") {
auto txFiatHistory_data = msg.value("data").toObject();
AppContext::txFiatHistory->onWSData(txFiatHistory_data);
}
}
void AppContext::onWSNodes(const QJsonArray &nodes) {
QList<QSharedPointer<FeatherNode>> l;
for (auto &&entry: nodes) {
auto obj = entry.toObject();
auto nettype = obj.value("nettype");
auto type = obj.value("type");
// filter remote node network types
if(nettype == "mainnet" && this->networkType != NetworkType::MAINNET)
continue;
if(nettype == "stagenet" && this->networkType != NetworkType::STAGENET)
continue;
if(nettype == "testnet" && this->networkType != NetworkType::TESTNET)
continue;
if(type == "clearnet" && (this->isTails || this->isWhonix || this->isTorSocks))
continue;
if(type == "tor" && (!(this->isTails || this->isWhonix || this->isTorSocks)))
continue;
auto node = new FeatherNode(
obj.value("address").toString(),
obj.value("height").toInt(),
obj.value("target_height").toInt(),
obj.value("online").toBool());
QSharedPointer<FeatherNode> r = QSharedPointer<FeatherNode>(node);
l.append(r);
}
this->nodes->onWSNodesReceived(l);
}
void AppContext::onWSReddit(const QJsonArray& reddit_data) {
QList<QSharedPointer<RedditPost>> l;
for (auto &&entry: reddit_data) {
auto obj = entry.toObject();
auto redditPost = new RedditPost(
obj.value("title").toString(),
obj.value("author").toString(),
obj.value("permalink").toString(),
obj.value("comments").toInt());
QSharedPointer<RedditPost> r = QSharedPointer<RedditPost>(redditPost);
l.append(r);
}
emit redditUpdated(l);
}
void AppContext::onWSCCS(const QJsonArray &ccs_data) {
QList<QSharedPointer<CCSEntry>> l;
QStringList fonts = {"state", "address", "author", "date",
"title", "target_amount", "raised_amount",
"percentage_funded", "contributions"};
for (auto &&entry: ccs_data) {
auto obj = entry.toObject();
auto c = QSharedPointer<CCSEntry>(new CCSEntry());
if (obj.value("state").toString() != "FUNDING-REQUIRED")
continue;
c->state = obj.value("state").toString();
c->address = obj.value("address").toString();
c->author = obj.value("author").toString();
c->date = obj.value("date").toString();
c->title = obj.value("title").toString();
c->url = obj.value("url").toString();
c->target_amount = obj.value("target_amount").toDouble();
c->raised_amount = obj.value("raised_amount").toDouble();
c->percentage_funded = obj.value("percentage_funded").toDouble();
c->contributions = obj.value("contributions").toInt();
l.append(c);
}
emit ccsUpdated(l);
}
void AppContext::createConfigDirectory(const QString &dir) {
@ -528,7 +423,18 @@ void AppContext::createWallet(FeatherSeed seed, const QString &path, const QStri
return;
}
this->createWalletFinish(password);
this->onWalletOpened(wallet);
}
void AppContext::createWalletFromDevice(const QString &path, const QString &password, int restoreHeight) {
if(Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
qCritical() << err;
emit walletCreatedError(err);
return;
}
this->walletManager->createWalletFromDeviceAsync(path, password, this->networkType, "Ledger", restoreHeight);
}
void AppContext::createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic) {
@ -539,7 +445,7 @@ void AppContext::createWalletFromKeys(const QString &path, const QString &passwo
return;
}
if(!this->walletManager->addressValid(address, this->networkType)) {
if(!WalletManager::addressValid(address, this->networkType)) {
auto err = QString("Failed to create wallet. Invalid address provided.").arg(path);
qCritical() << err;
emit walletCreatedError(err);
@ -560,21 +466,8 @@ void AppContext::createWalletFromKeys(const QString &path, const QString &passwo
return;
}
this->currentWallet = this->walletManager->createWalletFromKeys(path, this->seedLanguage, this->networkType, address, viewkey, spendkey, restoreHeight);
this->createWalletFinish(password);
}
void AppContext::createWalletFinish(const QString &password) {
this->currentWallet->setPassword(password);
this->currentWallet->store();
this->walletPassword = password;
emit walletCreated(this->currentWallet);
}
void AppContext::initRestoreHeights() {
restoreHeights[NetworkType::TESTNET] = new RestoreHeightLookup(NetworkType::TESTNET);
restoreHeights[NetworkType::STAGENET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_stagenet.txt", NetworkType::STAGENET);
restoreHeights[NetworkType::MAINNET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_mainnet.txt", NetworkType::MAINNET);
Wallet *wallet = this->walletManager->createWalletFromKeys(path, password, this->seedLanguage, this->networkType, address, viewkey, spendkey, restoreHeight);
this->walletManager->walletOpened(wallet);
}
void AppContext::onSetRestoreHeight(quint64 height){
@ -643,7 +536,7 @@ void AppContext::donateBeg() {
if (this->currentWallet->viewOnly()) return;
auto donationCounter = config()->get(Config::donateBeg).toInt();
if(donationCounter == -1)
if (donationCounter == -1)
return; // previously donated
donationCounter += 1;
@ -688,7 +581,13 @@ void AppContext::onWalletUpdate() {
this->updateBalance();
}
void AppContext::onWalletRefreshed(bool success) {
void AppContext::onWalletRefreshed(bool success, const QString &message) {
if (!success) {
// Something went wrong during refresh, in some cases we need to notify the user
qCritical() << "Exception during refresh: " << message; // Can't use ->errorString() here, other SLOT might snipe it first
return;
}
if (!this->refreshed) {
refreshModels();
this->refreshed = true;
@ -778,11 +677,10 @@ void AppContext::updateBalance() {
if (!this->currentWallet)
return;
quint64 balance_u = this->currentWallet->balance();
AppContext::balance = balance_u / globals::cdiv;
double spendable = this->currentWallet->unlockedBalance();
quint64 balance = this->currentWallet->balance();
quint64 spendable = this->currentWallet->unlockedBalance();
emit balanceUpdated(balance_u, spendable);
emit balanceUpdated(balance, spendable);
}
void AppContext::syncStatusUpdated(quint64 height, quint64 target) {

View file

@ -5,16 +5,13 @@
#define FEATHER_APPCONTEXT_H
#include <QObject>
#include <QProcess>
#include <QNetworkAccessManager>
#include <QTimer>
#include "utils/tails.h"
#include "utils/whonix.h"
#include "utils/prices.h"
#include "utils/networking.h"
#include "utils/tor.h"
#include "utils/xmrig.h"
#include "utils/TorManager.h"
#include "utils/wsclient.h"
#include "utils/txfiathistory.h"
#include "utils/FeatherSeed.h"
@ -28,9 +25,6 @@
#include "utils/keysfiles.h"
#include "PendingTransaction.h"
#define SUBADDRESS_LOOKAHEAD_MINOR 200
class AppContext : public QObject
{
Q_OBJECT
@ -43,7 +37,6 @@ public:
bool isTails = false;
bool isWhonix = false;
bool isTorSocks = false;
bool donationSending = false;
@ -54,25 +47,13 @@ public:
QString walletPassword = "";
NetworkType::Type networkType;
QMap<NetworkType::Type, int> heights;
QMap<NetworkType::Type, RestoreHeightLookup*> restoreHeights;
PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low;
QString seedLanguage = "English"; // 14 word `monero-seed` only has English
QNetworkAccessManager *network;
QNetworkAccessManager *networkClearnet;
QNetworkProxy *networkProxy{};
Nodes *nodes; // TODO: move this to mainwindow (?)
Tor *tor{};
WSClient *ws;
XmRig *XMRig;
Nodes *nodes;
DaemonRpc *daemonRpc;
static Prices *prices;
static WalletKeysFilesModel *wallets;
static double balance;
static QMap<QString, QString> txCache;
static TxFiatHistory *txFiatHistory;
static void createConfigDirectory(const QString &dir);
@ -81,17 +62,15 @@ public:
WalletManager *walletManager;
Wallet *currentWallet = nullptr;
void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = "");
void createWalletFromDevice(const QString &path, const QString &password, int restoreHeight);
void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false);
void createWalletFinish(const QString &password);
void commitTransaction(PendingTransaction *tx);
void syncStatusUpdated(quint64 height, quint64 target);
void updateBalance();
void initTor();
void initRestoreHeights();
void initWS();
void donateBeg();
void refreshModels();
void setWindowTitle(bool mining = false);
// Closes the currently opened wallet
void closeWallet(bool emitClosedSignal = true, bool storeWallet = false);
@ -99,6 +78,7 @@ public:
public slots:
void onOpenWallet(const QString& path, const QString &password);
void onWalletCreated(Wallet * wallet);
void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all);
void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
@ -109,18 +89,17 @@ public slots:
void onPreferredFiatCurrencyChanged(const QString &symbol);
void onAmountPrecisionChanged(int precision);
void onMultiBroadcast(PendingTransaction *tx);
void onDeviceButtonRequest(quint64 code);
void onTorSettingsChanged();
void onInitialNetworkConfigured();
void onDeviceError(const QString &message);
private slots:
void onWSNodes(const QJsonArray &nodes);
void onWSMessage(const QJsonObject& msg);
void onWSCCS(const QJsonArray &ccs_data);
void onWSReddit(const QJsonArray& reddit_data);
void onMoneySpent(const QString &txId, quint64 amount);
void onMoneyReceived(const QString &txId, quint64 amount);
void onUnconfirmedMoneyReceived(const QString &txId, quint64 amount);
void onWalletUpdate();
void onWalletRefreshed(bool success);
void onWalletRefreshed(bool success, const QString &message);
void onWalletOpened(Wallet *wallet);
void onWalletNewBlock(quint64 blockheight, quint64 targetHeight);
void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight);
@ -148,12 +127,6 @@ signals:
void createTransactionError(QString message);
void createTransactionCancelled(const QVector<QString> &address, double amount);
void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void redditUpdated(QList<QSharedPointer<RedditPost>> &posts);
void nodesUpdated(QList<QSharedPointer<FeatherNode>> &nodes);
void ccsUpdated(QList<QSharedPointer<CCSEntry>> &entries);
void nodeSourceChanged(NodeSource nodeSource);
void XMRigDownloads(const QJsonObject &data);
void setCustomNodes(QList<FeatherNode> nodes);
void openAliasResolveError(const QString &msg);
void openAliasResolved(const QString &address, const QString &openAlias);
void setRestoreHeightError(const QString &msg);
@ -162,10 +135,13 @@ signals:
void donationNag();
void initiateTransaction();
void endTransaction();
void setTitle(const QString &title); // set window title
void deviceButtonRequest(quint64 code);
void updatesAvailable(const QJsonObject &updates);
void deviceError(const QString &message);
private:
QTimer m_storeTimer;
bool m_openWalletTriedOnce = false;
};
#endif //FEATHER_APPCONTEXT_H

View file

@ -5,6 +5,7 @@
<file>assets/contributors.txt</file>
<file>assets/feather.desktop</file>
<file>assets/nodes.json</file>
<file>assets/gpg_keys/featherwallet.asc</file>
<file>assets/images/appicons/32x32.png</file>
<file>assets/images/appicons/48x48.png</file>
<file>assets/images/appicons/64x64.png</file>
@ -44,10 +45,18 @@
<file>assets/images/gnome-calc.png</file>
<file>assets/images/history.png</file>
<file>assets/images/info.png</file>
<file>assets/images/info2.svg</file>
<file>assets/images/key.png</file>
<file>assets/images/ledger.png</file>
<file>assets/images/ledger_unpaired.png</file>
<file>assets/images/lightning.png</file>
<file>assets/images/localMonero_search.svg</file>
<file>assets/images/localMonero_buy.svg</file>
<file>assets/images/localMonero_buy_white.svg</file>
<file>assets/images/localMonero_sell.svg</file>
<file>assets/images/localMonero_sell_white.svg</file>
<file>assets/images/localMonero_logo.png</file>
<file>assets/images/localMonero_register.svg</file>
<file>assets/images/lock.png</file>
<file>assets/images/lock_icon.png</file>
<file>assets/images/lock.svg</file>
@ -63,6 +72,12 @@
<file>assets/images/revealer_c.png</file>
<file>assets/images/revealer.png</file>
<file>assets/images/seal.png</file>
<file>assets/images/securityLevelSafer.png</file>
<file>assets/images/securityLevelSafest.png</file>
<file>assets/images/securityLevelStandard.png</file>
<file>assets/images/securityLevelSaferWhite.png</file>
<file>assets/images/securityLevelSafestWhite.png</file>
<file>assets/images/securityLevelStandardWhite.png</file>
<file>assets/images/seed.png</file>
<file>assets/images/speaker.png</file>
<file>assets/images/status_connected_fork.png</file>

View file

@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBF/HogkBEAChsjCJUsZhDxOx5FrnRA3X5/mJd2xdKskLSPFtnYiQUtKvpRW6
i/RVNMkTwFovzbXB6ucKJtY+OoEMu7xDhIkDWp//UlfHuP9AWAvqbhq6V5xVrZ41
9oQ7JNN4gwAI8+ZjcNq3IVFQQ9mZ3py9t1IUdgWtWM3P/SD7vWiPIjG0D3Bt3Ptl
/mZjIFZZWUtFBItJLkiTpW0Ue4t98XMP6mvQiQ/LhP82OtSyCZ6agj4Wa3ve5KjA
pdEqamBGytx2kmN+AQFgMt66yOvr+97zzuEzI6mlWYORzOc1CFMsmPd6bu/dtQ4Z
96T8PNI6i1Lv5VqvqC7RBErvD7hO36JZb8j+PnbE1YADTKrw0HmgpI6d3RLyVop3
n6ZQri0+nZ+TH0JG74MiihyZIz826zJO5OIwltexRcW0ZiRSpRCxZekU894lEs5Q
SxacRLeqM8ZVawB+9brqbeU3IJxmOCZgXLkkns0dBiSWGxtt+Tji+KXjogNfghmA
dVw9NQoBS+W5+pBtKEORD0YIGiUou9a7ukyMe2uvsl7rT+7BCOdvYtMBRbsfV5NP
s644wfJNIGa7OOjkWhuGwy6BVKTohDhJdKeZUpiTPKLV7ZLHjT4pkjuJgGQB7c+w
v7QYeUpwARwQNi8ZHuij2loG3Fb4l+3ejkcvivw0DLnDDhvUY57ezq53JwARAQAB
tCVGZWF0aGVyV2FsbGV0IDxkZXZAZmVhdGhlcndhbGxldC5vcmc+iQJOBBMBCgA4
FiEEgYXhWKMzMMf9YbwNH3bhVc77pxwFAl/HogkCGwMFCwkIBwMFFQoJCAsFFgID
AQACHgECF4AACgkQH3bhVc77pxzAxw/9GYXGm71lUlZl2yfBPmo91euSc3w/irEC
88X1kFBsdKwL19B8HUaksCOQJRG8fJQmKvJmFnRZg3NK/GLIHam+1WVObFZc1MTv
y2ERzX5ILr9sb7FptB0Wr9gk0y0Nv032ZKci3wn1j2nA87o40uopDoQTaadDTKXa
s3M2+y6zM4dCmCaV6ylJromTzIaL2Q+tWSHDD8EDF2GbnfSeeEV6TV4xj3vqfT5P
34rK4vuVNxEy/YvRQJVRYntveNMJu9C4KJvIpo8onauUHEgBu4m+qfFpixDLwQzq
bJiJQaCUrwJ3liKMolBKiPqjGNl5JRRDy+YR1Dgsj6CRobWg1fDNnrGXUwDLaBwx
zVdCB0VSmcjXpt+FKTxw1mbY+6i6trUfJSjaaawXJbktOkO6sl0bVX83oQxEgod1
aHwuo+eFCAW5zF0r+8R9Lk97Y5jkLWRKjXMFnMIyHaRhPdc24fOfojIQrXzQBMEO
lDhbWVd5vdOALhqvSOGYvjGjxBd9TE0pGzayNfPaee6kFEbxO3wZgF/QLPABl8i9
b6hHJewpY5W9mM9/yP4lHL2TRcEMzk6I7XxPQUGEb3fzTAEHRM+My4SLwaUBIFvM
L8+hRhbfNnLZPd0xDAmvH6wToL3qgK/xSl9SYwuZkzaynblmyXE4+dCFp+T2XTam
FIbphOl8Yt+5Ag0EX8eiCQEQAKv0XnHtGhWTaq/sQ4lulYWNRjBsFQRMqwSFIosO
PfzWwATQeHxxIgRlWkc25w8W0O//t8x0UcNA5rU4R+C7kVrchVSYYYl9PY0vBhKP
3efVtPgntl/VgGH8LAdShHEt3H8ZDMFjqT6gx4xnpgt3C5OdGOA3bIWuvSZ1P7qp
SYiFZakrDfPeCdI/ifucipd+EnZhFv7ivnaoIGs+jgaImQH/5uEEVxpA89Bpxoju
gXlEKSVkVAanZsUwQkc/xzhsh8dzuEF5yKomVbwTYmXDTYmpff02ycdUP7gHw0Qg
WrWaQ2M0Xq1qcZL3ZpoaWUa/A92OfuncCSDNq1pRLqwJrExqQUP9cHGwGbqeGl8K
n2tFds8Pnnv+57ZKiO8E1VTDyBey1J3/Y1hOzctfEz6BzrL52Vj4vPWh2WNNh5fL
u1ZEIdykflH/Kho0zQkRfBfD93FbN/nH1xL3V7pO/wXVGqHSD3HbFLIcJ9Ax+Jgc
Z9fm9Bvc2RkXC8lJU5+htQ+YwHPLDExvUKrBL8b8xksODCvJSWLKcTPooFQyKgbK
EnPW5kmn3eT0SHHHOArn6EHoQttkR0pV2Lrgpfg+uhy3LSTmKbtRWo7VgDY0kfVL
hsatIUqYAVdDTBzsuMhehaoWwtLAsJ01OqxAoc6+0velLddLBuLxtzGtsF0u2mEF
QJmBABEBAAGJAjYEGAEKACAWIQSBheFYozMwx/1hvA0fduFVzvunHAUCX8eiCQIb
DAAKCRAfduFVzvunHDx1D/45GVAtIP1X640PR6N8qa4Iysc/crKepgDqm8zzvpQ8
58MdeJZ9oPFEHDMkIMM8FGK9GbK4UE5mJzWJ2y5acMDOwvX4C9M206YaWQW9jPZt
fTfElP1KdAfTWz2/1UeOZKtOUuq9Wq+QlZGYg532JlX09TMyvINRM/w0+f4IBDlE
XIeRzRI6UQfz3BxpFpfWtMq/ayJnmJPrDsKQBPalai01OsbC+h4BUysZf1n7eTRF
DVaAKkSeOu+4gOVguE9PgKr11lDlKOI38tR6xBXzidBe3cPdun6vQbd1Bdfdmx3J
yFtlQo16kwwG2ZiVicXXugASBsrOFJa2/0lrtAPOnUWJsp2+1Ea6IzpRN8d1mNqr
6ND+CLxBsWj16UXq34GW6vt/QM7N1Br4/6SuPtv8OmDGRkRH7h2pz5yMf5GOwQFq
kgvOHt/x/sFPwk0GMgGn8aFr3vPH2YDg90mPn306Kv12e0JGkYVl4KqdL7u51gxT
3z5C/4+hhPVGHSPkf+g0VY/eY136kuuAZjV3P36M6UaBeCyqeD7b3fJ5IJcLwD9N
R0ustnn8IJ9zEwn+LY8kjRG8J3V57t2qAVGkMCiXnwFu3Vb+AYozOYi2ibu/N9QX
V4dTHarw64HUtLu/HEtcYuzuM5nGOXYvWPz3pQBtlqsyrhIfeaywQ+O55h5/KBo8
Ig==
=2rq8
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,5 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
<circle fill="#2196F3" cx="24" cy="24" r="21"/>
<rect x="22" y="22" fill="#fff" width="4" height="11"/>
<circle fill="#fff" cx="24" cy="16.5" r="2.5"/>
</svg>

After

Width:  |  Height:  |  Size: 278 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M10,0V4H8L12,8L16,4H14V0M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 574 B

View file

@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M10,0V4H8L12,8L16,4H14V0M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root-684" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root-6593" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></svg>

After

Width:  |  Height:  |  Size: 412 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M12,0L8,4H10V8H14V4H16M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 572 B

View file

@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" style="margin-right: 8px;"><path d="M12,0L8,4H10V8H14V4H16M1,2V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18Z"></path></svg>

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,13 @@
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
use:not(:target) {
display: none;
}
</style>
<defs>
<g id="safer_icon" stroke="none" stroke-width="1">
<path fill-rule="nonzero" d="M7.0 2.1658351C7.0 13.931584 7.0 2.1658351 7.0 13.931584C9.8656467 13.057677 12.0 10.241354 12.0 7.2727273C12.0 7.2727273 12.0 4.2437572 12.0 4.2437572C12.0 4.2437572 7.0 2.1658351 7.0 2.1658351C7.0 2.1658351 7.0 2.1658351 7.0 2.1658351M7.0 0.0C7.0 0.0 14.0 2.9090909 14.0 2.9090909C14.0 2.9090909 14.0 7.2727273 14.0 7.2727273C14.0 11.309091 11.013333 15.083636 7.0 16.0C2.9866667 15.083636 0.0 11.309091 0.0 7.2727273C0.0 7.2727273 0.0 2.9090909 0.0 2.9090909C0.0 2.9090909 7.0 0.0 7.0 0.0"/>
</g>
</defs>
<use id="safer" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_icon" />
</svg>

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,13 @@
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
use:not(:target) {
display: none;
}
</style>
<defs>
<g id="safest_icon" stroke="none" stroke-width="1">
<path d="M7.0 0.0C7.0 0.0 14.0 2.90909091 14.0 2.90909091C14.0 2.90909091 14.0 7.27272727 14.0 7.27272727C14.0 11.3090909 11.0133333 15.0836364 7.0 16.0C2.98666667 15.0836364 0.0 11.3090909 0.0 7.27272727C0.0 7.27272727 0.0 2.90909091 0.0 2.90909091C0.0 2.90909091 7.0 0.0 7.0 0.0C7.0 0.0 7.0 0.0 7.0 0.0" />
</g>
</defs>
<use id="safest" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_icon" />
</svg>

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,13 @@
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
use:not(:target) {
display: none;
}
</style>
<defs>
<g id="standard_icon" stroke="none" stroke-width="1">
<path d="M7.0 2.16583509C7.0 2.16583509 2.0 4.24375717 2.0 4.24375717C2.0 4.24375717 2.0 7.27272727 2.0 7.27272727C2.0 10.2413541 4.13435329 13.0576771 7.0 13.9315843C9.8656467 13.0576771 12.0 10.2413541 12.0 7.27272727C12.0 7.27272727 12.0 4.24375717 12.0 4.24375717C12.0 4.24375717 7.0 2.16583509 7.0 2.16583509C7.0 2.16583509 7.0 2.16583509 7.0 2.16583509M7.0 0.0C7.0 0.0 14.0 2.90909091 14.0 2.90909091C14.0 2.90909091 14.0 7.27272727 14.0 7.27272727C14.0 11.3090909 11.0133333 15.0836364 7.0 16.0C2.98666667 15.0836364 0.0 11.3090909 0.0 7.27272727C0.0 7.27272727 0.0 2.90909091 0.0 2.90909091C0.0 2.90909091 7.0 0.0 7.0 0.0C7.0 0.0 7.0 0.0 7.0 0.0" />
</g>
</defs>
<use id="standard" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_icon" />
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -5,16 +5,15 @@
#include "calcwidget.h"
#include "ui_calcwidget.h"
#include "mainwindow.h"
#include "components.h"
#include "utils/ColorScheme.h"
#include "utils/AppData.h"
#include "utils/config.h"
CalcWidget::CalcWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::CalcWidget)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
ui->imageExchange->setBackgroundRole(QPalette::Base);
ui->imageExchange->setAssets(":/assets/images/exchange.png", ":/assets/images/exchange_white.png");
@ -30,8 +29,8 @@ CalcWidget::CalcWidget(QWidget *parent) :
ui->lineFrom->setValidator(dv);
ui->lineTo->setValidator(dv);
connect(AppContext::prices, &Prices::fiatPricesUpdated, this, &CalcWidget::initFiat);
connect(AppContext::prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::initCrypto);
connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::initFiat);
connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::initCrypto);;
}
void CalcWidget::fromChanged(const QString &data) {
@ -56,12 +55,12 @@ void CalcWidget::fromChanged(const QString &data) {
}
double amount = amount_str.toDouble();
double result = AppContext::prices->convert(symbolFrom, symbolTo, amount);
double result = appData()->prices.convert(symbolFrom, symbolTo, amount);
this->m_changing = true;
int precision = 10;
if(AppContext::prices->rates.contains(symbolTo))
if (appData()->prices.rates.contains(symbolTo))
precision = 2;
ui->lineTo->setText(QString::number(result, 'f', precision));
@ -91,12 +90,12 @@ void CalcWidget::toChanged(const QString &data) {
}
double amount = amount_str.toDouble();
double result = AppContext::prices->convert(symbolTo, symbolFrom, amount);
double result = appData()->prices.convert(symbolTo, symbolFrom, amount);
this->m_changing = true;
int precision = 10;
if(AppContext::prices->rates.contains(symbolFrom))
if(appData()->prices.rates.contains(symbolFrom))
precision = 2;
ui->lineFrom->setText(QString::number(result, 'f', precision));
@ -116,8 +115,8 @@ void CalcWidget::initFiat() {
void CalcWidget::initComboBox() {
if(m_comboBoxInit) return;
QList<QString> marketsKeys = AppContext::prices->markets.keys();
QList<QString> ratesKeys = AppContext::prices->rates.keys();
QList<QString> marketsKeys = appData()->prices.markets.keys();
QList<QString> ratesKeys = appData()->prices.rates.keys();
if(marketsKeys.count() <= 0 || ratesKeys.count() <= 0) return;
ui->comboCalcFrom->addItems(marketsKeys);

View file

@ -4,8 +4,7 @@
#ifndef CALC_H
#define CALC_H
#include <QMainWindow>
#include "appcontext.h"
#include <QWidget>
namespace Ui {
class CalcWidget;
@ -32,7 +31,7 @@ public slots:
private:
Ui::CalcWidget *ui;
AppContext *m_ctx;
bool m_comboBoxInit = false;
void initComboBox();
bool m_changing = false;

View file

@ -3,6 +3,8 @@
#include "calcwindow.h"
#include "mainwindow.h"
#include "utils/Icons.h"
#include "utils/AppData.h"
#include "ui_calcwindow.h"
@ -14,10 +16,10 @@ CalcWindow::CalcWindow(QWidget *parent) :
this->setWindowFlags(flags|Qt::WindowStaysOnTopHint); // on top
ui->setupUi(this);
this->setWindowIcon(QIcon("://assets/images/gnome-calc.png"));
this->setWindowIcon(icons()->icon("gnome-calc.png"));
connect(AppContext::prices, &Prices::fiatPricesUpdated, this, &CalcWindow::initFiat);
connect(AppContext::prices, &Prices::cryptoPricesUpdated, this, &CalcWindow::initCrypto);
connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWindow::initFiat);
connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWindow::initCrypto);
}
void CalcWindow::initFiat() {

View file

@ -6,6 +6,7 @@
#include "dialog/outputinfodialog.h"
#include "dialog/outputsweepdialog.h"
#include "mainwindow.h"
#include "utils/Icons.h"
#include <QClipboard>
#include <QMessageBox>
@ -26,7 +27,7 @@ CoinsWidget::CoinsWidget(QWidget *parent)
connect(ui->coins->header(), &QHeaderView::customContextMenuRequested, this, &CoinsWidget::showHeaderMenu);
// copy menu
m_copyMenu->setIcon(QIcon(":/assets/images/copy.png"));
m_copyMenu->setIcon(icons()->icon("copy.png"));
m_copyMenu->addAction("Public key", this, [this]{copy(copyField::PubKey);});
m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);});
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
@ -44,7 +45,7 @@ CoinsWidget::CoinsWidget(QWidget *parent)
m_freezeAllSelectedAction = new QAction("Freeze selected", this);
m_thawAllSelectedAction = new QAction("Thaw selected", this);
m_viewOutputAction = new QAction(QIcon(":/assets/images/info.png"), "Details", this);
m_viewOutputAction = new QAction(icons()->icon("info2.svg"), "Details", this);
m_sweepOutputAction = new QAction("Sweep output", this);
connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeOutput);
connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawOutput);
@ -129,7 +130,7 @@ void CoinsWidget::setShowSpent(bool show)
void CoinsWidget::freezeOutput() {
QModelIndex index = ui->coins->currentIndex();
QVector<int> indexes = {m_proxyModel->mapToSource(index).row()};
emit freeze(indexes);
this->freezeCoins(indexes);
}
void CoinsWidget::freezeAllSelected() {
@ -139,13 +140,13 @@ void CoinsWidget::freezeAllSelected() {
for (QModelIndex index: list) {
indexes.push_back(m_proxyModel->mapToSource(index).row()); // todo: will segfault if index get invalidated
}
emit freeze(indexes);
this->freezeCoins(indexes);
}
void CoinsWidget::thawOutput() {
QModelIndex index = ui->coins->currentIndex();
QVector<int> indexes = {m_proxyModel->mapToSource(index).row()};
emit thaw(indexes);
this->thawCoins(indexes);
}
void CoinsWidget::thawAllSelected() {
@ -155,7 +156,7 @@ void CoinsWidget::thawAllSelected() {
for (QModelIndex index: list) {
indexes.push_back(m_proxyModel->mapToSource(index).row());
}
emit thaw(indexes);
this->thawCoins(indexes);
}
void CoinsWidget::viewOutput() {
@ -181,7 +182,7 @@ void CoinsWidget::onSweepOutput() {
int ret = dialog->exec();
if (!ret) return;
emit sweepOutput(keyImage, dialog->address(), dialog->churn(), dialog->outputs());
m_ctx->onSweepOutput(keyImage, dialog->address(), dialog->churn(), dialog->outputs());
dialog->deleteLater();
}
@ -230,6 +231,22 @@ CoinsInfo* CoinsWidget::currentEntry() {
}
}
void CoinsWidget::freezeCoins(const QVector<int>& indexes) {
for (int i : indexes) {
m_ctx->currentWallet->coins()->freeze(i);
}
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
m_ctx->updateBalance();
}
void CoinsWidget::thawCoins(const QVector<int> &indexes) {
for (int i : indexes) {
m_ctx->currentWallet->coins()->thaw(i);
}
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
m_ctx->updateBalance();
}
CoinsWidget::~CoinsWidget() {
delete ui;
}

View file

@ -9,6 +9,7 @@
#include "model/CoinsProxyModel.h"
#include "libwalletqt/Coins.h"
#include <QMenu>
#include <QWidget>
#include <QtSvg/QSvgWidget>
@ -38,12 +39,10 @@ private slots:
void viewOutput();
void onSweepOutput();
signals:
void freeze(QVector<int> indexes);
void thaw(QVector<int> indexes);
void sweepOutput(const QString &keyImage, const QString &address, bool isChurn, int outputs);
private:
void freezeCoins(const QVector<int>& indexes);
void thawCoins(const QVector<int>& indexes);
enum copyField {
PubKey = 0,
KeyImage,

View file

@ -7,6 +7,7 @@
#include "model/ModelUtils.h"
#include "mainwindow.h"
#include "libwalletqt/AddressBook.h"
#include "utils/Icons.h"
#include <QMessageBox>
@ -28,14 +29,14 @@ ContactsWidget::ContactsWidget(QWidget *parent) :
// context menu
ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu);
m_contextMenu = new QMenu(ui->contacts);
m_contextMenu->addAction(QIcon(":/assets/images/person.svg"), "New contact", [this]{
m_contextMenu->addAction(icons()->icon("person.svg"), "New contact", [this]{
this->newContact();
});
// row context menu
m_rowMenu = new QMenu(ui->contacts);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ContactsWidget::copyAddress);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy name", this, &ContactsWidget::copyName);
m_rowMenu->addAction(icons()->icon("copy.png"), "Copy address", this, &ContactsWidget::copyAddress);
m_rowMenu->addAction(icons()->icon("copy.png"), "Copy name", this, &ContactsWidget::copyName);
m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo);
m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact);

19
src/dialog/InfoDialog.cpp Normal file
View file

@ -0,0 +1,19 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "InfoDialog.h"
#include "ui_InfoDialog.h"
InfoDialog::InfoDialog(QWidget *parent, const QString &title, const QString &infoData)
: QDialog(parent)
, ui(new Ui::InfoDialog)
{
ui->setupUi(this);
this->setWindowTitle(title);
ui->info->setPlainText(infoData);
}
InfoDialog::~InfoDialog() {
delete ui;
}

26
src/dialog/InfoDialog.h Normal file
View file

@ -0,0 +1,26 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_INFODIALOG_H
#define FEATHER_INFODIALOG_H
#include <QDialog>
namespace Ui {
class InfoDialog;
}
class InfoDialog : public QDialog
{
Q_OBJECT
public:
explicit InfoDialog(QWidget *parent, const QString &title, const QString &infoText);
~InfoDialog() override;
private:
Ui::InfoDialog *ui;
};
#endif //FEATHER_INFODIALOG_H

71
src/dialog/InfoDialog.ui Normal file
View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InfoDialog</class>
<widget class="QDialog" name="InfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>689</width>
<height>439</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPlainTextEdit" name="info">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>InfoDialog</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>InfoDialog</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

@ -0,0 +1,51 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "LocalMoneroInfoDialog.h"
#include "ui_LocalMoneroInfoDialog.h"
#include "utils/config.h"
#include "utils/utils.h"
LocalMoneroInfoDialog::LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row)
: QDialog(parent)
, ui(new Ui::LocalMoneroInfoDialog)
, m_model(model)
, m_row(row)
{
ui->setupUi(this);
setLabelText(ui->label_price, LocalMoneroModel::PriceXMR);
setLabelText(ui->label_seller, LocalMoneroModel::Seller);
setLabelText(ui->label_paymentMethod, LocalMoneroModel::PaymentMethod);
setLabelText(ui->label_paymentDetail, LocalMoneroModel::PaymentMethodDetail);
setLabelText(ui->label_tradeLimits, LocalMoneroModel::Limits);
QJsonObject offerData = model->getOffer(row);
QString details = offerData["data"].toObject()["msg"].toString();
details.remove("*");
if (details.isEmpty()) {
details = "No details.";
}
ui->info->setPlainText(details);
connect(ui->btn_goToOffer, &QPushButton::clicked, this, &LocalMoneroInfoDialog::onGoToOffer);
}
void LocalMoneroInfoDialog::setLabelText(QLabel *label, LocalMoneroModel::Column column) {
QString data = m_model->data(m_model->index(m_row, column)).toString();
label->setText(data);
}
void LocalMoneroInfoDialog::onGoToOffer() {
QJsonObject offerData = m_model->getOffer(m_row);
QString frontend = config()->get(Config::localMoneroFrontend).toString();
QString offerUrl = QString("%1/ad/%2").arg(frontend, offerData["data"].toObject()["ad_id"].toString());
Utils::externalLinkWarning(this, offerUrl);
}
LocalMoneroInfoDialog::~LocalMoneroInfoDialog() {
delete ui;
}

View file

@ -0,0 +1,35 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_LOCALMONEROINFODIALOG_H
#define FEATHER_LOCALMONEROINFODIALOG_H
#include <QDialog>
#include <QLabel>
#include "model/LocalMoneroModel.h"
namespace Ui {
class LocalMoneroInfoDialog;
}
class LocalMoneroInfoDialog : public QDialog
{
Q_OBJECT
public:
explicit LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row);
~LocalMoneroInfoDialog() override;
private slots:
void onGoToOffer();
private:
void setLabelText(QLabel *label, LocalMoneroModel::Column column);
Ui::LocalMoneroInfoDialog *ui;
LocalMoneroModel *m_model;
int m_row;
};
#endif //FEATHER_INFODIALOG_H

View file

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LocalMoneroInfoDialog</class>
<widget class="QDialog" name="LocalMoneroInfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>758</width>
<height>557</height>
</rect>
</property>
<property name="windowTitle">
<string>Offer info</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Info</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Price:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_price">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Seller:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_seller">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Payment method:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_paymentMethod">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Trade limits:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_tradeLimits">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Payment detail:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_paymentDetail">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="info">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_goToOffer">
<property name="text">
<string>Go to offer</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LocalMoneroInfoDialog</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>LocalMoneroInfoDialog</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

@ -211,7 +211,8 @@ TxProof TxProofDialog::getProof() {
return m_wallet->getSpendProof(m_txid, message);
}
case Mode::OutProof:
case Mode::InProof: { // Todo: split this into separate functions
case Mode::InProof:
default: { // Todo: split this into separate functions
return m_wallet->getTxProof(m_txid, address, message);
}
}

223
src/dialog/UpdateDialog.cpp Normal file
View file

@ -0,0 +1,223 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "UpdateDialog.h"
#include "ui_UpdateDialog.h"
#include <QFileDialog>
#include <utility>
#include "utils/networking.h"
#include "utils/NetworkManager.h"
#include "utils/AsyncTask.h"
#include "utils/Updater.h"
#include "utils/utils.h"
#include "zip.h"
UpdateDialog::UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer)
: QDialog(parent)
, ui(new Ui::UpdateDialog)
, m_version(std::move(version))
, m_downloadUrl(std::move(downloadUrl))
, m_hash(std::move(hash))
, m_signer(std::move(signer))
{
ui->setupUi(this);
ui->btn_installUpdate->hide();
ui->btn_restart->hide();
ui->progressBar->hide();
auto bigFont = Utils::relativeFont(4);
ui->label_header->setFont(bigFont);
ui->label_header->setText(QString("New Feather version %1 is available").arg(m_version));
connect(ui->btn_cancel, &QPushButton::clicked, [this]{
if (m_reply) {
m_reply->abort();
}
this->reject();
});
connect(ui->btn_download, &QPushButton::clicked, this, &UpdateDialog::onDownloadClicked);
connect(ui->btn_installUpdate, &QPushButton::clicked, this, &UpdateDialog::onInstallUpdate);
connect(ui->btn_restart, &QPushButton::clicked, this, &UpdateDialog::onRestartClicked);
this->adjustSize();
}
void UpdateDialog::onDownloadClicked() {
ui->btn_download->hide();
ui->progressBar->show();
UtilsNetworking network{getNetworkTor()};
m_reply = network.get(m_downloadUrl);
connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress);
connect(m_reply, &QNetworkReply::finished, this, &UpdateDialog::onDownloadFinished);
}
void UpdateDialog::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
ui->progressBar->setValue(bytesReceived);
ui->progressBar->setMaximum(bytesTotal);
}
void UpdateDialog::onDownloadFinished() {
bool error = (m_reply->error() != QNetworkReply::NoError);
if (error) {
this->onDownloadError(QString("Network error: %1").arg(m_reply->errorString()));
return;
}
QByteArray response = m_reply->readAll();
if (response.isEmpty()) {
this->onDownloadError("Network error: Empty response");
return;
}
std::string responseStr = response.toStdString();
try {
const QByteArray calculatedHash = AsyncTask::runAndWaitForFuture([this, responseStr]{
return Updater().getHash(&responseStr[0], responseStr.size());
});
const QByteArray signedHash = QByteArray::fromHex(m_hash.toUtf8());
if (signedHash != calculatedHash) {
this->onDownloadError("Error: Hash sum mismatch.");
return;
}
}
catch (const std::exception &e)
{
this->onDownloadError(QString("Error: Unable to calculate sha256sum: %1").arg(e.what()));
return;
}
this->setStatus("Download finished and verified.", true);
ui->btn_installUpdate->show();
ui->progressBar->hide();
m_updateZipArchive = responseStr;
}
void UpdateDialog::onDownloadError(const QString &errMsg) {
// Clean up so download can be retried
this->setStatus(errMsg);
ui->progressBar->hide();
ui->progressBar->setMaximum(100);
ui->progressBar->setValue(0);
ui->btn_download->show();
ui->btn_download->setText("Retry download");
}
void UpdateDialog::onInstallUpdate() {
ui->btn_installUpdate->hide();
this->setStatus("Unzipping archive...");
zip_error_t err;
zip_error_init(&err);
zip_source_t *zip_source = zip_source_buffer_create(&m_updateZipArchive[0], m_updateZipArchive.size(), 0, &err);
if (!zip_source) {
this->onInstallError(QString("Error in libzip: Unable to create zip source from buffer: %1").arg(QString::fromStdString(err.str)));
return;
}
zip_t *zip_archive = zip_open_from_source(zip_source, 0, &err);
if (!zip_archive) {
this->onInstallError(QString("Error in libzip: Unable to open archive from source: %1").arg(QString::fromStdString(err.str)));
return;
}
auto num_entries = zip_get_num_entries(zip_archive, 0);
if (num_entries <= 0) {
this->onInstallError("Error in libzip: Archive has no entries");
return;
}
// We only expect the archive to contain 1 file
std::string fname = zip_get_name(zip_archive, 0, 0);
if (fname.empty()) {
this->onInstallError("Error in libzip: Invalid filename in archive");
return;
}
struct zip_stat sb;
if (zip_stat_index(zip_archive, 0, 0, &sb) != 0) {
this->onInstallError("Error in libzip: Entry index not found");
return;
}
QString name = QString::fromStdString(sb.name);
qDebug() << "File found in archive: " << name << ", with size: " << QString::number(sb.size);
struct zip_file *zf;
zf = zip_fopen_index(zip_archive, 0, 0);
if (!zf) {
this->onInstallError("Error in libzip: Unable to open entry");
return;
}
std::unique_ptr<char[]> contents{new char[sb.size]};
auto bytes_read = zip_fread(zf, contents.get(), sb.size);
if (bytes_read != sb.size){
this->onInstallError("Error in libzip: File size inconsistent");
return;
}
zip_fclose(zf);
zip_close(zip_archive);
QString applicationPath = qgetenv("APPIMAGE");
if (applicationPath.isEmpty()) {
applicationPath = QCoreApplication::applicationDirPath();
}
QDir applicationDir(applicationPath);
QString filePath = applicationDir.filePath(name);
m_updatePath = filePath;
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly))
{
this->onInstallError("Error: Could not write to application directory");
return;
}
if (static_cast<size_t>(file.write(&contents[0], sb.size)) != sb.size) {
this->onInstallError("Error: Unable to write file");
return;
}
if (!file.setPermissions(QFile::ExeUser | QFile::ExeOwner | QFile::ReadUser | QFile::ReadOwner | QFile::WriteUser | QFile::WriteOwner)) {
this->onInstallError("Error: Unable to set executable flags");
return;
}
this->setStatus("Installation successful. Do you want to restart Feather now?");
ui->btn_restart->show();
}
void UpdateDialog::onInstallError(const QString &errMsg) {
this->setStatus(errMsg);
}
void UpdateDialog::onRestartClicked() {
emit restartWallet(m_updatePath);
}
void UpdateDialog::setStatus(const QString &msg, bool success) {
ui->label_body->setText(msg);
if (success)
ui->label_body->setStyleSheet("QLabel { color : #2EB358; }");
else
ui->label_body->setStyleSheet("");
}
UpdateDialog::~UpdateDialog() {
delete ui;
}

51
src/dialog/UpdateDialog.h Normal file
View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_UPDATEDIALOG_H
#define FEATHER_UPDATEDIALOG_H
#include <QDialog>
#include <QNetworkReply>
namespace Ui {
class UpdateDialog;
}
class UpdateDialog : public QDialog
{
Q_OBJECT
public:
explicit UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer);
~UpdateDialog() override;
private slots:
void onDownloadClicked();
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onDownloadFinished();
void onDownloadError(const QString &errMsg);
void onInstallUpdate();
void onInstallError(const QString &errMsg);
void onRestartClicked();
signals:
void restartWallet(const QString &binaryFilename);
private:
void setStatus(const QString &msg, bool success = false);
QString m_version;
QString m_downloadUrl;
QString m_hash;
QString m_signer;
QString m_updatePath;
std::string m_updateZipArchive;
QNetworkReply *m_reply = nullptr;
Ui::UpdateDialog *ui;
};
#endif //FEATHER_UPDATEDIALOG_H

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UpdateDialog</class>
<widget class="QDialog" name="UpdateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>569</width>
<height>148</height>
</rect>
</property>
<property name="windowTitle">
<string>Update Available</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_header">
<property name="text">
<string>New Feather version is available.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_body">
<property name="text">
<string>Do you want to download and verify the new version?</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_download">
<property name="text">
<string>Download</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_installUpdate">
<property name="text">
<string>Install Update</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_restart">
<property name="text">
<string>Restart Feather</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -3,6 +3,7 @@
#include "broadcasttxdialog.h"
#include "ui_broadcasttxdialog.h"
#include "utils/NetworkManager.h"
#include <QMessageBox>
@ -13,10 +14,8 @@ BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QSt
{
ui->setupUi(this);
m_network = new UtilsNetworking(m_ctx->network, this);
auto node = ctx->nodes->connection();
m_rpc = new DaemonRpc(this, m_network, node.full);
m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress());
connect(ui->btn_Broadcast, &QPushButton::clicked, this, &BroadcastTxDialog::broadcastTx);
connect(ui->btn_Close, &QPushButton::clicked, this, &BroadcastTxDialog::reject);
@ -38,7 +37,7 @@ void BroadcastTxDialog::broadcastTx() {
if (ui->radio_useCustom->isChecked())
node = ui->customNode->text();
else if (ui->radio_useDefault->isChecked())
node = m_ctx->nodes->connection().full;
node = m_ctx->nodes->connection().toAddress();
if (!node.startsWith("http://"))
node = QString("http://%1").arg(node);

View file

@ -4,6 +4,9 @@
#include "debuginfodialog.h"
#include "ui_debuginfodialog.h"
#include "config-feather.h"
#include "utils/WebsocketClient.h"
#include "utils/TorManager.h"
#include "utils/WebsocketNotifier.h"
DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
: QDialog(parent)
@ -26,16 +29,16 @@ void DebugInfoDialog::updateInfo() {
// Special case for Tails because we know the status of the daemon by polling tails-tor-has-bootstrapped.target
if(m_ctx->isTails) {
if(m_ctx->tor->torConnected)
if(torManager()->torConnected)
torStatus = "Connected";
else
torStatus = "Disconnected";
}
else if(m_ctx->isTorSocks)
else if(Utils::isTorsocks())
torStatus = "Torsocks";
else if(m_ctx->tor->localTor)
else if(torManager()->isLocalTor())
torStatus = "Local (assumed to be running)";
else if(m_ctx->tor->torConnected)
else if(torManager()->torConnected)
torStatus = "Running";
else
torStatus = "Unknown";
@ -50,13 +53,37 @@ void DebugInfoDialog::updateInfo() {
ui->label_synchronized->setText(m_ctx->currentWallet->isSynchronized() ? "True" : "False");
auto node = m_ctx->nodes->connection();
ui->label_remoteNode->setText(node.full);
ui->label_remoteNode->setText(node.toAddress());
ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus()));
ui->label_torStatus->setText(torStatus);
ui->label_websocketStatus->setText(Utils::QtEnumToString(m_ctx->ws->webSocket.state()).remove("State"));
ui->label_websocketStatus->setText(Utils::QtEnumToString(websocketNotifier()->websocketClient.webSocket.state()).remove("State"));
QString seedType = [this](){
if (m_ctx->currentWallet->isHwBacked())
return "Hardware";
if (m_ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty())
return "25 word";
else
return "14 word";
}();
QString deviceType = [this](){
if (m_ctx->currentWallet->isHwBacked()) {
if (m_ctx->currentWallet->isLedger())
return "Ledger";
else if (m_ctx->currentWallet->isTrezor())
return "Trezor";
else
return "Unknown";
}
else {
return "Software";
}
}();
ui->label_netType->setText(Utils::QtEnumToString(m_ctx->currentWallet->nettype()));
ui->label_seedType->setText(m_ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word");
ui->label_seedType->setText(seedType);
ui->label_deviceType->setText(deviceType);
ui->label_viewOnly->setText(m_ctx->currentWallet->viewOnly() ? "True" : "False");
ui->label_primaryOnly->setText(m_ctx->currentWallet->balance(0) == m_ctx->currentWallet->balanceAll() ? "True" : "False");
@ -107,6 +134,7 @@ void DebugInfoDialog::copyToClipboad() {
text += QString("Network type: %1 \n").arg(ui->label_netType->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("View only: %1 \n").arg(ui->label_viewOnly->text());
text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text());

View file

@ -91,6 +91,23 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_targetHeight">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
@ -242,13 +259,27 @@
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Device type:</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QLabel" name="label_deviceType">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>View only:</string>
</property>
</widget>
</item>
<item row="16" column="1">
<item row="17" column="1">
<widget class="QLabel" name="label_viewOnly">
<property name="text">
<string>TextLabel</string>
@ -258,65 +289,14 @@
</property>
</widget>
</item>
<item row="20" column="0">
<widget class="QLabel" name="label_24">
<item row="18" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Timestamp:</string>
</property>
</widget>
</item>
<item row="20" column="1">
<widget class="QLabel" name="label_timestamp">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="19" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Operating system:</string>
</property>
</widget>
</item>
<item row="19" column="1">
<widget class="QLabel" name="label_OS">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
<string>Primary only:</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_targetHeight">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QLabel" name="label_primaryOnly">
<property name="text">
<string>TextLabel</string>
@ -326,10 +306,44 @@
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="label_11">
<item row="19" column="1">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="20" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Primary only:</string>
<string>Operating system:</string>
</property>
</widget>
</item>
<item row="20" column="1">
<widget class="QLabel" name="label_OS">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="21" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Timestamp:</string>
</property>
</widget>
</item>
<item row="21" column="1">
<widget class="QLabel" name="label_timestamp">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>

View file

@ -10,12 +10,15 @@ KeysDialog::KeysDialog(AppContext *ctx, QWidget *parent)
{
ui->setupUi(this);
ui->label_restoreHeight->setText(QString::number(ctx->currentWallet->getWalletCreationHeight()));
ui->label_primaryAddress->setText(ctx->currentWallet->address(0, 0));
ui->label_secretSpendKey->setText(ctx->currentWallet->getSecretSpendKey());
ui->label_secretViewKey->setText(ctx->currentWallet->getSecretViewKey());
ui->label_publicSpendKey->setText(ctx->currentWallet->getPublicSpendKey());
ui->label_publicViewKey->setText(ctx->currentWallet->getPublicViewKey());
auto w = ctx->currentWallet;
QString unavailable = "Unavailable: Key is stored on hardware device";
ui->label_restoreHeight->setText(QString::number(w->getWalletCreationHeight()));
ui->label_primaryAddress->setText(w->address(0, 0));
ui->label_secretSpendKey->setText(w->isHwBacked() ? unavailable : w->getSecretSpendKey());
ui->label_secretViewKey->setText(w->getSecretViewKey());
ui->label_publicSpendKey->setText(w->getPublicSpendKey());
ui->label_publicViewKey->setText(w->getPublicViewKey());
this->adjustSize();
}

View file

@ -18,7 +18,7 @@ RestoreDialog::RestoreDialog(AppContext *ctx, QWidget *parent)
ui->restoreHeightWidget->hideSlider();
} else {
// load restoreHeight lookup db
ui->restoreHeightWidget->initRestoreHeights(m_ctx->restoreHeights[m_ctx->networkType]);
ui->restoreHeightWidget->initRestoreHeights(appData()->restoreHeights[m_ctx->networkType]);
}
}

View file

@ -0,0 +1,24 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "splashdialog.h"
#include "ui_splashdialog.h"
SplashDialog::SplashDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::SplashDialog)
{
ui->setupUi(this);
QPixmap pixmap = QPixmap(":/assets/images/key.png");
ui->icon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
this->setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
this->adjustSize();
}
SplashDialog::~SplashDialog() {
delete ui;
}

25
src/dialog/splashdialog.h Normal file
View file

@ -0,0 +1,25 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_SPLASHDIALOG_H
#define FEATHER_SPLASHDIALOG_H
#include <QDialog>
namespace Ui {
class SplashDialog;
}
class SplashDialog : public QDialog
{
Q_OBJECT
public:
explicit SplashDialog(QWidget *parent = nullptr);
~SplashDialog() override;
private:
Ui::SplashDialog *ui;
};
#endif //FEATHER_SPLASHDIALOG_H

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SplashDialog</class>
<widget class="QDialog" name="SplashDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>751</width>
<height>101</height>
</rect>
</property>
<property name="windowTitle">
<string>Device Action Required</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_message">
<property name="text">
<string>Action required on device: Export the view key to open the wallet.</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -4,32 +4,146 @@
#include "torinfodialog.h"
#include "ui_torinfodialog.h"
#include <QPushButton>
#include <QDesktopServices>
#include <QMessageBox>
TorInfoDialog::TorInfoDialog(AppContext *ctx, QWidget *parent)
#include "utils/TorManager.h"
TorInfoDialog::TorInfoDialog(QWidget *parent, AppContext *ctx)
: QDialog(parent)
, ui(new Ui::TorInfoDialog)
, m_ctx(ctx)
{
ui->setupUi(this);
if (!m_ctx->tor->torConnected && !m_ctx->tor->errorMsg.isEmpty()) {
ui->message->setText(m_ctx->tor->errorMsg);
if (!torManager()->torConnected && !torManager()->errorMsg.isEmpty()) {
ui->message->setText(torManager()->errorMsg);
} else {
ui->message->setText(QString("Currently using Tor instance: %1:%2").arg(Tor::torHost).arg(Tor::torPort));
ui->message->hide();
}
if (m_ctx->tor->localTor) {
ui->logs->setHidden(true);
if (torManager()->isLocalTor()) {
ui->frame_logs->setHidden(true);
} else {
ui->logs->setPlainText(m_ctx->tor->torLogs);
ui->logs->setPlainText(torManager()->torLogs);
}
initConnectionSettings();
initPrivacyLevel();
onConnectionStatusChanged(torManager()->torConnected);
connect(torManager(), &TorManager::connectionStateChanged, this, &TorInfoDialog::onConnectionStatusChanged);
connect(torManager(), &TorManager::logsUpdated, this, &TorInfoDialog::onLogsUpdated);
connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &TorInfoDialog::onApplySettings);
connect(ui->line_host, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
connect(ui->line_port, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
connect(ui->check_useLocalTor, &QCheckBox::stateChanged, this, &TorInfoDialog::onSettingsChanged);
connect(ui->btnGroup_privacyLevel, &QButtonGroup::idToggled, this, &TorInfoDialog::onSettingsChanged);
ui->label_changes->hide();
#ifndef HAS_TOR_BIN
ui->check_useLocalTor->setChecked(true);
ui->check_useLocalTor->setEnabled(false);
ui->check_useLocalTor->setToolTip("Feather was bundled without Tor");
#endif
this->adjustSize();
}
void TorInfoDialog::onLogsUpdated() {
ui->logs->setPlainText(m_ctx->tor->torLogs);
ui->logs->setPlainText(torManager()->torLogs);
}
void TorInfoDialog::onConnectionStatusChanged(bool connected) {
if (connected) {
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_connected.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Connected");
} else {
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Disconnected");
}
}
void TorInfoDialog::onApplySettings() {
config()->set(Config::socks5Host, ui->line_host->text());
config()->set(Config::socks5Port, ui->line_port->text());
int id = ui->btnGroup_privacyLevel->checkedId();
config()->set(Config::torPrivacyLevel, id);
ui->label_changes->hide();
bool useLocalTor = ui->check_useLocalTor->isChecked();
if (config()->get(Config::useLocalTor).toBool() && useLocalTor && torManager()->isStarted()) {
QMessageBox::warning(this, "Warning", "Feather is running the bundled Tor daemon, "
"but the option to never start a bundled Tor daemon was selected. "
"A restart is required to apply the setting.");
}
config()->set(Config::useLocalTor, useLocalTor);
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_lagging.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Connecting");
emit torSettingsChanged();
}
void TorInfoDialog::onSettingsChanged() {
ui->label_changes->show();
}
void TorInfoDialog::initConnectionSettings() {
bool localTor = torManager()->isLocalTor();
ui->label_connectionSettingsMessage->setVisible(!localTor);
ui->frame_connectionSettings->setVisible(localTor);
ui->line_host->setText(config()->get(Config::socks5Host).toString());
ui->line_port->setText(config()->get(Config::socks5Port).toString());
ui->check_useLocalTor->setChecked(config()->get(Config::useLocalTor).toBool());
}
void TorInfoDialog::initPrivacyLevel() {
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptNode, Config::allTorExceptNode);
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptInitSync, Config::allTorExceptInitSync);
ui->btnGroup_privacyLevel->setId(ui->radio_allTor, Config::allTor);
int privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
auto button = ui->btnGroup_privacyLevel->button(privacyLevel);
if (button) {
button->setChecked(true);
}
if (m_ctx->nodes->connection().isLocal()) {
ui->label_notice->setText("You are connected to a local node. Traffic is not routed over Tor.");
}
else if (Utils::isTorsocks() || WhonixOS::detect() || TailsOS::detect()) {
ui->radio_allTorExceptNode->setEnabled(false);
ui->radio_allTorExceptInitSync->setEnabled(false);
if (Utils::isTorsocks())
ui->label_notice->setText("Feather was started with torsocks, all traffic is routed over Tor");
else if (WhonixOS::detect())
ui->label_notice->setText("Feather is running on Whonix, all traffic is routed over Tor");
else if (TailsOS::detect())
ui->label_notice->setText("Feather is running on Tails, all traffic is routed over Tor");
} else {
ui->frame_notice->hide();
}
QPixmap iconNoTor(":/assets/images/securityLevelStandardWhite.png");
QPixmap iconNoSync(":/assets/images/securityLevelSaferWhite.png");
QPixmap iconAllTor(":/assets/images/securityLevelSafestWhite.png");
ui->icon_noTor->setPixmap(iconNoTor.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_noSync->setPixmap(iconNoSync.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_allTor->setPixmap(iconAllTor.scaledToHeight(16, Qt::SmoothTransformation));
}
void TorInfoDialog::onStopTor() {
torManager()->stop();
}
TorInfoDialog::~TorInfoDialog() {

View file

@ -5,6 +5,7 @@
#define FEATHER_TORINFODIALOG_H
#include <QDialog>
#include <QAbstractButton>
#include "appcontext.h"
@ -17,13 +18,25 @@ class TorInfoDialog : public QDialog
Q_OBJECT
public:
explicit TorInfoDialog(AppContext *ctx, QWidget *parent = nullptr);
explicit TorInfoDialog(QWidget *parent, AppContext *ctx);
~TorInfoDialog() override;
public slots:
void onLogsUpdated();
private slots:
void onConnectionStatusChanged(bool connected);
void onApplySettings();
void onSettingsChanged();
void onStopTor();
signals:
void torSettingsChanged();
private:
void initConnectionSettings();
void initPrivacyLevel();
Ui::TorInfoDialog *ui;
AppContext *m_ctx;
};

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>618</width>
<height>386</height>
<width>703</width>
<height>804</height>
</rect>
</property>
<property name="windowTitle">
@ -15,24 +15,335 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="message">
<property name="text">
<string>Message</string>
<widget class="QGroupBox" name="group_connectionSettings">
<property name="title">
<string>Connection settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="frame_connectionSettings">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_host">
<property name="text">
<string>127.0.0.1</string>
</property>
<property name="placeholderText">
<string>127.0.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_port">
<property name="text">
<string>9050</string>
</property>
<property name="placeholderText">
<string>9050</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_connectionSettingsMessage">
<property name="text">
<string>Tor daemon is managed by Feather.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_useLocalTor">
<property name="text">
<string>Never start bundled Tor (requires local Tor daemon)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="logs">
<property name="minimumSize">
<widget class="QGroupBox" name="group_privacyLevel">
<property name="title">
<string>Privacy Level</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="icon_noTor">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTorExceptNode">
<property name="text">
<string>Route all traffic over Tor, except traffic to node</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="icon_noSync">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTorExceptInitSync">
<property name="text">
<string>Route all traffic over Tor, except initial wallet synchronization</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="icon_allTor">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTor">
<property name="text">
<string>Route all traffic over Tor</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame_notice">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_notice">
<property name="text">
<string>notice</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Status</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="icon_connectionStatus">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_testConnectionStatus">
<property name="text">
<string>status</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_changes">
<property name="text">
<string>(changes not applied)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="message">
<property name="text">
<string>Message</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_logs">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Logs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPlainTextEdit" name="logs">
<property name="minimumSize">
<size>
<width>600</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>600</width>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
@ -40,7 +351,7 @@
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::Apply|QDialogButtonBox::Close</set>
</property>
</widget>
</item>
@ -81,4 +392,7 @@
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="btnGroup_privacyLevel"/>
</buttongroups>
</ui>

View file

@ -8,7 +8,6 @@
#include "libwalletqt/Transfer.h"
#include "libwalletqt/Input.h"
#include "model/ModelUtils.h"
#include "utils/ColorScheme.h"
#include <QFileDialog>
#include <QMessageBox>

View file

@ -6,6 +6,7 @@
#include "model/ModelUtils.h"
#include "txconfadvdialog.h"
#include "globals.h"
#include "utils/AppData.h"
#include "utils/ColorScheme.h"
#include <QMessageBox>
@ -26,7 +27,7 @@ TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QStrin
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString();
auto convert = [preferredCur](double amount){
return QString::number(AppContext::prices->convert("XMR", preferredCur, amount), 'f', 2);
return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2);
};
QString amount = WalletManager::displayAmount(tx->amount());

View file

@ -3,6 +3,7 @@
#include "tximportdialog.h"
#include "ui_tximportdialog.h"
#include "utils/NetworkManager.h"
#include <QMessageBox>
@ -16,10 +17,8 @@ TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx)
ui->resp->hide();
ui->label_loading->hide();
m_network = new UtilsNetworking(m_ctx->network, this);
auto node = ctx->nodes->connection();
m_rpc = new DaemonRpc(this, m_network, node.full);
m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress());
connect(ui->btn_load, &QPushButton::clicked, this, &TxImportDialog::loadTx);
connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport);
@ -35,7 +34,7 @@ TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx)
void TxImportDialog::loadTx() {
QString txid = ui->line_txid->text();
QString node = m_ctx->nodes->connection().full;
QString node = m_ctx->nodes->connection().toAddress();
if (!node.startsWith("http://"))
node = QString("http://%1").arg(node);

View file

@ -22,6 +22,9 @@ namespace globals
// websocket constants
const QUrl websocketUrl = QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws"));
// website constants
const QString websiteUrl = "https://featherwallet.org";
}
#endif //FEATHER_GLOBALS_H

View file

@ -5,6 +5,10 @@
#include "ui_historywidget.h"
#include "dialog/transactioninfodialog.h"
#include "dialog/TxProofDialog.h"
#include "utils/Icons.h"
#include "utils/config.h"
#include "appcontext.h"
#include <QMessageBox>
HistoryWidget::HistoryWidget(QWidget *parent)
@ -15,11 +19,11 @@ HistoryWidget::HistoryWidget(QWidget *parent)
{
ui->setupUi(this);
m_contextMenu->addMenu(m_copyMenu);
m_contextMenu->addAction(QIcon(":/assets/images/info.png"), "Show details", this, &HistoryWidget::showTxDetails);
m_contextMenu->addAction(QIcon(":/assets/images/network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
m_contextMenu->addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails);
m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
// copy menu
m_copyMenu->setIcon(QIcon(":/assets/images/copy.png"));
m_copyMenu->setIcon(icons()->icon("copy.png"));
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);});
@ -56,12 +60,12 @@ void HistoryWidget::showContextMenu(const QPoint &point) {
bool unconfirmed = tx->isFailed() || tx->isPending();
if (AppContext::txCache.contains(tx->hash()) && unconfirmed && tx->direction() != TransactionInfo::Direction_In) {
menu.addAction(QIcon(":/assets/images/info.png"), "Resend transaction", this, &HistoryWidget::onResendTransaction);
menu.addAction(icons()->icon("info2.svg"), "Resend transaction", this, &HistoryWidget::onResendTransaction);
}
menu.addMenu(m_copyMenu);
menu.addAction(QIcon(":/assets/images/info.png"), "Show details", this, &HistoryWidget::showTxDetails);
menu.addAction(QIcon(":/assets/images/network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
menu.addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails);
menu.addAction(icons()->icon("network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof);
menu.exec(ui->history->viewport()->mapToGlobal(point));

View file

@ -10,6 +10,11 @@ Subaddress::Subaddress(Monero::Subaddress *subaddressImpl, QObject *parent)
getAll();
}
QString Subaddress::errorString() const
{
return QString::fromStdString(m_subaddressImpl->errorString());
}
void Subaddress::getAll() const
{
emit refreshStarted();
@ -46,17 +51,24 @@ bool Subaddress::getRow(int index, std::function<void (Monero::SubaddressRow &ro
return true;
}
void Subaddress::addRow(quint32 accountIndex, const QString &label) const
bool Subaddress::addRow(quint32 accountIndex, const QString &label) const
{
m_subaddressImpl->addRow(accountIndex, label.toStdString());
getAll();
bool r = m_subaddressImpl->addRow(accountIndex, label.toStdString());
if (r)
getAll();
return r;
}
void Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const
bool Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const
{
m_subaddressImpl->setLabel(accountIndex, addressIndex, label.toStdString());
getAll();
emit labelChanged();
bool r = m_subaddressImpl->setLabel(accountIndex, addressIndex, label.toStdString());
if (r) {
getAll();
emit labelChanged();
}
return r;
}
void Subaddress::refresh(quint32 accountIndex) const

View file

@ -16,13 +16,14 @@ class Subaddress : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE void getAll() const;
Q_INVOKABLE bool getRow(int index, std::function<void (Monero::SubaddressRow &row)> callback) const;
Q_INVOKABLE void addRow(quint32 accountIndex, const QString &label) const;
Q_INVOKABLE void setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const;
Q_INVOKABLE void refresh(quint32 accountIndex) const;
Q_INVOKABLE quint64 unusedLookahead() const;
void getAll() const;
bool getRow(int index, std::function<void (Monero::SubaddressRow &row)> callback) const;
bool addRow(quint32 accountIndex, const QString &label) const;
bool setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const;
void refresh(quint32 accountIndex) const;
quint64 unusedLookahead() const;
quint64 count() const;
QString errorString() const;
Monero::SubaddressRow* row(int index) const;
signals:

View file

@ -4,8 +4,8 @@
#include "TransactionHistory.h"
#include "TransactionInfo.h"
#include "utils/utils.h"
#include "appcontext.h"
#include "utils/AppData.h"
#include "utils/config.h"
bool TransactionHistory::transaction(int index, std::function<void (TransactionInfo &)> callback)
{
@ -164,11 +164,11 @@ bool TransactionHistory::writeCSV(const QString &path) {
// calc historical fiat price
QString fiatAmount;
QString preferredFiatSymbol = config()->get(Config::preferredFiatCurrency).toString();
const double usd_price = AppContext::txFiatHistory->get(timeStamp.toString("yyyyMMdd"));
const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd"));
double fiat_price = usd_price * amount;
if(preferredFiatSymbol != "USD")
fiat_price = AppContext::prices->convert("USD", preferredFiatSymbol, fiat_price);
fiat_price = appData()->prices.convert("USD", preferredFiatSymbol, fiat_price);
double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0;
if(fiat_price != 0)
fiatAmount = QString("%1 %2").arg(QString::number(fiat_rounded)).arg(preferredFiatSymbol);

View file

@ -283,6 +283,11 @@ bool Wallet::isTrezor() const
return m_walletImpl->getDeviceType() == Monero::Wallet::Device_Trezor;
}
bool Wallet::reconnectDevice()
{
return m_walletImpl->reconnectDevice();
}
//! create a view only wallet
bool Wallet::createViewOnly(const QString &path, const QString &password) const
{
@ -1224,13 +1229,23 @@ void Wallet::onWalletPassphraseNeeded(bool on_device)
}
quint64 Wallet::getBytesReceived() const {
return m_walletImpl->getBytesReceived();
// TODO: this can segfault. Unclear why.
try {
return m_walletImpl->getBytesReceived();
}
catch (...) {
return 0;
}
}
quint64 Wallet::getBytesSent() const {
return m_walletImpl->getBytesSent();
}
bool Wallet::isDeviceConnected() const {
return m_walletImpl->isDeviceConnected();
}
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{
if (m_walletListener != nullptr)
@ -1303,14 +1318,14 @@ Wallet::~Wallet()
delete m_coins;
m_coins = NULL;
//Monero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl);
if(status() == Status_Critical)
if(status() == Status_Critical || status() == Status_BadPassword)
qDebug("Not storing wallet cache");
else if( m_walletImpl->store(""))
qDebug("Wallet cache stored successfully");
else
qDebug("Error storing wallet cache");
delete m_walletImpl;
m_walletImpl = NULL;
m_walletImpl = nullptr;
delete m_walletListener;
m_walletListener = NULL;
qDebug("m_walletImpl deleted");
@ -1325,7 +1340,7 @@ void Wallet::startRefreshThread()
auto last = std::chrono::steady_clock::now();
while (!m_scheduler.stopping())
{
if (m_refreshEnabled)
if (m_refreshEnabled && (!isHwBacked() || isDeviceConnected()))
{
const auto now = std::chrono::steady_clock::now();
const auto elapsed = now - last;

View file

@ -78,9 +78,10 @@ Q_OBJECT
public:
enum Status {
Status_Ok = Monero::Wallet::Status_Ok,
Status_Error = Monero::Wallet::Status_Error,
Status_Critical = Monero::Wallet::Status_Critical
Status_Ok = Monero::Wallet::Status_Ok,
Status_Error = Monero::Wallet::Status_Error,
Status_Critical = Monero::Wallet::Status_Critical,
Status_BadPassword = Monero::Wallet::Status_BadPassword
};
Q_ENUM(Status)
@ -198,6 +199,9 @@ public:
bool isLedger() const;
bool isTrezor() const;
//! attempt to reconnect to hw-device
bool reconnectDevice();
//! returns if view only wallet
bool viewOnly() const;
@ -424,6 +428,8 @@ public:
quint64 getBytesReceived() const;
quint64 getBytesSent() const;
bool isDeviceConnected() const;
// TODO: setListenter() when it implemented in API
signals:
// emitted on every event happened with wallet
@ -432,7 +438,7 @@ signals:
// emitted when refresh process finished (could take a long time)
// signalling only after we
void refreshed(bool success);
void refreshed(bool success, const QString &message);
void moneySpent(const QString &txId, quint64 amount);
void moneyReceived(const QString &txId, quint64 amount);
@ -443,6 +449,7 @@ signals:
void walletCreationHeightChanged();
void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed();
void deviceError(const QString &message);
void walletPassphraseNeeded(bool onDevice);
void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid);
void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const;

View file

@ -45,8 +45,9 @@ void WalletListenerImpl::updated()
void WalletListenerImpl::refreshed(bool success)
{
qDebug() << __FUNCTION__;
QString message = m_wallet->errorString();
m_wallet->onRefreshed(success);
emit m_wallet->refreshed(success);
emit m_wallet->refreshed(success, message);
}
void WalletListenerImpl::onDeviceButtonRequest(uint64_t code)
@ -61,6 +62,12 @@ void WalletListenerImpl::onDeviceButtonPressed()
emit m_wallet->deviceButtonPressed();
}
void WalletListenerImpl::onDeviceError(const std::string &message)
{
qDebug() << __FUNCTION__;
emit m_wallet->deviceError(QString::fromStdString(message));
}
void WalletListenerImpl::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{
qDebug() << __FUNCTION__;

View file

@ -31,6 +31,8 @@ public:
virtual void onDeviceButtonPressed() override;
virtual void onDeviceError(const std::string &message) override;
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override;
virtual Monero::optional<std::string> onDevicePassphraseRequest(bool & on_device) override;

View file

@ -4,7 +4,6 @@
#include "libwalletqt/WalletManager.h"
#include "Wallet.h"
//#include "qt/updater.h"
#include "utils/ScopeGuard.h"
class WalletPassphraseListenerImpl : public Monero::WalletListener, public PassphraseReceiver
@ -31,11 +30,11 @@ public:
// return m_phelper.onDevicePassphraseRequest(on_device);
// }
//
// virtual void onDeviceButtonRequest(uint64_t code) override
// {
// qDebug() << __FUNCTION__;
// emit m_mgr->deviceButtonRequest(code);
// }
virtual void onDeviceButtonRequest(uint64_t code) override
{
qDebug() << __FUNCTION__;
emit m_mgr->deviceButtonRequest(code);
}
//
// virtual void onDeviceButtonPressed() override
// {
@ -43,6 +42,12 @@ public:
// emit m_mgr->deviceButtonPressed();
// }
virtual void onDeviceError(const std::string &message) override
{
qDebug() << __FUNCTION__;
emit m_mgr->deviceError(QString::fromStdString(message));
}
private:
WalletManager * m_mgr;
PassphraseHelper m_phelper;
@ -125,17 +130,17 @@ Wallet *WalletManager::recoveryWallet(const QString &path, const QString &passwo
return m_currentWallet;
}
Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &language, NetworkType::Type nettype,
const QString &address, const QString &viewkey, const QString &spendkey,
quint64 restoreHeight, quint64 kdfRounds)
Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &password, const QString &language,
NetworkType::Type nettype, const QString &address, const QString &viewkey,
const QString &spendkey, quint64 restoreHeight, quint64 kdfRounds)
{
QMutexLocker locker(&m_mutex);
if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet;
m_currentWallet = NULL;
m_currentWallet = nullptr;
}
Monero::Wallet * w = m_pimpl->createWalletFromKeys(path.toStdString(), "", language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight,
Monero::Wallet * w = m_pimpl->createWalletFromKeys(path.toStdString(), password.toStdString(), language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight,
address.toStdString(), viewkey.toStdString(), spendkey.toStdString(), kdfRounds);
m_currentWallet = new Wallet(w);
return m_currentWallet;
@ -148,9 +153,9 @@ Wallet *WalletManager::createDeterministicWalletFromSpendKey(const QString &path
if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet;
m_currentWallet = NULL;
m_currentWallet = nullptr;
}
Monero::Wallet * w = m_pimpl->createDeterministicWalletFromSpendKey(path.toStdString(), "", language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight,
Monero::Wallet * w = m_pimpl->createDeterministicWalletFromSpendKey(path.toStdString(), password.toStdString(), language.toStdString(), static_cast<Monero::NetworkType>(nettype), restoreHeight,
spendkey.toStdString(), kdfRounds, offset_passphrase.toStdString());
m_currentWallet = new Wallet(w);
return m_currentWallet;
@ -172,7 +177,7 @@ Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString
if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet;
m_currentWallet = NULL;
m_currentWallet = nullptr;
}
Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype),
deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString(), 1, &tmpListener);
@ -201,10 +206,12 @@ void WalletManager::createWalletFromDeviceAsync(const QString &path, const QStri
QString WalletManager::closeWallet()
{
QMutexLocker locker(&m_mutex);
qDebug() << Q_FUNC_INFO ;
QString result;
if (m_currentWallet) {
result = m_currentWallet->address(0, 0);
delete m_currentWallet;
m_currentWallet = nullptr;
} else {
qCritical() << "Trying to close non existing wallet " << m_currentWallet;
result = "0";
@ -428,15 +435,6 @@ QUrl WalletManager::localPathToUrl(const QString &path) const
return QUrl::fromLocalFile(path);
}
QString WalletManager::checkUpdates(const QString &software, const QString &subdir) const
{
qDebug() << "Checking for updates";
const std::tuple<bool, std::string, std::string, std::string, std::string> result = Monero::WalletManager::checkUpdates(software.toStdString(), subdir.toStdString());
if (!std::get<0>(result))
return QString("");
return QString::fromStdString(std::get<1>(result) + "|" + std::get<2>(result) + "|" + std::get<3>(result) + "|" + std::get<4>(result));
}
bool WalletManager::clearWalletCache(const QString &wallet_path) const
{
@ -459,6 +457,7 @@ WalletManager::WalletManager(QObject *parent)
: QObject(parent)
, m_passphraseReceiver(nullptr)
, m_scheduler(this)
, m_currentWallet(nullptr)
{
m_pimpl = Monero::WalletManagerFactory::getWalletManager();
}

View file

@ -62,6 +62,7 @@ public:
NetworkType::Type nettype = NetworkType::MAINNET, quint64 restoreHeight = 0, quint64 kdfRounds = 1);
Q_INVOKABLE Wallet * createWalletFromKeys(const QString &path,
const QString &password,
const QString &language,
NetworkType::Type nettype,
const QString &address,
@ -159,12 +160,6 @@ public:
Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error) const;
Q_INVOKABLE QVariantMap parse_uri_to_object(const QString &uri) const;
// Q_INVOKABLE bool saveQrCode(const QString &, const QString &) const;
// Q_INVOKABLE void checkUpdatesAsync(
// const QString &software,
// const QString &subdir,
// const QString &buildTag,
// const QString &version);
Q_INVOKABLE QString checkUpdates(const QString &software, const QString &subdir) const;
// clear/rename wallet cache
Q_INVOKABLE bool clearWalletCache(const QString &fileName) const;
@ -182,12 +177,7 @@ signals:
void walletPassphraseNeeded(bool onDevice);
void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed();
void checkUpdatesComplete(
const QString &version,
const QString &downloadUrl,
const QString &hash,
const QString &firstSigner,
const QString &secondSigner) const;
void deviceError(const QString &message);
void miningStatus(bool isMining) const;
void proxyAddressChanged() const;

File diff suppressed because it is too large Load diff

View file

@ -4,32 +4,16 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#ifdef Q_OS_MAC
#include "src/kdmactouchbar.h"
#endif
#include <QMainWindow>
#include <QSystemTrayIcon>
#include <QScreen>
#include <QtWidgets/QMenu>
#include <QMenu>
#include <utility>
#include <model/SubaddressModel.h>
#include <model/SubaddressProxyModel.h>
#include <model/TransactionHistoryModel.h>
#include <model/CoinsModel.h>
#include <model/CoinsProxyModel.h>
#include "appcontext.h"
#include "components.h"
#include "calcwindow.h"
#include "widgets/ccswidget.h"
#include "widgets/redditwidget.h"
#include "widgets/tickerwidget.h"
#include "widgets/xmrigwidget.h"
#include "utils/networking.h"
#include "appcontext.h"
#include "utils/config.h"
#include "wizard/WalletWizard.h"
#include "settings.h"
#include "dialog/aboutdialog.h"
#include "dialog/signverifydialog.h"
#include "dialog/verifyproofdialog.h"
@ -38,7 +22,31 @@
#include "dialog/keysdialog.h"
#include "dialog/aboutdialog.h"
#include "dialog/restoredialog.h"
#include "dialog/splashdialog.h"
#include "libwalletqt/Wallet.h"
#include "model/SubaddressModel.h"
#include "model/SubaddressProxyModel.h"
#include "model/TransactionHistoryModel.h"
#include "model/CoinsModel.h"
#include "model/CoinsProxyModel.h"
#include "utils/networking.h"
#include "utils/config.h"
#include "widgets/ccswidget.h"
#include "widgets/redditwidget.h"
#include "widgets/tickerwidget.h"
#include "wizard/WalletWizard.h"
#ifdef HAS_LOCALMONERO
#include "widgets/LocalMoneroWidget.h"
#endif
#ifdef HAS_XMRIG
#include "widgets/xmrigwidget.h"
#endif
#ifdef Q_OS_MAC
#include "src/kdmactouchbar.h"
#endif
namespace Ui {
class MainWindow;
@ -64,13 +72,6 @@ public:
static AppContext *getContext();
~MainWindow() override;
qreal screenDpiRef;
QRect screenGeo;
QRect screenRect;
qreal screenDpi;
qreal screenDpiPhysical;
qreal screenRatio;
enum Tabs {
HOME = 0,
HISTORY,
@ -88,8 +89,6 @@ public:
};
public slots:
void initWidgets();
void initMenu();
void showWizard(WalletWizard::Page startPage);
void menuNewRestoreClicked();
void menuQuitClicked();
@ -116,14 +115,17 @@ public slots:
void onRefreshSync(int height, int target);
void onWalletOpenedError(const QString &err);
void onWalletCreatedError(const QString &err);
void onWalletCreated(Wallet *wallet);
void menuWalletCloseClicked();
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
void onDeviceButtonRequest(quint64 code);
void onViewOnBlockExplorer(const QString &txid);
void onResendTransaction(const QString &txid);
void importContacts();
void showRestoreHeightDialog();
void importTransaction();
void onDeviceError(const QString &error);
void menuHwDeviceClicked();
void onUpdatesAvailable(const QJsonObject &updates);
// offline tx signing
void exportKeyImages();
@ -148,15 +150,42 @@ public slots:
signals:
void closed();
private slots:
void onInitialNetworkConfigured();
void onCheckUpdatesComplete(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer);
void onShowUpdateCheck(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer);
void onRestartApplication(const QString &binaryFilename);
void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version);
void onShowDonationNag();
void onInitiateTransaction();
void onEndTransaction();
void onCustomRestoreHeightSet(int height);
void onWalletAboutToClose();
// Menu
void onExportHistoryCSV(bool checked);
void onExportContactsCSV(bool checked);
void onCreateDesktopEntry(bool checked);
void onReportBug(bool checked);
private:
void initSkins();
void initStatusBar();
void initWidgets();
void initMenu();
void initTray();
void initHome();
void initTouchBar();
void initWalletContext();
void initWizard();
void startupWarning();
bool autoOpenWallet();
AppContext *m_ctx;
static MainWindow * pMainWindow;
void closeEvent(QCloseEvent *event) override;
void cleanupBeforeClose();
void create_status_bar();
void initMain();
void loadSkins();
QString loadStylesheet(const QString &resource);
void saveGeo();
void restoreGeo();
@ -173,7 +202,10 @@ private:
void showBalanceDialog();
QString statusDots();
void bringToFront();
void centerWidget(QWidget &w);
QString getPlatformTag();
void displayWalletErrorMsg(const QString &err);
QString getHardwareDevice();
void setTitle(bool mining);
WalletWizard *createWizard(WalletWizard::Page startPage);
@ -181,8 +213,12 @@ private:
Settings *m_windowSettings = nullptr;
CalcWindow *m_windowCalc = nullptr;
RestoreDialog *m_restoreDialog = nullptr;
AboutDialog *m_aboutDialog = nullptr;
XMRigWidget *m_xmrig = nullptr;
SplashDialog *m_splashDialog = nullptr;
#ifdef HAS_LOCALMONERO
LocalMoneroWidget *m_localMoneroWidget = nullptr;
#endif
QSystemTrayIcon *m_trayIcon;
QMenu m_trayMenu;
@ -195,6 +231,7 @@ private:
TickerWidget *m_balanceWidget;
// lower status bar
QPushButton *m_statusUpdateAvailable;
ClickableLabel *m_statusLabelBalance;
QLabel *m_statusLabelStatus;
QLabel *m_statusLabelNetStats;
@ -203,6 +240,7 @@ private:
StatusBarButton *m_statusBtnPreferences;
StatusBarButton *m_statusBtnSeed;
StatusBarButton *m_statusBtnTor;
StatusBarButton *m_statusBtnHwDevice;
#ifdef Q_OS_MAC
QAction *m_touchbarActionWelcome;
@ -210,6 +248,7 @@ private:
QList<QAction *> m_touchbarWalletItems;
QList<QAction *> m_touchbarWizardItems;
#endif
QSignalMapper *m_tabShowHideSignalMapper;
QMap<QString, ToggleTab*> m_tabShowHideMapper;
WalletWizard *m_wizard = nullptr;
@ -222,13 +261,9 @@ private:
int m_statusDots;
bool m_constructingTransaction = false;
bool m_statusOverrideActive = false;
bool m_showDeviceError = false;
QTimer m_txTimer;
QIcon m_statusDisconnected;
QIcon m_statusConnecting;
QIcon m_statusSynchronizing;
QIcon m_statusSynchronized;
private slots:
void menuToggleTabVisible(const QString &key);
};

View file

@ -293,6 +293,39 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidgetExchanges">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabLocalMonero">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/localMonero_logo.png</normaloff>:/assets/images/localMonero_logo.png</iconset>
</attribute>
<attribute name="title">
<string>LocalMonero</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="localMoneroLayout"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabXmrRig">
@ -320,7 +353,7 @@
<x>0</x>
<y>0</y>
<width>1156</width>
<height>30</height>
<height>27</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">

View file

@ -5,6 +5,7 @@
#include "AddressBook.h"
#include "ModelUtils.h"
#include "utils/utils.h"
#include "utils/Icons.h"
AddressBookModel::AddressBookModel(QObject *parent, AddressBook *addressBook)
: QAbstractTableModel(parent),
@ -13,7 +14,7 @@ AddressBookModel::AddressBookModel(QObject *parent, AddressBook *addressBook)
{
connect(m_addressBook, &AddressBook::refreshStarted, this, &AddressBookModel::startReset);
connect(m_addressBook, &AddressBook::refreshFinished, this, &AddressBookModel::endReset);
m_contactIcon = QIcon(":/assets/images/person.svg");
m_contactIcon = icons()->icon("person.svg");
}
void AddressBookModel::startReset(){

View file

@ -7,9 +7,9 @@
#include "ModelUtils.h"
#include "globals.h"
#include "utils/ColorScheme.h"
#include "utils/Icons.h"
#include <QBrush>
#include <QFont>
CoinsModel::CoinsModel(QObject *parent, Coins *coins)
: QAbstractTableModel(parent),
@ -17,9 +17,6 @@ CoinsModel::CoinsModel(QObject *parent, Coins *coins)
{
connect(m_coins, &Coins::refreshStarted, this, &CoinsModel::startReset);
connect(m_coins, &Coins::refreshFinished, this, &CoinsModel::endReset);
m_eye = QIcon(":/assets/images/eye1.png");
m_eyeBlind = QIcon(":/assets/images/eye_blind.png");
}
void CoinsModel::startReset(){
@ -88,10 +85,10 @@ QVariant CoinsModel::data(const QModelIndex &index, int role) const
case KeyImageKnown:
{
if (cInfo.keyImageKnown()) {
result = QVariant(m_eye);
result = QVariant(icons()->icon("eye1.png"));
}
else {
result = QVariant(m_eyeBlind);
result = QVariant(icons()->icon("eye_blind.png"));
}
}
}

View file

@ -53,8 +53,6 @@ private:
QVariant parseTransactionInfo(const CoinsInfo &cInfo, int column, int role) const;
Coins *m_coins;
QIcon m_eye;
QIcon m_eyeBlind;
};
#endif //FEATHER_COINSMODEL_H

View file

@ -5,6 +5,7 @@
#include "TransactionHistoryProxyModel.h"
#include "libwalletqt/TransactionInfo.h"
#include "utils/utils.h"
#include <QHeaderView>
#include <QMenu>

View file

@ -9,6 +9,7 @@
#include <QActionGroup>
#include "TransactionHistoryModel.h"
#include "TransactionHistoryProxyModel.h"
class HistoryView : public QTreeView
{

View file

@ -0,0 +1,172 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "LocalMoneroModel.h"
#include <QColor>
#include "utils/utils.h"
LocalMoneroModel::LocalMoneroModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int LocalMoneroModel::rowCount(const QModelIndex &parent) const {
return m_data.count();
}
int LocalMoneroModel::columnCount(const QModelIndex &parent) const {
return Column::COUNT;
}
QVariant LocalMoneroModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case Seller:
return QString("Seller");
case Country:
return QString("Country");
case PaymentMethod:
return QString("Payment Method");
case PaymentMethodDetail:
return QString("Detail");
case PriceXMR:
return QString("Price/XMR");
case Limits:
return QString("Limits");
default:
return QVariant();
}
}
return QVariant();
}
QVariant LocalMoneroModel::data(const QModelIndex &index, int role) const {
const int col = index.column();
const auto row = m_data.at(index.row()).toObject()["data"].toObject();
if (row.isEmpty()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
switch (col) {
case Column::Seller: {
auto seller = row["profile"].toObject();
return seller["name"].toString();
// TODO: online indicator
}
case Column::Country: {
return row["countrycode"].toString();
}
case Column::PaymentMethod: {
auto paymentMethodCode = row["online_provider"].toString();
if (paymentMethodCode == "NATIONAL_BANK") {
return QString("National bank transfer");
}
return m_paymentMethodNames.value(paymentMethodCode, paymentMethodCode);
}
case Column::PaymentMethodDetail: {
auto paymentMethodDetailText = row["payment_method_detail"].toString();
QString filteredString; // We can't display emojis in QTreeView
for (const auto &Char : paymentMethodDetailText) {
if (Char.unicode() < 256)
filteredString.append(Char);
else
filteredString.append(" ");
}
return filteredString.trimmed();
}
case Column::PriceXMR: {
return QString("%1 %2").arg(row["temp_price"].toString(), row["currency"].toString());
}
case Column::Limits: {
auto minAmount = row["min_amount"].toString();
auto maxAmount = row["max_amount"].toString();
if (maxAmount.isEmpty()) {
maxAmount = row["max_amount_available"].toString();
}
auto currency = row["currency"].toString();
if (minAmount.isEmpty() && maxAmount.isEmpty()) {
return QString("Up to any amount %1").arg(currency);
}
if (!minAmount.isEmpty() && maxAmount.isEmpty()) {
return QString("%1 - any amount %2").arg(minAmount, currency);
}
if (!minAmount.isEmpty() && !maxAmount.isEmpty()) {
return QString("%1 - %2 %3").arg(minAmount, maxAmount, currency);
}
if (minAmount.isEmpty() && !maxAmount.isEmpty()) {
return QString("Up to %1 %2").arg(maxAmount, currency);
}
return QVariant();
}
}
}
else if (role == Qt::ForegroundRole) {
switch (col) {
case Column::PriceXMR: {
return QVariant(QColor("#388538"));
}
}
}
else if (role == Qt::FontRole) {
switch (col) {
case Column::PriceXMR: {
auto bigFont = Utils::relativeFont(2);
bigFont.setBold(true);
return bigFont;
}
}
}
return QVariant();
}
void LocalMoneroModel::setData(const QJsonArray &data) {
beginResetModel();
m_data = data;
endResetModel();
}
void LocalMoneroModel::addData(const QJsonArray &data) {
beginResetModel();
for (const auto &row : data) {
m_data.append(row);
}
endResetModel();
}
void LocalMoneroModel::clearData() {
beginResetModel();
m_data = {};
endResetModel();
}
void LocalMoneroModel::setPaymentMethods(const QJsonObject &data) {
beginResetModel();
m_paymentMethods = data;
m_paymentMethodNames.clear();
for (const auto &payment_method : data) {
auto code = payment_method["code"].toString();
auto name = payment_method["name"].toString();
if (!code.isEmpty() && !name.isEmpty()) {
m_paymentMethodNames[code] = name;
}
}
endResetModel();
}
QJsonObject LocalMoneroModel::getOffer(int index) const {
return m_data.at(index).toObject();
}

View file

@ -0,0 +1,47 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_LOCALMONEROMODEL_H
#define FEATHER_LOCALMONEROMODEL_H
#include <QAbstractTableModel>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
class LocalMoneroModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Column
{
Seller = 0,
Country,
PaymentMethod,
PaymentMethodDetail,
PriceXMR,
Limits,
COUNT
};
LocalMoneroModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void setData(const QJsonArray &data);
void setPaymentMethods(const QJsonObject &data);
void addData(const QJsonArray &data);
void clearData();
QJsonObject getOffer(int index) const;
private:
QJsonArray m_data;
QJsonObject m_paymentMethods;
QHash<QString, QString> m_paymentMethodNames;
};
#endif //FEATHER_LOCALMONEROMODEL_H

View file

@ -4,12 +4,13 @@
#include "NodeModel.h"
#include "utils/nodes.h"
#include "utils/ColorScheme.h"
#include "utils/Icons.h"
NodeModel::NodeModel(int nodeSource, QObject *parent)
: QAbstractTableModel(parent)
, m_nodeSource(nodeSource)
, m_offline(QIcon(":/assets/images/expired_icon.png"))
, m_online(QIcon(":/assets/images/confirmed_icon.png"))
, m_offline(icons()->icon("expired_icon.png"))
, m_online(icons()->icon("confirmed_icon.png"))
{
}
@ -47,7 +48,7 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
if(role == Qt::DisplayRole) {
switch(index.column()) {
case NodeModel::URL:
return node.full;
return node.toFullAddress();
case NodeModel::Height:
if(node.online)
return node.height == 0 ? QString("-") : QString::number(node.height);

View file

@ -9,7 +9,6 @@
#include <QPoint>
#include <QColor>
#include <QBrush>
#include <QFont>
SubaddressModel::SubaddressModel(QObject *parent, Subaddress *subaddress)
: QAbstractTableModel(parent),

View file

@ -5,21 +5,16 @@
#include "TransactionHistory.h"
#include "TransactionInfo.h"
#include "globals.h"
#include "utils/config.h"
#include "utils/ColorScheme.h"
#include "utils/Icons.h"
#include "utils/AppData.h"
#include "ModelUtils.h"
TransactionHistoryModel::TransactionHistoryModel(QObject *parent)
: QAbstractTableModel(parent),
m_transactionHistory(nullptr)
{
m_unconfirmedTx = QIcon(":/assets/images/unconfirmed.png");
m_warning = QIcon(":/assets/images/warning.png");
m_clock1 = QIcon(":/assets/images/clock1.png");
m_clock2 = QIcon(":/assets/images/clock2.png");
m_clock3 = QIcon(":/assets/images/clock3.png");
m_clock4 = QIcon(":/assets/images/clock4.png");
m_clock5 = QIcon(":/assets/images/clock5.png");
m_confirmedTx = QIcon(":/assets/images/confirmed.png");
}
void TransactionHistoryModel::setTransactionHistory(TransactionHistory *th) {
@ -86,21 +81,21 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
case Column::Date:
{
if (tInfo.isFailed())
result = QVariant(m_warning);
result = QVariant(icons()->icon("warning.png"));
else if (tInfo.isPending())
result = QVariant(m_unconfirmedTx);
result = QVariant(icons()->icon("unconfirmed.png"));
else if (tInfo.confirmations() <= (1.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock1);
result = QVariant(icons()->icon("clock1.png"));
else if (tInfo.confirmations() <= (2.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock2);
result = QVariant(icons()->icon("clock2.png"));
else if (tInfo.confirmations() <= (3.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock3);
result = QVariant(icons()->icon("clock3.png"));
else if (tInfo.confirmations() <= (4.0/5.0 * tInfo.confirmationsRequired()))
result = QVariant(m_clock4);
result = QVariant(icons()->icon("clock4.png"));
else if (tInfo.confirmations() < tInfo.confirmationsRequired())
result = QVariant(m_clock5);
result = QVariant(icons()->icon("clock5.png"));
else if (tInfo.confirmations())
result = QVariant(m_confirmedTx);
result = QVariant(icons()->icon("confirmed.png"));
}
}
}
@ -161,13 +156,13 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
}
case Column::FiatAmount:
{
double usd_price = AppContext::txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd"));
double usd_price = appData()->txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd"));
if (usd_price == 0.0)
return QVariant("?");
double usd_amount = usd_price * (tInfo.balanceDelta() / globals::cdiv);
if(this->preferredFiatSymbol != "USD")
usd_amount = AppContext::prices->convert("USD", this->preferredFiatSymbol, usd_amount);
usd_amount = appData()->prices.convert("USD", this->preferredFiatSymbol, usd_amount);
if (role == Qt::UserRole) {
return usd_amount;
}

View file

@ -6,7 +6,6 @@
#include <QAbstractListModel>
#include <QIcon>
#include "appcontext.h"
class TransactionHistory;
class TransactionInfo;
@ -54,14 +53,6 @@ private:
QVariant parseTransactionInfo(const TransactionInfo &tInfo, int column, int role) const;
TransactionHistory * m_transactionHistory;
QIcon m_unconfirmedTx;
QIcon m_warning;
QIcon m_clock1;
QIcon m_clock2;
QIcon m_clock3;
QIcon m_clock4;
QIcon m_clock5;
QIcon m_confirmedTx;
};
#endif // TRANSACTIONHISTORYMODEL_H

View file

@ -5,8 +5,10 @@
#include "receivewidget.h"
#include "model/ModelUtils.h"
#include "dialog/qrcodedialog.h"
#include "utils/Icons.h"
#include <QMenu>
#include <QMessageBox>
ReceiveWidget::ReceiveWidget(QWidget *parent) :
QWidget(parent),
@ -29,9 +31,7 @@ ReceiveWidget::ReceiveWidget(QWidget *parent) :
connect(m_showTransactionsAction, &QAction::triggered, this, &ReceiveWidget::onShowTransactions);
connect(ui->addresses, &QTreeView::customContextMenuRequested, this, &ReceiveWidget::showContextMenu);
connect(ui->btn_generateSubaddress, &QPushButton::clicked, [=]() {
emit generateSubaddress();
});
connect(ui->btn_generateSubaddress, &QPushButton::clicked, this, &ReceiveWidget::generateSubaddress);
connect(ui->qrCode, &ClickableLabel::clicked, this, &ReceiveWidget::showQrCodeDialog);
connect(ui->label_addressSearch, &QLineEdit::textChanged, this, &ReceiveWidget::setSearchFilter);
@ -88,9 +88,9 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
auto *menu = new QMenu(ui->addresses);
menu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ReceiveWidget::copyAddress);
menu->addAction(QIcon(":/assets/images/copy.png"), "Copy label", this, &ReceiveWidget::copyLabel);
menu->addAction(QIcon(":/assets/images/edit.png"), "Edit label", this, &ReceiveWidget::editLabel);
menu->addAction(icons()->icon("copy.png"), "Copy address", this, &ReceiveWidget::copyAddress);
menu->addAction(icons()->icon("copy.png"), "Copy label", this, &ReceiveWidget::copyLabel);
menu->addAction(icons()->icon("edit.png"), "Edit label", this, &ReceiveWidget::editLabel);
if (isUsed) {
menu->addAction(m_showTransactionsAction);
@ -103,6 +103,10 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
menu->addAction("Hide address", this, &ReceiveWidget::hideAddress);
}
if (m_wallet->isHwBacked()) {
menu->addAction("Show on device", this, &ReceiveWidget::showOnDevice);
}
menu->popup(ui->addresses->viewport()->mapToGlobal(point));
}
@ -164,6 +168,21 @@ void ReceiveWidget::showAddress()
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
}
void ReceiveWidget::showOnDevice() {
Monero::SubaddressRow* row = this->currentEntry();
if (!row) return;
m_wallet->deviceShowAddressAsync(m_wallet->currentSubaddressAccount(), row->getRowId(), "");
}
void ReceiveWidget::generateSubaddress() {
if (!m_wallet) return;
bool r = m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), "");
if (!r) {
QMessageBox::warning(this, "Warning", QString("Failed to generate subaddress:\n\n%1").arg(m_wallet->subaddress()->errorString()));
}
}
void ReceiveWidget::updateQrCode(){
QModelIndex index = ui->addresses->currentIndex();
if (!index.isValid()) {

View file

@ -40,13 +40,14 @@ public slots:
void resetModel();
signals:
void generateSubaddress();
void showTransactions(const QString& address);
private slots:
void showHeaderMenu(const QPoint& position);
void hideAddress();
void showAddress();
void showOnDevice();
void generateSubaddress();
private:
Ui::ReceiveWidget *ui;

View file

@ -6,10 +6,11 @@
#include "mainwindow.h"
#include "ui_sendwidget.h"
#include "globals.h"
#include "utils/AppData.h"
SendWidget::SendWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::SendWidget)
SendWidget::SendWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::SendWidget)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
@ -20,6 +21,12 @@ SendWidget::SendWidget(QWidget *parent) :
QValidator *validator = new QRegExpValidator(rx, this);
ui->lineAmount->setValidator(validator);
connect(m_ctx, &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction);
connect(m_ctx, &AppContext::endTransaction, this, &SendWidget::onEndTransaction);
connect(m_ctx, &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved);
connect(m_ctx, &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError);
connect(m_ctx, &AppContext::walletClosed, this, &SendWidget::onWalletClosed);
connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked);
connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked);
connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked);
@ -140,7 +147,7 @@ void SendWidget::sendClicked() {
amounts.push_back(output.amount);
}
emit createTransactionMultiDest(addresses, amounts, description);
m_ctx->onCreateTransactionMultiDest(addresses, amounts, description);
return;
}
@ -152,20 +159,20 @@ void SendWidget::sendClicked() {
QMessageBox::warning(this, "Amount error", "Invalid amount specified.");
return;
}
emit createTransaction(recipient, amount, description, sendAll);
m_ctx->onCreateTransaction(recipient, amount, description, sendAll);
} else {
amount = WalletManager::amountFromDouble(this->conversionAmount());
if (amount == 0) {
QMessageBox::warning(this, "Fiat conversion error", "Could not create transaction.");
return;
}
emit createTransaction(recipient, amount, description, false);
m_ctx->onCreateTransaction(recipient, amount, description, false);
}
}
void SendWidget::aliasClicked() {
auto address = ui->lineAddress->text();
emit resolveOpenAlias(address);
m_ctx->onOpenAliasResolve(address);
}
void SendWidget::clearClicked() {
@ -195,7 +202,7 @@ void SendWidget::updateConversionLabel() {
} else {
auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString();
double conversionAmount = AppContext::prices->convert("XMR", preferredFiatCurrency, this->amountDouble());
double conversionAmount = appData()->prices.convert("XMR", preferredFiatCurrency, this->amountDouble());
return QString("~%1 %2").arg(QString::number(conversionAmount, 'f', 2), preferredFiatCurrency);
}
}();
@ -206,7 +213,7 @@ void SendWidget::updateConversionLabel() {
double SendWidget::conversionAmount() {
QString currency = ui->comboCurrencySelection->currentText();
return AppContext::prices->convert(currency, "XMR", this->amountDouble());
return appData()->prices.convert(currency, "XMR", this->amountDouble());
}
quint64 SendWidget::amount() {

View file

@ -43,11 +43,6 @@ public slots:
void onInitiateTransaction();
void onEndTransaction();
signals:
void resolveOpenAlias(const QString &address);
void createTransaction(const QString &address, quint64 amount, const QString &description, bool all);
void createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
private:
void setupComboBox();
double amountDouble();

View file

@ -17,7 +17,7 @@ Settings::Settings(QWidget *parent) :
this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png"));
ui->tabWidget->setTabVisible(2, false);
ui->tabWidget->setTabVisible(4, false);
ui->tabWidget->setTabVisible(5, false);
connect(ui->btnCopyToClipboard, &QPushButton::clicked, this, &Settings::copyToClipboard);
connect(ui->checkBox_multiBroadcast, &QCheckBox::toggled, [](bool toggled){
@ -70,8 +70,6 @@ Settings::Settings(QWidget *parent) :
ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting));
connect(ui->comboBox_skin, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_skinChanged);
connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_blockExplorerChanged);
connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_redditFrontendChanged);
connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_amountPrecisionChanged);
connect(ui->comboBox_dateFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_dateFormatChanged);
connect(ui->comboBox_timeFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_timeFormatChanged);
@ -98,6 +96,16 @@ Settings::Settings(QWidget *parent) :
ui->lineEdit_defaultWalletDir->setText(m_ctx->defaultWalletDir);
});
// Links tab
connect(ui->combo_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_blockExplorerChanged);
connect(ui->combo_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_redditFrontendChanged);
connect(ui->combo_localMoneroFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_localMoneroFrontendChanged);
ui->combo_blockExplorer->setCurrentIndex(ui->combo_blockExplorer->findText(config()->get(Config::blockExplorer).toString()));
ui->combo_redditFrontend->setCurrentIndex(ui->combo_redditFrontend->findText(config()->get(Config::redditFrontend).toString()));
this->setupLocalMoneroFrontendCombobox();
this->adjustSize();
}
@ -118,16 +126,21 @@ void Settings::comboBox_skinChanged(int pos) {
}
void Settings::comboBox_blockExplorerChanged(int pos) {
QString blockExplorer = ui->comboBox_blockExplorer->currentText();
QString blockExplorer = ui->combo_blockExplorer->currentText();
config()->set(Config::blockExplorer, blockExplorer);
emit blockExplorerChanged(blockExplorer);
}
void Settings::comboBox_redditFrontendChanged(int pos) {
QString redditFrontend = ui->comboBox_redditFrontend->currentText();
QString redditFrontend = ui->combo_redditFrontend->currentText();
config()->set(Config::redditFrontend, redditFrontend);
}
void Settings::comboBox_localMoneroFrontendChanged(int pos) {
QString localMoneroFrontend = ui->combo_localMoneroFrontend->currentData().toString();
config()->set(Config::localMoneroFrontend, localMoneroFrontend);
}
void Settings::comboBox_amountPrecisionChanged(int pos) {
config()->set(Config::amountPrecision, pos);
emit amountPrecisionChanged(pos);
@ -161,6 +174,15 @@ void Settings::setupSkinCombobox() {
ui->comboBox_skin->insertItems(0, m_skins);
}
void Settings::setupLocalMoneroFrontendCombobox() {
ui->combo_localMoneroFrontend->addItem("localmonero.co", "https://localmonero.co");
ui->combo_localMoneroFrontend->addItem("localmonero.co/nojs", "https://localmonero.co/nojs");
ui->combo_localMoneroFrontend->addItem("nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion",
"http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion");
ui->combo_localMoneroFrontend->setCurrentIndex(ui->combo_localMoneroFrontend->findData(config()->get(Config::localMoneroFrontend).toString()));
}
Settings::~Settings() {
delete ui;
}

View file

@ -37,14 +37,17 @@ public slots:
void checkboxExternalLinkWarn();
void fiatCurrencySelected(int index);
void comboBox_skinChanged(int pos);
void comboBox_blockExplorerChanged(int pos);
void comboBox_redditFrontendChanged(int pos);
void comboBox_amountPrecisionChanged(int pos);
void comboBox_dateFormatChanged(int pos);
void comboBox_timeFormatChanged(int pos);
void comboBox_blockExplorerChanged(int pos);
void comboBox_redditFrontendChanged(int pos);
void comboBox_localMoneroFrontendChanged(int pos);
private:
void setupSkinCombobox();
void setupLocalMoneroFrontendCombobox();
AppContext *m_ctx;
Ui::Settings *ui;

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1123</width>
<height>555</height>
<width>915</width>
<height>519</height>
</rect>
</property>
<property name="windowTitle">
@ -133,90 +133,33 @@
<widget class="QComboBox" name="comboBox_skin"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Block explorer:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_blockExplorer">
<item>
<property name="text">
<string>exploremonero.com</string>
</property>
</item>
<item>
<property name="text">
<string>xmrchain.net</string>
</property>
</item>
<item>
<property name="text">
<string>moneroblocks.info</string>
</property>
</item>
<item>
<property name="text">
<string>blockchair.com</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Reddit frontend:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_redditFrontend">
<item>
<property name="text">
<string>old.reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>teddit.net</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Amount precision:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_amountPrecision"/>
</item>
<item row="5" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Date format:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_dateFormat"/>
</item>
<item row="6" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Time format:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="4" column="1">
<widget class="QComboBox" name="comboBox_timeFormat"/>
</item>
</layout>
@ -420,6 +363,80 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_links">
<attribute name="title">
<string>Links</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Block explorer:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="combo_blockExplorer">
<item>
<property name="text">
<string>exploremonero.com</string>
</property>
</item>
<item>
<property name="text">
<string>xmrchain.net</string>
</property>
</item>
<item>
<property name="text">
<string>moneroblocks.info</string>
</property>
</item>
<item>
<property name="text">
<string>blockchair.com</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Reddit frontend:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="combo_redditFrontend">
<item>
<property name="text">
<string>old.reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>reddit.com</string>
</property>
</item>
<item>
<property name="text">
<string>teddit.net</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>LocalMonero frontend:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="combo_localMoneroFrontend"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_console">
<attribute name="icon">
<iconset theme=":/assets/images/terminal.png">

52
src/utils/AppData.cpp Normal file
View file

@ -0,0 +1,52 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "AppData.h"
#include "config.h"
#include "WebsocketNotifier.h"
AppData::AppData(QObject *parent)
: QObject(parent)
{
this->initRestoreHeights();
auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey();
this->txFiatHistory = new TxFiatHistory(genesis_timestamp, Config::defaultConfigDir().path());
connect(&websocketNotifier()->websocketClient, &WebsocketClient::connectionEstablished, this->txFiatHistory, &TxFiatHistory::onUpdateDatabase);
connect(this->txFiatHistory, &TxFiatHistory::requestYear, [](int year){
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1}})").arg(year).toUtf8();
websocketNotifier()->websocketClient.sendMsg(data);
});
connect(this->txFiatHistory, &TxFiatHistory::requestYearMonth, [](int year, int month){
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8();
websocketNotifier()->websocketClient.sendMsg(data);
});
connect(websocketNotifier(), &WebsocketNotifier::CryptoRatesReceived, &this->prices, &Prices::cryptoPricesReceived);
connect(websocketNotifier(), &WebsocketNotifier::FiatRatesReceived, &this->prices, &Prices::fiatPricesReceived);
connect(websocketNotifier(), &WebsocketNotifier::TxFiatHistoryReceived, this->txFiatHistory, &TxFiatHistory::onWSData);
connect(websocketNotifier(), &WebsocketNotifier::BlockHeightsReceived, this, &AppData::onBlockHeightsReceived);
}
QPointer<AppData> AppData::m_instance(nullptr);
void AppData::onBlockHeightsReceived(int mainnet, int stagenet) {
this->heights[NetworkType::MAINNET] = mainnet;
this->heights[NetworkType::STAGENET] = stagenet;
}
void AppData::initRestoreHeights() {
restoreHeights[NetworkType::TESTNET] = new RestoreHeightLookup(NetworkType::TESTNET);
restoreHeights[NetworkType::STAGENET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_stagenet.txt", NetworkType::STAGENET);
restoreHeights[NetworkType::MAINNET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_monero_mainnet.txt", NetworkType::MAINNET);
}
AppData* AppData::instance()
{
if (!m_instance) {
m_instance = new AppData(QCoreApplication::instance());
}
return m_instance;
}

41
src/utils/AppData.h Normal file
View file

@ -0,0 +1,41 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_APPDATA_H
#define FEATHER_APPDATA_H
#include <QObject>
#include <QPointer>
#include <QCoreApplication>
#include "prices.h"
#include "txfiathistory.h"
#include "RestoreHeightLookup.h"
class AppData : public QObject {
Q_OBJECT
public:
explicit AppData(QObject *parent);
static AppData* instance();
Prices prices;
TxFiatHistory *txFiatHistory;
QMap<NetworkType::Type, int> heights;
QMap<NetworkType::Type, RestoreHeightLookup*> restoreHeights;
private slots:
void onBlockHeightsReceived(int mainnet, int stagenet);
private:
void initRestoreHeights();
static QPointer<AppData> m_instance;
};
inline AppData* appData()
{
return AppData::instance();
}
#endif //FEATHER_APPDATA_H

85
src/utils/AsyncTask.h Normal file
View file

@ -0,0 +1,85 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_ASYNCTASK_HPP
#define KEEPASSXC_ASYNCTASK_HPP
#include <QFuture>
#include <QFutureWatcher>
#include <QtConcurrent/QtConcurrent>
/**
* Asynchronously run computations outside the GUI thread.
*/
namespace AsyncTask
{
/**
* Wait for the given future without blocking the event loop.
*
* @param future future to wait for
* @return async task result
*/
template <typename FunctionObject>
typename std::result_of<FunctionObject()>::type
waitForFuture(QFuture<typename std::result_of<FunctionObject()>::type> future)
{
QEventLoop loop;
QFutureWatcher<typename std::result_of<FunctionObject()>::type> watcher;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
watcher.setFuture(future);
loop.exec();
return future.result();
}
/**
* Run a given task and wait for it to finish without blocking the event loop.
*
* @param task std::function object to run
* @return async task result
*/
template <typename FunctionObject>
typename std::result_of<FunctionObject()>::type runAndWaitForFuture(FunctionObject task)
{
return waitForFuture<FunctionObject>(QtConcurrent::run(task));
}
/**
* Run a given task then call the defined callback. Prevents event loop blocking and
* ensures the validity of the follow-on task through the context. If the context is
* deleted, the callback will not be processed preventing use after free errors.
*
* @param task std::function object to run
* @param context QObject responsible for calling this function
* @param callback std::function object to run after the task completess
*/
template <typename FunctionObject, typename FunctionObject2>
void runThenCallback(FunctionObject task, QObject* context, FunctionObject2 callback)
{
typedef QFutureWatcher<typename std::result_of<FunctionObject()>::type> FutureWatcher;
auto future = QtConcurrent::run(task);
auto watcher = new FutureWatcher(context);
QObject::connect(watcher, &QFutureWatcherBase::finished, context, [=]() {
watcher->deleteLater();
callback(future.result());
});
watcher->setFuture(future);
}
}; // namespace AsyncTask
#endif // KEEPASSXC_ASYNCTASK_HPP

View file

@ -3,6 +3,7 @@
// Copyright (c) 2012 thomasv@gitorious
#include "ColorScheme.h"
#include <QDebug>
bool ColorScheme::darkScheme = false;
ColorSchemeItem ColorScheme::GREEN = ColorSchemeItem("#117c11", "#8af296");

View file

@ -3,6 +3,7 @@
#include "libwalletqt/WalletManager.h"
#include "libwalletqt/Wallet.h"
#include "utils/AppData.h"
#include <sstream>
#include "RestoreHeightLookup.h"
@ -13,6 +14,8 @@ enum SeedType {
};
struct FeatherSeed {
// TODO: this is spaghetti, needs refactor
QString coin;
QString language;
SeedType seedType;
@ -21,17 +24,18 @@ struct FeatherSeed {
QString spendKey;
QString correction;
NetworkType::Type netType;
time_t time;
int restoreHeight = 0;
RestoreHeightLookup *lookup = nullptr;
QString errorString;
explicit FeatherSeed(RestoreHeightLookup *lookup,
explicit FeatherSeed(NetworkType::Type networkType = NetworkType::MAINNET,
const QString &coin = "monero",
const QString &language = "English",
const QStringList &mnemonic = {})
: lookup(lookup), coin(coin), language(language), mnemonic(mnemonic)
: netType(networkType), coin(coin), language(language), mnemonic(mnemonic)
{
// Generate a new mnemonic if none was given
if (mnemonic.length() == 0) {
@ -85,21 +89,17 @@ struct FeatherSeed {
}
}
int setRestoreHeight() {
if (this->lookup == nullptr)
return 1;
void setRestoreHeight() {
if (this->time == 0)
return 1;
this->restoreHeight = 1;
this->restoreHeight = this->lookup->dateToRestoreHeight(this->time);
return this->restoreHeight;
this->restoreHeight = appData()->restoreHeights[netType]->dateToRestoreHeight(this->time);
}
int setRestoreHeight(int height) {
auto now = std::time(nullptr);
auto nowClearance = 3600 * 24;
auto currentBlockHeight = this->lookup->dateToRestoreHeight(now - nowClearance);
auto currentBlockHeight = appData()->restoreHeights[netType]->dateToRestoreHeight(now - nowClearance);
if (height >= currentBlockHeight + nowClearance) {
qCritical() << "unrealistic restore height detected, setting to current blockheight instead: " << currentBlockHeight;
this->restoreHeight = currentBlockHeight;

32
src/utils/Icons.cpp Normal file
View file

@ -0,0 +1,32 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "Icons.h"
Icons* Icons::m_instance(nullptr);
Icons::Icons()
= default;
QIcon Icons::icon(const QString& name)
{
QIcon icon = m_iconCache.value(name);
if (!icon.isNull()) {
return icon;
}
icon = QIcon{":/assets/images/" + name};
m_iconCache.insert(name, icon);
return icon;
}
Icons* Icons::instance()
{
if (!m_instance) {
m_instance = new Icons();
}
return m_instance;
}

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