Reacted users list on inline reaction right click.

This commit is contained in:
John Preston 2022-01-14 18:42:24 +03:00
parent df044dbd83
commit 1060b04b1e
11 changed files with 396 additions and 201 deletions

View file

@ -80,7 +80,9 @@ struct CachedReacted {
struct Context {
base::flat_map<not_null<HistoryItem*>, CachedRead> cachedRead;
base::flat_map<not_null<HistoryItem*>, CachedReacted> cachedReacted;
base::flat_map<
not_null<HistoryItem*>,
base::flat_map<QString, CachedReacted>> cachedReacted;
base::flat_map<not_null<Main::Session*>, rpl::lifetime> subscriptions;
[[nodiscard]] CachedRead &cacheRead(not_null<HistoryItem*> item) {
@ -91,12 +93,15 @@ struct Context {
return cachedRead.emplace(item, CachedRead()).first->second;
}
[[nodiscard]] CachedReacted &cacheReacted(not_null<HistoryItem*> item) {
const auto i = cachedReacted.find(item);
if (i != end(cachedReacted)) {
[[nodiscard]] CachedReacted &cacheReacted(
not_null<HistoryItem*> item,
const QString &reaction) {
auto &map = cachedReacted[item];
const auto i = map.find(reaction);
if (i != end(map)) {
return i->second;
}
return cachedReacted.emplace(item, CachedReacted()).first->second;
return map.emplace(reaction, CachedReacted()).first->second;
}
};
@ -140,9 +145,11 @@ struct State {
item->history()->session().api().request(requestId).cancel();
}
}
for (auto &[item, entry] : i->second->cachedReacted) {
if (const auto requestId = entry.requestId) {
item->history()->session().api().request(requestId).cancel();
for (auto &[item, map] : i->second->cachedReacted) {
for (auto &[reaction, entry] : map) {
if (const auto requestId = entry.requestId) {
item->history()->session().api().request(requestId).cancel();
}
}
}
contexts.erase(i);
@ -150,7 +157,9 @@ struct State {
return result;
}
[[nodiscard]] not_null<Context*> PreparedContextAt(not_null<QWidget*> key, not_null<Main::Session*> session) {
[[nodiscard]] not_null<Context*> PreparedContextAt(
not_null<QWidget*> key,
not_null<Main::Session*> session) {
const auto context = ContextAt(key);
if (context->subscriptions.contains(session)) {
return context;
@ -165,7 +174,9 @@ struct State {
}
const auto j = context->cachedReacted.find(update.item);
if (j != end(context->cachedReacted)) {
session->api().request(j->second.requestId).cancel();
for (auto &[reaction, entry] : j->second) {
session->api().request(entry.requestId).cancel();
}
context->cachedReacted.erase(j);
}
}, context->subscriptions[session]);
@ -244,6 +255,7 @@ struct State {
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context) {
auto weak = QPointer<QWidget>(context.get());
const auto session = &item->history()->session();
@ -252,19 +264,22 @@ struct State {
return rpl::lifetime();
}
const auto context = PreparedContextAt(weak.data(), session);
auto &entry = context->cacheReacted(item);
auto &entry = context->cacheReacted(item, reaction);
if (!entry.requestId) {
using Flag = MTPmessages_GetMessageReactionsList::Flag;
entry.requestId = session->api().request(
MTPmessages_GetMessageReactionsList(
MTP_flags(0),
MTP_flags(reaction.isEmpty()
? Flag(0)
: Flag::f_reaction),
item->history()->peer->input,
MTP_int(item->id),
MTPstring(), // reaction
MTP_string(reaction),
MTPstring(), // offset
MTP_int(kContextReactionsLimit)
)
).done([=](const MTPmessages_MessageReactionsList &result) {
auto &entry = context->cacheReacted(item);
auto &entry = context->cacheReacted(item, reaction);
entry.requestId = 0;
result.match([&](
@ -286,7 +301,7 @@ struct State {
entry.data = std::move(parsed);
});
}).fail([=] {
auto &entry = context->cacheReacted(item);
auto &entry = context->cacheReacted(item, reaction);
entry.requestId = 0;
if (entry.data.current().unknown) {
entry.data = PeersWithReactions();
@ -302,7 +317,7 @@ struct State {
not_null<QWidget*> context)
-> rpl::producer<PeersWithReactions> {
return rpl::combine(
WhoReactedIds(item, context),
WhoReactedIds(item, QString(), context),
WhoReadIds(item, context)
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
if (reacted.unknown || read.unknown) {
@ -468,37 +483,52 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st) {
return WhoReacted(item, QString(), context, st);
}
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> 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 = WhoReadExists(item);
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 = item->canViewReactions();
const auto resolveWhoReacted = !reaction.isEmpty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, context);
: WhoReactedIds(item, reaction, context);
state->current.type = resolveWhoRead
? DetectSeenType(item)
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = ranges::accumulate(
list,
0,
ranges::plus{},
[](const auto &pair) { return pair.second; });
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 = (list.size() == 1)
state->current.singleReaction = !reaction.isEmpty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
}
@ -509,6 +539,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount,
.unknown = true,
});
return;

View file

@ -27,5 +27,10 @@ namespace Api {
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st); // Cache results for this lifetime.
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st); // Cache results for this lifetime.
} // namespace Api

View file

@ -1892,6 +1892,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto hasWhoReactedItem = _dragStateItem
&& Api::WhoReactedExists(_dragStateItem);
const auto clickedEmoji = link
? link->property(kReactionsCountEmojiProperty).toString()
: QString();
_whoReactedMenuLifetime.destroy();
if (hasWhoReactedItem && !clickedEmoji.isEmpty()) {
HistoryView::ShowWhoReactedMenu(
&_menu,
e->globalPos(),
this,
_dragStateItem,
clickedEmoji,
_controller,
_whoReactedMenuLifetime);
e->accept();
return;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
hasWhoReactedItem ? st::whoReadMenu : st::popupMenuWithIcons);

View file

@ -469,6 +469,8 @@ private:
Ui::Animations::Simple _spoilerOpacity;
// _menu must be destroyed before _whoReactedMenuLifetime.
rpl::lifetime _whoReactedMenuLifetime;
base::unique_qptr<Ui::PopupMenu> _menu;
bool _scrollDateShown = false;

View file

@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "data/data_scheduled_messages.h"
#include "data/data_message_reactions.h"
#include "core/file_utilities.h"
#include "core/click_handler_types.h"
#include "base/platform/base_platform_info.h"
@ -1107,6 +1108,67 @@ void AddWhoReactedAction(
showAllChosen));
}
void ShowWhoReactedMenu(
not_null<base::unique_qptr<Ui::PopupMenu>*> menu,
QPoint position,
not_null<QWidget*> context,
not_null<HistoryItem*> item,
const QString &emoji,
not_null<Window::SessionController*> controller,
rpl::lifetime &lifetime) {
const auto participantChosen = [=](uint64 id) {
controller->showPeerInfo(PeerId(id));
};
const auto showAllChosen = [=, itemId = item->fullId()]{
if (const auto item = controller->session().data().message(itemId)) {
controller->window().show(ReactionsListBox(
controller,
item,
emoji));
}
};
const auto reactions = &controller->session().data().reactions();
const auto &list = reactions->list(
Data::Reactions::Type::Active);
const auto active = ranges::contains(
list,
emoji,
&Data::Reaction::emoji);
const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>(
participantChosen,
showAllChosen);
Api::WhoReacted(
item,
emoji,
context,
st::defaultWhoRead
) | rpl::filter([=](const Ui::WhoReadContent &content) {
return !content.unknown;
}) | rpl::start_with_next([=, &lifetime](Ui::WhoReadContent &&content) {
const auto creating = !*menu;
const auto refill = [=] {
if (active) {
(*menu)->addAction(tr::lng_context_set_as_quick(tr::now), [=] {
reactions->setFavorite(emoji);
}, &st::menuIconFave);
(*menu)->addSeparator();
}
};
if (creating) {
*menu = base::make_unique_q<Ui::PopupMenu>(
context,
st::whoReadMenu);
(*menu)->lifetime().add(base::take(lifetime));
refill();
}
filler->populate(menu->get(), content);
if (creating) {
(*menu)->popup(position);
}
}, lifetime);
}
void ShowReportItemsBox(not_null<PeerData*> peer, MessageIdsList ids) {
const auto chosen = [=](Ui::ReportReason reason) {
Ui::show(Box(Ui::ReportDetailsBox, [=](const QString &text) {

View file

@ -65,6 +65,14 @@ void AddWhoReactedAction(
not_null<QWidget*> context,
not_null<HistoryItem*> item,
not_null<Window::SessionController*> controller);
void ShowWhoReactedMenu(
not_null<base::unique_qptr<Ui::PopupMenu>*> menu,
QPoint position,
not_null<QWidget*> context,
not_null<HistoryItem*> item,
const QString &emoji,
not_null<Window::SessionController*> controller,
rpl::lifetime &lifetime);
void ShowReportItemsBox(not_null<PeerData*> peer, MessageIdsList ids);
void ShowReportPeerBox(

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "core/click_handler_types.h"
#include "apiwrap.h"
#include "api/api_who_reacted.h"
#include "layout/layout_selection.h"
#include "window/window_adaptive.h"
#include "window/window_session_controller.h"
@ -2098,16 +2099,35 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&& _reactionsManager->showContextMenu(this, e)) {
return;
}
const auto overItem = _overItemExact
? _overItemExact
: _overElement
? _overElement->data().get()
: nullptr;
const auto hasWhoReactedItem = overItem
&& Api::WhoReactedExists(overItem);
const auto clickedEmoji = link
? link->property(kReactionsCountEmojiProperty).toString()
: QString();
_whoReactedMenuLifetime.destroy();
if (hasWhoReactedItem && !clickedEmoji.isEmpty()) {
HistoryView::ShowWhoReactedMenu(
&_menu,
e->globalPos(),
this,
overItem,
clickedEmoji,
_controller,
_whoReactedMenuLifetime);
e->accept();
return;
}
auto request = ContextMenuRequest(_controller);
request.link = link;
request.view = _overElement;
request.item = _overItemExact
? _overItemExact
: _overElement
? _overElement->data().get()
: nullptr;
request.item = overItem;
request.pointState = _overState.pointState;
request.selectedText = _selectedText;
request.selectedItems = collectSelectedItems();

View file

@ -614,6 +614,8 @@ private:
bool _isChatWide = false;
// _menu must be destroyed before _whoReactedMenuLifetime.
rpl::lifetime _whoReactedMenuLifetime;
base::unique_qptr<Ui::PopupMenu> _menu;
QPoint _trippleClickPoint;

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_react_animation.h"
#include "history/view/history_view_group_call_bar.h"
#include "core/click_handler_types.h"
#include "data/data_message_reactions.h"
#include "data/data_user.h"
#include "lang/lang_tag.h"
@ -349,6 +350,9 @@ bool InlineList::getState(
if (button.geometry.contains(point)) {
if (!button.link) {
button.link = _handlerFactory(button.emoji);
button.link->setProperty(
kReactionsCountEmojiProperty,
button.emoji);
_owner->preloadAnimationsFor(button.emoji);
}
outResult->link = button.link;

View file

@ -26,34 +26,6 @@ struct EntryData {
Fn<void()> callback;
};
class EntryAction final : public Menu::ItemBase {
public:
EntryAction(
not_null<RpWidget*> parent,
const style::Menu &st,
EntryData &&data);
void setData(EntryData &&data);
not_null<QAction*> action() const override;
bool isEnabled() const override;
private:
int contentHeight() const override;
void paint(Painter &&p);
const not_null<QAction*> _dummyAction;
const style::Menu &_st;
const int _height = 0;
Text::String _text;
EmojiPtr _emoji = nullptr;
int _textWidth = 0;
QImage _userpic;
};
class Action final : public Menu::ItemBase {
public:
Action(
@ -89,7 +61,7 @@ private:
const std::unique_ptr<GroupCallUserpics> _userpics;
const style::Menu &_st;
std::vector<not_null<EntryAction*>> _submenuActions;
WhoReactedListMenu _submenu;
Text::String _text;
int _textWidth = 0;
@ -108,106 +80,6 @@ TextParseOptions MenuTextOptions = {
Qt::LayoutDirectionAuto, // dir
};
EntryAction::EntryAction(
not_null<RpWidget*> parent,
const style::Menu &st,
EntryData &&data)
: ItemBase(parent, st)
, _dummyAction(CreateChild<QAction>(parent.get()))
, _st(st)
, _height(st::defaultWhoRead.photoSkip * 2 + st::defaultWhoRead.photoSize) {
setAcceptBoth(true);
initResizeHook(parent->sizeValue());
setData(std::move(data));
paintRequest(
) | rpl::start_with_next([=] {
paint(Painter(this));
}, lifetime());
enableMouseSelecting();
}
not_null<QAction*> EntryAction::action() const {
return _dummyAction.get();
}
bool EntryAction::isEnabled() const {
return true;
}
int EntryAction::contentHeight() const {
return _height;
}
void EntryAction::setData(EntryData &&data) {
setClickedCallback(std::move(data.callback));
_userpic = std::move(data.userpic);
_text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions);
_emoji = Emoji::Find(data.reaction);
const auto textWidth = _text.maxWidth();
const auto &padding = _st.itemPadding;
const auto rightSkip = padding.right()
+ (_emoji
? ((Emoji::GetSizeNormal() / style::DevicePixelRatio())
+ padding.right())
: 0);
const auto goodWidth = st::defaultWhoRead.nameLeft
+ textWidth
+ rightSkip;
const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
_textWidth = w - (goodWidth - textWidth);
setMinWidth(w);
update();
}
void EntryAction::paint(Painter &&p) {
const auto enabled = isEnabled();
const auto selected = isSelected();
if (selected && _st.itemBgOver->c.alpha() < 255) {
p.fillRect(0, 0, width(), _height, _st.itemBg);
}
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
if (enabled) {
paintRipple(p, 0, 0);
}
const auto photoSize = st::defaultWhoRead.photoSize;
const auto photoLeft = st::defaultWhoRead.photoLeft;
const auto photoTop = (height() - photoSize) / 2;
if (!_userpic.isNull()) {
p.drawImage(photoLeft, photoTop, _userpic);
} else if (!_emoji) {
st::menuIconReactions.paintInCenter(
p,
QRect(photoLeft, photoTop, photoSize, photoSize));
}
p.setPen(selected
? _st.itemFgOver
: enabled
? _st.itemFg
: _st.itemFgDisabled);
_text.drawLeftElided(
p,
st::defaultWhoRead.nameLeft,
(height() - _st.itemStyle.font->height) / 2,
_textWidth,
width());
if (_emoji) {
// #TODO reactions
const auto size = Emoji::GetSizeNormal();
const auto ratio = style::DevicePixelRatio();
Emoji::Draw(
p,
_emoji,
size,
width() - _st.itemPadding.right() - (size / ratio),
(height() - (size / ratio)) / 2);
}
}
Action::Action(
not_null<PopupMenu*> parentMenu,
rpl::producer<WhoReadContent> content,
@ -223,6 +95,7 @@ Action::Action(
rpl::never<bool>(),
[=] { update(); }))
, _st(parentMenu->menu()->st())
, _submenu(_participantChosen, _showAllChosen)
, _height(st::defaultWhoRead.itemPadding.top()
+ _st.itemStyle.font->height
+ st::defaultWhoRead.itemPadding.bottom()) {
@ -344,7 +217,7 @@ void Action::updateUserpicsFromContent() {
void Action::populateSubmenu() {
if (_content.participants.size() < 2) {
_submenuActions.clear();
_submenu.clear();
_parentMenu->removeSubmenu(action());
if (!isEnabled()) {
setSelected(false);
@ -353,47 +226,7 @@ void Action::populateSubmenu() {
}
const auto submenu = _parentMenu->ensureSubmenu(action());
const auto reactions = ranges::count_if(
_content.participants,
[](const auto &p) { return !p.reaction.isEmpty(); });
const auto addShowAll = (_content.fullReactionsCount > reactions);
const auto actionsCount = int(_content.participants.size())
+ (addShowAll ? 1 : 0);
if (_submenuActions.size() > actionsCount) {
_submenuActions.clear();
submenu->clearActions();
}
auto index = 0;
const auto append = [&](EntryData &&data) {
if (index < _submenuActions.size()) {
_submenuActions[index]->setData(std::move(data));
} else {
auto item = base::make_unique_q<EntryAction>(
submenu->menu(),
_st,
std::move(data));
_submenuActions.push_back(item.get());
submenu->addAction(std::move(item));
}
++index;
};
for (const auto &participant : _content.participants) {
const auto chosen = [call = _participantChosen, id = participant.id] {
call(id);
};
append({
.text = participant.name,
.reaction = participant.reaction,
.userpic = participant.userpicLarge,
.callback = chosen,
});
}
if (addShowAll) {
append({
.text = tr::lng_context_seen_reacted_all(tr::now),
.callback = _showAllChosen,
});
}
_submenu.populate(submenu, _content);
_parentMenu->checkSubmenuShow();
}
@ -537,6 +370,134 @@ void Action::handleKeyPress(not_null<QKeyEvent*> e) {
} // namespace
class WhoReactedListMenu::EntryAction final : public Menu::ItemBase {
public:
EntryAction(
not_null<RpWidget*> parent,
const style::Menu &st,
EntryData &&data);
void setData(EntryData &&data);
not_null<QAction*> action() const override;
bool isEnabled() const override;
private:
int contentHeight() const override;
void paint(Painter &&p);
const not_null<QAction*> _dummyAction;
const style::Menu &_st;
const int _height = 0;
Text::String _text;
EmojiPtr _emoji = nullptr;
int _textWidth = 0;
QImage _userpic;
};
WhoReactedListMenu::EntryAction::EntryAction(
not_null<RpWidget*> parent,
const style::Menu &st,
EntryData &&data)
: ItemBase(parent, st)
, _dummyAction(CreateChild<QAction>(parent.get()))
, _st(st)
, _height(st::defaultWhoRead.photoSkip * 2 + st::defaultWhoRead.photoSize) {
setAcceptBoth(true);
initResizeHook(parent->sizeValue());
setData(std::move(data));
paintRequest(
) | rpl::start_with_next([=] {
paint(Painter(this));
}, lifetime());
enableMouseSelecting();
}
not_null<QAction*> WhoReactedListMenu::EntryAction::action() const {
return _dummyAction.get();
}
bool WhoReactedListMenu::EntryAction::isEnabled() const {
return true;
}
int WhoReactedListMenu::EntryAction::contentHeight() const {
return _height;
}
void WhoReactedListMenu::EntryAction::setData(EntryData &&data) {
setClickedCallback(std::move(data.callback));
_userpic = std::move(data.userpic);
_text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions);
_emoji = Emoji::Find(data.reaction);
const auto textWidth = _text.maxWidth();
const auto &padding = _st.itemPadding;
const auto rightSkip = padding.right()
+ (_emoji
? ((Emoji::GetSizeNormal() / style::DevicePixelRatio())
+ padding.right())
: 0);
const auto goodWidth = st::defaultWhoRead.nameLeft
+ textWidth
+ rightSkip;
const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
_textWidth = w - (goodWidth - textWidth);
setMinWidth(w);
update();
}
void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
const auto enabled = isEnabled();
const auto selected = isSelected();
if (selected && _st.itemBgOver->c.alpha() < 255) {
p.fillRect(0, 0, width(), _height, _st.itemBg);
}
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
if (enabled) {
paintRipple(p, 0, 0);
}
const auto photoSize = st::defaultWhoRead.photoSize;
const auto photoLeft = st::defaultWhoRead.photoLeft;
const auto photoTop = (height() - photoSize) / 2;
if (!_userpic.isNull()) {
p.drawImage(photoLeft, photoTop, _userpic);
} else if (!_emoji) {
st::menuIconReactions.paintInCenter(
p,
QRect(photoLeft, photoTop, photoSize, photoSize));
}
p.setPen(selected
? _st.itemFgOver
: enabled
? _st.itemFg
: _st.itemFgDisabled);
_text.drawLeftElided(
p,
st::defaultWhoRead.nameLeft,
(height() - _st.itemStyle.font->height) / 2,
_textWidth,
width());
if (_emoji) {
// #TODO reactions
const auto size = Emoji::GetSizeNormal();
const auto ratio = style::DevicePixelRatio();
Emoji::Draw(
p,
_emoji,
size,
width() - _st.itemPadding.right() - (size / ratio),
(height() - (size / ratio)) / 2);
}
}
bool operator==(const WhoReadParticipant &a, const WhoReadParticipant &b) {
return (a.id == b.id)
&& (a.name == b.name)
@ -559,4 +520,66 @@ base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
std::move(showAllChosen));
}
WhoReactedListMenu::WhoReactedListMenu(
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen)
: _participantChosen(std::move(participantChosen))
, _showAllChosen(std::move(showAllChosen)) {
}
void WhoReactedListMenu::clear() {
_actions.clear();
}
void WhoReactedListMenu::populate(
not_null<PopupMenu*> menu,
const WhoReadContent &content,
Fn<void()> refillTopActions) {
const auto reactions = ranges::count_if(
content.participants,
[](const auto &p) { return !p.reaction.isEmpty(); });
const auto addShowAll = (content.fullReactionsCount > reactions);
const auto actionsCount = int(content.participants.size())
+ (addShowAll ? 1 : 0);
if (_actions.size() > actionsCount) {
_actions.clear();
menu->clearActions();
if (refillTopActions) {
refillTopActions();
}
}
auto index = 0;
const auto append = [&](EntryData &&data) {
if (index < _actions.size()) {
_actions[index]->setData(std::move(data));
} else {
auto item = base::make_unique_q<EntryAction>(
menu->menu(),
menu->menu()->st(),
std::move(data));
_actions.push_back(item.get());
menu->addAction(std::move(item));
}
++index;
};
for (const auto &participant : content.participants) {
const auto chosen = [call = _participantChosen, id = participant.id]{
call(id);
};
append({
.text = participant.name,
.reaction = participant.reaction,
.userpic = participant.userpicLarge,
.callback = chosen,
});
}
if (addShowAll) {
append({
.text = tr::lng_context_seen_reacted_all(tr::now),
.callback = _showAllChosen,
});
}
}
} // namespace Ui

View file

@ -51,4 +51,26 @@ struct WhoReadContent {
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen);
class WhoReactedListMenu final {
public:
WhoReactedListMenu(
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen);
void clear();
void populate(
not_null<PopupMenu*> menu,
const WhoReadContent &content,
Fn<void()> refillTopActions = nullptr);
private:
class EntryAction;
const Fn<void(uint64)> _participantChosen;
const Fn<void()> _showAllChosen;
std::vector<not_null<EntryAction*>> _actions;
};
} // namespace Ui