mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Reacted users list on inline reaction right click.
This commit is contained in:
parent
df044dbd83
commit
1060b04b1e
11 changed files with 396 additions and 201 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue