run: cmake --build build
- name: Validate Version
- run: ./build/bin/feather --version
+ run: ./build/bin/feather --version | grep "Feather Wallet"
if (UNIX AND NOT APPLE)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/feather.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications)
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/32x32.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/32x32/apps RENAME "feather.png")
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/48x48.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/48x48/apps RENAME "feather.png")
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/64x64.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/64x64/apps RENAME "feather.png")
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/96x96.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/96x96/apps RENAME "feather.png")
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/128x128.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/128x128/apps RENAME "feather.png")
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/256x256.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/256x256/apps RENAME "feather.png")
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/512x512.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/512x512/apps RENAME "feather.png")
+
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/32x32.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/32x32/apps RENAME "FeatherWallet.png")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/48x48.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/48x48/apps RENAME "FeatherWallet.png")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/images/appicons/64x64.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/64x64/apps RENAME "FeatherWallet.png")
#include <QMessageBox>
#include <QClipboard>
#include <QLocale>
+#include <QHBoxLayout>
#include <QCheckBox>
#include <QFormLayout>
#include <QSpinBox>
m_statusUpdateAvailable->hide();
this->statusBar()->addPermanentWidget(m_statusUpdateAvailable);
+ QWidget *balanceContainer = new QWidget(this);
+ QHBoxLayout *balanceLayout = new QHBoxLayout(balanceContainer);
+ balanceLayout->setContentsMargins(0, 0, 0, 0);
+ balanceLayout->setSpacing(0);
+
QLabel *balancePrefix = new QLabel("Balance:", this);
- this->statusBar()->addPermanentWidget(balancePrefix);
+ balanceLayout->addWidget(balancePrefix);
m_statusLabelBalance = new QLabel(this);
m_statusLabelBalance->setText("0");
m_statusLabelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse);
m_statusLabelBalance->setContextMenuPolicy(Qt::ActionsContextMenu);
- this->statusBar()->addPermanentWidget(m_statusLabelBalance);
+ balanceLayout->addWidget(m_statusLabelBalance);
m_statusLabelBalanceSuffix = new QLabel("XMR", this);
- this->statusBar()->addPermanentWidget(m_statusLabelBalanceSuffix);
+ balanceLayout->addWidget(m_statusLabelBalanceSuffix);
+
+ this->statusBar()->addPermanentWidget(balanceContainer);
QAction *copyBalanceAction = new QAction(tr("Copy"), this);
connect(copyBalanceAction, &QAction::triggered, this, [this](){
if (conf()->get(Config::balanceShowFiat).toBool() && !hide) {
QString fiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
double balanceFiatAmount = appData()->prices.convert("XMR", fiatCurrency, balance / constants::cdiv);
- bool isCacheFresh = appData()->prices.lastUpdateTime.isValid() &&
- appData()->prices.lastUpdateTime.secsTo(QDateTime::currentDateTime()) < 3600;
+ bool isCacheValid = appData()->prices.lastUpdateTime.isValid();
+ bool isCacheFresh = isCacheValid && appData()->prices.lastUpdateTime.secsTo(QDateTime::currentDateTime()) < 3; // TODO: 3600
- if (balance > 0 && (balanceFiatAmount == 0.0 || !isCacheFresh)) {
+ if (balance > 0 && (balanceFiatAmount == 0.0 || !isCacheValid)) {
if (conf()->get(Config::offlineMode).toBool() || m_wallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected) {
suffixStr += " (offline)";
} else if (!appData()->prices.markets.contains("XMR")) {
suffixStr += " (unknown)";
}
} else {
- suffixStr += QString(" (%1)").arg(Utils::amountToCurrencyString(balanceFiatAmount, fiatCurrency));
+ QString approx = isCacheFresh ? "" : "~ ";
+ suffixStr += QString(" (%1%2)").arg(approx, Utils::amountToCurrencyString(balanceFiatAmount, fiatCurrency));
}
}
- m_statusLabelBalance->setToolTip("Click for details");
+ QString toolTip = "Click for details";
+ if (appData()->prices.lastUpdateTime.isValid()) {
+ toolTip += QString("\nLast updated: %1").arg(Utils::timeAgo(appData()->prices.lastUpdateTime));
+ }
+ m_statusLabelBalance->setToolTip(toolTip);
m_statusLabelBalance->setText(valueStr);
+ m_statusLabelBalanceSuffix->setToolTip(toolTip);
m_statusLabelBalanceSuffix->setText(suffixStr);
}
if (reason == QSystemTrayIcon::Trigger) {
if (conf()->get(Config::trayLeftClickTogglesFocus).toBool()) {
for (const auto &window : m_windows) {
- if (window->isVisible()) {
+ if (window->isVisible() && window->isActiveWindow()) {
window->hide();
} else {
window->show();
getNetworkSocks5()->setProxy(proxy);
}
- qInfo() << "Proxy: " << proxy.hostName() << " " << proxy.port();
+ qDebug() << "Proxy: " << proxy.hostName() << " " << proxy.port();
// Switch websocket to new proxy and update URL
websocketNotifier()->websocketClient->stop();
return font;
}
+QString timeAgo(const QDateTime &dt) {
+ qint64 diff = dt.secsTo(QDateTime::currentDateTime());
+
+ if (diff < 0) return "in the future";
+ if (diff < 60) return QString("%1 second%2 ago").arg(diff).arg(diff == 1 ? "" : "s");
+
+ diff /= 60; // minutes
+ if (diff < 60) return QString("%1 minute%2 ago").arg(diff).arg(diff == 1 ? "" : "s");
+
+ qint64 minutes = diff % 60;
+ diff /= 60; // hours
+ if (diff < 24) {
+ if (minutes > 0)
+ return QString("%1 hour%2 %3 minute%4 ago").arg(diff).arg(diff == 1 ? "" : "s").arg(minutes).arg(minutes == 1 ? "" : "s");
+ return QString("%1 hour%2 ago").arg(diff).arg(diff == 1 ? "" : "s");
+ }
+
+ diff /= 24; // days
+ if (diff < 30) return QString("%1 day%2 ago").arg(diff).arg(diff == 1 ? "" : "s");
+
+ diff /= 30; // months (approx)
+ if (diff < 12) return QString("%1 month%2 ago").arg(diff).arg(diff == 1 ? "" : "s");
+
+ return QString("%1 year%2 ago").arg(diff / 12).arg((diff / 12) == 1 ? "" : "s");
+}
+
bool isLocalUrl(const QUrl &url) {
QRegularExpression localNetwork(R"((^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.))");
return (localNetwork.match(url.host()).hasMatch() || url.host() == "localhost");
QFont getMonospaceFont();
QFont relativeFont(int delta);
+ QString timeAgo(const QDateTime &dt);
void applicationLogHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
QString barrayToString(const QByteArray &data);
double balance = (m_totalBalance ? m_wallet->balanceAll() : m_wallet->balance()) / constants::cdiv;
QString fiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
double balanceFiatAmount = appData()->prices.convert("XMR", fiatCurrency, balance);
- if (balanceFiatAmount < 0)
- return;
- this->setFiatText(balanceFiatAmount, fiatCurrency);
+
+ bool isCacheValid = appData()->prices.lastUpdateTime.isValid();
+ bool isCacheFresh = isCacheValid && appData()->prices.lastUpdateTime.secsTo(QDateTime::currentDateTime()) < 3600;
+
+ if (balanceFiatAmount == 0.0 || !isCacheValid) {
+ if (conf()->get(Config::offlineMode).toBool() || m_wallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected) {
+ this->setDisplayText("offline");
+ } else if (!appData()->prices.markets.contains("XMR")) {
+ this->setDisplayText("connecting");
+ } else {
+ this->setDisplayText("unknown");
+ }
+ } else {
+ QString approx = isCacheFresh ? "" : "~ ";
+ this->setDisplayText(approx + Utils::amountToCurrencyString(balanceFiatAmount, fiatCurrency));
+ }
}
// PriceTickerWidget