Add a tab with "Who Seen" to "Who Reacted" box.

This commit is contained in:
John Preston 2022-01-18 20:46:10 +03:00
parent 74a28ffdf7
commit c8f7a8c795
9 changed files with 252 additions and 137 deletions

View file

@ -51,16 +51,16 @@ inline bool operator==(
struct PeersWithReactions { struct PeersWithReactions {
std::vector<PeerWithReaction> list; std::vector<PeerWithReaction> list;
std::vector<PeerId> read;
int fullReactionsCount = 0; int fullReactionsCount = 0;
int fullReadCount = 0;
bool unknown = false; bool unknown = false;
}; };
inline bool operator==( inline bool operator==(
const PeersWithReactions &a, const PeersWithReactions &a,
const PeersWithReactions &b) noexcept { const PeersWithReactions &b) noexcept {
return (a.fullReactionsCount == b.fullReactionsCount) return (a.fullReactionsCount == b.fullReactionsCount)
&& (a.fullReadCount == b.fullReadCount)
&& (a.list == b.list) && (a.list == b.list)
&& (a.read == b.read)
&& (a.unknown == b.unknown); && (a.unknown == b.unknown);
} }
@ -246,14 +246,15 @@ struct State {
} }
[[nodiscard]] PeersWithReactions WithEmptyReactions( [[nodiscard]] PeersWithReactions WithEmptyReactions(
const Peers &peers) { Peers &&peers) {
return PeersWithReactions{ auto result = PeersWithReactions{
.list = peers.list | ranges::views::transform([](PeerId peer) { .list = peers.list | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{.peer = peer }; return PeerWithReaction{.peer = peer };
}) | ranges::to_vector, }) | ranges::to_vector,
.fullReadCount = int(peers.list.size()),
.unknown = peers.unknown, .unknown = peers.unknown,
}; };
result.read = std::move(peers.list);
return result;
} }
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds( [[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
@ -322,17 +323,17 @@ struct State {
return rpl::combine( return rpl::combine(
WhoReactedIds(item, QString(), context), WhoReactedIds(item, QString(), context),
WhoReadIds(item, context) WhoReadIds(item, context)
) | rpl::map([=](PeersWithReactions reacted, Peers read) { ) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
if (reacted.unknown || read.unknown) { if (reacted.unknown || read.unknown) {
return PeersWithReactions{ .unknown = true }; return PeersWithReactions{ .unknown = true };
} }
auto &list = reacted.list; auto &list = reacted.list;
reacted.fullReadCount = int(read.list.size());
for (const auto &peer : read.list) { for (const auto &peer : read.list) {
if (!ranges::contains(list, peer, &PeerWithReaction::peer)) { if (!ranges::contains(list, peer, &PeerWithReaction::peer)) {
list.push_back({ .peer = peer }); list.push_back({ .peer = peer });
} }
} }
reacted.read = std::move(read.list);
return reacted; return reacted;
}); });
} }
@ -442,6 +443,104 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
RegenerateUserpics(state, small, large); RegenerateUserpics(state, small, large);
} }
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds) {
const auto small = st.userpics.size;
const auto large = st.photoSize;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto resolveWhoRead = reaction.isEmpty()
&& WhoReadExists(item);
const auto state = lifetime.make_state<State>();
const auto pushNext = [=] {
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = !reaction.isEmpty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, reaction, context);
state->current.type = resolveWhoRead
? DetectSeenType(item)
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = reaction.isEmpty()
? ranges::accumulate(
list,
0,
ranges::plus{},
[](const auto &pair) { return pair.second; })
: list.contains(reaction)
? list.find(reaction)->second
: 0;
// #TODO reactions
state->current.singleReaction = !reaction.isEmpty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
if (peers.unknown) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount,
.fullReadCount = state->current.fullReadCount,
.unknown = true,
});
return;
}
state->current.fullReadCount = int(peers.read.size());
state->current.fullReactionsCount = peers.fullReactionsCount;
if (whoReadIds) {
whoReadIds->list = (peers.read.size() > peers.list.size())
? std::move(peers.read)
: std::vector<PeerId>();
}
if (UpdateUserpics(state, item, peers.list)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.list.empty()) {
pushNext();
}
}, lifetime);
item->history()->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->someUserpicsNotLoaded && !state->scheduled;
}) | rpl::start_with_next([=] {
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
state->scheduled = true;
crl::on_main(&state->guard, [=] {
state->scheduled = false;
RegenerateUserpics(state, small, large);
pushNext();
});
return;
}
}
}, lifetime);
return lifetime;
};
}
} // namespace } // namespace
bool WhoReadExists(not_null<HistoryItem*> item) { bool WhoReadExists(not_null<HistoryItem*> item) {
@ -486,8 +585,9 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
rpl::producer<Ui::WhoReadContent> WhoReacted( rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
not_null<QWidget*> context, not_null<QWidget*> context,
const style::WhoRead &st) { const style::WhoRead &st,
return WhoReacted(item, QString(), context, st); std::shared_ptr<WhoReadList> whoReadIds) {
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
} }
rpl::producer<Ui::WhoReadContent> WhoReacted( rpl::producer<Ui::WhoReadContent> WhoReacted(
@ -495,90 +595,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
const QString &reaction, const QString &reaction,
not_null<QWidget*> context, not_null<QWidget*> context,
const style::WhoRead &st) { const style::WhoRead &st) {
const auto small = st.userpics.size; return WhoReacted(item, reaction, context, st, nullptr);
const auto large = st.photoSize;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto resolveWhoRead = reaction.isEmpty() && WhoReadExists(item);
const auto state = lifetime.make_state<State>();
const auto pushNext = [=] {
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = !reaction.isEmpty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, reaction, context);
state->current.type = resolveWhoRead
? DetectSeenType(item)
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = reaction.isEmpty()
? ranges::accumulate(
list,
0,
ranges::plus{},
[](const auto &pair) { return pair.second; })
: list.contains(reaction)
? list.find(reaction)->second
: 0;
// #TODO reactions
state->current.singleReaction = !reaction.isEmpty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
if (peers.unknown) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount,
.fullReadCount = state->current.fullReadCount,
.unknown = true,
});
return;
}
state->current.fullReadCount = peers.fullReadCount;
state->current.fullReactionsCount = peers.fullReactionsCount;
if (UpdateUserpics(state, item, peers.list)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.list.empty()) {
pushNext();
}
}, lifetime);
item->history()->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->someUserpicsNotLoaded && !state->scheduled;
}) | rpl::start_with_next([=] {
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
state->scheduled = true;
crl::on_main(&state->guard, [=] {
state->scheduled = false;
RegenerateUserpics(state, small, large);
pushNext();
});
return;
}
}
}, lifetime);
return lifetime;
};
} }
} // namespace Api } // namespace Api

