Rework the Account Popup Widget

Fixed Account Popup Widget Follow/Unfollow
Ignoring now also works, but doesn't have the ability to unignore

Add a URL Delete method to the network manager

Fixes #235
This commit is contained in:
Rasmus Karlsson 2018-01-18 18:20:40 +01:00
parent 1e7d3a2ec6
commit 702d4b2eec
10 changed files with 268 additions and 78 deletions

View file

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>461</width> <width>469</width>
<height>301</height> <height>301</height>
</rect> </rect>
</property> </property>
@ -189,6 +189,9 @@
</property> </property>
<item> <item>
<widget class="QPushButton" name="follow"> <widget class="QPushButton" name="follow">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>Follow</string> <string>Follow</string>
</property> </property>
@ -196,6 +199,9 @@
</item> </item>
<item> <item>
<widget class="QPushButton" name="ignore"> <widget class="QPushButton" name="ignore">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text"> <property name="text">
<string>Ignore</string> <string>Ignore</string>
</property> </property>

View file

@ -17,5 +17,5 @@ public:
twitch::TwitchAccountManager Twitch; twitch::TwitchAccountManager Twitch;
}; };
} // namespace singletons
} // namespace chatterino } // namespace chatterino
}

View file

@ -186,5 +186,6 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
return AddUserResponse::UserAdded; return AddUserResponse::UserAdded;
} }
}
} } // namespace twitch
} // namespace chatterino

View file

@ -63,5 +63,6 @@ private:
friend class chatterino::singletons::AccountManager; friend class chatterino::singletons::AccountManager;
}; };
}
} } // namespace twitch
} // namespace chatterino

View file

@ -159,6 +159,28 @@ public:
emit requester.requestUrl(); emit requester.requestUrl();
} }
template <typename FinishedCallback>
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 class NetworkRequest

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "singletons/accountmanager.hpp"
#include "credentials.hpp" #include "credentials.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "singletons/accountmanager.hpp"
#include "util/networkmanager.hpp" #include "util/networkmanager.hpp"
#include <rapidjson/document.h> #include <rapidjson/document.h>
@ -160,6 +160,36 @@ static void put(QUrl url, std::function<void(QJsonObject)> successCallback)
}); });
} }
static void sendDelete(QUrl url, std::function<void()> 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 twitch
} // namespace util } // namespace util
} // namespace chatterino } // namespace chatterino

View file

