mirror-chatterino2/src/widgets/dialogs/UserInfoPopup.cpp

635 lines
21 KiB
C++
Raw Normal View History

2018-06-26 14:09:39 +02:00
#include "UserInfoPopup.hpp"
2018-06-06 13:35:06 +02:00
2018-06-26 14:09:39 +02:00
#include "Application.hpp"
#include "common/Channel.hpp"
2018-07-15 14:11:46 +02:00
#include "common/NetworkRequest.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "messages/Message.hpp"
#include "providers/twitch/PartialTwitchUser.hpp"
2018-06-26 14:09:39 +02:00
#include "providers/twitch/TwitchChannel.hpp"
2018-06-28 19:46:45 +02:00
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
2018-06-26 14:09:39 +02:00
#include "util/LayoutCreator.hpp"
#include "util/PostToThread.hpp"
2018-06-26 17:20:03 +02:00
#include "widgets/Label.hpp"
#include "widgets/dialogs/LogsPopup.hpp"
#include "widgets/helper/ChannelView.hpp"
2018-08-08 15:35:54 +02:00
#include "widgets/helper/EffectLabel.hpp"
2018-06-26 14:09:39 +02:00
#include "widgets/helper/Line.hpp"
2018-06-06 13:35:06 +02:00
#include <QCheckBox>
#include <QDesktopServices>
#include <QLabel>
2018-08-02 14:23:27 +02:00
#include <QNetworkAccessManager>
#include <QNetworkReply>
2018-06-06 13:35:06 +02:00
#define TEXT_FOLLOWERS "Followers: "
#define TEXT_VIEWS "Views: "
#define TEXT_CREATED "Created: "
2018-06-06 13:35:06 +02:00
namespace chatterino {
UserInfoPopup::UserInfoPopup()
2018-08-06 21:17:03 +02:00
: BaseWindow(nullptr, BaseWindow::Flags(BaseWindow::Frameless |
BaseWindow::FramelessDraggable))
, hack_(new bool)
2018-06-06 13:35:06 +02:00
{
2018-06-19 18:55:45 +02:00
this->setStayInScreenRect(true);
#ifdef Q_OS_LINUX
this->setWindowFlag(Qt::Popup);
#endif
2018-06-06 13:35:06 +02:00
auto app = getApp();
2018-08-06 21:17:03 +02:00
auto layout =
LayoutCreator<UserInfoPopup>(this).setLayoutType<QVBoxLayout>();
2018-06-06 13:35:06 +02:00
// first line
auto head = layout.emplace<QHBoxLayout>().withoutMargin();
{
// avatar
2018-08-08 15:35:54 +02:00
auto avatar =
head.emplace<Button>(nullptr).assign(&this->ui_.avatarButton);
2018-06-11 21:57:17 +02:00
avatar->setScaleIndependantSize(100, 100);
QObject::connect(avatar.getElement(), &Button::leftClicked, [this] {
2018-08-08 15:35:54 +02:00
QDesktopServices::openUrl(
QUrl("https://twitch.tv/" + this->userName_.toLower()));
2018-08-08 15:35:54 +02:00
});
2018-06-06 13:35:06 +02:00
// items on the right
auto vbox = head.emplace<QVBoxLayout>();
{
2018-06-11 21:57:17 +02:00
auto name = vbox.emplace<Label>().assign(&this->ui_.nameLabel);
2018-06-06 13:35:06 +02:00
auto font = name->font();
font.setBold(true);
name->setFont(font);
2018-06-11 21:57:17 +02:00
vbox.emplace<Label>(TEXT_VIEWS).assign(&this->ui_.viewCountLabel);
2018-08-06 21:17:03 +02:00
vbox.emplace<Label>(TEXT_FOLLOWERS)
.assign(&this->ui_.followerCountLabel);
vbox.emplace<Label>(TEXT_CREATED)
.assign(&this->ui_.createdDateLabel);
2018-06-06 13:35:06 +02:00
}
}
layout.emplace<Line>(false);
// second line
auto user = layout.emplace<QHBoxLayout>().withoutMargin();
{
user->addStretch(1);
user.emplace<QCheckBox>("Follow").assign(&this->ui_.follow);
user.emplace<QCheckBox>("Ignore").assign(&this->ui_.ignore);
2018-08-06 21:17:03 +02:00
user.emplace<QCheckBox>("Ignore highlights")
.assign(&this->ui_.ignoreHighlights);
2018-08-08 15:35:54 +02:00
auto viewLogs = user.emplace<EffectLabel2>(this);
viewLogs->getLabel().setText("Online logs");
auto usercard = user.emplace<EffectLabel2>(this);
usercard->getLabel().setText("Usercard");
2018-06-06 13:35:06 +02:00
2018-08-08 15:35:54 +02:00
auto mod = user.emplace<Button>(this);
2018-06-06 13:35:06 +02:00
mod->setPixmap(app->resources->buttons.mod);
mod->setScaleIndependantSize(30, 30);
2018-08-08 15:35:54 +02:00
auto unmod = user.emplace<Button>(this);
2018-06-06 13:35:06 +02:00
unmod->setPixmap(app->resources->buttons.unmod);
unmod->setScaleIndependantSize(30, 30);
user->addStretch(1);
QObject::connect(viewLogs.getElement(), &Button::leftClicked, [this] {
2018-08-08 15:35:54 +02:00
auto logs = new LogsPopup();
logs->setChannel(this->channel_);
logs->setTargetUserName(this->userName_);
logs->getLogs();
2018-08-08 15:35:54 +02:00
logs->setAttribute(Qt::WA_DeleteOnClose);
logs->show();
});
QObject::connect(usercard.getElement(), &Button::leftClicked, [this] {
QDesktopServices::openUrl("https://www.twitch.tv/popout/" +
this->channel_->getName() +
"/viewercard/" + this->userName_);
});
QObject::connect(mod.getElement(), &Button::leftClicked, [this] {
2018-08-08 15:35:54 +02:00
this->channel_->sendMessage("/mod " + this->userName_);
});
QObject::connect(unmod.getElement(), &Button::leftClicked, [this] {
2018-08-08 15:35:54 +02:00
this->channel_->sendMessage("/unmod " + this->userName_);
});
2018-06-19 18:55:45 +02:00
// userstate
2018-07-06 19:23:47 +02:00
this->userStateChanged_.connect([this, mod, unmod]() mutable {
2018-08-06 21:17:03 +02:00
TwitchChannel *twitchChannel =
dynamic_cast<TwitchChannel *>(this->channel_.get());
bool visibilityMod = false;
bool visibilityUnmod = false;
2018-10-21 13:43:02 +02:00
if (twitchChannel)
{
qDebug() << this->userName_;
bool isMyself =
2018-08-06 21:17:03 +02:00
QString::compare(
getApp()->accounts->twitch.getCurrent()->getUserName(),
this->userName_, Qt::CaseInsensitive) == 0;
visibilityMod = twitchChannel->isBroadcaster() && !isMyself;
visibilityUnmod =
visibilityMod || (twitchChannel->isMod() && isMyself);
}
mod->setVisible(visibilityMod);
unmod->setVisible(visibilityUnmod);
});
2018-06-06 13:35:06 +02:00
}
auto lineMod = layout.emplace<Line>(false);
2018-06-06 13:35:06 +02:00
// third line
auto moderation = layout.emplace<QHBoxLayout>().withoutMargin();
{
auto timeout = moderation.emplace<TimeoutWidget>();
2018-07-06 19:23:47 +02:00
this->userStateChanged_.connect([this, lineMod, timeout]() mutable {
2018-08-06 21:17:03 +02:00
TwitchChannel *twitchChannel =
dynamic_cast<TwitchChannel *>(this->channel_.get());
bool hasModRights =
twitchChannel ? twitchChannel->hasModRights() : false;
lineMod->setVisible(hasModRights);
timeout->setVisible(hasModRights);
});
timeout->buttonClicked.connect([this](auto item) {
TimeoutWidget::Action action;
int arg;
std::tie(action, arg) = item;
2018-10-21 13:43:02 +02:00
switch (action)
{
case TimeoutWidget::Ban:
{
if (this->channel_)
{
this->channel_->sendMessage("/ban " + this->userName_);
}
2018-10-21 13:43:02 +02:00
}
break;
case TimeoutWidget::Unban:
{
if (this->channel_)
{
2018-08-06 21:17:03 +02:00
this->channel_->sendMessage("/unban " +
this->userName_);
}
2018-10-21 13:43:02 +02:00
}
break;
case TimeoutWidget::Timeout:
{
if (this->channel_)
{
2018-08-06 21:17:03 +02:00
this->channel_->sendMessage("/timeout " +
this->userName_ + " " +
QString::number(arg));
}
2018-10-21 13:43:02 +02:00
}
break;
}
});
2018-06-06 13:35:06 +02:00
}
// fourth line (last messages)
this->latestMessages_ = new ChannelView();
this->latestMessages_->setScaleIndependantHeight(150);
layout.append(this->latestMessages_);
2018-06-06 13:35:06 +02:00
this->setStyleSheet("font-size: 11pt;");
this->installEvents();
}
2018-07-06 17:11:37 +02:00
void UserInfoPopup::themeChangedEvent()
2018-06-07 17:43:21 +02:00
{
2018-07-06 17:11:37 +02:00
BaseWindow::themeChangedEvent();
2018-06-07 17:43:21 +02:00
this->setStyleSheet("background: #333");
}
void UserInfoPopup::installEvents()
{
std::weak_ptr<bool> hack = this->hack_;
// follow
2018-08-06 21:17:03 +02:00
QObject::connect(
this->ui_.follow, &QCheckBox::stateChanged, [this](int) mutable {
auto currentUser = getApp()->accounts->twitch.getCurrent();
2018-08-06 21:17:03 +02:00
QUrl requestUrl("https://api.twitch.tv/kraken/users/" +
currentUser->getUserId() + "/follows/channels/" +
this->userId_);
2018-08-06 21:17:03 +02:00
const auto reenableFollowCheckbox = [this] {
this->ui_.follow->setEnabled(true); //
};
2018-08-06 21:17:03 +02:00
this->ui_.follow->setEnabled(false);
2018-10-21 13:43:02 +02:00
if (this->ui_.follow->isChecked())
{
2018-08-06 21:17:03 +02:00
currentUser->followUser(this->userId_, reenableFollowCheckbox);
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-06 21:17:03 +02:00
currentUser->unfollowUser(this->userId_,
reenableFollowCheckbox);
}
});
std::shared_ptr<bool> ignoreNext = std::make_shared<bool>(false);
// ignore
QObject::connect(
2018-08-06 21:17:03 +02:00
this->ui_.ignore, &QCheckBox::stateChanged,
[this, ignoreNext, hack](int) mutable {
2018-10-21 13:43:02 +02:00
if (*ignoreNext)
{
*ignoreNext = false;
return;
}
this->ui_.ignore->setEnabled(false);
auto currentUser = getApp()->accounts->twitch.getCurrent();
2018-10-21 13:43:02 +02:00
if (this->ui_.ignore->isChecked())
{
2018-08-06 21:17:03 +02:00
currentUser->ignoreByID(
this->userId_, this->userName_,
[=](auto result, const auto &message) mutable {
2018-10-21 13:43:02 +02:00
if (hack.lock())
{
if (result == IgnoreResult_Failed)
{
2018-08-06 21:17:03 +02:00
*ignoreNext = true;
this->ui_.ignore->setChecked(false);
}
this->ui_.ignore->setEnabled(true);
}
});
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-06 21:17:03 +02:00
currentUser->unignoreByID(
this->userId_, this->userName_,
[=](auto result, const auto &message) mutable {
2018-10-21 13:43:02 +02:00
if (hack.lock())
{
if (result == UnignoreResult_Failed)
{
2018-08-06 21:17:03 +02:00
*ignoreNext = true;
this->ui_.ignore->setChecked(true);
}
this->ui_.ignore->setEnabled(true);
}
});
}
});
// ignore highlights
2018-08-06 21:17:03 +02:00
QObject::connect(
this->ui_.ignoreHighlights, &QCheckBox::clicked,
[this](bool checked) mutable {
this->ui_.ignoreHighlights->setEnabled(false);
2018-10-21 13:43:02 +02:00
if (checked)
{
2018-08-06 21:17:03 +02:00
getApp()->highlights->blacklistedUsers.insertItem(
HighlightBlacklistUser{this->userName_, false});
this->ui_.ignoreHighlights->setEnabled(true);
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-06 21:17:03 +02:00
const auto &vector =
getApp()->highlights->blacklistedUsers.getVector();
2018-10-21 13:43:02 +02:00
for (int i = 0; i < vector.size(); i++)
{
if (this->userName_ == vector[i].getPattern())
{
2018-08-06 21:17:03 +02:00
getApp()->highlights->blacklistedUsers.removeItem(i);
i--;
}
}
2018-10-21 13:43:02 +02:00
if (getApp()->highlights->blacklistContains(this->userName_))
{
2018-08-06 21:17:03 +02:00
this->ui_.ignoreHighlights->setToolTip(
"Name matched by regex");
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-06 21:17:03 +02:00
this->ui_.ignoreHighlights->setEnabled(true);
}
}
2018-08-06 21:17:03 +02:00
});
2018-06-06 13:35:06 +02:00
}
void UserInfoPopup::setData(const QString &name, const ChannelPtr &channel)
{
this->userName_ = name;
this->channel_ = channel;
this->ui_.nameLabel->setText(name);
this->updateUserData();
2018-07-06 19:23:47 +02:00
this->userStateChanged_.invoke();
this->fillLatestMessages();
2018-06-06 13:35:06 +02:00
}
void UserInfoPopup::updateUserData()
{
std::weak_ptr<bool> hack = this->hack_;
const auto onIdFetched = [this, hack](QString id) {
auto currentUser = getApp()->accounts->twitch.getCurrent();
this->userId_ = id;
2018-07-15 14:11:46 +02:00
QString url("https://api.twitch.tv/kraken/channels/" + id);
auto request = NetworkRequest::twitchRequest(url);
request.setCaller(this);
2018-08-02 14:23:27 +02:00
request.onSuccess([this](auto result) -> Outcome {
auto obj = result.parseJson();
2018-08-06 21:17:03 +02:00
this->ui_.followerCountLabel->setText(
TEXT_FOLLOWERS +
QString::number(obj.value("followers").toInt()));
this->ui_.viewCountLabel->setText(
TEXT_VIEWS + QString::number(obj.value("views").toInt()));
this->ui_.createdDateLabel->setText(
2018-08-06 21:17:03 +02:00
TEXT_CREATED +
obj.value("created_at").toString().section("T", 0, 0));
this->loadAvatar(QUrl(obj.value("logo").toString()));
2018-08-02 14:23:27 +02:00
return Success;
});
request.execute();
// get follow state
currentUser->checkFollow(id, [this, hack](auto result) {
2018-10-21 13:43:02 +02:00
if (hack.lock())
{
if (result != FollowResult_Failed)
{
this->ui_.follow->setEnabled(true);
2018-08-06 21:17:03 +02:00
this->ui_.follow->setChecked(result ==
FollowResult_Following);
}
}
});
// get ignore state
bool isIgnoring = false;
2018-10-21 13:43:02 +02:00
for (const auto &ignoredUser : currentUser->getIgnores())
{
if (id == ignoredUser.id)
{
isIgnoring = true;
break;
}
}
// get ignoreHighlights state
bool isIgnoringHighlights = false;
const auto &vector = getApp()->highlights->blacklistedUsers.getVector();
2018-10-21 13:43:02 +02:00
for (int i = 0; i < vector.size(); i++)
{
if (this->userName_ == vector[i].getPattern())
{
isIgnoringHighlights = true;
break;
}
}
2018-08-06 21:17:03 +02:00
if (getApp()->highlights->blacklistContains(this->userName_) &&
2018-10-21 13:43:02 +02:00
!isIgnoringHighlights)
{
this->ui_.ignoreHighlights->setToolTip("Name matched by regex");
2018-10-21 13:43:02 +02:00
}
else
{
this->ui_.ignoreHighlights->setEnabled(true);
}
this->ui_.ignore->setEnabled(true);
this->ui_.ignore->setChecked(isIgnoring);
this->ui_.ignoreHighlights->setChecked(isIgnoringHighlights);
};
PartialTwitchUser::byName(this->userName_).getId(onIdFetched, this);
this->ui_.follow->setEnabled(false);
this->ui_.ignore->setEnabled(false);
this->ui_.ignoreHighlights->setEnabled(false);
2018-06-06 13:35:06 +02:00
}
void UserInfoPopup::loadAvatar(const QUrl &url)
{
QNetworkRequest req(url);
static auto manager = new QNetworkAccessManager();
auto *reply = manager->get(req);
QObject::connect(reply, &QNetworkReply::finished, this, [=] {
2018-10-21 13:43:02 +02:00
if (reply->error() == QNetworkReply::NoError)
{
2018-06-06 13:35:06 +02:00
const auto data = reply->readAll();
// might want to cache the avatar image
QPixmap avatar;
avatar.loadFromData(data);
this->ui_.avatarButton->setPixmap(avatar);
2018-10-21 13:43:02 +02:00
}
else
{
2018-06-06 13:35:06 +02:00
this->ui_.avatarButton->setPixmap(QPixmap());
}
});
}
//
// TimeoutWidget
//
UserInfoPopup::TimeoutWidget::TimeoutWidget()
: BaseWidget(nullptr)
{
2018-08-06 21:17:03 +02:00
auto layout = LayoutCreator<TimeoutWidget>(this)
.setLayoutType<QHBoxLayout>()
.withoutMargin();
2018-06-06 13:35:06 +02:00
QColor color1(255, 255, 255, 80);
QColor color2(255, 255, 255, 0);
2018-06-19 18:55:45 +02:00
int buttonWidth = 24;
int buttonWidth2 = 40;
2018-06-06 13:35:06 +02:00
int buttonHeight = 32;
layout->setSpacing(16);
2018-08-06 21:17:03 +02:00
auto addButton = [&](Action action, const QString &text,
const QPixmap &pixmap) {
2018-06-06 13:35:06 +02:00
auto vbox = layout.emplace<QVBoxLayout>().withoutMargin();
{
auto title = vbox.emplace<QHBoxLayout>().withoutMargin();
title->addStretch(1);
2018-06-11 21:57:17 +02:00
auto label = title.emplace<Label>(text);
2018-06-19 18:55:45 +02:00
label->setHasOffset(false);
label->setStyleSheet("color: #BBB");
2018-06-06 13:35:06 +02:00
title->addStretch(1);
auto hbox = vbox.emplace<QHBoxLayout>().withoutMargin();
hbox->setSpacing(0);
{
2018-08-08 15:35:54 +02:00
auto button = hbox.emplace<Button>(nullptr);
button->setPixmap(pixmap);
button->setScaleIndependantSize(buttonHeight, buttonHeight);
button->setBorderColor(QColor(255, 255, 255, 127));
2018-08-06 21:17:03 +02:00
QObject::connect(
button.getElement(), &Button::leftClicked, [this, action] {
2018-08-06 21:17:03 +02:00
this->buttonClicked.invoke(std::make_pair(action, -1));
});
2018-06-06 13:35:06 +02:00
}
}
};
2018-06-06 13:35:06 +02:00
auto addTimeouts = [&](const QString &title_,
const std::vector<std::pair<QString, int>> &items) {
2018-06-06 13:35:06 +02:00
auto vbox = layout.emplace<QVBoxLayout>().withoutMargin();
{
auto title = vbox.emplace<QHBoxLayout>().withoutMargin();
title->addStretch(1);
2018-06-11 21:57:17 +02:00
auto label = title.emplace<Label>(title_);
label->setStyleSheet("color: #BBB");
2018-06-19 18:55:45 +02:00
label->setHasOffset(false);
2018-06-06 13:35:06 +02:00
title->addStretch(1);
auto hbox = vbox.emplace<QHBoxLayout>().withoutMargin();
hbox->setSpacing(0);
2018-10-21 13:43:02 +02:00
for (const auto &item : items)
{
2018-08-08 15:35:54 +02:00
auto a = hbox.emplace<EffectLabel2>();
a->getLabel().setText(std::get<0>(item));
2018-06-06 13:35:06 +02:00
a->setScaleIndependantSize(buttonWidth2, buttonHeight);
2018-06-19 18:55:45 +02:00
a->setBorderColor(color1);
2018-06-06 13:35:06 +02:00
QObject::connect(a.getElement(), &EffectLabel2::leftClicked,
2018-08-06 21:17:03 +02:00
[this, timeout = std::get<1>(item)] {
this->buttonClicked.invoke(std::make_pair(
Action::Timeout, timeout));
});
2018-06-06 13:35:06 +02:00
}
}
};
addButton(Unban, "unban", getApp()->resources->buttons.unban);
std::vector<QString> durationsPerUnit =
getSettings()->timeoutDurationsPerUnit;
std::vector<QString> durationUnits = getSettings()->timeoutDurationUnits;
addTimeouts(
"Timeouts",
{{durationsPerUnit[0] + durationUnits[0],
calculateTimeoutDuration(durationsPerUnit[0], durationUnits[0])},
{durationsPerUnit[1] + durationUnits[1],
calculateTimeoutDuration(durationsPerUnit[1], durationUnits[1])},
{durationsPerUnit[2] + durationUnits[2],
calculateTimeoutDuration(durationsPerUnit[2], durationUnits[2])},
{durationsPerUnit[3] + durationUnits[3],
calculateTimeoutDuration(durationsPerUnit[3], durationUnits[3])},
{durationsPerUnit[4] + durationUnits[4],
calculateTimeoutDuration(durationsPerUnit[4], durationUnits[4])},
{durationsPerUnit[5] + durationUnits[5],
calculateTimeoutDuration(durationsPerUnit[5], durationUnits[5])},
{durationsPerUnit[6] + durationUnits[6],
calculateTimeoutDuration(durationsPerUnit[6], durationUnits[6])},
{durationsPerUnit[7] + durationUnits[7],
calculateTimeoutDuration(durationsPerUnit[7], durationUnits[7])}});
addButton(Ban, "ban", getApp()->resources->buttons.ban);
2018-06-06 13:35:06 +02:00
}
void UserInfoPopup::TimeoutWidget::paintEvent(QPaintEvent *)
{
// QPainter painter(this);
// painter.setPen(QColor(255, 255, 255, 63));
2018-08-06 21:17:03 +02:00
// painter.drawLine(0, this->height() / 2, this->width(), this->height()
// / 2);
2018-06-06 13:35:06 +02:00
}
void UserInfoPopup::fillLatestMessages()
{
LimitedQueueSnapshot<MessagePtr> snapshot =
this->channel_->getMessageSnapshot();
ChannelPtr channelPtr(new Channel("search", Channel::Type::None));
for (size_t i = 0; i < snapshot.size(); i++)
{
MessagePtr message = snapshot[i];
if (message->loginName.compare(this->userName_, Qt::CaseInsensitive) ==
0 &&
!message->flags.has(MessageFlag::Whisper))
{
channelPtr->addMessage(message);
}
}
this->latestMessages_->setChannel(channelPtr);
}
int UserInfoPopup::calculateTimeoutDuration(const QString &durationPerUnit,
const QString &unit)
{
int valueInUnit = durationPerUnit.toInt();
int valueInSec = 0;
if (unit == "s")
{
valueInSec = valueInUnit;
}
else if (unit == "m")
{
valueInSec = valueInUnit * 60;
}
else if (unit == "h")
{
valueInSec = valueInUnit * 60 * 60;
}
else if (unit == "d")
{
valueInSec = valueInUnit * 24 * 60 * 60;
}
else if (unit == "w")
{
valueInSec = valueInUnit * 7 * 24 * 60 * 60;
}
return valueInSec;
}
2018-06-06 13:35:06 +02:00
} // namespace chatterino