View file

@ -15,6 +15,7 @@ struct WhoRead;
namespace Ui { namespace Ui {
struct WhoReadContent; struct WhoReadContent;
enum class WhoReadType;
} // namespace Ui } // namespace Ui
namespace Api { namespace Api {
@ -22,15 +23,21 @@ namespace Api {
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item); [[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item); [[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
struct WhoReadList {
std::vector<PeerId> list;
Ui::WhoReadType type = {};
};
// The context must be destroyed before the session holding this item. // The context must be destroyed before the session holding this item.
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted( [[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
not_null<QWidget*> context, not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st); // Cache results for this lifetime. const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted( [[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const QString &reaction, const QString &reaction,
not_null<QWidget*> context, not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st); // Cache results for this lifetime. const style::WhoRead &st);
} // namespace Api } // namespace Api

View file

@ -1078,6 +1078,7 @@ void AddWhoReactedAction(
not_null<QWidget*> context, not_null<QWidget*> context,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
not_null<Window::SessionController*> controller) { not_null<Window::SessionController*> controller) {
const auto whoReadIds = std::make_shared<Api::WhoReadList>();
const auto participantChosen = [=](uint64 id) { const auto participantChosen = [=](uint64 id) {
controller->showPeerInfo(PeerId(id)); controller->showPeerInfo(PeerId(id));
}; };
@ -1091,7 +1092,8 @@ void AddWhoReactedAction(
controller->window().show(ReactionsListBox( controller->window().show(ReactionsListBox(
controller, controller,
item, item,
QString())); QString(),
whoReadIds));
} }
}; };
if (!menu->empty()) { if (!menu->empty()) {
@ -1099,7 +1101,7 @@ void AddWhoReactedAction(
} }
menu->addAction(Ui::WhoReactedContextAction( menu->addAction(Ui::WhoReactedContextAction(
menu.get(), menu.get(),
Api::WhoReacted(item, context, st::defaultWhoRead), Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds),
participantChosen, participantChosen,
showAllChosen)); showAllChosen));
} }

View file

@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
#include "api/api_who_reacted.h"
#include "ui/controls/who_reacted_context_action.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -50,7 +52,8 @@ public:
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const QString &selected, const QString &selected,
rpl::producer<QString> switches); rpl::producer<QString> switches,
std::shared_ptr<Api::WhoReadList> whoReadIds);
Main::Session &session() const override; Main::Session &session() const override;
void prepare() override; void prepare() override;
@ -60,7 +63,8 @@ public:
private: private:
using AllEntry = std::pair<not_null<UserData*>, QString>; using AllEntry = std::pair<not_null<UserData*>, QString>;
void loadMore(const QString &offset); void fillWhoRead();
void loadMore(const QString &reaction);
bool appendRow(not_null<UserData*> user, QString reaction); bool appendRow(not_null<UserData*> user, QString reaction);
std::unique_ptr<PeerListRow> createRow( std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user, not_null<UserData*> user,
@ -72,6 +76,8 @@ private:
MTP::Sender _api; MTP::Sender _api;
QString _shownReaction; QString _shownReaction;
std::shared_ptr<Api::WhoReadList> _whoReadIds;
std::vector<not_null<UserData*>> _whoRead;
std::vector<AllEntry> _all; std::vector<AllEntry> _all;
QString _allOffset; QString _allOffset;
@ -127,11 +133,13 @@ Controller::Controller(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const QString &selected, const QString &selected,
rpl::producer<QString> switches) rpl::producer<QString> switches,
std::shared_ptr<Api::WhoReadList> whoReadIds)
: _window(window) : _window(window)
, _item(item) , _item(item)
, _api(&window->session().mtp()) , _api(&window->session().mtp())
, _shownReaction(selected) { , _shownReaction(selected)
, _whoReadIds(whoReadIds) {
std::move( std::move(
switches switches
) | rpl::filter([=](const QString &reaction) { ) | rpl::filter([=](const QString &reaction) {
@ -146,10 +154,14 @@ Main::Session &Controller::session() const {
} }
void Controller::prepare() { void Controller::prepare() {
setDescriptionText(tr::lng_contacts_loading(tr::now)); if (_shownReaction == u"read"_q) {
fillWhoRead();
setDescriptionText(QString());
} else {
setDescriptionText(tr::lng_contacts_loading(tr::now));
}
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
loadMore(_shownReaction);
loadMore(QString());
} }
void Controller::showReaction(const QString &reaction) { void Controller::showReaction(const QString &reaction) {
@ -163,7 +175,9 @@ void Controller::showReaction(const QString &reaction) {
} }
_shownReaction = reaction; _shownReaction = reaction;
if (_shownReaction.isEmpty()) { if (_shownReaction == u"read"_q) {
fillWhoRead();
} else if (_shownReaction.isEmpty()) {
_filtered.clear(); _filtered.clear();
for (const auto &[user, reaction] : _all) { for (const auto &[user, reaction] : _all) {
appendRow(user, reaction); appendRow(user, reaction);
@ -177,14 +191,29 @@ void Controller::showReaction(const QString &reaction) {
for (const auto user : _filtered) { for (const auto user : _filtered) {
appendRow(user, _shownReaction); appendRow(user, _shownReaction);
} }
loadMore(QString()); _filteredOffset = QString();
} }
loadMore(_shownReaction);
setDescriptionText(delegate()->peerListFullRowsCount() setDescriptionText(delegate()->peerListFullRowsCount()
? QString() ? QString()
: tr::lng_contacts_loading(tr::now)); : tr::lng_contacts_loading(tr::now));
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
} }
void Controller::fillWhoRead() {
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
auto &owner = _window->session().data();
for (const auto &peerId : _whoReadIds->list) {
if (const auto user = owner.userLoaded(peerToUser(peerId))) {
_whoRead.push_back(user);
}
}
}
for (const auto &user : _whoRead) {
appendRow(user, QString());
}
}
void Controller::loadMoreRows() { void Controller::loadMoreRows() {
const auto &offset = _shownReaction.isEmpty() const auto &offset = _shownReaction.isEmpty()
? _allOffset ? _allOffset
@ -192,26 +221,37 @@ void Controller::loadMoreRows() {
if (_loadRequestId || offset.isEmpty()) { if (_loadRequestId || offset.isEmpty()) {
return; return;
} }
loadMore(offset); loadMore(_shownReaction);
} }
void Controller::loadMore(const QString &offset) { void Controller::loadMore(const QString &reaction) {
if (reaction == u"read"_q) {
loadMore(QString());
return;
} else if (reaction.isEmpty() && _allOffset.isEmpty() && !_all.empty()) {
return;
}
_api.request(_loadRequestId).cancel(); _api.request(_loadRequestId).cancel();
const auto &offset = reaction.isEmpty()
? _allOffset
: _filteredOffset;
using Flag = MTPmessages_GetMessageReactionsList::Flag; using Flag = MTPmessages_GetMessageReactionsList::Flag;
const auto flags = Flag(0) const auto flags = Flag(0)
| (offset.isEmpty() ? Flag(0) : Flag::f_offset) | (offset.isEmpty() ? Flag(0) : Flag::f_offset)
| (_shownReaction.isEmpty() ? Flag(0) : Flag::f_reaction); | (reaction.isEmpty() ? Flag(0) : Flag::f_reaction);
_loadRequestId = _api.request(MTPmessages_GetMessageReactionsList( _loadRequestId = _api.request(MTPmessages_GetMessageReactionsList(
MTP_flags(flags), MTP_flags(flags),
_item->history()->peer->input, _item->history()->peer->input,
MTP_int(_item->id), MTP_int(_item->id),
MTP_string(_shownReaction), MTP_string(reaction),
MTP_string(offset), MTP_string(offset),
MTP_int(kPerPageFirst) MTP_int(kPerPageFirst)
)).done([=](const MTPmessages_MessageReactionsList &result) { )).done([=](const MTPmessages_MessageReactionsList &result) {
_loadRequestId = 0; _loadRequestId = 0;
const auto filtered = !_shownReaction.isEmpty(); const auto filtered = !reaction.isEmpty();
const auto shown = (reaction == _shownReaction);
result.match([&](const MTPDmessages_messageReactionsList &data) { result.match([&](const MTPDmessages_messageReactionsList &data) {
const auto sessionData = &session().data(); const auto sessionData = &session().data();
sessionData->processUsers(data.vusers()); sessionData->processUsers(data.vusers());
@ -222,7 +262,7 @@ void Controller::loadMore(const QString &offset) {
const auto user = sessionData->userLoaded( const auto user = sessionData->userLoaded(
data.vuser_id().v); data.vuser_id().v);
const auto reaction = qs(data.vreaction()); const auto reaction = qs(data.vreaction());
if (user && appendRow(user, reaction)) { if (user && (!shown || appendRow(user, reaction))) {
if (filtered) { if (filtered) {
_filtered.emplace_back(user); _filtered.emplace_back(user);
} else { } else {
@ -232,8 +272,10 @@ void Controller::loadMore(const QString &offset) {
}); });
} }
}); });
setDescriptionText(QString()); if (shown) {
delegate()->peerListRefreshRows(); setDescriptionText(QString());
delegate()->peerListRefreshRows();
}
}).send(); }).send();
} }
@ -264,20 +306,29 @@ std::unique_ptr<PeerListRow> Controller::createRow(
object_ptr<Ui::BoxContent> ReactionsListBox( object_ptr<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
QString selected) { QString selected,
std::shared_ptr<Api::WhoReadList> whoReadIds) {
Expects(IsServerMsgId(item->id)); Expects(IsServerMsgId(item->id));
if (!item->reactions().contains(selected)) { if (!item->reactions().contains(selected)) {
selected = QString(); selected = QString();
} }
if (selected.isEmpty() && whoReadIds && !whoReadIds->list.empty()) {
selected = u"read"_q;
}
const auto tabRequests = std::make_shared<rpl::event_stream<QString>>(); const auto tabRequests = std::make_shared<rpl::event_stream<QString>>();
const auto initBox = [=](not_null<PeerListBox*> box) { const auto initBox = [=](not_null<PeerListBox*> box) {
box->setNoContentMargin(true); box->setNoContentMargin(true);
auto map = item->reactions();
if (whoReadIds && !whoReadIds->list.empty()) {
map.emplace(u"read"_q, int(whoReadIds->list.size()));
}
const auto selector = CreateReactionSelector( const auto selector = CreateReactionSelector(
box, box,
item->reactions(), map,
selected); selected,
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
selector->changes( selector->changes(
) | rpl::start_to_stream(*tabRequests, box->lifetime()); ) | rpl::start_to_stream(*tabRequests, box->lifetime());
@ -299,7 +350,8 @@ object_ptr<Ui::BoxContent> ReactionsListBox(
window, window,
item, item,
selected, selected,
tabRequests->events()), tabRequests->events(),
whoReadIds),
initBox); initBox);
} }

View file

@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem; class HistoryItem;
namespace Api {
struct WhoReadList;
} // namespace Api
namespace Window { namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
@ -24,6 +28,7 @@ namespace HistoryView {
object_ptr<Ui::BoxContent> ReactionsListBox( object_ptr<Ui::BoxContent> ReactionsListBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
QString selected); QString selected,
std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
} // namespace HistoryView } // namespace HistoryView

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "ui/abstract_button.h" #include "ui/abstract_button.h"
#include "ui/controls/who_reacted_context_action.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -19,6 +20,7 @@ not_null<Ui::AbstractButton*> CreateTab(
not_null<QWidget*> parent, not_null<QWidget*> parent,
const style::MultiSelect &st, const style::MultiSelect &st,
const QString &reaction, const QString &reaction,
Ui::WhoReadType whoReadType,
int count, int count,
rpl::producer<bool> selected) { rpl::producer<bool> selected) {
struct State { struct State {
@ -71,9 +73,19 @@ not_null<Ui::AbstractButton*> CreateTab(
const auto shift = (height - (size / factor)) / 2; const auto shift = (height - (size / factor)) / 2;
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift); Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
} else { } else {
(state->selected using Type = Ui::WhoReadType;
? st::reactionsTabAllSelected (reaction.isEmpty()
: st::reactionsTabAll).paintInCenter(p, icon); ? (state->selected
? st::reactionsTabAllSelected
: st::reactionsTabAll)
: (whoReadType == Type::Watched
|| whoReadType == Type::Listened)
? (state->selected
? st::reactionsTabPlayedSelected
: st::reactionsTabPlayed)
: (state->selected
? st::reactionsTabChecksSelected
: st::reactionsTabChecks)).paintInCenter(p, icon);
} }
const auto textLeft = height + stm->padding.left(); const auto textLeft = height + stm->padding.left();
@ -91,23 +103,14 @@ not_null<Ui::AbstractButton*> CreateTab(
not_null<Selector*> CreateReactionSelector( not_null<Selector*> CreateReactionSelector(
not_null<QWidget*> parent, not_null<QWidget*> parent,
const base::flat_map<QString, int> &items, const base::flat_map<QString, int> &items,
const QString &selected) { const QString &selected,
Ui::WhoReadType whoReadType) {
struct State { struct State {
rpl::variable<QString> selected; rpl::variable<QString> selected;
std::vector<not_null<Ui::AbstractButton*>> tabs; std::vector<not_null<Ui::AbstractButton*>> tabs;
}; };
const auto result = Ui::CreateChild<Selector>(parent.get()); const auto result = Ui::CreateChild<Selector>(parent.get());
using Entry = std::pair<int, QString>; using Entry = std::pair<int, QString>;
auto sorted = std::vector<Entry>();
for (const auto &[reaction, count] : items) {
sorted.emplace_back(count, reaction);
}
ranges::sort(sorted, std::greater<>(), &Entry::first);
const auto count = ranges::accumulate(
sorted,
0,
std::plus<>(),
&Entry::first);
auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get()); auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto st = &st::reactionsTabs; const auto st = &st::reactionsTabs;
const auto state = tabs->lifetime().make_state<State>(); const auto state = tabs->lifetime().make_state<State>();
@ -118,6 +121,7 @@ not_null<Selector*> CreateReactionSelector(
tabs, tabs,
*st, *st,
reaction, reaction,
whoReadType,
count, count,
state->selected.value() | rpl::map(_1 == reaction)); state->selected.value() | rpl::map(_1 == reaction));
tab->setClickedCallback([=] { tab->setClickedCallback([=] {
@ -125,6 +129,20 @@ not_null<Selector*> CreateReactionSelector(
}); });
state->tabs.push_back(tab); state->tabs.push_back(tab);
}; };
auto sorted = std::vector<Entry>();
for (const auto &[reaction, count] : items) {
if (reaction == u"read"_q) {
append(reaction, count);
} else {
sorted.emplace_back(count, reaction);
}
}
ranges::sort(sorted, std::greater<>(), &Entry::first);
const auto count = ranges::accumulate(
sorted,
0,
std::plus<>(),
&Entry::first);
append(QString(), count); append(QString(), count);
for (const auto &[count, reaction] : sorted) { for (const auto &[count, reaction] : sorted) {
append(reaction, count); append(reaction, count);

View file

@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
namespace Ui {
enum class WhoReadType;
} // namespace Ui
namespace HistoryView { namespace HistoryView {
struct Selector { struct Selector {
@ -19,6 +23,7 @@ struct Selector {
not_null<Selector*> CreateReactionSelector( not_null<Selector*> CreateReactionSelector(
not_null<QWidget*> parent, not_null<QWidget*> parent,
const base::flat_map<QString, int> &items, const base::flat_map<QString, int> &items,
const QString &selected); const QString &selected,
Ui::WhoReadType whoReadType);
} // namespace HistoryView } // namespace HistoryView

View file

@ -926,6 +926,10 @@ whoReadReactionsDisabled: icon{{ "menu/read_reactions", menuFgDisabled }};
reactionsTabAll: icon {{ "menu/read_reactions", windowFg }}; reactionsTabAll: icon {{ "menu/read_reactions", windowFg }};
reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }}; reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }};
reactionsTabPlayed: icon {{ "menu/read_audio", windowFg }};
reactionsTabPlayedSelected: icon {{ "menu/read_audio", activeButtonFg }};
reactionsTabChecks: icon {{ "menu/read_ticks", windowFg }};
reactionsTabChecksSelected: icon {{ "menu/read_ticks", activeButtonFg }};
reactionsTabs: MultiSelect(defaultMultiSelect) { reactionsTabs: MultiSelect(defaultMultiSelect) {
padding: margins(12px, 10px, 12px, 10px); padding: margins(12px, 10px, 12px, 10px);
} }

View file

@ -352,6 +352,10 @@ void Action::paint(Painter &p) {
void Action::refreshText() { void Action::refreshText() {
const auto usersCount = int(_content.participants.size()); const auto usersCount = int(_content.participants.size());
const auto onlySeenCount = ranges::count(
_content.participants,
QString(),
&WhoReadParticipant::reaction);
const auto count = std::max(_content.fullReactionsCount, usersCount); const auto count = std::max(_content.fullReactionsCount, usersCount);
_text.setMarkedText( _text.setMarkedText(
_st.itemStyle, _st.itemStyle,
@ -365,7 +369,8 @@ void Action::refreshText() {
_content.fullReactionsCount, _content.fullReactionsCount,
_content.fullReadCount) _content.fullReadCount)
: (_content.type == WhoReadType::Reacted : (_content.type == WhoReadType::Reacted
|| (count > 0 && _content.fullReactionsCount > usersCount)) || (count > 0 && _content.fullReactionsCount > usersCount)
|| (count > 0 && onlySeenCount == 0))
? (count ? (count
? tr::lng_context_seen_reacted( ? tr::lng_context_seen_reacted(
tr::now, tr::now,