@ -31,9 +31,29 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->resize(0, 0); 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(); singletons::SettingManager &settings = singletons::SettingManager::getInstance();
this->permission = permissions::User;
for (auto button : this->ui->profileLayout->findChildren<QPushButton *>()) { for (auto button : this->ui->profileLayout->findChildren<QPushButton *>()) {
button->setFocusProxy(this); button->setFocusProxy(this);
} }
@ -58,17 +78,8 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->sendCommand(this->ui->mod, "/mod "); this->sendCommand(this->ui->mod, "/mod ");
this->sendCommand(this->ui->unMod, "/unmod "); 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, [=]() { 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, [=]() { QObject::connect(this->ui->sendMessage, &QPushButton::clicked, this, [=]() {
@ -80,17 +91,41 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
[=]() { QApplication::clipboard()->setText(this->ui->lblUsername->text()); }); [=]() { QApplication::clipboard()->setText(this->ui->lblUsername->text()); });
QObject::connect(this->ui->follow, &QPushButton::clicked, this, [=]() { QObject::connect(this->ui->follow, &QPushButton::clicked, this, [=]() {
QUrl requestUrl("https://api.twitch.tv/kraken/users/" + userId + "/follows/channels/" + debug::Log("Attempt to toggle follow user {}({}) as user {}({})",
this->userID); this->popupWidgetUser.username, this->popupWidgetUser.userID,
this->loggedInUser.username, this->loggedInUser.userID);
util::twitch::put(requestUrl, QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->loggedInUser.userID +
[](QJsonObject obj) { qDebug() << "follows channel: " << obj; }); "/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, [=]() { 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]() { QObject::connect(this->ui->disableHighlights, &QPushButton::clicked, this, [=, &settings]() {
@ -119,16 +154,32 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->hide(); // this->hide(); //
}); });
util::twitch::getUserID(userNickname, this,
[=](const QString &id) { currentTwitchUser->setUserId(id); });
this->dpiMultiplierChanged(this->getDpiMultiplier(), this->getDpiMultiplier()); this->dpiMultiplierChanged(this->getDpiMultiplier(), this->getDpiMultiplier());
} }
void AccountPopupWidget::setName(const QString &name) void AccountPopupWidget::setName(const QString &name)
{ {
this->relationship.following = false;
this->relationship.ignoring = false;
this->popupWidgetUser.username = name;
this->ui->lblUsername->setText(name); this->ui->lblUsername->setText(name);
this->getUserId(); 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) void AccountPopupWidget::setChannel(SharedChannel _channel)
@ -138,8 +189,8 @@ void AccountPopupWidget::setChannel(SharedChannel _channel)
void AccountPopupWidget::getUserId() void AccountPopupWidget::getUserId()
{ {
util::twitch::getUserID(this->ui->lblUsername->text(), this, [=](const QString &id) { util::twitch::getUserID(this->popupWidgetUser.username, this, [=](const QString &id) {
userID = id; this->popupWidgetUser.userID = id;
this->getUserData(); this->getUserData();
}); });
} }
@ -147,18 +198,31 @@ void AccountPopupWidget::getUserId()
void AccountPopupWidget::getUserData() void AccountPopupWidget::getUserData()
{ {
util::twitch::get( 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->lblFollowers->setText(QString::number(obj.value("followers").toInt()));
this->ui->lblViews->setText(QString::number(obj.value("views").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->ui->lblAccountAge->setText(obj.value("created_at").toString().section("T", 0, 0));
this->loadAvatar(QUrl(obj.value("logo").toString())); 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) 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()) { if (!avatarUrl.isEmpty()) {
QNetworkRequest req(avatarUrl); QNetworkRequest req(avatarUrl);
static auto manager = new QNetworkAccessManager(); static auto manager = new QNetworkAccessManager();
@ -168,7 +232,7 @@ void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl)
if (reply->error() == QNetworkReply::NoError) { if (reply->error() == QNetworkReply::NoError) {
const auto data = reply->readAll(); const auto data = reply->readAll();
this->avatar.loadFromData(data); 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); this->ui->lblAvatar->setPixmap(this->avatar);
} else { } else {
this->ui->lblAvatar->setText("ERROR"); 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) void AccountPopupWidget::dpiMultiplierChanged(float /*oldDpi*/, float newDpi)
{ {
this->setStyleSheet(QString("* { font-size: <font-size>px; }") this->setStyleSheet(QString("* { font-size: <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 *) void AccountPopupWidget::focusOutEvent(QFocusEvent *)
{ {
this->hide(); this->hide();
@ -242,29 +366,16 @@ void AccountPopupWidget::focusOutEvent(QFocusEvent *)
void AccountPopupWidget::showEvent(QShowEvent *) void AccountPopupWidget::showEvent(QShowEvent *)
{ {
singletons::AccountManager &accountManager = singletons::AccountManager::getInstance(); this->loggedInUser.refreshUserType(this->channel, true);
auto currentTwitchUser = accountManager.Twitch.getCurrent(); this->popupWidgetUser.refreshUserType(this->channel, false);
if (!currentTwitchUser) {
// No twitch user set (should never happen)
return;
}
if (this->ui->lblUsername->text() != currentTwitchUser->getNickName()) { this->ui->follow->setEnabled(false);
this->updateButtons(this->ui->userLayout, true); // XXX: Uncomment when ignore/unignore is fully implemented
if (this->permission != permissions::User) { // this->ui->ignore->setEnabled(false);
if (!this->channel->modList.contains(this->ui->lblUsername->text())) {
this->updateButtons(this->ui->modLayout, true); this->refreshButtons();
}
if (this->permission == permissions::Owner) { this->refreshLayouts();
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);
}
QString blacklisted = singletons::SettingManager::getInstance().highlightUserBlacklist; QString blacklisted = singletons::SettingManager::getInstance().highlightUserBlacklist;
QStringList list = blacklisted.split("\n", QString::SkipEmptyParts); QStringList list = blacklisted.split("\n", QString::SkipEmptyParts);

View file

@ -28,7 +28,11 @@ public:
void setName(const QString &name); void setName(const QString &name);
void setChannel(SharedChannel _channel); void setChannel(SharedChannel _channel);
void updatePermissions(); public slots:
void actuallyRefreshButtons();
signals:
void refreshButtons();
protected: protected:
virtual void dpiMultiplierChanged(float oldDpi, float newDpi) override; virtual void dpiMultiplierChanged(float oldDpi, float newDpi) override;
@ -44,16 +48,33 @@ private:
void timeout(QPushButton *button, int time); void timeout(QPushButton *button, int time);
void sendCommand(QPushButton *button, QString command); void sendCommand(QPushButton *button, QString command);
enum class permissions { User, Mod, Owner }; void refreshLayouts();
permissions permission;
enum class UserType { User, Mod, Owner };
SharedChannel channel; SharedChannel channel;
QString userID;
QPixmap avatar; QPixmap avatar;
util::ConcurrentMap<QString, QPixmap> avatarMap; util::ConcurrentMap<QString, QPixmap> 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: protected:
virtual void focusOutEvent(QFocusEvent *event) override; virtual void focusOutEvent(QFocusEvent *event) override;
virtual void showEvent(QShowEvent *event) override; virtual void showEvent(QShowEvent *event) override;

View file

@ -787,7 +787,6 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
auto user = link.getValue(); auto user = link.getValue();
this->userPopupWidget.setName(user); this->userPopupWidget.setName(user);
this->userPopupWidget.move(event->screenPos().toPoint()); this->userPopupWidget.move(event->screenPos().toPoint());
this->userPopupWidget.updatePermissions();
this->userPopupWidget.show(); this->userPopupWidget.show();
this->userPopupWidget.setFocus(); this->userPopupWidget.setFocus();

View file

@ -539,7 +539,6 @@ void Split::doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user)
{ {
widget->setName(user); widget->setName(user);
widget->move(QCursor::pos()); widget->move(QCursor::pos());
widget->updatePermissions();
widget->show(); widget->show();
widget->setFocus(); widget->setFocus();
} }