From c8f7a8c795cd33e4df7a8dd78a077d362bb6316b Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Jan 2022 20:46:10 +0300 Subject: [PATCH] Add a tab with "Who Seen" to "Who Reacted" box. --- Telegram/SourceFiles/api/api_who_reacted.cpp | 203 ++++++++++-------- Telegram/SourceFiles/api/api_who_reacted.h | 15 +- .../view/history_view_context_menu.cpp | 6 +- .../view/reactions/message_reactions_list.cpp | 94 ++++++-- .../view/reactions/message_reactions_list.h | 7 +- .../reactions/message_reactions_selector.cpp | 46 ++-- .../reactions/message_reactions_selector.h | 7 +- Telegram/SourceFiles/ui/chat/chat.style | 4 + .../controls/who_reacted_context_action.cpp | 7 +- 9 files changed, 252 insertions(+), 137 deletions(-) diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index c656b3c93..780249ac6 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -51,16 +51,16 @@ inline bool operator==( struct PeersWithReactions { std::vector list; + std::vector read; int fullReactionsCount = 0; - int fullReadCount = 0; bool unknown = false; }; inline bool operator==( const PeersWithReactions &a, const PeersWithReactions &b) noexcept { return (a.fullReactionsCount == b.fullReactionsCount) - && (a.fullReadCount == b.fullReadCount) && (a.list == b.list) + && (a.read == b.read) && (a.unknown == b.unknown); } @@ -246,14 +246,15 @@ struct State { } [[nodiscard]] PeersWithReactions WithEmptyReactions( - const Peers &peers) { - return PeersWithReactions{ + Peers &&peers) { + auto result = PeersWithReactions{ .list = peers.list | ranges::views::transform([](PeerId peer) { return PeerWithReaction{.peer = peer }; }) | ranges::to_vector, - .fullReadCount = int(peers.list.size()), .unknown = peers.unknown, }; + result.read = std::move(peers.list); + return result; } [[nodiscard]] rpl::producer WhoReactedIds( @@ -322,17 +323,17 @@ struct State { return rpl::combine( WhoReactedIds(item, QString(), context), WhoReadIds(item, context) - ) | rpl::map([=](PeersWithReactions reacted, Peers read) { + ) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) { if (reacted.unknown || read.unknown) { return PeersWithReactions{ .unknown = true }; } auto &list = reacted.list; - reacted.fullReadCount = int(read.list.size()); for (const auto &peer : read.list) { if (!ranges::contains(list, peer, &PeerWithReaction::peer)) { list.push_back({ .peer = peer }); } } + reacted.read = std::move(read.list); return reacted; }); } @@ -442,6 +443,104 @@ void RegenerateParticipants(not_null state, int small, int large) { RegenerateUserpics(state, small, large); } +rpl::producer WhoReacted( + not_null item, + const QString &reaction, + not_null context, + const style::WhoRead &st, + std::shared_ptr 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(); + 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(); + } + 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 bool WhoReadExists(not_null item) { @@ -486,8 +585,9 @@ bool WhoReactedExists(not_null item) { rpl::producer WhoReacted( not_null item, not_null context, - const style::WhoRead &st) { - return WhoReacted(item, QString(), context, st); + const style::WhoRead &st, + std::shared_ptr whoReadIds) { + return WhoReacted(item, QString(), context, st, std::move(whoReadIds)); } rpl::producer WhoReacted( @@ -495,90 +595,7 @@ rpl::producer WhoReacted( const QString &reaction, not_null context, const style::WhoRead &st) { - 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(); - 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; - }; + return WhoReacted(item, reaction, context, st, nullptr); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_who_reacted.h b/Telegram/SourceFiles/api/api_who_reacted.h index fe05c3685..7a2325b88 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.h +++ b/Telegram/SourceFiles/api/api_who_reacted.h @@ -15,6 +15,7 @@ struct WhoRead; namespace Ui { struct WhoReadContent; +enum class WhoReadType; } // namespace Ui namespace Api { @@ -22,15 +23,21 @@ namespace Api { [[nodiscard]] bool WhoReadExists(not_null item); [[nodiscard]] bool WhoReactedExists(not_null item); +struct WhoReadList { + std::vector list; + Ui::WhoReadType type = {}; +}; + // The context must be destroyed before the session holding this item. [[nodiscard]] rpl::producer WhoReacted( not_null item, - not_null context, - const style::WhoRead &st); // Cache results for this lifetime. + not_null context, // Cache results for this lifetime. + const style::WhoRead &st, + std::shared_ptr whoReadIds = nullptr); [[nodiscard]] rpl::producer WhoReacted( not_null item, const QString &reaction, - not_null context, - const style::WhoRead &st); // Cache results for this lifetime. + not_null context, // Cache results for this lifetime. + const style::WhoRead &st); } // namespace Api diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 4caee48a7..489d11e32 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1078,6 +1078,7 @@ void AddWhoReactedAction( not_null context, not_null item, not_null controller) { + const auto whoReadIds = std::make_shared(); const auto participantChosen = [=](uint64 id) { controller->showPeerInfo(PeerId(id)); }; @@ -1091,7 +1092,8 @@ void AddWhoReactedAction( controller->window().show(ReactionsListBox( controller, item, - QString())); + QString(), + whoReadIds)); } }; if (!menu->empty()) { @@ -1099,7 +1101,7 @@ void AddWhoReactedAction( } menu->addAction(Ui::WhoReactedContextAction( menu.get(), - Api::WhoReacted(item, context, st::defaultWhoRead), + Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds), participantChosen, showAllChosen)); } diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp index fe33c63b7..da999c6a0 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "history/history_item.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 "data/data_session.h" #include "data/data_user.h" @@ -50,7 +52,8 @@ public: not_null window, not_null item, const QString &selected, - rpl::producer switches); + rpl::producer switches, + std::shared_ptr whoReadIds); Main::Session &session() const override; void prepare() override; @@ -60,7 +63,8 @@ public: private: using AllEntry = std::pair, QString>; - void loadMore(const QString &offset); + void fillWhoRead(); + void loadMore(const QString &reaction); bool appendRow(not_null user, QString reaction); std::unique_ptr createRow( not_null user, @@ -72,6 +76,8 @@ private: MTP::Sender _api; QString _shownReaction; + std::shared_ptr _whoReadIds; + std::vector> _whoRead; std::vector _all; QString _allOffset; @@ -127,11 +133,13 @@ Controller::Controller( not_null window, not_null item, const QString &selected, - rpl::producer switches) + rpl::producer switches, + std::shared_ptr whoReadIds) : _window(window) , _item(item) , _api(&window->session().mtp()) -, _shownReaction(selected) { +, _shownReaction(selected) +, _whoReadIds(whoReadIds) { std::move( switches ) | rpl::filter([=](const QString &reaction) { @@ -146,10 +154,14 @@ Main::Session &Controller::session() const { } 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(); - - loadMore(QString()); + loadMore(_shownReaction); } void Controller::showReaction(const QString &reaction) { @@ -163,7 +175,9 @@ void Controller::showReaction(const QString &reaction) { } _shownReaction = reaction; - if (_shownReaction.isEmpty()) { + if (_shownReaction == u"read"_q) { + fillWhoRead(); + } else if (_shownReaction.isEmpty()) { _filtered.clear(); for (const auto &[user, reaction] : _all) { appendRow(user, reaction); @@ -177,14 +191,29 @@ void Controller::showReaction(const QString &reaction) { for (const auto user : _filtered) { appendRow(user, _shownReaction); } - loadMore(QString()); + _filteredOffset = QString(); } + loadMore(_shownReaction); setDescriptionText(delegate()->peerListFullRowsCount() ? QString() : tr::lng_contacts_loading(tr::now)); 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() { const auto &offset = _shownReaction.isEmpty() ? _allOffset @@ -192,26 +221,37 @@ void Controller::loadMoreRows() { if (_loadRequestId || offset.isEmpty()) { 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(); + const auto &offset = reaction.isEmpty() + ? _allOffset + : _filteredOffset; + using Flag = MTPmessages_GetMessageReactionsList::Flag; const auto flags = Flag(0) | (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( MTP_flags(flags), _item->history()->peer->input, MTP_int(_item->id), - MTP_string(_shownReaction), + MTP_string(reaction), MTP_string(offset), MTP_int(kPerPageFirst) )).done([=](const MTPmessages_MessageReactionsList &result) { _loadRequestId = 0; - const auto filtered = !_shownReaction.isEmpty(); + const auto filtered = !reaction.isEmpty(); + const auto shown = (reaction == _shownReaction); result.match([&](const MTPDmessages_messageReactionsList &data) { const auto sessionData = &session().data(); sessionData->processUsers(data.vusers()); @@ -222,7 +262,7 @@ void Controller::loadMore(const QString &offset) { const auto user = sessionData->userLoaded( data.vuser_id().v); const auto reaction = qs(data.vreaction()); - if (user && appendRow(user, reaction)) { + if (user && (!shown || appendRow(user, reaction))) { if (filtered) { _filtered.emplace_back(user); } else { @@ -232,8 +272,10 @@ void Controller::loadMore(const QString &offset) { }); } }); - setDescriptionText(QString()); - delegate()->peerListRefreshRows(); + if (shown) { + setDescriptionText(QString()); + delegate()->peerListRefreshRows(); + } }).send(); } @@ -264,20 +306,29 @@ std::unique_ptr Controller::createRow( object_ptr ReactionsListBox( not_null window, not_null item, - QString selected) { + QString selected, + std::shared_ptr whoReadIds) { Expects(IsServerMsgId(item->id)); if (!item->reactions().contains(selected)) { selected = QString(); } + if (selected.isEmpty() && whoReadIds && !whoReadIds->list.empty()) { + selected = u"read"_q; + } const auto tabRequests = std::make_shared>(); const auto initBox = [=](not_null box) { 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( box, - item->reactions(), - selected); + map, + selected, + whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted); selector->changes( ) | rpl::start_to_stream(*tabRequests, box->lifetime()); @@ -299,7 +350,8 @@ object_ptr ReactionsListBox( window, item, selected, - tabRequests->events()), + tabRequests->events(), + whoReadIds), initBox); } diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h index 8782aff4b..e4464ef28 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; +namespace Api { +struct WhoReadList; +} // namespace Api + namespace Window { class SessionController; } // namespace Window @@ -24,6 +28,7 @@ namespace HistoryView { object_ptr ReactionsListBox( not_null window, not_null item, - QString selected); + QString selected, + std::shared_ptr whoReadIds = nullptr); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp index a8c795038..68f39c059 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/abstract_button.h" +#include "ui/controls/who_reacted_context_action.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" @@ -19,6 +20,7 @@ not_null CreateTab( not_null parent, const style::MultiSelect &st, const QString &reaction, + Ui::WhoReadType whoReadType, int count, rpl::producer selected) { struct State { @@ -71,9 +73,19 @@ not_null CreateTab( const auto shift = (height - (size / factor)) / 2; Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift); } else { - (state->selected - ? st::reactionsTabAllSelected - : st::reactionsTabAll).paintInCenter(p, icon); + using Type = Ui::WhoReadType; + (reaction.isEmpty() + ? (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(); @@ -91,23 +103,14 @@ not_null CreateTab( not_null CreateReactionSelector( not_null parent, const base::flat_map &items, - const QString &selected) { + const QString &selected, + Ui::WhoReadType whoReadType) { struct State { rpl::variable selected; std::vector> tabs; }; const auto result = Ui::CreateChild(parent.get()); using Entry = std::pair; - auto sorted = std::vector(); - 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(parent.get()); const auto st = &st::reactionsTabs; const auto state = tabs->lifetime().make_state(); @@ -118,6 +121,7 @@ not_null CreateReactionSelector( tabs, *st, reaction, + whoReadType, count, state->selected.value() | rpl::map(_1 == reaction)); tab->setClickedCallback([=] { @@ -125,6 +129,20 @@ not_null CreateReactionSelector( }); state->tabs.push_back(tab); }; + auto sorted = std::vector(); + 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); for (const auto &[count, reaction] : sorted) { append(reaction, count); diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h index 0961a6be1..1f28f492d 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace Ui { +enum class WhoReadType; +} // namespace Ui + namespace HistoryView { struct Selector { @@ -19,6 +23,7 @@ struct Selector { not_null CreateReactionSelector( not_null parent, const base::flat_map &items, - const QString &selected); + const QString &selected, + Ui::WhoReadType whoReadType); } // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 205d645eb..3af40d1ad 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -926,6 +926,10 @@ whoReadReactionsDisabled: icon{{ "menu/read_reactions", menuFgDisabled }}; reactionsTabAll: icon {{ "menu/read_reactions", windowFg }}; 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) { padding: margins(12px, 10px, 12px, 10px); } diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index ed860da29..de2432038 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -352,6 +352,10 @@ void Action::paint(Painter &p) { void Action::refreshText() { 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); _text.setMarkedText( _st.itemStyle, @@ -365,7 +369,8 @@ void Action::refreshText() { _content.fullReactionsCount, _content.fullReadCount) : (_content.type == WhoReadType::Reacted - || (count > 0 && _content.fullReactionsCount > usersCount)) + || (count > 0 && _content.fullReactionsCount > usersCount) + || (count > 0 && onlySeenCount == 0)) ? (count ? tr::lng_context_seen_reacted( tr::now,