847 lines
22 KiB
C++
847 lines
22 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "info/statistics/info_statistics_list_controllers.h"
|
|
|
|
#include "api/api_statistics.h"
|
|
#include "boxes/peer_list_controllers.h"
|
|
#include "data/data_channel.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_stories.h"
|
|
#include "data/data_user.h"
|
|
#include "history/history_item.h"
|
|
#include "info/channel_statistics/boosts/giveaway/boost_badge.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "main/main_session.h"
|
|
#include "ui/effects/outline_segments.h" // Ui::UnreadStoryOutlineGradient.
|
|
#include "ui/effects/toggle_arrow.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/rect.h"
|
|
#include "ui/vertical_list.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/popup_menu.h"
|
|
#include "ui/wrap/slide_wrap.h"
|
|
#include "ui/wrap/vertical_layout.h"
|
|
#include "styles/style_dialogs.h" // dialogsStoriesFull.
|
|
#include "styles/style_menu_icons.h"
|
|
#include "styles/style_settings.h"
|
|
#include "styles/style_statistics.h"
|
|
#include "styles/style_window.h"
|
|
|
|
namespace Info::Statistics {
|
|
namespace {
|
|
|
|
using BoostCallback = Fn<void(const Data::Boost &)>;
|
|
constexpr auto kColorIndexUnclaimed = int(3);
|
|
constexpr auto kColorIndexPending = int(4);
|
|
|
|
void AddArrow(not_null<Ui::RpWidget*> parent) {
|
|
const auto arrow = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
|
arrow->paintRequest(
|
|
) | rpl::start_with_next([=](const QRect &r) {
|
|
auto p = QPainter(arrow);
|
|
|
|
const auto path = Ui::ToggleUpDownArrowPath(
|
|
st::statisticsShowMoreButtonArrowSize,
|
|
st::statisticsShowMoreButtonArrowSize,
|
|
st::statisticsShowMoreButtonArrowSize,
|
|
st::mainMenuToggleFourStrokes,
|
|
0.);
|
|
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
p.fillPath(path, st::lightButtonFg);
|
|
}, arrow->lifetime());
|
|
arrow->resize(Size(st::statisticsShowMoreButtonArrowSize * 2));
|
|
arrow->move(st::statisticsShowMoreButtonArrowPosition);
|
|
arrow->show();
|
|
}
|
|
|
|
void AddSubtitle(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
rpl::producer<QString> title) {
|
|
const auto &subtitlePadding = st::settingsButton.padding;
|
|
Ui::AddSubsectionTitle(
|
|
container,
|
|
std::move(title),
|
|
{ 0, -subtitlePadding.top(), 0, -subtitlePadding.bottom() });
|
|
}
|
|
|
|
[[nodiscard]] QString FormatText(
|
|
int value1, tr::phrase<lngtag_count> phrase1,
|
|
int value2, tr::phrase<lngtag_count> phrase2,
|
|
int value3, tr::phrase<lngtag_count> phrase3) {
|
|
const auto separator = u", "_q;
|
|
auto resultText = QString();
|
|
if (value1 > 0) {
|
|
resultText += phrase1(tr::now, lt_count, value1);
|
|
}
|
|
if (value2 > 0) {
|
|
if (!resultText.isEmpty()) {
|
|
resultText += separator;
|
|
}
|
|
resultText += phrase2(tr::now, lt_count, value2);
|
|
}
|
|
if (value3 > 0) {
|
|
if (!resultText.isEmpty()) {
|
|
resultText += separator;
|
|
}
|
|
resultText += phrase3(tr::now, lt_count, value3);
|
|
}
|
|
return resultText;
|
|
}
|
|
|
|
struct PublicForwardsDescriptor final {
|
|
Data::PublicForwardsSlice firstSlice;
|
|
Fn<void(Data::RecentPostId)> requestShow;
|
|
not_null<PeerData*> peer;
|
|
Data::RecentPostId contextId;
|
|
};
|
|
|
|
struct MembersDescriptor final {
|
|
not_null<Main::Session*> session;
|
|
Fn<void(not_null<PeerData*>)> showPeerInfo;
|
|
Data::SupergroupStatistics data;
|
|
};
|
|
|
|
struct BoostsDescriptor final {
|
|
Data::BoostsListSlice firstSlice;
|
|
BoostCallback boostClickedCallback;
|
|
not_null<PeerData*> peer;
|
|
};
|
|
|
|
class PeerListRowWithFullId : public PeerListRow {
|
|
public:
|
|
PeerListRowWithFullId(
|
|
not_null<PeerData*> peer,
|
|
Data::RecentPostId contextId);
|
|
|
|
[[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback(
|
|
bool) override;
|
|
|
|
[[nodiscard]] Data::RecentPostId contextId() const;
|
|
|
|
private:
|
|
const Data::RecentPostId _contextId;
|
|
|
|
};
|
|
|
|
PeerListRowWithFullId::PeerListRowWithFullId(
|
|
not_null<PeerData*> peer,
|
|
Data::RecentPostId contextId)
|
|
: PeerListRow(peer)
|
|
, _contextId(contextId) {
|
|
}
|
|
|
|
PaintRoundImageCallback PeerListRowWithFullId::generatePaintUserpicCallback(
|
|
bool forceRound) {
|
|
if (!_contextId.storyId) {
|
|
return PeerListRow::generatePaintUserpicCallback(forceRound);
|
|
}
|
|
const auto peer = PeerListRow::peer();
|
|
auto userpic = PeerListRow::ensureUserpicView();
|
|
|
|
const auto line = st::dialogsStoriesFull.lineTwice;
|
|
const auto penWidth = line / 2.;
|
|
const auto offset = 1.5 * penWidth * 2;
|
|
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
|
const auto rect = QRect(QPoint(x, y), Size(size));
|
|
peer->paintUserpicLeft(
|
|
p,
|
|
userpic,
|
|
x + offset,
|
|
y + offset,
|
|
outerWidth,
|
|
size - offset * 2);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
auto gradient = Ui::UnreadStoryOutlineGradient();
|
|
gradient.setStart(rect.topRight());
|
|
gradient.setFinalStop(rect.bottomLeft());
|
|
|
|
p.setPen(QPen(gradient, penWidth));
|
|
p.setBrush(Qt::NoBrush);
|
|
p.drawEllipse(rect - Margins(penWidth));
|
|
};
|
|
}
|
|
|
|
Data::RecentPostId PeerListRowWithFullId::contextId() const {
|
|
return _contextId;
|
|
}
|
|
|
|
class MembersController final : public PeerListController {
|
|
public:
|
|
MembersController(MembersDescriptor d);
|
|
|
|
Main::Session &session() const override;
|
|
void prepare() override;
|
|
void rowClicked(not_null<PeerListRow*> row) override;
|
|
void loadMoreRows() override;
|
|
|
|
void setLimit(int limit);
|
|
|
|
private:
|
|
void addRows(int from, int to);
|
|
|
|
const not_null<Main::Session*> _session;
|
|
Fn<void(not_null<PeerData*>)> _showPeerInfo;
|
|
Data::SupergroupStatistics _data;
|
|
int _limit = 0;
|
|
|
|
};
|
|
|
|
MembersController::MembersController(MembersDescriptor d)
|
|
: _session(std::move(d.session))
|
|
, _showPeerInfo(std::move(d.showPeerInfo))
|
|
, _data(std::move(d.data)) {
|
|
}
|
|
|
|
Main::Session &MembersController::session() const {
|
|
return *_session;
|
|
}
|
|
|
|
void MembersController::setLimit(int limit) {
|
|
addRows(_limit, limit);
|
|
_limit = limit;
|
|
}
|
|
|
|
void MembersController::addRows(int from, int to) {
|
|
const auto addRow = [&](UserId userId, QString text) {
|
|
const auto user = _session->data().user(userId);
|
|
auto row = std::make_unique<PeerListRow>(user);
|
|
row->setCustomStatus(std::move(text));
|
|
delegate()->peerListAppendRow(std::move(row));
|
|
};
|
|
if (!_data.topSenders.empty()) {
|
|
for (auto i = from; i < to; i++) {
|
|
const auto &member = _data.topSenders[i];
|
|
addRow(
|
|
member.userId,
|
|
FormatText(
|
|
member.sentMessageCount,
|
|
tr::lng_stats_member_messages,
|
|
member.averageCharacterCount,
|
|
tr::lng_stats_member_characters,
|
|
0,
|
|
{}));
|
|
}
|
|
} else if (!_data.topAdministrators.empty()) {
|
|
for (auto i = from; i < to; i++) {
|
|
const auto &admin = _data.topAdministrators[i];
|
|
addRow(
|
|
admin.userId,
|
|
FormatText(
|
|
admin.deletedMessageCount,
|
|
tr::lng_stats_member_deletions,
|
|
admin.bannedUserCount,
|
|
tr::lng_stats_member_bans,
|
|
admin.restrictedUserCount,
|
|
tr::lng_stats_member_restrictions));
|
|
}
|
|
} else if (!_data.topInviters.empty()) {
|
|
for (auto i = from; i < to; i++) {
|
|
const auto &inviter = _data.topInviters[i];
|
|
addRow(
|
|
inviter.userId,
|
|
FormatText(
|
|
inviter.addedMemberCount,
|
|
tr::lng_stats_member_invitations,
|
|
0,
|
|
{},
|
|
0,
|
|
{}));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MembersController::prepare() {
|
|
}
|
|
|
|
void MembersController::loadMoreRows() {
|
|
}
|
|
|
|
void MembersController::rowClicked(not_null<PeerListRow*> row) {
|
|
crl::on_main([=, peer = row->peer()] {
|
|
_showPeerInfo(peer);
|
|
});
|
|
}
|
|
|
|
class PublicForwardsController final : public PeerListController {
|
|
public:
|
|
explicit PublicForwardsController(PublicForwardsDescriptor d);
|
|
|
|
Main::Session &session() const override;
|
|
void prepare() override;
|
|
void rowClicked(not_null<PeerListRow*> row) override;
|
|
void loadMoreRows() override;
|
|
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
|
QWidget *parent,
|
|
not_null<PeerListRow*> row) override;
|
|
|
|
private:
|
|
void appendRow(not_null<PeerData*> peer, Data::RecentPostId contextId);
|
|
void applySlice(const Data::PublicForwardsSlice &slice);
|
|
|
|
const not_null<Main::Session*> _session;
|
|
Fn<void(Data::RecentPostId)> _requestShow;
|
|
|
|
Api::PublicForwards _api;
|
|
Data::PublicForwardsSlice _firstSlice;
|
|
Data::PublicForwardsSlice::OffsetToken _apiToken;
|
|
|
|
bool _allLoaded = false;
|
|
|
|
};
|
|
|
|
PublicForwardsController::PublicForwardsController(PublicForwardsDescriptor d)
|
|
: _session(&d.peer->session())
|
|
, _requestShow(std::move(d.requestShow))
|
|
, _api(d.peer->asChannel(), d.contextId)
|
|
, _firstSlice(std::move(d.firstSlice)) {
|
|
}
|
|
|
|
Main::Session &PublicForwardsController::session() const {
|
|
return *_session;
|
|
}
|
|
|
|
void PublicForwardsController::prepare() {
|
|
applySlice(base::take(_firstSlice));
|
|
delegate()->peerListRefreshRows();
|
|
}
|
|
|
|
void PublicForwardsController::loadMoreRows() {
|
|
if (_allLoaded) {
|
|
return;
|
|
}
|
|
_api.request(_apiToken, [=](const Data::PublicForwardsSlice &slice) {
|
|
applySlice(slice);
|
|
});
|
|
}
|
|
|
|
void PublicForwardsController::applySlice(
|
|
const Data::PublicForwardsSlice &slice) {
|
|
_allLoaded = slice.allLoaded;
|
|
_apiToken = slice.token;
|
|
|
|
for (const auto &item : slice.list) {
|
|
if (const auto &full = item.messageId) {
|
|
if (const auto peer = session().data().peerLoaded(full.peer)) {
|
|
appendRow(peer, item);
|
|
}
|
|
} else if (const auto &full = item.storyId) {
|
|
if (const auto story = session().data().stories().lookup(full)) {
|
|
appendRow((*story)->peer(), item);
|
|
}
|
|
}
|
|
}
|
|
delegate()->peerListRefreshRows();
|
|
}
|
|
|
|
void PublicForwardsController::rowClicked(not_null<PeerListRow*> row) {
|
|
const auto rowWithId = static_cast<PeerListRowWithFullId*>(row.get());
|
|
crl::on_main([=, id = rowWithId->contextId()] { _requestShow(id); });
|
|
}
|
|
|
|
base::unique_qptr<Ui::PopupMenu> PublicForwardsController::rowContextMenu(
|
|
QWidget *parent,
|
|
not_null<PeerListRow*> row) {
|
|
auto menu = base::make_unique_q<Ui::PopupMenu>(
|
|
parent,
|
|
st::popupMenuWithIcons);
|
|
const auto peer = row->peer();
|
|
const auto text = (peer->isChat() || peer->isMegagroup())
|
|
? tr::lng_context_view_group(tr::now)
|
|
: peer->isUser()
|
|
? tr::lng_context_view_profile(tr::now)
|
|
: peer->isChannel()
|
|
? tr::lng_context_view_channel(tr::now)
|
|
: QString();
|
|
if (text.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
menu->addAction(text, crl::guard(parent, [=, peerId = peer->id] {
|
|
_requestShow({ .messageId = { peerId, MsgId() } });
|
|
}), peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo);
|
|
return menu;
|
|
}
|
|
|
|
void PublicForwardsController::appendRow(
|
|
not_null<PeerData*> peer,
|
|
Data::RecentPostId contextId) {
|
|
if (delegate()->peerListFindRow(peer->id.value)) {
|
|
return;
|
|
}
|
|
|
|
auto row = std::make_unique<PeerListRowWithFullId>(peer, contextId);
|
|
|
|
const auto members = peer->isChannel()
|
|
? peer->asChannel()->membersCount()
|
|
: 0;
|
|
const auto views = [&] {
|
|
if (contextId.messageId) {
|
|
const auto message = peer->owner().message(contextId.messageId);
|
|
return message ? message->viewsCount() : 0;
|
|
} else if (const auto &id = contextId.storyId) {
|
|
const auto story = peer->owner().stories().lookup(id);
|
|
return story ? (*story)->views() : 0;
|
|
}
|
|
return 0;
|
|
}();
|
|
|
|
const auto membersText = !members
|
|
? QString()
|
|
: peer->isMegagroup()
|
|
? tr::lng_chat_status_members(tr::now, lt_count_decimal, members)
|
|
: tr::lng_chat_status_subscribers(tr::now, lt_count_decimal, members);
|
|
const auto viewsText = views
|
|
? tr::lng_stats_recent_messages_views({}, lt_count_decimal, views)
|
|
: QString();
|
|
const auto resultText = (membersText.isEmpty() && viewsText.isEmpty())
|
|
? tr::lng_stories_no_views(tr::now)
|
|
: (membersText.isEmpty() || viewsText.isEmpty())
|
|
? membersText + viewsText
|
|
: QString("%1, %2").arg(membersText, viewsText);
|
|
row->setCustomStatus(resultText);
|
|
|
|
delegate()->peerListAppendRow(std::move(row));
|
|
return;
|
|
}
|
|
|
|
class BoostRow final : public PeerListRow {
|
|
public:
|
|
BoostRow(not_null<PeerData*> peer, const Data::Boost &boost);
|
|
BoostRow(const Data::Boost &boost);
|
|
|
|
[[nodiscard]] const Data::Boost &boost() const;
|
|
[[nodiscard]] QString generateName() override;
|
|
|
|
[[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback(
|
|
bool forceRound) override;
|
|
|
|
int paintNameIconGetWidth(
|
|
Painter &p,
|
|
Fn<void()> repaint,
|
|
crl::time now,
|
|
int nameLeft,
|
|
int nameTop,
|
|
int nameWidth,
|
|
int availableWidth,
|
|
int outerWidth,
|
|
bool selected) override;
|
|
|
|
QSize rightActionSize() const override;
|
|
QMargins rightActionMargins() const override;
|
|
bool rightActionDisabled() const override;
|
|
void rightActionPaint(
|
|
Painter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
bool selected,
|
|
bool actionSelected) override;
|
|
|
|
private:
|
|
void init();
|
|
void invalidateBadges();
|
|
|
|
const Data::Boost _boost;
|
|
Ui::EmptyUserpic _userpic;
|
|
QImage _badge;
|
|
QImage _rightBadge;
|
|
|
|
};
|
|
|
|
BoostRow::BoostRow(not_null<PeerData*> peer, const Data::Boost &boost)
|
|
: PeerListRow(peer, UniqueRowIdFromString(boost.id))
|
|
, _boost(boost)
|
|
, _userpic(Ui::EmptyUserpic::UserpicColor(0), QString()) {
|
|
init();
|
|
}
|
|
|
|
BoostRow::BoostRow(const Data::Boost &boost)
|
|
: PeerListRow(UniqueRowIdFromString(boost.id))
|
|
, _boost(boost)
|
|
, _userpic(
|
|
Ui::EmptyUserpic::UserpicColor(boost.isUnclaimed
|
|
? kColorIndexUnclaimed
|
|
: kColorIndexPending),
|
|
QString()) {
|
|
init();
|
|
}
|
|
|
|
void BoostRow::init() {
|
|
invalidateBadges();
|
|
auto status = !PeerListRow::special()
|
|
? tr::lng_boosts_list_status(
|
|
tr::now,
|
|
lt_date,
|
|
langDayOfMonth(_boost.expiresAt.date()))
|
|
: tr::lng_months_tiny(tr::now, lt_count, _boost.expiresAfterMonths)
|
|
+ ' '
|
|
+ QChar(0x2022)
|
|
+ ' '
|
|
+ langDayOfMonth(_boost.date.date());
|
|
PeerListRow::setCustomStatus(std::move(status));
|
|
}
|
|
|
|
const Data::Boost &BoostRow::boost() const {
|
|
return _boost;
|
|
}
|
|
|
|
QString BoostRow::generateName() {
|
|
return !PeerListRow::special()
|
|
? PeerListRow::generateName()
|
|
: _boost.isUnclaimed
|
|
? tr::lng_boosts_list_unclaimed(tr::now)
|
|
: tr::lng_boosts_list_pending(tr::now);
|
|
}
|
|
|
|
PaintRoundImageCallback BoostRow::generatePaintUserpicCallback(bool force) {
|
|
if (!PeerListRow::special()) {
|
|
return PeerListRow::generatePaintUserpicCallback(force);
|
|
}
|
|
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
|
_userpic.paintCircle(p, x, y, outerWidth, size);
|
|
(_boost.isUnclaimed
|
|
? st::boostsListUnclaimedIcon
|
|
: st::boostsListUnknownIcon).paintInCenter(
|
|
p,
|
|
{ x, y, size, size });
|
|
};
|
|
}
|
|
|
|
void BoostRow::invalidateBadges() {
|
|
_badge = _boost.multiplier
|
|
? CreateBadge(
|
|
st::statisticsDetailsBottomCaptionStyle,
|
|
QString::number(_boost.multiplier),
|
|
st::boostsListBadgeHeight,
|
|
st::boostsListBadgeTextPadding,
|
|
st::premiumButtonBg2,
|
|
st::premiumButtonFg,
|
|
1.,
|
|
st::boostsListMiniIconPadding,
|
|
st::boostsListMiniIcon)
|
|
: QImage();
|
|
|
|
constexpr auto kBadgeBgOpacity = 0.2;
|
|
const auto &rightColor = _boost.isGiveaway
|
|
? st::historyPeer4UserpicBg2
|
|
: st::historyPeer8UserpicBg2;
|
|
const auto &rightIcon = _boost.isGiveaway
|
|
? st::boostsListGiveawayMiniIcon
|
|
: st::boostsListGiftMiniIcon;
|
|
_rightBadge = (_boost.isGift || _boost.isGiveaway)
|
|
? CreateBadge(
|
|
st::boostsListRightBadgeTextStyle,
|
|
_boost.isGiveaway
|
|
? tr::lng_gift_link_reason_giveaway(tr::now)
|
|
: tr::lng_gift_link_label_gift(tr::now),
|
|
st::boostsListRightBadgeHeight,
|
|
st::boostsListRightBadgeTextPadding,
|
|
rightColor,
|
|
rightColor,
|
|
kBadgeBgOpacity,
|
|
st::boostsListGiftMiniIconPadding,
|
|
rightIcon)
|
|
: QImage();
|
|
}
|
|
|
|
|
|
QSize BoostRow::rightActionSize() const {
|
|
return _rightBadge.size() / style::DevicePixelRatio();
|
|
}
|
|
|
|
QMargins BoostRow::rightActionMargins() const {
|
|
return st::boostsListRightBadgePadding;
|
|
}
|
|
|
|
bool BoostRow::rightActionDisabled() const {
|
|
return true;
|
|
}
|
|
|
|
void BoostRow::rightActionPaint(
|
|
Painter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
bool selected,
|
|
bool actionSelected) {
|
|
if (!_rightBadge.isNull()) {
|
|
p.drawImage(x, y, _rightBadge);
|
|
}
|
|
}
|
|
|
|
int BoostRow::paintNameIconGetWidth(
|
|
Painter &p,
|
|
Fn<void()> repaint,
|
|
crl::time now,
|
|
int nameLeft,
|
|
int nameTop,
|
|
int nameWidth,
|
|
int availableWidth,
|
|
int outerWidth,
|
|
bool selected) {
|
|
if (_badge.isNull()) {
|
|
return 0;
|
|
}
|
|
const auto badgew = _badge.width() / style::DevicePixelRatio();
|
|
const auto nameTooLarge = (nameWidth > availableWidth);
|
|
const auto &padding = st::boostsListBadgePadding;
|
|
const auto left = nameTooLarge
|
|
? ((nameLeft + availableWidth) - badgew - padding.left())
|
|
: (nameLeft + nameWidth + padding.right());
|
|
p.drawImage(left, nameTop + padding.top(), _badge);
|
|
return badgew + (nameTooLarge ? padding.left() : 0);
|
|
}
|
|
|
|
class BoostsController final : public PeerListController {
|
|
public:
|
|
explicit BoostsController(BoostsDescriptor d);
|
|
|
|
Main::Session &session() const override;
|
|
void prepare() override;
|
|
void rowClicked(not_null<PeerListRow*> row) override;
|
|
void loadMoreRows() override;
|
|
|
|
[[nodiscard]] bool skipRequest() const;
|
|
void requestNext();
|
|
|
|
[[nodiscard]] rpl::producer<int> totalBoostsValue() const;
|
|
|
|
private:
|
|
void applySlice(const Data::BoostsListSlice &slice);
|
|
|
|
const not_null<Main::Session*> _session;
|
|
BoostCallback _boostClickedCallback;
|
|
|
|
Api::Boosts _api;
|
|
Data::BoostsListSlice _firstSlice;
|
|
Data::BoostsListSlice::OffsetToken _apiToken;
|
|
|
|
bool _allLoaded = false;
|
|
bool _requesting = false;
|
|
|
|
rpl::variable<int> _totalBoosts;
|
|
|
|
};
|
|
|
|
BoostsController::BoostsController(BoostsDescriptor d)
|
|
: _session(&d.peer->session())
|
|
, _boostClickedCallback(std::move(d.boostClickedCallback))
|
|
, _api(d.peer)
|
|
, _firstSlice(std::move(d.firstSlice)) {
|
|
PeerListController::setStyleOverrides(&st::boostsListBox);
|
|
}
|
|
|
|
Main::Session &BoostsController::session() const {
|
|
return *_session;
|
|
}
|
|
|
|
bool BoostsController::skipRequest() const {
|
|
return _requesting || _allLoaded;
|
|
}
|
|
|
|
void BoostsController::requestNext() {
|
|
_requesting = true;
|
|
_api.requestBoosts(_apiToken, [=](const Data::BoostsListSlice &slice) {
|
|
_requesting = false;
|
|
applySlice(slice);
|
|
});
|
|
}
|
|
|
|
void BoostsController::prepare() {
|
|
applySlice(base::take(_firstSlice));
|
|
delegate()->peerListRefreshRows();
|
|
}
|
|
|
|
void BoostsController::loadMoreRows() {
|
|
}
|
|
|
|
void BoostsController::applySlice(const Data::BoostsListSlice &slice) {
|
|
_allLoaded = slice.allLoaded;
|
|
_apiToken = slice.token;
|
|
|
|
auto sumFromSlice = 0;
|
|
for (const auto &item : slice.list) {
|
|
sumFromSlice += item.multiplier ? item.multiplier : 1;
|
|
auto row = [&] {
|
|
if (item.userId && !item.isUnclaimed) {
|
|
const auto user = session().data().user(item.userId);
|
|
return std::make_unique<BoostRow>(user, item);
|
|
} else {
|
|
return std::make_unique<BoostRow>(item);
|
|
}
|
|
}();
|
|
delegate()->peerListAppendRow(std::move(row));
|
|
}
|
|
delegate()->peerListRefreshRows();
|
|
_totalBoosts = _totalBoosts.current() + sumFromSlice;
|
|
}
|
|
|
|
void BoostsController::rowClicked(not_null<PeerListRow*> row) {
|
|
if (_boostClickedCallback) {
|
|
_boostClickedCallback(
|
|
static_cast<const BoostRow*>(row.get())->boost());
|
|
}
|
|
}
|
|
|
|
rpl::producer<int> BoostsController::totalBoostsValue() const {
|
|
return _totalBoosts.value();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void AddPublicForwards(
|
|
const Data::PublicForwardsSlice &firstSlice,
|
|
not_null<Ui::VerticalLayout*> container,
|
|
Fn<void(Data::RecentPostId)> requestShow,
|
|
not_null<PeerData*> peer,
|
|
Data::RecentPostId contextId) {
|
|
if (!peer->isChannel()) {
|
|
return;
|
|
}
|
|
|
|
struct State final {
|
|
State(PublicForwardsDescriptor d) : controller(std::move(d)) {
|
|
}
|
|
PeerListContentDelegateSimple delegate;
|
|
PublicForwardsController controller;
|
|
};
|
|
auto d = PublicForwardsDescriptor{
|
|
firstSlice,
|
|
std::move(requestShow),
|
|
peer,
|
|
contextId,
|
|
};
|
|
const auto state = container->lifetime().make_state<State>(std::move(d));
|
|
|
|
if (const auto total = firstSlice.total; total > 0) {
|
|
AddSubtitle(
|
|
container,
|
|
tr::lng_stats_overview_message_public_share(
|
|
lt_count_decimal,
|
|
rpl::single<float64>(total)));
|
|
}
|
|
|
|
state->delegate.setContent(container->add(
|
|
object_ptr<PeerListContent>(container, &state->controller)));
|
|
state->controller.setDelegate(&state->delegate);
|
|
}
|
|
|
|
void AddMembersList(
|
|
Data::SupergroupStatistics data,
|
|
not_null<Ui::VerticalLayout*> container,
|
|
Fn<void(not_null<PeerData*>)> showPeerInfo,
|
|
not_null<PeerData*> peer,
|
|
rpl::producer<QString> title) {
|
|
if (!peer->isMegagroup()) {
|
|
return;
|
|
}
|
|
const auto max = !data.topSenders.empty()
|
|
? data.topSenders.size()
|
|
: !data.topAdministrators.empty()
|
|
? data.topAdministrators.size()
|
|
: !data.topInviters.empty()
|
|
? data.topInviters.size()
|
|
: 0;
|
|
if (!max) {
|
|
return;
|
|
}
|
|
|
|
constexpr auto kPerPage = 40;
|
|
struct State final {
|
|
State(MembersDescriptor d) : controller(std::move(d)) {
|
|
}
|
|
PeerListContentDelegateSimple delegate;
|
|
MembersController controller;
|
|
int limit = 0;
|
|
};
|
|
auto d = MembersDescriptor{
|
|
&peer->session(),
|
|
std::move(showPeerInfo),
|
|
std::move(data),
|
|
};
|
|
const auto state = container->lifetime().make_state<State>(std::move(d));
|
|
|
|
AddSubtitle(container, std::move(title));
|
|
|
|
state->delegate.setContent(container->add(
|
|
object_ptr<PeerListContent>(container, &state->controller)));
|
|
state->controller.setDelegate(&state->delegate);
|
|
|
|
const auto wrap = container->add(
|
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
|
container,
|
|
object_ptr<Ui::SettingsButton>(
|
|
container,
|
|
tr::lng_stories_show_more())),
|
|
{ 0, -st::settingsButton.padding.top(), 0, 0 });
|
|
|
|
const auto showMore = [=] {
|
|
state->limit = std::min(int(max), state->limit + kPerPage);
|
|
state->controller.setLimit(state->limit);
|
|
if (state->limit == max) {
|
|
wrap->toggle(false, anim::type::instant);
|
|
}
|
|
container->resizeToWidth(container->width());
|
|
};
|
|
wrap->entity()->setClickedCallback(showMore);
|
|
showMore();
|
|
}
|
|
|
|
void AddBoostsList(
|
|
const Data::BoostsListSlice &firstSlice,
|
|
not_null<Ui::VerticalLayout*> container,
|
|
BoostCallback boostClickedCallback,
|
|
not_null<PeerData*> peer,
|
|
rpl::producer<QString> title) {
|
|
const auto max = firstSlice.multipliedTotal;
|
|
struct State final {
|
|
State(BoostsDescriptor d) : controller(std::move(d)) {
|
|
}
|
|
PeerListContentDelegateSimple delegate;
|
|
BoostsController controller;
|
|
};
|
|
auto d = BoostsDescriptor{ firstSlice, boostClickedCallback, peer };
|
|
const auto state = container->lifetime().make_state<State>(std::move(d));
|
|
|
|
state->delegate.setContent(container->add(
|
|
object_ptr<PeerListContent>(container, &state->controller)));
|
|
state->controller.setDelegate(&state->delegate);
|
|
|
|
const auto wrap = container->add(
|
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
|
container,
|
|
object_ptr<Ui::SettingsButton>(
|
|
container,
|
|
(firstSlice.token.gifts
|
|
? tr::lng_boosts_show_more_gifts
|
|
: tr::lng_boosts_show_more_boosts)(
|
|
lt_count,
|
|
state->controller.totalBoostsValue(
|
|
) | rpl::map(
|
|
max - rpl::mappers::_1
|
|
) | tr::to_count()),
|
|
st::statisticsShowMoreButton)),
|
|
{ 0, -st::settingsButton.padding.top(), 0, 0 });
|
|
const auto button = wrap->entity();
|
|
AddArrow(button);
|
|
|
|
const auto showMore = [=] {
|
|
if (!state->controller.skipRequest()) {
|
|
state->controller.requestNext();
|
|
container->resizeToWidth(container->width());
|
|
}
|
|
};
|
|
wrap->toggleOn(
|
|
state->controller.totalBoostsValue(
|
|
) | rpl::map(rpl::mappers::_1 > 0 && rpl::mappers::_1 < max),
|
|
anim::type::instant);
|
|
button->setClickedCallback(showMore);
|
|
}
|
|
|
|
} // namespace Info::Statistics
|