From 784da1a652356a93b6e98e45768d021cb831b1bb Mon Sep 17 00:00:00 2001 From: Yuriy Puchkov Date: Wed, 9 Dec 2020 18:12:37 +0200 Subject: [PATCH] Update to a new version notification --- src/core/controller.cpp | 80 ++++++++++++- src/core/controller.h | 17 +++ src/tools/storage/s3/imgs3settings.cpp | 1 + src/utils/confighandler.cpp | 10 ++ src/utils/confighandler.h | 3 + src/widgets/CMakeLists.txt | 2 + src/widgets/capture/capturewidget.cpp | 30 ++++- src/widgets/capture/capturewidget.h | 4 + src/widgets/updatenotificationwidget.cpp | 141 +++++++++++++++++++++++ src/widgets/updatenotificationwidget.h | 47 ++++++++ 10 files changed, 329 insertions(+), 6 deletions(-) create mode 100644 src/widgets/updatenotificationwidget.cpp create mode 100644 src/widgets/updatenotificationwidget.h diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 267adfde..bb899c7c 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -30,8 +30,14 @@ #include #include #include +#include #include +#include +#include #include +#include +#include +#include #include #ifdef Q_OS_WIN @@ -49,10 +55,12 @@ Controller::Controller() : m_captureWindow(nullptr) + , m_history(nullptr) + , m_trayIconMenu(nullptr) + , m_networkCheckUpdates(nullptr) + , m_showCheckAppUpdateStatus(false) { - m_history = nullptr; - m_trayIconMenu = nullptr; - + m_appLatestVersion = QStringLiteral(APP_VERSION).replace("v", ""); qApp->setQuitOnLastWindowClosed(false); // set default shortcusts if not set yet @@ -84,6 +92,7 @@ Controller::Controller() QScreen* currentScreen = QGuiApplication::screenAt(QCursor::pos()); currentScreen->grabWindow(QApplication::desktop()->winId(), 0, 0, 1, 1); #endif + getLatestAvailableVersion(); } Controller::~Controller() @@ -106,6 +115,58 @@ void Controller::enableExports() this, &Controller::captureFailed, this, &Controller::handleCaptureFailed); } +void Controller::getLatestAvailableVersion() +{ + // This features is required for MacOS and Windows user and for Linux users + // who installed Flameshot not from the repository. + m_networkCheckUpdates = new QNetworkAccessManager(); + m_networkCheckUpdates = new QNetworkAccessManager(this); + QNetworkRequest requestCheckUpdates(QUrl(FLAMESHOT_APP_VERSION_URL)); + connect(m_networkCheckUpdates, + &QNetworkAccessManager::finished, + this, + &Controller::handleReplyCheckUpdates); + m_networkCheckUpdates->get(requestCheckUpdates); +} + +void Controller::handleReplyCheckUpdates(QNetworkReply* reply) +{ + if (reply->error() == QNetworkReply::NoError) { + QJsonDocument response = QJsonDocument::fromJson(reply->readAll()); + QJsonObject json = response.object(); + m_appLatestVersion = json["tag_name"].toString().replace("v", ""); + if (m_appLatestVersion.compare( + QStringLiteral(APP_VERSION).replace("v", "")) < 0) { + // Next commented lines are for debugging + // if (m_appLatestVersion.compare( + // QStringLiteral("v0.8.5.4").replace("v", "")) > 0) { + m_appLatestUrl = json["html_url"].toString(); + QString newVersion = + tr("New version %1 is available").arg(m_appLatestVersion); + m_appUpdates->setText(newVersion); + if (m_showCheckAppUpdateStatus) { + sendTrayNotification(newVersion, "Flameshot", 5); + QDesktopServices::openUrl(QUrl(m_appLatestUrl)); + } + } else if (m_showCheckAppUpdateStatus) { + sendTrayNotification( + tr("You have the latest version"), "Flameshot", 5); + } + } + // nothing to do on fails, is not critical for checking for updates + m_showCheckAppUpdateStatus = false; +} + +void Controller::appUpdates() +{ + if (m_appLatestUrl.isEmpty()) { + m_showCheckAppUpdateStatus = true; + getLatestAvailableVersion(); + } else { + QDesktopServices::openUrl(QUrl(m_appLatestUrl)); + } +} + void Controller::requestCapture(const CaptureRequest& request) { uint id = request.id(); @@ -170,13 +231,18 @@ void Controller::startVisualCapture(const uint id, #elif (defined(Q_OS_MAC) || defined(Q_OS_MAC64) || defined(Q_OS_MACOS) || \ defined(Q_OS_MACX)) // In "Emulate fullscreen mode" - // m_captureWindow->show(); m_captureWindow->showFullScreen(); m_captureWindow->activateWindow(); m_captureWindow->raise(); #else m_captureWindow->showFullScreen(); #endif + if (!m_appLatestUrl.isEmpty() && + 0 != m_appLatestVersion.compare( + ConfigHandler().ignoreUpdateToVersion())) { + m_captureWindow->showAppUpdateNotification(m_appLatestVersion, + m_appLatestUrl); + } } else { emit captureFailed(id); } @@ -262,6 +328,10 @@ void Controller::enableTrayIcon() configAction, &QAction::triggered, this, &Controller::openConfigWindow); QAction* infoAction = new QAction(tr("&About"), this); connect(infoAction, &QAction::triggered, this, &Controller::openInfoWindow); + + m_appUpdates = new QAction(tr("Check for updates"), this); + connect(m_appUpdates, &QAction::triggered, this, &Controller::appUpdates); + QAction* quitAction = new QAction(tr("&Quit"), this); connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit); @@ -277,6 +347,8 @@ void Controller::enableTrayIcon() m_trayIconMenu->addAction(recentAction); m_trayIconMenu->addSeparator(); m_trayIconMenu->addAction(configAction); + m_trayIconMenu->addSeparator(); + m_trayIconMenu->addAction(m_appUpdates); m_trayIconMenu->addAction(infoAction); m_trayIconMenu->addSeparator(); m_trayIconMenu->addAction(quitAction); diff --git a/src/core/controller.h b/src/core/controller.h index c061f6d8..3ffff381 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -17,6 +17,9 @@ #pragma once +#define FLAMESHOT_APP_VERSION_URL \ + "https://api.github.com/repos/namecheap/flameshot/releases/latest" + #include "src/core/capturerequest.h" #include #include @@ -32,6 +35,8 @@ class InfoWindow; class QSystemTrayIcon; class CaptureLauncher; class HistoryWidget; +class QNetworkAccessManager; +class QNetworkReply; using lambda = std::function; class Controller : public QObject @@ -57,6 +62,7 @@ public slots: void openConfigWindow(); void openInfoWindow(); + void appUpdates(); void openLauncherWindow(); void enableTrayIcon(); void disableTrayIcon(); @@ -78,13 +84,22 @@ private slots: void handleCaptureTaken(uint id, QPixmap p); void handleCaptureFailed(uint id); + void handleReplyCheckUpdates(QNetworkReply* reply); + private: Controller(); + void getLatestAvailableVersion(); // replace QTimer::singleShot introduced in Qt 5.4 // the actual target Qt version is 5.3 void doLater(int msec, QObject* receiver, lambda func); + // class members + QAction* m_appUpdates; + QString m_appLatestUrl; + QString m_appLatestVersion; + bool m_showCheckAppUpdateStatus; + QMap m_requestMap; QPointer m_captureWindow; QPointer m_infoWindow; @@ -94,4 +109,6 @@ private: HistoryWidget* m_history; QMenu* m_trayIconMenu; + + QNetworkAccessManager* m_networkCheckUpdates; }; diff --git a/src/tools/storage/s3/imgs3settings.cpp b/src/tools/storage/s3/imgs3settings.cpp index 5ce12c3c..6f8df23a 100644 --- a/src/tools/storage/s3/imgs3settings.cpp +++ b/src/tools/storage/s3/imgs3settings.cpp @@ -1,4 +1,5 @@ #include "imgs3settings.h" +#include "src/core/controller.h" #include "src/tools/storage/imgstorages.h" #include "src/utils/confighandler.h" #include diff --git a/src/utils/confighandler.cpp b/src/utils/confighandler.cpp index 531c0f1e..2f21c061 100644 --- a/src/utils/confighandler.cpp +++ b/src/utils/confighandler.cpp @@ -236,6 +236,16 @@ void ConfigHandler::setShowSidePanelButton(const bool showSidePanelButton) showSidePanelButton); } +void ConfigHandler::setIgnoreUpdateToVersion(const QString& text) +{ + m_settings.setValue(QStringLiteral("ignoreUpdateToVersion"), text); +} + +QString ConfigHandler::ignoreUpdateToVersion() +{ + return m_settings.value(QStringLiteral("ignoreUpdateToVersion")).toString(); +} + bool ConfigHandler::desktopNotificationValue() { bool res = true; diff --git a/src/utils/confighandler.h b/src/utils/confighandler.h index 8670a893..637dcbf9 100644 --- a/src/utils/confighandler.h +++ b/src/utils/confighandler.h @@ -97,6 +97,9 @@ public: void setDefaults(); void setAllTheButtons(); + void setIgnoreUpdateToVersion(const QString& text); + QString ignoreUpdateToVersion(); + QVector shortcuts(); void setShortcutsDefault(); bool setShortcut(const QString&, const QString&); diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 55eee41a..dfb6b6ef 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources( notificationwidget.h orientablepushbutton.h historywidget.h + updatenotificationwidget.h ) target_sources( @@ -24,4 +25,5 @@ target_sources( notificationwidget.cpp orientablepushbutton.cpp historywidget.cpp + updatenotificationwidget.cpp ) diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index 656691a3..21fd1b09 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -24,7 +24,6 @@ // #include "capturewidget.h" -#include "src/core/controller.h" #include "src/tools/storage/storagemanager.h" #include "src/tools/toolfactory.h" #include "src/utils/colorutils.h" @@ -37,6 +36,7 @@ #include "src/widgets/capture/notifierbox.h" #include "src/widgets/orientablepushbutton.h" #include "src/widgets/panel/sidepanelwidget.h" +#include "src/widgets/updatenotificationwidget.h" #include #include #include @@ -68,8 +68,9 @@ CaptureWidget::CaptureWidget(const uint id, , m_toolWidget(nullptr) , m_mouseOverHandle(SelectionWidget::NO_SIDE) , m_id(id) + , m_lastMouseWheel(0) + , m_updateNotificationWidget(nullptr) { - m_lastMouseWheel = 0; // Base config of the widget m_eventFilter = new HoverEventFilter(this); connect(m_eventFilter, @@ -294,7 +295,19 @@ void CaptureWidget::paintEvent(QPaintEvent*) painter.setClipRect(rect()); if (m_showInitialMsg) { +#if (defined(Q_OS_MAC) || defined(Q_OS_MAC64) || defined(Q_OS_MACOS) || \ + defined(Q_OS_MACX)) + QRect helpRect; + QScreen* currentScreen = QGuiApplication::screenAt(QCursor::pos()); + if (currentScreen) { + helpRect = currentScreen->geometry(); + } else { + helpRect = QGuiApplication::primaryScreen()->geometry(); + } +#else QRect helpRect = QGuiApplication::primaryScreen()->geometry(); +#endif + helpRect.moveTo(mapFromGlobal(helpRect.topLeft())); QString helpTxt = @@ -764,6 +777,19 @@ void CaptureWidget::initPanel() m_panel->pushWidget(new QUndoView(&m_undoStack, this)); } +void CaptureWidget::showAppUpdateNotification(const QString& appLatestVersion, + const QString& appLatestUrl) +{ + if (nullptr == m_updateNotificationWidget) { + m_updateNotificationWidget = + new UpdateNotificationWidget(this, appLatestVersion, appLatestUrl); + } + m_updateNotificationWidget->move( + (width() - m_updateNotificationWidget->width()) / 2, 0); + makeChild(m_updateNotificationWidget); + m_updateNotificationWidget->show(); +} + void CaptureWidget::initSelection() { m_selection = new SelectionWidget(m_uiColor, this); diff --git a/src/widgets/capture/capturewidget.h b/src/widgets/capture/capturewidget.h index 808f8349..74112856 100644 --- a/src/widgets/capture/capturewidget.h +++ b/src/widgets/capture/capturewidget.h @@ -44,6 +44,7 @@ class QNetworkReply; class ColorPicker; class NotifierBox; class HoverEventFilter; +class UpdateNotificationWidget; class CaptureWidget : public QWidget { @@ -58,6 +59,8 @@ public: void updateButtons(); QPixmap pixmap(); + void showAppUpdateNotification(const QString& appLatestVersion, + const QString& appLatestUrl); public slots: void deleteToolwidgetOrClose(); @@ -145,6 +148,7 @@ private: QRect extendedRect(QRect* r) const; private: + UpdateNotificationWidget* m_updateNotificationWidget; quint64 m_lastMouseWheel; QUndoStack m_undoStack; QPointer m_sizeIndButton; diff --git a/src/widgets/updatenotificationwidget.cpp b/src/widgets/updatenotificationwidget.cpp new file mode 100644 index 00000000..5a3c5e92 --- /dev/null +++ b/src/widgets/updatenotificationwidget.cpp @@ -0,0 +1,141 @@ +// +// Created by yuriypuchkov on 09.12.2020. +// + +#include "updatenotificationwidget.h" +#include "src/utils/confighandler.h" +#include +#include +#include +#include +#include +#include +#include +#include + +UpdateNotificationWidget::UpdateNotificationWidget( + QWidget* parent, + const QString& appLatestVersion, + const QString& appLatestUrl) + : QWidget(parent) + , m_appLatestVersion(appLatestVersion) + , m_appLatestUrl(appLatestUrl) + , m_layout(nullptr) +{ + setMinimumSize(400, 100); + initInternalPanel(); + setAttribute(Qt::WA_TransparentForMouseEvents); + setCursor(Qt::ArrowCursor); + + m_showAnimation = new QPropertyAnimation(m_internalPanel, "geometry", this); + m_showAnimation->setEasingCurve(QEasingCurve::InOutQuad); + m_showAnimation->setDuration(300); + + m_hideAnimation = new QPropertyAnimation(m_internalPanel, "geometry", this); + m_hideAnimation->setEasingCurve(QEasingCurve::InOutQuad); + m_hideAnimation->setDuration(300); + + connect(m_hideAnimation, + &QPropertyAnimation::finished, + m_internalPanel, + &QWidget::hide); + setAppLatestVersion(appLatestVersion); +} + +void UpdateNotificationWidget::show() +{ + setAttribute(Qt::WA_TransparentForMouseEvents, false); + m_showAnimation->setStartValue(QRect(0, -height(), width(), height())); + m_showAnimation->setEndValue(QRect(0, 0, width(), height())); + m_internalPanel->show(); + m_showAnimation->start(); + QWidget::show(); +} + +void UpdateNotificationWidget::hide() +{ + setAttribute(Qt::WA_TransparentForMouseEvents); + m_hideAnimation->setStartValue(QRect(0, 0, width(), height())); + m_hideAnimation->setEndValue(QRect(0, -height(), 0, height())); + m_hideAnimation->start(); + m_internalPanel->hide(); + QWidget::hide(); +} + +void UpdateNotificationWidget::setAppLatestVersion(const QString& latestVersion) +{ + m_appLatestVersion = latestVersion; + QString newVersion = + tr("New Flameshot version %1 is available").arg(latestVersion); + m_notification->setText(newVersion); +} + +void UpdateNotificationWidget::laterButton() +{ + hide(); +} + +void UpdateNotificationWidget::ignoreButton() +{ + ConfigHandler().setIgnoreUpdateToVersion(m_appLatestVersion); + hide(); +} + +void UpdateNotificationWidget::updateButton() +{ + QDesktopServices::openUrl(m_appLatestUrl); + hide(); +} + +void UpdateNotificationWidget::initInternalPanel() +{ + m_internalPanel = new QScrollArea(this); + m_internalPanel->setAttribute(Qt::WA_NoMousePropagation); + QWidget* widget = new QWidget(); + m_internalPanel->setWidget(widget); + m_internalPanel->setWidgetResizable(true); + + QColor bgColor = palette().window().color(); + bgColor.setAlphaF(0.0); + m_internalPanel->setStyleSheet( + QStringLiteral("QScrollArea {background-color: %1}").arg(bgColor.name())); + m_internalPanel->hide(); + + // + m_layout = new QVBoxLayout(); + widget->setLayout(m_layout); + + // caption + m_notification = new QLabel(m_appLatestVersion, this); + m_layout->addWidget(m_notification); + + // buttons layout + QHBoxLayout* buttonsLayout = new QHBoxLayout(); + QSpacerItem* bottonsSpacer = new QSpacerItem(1, 1, QSizePolicy::Expanding); + buttonsLayout->addSpacerItem(bottonsSpacer); + m_layout->addLayout(buttonsLayout); + + // ignore + QPushButton* ignoreBtn = new QPushButton(tr("Ignore"), this); + buttonsLayout->addWidget(ignoreBtn); + connect(ignoreBtn, + &QPushButton::clicked, + this, + &UpdateNotificationWidget::ignoreButton); + + // later + QPushButton* laterBtn = new QPushButton(tr("Later"), this); + buttonsLayout->addWidget(laterBtn); + connect(laterBtn, + &QPushButton::clicked, + this, + &UpdateNotificationWidget::laterButton); + + // update + QPushButton* updateBtn = new QPushButton(tr("Update"), this); + buttonsLayout->addWidget(updateBtn); + connect(updateBtn, + &QPushButton::clicked, + this, + &UpdateNotificationWidget::updateButton); +} diff --git a/src/widgets/updatenotificationwidget.h b/src/widgets/updatenotificationwidget.h new file mode 100644 index 00000000..b214842c --- /dev/null +++ b/src/widgets/updatenotificationwidget.h @@ -0,0 +1,47 @@ +// +// Created by yuriypuchkov on 09.12.2020. +// + +#ifndef FLAMESHOT_UPDATENOTIFICATIONWIDGET_H +#define FLAMESHOT_UPDATENOTIFICATIONWIDGET_H + +#include +#include + +class QVBoxLayout; +class QPropertyAnimation; +class QScrollArea; +class QPushButton; +class QLabel; + +class UpdateNotificationWidget : public QWidget +{ + Q_OBJECT +public: + explicit UpdateNotificationWidget(QWidget* parent, + const QString& appLatestVersion, + const QString& appLatestUrl); + void setAppLatestVersion(const QString& latestVersion); + + void hide(); + void show(); + +public slots: + void ignoreButton(); + void laterButton(); + void updateButton(); + +private: + void initInternalPanel(); + + // class members + QString m_appLatestVersion; + QString m_appLatestUrl; + QVBoxLayout* m_layout; + QLabel* m_notification; + QScrollArea* m_internalPanel; + QPropertyAnimation* m_showAnimation; + QPropertyAnimation* m_hideAnimation; +}; + +#endif // FLAMESHOT_UPDATENOTIFICATIONWIDGET_H