mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Merge pull request #678 from apa420/apa-notification-on-live
Notification when a stream goes live
This commit is contained in:
commit
45988fc510
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -20,3 +20,7 @@
|
|||
[submodule "lib/qBreakpad"]
|
||||
path = lib/qBreakpad
|
||||
url = https://github.com/jiakuan/qBreakpad.git
|
||||
[submodule "lib/WinToast"]
|
||||
path = lib/WinToast
|
||||
url = https://github.com/mohabouje/WinToast
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ include(dependencies/libcommuni.pri)
|
|||
include(dependencies/websocketpp.pri)
|
||||
include(dependencies/openssl.pri)
|
||||
include(dependencies/boost.pri)
|
||||
include(dependencies/wintoast.pri)
|
||||
|
||||
# Optional feature: QtWebEngine
|
||||
#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
|
||||
|
@ -124,6 +125,7 @@ SOURCES += \
|
|||
src/controllers/highlights/UserHighlightModel.cpp \
|
||||
src/controllers/ignores/IgnoreController.cpp \
|
||||
src/controllers/ignores/IgnoreModel.cpp \
|
||||
src/controllers/notifications/NotificationController.cpp \
|
||||
src/controllers/taggedusers/TaggedUser.cpp \
|
||||
src/controllers/taggedusers/TaggedUsersController.cpp \
|
||||
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
||||
|
@ -205,6 +207,7 @@ SOURCES += \
|
|||
src/widgets/settingspages/KeyboardSettingsPage.cpp \
|
||||
src/widgets/settingspages/LogsPage.cpp \
|
||||
src/widgets/settingspages/ModerationPage.cpp \
|
||||
src/widgets/settingspages/NotificationPage.cpp \
|
||||
src/widgets/settingspages/SettingsPage.cpp \
|
||||
src/widgets/settingspages/SpecialChannelsPage.cpp \
|
||||
src/widgets/splits/Split.cpp \
|
||||
|
@ -250,6 +253,9 @@ SOURCES += \
|
|||
src/BrowserExtension.cpp \
|
||||
src/util/FormatTime.cpp \
|
||||
src/util/FunctionEventFilter.cpp \
|
||||
src/controllers/notifications/NotificationModel.cpp \
|
||||
src/singletons/Toasts.cpp \
|
||||
src/common/DownloadManager.cpp \
|
||||
src/widgets/helper/EffectLabel.cpp \
|
||||
src/widgets/helper/Button.cpp \
|
||||
src/messages/MessageContainer.cpp \
|
||||
|
@ -292,6 +298,7 @@ HEADERS += \
|
|||
src/controllers/ignores/IgnoreController.hpp \
|
||||
src/controllers/ignores/IgnoreModel.hpp \
|
||||
src/controllers/ignores/IgnorePhrase.hpp \
|
||||
src/controllers/notifications/NotificationController.hpp \
|
||||
src/controllers/taggedusers/TaggedUser.hpp \
|
||||
src/controllers/taggedusers/TaggedUsersController.hpp \
|
||||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||
|
@ -394,6 +401,7 @@ HEADERS += \
|
|||
src/widgets/settingspages/KeyboardSettingsPage.hpp \
|
||||
src/widgets/settingspages/LogsPage.hpp \
|
||||
src/widgets/settingspages/ModerationPage.hpp \
|
||||
src/widgets/settingspages/NotificationPage.hpp \
|
||||
src/widgets/settingspages/SettingsPage.hpp \
|
||||
src/widgets/settingspages/SpecialChannelsPage.hpp \
|
||||
src/widgets/splits/Split.hpp \
|
||||
|
@ -446,6 +454,9 @@ HEADERS += \
|
|||
src/BrowserExtension.hpp \
|
||||
src/util/FormatTime.hpp \
|
||||
src/util/FunctionEventFilter.hpp \
|
||||
src/controllers/notifications/NotificationModel.hpp \
|
||||
src/singletons/Toasts.hpp \
|
||||
src/common/DownloadManager.hpp \
|
||||
src/widgets/helper/EffectLabel.hpp \
|
||||
src/util/LayoutHelper.hpp \
|
||||
src/widgets/helper/Button.hpp \
|
||||
|
@ -453,7 +464,7 @@ HEADERS += \
|
|||
src/common/UsernameSet.hpp \
|
||||
src/widgets/settingspages/AdvancedPage.hpp
|
||||
|
||||
RESOURCES += \
|
||||
RESOURCES += \
|
||||
resources/resources.qrc \
|
||||
resources/resources_autogenerated.qrc
|
||||
|
||||
|
|
5
dependencies/wintoast.pri
vendored
Normal file
5
dependencies/wintoast.pri
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
win32 {
|
||||
INCLUDEPATH += $$PWD/../lib/wintoast/src/
|
||||
SOURCES += \
|
||||
$$PWD/../lib/WinToast/src/wintoastlib.cpp
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
#include "controllers/moderationactions/ModerationActions.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
|
@ -21,6 +22,7 @@
|
|||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/IsBigEndian.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
@ -45,16 +47,19 @@ Application::Application(Settings &_settings, Paths &_paths)
|
|||
, fonts(&this->emplace<Fonts>())
|
||||
, emotes(&this->emplace<Emotes>())
|
||||
, windows(&this->emplace<WindowManager>())
|
||||
, toasts(&this->emplace<Toasts>())
|
||||
|
||||
, accounts(&this->emplace<AccountController>())
|
||||
, commands(&this->emplace<CommandController>())
|
||||
, highlights(&this->emplace<HighlightController>())
|
||||
, notifications(&this->emplace<NotificationController>())
|
||||
, ignores(&this->emplace<IgnoreController>())
|
||||
, taggedUsers(&this->emplace<TaggedUsersController>())
|
||||
, moderationActions(&this->emplace<ModerationActions>())
|
||||
, twitch2(&this->emplace<TwitchServer>())
|
||||
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
||||
, logging(&this->emplace<Logging>())
|
||||
|
||||
{
|
||||
this->instance = this;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ class IgnoreController;
|
|||
class TaggedUsersController;
|
||||
class AccountController;
|
||||
class ModerationActions;
|
||||
class NotificationController;
|
||||
|
||||
class Theme;
|
||||
class WindowManager;
|
||||
|
@ -26,6 +27,7 @@ class Emotes;
|
|||
class Settings;
|
||||
class Fonts;
|
||||
class Resources2;
|
||||
class Toasts;
|
||||
class ChatterinoBadges;
|
||||
|
||||
class Application
|
||||
|
@ -53,10 +55,12 @@ public:
|
|||
Fonts *const fonts{};
|
||||
Emotes *const emotes{};
|
||||
WindowManager *const windows{};
|
||||
Toasts *const toasts{};
|
||||
|
||||
AccountController *const accounts{};
|
||||
CommandController *const commands{};
|
||||
HighlightController *const highlights{};
|
||||
NotificationController *const notifications{};
|
||||
IgnoreController *const ignores{};
|
||||
TaggedUsersController *const taggedUsers{};
|
||||
ModerationActions *const moderationActions{};
|
||||
|
|
|
@ -17,6 +17,7 @@ enum class HighlightState {
|
|||
None,
|
||||
Highlighted,
|
||||
NewMessage,
|
||||
Notification,
|
||||
};
|
||||
|
||||
inline QString qS(const std::string &string)
|
||||
|
|
79
src/common/DownloadManager.cpp
Normal file
79
src/common/DownloadManager.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#include "DownloadManager.hpp"
|
||||
|
||||
#include "singletons/Paths.hpp"
|
||||
|
||||
#include <QDesktopServices>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
DownloadManager::DownloadManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
manager = new QNetworkAccessManager;
|
||||
}
|
||||
|
||||
DownloadManager::~DownloadManager()
|
||||
{
|
||||
manager->deleteLater();
|
||||
}
|
||||
|
||||
void DownloadManager::setFile(QString fileURL, const QString &channelName)
|
||||
{
|
||||
QString filePath = fileURL;
|
||||
QString saveFilePath;
|
||||
QStringList filePathList = filePath.split('/');
|
||||
saveFilePath =
|
||||
getPaths()->twitchProfileAvatars + "/twitch/" + channelName + ".png";
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(fileURL));
|
||||
reply = manager->get(request);
|
||||
|
||||
file = new QFile;
|
||||
file->setFileName(saveFilePath);
|
||||
file->open(QIODevice::WriteOnly);
|
||||
|
||||
connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this,
|
||||
SLOT(onDownloadProgress(qint64, qint64)));
|
||||
connect(manager, SIGNAL(finished(QNetworkReply *)), this,
|
||||
SLOT(onFinished(QNetworkReply *)));
|
||||
connect(reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
|
||||
connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
|
||||
}
|
||||
|
||||
void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal)
|
||||
{
|
||||
qDebug(QString::number(bytesRead).toLatin1() + " - " +
|
||||
QString::number(bytesTotal).toLatin1());
|
||||
}
|
||||
|
||||
void DownloadManager::onFinished(QNetworkReply *reply)
|
||||
{
|
||||
switch (reply->error()) {
|
||||
case QNetworkReply::NoError: {
|
||||
qDebug("file is downloaded successfully.");
|
||||
} break;
|
||||
default: {
|
||||
qDebug(reply->errorString().toLatin1());
|
||||
};
|
||||
}
|
||||
|
||||
if (file->isOpen()) {
|
||||
file->close();
|
||||
file->deleteLater();
|
||||
}
|
||||
emit downloadComplete();
|
||||
}
|
||||
|
||||
void DownloadManager::onReadyRead()
|
||||
{
|
||||
file->write(reply->readAll());
|
||||
}
|
||||
|
||||
void DownloadManager::onReplyFinished()
|
||||
{
|
||||
if (file->isOpen()) {
|
||||
file->close();
|
||||
file->deleteLater();
|
||||
}
|
||||
}
|
||||
} // namespace chatterino
|
36
src/common/DownloadManager.hpp
Normal file
36
src/common/DownloadManager.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "Application.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
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
|
203
src/controllers/notifications/NotificationController.cpp
Normal file
203
src/controllers/notifications/NotificationController.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
#include "controllers/notifications/NotificationController.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "controllers/notifications/NotificationModel.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchApi.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <wintoastlib.h>
|
||||
#endif
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QMediaPlayer>
|
||||
#include <QUrl>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void NotificationController::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
this->initialized_ = true;
|
||||
for (const QString &channelName : this->twitchSetting_.getValue()) {
|
||||
this->channelMap[Platform::Twitch].appendItem(channelName);
|
||||
}
|
||||
|
||||
this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] { //
|
||||
this->twitchSetting_.setValue(
|
||||
this->channelMap[Platform::Twitch].getVector());
|
||||
});
|
||||
/*
|
||||
for (const QString &channelName : this->mixerSetting_.getValue()) {
|
||||
this->channelMap[Platform::Mixer].appendItem(channelName);
|
||||
}
|
||||
|
||||
this->channelMap[Platform::Mixer].delayedItemsChanged.connect([this] { //
|
||||
this->mixerSetting_.setValue(
|
||||
this->channelMap[Platform::Mixer].getVector());
|
||||
});*/
|
||||
|
||||
liveStatusTimer_ = new QTimer();
|
||||
|
||||
this->fetchFakeChannels();
|
||||
|
||||
QObject::connect(this->liveStatusTimer_, &QTimer::timeout,
|
||||
[=] { this->fetchFakeChannels(); });
|
||||
this->liveStatusTimer_->start(60 * 1000);
|
||||
}
|
||||
|
||||
void NotificationController::updateChannelNotification(
|
||||
const QString &channelName, Platform p)
|
||||
{
|
||||
if (isChannelNotified(channelName, p)) {
|
||||
removeChannelNotification(channelName, p);
|
||||
} else {
|
||||
addChannelNotification(channelName, p);
|
||||
}
|
||||
}
|
||||
|
||||
bool NotificationController::isChannelNotified(const QString &channelName,
|
||||
Platform p)
|
||||
{
|
||||
for (const auto &channel : this->channelMap[p].getVector()) {
|
||||
if (channelName.toLower() == channel.toLower()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NotificationController::addChannelNotification(const QString &channelName,
|
||||
Platform p)
|
||||
{
|
||||
channelMap[p].appendItem(channelName);
|
||||
}
|
||||
|
||||
void NotificationController::removeChannelNotification(
|
||||
const QString &channelName, Platform p)
|
||||
{
|
||||
for (std::vector<int>::size_type i = 0;
|
||||
i != channelMap[p].getVector().size(); i++) {
|
||||
if (channelMap[p].getVector()[i].toLower() == channelName.toLower()) {
|
||||
channelMap[p].removeItem(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationController::playSound()
|
||||
{
|
||||
static auto player = new QMediaPlayer;
|
||||
static QUrl currentPlayerUrl;
|
||||
|
||||
QUrl highlightSoundUrl;
|
||||
if (getSettings()->notificationCustomSound) {
|
||||
highlightSoundUrl = QUrl::fromLocalFile(
|
||||
getSettings()->notificationPathSound.getValue());
|
||||
} else {
|
||||
highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
|
||||
}
|
||||
if (currentPlayerUrl != highlightSoundUrl) {
|
||||
player->setMedia(highlightSoundUrl);
|
||||
|
||||
currentPlayerUrl = highlightSoundUrl;
|
||||
}
|
||||
player->play();
|
||||
}
|
||||
|
||||
NotificationModel *NotificationController::createModel(QObject *parent,
|
||||
Platform p)
|
||||
{
|
||||
NotificationModel *model = new NotificationModel(parent);
|
||||
model->init(&this->channelMap[p]);
|
||||
return model;
|
||||
}
|
||||
|
||||
void NotificationController::fetchFakeChannels()
|
||||
{
|
||||
for (std::vector<int>::size_type i = 0;
|
||||
i != channelMap[Platform::Twitch].getVector().size(); i++) {
|
||||
auto chan = getApp()->twitch.server->getChannelOrEmpty(
|
||||
channelMap[Platform::Twitch].getVector()[i]);
|
||||
if (chan->isEmpty()) {
|
||||
getFakeTwitchChannelLiveStatus(
|
||||
channelMap[Platform::Twitch].getVector()[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationController::getFakeTwitchChannelLiveStatus(
|
||||
const QString &channelName)
|
||||
{
|
||||
TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
|
||||
if (roomID.isEmpty()) {
|
||||
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
|
||||
channelName);
|
||||
removeFakeChannel(channelName);
|
||||
return;
|
||||
}
|
||||
log("[TwitchChannel:{}] Refreshing live status", channelName);
|
||||
|
||||
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
|
||||
auto request = NetworkRequest::twitchRequest(url);
|
||||
request.setCaller(QThread::currentThread());
|
||||
|
||||
request.onSuccess([this, channelName](auto result) -> Outcome {
|
||||
rapidjson::Document document = result.parseRapidJson();
|
||||
if (!document.IsObject()) {
|
||||
log("[TwitchChannel:refreshLiveStatus]root is not an object");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (!document.HasMember("stream")) {
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
const auto &stream = document["stream"];
|
||||
|
||||
if (!stream.IsObject()) {
|
||||
// Stream is offline (stream is most likely null)
|
||||
// removeFakeChannel(channelName);
|
||||
return Failure;
|
||||
}
|
||||
// Stream is live
|
||||
auto i = std::find(fakeTwitchChannels.begin(),
|
||||
fakeTwitchChannels.end(), channelName);
|
||||
|
||||
if (!(i != fakeTwitchChannels.end())) {
|
||||
fakeTwitchChannels.push_back(channelName);
|
||||
if (Toasts::isEnabled()) {
|
||||
getApp()->toasts->sendChannelNotification(channelName,
|
||||
Platform::Twitch);
|
||||
}
|
||||
if (getSettings()->notificationPlaySound) {
|
||||
getApp()->notifications->playSound();
|
||||
}
|
||||
if (getSettings()->notificationFlashTaskbar) {
|
||||
QApplication::alert(
|
||||
getApp()->windows->getMainWindow().window(), 2500);
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
});
|
||||
|
||||
request.execute();
|
||||
});
|
||||
}
|
||||
|
||||
void NotificationController::removeFakeChannel(const QString channelName)
|
||||
{
|
||||
auto i = std::find(fakeTwitchChannels.begin(), fakeTwitchChannels.end(),
|
||||
channelName);
|
||||
if (i != fakeTwitchChannels.end()) {
|
||||
fakeTwitchChannels.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
57
src/controllers/notifications/NotificationController.hpp
Normal file
57
src/controllers/notifications/NotificationController.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class NotificationModel;
|
||||
|
||||
enum class Platform : uint8_t {
|
||||
Twitch, // 0
|
||||
// Mixer, // 1
|
||||
};
|
||||
|
||||
class NotificationController final : public Singleton, private QObject
|
||||
{
|
||||
public:
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
bool isChannelNotified(const QString &channelName, Platform p);
|
||||
void updateChannelNotification(const QString &channelName, Platform p);
|
||||
void addChannelNotification(const QString &channelName, Platform p);
|
||||
void removeChannelNotification(const QString &channelName, Platform p);
|
||||
|
||||
void playSound();
|
||||
|
||||
UnsortedSignalVector<QString> getVector(Platform p);
|
||||
|
||||
std::map<Platform, UnsortedSignalVector<QString>> channelMap;
|
||||
|
||||
NotificationModel *createModel(QObject *parent, Platform p);
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
|
||||
void fetchFakeChannels();
|
||||
void removeFakeChannel(const QString channelName);
|
||||
void getFakeTwitchChannelLiveStatus(const QString &channelName);
|
||||
|
||||
std::vector<QString> fakeTwitchChannels;
|
||||
QTimer *liveStatusTimer_;
|
||||
|
||||
ChatterinoSetting<std::vector<QString>> twitchSetting_ = {
|
||||
"/notifications/twitch"};
|
||||
/*
|
||||
ChatterinoSetting<std::vector<QString>> mixerSetting_ = {
|
||||
"/notifications/mixer"};
|
||||
*/
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
28
src/controllers/notifications/NotificationModel.cpp
Normal file
28
src/controllers/notifications/NotificationModel.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include "NotificationModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
NotificationModel::NotificationModel(QObject *parent)
|
||||
: SignalVectorModel<QString>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
QString NotificationModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const QString &original)
|
||||
{
|
||||
return QString(row[0]->data(Qt::DisplayRole).toString());
|
||||
}
|
||||
|
||||
// turn a model
|
||||
void NotificationModel::getRowFromItem(const QString &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
28
src/controllers/notifications/NotificationModel.hpp
Normal file
28
src/controllers/notifications/NotificationModel.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NotificationController;
|
||||
|
||||
class NotificationModel : public SignalVectorModel<QString>
|
||||
{
|
||||
explicit NotificationModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual QString getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const QString &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const QString &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
friend class NotificationController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -26,6 +26,7 @@ enum class MessageFlag : uint16_t {
|
|||
Untimeout = (1 << 9),
|
||||
PubSub = (1 << 10),
|
||||
Subscription = (1 << 11),
|
||||
Notification = (1 << 12),
|
||||
};
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
|
@ -14,7 +15,10 @@
|
|||
#include "providers/twitch/TwitchParseCheerEmotes.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
|
||||
#include <IrcConnection>
|
||||
#include <QJsonArray>
|
||||
|
@ -86,6 +90,12 @@ TwitchChannel::TwitchChannel(const QString &name,
|
|||
{
|
||||
log("[TwitchChannel:{}] Opened", name);
|
||||
|
||||
this->tabHighlightRequested.connect([](HighlightState state) {});
|
||||
this->liveStatusChanged.connect([this]() {
|
||||
if (this->isLive() == 1) {
|
||||
}
|
||||
});
|
||||
|
||||
this->managedConnect(getApp()->accounts->twitch.currentUserChanged,
|
||||
[=] { this->setMod(false); });
|
||||
|
||||
|
@ -385,6 +395,30 @@ void TwitchChannel::setLive(bool newLiveStatus)
|
|||
auto guard = this->streamStatus_.access();
|
||||
if (guard->live != newLiveStatus) {
|
||||
gotNewLiveStatus = true;
|
||||
if (newLiveStatus) {
|
||||
if (getApp()->notifications->isChannelNotified(
|
||||
this->getName(), Platform::Twitch)) {
|
||||
if (Toasts::isEnabled()) {
|
||||
getApp()->toasts->sendChannelNotification(
|
||||
this->getName(), Platform::Twitch);
|
||||
}
|
||||
if (getSettings()->notificationPlaySound) {
|
||||
getApp()->notifications->playSound();
|
||||
}
|
||||
if (getSettings()->notificationFlashTaskbar) {
|
||||
QApplication::alert(
|
||||
getApp()->windows->getMainWindow().window(), 2500);
|
||||
}
|
||||
}
|
||||
auto live = makeSystemMessage(this->getName() + " is live");
|
||||
this->addMessage(live);
|
||||
this->tabHighlightRequested.invoke(
|
||||
HighlightState::Notification);
|
||||
} else {
|
||||
auto offline =
|
||||
makeSystemMessage(this->getName() + " is offline");
|
||||
this->addMessage(offline);
|
||||
}
|
||||
guard->live = newLiveStatus;
|
||||
}
|
||||
}
|
||||
|
@ -466,7 +500,6 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
|||
|
||||
{
|
||||
auto status = this->streamStatus_.access();
|
||||
status->live = true;
|
||||
status->viewerCount = stream["viewers"].GetUint();
|
||||
status->game = stream["game"].GetString();
|
||||
status->title = streamChannel["status"].GetString();
|
||||
|
@ -495,7 +528,7 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLive(true);
|
||||
// Signal all listeners that the stream status has been updated
|
||||
this->liveStatusChanged.invoke();
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
enum class HighlightState;
|
||||
|
||||
struct Emote;
|
||||
using EmotePtr = std::shared_ptr<const Emote>;
|
||||
class EmoteMap;
|
||||
|
@ -90,6 +92,7 @@ public:
|
|||
pajlada::Signals::NoArgSignal userStateChanged;
|
||||
pajlada::Signals::NoArgSignal liveStatusChanged;
|
||||
pajlada::Signals::NoArgSignal roomModesChanged;
|
||||
pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
|
||||
|
||||
protected:
|
||||
void addRecentChatter(const MessagePtr &message) override;
|
||||
|
|
|
@ -130,6 +130,8 @@ void Paths::initSubDirectories()
|
|||
this->cacheDirectory_ = makePath("Cache");
|
||||
this->messageLogDirectory = makePath("Logs");
|
||||
this->miscDirectory = makePath("Misc");
|
||||
this->twitchProfileAvatars = makePath("ProfileAvatars");
|
||||
QDir().mkdir(this->twitchProfileAvatars + "/twitch");
|
||||
}
|
||||
|
||||
Paths *getPaths()
|
||||
|
|
|
@ -28,6 +28,9 @@ public:
|
|||
// Hash of QCoreApplication::applicationFilePath()
|
||||
QString applicationFilePathHash;
|
||||
|
||||
// Profile avatars for twitch <appDataDirectory>/cache/twitch
|
||||
QString twitchProfileAvatars;
|
||||
|
||||
bool createFolder(const QString &folderPath);
|
||||
bool isPortable();
|
||||
|
||||
|
|
|
@ -152,6 +152,18 @@ public:
|
|||
|
||||
BoolSetting inlineWhispers = {"/whispers/enableInlineWhispers", true};
|
||||
|
||||
/// Notifications
|
||||
BoolSetting notificationFlashTaskbar = {"/notifications/enableFlashTaskbar",
|
||||
false};
|
||||
BoolSetting notificationPlaySound = {"/notifications/enablePlaySound",
|
||||
false};
|
||||
BoolSetting notificationCustomSound = {"/notifications/customPlaySound",
|
||||
false};
|
||||
QStringSetting notificationPathSound = {"/notifications/highlightSoundPath",
|
||||
"qrc:/sounds/ping3.wav"};
|
||||
|
||||
BoolSetting notificationToast = {"/notifications/enableToast", false};
|
||||
|
||||
/// External tools
|
||||
// Streamlink
|
||||
BoolSetting streamlinkUseCustomPath = {"/external/streamlink/useCustomPath",
|
||||
|
|
|
@ -105,6 +105,10 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
QColor("#000"),
|
||||
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
this->tabs.notified = {
|
||||
fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#F824A8"), QColor("#F824A8"), QColor("#F824A8")}};
|
||||
} else {
|
||||
this->tabs.regular = {
|
||||
QColor("#aaa"),
|
||||
|
@ -123,6 +127,10 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
QColor("#fff"),
|
||||
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
this->tabs.notified = {
|
||||
fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#F824A8"), QColor("#F824A8"), QColor("#F824A8")}};
|
||||
}
|
||||
|
||||
this->splits.input.focusedLine = highlighted;
|
||||
|
@ -150,7 +158,7 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
// QColor("#777"), QColor("#666")}};
|
||||
|
||||
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
||||
// Split
|
||||
bool flat = isLight_;
|
||||
|
@ -232,7 +240,7 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
|
||||
|
||||
this->updated.invoke();
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
||||
QColor Theme::blendColors(const QColor &color1, const QColor &color2,
|
||||
qreal ratio)
|
||||
|
|
|
@ -49,6 +49,7 @@ public:
|
|||
TabColors newMessage;
|
||||
TabColors highlighted;
|
||||
TabColors selected;
|
||||
TabColors notified;
|
||||
QColor border;
|
||||
QColor bottomLine;
|
||||
} tabs;
|
||||
|
|
192
src/singletons/Toasts.cpp
Normal file
192
src/singletons/Toasts.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
#include "Toasts.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/DownloadManager.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
# include <wintoastlib.h>
|
||||
|
||||
#endif
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QFileInfo>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
bool Toasts::isEnabled()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return WinToastLib::WinToast::isCompatible() &&
|
||||
getSettings()->notificationToast;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void Toasts::sendChannelNotification(const QString &channelName, Platform p)
|
||||
{
|
||||
// Fetch user profile avatar
|
||||
if (p == Platform::Twitch) {
|
||||
QFileInfo check_file(getPaths()->twitchProfileAvatars + "/twitch/" +
|
||||
channelName + ".png");
|
||||
if (check_file.exists() && check_file.isFile()) {
|
||||
#ifdef Q_OS_WIN
|
||||
this->sendWindowsNotification(channelName, p);
|
||||
#endif
|
||||
// OSX
|
||||
|
||||
// LINUX
|
||||
|
||||
} else {
|
||||
this->fetchChannelAvatar(
|
||||
channelName, [this, channelName, p](QString avatarLink) {
|
||||
DownloadManager *manager = new DownloadManager();
|
||||
manager->setFile(avatarLink, channelName);
|
||||
manager->connect(
|
||||
manager, &DownloadManager::downloadComplete,
|
||||
[this, channelName, p]() {
|
||||
#ifdef Q_OS_WIN
|
||||
this->sendWindowsNotification(channelName, p);
|
||||
#endif
|
||||
// OSX
|
||||
|
||||
// LINUX
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
class CustomHandler : public WinToastLib::IWinToastHandler
|
||||
{
|
||||
private:
|
||||
QString channelName_;
|
||||
Platform platform_;
|
||||
|
||||
public:
|
||||
CustomHandler(QString channelName, Platform p)
|
||||
: channelName_(channelName)
|
||||
, platform_(p)
|
||||
{
|
||||
}
|
||||
void toastActivated() const
|
||||
{
|
||||
QString link;
|
||||
if (platform_ == Platform::Twitch) {
|
||||
link = "http://www.twitch.tv/" + channelName_;
|
||||
}
|
||||
QDesktopServices::openUrl(QUrl(link));
|
||||
}
|
||||
|
||||
void toastActivated(int actionIndex) const
|
||||
{
|
||||
}
|
||||
|
||||
void toastFailed() const
|
||||
{
|
||||
}
|
||||
|
||||
void toastDismissed(WinToastDismissalReason state) const
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void Toasts::sendWindowsNotification(const QString &channelName, Platform p)
|
||||
{
|
||||
WinToastLib::WinToastTemplate templ = WinToastLib::WinToastTemplate(
|
||||
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);
|
||||
templ.setTextField(L"Click here to open in browser",
|
||||
WinToastLib::WinToastTemplate::SecondLine);
|
||||
QString Path;
|
||||
if (p == Platform::Twitch) {
|
||||
Path = getPaths()->twitchProfileAvatars + "/twitch/" + channelName +
|
||||
".png";
|
||||
}
|
||||
std::string temp_Utf8 = Path.toUtf8().constData();
|
||||
std::wstring imagePath = std::wstring(temp_Utf8.begin(), temp_Utf8.end());
|
||||
templ.setImagePath(imagePath);
|
||||
if (getSettings()->notificationPlaySound) {
|
||||
templ.setAudioOption(
|
||||
WinToastLib::WinToastTemplate::AudioOption::Silent);
|
||||
}
|
||||
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()->initialize();
|
||||
WinToastLib::WinToast::instance()->showToast(
|
||||
templ, new CustomHandler(channelName, p));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Toasts::fetchChannelAvatar(const QString channelName,
|
||||
std::function<void(QString)> successCallback)
|
||||
{
|
||||
QString requestUrl("https://api.twitch.tv/kraken/users?login=" +
|
||||
channelName);
|
||||
|
||||
NetworkRequest request(requestUrl);
|
||||
request.setCaller(QThread::currentThread());
|
||||
request.makeAuthorizedV5(getDefaultClientID());
|
||||
request.setTimeout(30000);
|
||||
request.onSuccess([successCallback](auto result) mutable -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
if (!root.value("users").isArray()) {
|
||||
// log("API Error while getting user id, users is not an array");
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
auto users = root.value("users").toArray();
|
||||
if (users.size() != 1) {
|
||||
// log("API Error while getting user id, users array size is not
|
||||
// 1");
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
if (!users[0].isObject()) {
|
||||
// log("API Error while getting user id, first user is not an
|
||||
// object");
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
auto firstUser = users[0].toObject();
|
||||
auto avatar = firstUser.value("logo");
|
||||
if (!avatar.isString()) {
|
||||
// log("API Error: while getting user avatar, first user object "
|
||||
// "`avatar` key "
|
||||
// "is not a "
|
||||
// "string");
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
successCallback(avatar.toString());
|
||||
return Success;
|
||||
});
|
||||
|
||||
request.execute();
|
||||
}
|
||||
} // namespace chatterino
|
25
src/singletons/Toasts.hpp
Normal file
25
src/singletons/Toasts.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class Platform : uint8_t;
|
||||
|
||||
class Toasts final : public Singleton
|
||||
{
|
||||
public:
|
||||
void sendChannelNotification(const QString &channelName, Platform p);
|
||||
|
||||
static bool isEnabled();
|
||||
|
||||
private:
|
||||
#ifdef Q_OS_WIN
|
||||
void sendWindowsNotification(const QString &channelName, Platform p);
|
||||
#endif
|
||||
static void fetchChannelAvatar(
|
||||
const QString channelName,
|
||||
std::function<void(QString)> successCallback);
|
||||
};
|
||||
} // namespace chatterino
|
|
@ -17,6 +17,7 @@
|
|||
#include "widgets/settingspages/LogsPage.hpp"
|
||||
#include "widgets/settingspages/LookPage.hpp"
|
||||
#include "widgets/settingspages/ModerationPage.hpp"
|
||||
#include "widgets/settingspages/NotificationPage.hpp"
|
||||
#include "widgets/settingspages/SpecialChannelsPage.hpp"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
|
@ -102,6 +103,7 @@ void SettingsDialog::addTabs()
|
|||
this->addTab(new KeyboardSettingsPage);
|
||||
// this->addTab(new LogsPage);
|
||||
this->addTab(new ModerationPage);
|
||||
this->addTab(new NotificationPage);
|
||||
// this->addTab(new SpecialChannelsPage);
|
||||
this->addTab(new BrowserExtensionPage);
|
||||
this->addTab(new ExternalToolsPage);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "messages/layouts/MessageLayout.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
@ -535,6 +536,14 @@ void ChannelView::setChannel(ChannelPtr newChannel)
|
|||
|
||||
this->layoutMessages();
|
||||
this->queueUpdate();
|
||||
|
||||
// Notifications
|
||||
TwitchChannel *tc = dynamic_cast<TwitchChannel *>(newChannel.get());
|
||||
if (tc != nullptr) {
|
||||
tc->tabHighlightRequested.connect([this](HighlightState state) {
|
||||
this->tabHighlightRequested.invoke(HighlightState::Notification);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelView::detachChannel()
|
||||
|
|
|
@ -171,8 +171,8 @@ void NotebookTab::setHighlightState(HighlightState newHighlightStyle)
|
|||
if (this->isSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->highlightState_ != HighlightState::Highlighted) {
|
||||
if (this->highlightState_ != HighlightState::Highlighted &&
|
||||
this->highlightState_ != HighlightState::Notification) {
|
||||
this->highlightState_ = newHighlightStyle;
|
||||
|
||||
this->update();
|
||||
|
@ -239,6 +239,8 @@ void NotebookTab::paintEvent(QPaintEvent *)
|
|||
colors = this->theme->tabs.selected;
|
||||
} else if (this->highlightState_ == HighlightState::Highlighted) {
|
||||
colors = this->theme->tabs.highlighted;
|
||||
} else if (this->highlightState_ == HighlightState::Notification) {
|
||||
colors = this->theme->tabs.notified;
|
||||
} else if (this->highlightState_ == HighlightState::NewMessage) {
|
||||
colors = this->theme->tabs.newMessage;
|
||||
} else {
|
||||
|
|
123
src/widgets/settingspages/NotificationPage.cpp
Normal file
123
src/widgets/settingspages/NotificationPage.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
#include "NotificationPage.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "controllers/notifications/NotificationModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QListView>
|
||||
#include <QPushButton>
|
||||
#include <QTableView>
|
||||
#include <QTimer>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
NotificationPage::NotificationPage()
|
||||
: SettingsPage("Notifications", "")
|
||||
{
|
||||
LayoutCreator<NotificationPage> layoutCreator(this);
|
||||
|
||||
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
{
|
||||
auto tabs = layout.emplace<QTabWidget>();
|
||||
{
|
||||
auto settings = tabs.appendTab(new QVBoxLayout, "Options");
|
||||
{
|
||||
settings.emplace<QLabel>("Enable for selected channels");
|
||||
settings.append(this->createCheckBox(
|
||||
"Flash taskbar",
|
||||
getSettings()->notificationFlashTaskbar));
|
||||
settings.append(this->createCheckBox(
|
||||
"Playsound (doesn't mute the Windows 8.x sound of toasts)",
|
||||
getSettings()->notificationPlaySound));
|
||||
#ifdef Q_OS_WIN
|
||||
settings.append(this->createCheckBox(
|
||||
"Enable toasts (currently only for windows 8.x or 10)",
|
||||
getSettings()->notificationToast));
|
||||
#endif
|
||||
auto customSound =
|
||||
layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
{
|
||||
customSound.append(this->createCheckBox(
|
||||
"Custom sound",
|
||||
getSettings()->notificationCustomSound));
|
||||
auto selectFile = customSound.emplace<QPushButton>(
|
||||
"Select custom sound file");
|
||||
QObject::connect(
|
||||
selectFile.getElement(), &QPushButton::clicked, this,
|
||||
[this] {
|
||||
auto fileName = QFileDialog::getOpenFileName(
|
||||
this, tr("Open Sound"), "",
|
||||
tr("Audio Files (*.mp3 *.wav)"));
|
||||
getSettings()->notificationPathSound =
|
||||
fileName;
|
||||
});
|
||||
}
|
||||
|
||||
settings->addStretch(1);
|
||||
}
|
||||
auto twitchChannels = tabs.appendTab(new QVBoxLayout, "Twitch");
|
||||
{
|
||||
EditableModelView *view =
|
||||
twitchChannels
|
||||
.emplace<EditableModelView>(
|
||||
getApp()->notifications->createModel(
|
||||
nullptr, Platform::Twitch))
|
||||
.getElement();
|
||||
view->setTitles({"Twitch channels"});
|
||||
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
0, QHeaderView::Stretch);
|
||||
|
||||
QTimer::singleShot(1, [view] {
|
||||
view->getTableView()->resizeColumnsToContents();
|
||||
view->getTableView()->setColumnWidth(0, 200);
|
||||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()
|
||||
->notifications->channelMap[Platform::Twitch]
|
||||
.appendItem("channel");
|
||||
});
|
||||
}
|
||||
/*
|
||||
auto mixerChannels = tabs.appendTab(new QVBoxLayout, "Mixer");
|
||||
{
|
||||
EditableModelView *view =
|
||||
mixerChannels
|
||||
.emplace<EditableModelView>(
|
||||
getApp()->notifications->createModel(
|
||||
nullptr, Platform::Mixer))
|
||||
.getElement();
|
||||
view->setTitles({"Mixer channels"});
|
||||
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
0, QHeaderView::Stretch);
|
||||
|
||||
QTimer::singleShot(1, [view] {
|
||||
view->getTableView()->resizeColumnsToContents();
|
||||
view->getTableView()->setColumnWidth(0, 200);
|
||||
});
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getApp()
|
||||
->notifications->channelMap[Platform::Mixer]
|
||||
.appendItem("channel");
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace chatterino
|
20
src/widgets/settingspages/NotificationPage.hpp
Normal file
20
src/widgets/settingspages/NotificationPage.hpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
class QPushButton;
|
||||
class QListWidget;
|
||||
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NotificationPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
NotificationPage();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
|
@ -211,6 +212,22 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
|||
&Split::openBrowserPlayer);
|
||||
#endif
|
||||
menu->addAction("Open streamlink", this->split_, &Split::openInStreamlink);
|
||||
|
||||
auto action = new QAction(this);
|
||||
action->setText("Notify when live");
|
||||
action->setCheckable(true);
|
||||
|
||||
QObject::connect(menu.get(), &QMenu::aboutToShow, this, [action, this]() {
|
||||
action->setChecked(getApp()->notifications->isChannelNotified(
|
||||
this->split_->getChannel()->getName(), Platform::Twitch));
|
||||
});
|
||||
action->connect(action, &QAction::triggered, this, [this]() {
|
||||
getApp()->notifications->updateChannelNotification(
|
||||
this->split_->getChannel()->getName(), Platform::Twitch);
|
||||
});
|
||||
|
||||
menu->addAction(action);
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addAction("Reload channel emotes", this, SLOT(reloadChannelEmotes()));
|
||||
menu->addAction("Reconnect", this, SLOT(reconnect()));
|
||||
|
|
Loading…
Reference in a new issue