diff --git a/forms/accountpopupform.ui b/forms/accountpopupform.ui
index 969e73e8e..d0467ad9f 100644
--- a/forms/accountpopupform.ui
+++ b/forms/accountpopupform.ui
@@ -6,7 +6,7 @@
0
0
- 461
+ 469
301
@@ -189,6 +189,9 @@
-
+
+ false
+
Follow
@@ -196,6 +199,9 @@
-
+
+ true
+
Ignore
diff --git a/src/singletons/accountmanager.hpp b/src/singletons/accountmanager.hpp
index b80d79a8a..d72424295 100644
--- a/src/singletons/accountmanager.hpp
+++ b/src/singletons/accountmanager.hpp
@@ -17,5 +17,5 @@ public:
twitch::TwitchAccountManager Twitch;
};
+} // namespace singletons
} // namespace chatterino
-}
diff --git a/src/twitch/twitchaccountmanager.cpp b/src/twitch/twitchaccountmanager.cpp
index 70ad8d0a7..f5bb57ddc 100644
--- a/src/twitch/twitchaccountmanager.cpp
+++ b/src/twitch/twitchaccountmanager.cpp
@@ -186,5 +186,6 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
return AddUserResponse::UserAdded;
}
-}
-}
+
+} // namespace twitch
+} // namespace chatterino
diff --git a/src/twitch/twitchaccountmanager.hpp b/src/twitch/twitchaccountmanager.hpp
index 191a86b40..535f3e1c2 100644
--- a/src/twitch/twitchaccountmanager.hpp
+++ b/src/twitch/twitchaccountmanager.hpp
@@ -63,5 +63,6 @@ private:
friend class chatterino::singletons::AccountManager;
};
-}
-}
+
+} // namespace twitch
+} // namespace chatterino
diff --git a/src/util/networkmanager.hpp b/src/util/networkmanager.hpp
index 5ea0a3b3a..dc43c44ff 100644
--- a/src/util/networkmanager.hpp
+++ b/src/util/networkmanager.hpp
@@ -159,6 +159,28 @@ public:
emit requester.requestUrl();
}
+
+ template
+ static void urlDelete(QNetworkRequest request, FinishedCallback onFinished)
+ {
+ NetworkRequester requester;
+ NetworkWorker *worker = new NetworkWorker;
+
+ worker->moveToThread(&NetworkManager::workerThread);
+ QObject::connect(
+ &requester, &NetworkRequester::requestUrl, worker,
+ [ onFinished = std::move(onFinished), request = std::move(request), worker ]() {
+ QNetworkReply *reply = NetworkManager::NaM.deleteResource(request);
+
+ QObject::connect(reply, &QNetworkReply::finished,
+ [ onFinished = std::move(onFinished), reply, worker ]() {
+ onFinished(reply);
+ delete worker;
+ });
+ });
+
+ emit requester.requestUrl();
+ }
};
class NetworkRequest
diff --git a/src/util/urlfetch.hpp b/src/util/urlfetch.hpp
index f2bb884bd..a9b180e85 100644
--- a/src/util/urlfetch.hpp
+++ b/src/util/urlfetch.hpp
@@ -1,8 +1,8 @@
#pragma once
-#include "singletons/accountmanager.hpp"
#include "credentials.hpp"
#include "debug/log.hpp"
+#include "singletons/accountmanager.hpp"
#include "util/networkmanager.hpp"
#include
@@ -160,6 +160,36 @@ static void put(QUrl url, std::function successCallback)
});
}
+static void sendDelete(QUrl url, std::function successCallback)
+{
+ QNetworkRequest request(url);
+
+ auto &accountManager = singletons::AccountManager::getInstance();
+ auto currentTwitchUser = accountManager.Twitch.getCurrent();
+ QByteArray oauthToken;
+ if (currentTwitchUser) {
+ oauthToken = currentTwitchUser->getOAuthToken().toUtf8();
+ } else {
+ // XXX(pajlada): Bail out?
+ }
+
+ request.setRawHeader("Client-ID", getDefaultClientID());
+ request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
+ request.setRawHeader("Authorization", "OAuth " + oauthToken);
+
+ NetworkManager::urlDelete(std::move(request), [=](QNetworkReply *reply) {
+ if (reply->error() == QNetworkReply::NetworkError::NoError) {
+ int code =
+ reply->attribute(QNetworkRequest::Attribute::HttpStatusCodeAttribute).toInt();
+
+ if (code >= 200 && code <= 299) {
+ successCallback();
+ }
+ }
+ reply->deleteLater();
+ });
+}
+
} // namespace twitch
} // namespace util
} // namespace chatterino
diff --git a/src/widgets/accountpopup.cpp b/src/widgets/accountpopup.cpp
index d99fe3955..78020a8cf 100644
--- a/src/widgets/accountpopup.cpp
+++ b/src/widgets/accountpopup.cpp
@@ -31,9 +31,29 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->resize(0, 0);
+ auto &accountManager = singletons::AccountManager::getInstance();
+
+ connect(this, &AccountPopupWidget::refreshButtons, this,
+ &AccountPopupWidget::actuallyRefreshButtons, Qt::QueuedConnection);
+
+ accountManager.Twitch.userChanged.connect([this] {
+ singletons::AccountManager &accountManager = singletons::AccountManager::getInstance();
+
+ auto currentTwitchUser = accountManager.Twitch.getCurrent();
+ if (!currentTwitchUser) {
+ // No twitch user set (should never happen)
+ return;
+ }
+
+ this->loggedInUser.username = currentTwitchUser->getUserName();
+ this->loggedInUser.userID = currentTwitchUser->getUserId();
+
+ this->loggedInUser.refreshUserType(this->channel, true);
+
+ });
+
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
- this->permission = permissions::User;
for (auto button : this->ui->profileLayout->findChildren()) {
button->setFocusProxy(this);
}
@@ -58,17 +78,8 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->sendCommand(this->ui->mod, "/mod ");
this->sendCommand(this->ui->unMod, "/unmod ");
- auto &accountManager = singletons::AccountManager::getInstance();
- QString userId;
- QString userNickname;
- auto currentTwitchUser = accountManager.Twitch.getCurrent();
- if (currentTwitchUser) {
- userId = currentTwitchUser->getUserId();
- userNickname = currentTwitchUser->getNickName();
- }
-
QObject::connect(this->ui->profile, &QPushButton::clicked, this, [=]() {
- QDesktopServices::openUrl(QUrl("https://twitch.tv/" + this->ui->lblUsername->text()));
+ QDesktopServices::openUrl(QUrl("https://twitch.tv/" + this->popupWidgetUser.username));
});
QObject::connect(this->ui->sendMessage, &QPushButton::clicked, this, [=]() {
@@ -80,17 +91,41 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
[=]() { QApplication::clipboard()->setText(this->ui->lblUsername->text()); });
QObject::connect(this->ui->follow, &QPushButton::clicked, this, [=]() {
- QUrl requestUrl("https://api.twitch.tv/kraken/users/" + userId + "/follows/channels/" +
- this->userID);
+ debug::Log("Attempt to toggle follow user {}({}) as user {}({})",
+ this->popupWidgetUser.username, this->popupWidgetUser.userID,
+ this->loggedInUser.username, this->loggedInUser.userID);
- util::twitch::put(requestUrl,
- [](QJsonObject obj) { qDebug() << "follows channel: " << obj; });
+ QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->loggedInUser.userID +
+ "/follows/channels/" + this->popupWidgetUser.userID);
+
+ this->ui->follow->setEnabled(false);
+ if (!this->relationship.following) {
+ util::twitch::put(requestUrl, [this](QJsonObject obj) {
+ qDebug() << "follows channel: " << obj;
+ this->relationship.following = true;
+ emit refreshButtons();
+ });
+ } else {
+ util::twitch::sendDelete(requestUrl, [this] {
+ this->relationship.following = false;
+ emit refreshButtons();
+ });
+ }
});
QObject::connect(this->ui->ignore, &QPushButton::clicked, this, [=]() {
- QUrl requestUrl("https://api.twitch.tv/kraken/users/" + userId + "/blocks/" + this->userID);
+ QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->loggedInUser.userID +
+ "/blocks/" + this->popupWidgetUser.userID);
- util::twitch::put(requestUrl, [](QJsonObject obj) { qDebug() << "blocks user: " << obj; });
+ if (!this->relationship.ignoring) {
+ util::twitch::put(requestUrl, [this](auto) {
+ this->relationship.ignoring = true; //
+ });
+ } else {
+ util::twitch::sendDelete(requestUrl, [this] {
+ this->relationship.ignoring = false; //
+ });
+ }
});
QObject::connect(this->ui->disableHighlights, &QPushButton::clicked, this, [=, &settings]() {
@@ -119,16 +154,32 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->hide(); //
});
- util::twitch::getUserID(userNickname, this,
- [=](const QString &id) { currentTwitchUser->setUserId(id); });
-
this->dpiMultiplierChanged(this->getDpiMultiplier(), this->getDpiMultiplier());
}
void AccountPopupWidget::setName(const QString &name)
{
+ this->relationship.following = false;
+ this->relationship.ignoring = false;
+
+ this->popupWidgetUser.username = name;
this->ui->lblUsername->setText(name);
this->getUserId();
+
+ // Refresh popup widget users type
+
+ this->popupWidgetUser.refreshUserType(this->channel, false);
+}
+
+void AccountPopupWidget::User::refreshUserType(const SharedChannel &channel, bool loggedInUser)
+{
+ if (channel->name == this->username) {
+ this->userType = UserType::Owner;
+ } else if ((loggedInUser && channel->isMod()) || channel->modList.contains(this->username)) {
+ this->userType = UserType::Mod;
+ } else {
+ this->userType = UserType::User;
+ }
}
void AccountPopupWidget::setChannel(SharedChannel _channel)
@@ -138,8 +189,8 @@ void AccountPopupWidget::setChannel(SharedChannel _channel)
void AccountPopupWidget::getUserId()
{
- util::twitch::getUserID(this->ui->lblUsername->text(), this, [=](const QString &id) {
- userID = id;
+ util::twitch::getUserID(this->popupWidgetUser.username, this, [=](const QString &id) {
+ this->popupWidgetUser.userID = id;
this->getUserData();
});
}
@@ -147,18 +198,31 @@ void AccountPopupWidget::getUserId()
void AccountPopupWidget::getUserData()
{
util::twitch::get(
- "https://api.twitch.tv/kraken/channels/" + this->userID, this, [=](const QJsonObject &obj) {
+ "https://api.twitch.tv/kraken/channels/" + this->popupWidgetUser.userID, this,
+ [=](const QJsonObject &obj) {
this->ui->lblFollowers->setText(QString::number(obj.value("followers").toInt()));
this->ui->lblViews->setText(QString::number(obj.value("views").toInt()));
this->ui->lblAccountAge->setText(obj.value("created_at").toString().section("T", 0, 0));
this->loadAvatar(QUrl(obj.value("logo").toString()));
});
+
+ util::twitch::get("https://api.twitch.tv/kraken/users/" + this->loggedInUser.userID +
+ "/follows/channels/" + this->popupWidgetUser.userID,
+ this, [=](const QJsonObject &obj) {
+ this->ui->follow->setEnabled(true);
+ this->relationship.following = obj.contains("channel");
+
+ emit refreshButtons();
+ });
+
+ // TODO: Get ignore relationship between logged in user and popup widget user and update
+ // relationship.ignoring
}
void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl)
{
- if (!this->avatarMap.tryGet(this->userID, this->avatar)) {
+ if (!this->avatarMap.tryGet(this->popupWidgetUser.userID, this->avatar)) {
if (!avatarUrl.isEmpty()) {
QNetworkRequest req(avatarUrl);
static auto manager = new QNetworkAccessManager();
@@ -168,7 +232,7 @@ void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl)
if (reply->error() == QNetworkReply::NoError) {
const auto data = reply->readAll();
this->avatar.loadFromData(data);
- this->avatarMap.insert(this->userID, this->avatar);
+ this->avatarMap.insert(this->popupWidgetUser.userID, this->avatar);
this->ui->lblAvatar->setPixmap(this->avatar);
} else {
this->ui->lblAvatar->setText("ERROR");
@@ -182,24 +246,6 @@ void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl)
}
}
-void AccountPopupWidget::updatePermissions()
-{
- singletons::AccountManager &accountManager = singletons::AccountManager::getInstance();
-
- auto currentTwitchUser = accountManager.Twitch.getCurrent();
- if (!currentTwitchUser) {
- // No twitch user set (should never happen)
- return;
- }
-
- if (this->channel.get()->name == currentTwitchUser->getNickName()) {
- this->permission = permissions::Owner;
- } else if (this->channel->modList.contains(currentTwitchUser->getNickName())) {
- // XXX(pajlada): This might always trigger if user is anonymous (if nickName is empty?)
- this->permission = permissions::Mod;
- }
-}
-
void AccountPopupWidget::dpiMultiplierChanged(float /*oldDpi*/, float newDpi)
{
this->setStyleSheet(QString("* { font-size: px; }")
@@ -230,6 +276,84 @@ void AccountPopupWidget::sendCommand(QPushButton *button, QString command)
});
}
+void AccountPopupWidget::refreshLayouts()
+{
+ singletons::AccountManager &accountManager = singletons::AccountManager::getInstance();
+ auto currentTwitchUser = accountManager.Twitch.getCurrent();
+ if (!currentTwitchUser) {
+ // No twitch user set (should never happen)
+ return;
+ }
+
+ QString loggedInUsername = currentTwitchUser->getUserName();
+ QString popupUsername = this->ui->lblUsername->text();
+
+ bool showModLayout = false;
+ bool showUserLayout = false;
+ bool showOwnerLayout = false;
+
+ if (loggedInUsername == popupUsername) {
+ // Clicked user is the same as the logged in user
+ showModLayout = false;
+ showUserLayout = false;
+ showOwnerLayout = false;
+ } else {
+ showUserLayout = true;
+
+ switch (this->loggedInUser.userType) {
+ case UserType::Mod: {
+ showModLayout = true;
+ } break;
+
+ case UserType::Owner: {
+ showModLayout = true;
+ showOwnerLayout = true;
+ } break;
+ }
+ }
+
+ if (this->popupWidgetUser.userType == UserType::Owner) {
+ showModLayout = false;
+ showOwnerLayout = false;
+ }
+
+ if (this->popupWidgetUser.userType == UserType::Mod &&
+ this->loggedInUser.userType != UserType::Owner) {
+ showModLayout = false;
+ }
+
+ this->updateButtons(this->ui->modLayout, showModLayout);
+ this->updateButtons(this->ui->userLayout, showUserLayout);
+ this->updateButtons(this->ui->ownerLayout, showOwnerLayout);
+}
+
+void AccountPopupWidget::actuallyRefreshButtons()
+{
+ if (this->relationship.following) {
+ if (this->ui->follow->text() != "Unfollow") {
+ this->ui->follow->setText("Unfollow");
+ this->ui->follow->setEnabled(true);
+ }
+ } else {
+ if (this->ui->follow->text() != "Follow") {
+ this->ui->follow->setText("Follow");
+ this->ui->follow->setEnabled(true);
+ }
+ }
+
+ if (this->relationship.ignoring) {
+ if (this->ui->ignore->text() != "Unignore") {
+ this->ui->ignore->setText("Unignore");
+ this->ui->ignore->setEnabled(true);
+ }
+ } else {
+ if (this->ui->ignore->text() != "Ignore") {
+ this->ui->ignore->setText("Ignore");
+ this->ui->ignore->setEnabled(true);
+ }
+ }
+}
+
void AccountPopupWidget::focusOutEvent(QFocusEvent *)
{
this->hide();
@@ -242,29 +366,16 @@ void AccountPopupWidget::focusOutEvent(QFocusEvent *)
void AccountPopupWidget::showEvent(QShowEvent *)
{
- singletons::AccountManager &accountManager = singletons::AccountManager::getInstance();
- auto currentTwitchUser = accountManager.Twitch.getCurrent();
- if (!currentTwitchUser) {
- // No twitch user set (should never happen)
- return;
- }
+ this->loggedInUser.refreshUserType(this->channel, true);
+ this->popupWidgetUser.refreshUserType(this->channel, false);
- if (this->ui->lblUsername->text() != currentTwitchUser->getNickName()) {
- this->updateButtons(this->ui->userLayout, true);
- if (this->permission != permissions::User) {
- if (!this->channel->modList.contains(this->ui->lblUsername->text())) {
- this->updateButtons(this->ui->modLayout, true);
- }
- if (this->permission == permissions::Owner) {
- this->updateButtons(this->ui->ownerLayout, true);
- this->updateButtons(this->ui->modLayout, true);
- }
- }
- } else {
- this->updateButtons(this->ui->modLayout, false);
- this->updateButtons(this->ui->userLayout, false);
- this->updateButtons(this->ui->ownerLayout, false);
- }
+ this->ui->follow->setEnabled(false);
+ // XXX: Uncomment when ignore/unignore is fully implemented
+ // this->ui->ignore->setEnabled(false);
+
+ this->refreshButtons();
+
+ this->refreshLayouts();
QString blacklisted = singletons::SettingManager::getInstance().highlightUserBlacklist;
QStringList list = blacklisted.split("\n", QString::SkipEmptyParts);
diff --git a/src/widgets/accountpopup.hpp b/src/widgets/accountpopup.hpp
index 6d53bd3a0..e5bae7d1a 100644
--- a/src/widgets/accountpopup.hpp
+++ b/src/widgets/accountpopup.hpp
@@ -28,7 +28,11 @@ public:
void setName(const QString &name);
void setChannel(SharedChannel _channel);
- void updatePermissions();
+public slots:
+ void actuallyRefreshButtons();
+
+signals:
+ void refreshButtons();
protected:
virtual void dpiMultiplierChanged(float oldDpi, float newDpi) override;
@@ -44,16 +48,33 @@ private:
void timeout(QPushButton *button, int time);
void sendCommand(QPushButton *button, QString command);
- enum class permissions { User, Mod, Owner };
- permissions permission;
+ void refreshLayouts();
+
+ enum class UserType { User, Mod, Owner };
SharedChannel channel;
- QString userID;
QPixmap avatar;
util::ConcurrentMap avatarMap;
+ struct User {
+ QString username;
+ QString userID;
+ UserType userType = UserType::User;
+
+ void refreshUserType(const SharedChannel &channel, bool loggedInUser);
+ };
+
+ User loggedInUser;
+
+ User popupWidgetUser;
+
+ struct {
+ bool following = false;
+ bool ignoring = false;
+ } relationship;
+
protected:
virtual void focusOutEvent(QFocusEvent *event) override;
virtual void showEvent(QShowEvent *event) override;
diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp
index 998d48a45..6f691502c 100644
--- a/src/widgets/helper/channelview.cpp
+++ b/src/widgets/helper/channelview.cpp
@@ -787,7 +787,6 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
auto user = link.getValue();
this->userPopupWidget.setName(user);
this->userPopupWidget.move(event->screenPos().toPoint());
- this->userPopupWidget.updatePermissions();
this->userPopupWidget.show();
this->userPopupWidget.setFocus();
diff --git a/src/widgets/split.cpp b/src/widgets/split.cpp
index 55d29fa94..30085eae3 100644
--- a/src/widgets/split.cpp
+++ b/src/widgets/split.cpp
@@ -539,7 +539,6 @@ void Split::doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user)
{
widget->setName(user);
widget->move(QCursor::pos());
- widget->updatePermissions();
widget->show();
widget->setFocus();
}