mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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 {
|
struct Context {
|
||||||
base::flat_map<not_null<HistoryItem*>, CachedRead> cachedRead;
|
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;
|
base::flat_map<not_null<Main::Session*>, rpl::lifetime> subscriptions;
|
||||||
|
|
||||||
[[nodiscard]] CachedRead &cacheRead(not_null<HistoryItem*> item) {
|
[[nodiscard]] CachedRead &cacheRead(not_null<HistoryItem*> item) {
|
||||||
|
@ -91,12 +93,15 @@ struct Context {
|
||||||
return cachedRead.emplace(item, CachedRead()).first->second;
|
return cachedRead.emplace(item, CachedRead()).first->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] CachedReacted &cacheReacted(not_null<HistoryItem*> item) {
|
[[nodiscard]] CachedReacted &cacheReacted(
|
||||||
const auto i = cachedReacted.find(item);
|
not_null<HistoryItem*> item,
|
||||||
if (i != end(cachedReacted)) {
|
const QString &reaction) {
|
||||||
|
auto &map = cachedReacted[item];
|
||||||
|
const auto i = map.find(reaction);
|
||||||
|
if (i != end(map)) {
|
||||||
return i->second;
|
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();
|
item->history()->session().api().request(requestId).cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto &[item, entry] : i->second->cachedReacted) {
|
for (auto &[item, map] : i->second->cachedReacted) {
|
||||||
if (const auto requestId = entry.requestId) {
|
for (auto &[reaction, entry] : map) {
|
||||||
item->history()->session().api().request(requestId).cancel();
|
if (const auto requestId = entry.requestId) {
|
||||||
|
item->history()->session().api().request(requestId).cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contexts.erase(i);
|
contexts.erase(i);
|
||||||
|
@ -150,7 +157,9 @@ struct State {
|
||||||
return result;
|
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);
|
const auto context = ContextAt(key);
|
||||||
if (context->subscriptions.contains(session)) {
|
if (context->subscriptions.contains(session)) {
|
||||||
return context;
|
return context;
|
||||||
|
@ -165,7 +174,9 @@ struct State {
|
||||||
}
|
}
|
||||||
const auto j = context->cachedReacted.find(update.item);
|
const auto j = context->cachedReacted.find(update.item);
|
||||||
if (j != end(context->cachedReacted)) {
|
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->cachedReacted.erase(j);
|
||||||
}
|
}
|
||||||
}, context->subscriptions[session]);
|
}, context->subscriptions[session]);
|
||||||
|
@ -244,6 +255,7 @@ struct State {
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
|
const QString &reaction,
|
||||||
not_null<QWidget*> context) {
|
not_null<QWidget*> context) {
|
||||||
auto weak = QPointer<QWidget>(context.get());
|
auto weak = QPointer<QWidget>(context.get());
|
||||||
const auto session = &item->history()->session();
|
const auto session = &item->history()->session();
|
||||||
|
@ -252,19 +264,22 @@ struct State {
|
||||||
return rpl::lifetime();
|
return rpl::lifetime();
|
||||||
}
|
}
|
||||||
const auto context = PreparedContextAt(weak.data(), session);
|
const auto context = PreparedContextAt(weak.data(), session);
|
||||||
auto &entry = context->cacheReacted(item);
|
auto &entry = context->cacheReacted(item, reaction);
|
||||||
if (!entry.requestId) {
|
if (!entry.requestId) {
|
||||||
|
using Flag = MTPmessages_GetMessageReactionsList::Flag;
|
||||||
entry.requestId = session->api().request(
|
entry.requestId = session->api().request(
|
||||||
MTPmessages_GetMessageReactionsList(
|
MTPmessages_GetMessageReactionsList(
|
||||||
MTP_flags(0),
|
MTP_flags(reaction.isEmpty()
|
||||||
|
? Flag(0)
|
||||||
|
: Flag::f_reaction),
|
||||||
item->history()->peer->input,
|
item->history()->peer->input,
|
||||||
MTP_int(item->id),
|
MTP_int(item->id),
|
||||||
MTPstring(), // reaction
|
MTP_string(reaction),
|
||||||
MTPstring(), // offset
|
MTPstring(), // offset
|
||||||
MTP_int(kContextReactionsLimit)
|
MTP_int(kContextReactionsLimit)
|
||||||
)
|
)
|
||||||
).done([=](const MTPmessages_MessageReactionsList &result) {
|
).done([=](const MTPmessages_MessageReactionsList &result) {
|
||||||
auto &entry = context->cacheReacted(item);
|
auto &entry = context->cacheReacted(item, reaction);
|
||||||
entry.requestId = 0;
|
entry.requestId = 0;
|
||||||
|
|
||||||
result.match([&](
|
result.match([&](
|
||||||
|
@ -286,7 +301,7 @@ struct State {
|
||||||
entry.data = std::move(parsed);
|
entry.data = std::move(parsed);
|
||||||
});
|
});
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
auto &entry = context->cacheReacted(item);
|
auto &entry = context->cacheReacted(item, reaction);
|
||||||
entry.requestId = 0;
|
entry.requestId = 0;
|
||||||
if (entry.data.current().unknown) {
|
if (entry.data.current().unknown) {
|
||||||
entry.data = PeersWithReactions();
|
entry.data = PeersWithReactions();
|
||||||
|
@ -302,7 +317,7 @@ struct State {
|
||||||
not_null<QWidget*> context)
|
not_null<QWidget*> context)
|
||||||
-> rpl::producer<PeersWithReactions> {
|
-> rpl::producer<PeersWithReactions> {
|
||||||
return rpl::combine(
|
return rpl::combine(
|
||||||
WhoReactedIds(item, 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) {
|
||||||
|
@ -468,37 +483,52 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 small = st.userpics.size;
|
||||||
const auto large = st.photoSize;
|
const auto large = st.photoSize;
|
||||||
return [=](auto consumer) {
|
return [=](auto consumer) {
|
||||||
auto lifetime = rpl::lifetime();
|
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 state = lifetime.make_state<State>();
|
||||||
const auto pushNext = [=] {
|
const auto pushNext = [=] {
|
||||||
consumer.put_next_copy(state->current);
|
consumer.put_next_copy(state->current);
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto resolveWhoReacted = item->canViewReactions();
|
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||||
|
|| item->canViewReactions();
|
||||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||||
? WhoReadOrReactedIds(item, context)
|
? WhoReadOrReactedIds(item, context)
|
||||||
: resolveWhoRead
|
: resolveWhoRead
|
||||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||||
: WhoReactedIds(item, context);
|
: WhoReactedIds(item, reaction, context);
|
||||||
state->current.type = resolveWhoRead
|
state->current.type = resolveWhoRead
|
||||||
? DetectSeenType(item)
|
? DetectSeenType(item)
|
||||||
: Ui::WhoReadType::Reacted;
|
: Ui::WhoReadType::Reacted;
|
||||||
if (resolveWhoReacted) {
|
if (resolveWhoReacted) {
|
||||||
const auto &list = item->reactions();
|
const auto &list = item->reactions();
|
||||||
state->current.fullReactionsCount = ranges::accumulate(
|
state->current.fullReactionsCount = reaction.isEmpty()
|
||||||
list,
|
? ranges::accumulate(
|
||||||
0,
|
list,
|
||||||
ranges::plus{},
|
0,
|
||||||
[](const auto &pair) { return pair.second; });
|
ranges::plus{},
|
||||||
|
[](const auto &pair) { return pair.second; })
|
||||||
|
: list.contains(reaction)
|
||||||
|
? list.find(reaction)->second
|
||||||
|
: 0;
|
||||||
|
|
||||||
// #TODO reactions
|
// #TODO reactions
|
||||||
state->current.singleReaction = (list.size() == 1)
|
state->current.singleReaction = !reaction.isEmpty()
|
||||||
|
? reaction
|
||||||
|
: (list.size() == 1)
|
||||||
? list.front().first
|
? list.front().first
|
||||||
: QString();
|
: QString();
|
||||||
}
|
}
|
||||||
|
@ -509,6 +539,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
state->userpics.clear();
|
state->userpics.clear();
|
||||||
consumer.put_next(Ui::WhoReadContent{
|
consumer.put_next(Ui::WhoReadContent{
|
||||||
.type = state->current.type,
|
.type = state->current.type,
|
||||||
|
.fullReactionsCount = state->current.fullReactionsCount,
|
||||||
.unknown = true,
|
.unknown = true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -27,5 +27,10 @@ namespace Api {
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
not_null<QWidget*> context,
|
not_null<QWidget*> context,
|
||||||
const style::WhoRead &st); // Cache results for this lifetime.
|
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
|
} // namespace Api
|
||||||
|
|
|
@ -1892,6 +1892,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
|
|
||||||
const auto hasWhoReactedItem = _dragStateItem
|
const auto hasWhoReactedItem = _dragStateItem
|
||||||
&& Api::WhoReactedExists(_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>(
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
this,
|
this,
|
||||||
hasWhoReactedItem ? st::whoReadMenu : st::popupMenuWithIcons);
|
hasWhoReactedItem ? st::whoReadMenu : st::popupMenuWithIcons);
|
||||||
|
|
|
@ -469,6 +469,8 @@ private:
|
||||||
|
|
||||||
Ui::Animations::Simple _spoilerOpacity;
|
Ui::Animations::Simple _spoilerOpacity;
|
||||||
|
|
||||||
|
// _menu must be destroyed before _whoReactedMenuLifetime.
|
||||||
|
rpl::lifetime _whoReactedMenuLifetime;
|
||||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
|
||||||
bool _scrollDateShown = false;
|
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_click_handler.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_scheduled_messages.h"
|
#include "data/data_scheduled_messages.h"
|
||||||
|
#include "data/data_message_reactions.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
|
@ -1107,6 +1108,67 @@ void AddWhoReactedAction(
|
||||||
showAllChosen));
|
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) {
|
void ShowReportItemsBox(not_null<PeerData*> peer, MessageIdsList ids) {
|
||||||
const auto chosen = [=](Ui::ReportReason reason) {
|
const auto chosen = [=](Ui::ReportReason reason) {
|
||||||
Ui::show(Box(Ui::ReportDetailsBox, [=](const QString &text) {
|
Ui::show(Box(Ui::ReportDetailsBox, [=](const QString &text) {
|
||||||
|
|
|
@ -65,6 +65,14 @@ 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);
|
||||||
|
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 ShowReportItemsBox(not_null<PeerData*> peer, MessageIdsList ids);
|
||||||
void ShowReportPeerBox(
|
void ShowReportPeerBox(
|
||||||
|
|
|
@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "api/api_who_reacted.h"
|
||||||
#include "layout/layout_selection.h"
|
#include "layout/layout_selection.h"
|
||||||
#include "window/window_adaptive.h"
|
#include "window/window_adaptive.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
@ -2098,16 +2099,35 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
&& _reactionsManager->showContextMenu(this, e)) {
|
&& _reactionsManager->showContextMenu(this, e)) {
|
||||||
return;
|
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);
|
auto request = ContextMenuRequest(_controller);
|
||||||
|
|
||||||
request.link = link;
|
request.link = link;
|
||||||
request.view = _overElement;
|
request.view = _overElement;
|
||||||
request.item = _overItemExact
|
request.item = overItem;
|
||||||
? _overItemExact
|
|
||||||
: _overElement
|
|
||||||
? _overElement->data().get()
|
|
||||||
: nullptr;
|
|
||||||
request.pointState = _overState.pointState;
|
request.pointState = _overState.pointState;
|
||||||
request.selectedText = _selectedText;
|
request.selectedText = _selectedText;
|
||||||
request.selectedItems = collectSelectedItems();
|
request.selectedItems = collectSelectedItems();
|
||||||
|
|
|
@ -614,6 +614,8 @@ private:
|
||||||
|
|
||||||
bool _isChatWide = false;
|
bool _isChatWide = false;
|
||||||
|
|
||||||
|
// _menu must be destroyed before _whoReactedMenuLifetime.
|
||||||
|
rpl::lifetime _whoReactedMenuLifetime;
|
||||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
|
||||||
QPoint _trippleClickPoint;
|
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_cursor_state.h"
|
||||||
#include "history/view/history_view_react_animation.h"
|
#include "history/view/history_view_react_animation.h"
|
||||||
#include "history/view/history_view_group_call_bar.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_message_reactions.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "lang/lang_tag.h"
|
#include "lang/lang_tag.h"
|
||||||
|
@ -349,6 +350,9 @@ bool InlineList::getState(
|
||||||
if (button.geometry.contains(point)) {
|
if (button.geometry.contains(point)) {
|
||||||
if (!button.link) {
|
if (!button.link) {
|
||||||
button.link = _handlerFactory(button.emoji);
|
button.link = _handlerFactory(button.emoji);
|
||||||
|
button.link->setProperty(
|
||||||
|
kReactionsCountEmojiProperty,
|
||||||
|
button.emoji);
|
||||||
_owner->preloadAnimationsFor(button.emoji);
|
_owner->preloadAnimationsFor(button.emoji);
|
||||||
}
|
}
|
||||||
outResult->link = button.link;
|
outResult->link = button.link;
|
||||||
|
|
|
@ -26,34 +26,6 @@ struct EntryData {
|
||||||
Fn<void()> callback;
|
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 {
|
class Action final : public Menu::ItemBase {
|
||||||
public:
|
public:
|
||||||
Action(
|
Action(
|
||||||
|
@ -89,7 +61,7 @@ private:
|
||||||
const std::unique_ptr<GroupCallUserpics> _userpics;
|
const std::unique_ptr<GroupCallUserpics> _userpics;
|
||||||
const style::Menu &_st;
|
const style::Menu &_st;
|
||||||
|
|
||||||
std::vector<not_null<EntryAction*>> _submenuActions;
|
WhoReactedListMenu _submenu;
|
||||||
|
|
||||||
Text::String _text;
|
Text::String _text;
|
||||||
int _textWidth = 0;
|
int _textWidth = 0;
|
||||||
|
@ -108,106 +80,6 @@ TextParseOptions MenuTextOptions = {
|
||||||
Qt::LayoutDirectionAuto, // dir
|
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(
|
Action::Action(
|
||||||
not_null<PopupMenu*> parentMenu,
|
not_null<PopupMenu*> parentMenu,
|
||||||
rpl::producer<WhoReadContent> content,
|
rpl::producer<WhoReadContent> content,
|
||||||
|
@ -223,6 +95,7 @@ Action::Action(
|
||||||
rpl::never<bool>(),
|
rpl::never<bool>(),
|
||||||
[=] { update(); }))
|
[=] { update(); }))
|
||||||
, _st(parentMenu->menu()->st())
|
, _st(parentMenu->menu()->st())
|
||||||
|
, _submenu(_participantChosen, _showAllChosen)
|
||||||
, _height(st::defaultWhoRead.itemPadding.top()
|
, _height(st::defaultWhoRead.itemPadding.top()
|
||||||
+ _st.itemStyle.font->height
|
+ _st.itemStyle.font->height
|
||||||
+ st::defaultWhoRead.itemPadding.bottom()) {
|
+ st::defaultWhoRead.itemPadding.bottom()) {
|
||||||
|
@ -344,7 +217,7 @@ void Action::updateUserpicsFromContent() {
|
||||||
|
|
||||||
void Action::populateSubmenu() {
|
void Action::populateSubmenu() {
|
||||||
if (_content.participants.size() < 2) {
|
if (_content.participants.size() < 2) {
|
||||||
_submenuActions.clear();
|
_submenu.clear();
|
||||||
_parentMenu->removeSubmenu(action());
|
_parentMenu->removeSubmenu(action());
|
||||||
if (!isEnabled()) {
|
if (!isEnabled()) {
|
||||||
setSelected(false);
|
setSelected(false);
|
||||||
|
@ -353,47 +226,7 @@ void Action::populateSubmenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto submenu = _parentMenu->ensureSubmenu(action());
|
const auto submenu = _parentMenu->ensureSubmenu(action());
|
||||||
const auto reactions = ranges::count_if(
|
_submenu.populate(submenu, _content);
|
||||||
_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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_parentMenu->checkSubmenuShow();
|
_parentMenu->checkSubmenuShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,6 +370,134 @@ void Action::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||||
|
|
||||||
} // namespace
|
} // 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) {
|
bool operator==(const WhoReadParticipant &a, const WhoReadParticipant &b) {
|
||||||
return (a.id == b.id)
|
return (a.id == b.id)
|
||||||
&& (a.name == b.name)
|
&& (a.name == b.name)
|
||||||
|
@ -559,4 +520,66 @@ base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
||||||
std::move(showAllChosen));
|
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
|
} // namespace Ui
|
||||||
|
|
|
@ -51,4 +51,26 @@ struct WhoReadContent {
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen);
|
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
|
} // namespace Ui
|
||||||
|
|
Loading…
Add table
Reference in a new issue