refactor: Toast (#4899)
* Fixes a bug where avatars weren't loaded on fresh installations of Chatterino. * Avatars now update every two weeks. * Removes misleading `DownlaodManager` (now part of `Toasts.cpp`). * Refactors usage of WinToast to be easier to read. * Added version to AUMI. * Removes manual `QString` → `std::wstring` conversions. * Removes uses of implicit ASCII casts in `Toasts.cpp`, meaning it can be compiled with `QT_NO_CAST_FROM_ASCII`.
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
- Bugfix: Fixed issue on Windows preventing the title bar from being dragged in the top left corner. (#4873)
|
- Bugfix: Fixed issue on Windows preventing the title bar from being dragged in the top left corner. (#4873)
|
||||||
- Bugfix: Fixed an issue where reply context didn't render correctly if an emoji was touching text. (#4875)
|
- Bugfix: Fixed an issue where reply context didn't render correctly if an emoji was touching text. (#4875)
|
||||||
- Bugfix: Fixed the input completion popup from disappearing when clicking on it on Windows and macOS. (#4876)
|
- Bugfix: Fixed the input completion popup from disappearing when clicking on it on Windows and macOS. (#4876)
|
||||||
|
- Bugfix: Fixed an issue where notifications on Windows would contain no or an old avatar. (#4899)
|
||||||
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
|
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
|
||||||
- Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767)
|
- Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767)
|
||||||
- Dev: Tests now run on Ubuntu 22.04 instead of 20.04 to loosen C++ restrictions in tests. (#4774)
|
- Dev: Tests now run on Ubuntu 22.04 instead of 20.04 to loosen C++ restrictions in tests. (#4774)
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ set(SOURCE_FILES
|
|||||||
common/ChatterSet.hpp
|
common/ChatterSet.hpp
|
||||||
common/Credentials.cpp
|
common/Credentials.cpp
|
||||||
common/Credentials.hpp
|
common/Credentials.hpp
|
||||||
common/DownloadManager.cpp
|
|
||||||
common/DownloadManager.hpp
|
|
||||||
common/Env.cpp
|
common/Env.cpp
|
||||||
common/Env.hpp
|
common/Env.hpp
|
||||||
common/LinkParser.cpp
|
common/LinkParser.cpp
|
||||||
|
|||||||
+14
-7
@@ -20,6 +20,7 @@
|
|||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
#ifdef USEWINSDK
|
#ifdef USEWINSDK
|
||||||
# include "util/WindowsHelper.hpp"
|
# include "util/WindowsHelper.hpp"
|
||||||
@@ -184,17 +185,20 @@ namespace {
|
|||||||
// improved in the future.
|
// improved in the future.
|
||||||
void clearCache(const QDir &dir)
|
void clearCache(const QDir &dir)
|
||||||
{
|
{
|
||||||
int deletedCount = 0;
|
size_t deletedCount = 0;
|
||||||
for (auto &&info : dir.entryInfoList(QDir::Files))
|
for (const auto &info : dir.entryInfoList(QDir::Files))
|
||||||
{
|
{
|
||||||
if (info.lastModified().addDays(14) < QDateTime::currentDateTime())
|
if (info.lastModified().addDays(14) < QDateTime::currentDateTime())
|
||||||
{
|
{
|
||||||
bool res = QFile(info.absoluteFilePath()).remove();
|
bool res = QFile(info.absoluteFilePath()).remove();
|
||||||
if (res)
|
if (res)
|
||||||
|
{
|
||||||
++deletedCount;
|
++deletedCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qCDebug(chatterinoCache) << "Deleted" << deletedCount << "files";
|
qCDebug(chatterinoCache)
|
||||||
|
<< "Deleted" << deletedCount << "files in" << dir.path();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We delete all but the five most recent crashdumps. This strategy may be
|
// We delete all but the five most recent crashdumps. This strategy may be
|
||||||
@@ -259,12 +263,15 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
|
|||||||
|
|
||||||
// Clear the cache 1 minute after start.
|
// Clear the cache 1 minute after start.
|
||||||
QTimer::singleShot(60 * 1000, [cachePath = paths.cacheDirectory(),
|
QTimer::singleShot(60 * 1000, [cachePath = paths.cacheDirectory(),
|
||||||
crashDirectory = paths.crashdumpDirectory] {
|
crashDirectory = paths.crashdumpDirectory,
|
||||||
QtConcurrent::run([cachePath]() {
|
avatarPath = paths.twitchProfileAvatars] {
|
||||||
|
std::ignore = QtConcurrent::run([cachePath] {
|
||||||
clearCache(cachePath);
|
clearCache(cachePath);
|
||||||
});
|
});
|
||||||
|
std::ignore = QtConcurrent::run([avatarPath] {
|
||||||
QtConcurrent::run([crashDirectory]() {
|
clearCache(avatarPath);
|
||||||
|
});
|
||||||
|
std::ignore = QtConcurrent::run([crashDirectory] {
|
||||||
clearCrashes(crashDirectory);
|
clearCrashes(crashDirectory);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
#include "DownloadManager.hpp"
|
|
||||||
|
|
||||||
#include "common/QLogging.hpp"
|
|
||||||
#include "singletons/Paths.hpp"
|
|
||||||
|
|
||||||
#include <QDesktopServices>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
DownloadManager::DownloadManager(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, manager_(new QNetworkAccessManager)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadManager::~DownloadManager()
|
|
||||||
{
|
|
||||||
this->manager_->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::setFile(QString fileURL, const QString &channelName)
|
|
||||||
{
|
|
||||||
QString saveFilePath;
|
|
||||||
saveFilePath =
|
|
||||||
getPaths()->twitchProfileAvatars + "/twitch/" + channelName + ".png";
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setUrl(QUrl(fileURL));
|
|
||||||
this->reply_ = this->manager_->get(request);
|
|
||||||
|
|
||||||
this->file_ = new QFile;
|
|
||||||
this->file_->setFileName(saveFilePath);
|
|
||||||
this->file_->open(QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
connect(this->reply_, SIGNAL(downloadProgress(qint64, qint64)), this,
|
|
||||||
SLOT(onDownloadProgress(qint64, qint64)));
|
|
||||||
connect(this->manager_, SIGNAL(finished(QNetworkReply *)), this,
|
|
||||||
SLOT(onFinished(QNetworkReply *)));
|
|
||||||
connect(this->reply_, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
|
|
||||||
connect(this->reply_, SIGNAL(finished()), this, SLOT(onReplyFinished()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoCommon)
|
|
||||||
<< "Download progress: " << bytesRead << "/" << bytesTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::onFinished(QNetworkReply *reply)
|
|
||||||
{
|
|
||||||
switch (reply->error())
|
|
||||||
{
|
|
||||||
case QNetworkReply::NoError: {
|
|
||||||
qCDebug(chatterinoCommon) << "file is downloaded successfully.";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
qCDebug(chatterinoCommon) << reply->errorString().toLatin1();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->file_->isOpen())
|
|
||||||
{
|
|
||||||
this->file_->close();
|
|
||||||
this->file_->deleteLater();
|
|
||||||
}
|
|
||||||
emit downloadComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::onReadyRead()
|
|
||||||
{
|
|
||||||
this->file_->write(this->reply_->readAll());
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::onReplyFinished()
|
|
||||||
{
|
|
||||||
if (this->file_->isOpen())
|
|
||||||
{
|
|
||||||
this->file_->close();
|
|
||||||
this->file_->deleteLater();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class DownloadManager : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit DownloadManager(QObject *parent = nullptr);
|
|
||||||
virtual ~DownloadManager();
|
|
||||||
void setFile(QString fileURL, const QString &channelName);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QNetworkAccessManager *manager_;
|
|
||||||
QNetworkReply *reply_{};
|
|
||||||
QFile *file_{};
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onDownloadProgress(qint64, qint64);
|
|
||||||
void onFinished(QNetworkReply *);
|
|
||||||
void onReadyRead();
|
|
||||||
void onReplyFinished();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void downloadComplete();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -122,9 +122,8 @@ void Paths::initSubDirectories()
|
|||||||
|
|
||||||
// create settings subdirectories and validate that they are created
|
// create settings subdirectories and validate that they are created
|
||||||
// properly
|
// properly
|
||||||
auto makePath = [&](const std::string &name) -> QString {
|
auto makePath = [&](const QString &name) -> QString {
|
||||||
auto path = combinePath(this->rootAppDataDirectory,
|
auto path = combinePath(this->rootAppDataDirectory, name);
|
||||||
QString::fromStdString(name));
|
|
||||||
|
|
||||||
if (!QDir().mkpath(path))
|
if (!QDir().mkpath(path))
|
||||||
{
|
{
|
||||||
@@ -140,11 +139,11 @@ void Paths::initSubDirectories()
|
|||||||
this->cacheDirectory_ = makePath("Cache");
|
this->cacheDirectory_ = makePath("Cache");
|
||||||
this->messageLogDirectory = makePath("Logs");
|
this->messageLogDirectory = makePath("Logs");
|
||||||
this->miscDirectory = makePath("Misc");
|
this->miscDirectory = makePath("Misc");
|
||||||
this->twitchProfileAvatars = makePath("ProfileAvatars");
|
this->twitchProfileAvatars =
|
||||||
|
makePath(combinePath("ProfileAvatars", "twitch"));
|
||||||
this->pluginsDirectory = makePath("Plugins");
|
this->pluginsDirectory = makePath("Plugins");
|
||||||
this->themesDirectory = makePath("Themes");
|
this->themesDirectory = makePath("Themes");
|
||||||
this->crashdumpDirectory = makePath("Crashes");
|
this->crashdumpDirectory = makePath("Crashes");
|
||||||
//QDir().mkdir(this->twitchProfileAvatars + "/twitch");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Paths *getPaths()
|
Paths *getPaths()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public:
|
|||||||
// Hash of QCoreApplication::applicationFilePath()
|
// Hash of QCoreApplication::applicationFilePath()
|
||||||
QString applicationFilePathHash;
|
QString applicationFilePathHash;
|
||||||
|
|
||||||
// Profile avatars for Twitch <appDataDirectory>/cache/twitch
|
// Profile avatars for Twitch <appDataDirectory>/ProfileAvatars/twitch
|
||||||
QString twitchProfileAvatars;
|
QString twitchProfileAvatars;
|
||||||
|
|
||||||
// Plugin files live here. <appDataDirectory>/Plugins
|
// Plugin files live here. <appDataDirectory>/Plugins
|
||||||
|
|||||||
+163
-78
@@ -1,12 +1,11 @@
|
|||||||
#include "Toasts.hpp"
|
#include "Toasts.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/DownloadManager.hpp"
|
#include "common/Literals.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "common/Version.hpp"
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
@@ -14,32 +13,64 @@
|
|||||||
#include "widgets/helper/CommonTexts.hpp"
|
#include "widgets/helper/CommonTexts.hpp"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|
||||||
# include <wintoastlib.h>
|
# include <wintoastlib.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QStringBuilder>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <utility>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
using namespace literals;
|
||||||
|
|
||||||
|
QString avatarFilePath(const QString &channelName)
|
||||||
|
{
|
||||||
|
// TODO: cleanup channel (to be used as a file) and use combinePath
|
||||||
|
return getPaths()->twitchProfileAvatars % '/' % channelName % u".png";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasAvatarForChannel(const QString &channelName)
|
||||||
|
{
|
||||||
|
QFileInfo avatarFile(avatarFilePath(channelName));
|
||||||
|
return avatarFile.exists() && avatarFile.isFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A job that downlaods a twitch avatar and saves it to a file
|
||||||
|
class AvatarDownloader : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AvatarDownloader(const QString &avatarURL, const QString &channelName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QNetworkAccessManager manager_;
|
||||||
|
QFile file_;
|
||||||
|
QNetworkReply *reply_{};
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void downloadComplete();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
std::map<ToastReaction, QString> Toasts::reactionToString = {
|
#ifdef Q_OS_WIN
|
||||||
{ToastReaction::OpenInBrowser, OPEN_IN_BROWSER},
|
using WinToastLib::WinToast;
|
||||||
{ToastReaction::OpenInPlayer, OPEN_PLAYER_IN_BROWSER},
|
using WinToastLib::WinToastTemplate;
|
||||||
{ToastReaction::OpenInStreamlink, OPEN_IN_STREAMLINK},
|
#endif
|
||||||
{ToastReaction::DontOpen, DONT_OPEN}};
|
|
||||||
|
|
||||||
bool Toasts::isEnabled()
|
bool Toasts::isEnabled()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
return WinToastLib::WinToast::isCompatible() &&
|
return WinToast::isCompatible() && getSettings()->notificationToast &&
|
||||||
getSettings()->notificationToast &&
|
|
||||||
!(isInStreamerMode() &&
|
!(isInStreamerMode() &&
|
||||||
getSettings()->streamerModeSuppressLiveNotifications);
|
getSettings()->streamerModeSuppressLiveNotifications);
|
||||||
#else
|
#else
|
||||||
@@ -49,24 +80,31 @@ bool Toasts::isEnabled()
|
|||||||
|
|
||||||
QString Toasts::findStringFromReaction(const ToastReaction &reaction)
|
QString Toasts::findStringFromReaction(const ToastReaction &reaction)
|
||||||
{
|
{
|
||||||
auto iterator = Toasts::reactionToString.find(reaction);
|
// The constants are macros right now, but we want to avoid ASCII casts,
|
||||||
if (iterator != Toasts::reactionToString.end())
|
// so we're concatenating them with a QString literal - effectively making them part of it.
|
||||||
|
switch (reaction)
|
||||||
{
|
{
|
||||||
return iterator->second;
|
case ToastReaction::OpenInBrowser:
|
||||||
}
|
return OPEN_IN_BROWSER u""_s;
|
||||||
else
|
case ToastReaction::OpenInPlayer:
|
||||||
{
|
return OPEN_PLAYER_IN_BROWSER u""_s;
|
||||||
return DONT_OPEN;
|
case ToastReaction::OpenInStreamlink:
|
||||||
|
return OPEN_IN_STREAMLINK u""_s;
|
||||||
|
case ToastReaction::DontOpen:
|
||||||
|
default:
|
||||||
|
return DONT_OPEN u""_s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Toasts::findStringFromReaction(
|
QString Toasts::findStringFromReaction(
|
||||||
const pajlada::Settings::Setting<int> &value)
|
const pajlada::Settings::Setting<int> &reaction)
|
||||||
{
|
{
|
||||||
int i = static_cast<int>(value);
|
static_assert(std::is_same_v<std::underlying_type_t<ToastReaction>, int>);
|
||||||
return Toasts::findStringFromReaction(static_cast<ToastReaction>(i));
|
int value = reaction;
|
||||||
|
return Toasts::findStringFromReaction(static_cast<ToastReaction>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
void Toasts::sendChannelNotification(const QString &channelName,
|
void Toasts::sendChannelNotification(const QString &channelName,
|
||||||
const QString &channelTitle, Platform p)
|
const QString &channelTitle, Platform p)
|
||||||
{
|
{
|
||||||
@@ -75,6 +113,7 @@ void Toasts::sendChannelNotification(const QString &channelName,
|
|||||||
this->sendWindowsNotification(channelName, channelTitle, p);
|
this->sendWindowsNotification(channelName, channelTitle, p);
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
|
(void)channelTitle;
|
||||||
auto sendChannelNotification = [] {
|
auto sendChannelNotification = [] {
|
||||||
// Unimplemented for macOS and Linux
|
// Unimplemented for macOS and Linux
|
||||||
};
|
};
|
||||||
@@ -82,9 +121,7 @@ void Toasts::sendChannelNotification(const QString &channelName,
|
|||||||
// Fetch user profile avatar
|
// Fetch user profile avatar
|
||||||
if (p == Platform::Twitch)
|
if (p == Platform::Twitch)
|
||||||
{
|
{
|
||||||
QFileInfo check_file(getPaths()->twitchProfileAvatars + "/twitch/" +
|
if (hasAvatarForChannel(channelName))
|
||||||
channelName + ".png");
|
|
||||||
if (check_file.exists() && check_file.isFile())
|
|
||||||
{
|
{
|
||||||
sendChannelNotification();
|
sendChannelNotification();
|
||||||
}
|
}
|
||||||
@@ -93,10 +130,11 @@ void Toasts::sendChannelNotification(const QString &channelName,
|
|||||||
getHelix()->getUserByName(
|
getHelix()->getUserByName(
|
||||||
channelName,
|
channelName,
|
||||||
[channelName, sendChannelNotification](const auto &user) {
|
[channelName, sendChannelNotification](const auto &user) {
|
||||||
DownloadManager *manager = new DownloadManager();
|
// gets deleted when finished
|
||||||
manager->setFile(user.profileImageUrl, channelName);
|
auto *downloader =
|
||||||
manager->connect(manager,
|
new AvatarDownloader(user.profileImageUrl, channelName);
|
||||||
&DownloadManager::downloadComplete,
|
QObject::connect(downloader,
|
||||||
|
&AvatarDownloader::downloadComplete,
|
||||||
sendChannelNotification);
|
sendChannelNotification);
|
||||||
},
|
},
|
||||||
[] {
|
[] {
|
||||||
@@ -116,13 +154,12 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
CustomHandler(QString channelName, Platform p)
|
CustomHandler(QString channelName, Platform p)
|
||||||
: channelName_(channelName)
|
: channelName_(std::move(channelName))
|
||||||
, platform_(p)
|
, platform_(p)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
void toastActivated() const
|
void toastActivated() const override
|
||||||
{
|
{
|
||||||
QString link;
|
|
||||||
auto toastReaction =
|
auto toastReaction =
|
||||||
static_cast<ToastReaction>(getSettings()->openFromToast.getValue());
|
static_cast<ToastReaction>(getSettings()->openFromToast.getValue());
|
||||||
|
|
||||||
@@ -131,51 +168,74 @@ public:
|
|||||||
case ToastReaction::OpenInBrowser:
|
case ToastReaction::OpenInBrowser:
|
||||||
if (platform_ == Platform::Twitch)
|
if (platform_ == Platform::Twitch)
|
||||||
{
|
{
|
||||||
link = "https://www.twitch.tv/" + channelName_;
|
QDesktopServices::openUrl(
|
||||||
|
QUrl(u"https://www.twitch.tv/" % channelName_));
|
||||||
}
|
}
|
||||||
QDesktopServices::openUrl(QUrl(link));
|
|
||||||
break;
|
break;
|
||||||
case ToastReaction::OpenInPlayer:
|
case ToastReaction::OpenInPlayer:
|
||||||
if (platform_ == Platform::Twitch)
|
if (platform_ == Platform::Twitch)
|
||||||
{
|
{
|
||||||
link =
|
QDesktopServices::openUrl(QUrl(
|
||||||
"https://player.twitch.tv/?parent=twitch.tv&channel=" +
|
u"https://player.twitch.tv/?parent=twitch.tv&channel=" %
|
||||||
channelName_;
|
channelName_));
|
||||||
}
|
}
|
||||||
QDesktopServices::openUrl(QUrl(link));
|
|
||||||
break;
|
break;
|
||||||
case ToastReaction::OpenInStreamlink: {
|
case ToastReaction::OpenInStreamlink: {
|
||||||
openStreamlinkForChannel(channelName_);
|
openStreamlinkForChannel(channelName_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// the fourth and last option is "don't open"
|
case ToastReaction::DontOpen:
|
||||||
// in this case obviously nothing should happen
|
// nothing should happen
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void toastActivated(int actionIndex) const
|
void toastActivated(int actionIndex) const override
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void toastFailed() const
|
void toastFailed() const override
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void toastDismissed(WinToastDismissalReason state) const
|
void toastDismissed(WinToastDismissalReason state) const override
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void Toasts::ensureInitialized()
|
||||||
|
{
|
||||||
|
if (this->initialized_)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->initialized_ = true;
|
||||||
|
|
||||||
|
auto *instance = WinToast::instance();
|
||||||
|
instance->setAppName(L"Chatterino2");
|
||||||
|
instance->setAppUserModelId(
|
||||||
|
WinToast::configureAUMI(L"", L"Chatterino 2", L"",
|
||||||
|
Version::instance().version().toStdWString()));
|
||||||
|
instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
|
||||||
|
WinToast::WinToastError error{};
|
||||||
|
instance->initialize(&error);
|
||||||
|
|
||||||
|
if (error != WinToast::NoError)
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoNotification)
|
||||||
|
<< "Failed to initialize WinToast - error:" << error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Toasts::sendWindowsNotification(const QString &channelName,
|
void Toasts::sendWindowsNotification(const QString &channelName,
|
||||||
const QString &channelTitle, Platform p)
|
const QString &channelTitle, Platform p)
|
||||||
{
|
{
|
||||||
WinToastLib::WinToastTemplate templ = WinToastLib::WinToastTemplate(
|
this->ensureInitialized();
|
||||||
WinToastLib::WinToastTemplate::ImageAndText03);
|
|
||||||
QString str = channelName + " is live!";
|
|
||||||
std::string utf8_text = str.toUtf8().constData();
|
|
||||||
std::wstring widestr = std::wstring(utf8_text.begin(), utf8_text.end());
|
|
||||||
|
|
||||||
templ.setTextField(widestr, WinToastLib::WinToastTemplate::FirstLine);
|
WinToastTemplate templ(WinToastTemplate::ImageAndText03);
|
||||||
|
QString str = channelName % u" is live!";
|
||||||
|
|
||||||
|
templ.setTextField(str.toStdWString(), WinToastTemplate::FirstLine);
|
||||||
if (static_cast<ToastReaction>(getSettings()->openFromToast.getValue()) !=
|
if (static_cast<ToastReaction>(getSettings()->openFromToast.getValue()) !=
|
||||||
ToastReaction::DontOpen)
|
ToastReaction::DontOpen)
|
||||||
{
|
{
|
||||||
@@ -183,43 +243,68 @@ void Toasts::sendWindowsNotification(const QString &channelName,
|
|||||||
Toasts::findStringFromReaction(getSettings()->openFromToast);
|
Toasts::findStringFromReaction(getSettings()->openFromToast);
|
||||||
mode = mode.toLower();
|
mode = mode.toLower();
|
||||||
|
|
||||||
templ.setTextField(QString("%1 \nClick to %2")
|
templ.setTextField(
|
||||||
.arg(channelTitle)
|
u"%1 \nClick to %2"_s.arg(channelTitle).arg(mode).toStdWString(),
|
||||||
.arg(mode)
|
WinToastTemplate::SecondLine);
|
||||||
.toStdWString(),
|
|
||||||
WinToastLib::WinToastTemplate::SecondLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Path;
|
QString avatarPath;
|
||||||
if (p == Platform::Twitch)
|
if (p == Platform::Twitch)
|
||||||
{
|
{
|
||||||
Path = getPaths()->twitchProfileAvatars + "/twitch/" + channelName +
|
avatarPath = avatarFilePath(channelName);
|
||||||
".png";
|
|
||||||
}
|
}
|
||||||
std::string temp_Utf8 = Path.toUtf8().constData();
|
templ.setImagePath(avatarPath.toStdWString());
|
||||||
std::wstring imagePath = std::wstring(temp_Utf8.begin(), temp_Utf8.end());
|
|
||||||
templ.setImagePath(imagePath);
|
|
||||||
if (getSettings()->notificationPlaySound)
|
if (getSettings()->notificationPlaySound)
|
||||||
{
|
{
|
||||||
templ.setAudioOption(
|
templ.setAudioOption(WinToastTemplate::AudioOption::Silent);
|
||||||
WinToastLib::WinToastTemplate::AudioOption::Silent);
|
}
|
||||||
|
|
||||||
|
WinToast::WinToastError error = WinToast::NoError;
|
||||||
|
WinToast::instance()->showToast(templ, new CustomHandler(channelName, p),
|
||||||
|
&error);
|
||||||
|
if (error != WinToast::NoError)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoNotification) << "Failed to show toast:" << error;
|
||||||
}
|
}
|
||||||
WinToastLib::WinToast::instance()->setAppName(L"Chatterino2");
|
|
||||||
int mbstowcs(wchar_t * aumi_version, const char *CHATTERINO_VERSION,
|
|
||||||
size_t size);
|
|
||||||
std::string(CHATTERINO_VERSION);
|
|
||||||
std::wstring aumi_version =
|
|
||||||
std::wstring(CHATTERINO_VERSION.begin(), CHATTERINO_VERSION.end());
|
|
||||||
WinToastLib::WinToast::instance()->setAppUserModelId(
|
|
||||||
WinToastLib::WinToast::configureAUMI(L"", L"Chatterino 2", L"",
|
|
||||||
aumi_version));
|
|
||||||
WinToastLib::WinToast::instance()->setShortcutPolicy(
|
|
||||||
WinToastLib::WinToast::SHORTCUT_POLICY_IGNORE);
|
|
||||||
WinToastLib::WinToast::instance()->initialize();
|
|
||||||
WinToastLib::WinToast::instance()->showToast(
|
|
||||||
templ, new CustomHandler(channelName, p));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
AvatarDownloader::AvatarDownloader(const QString &avatarURL,
|
||||||
|
const QString &channelName)
|
||||||
|
: file_(avatarFilePath(channelName))
|
||||||
|
{
|
||||||
|
if (!this->file_.open(QFile::WriteOnly | QFile::Truncate))
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoNotification)
|
||||||
|
<< "Failed to open avatar file" << this->file_.errorString();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->reply_ = this->manager_.get(QNetworkRequest(avatarURL));
|
||||||
|
|
||||||
|
connect(this->reply_, &QNetworkReply::readyRead, this, [this] {
|
||||||
|
this->file_.write(this->reply_->readAll());
|
||||||
|
});
|
||||||
|
connect(this->reply_, &QNetworkReply::finished, this, [this] {
|
||||||
|
if (this->reply_->error() != QNetworkReply::NoError)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoNotification)
|
||||||
|
<< "Failed to download avatar" << this->reply_->errorString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->file_.isOpen())
|
||||||
|
{
|
||||||
|
this->file_.close();
|
||||||
|
}
|
||||||
|
emit downloadComplete();
|
||||||
|
this->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "Toasts.moc"
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|||||||
@@ -24,14 +24,16 @@ public:
|
|||||||
static QString findStringFromReaction(const ToastReaction &reaction);
|
static QString findStringFromReaction(const ToastReaction &reaction);
|
||||||
static QString findStringFromReaction(
|
static QString findStringFromReaction(
|
||||||
const pajlada::Settings::Setting<int> &reaction);
|
const pajlada::Settings::Setting<int> &reaction);
|
||||||
static std::map<ToastReaction, QString> reactionToString;
|
|
||||||
|
|
||||||
static bool isEnabled();
|
static bool isEnabled();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
void ensureInitialized();
|
||||||
void sendWindowsNotification(const QString &channelName,
|
void sendWindowsNotification(const QString &channelName,
|
||||||
const QString &channelTitle, Platform p);
|
const QString &channelTitle, Platform p);
|
||||||
|
|
||||||
|
bool initialized_ = false;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
Reference in New Issue
Block a user