mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-23 08:46:55 +00:00
Show latest reacted in context menu.
This commit is contained in:
parent
fe468ce7e9
commit
79a09a4510
@ -153,8 +153,8 @@ PRIVATE
|
||||
api/api_user_privacy.h
|
||||
api/api_views.cpp
|
||||
api/api_views.h
|
||||
api/api_who_read.cpp
|
||||
api/api_who_read.h
|
||||
api/api_who_reacted.cpp
|
||||
api/api_who_reacted.h
|
||||
boxes/filters/edit_filter_box.cpp
|
||||
boxes/filters/edit_filter_box.h
|
||||
boxes/filters/edit_filter_chats_list.cpp
|
||||
|
@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_who_read.h"
|
||||
#include "api/api_who_reacted.h"
|
||||
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
@ -22,39 +22,71 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_account.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "ui/controls/who_read_context_action.h"
|
||||
#include "ui/controls/who_reacted_context_action.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
struct Cached {
|
||||
explicit Cached(PeerId unknownFlag)
|
||||
constexpr auto kContextReactionsLimit = 50;
|
||||
|
||||
struct PeerWithReaction {
|
||||
PeerId peer = 0;
|
||||
QString reaction;
|
||||
};
|
||||
bool operator==(const PeerWithReaction &a, const PeerWithReaction &b) {
|
||||
return (a.peer == b.peer) && (a.reaction == b.reaction);
|
||||
}
|
||||
|
||||
struct CachedRead {
|
||||
explicit CachedRead(PeerId unknownFlag)
|
||||
: list(std::vector<PeerId>{ unknownFlag }) {
|
||||
}
|
||||
rpl::variable<std::vector<PeerId>> list;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
struct CachedReacted {
|
||||
explicit CachedReacted(PeerId unknownFlag)
|
||||
: list(
|
||||
std::vector<PeerWithReaction>{ PeerWithReaction{ unknownFlag } }) {
|
||||
}
|
||||
rpl::variable<std::vector<PeerWithReaction>> list;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
base::flat_map<not_null<HistoryItem*>, Cached> cached;
|
||||
base::flat_map<not_null<HistoryItem*>, CachedRead> cachedRead;
|
||||
base::flat_map<not_null<HistoryItem*>, CachedReacted> cachedReacted;
|
||||
base::flat_map<not_null<Main::Session*>, rpl::lifetime> subscriptions;
|
||||
|
||||
[[nodiscard]] Cached &cache(not_null<HistoryItem*> item) {
|
||||
const auto i = cached.find(item);
|
||||
if (i != end(cached)) {
|
||||
[[nodiscard]] CachedRead &cacheRead(not_null<HistoryItem*> item) {
|
||||
const auto i = cachedRead.find(item);
|
||||
if (i != end(cachedRead)) {
|
||||
return i->second;
|
||||
}
|
||||
return cached.emplace(
|
||||
return cachedRead.emplace(
|
||||
item,
|
||||
Cached(item->history()->session().userPeerId())
|
||||
CachedRead(item->history()->session().userPeerId())
|
||||
).first->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] CachedReacted &cacheReacted(not_null<HistoryItem*> item) {
|
||||
const auto i = cachedReacted.find(item);
|
||||
if (i != end(cachedReacted)) {
|
||||
return i->second;
|
||||
}
|
||||
return cachedReacted.emplace(
|
||||
item,
|
||||
CachedReacted(item->history()->session().userPeerId())
|
||||
).first->second;
|
||||
}
|
||||
};
|
||||
|
||||
struct Userpic {
|
||||
not_null<PeerData*> peer;
|
||||
QString reaction;
|
||||
mutable std::shared_ptr<Data::CloudImageView> view;
|
||||
mutable InMemoryKey uniqueKey;
|
||||
};
|
||||
@ -87,7 +119,12 @@ struct State {
|
||||
QObject::connect(key.get(), &QObject::destroyed, [=] {
|
||||
auto &contexts = Contexts();
|
||||
const auto i = contexts.find(key);
|
||||
for (auto &[item, entry] : i->second->cached) {
|
||||
for (auto &[item, entry] : i->second->cachedRead) {
|
||||
if (const auto requestId = entry.requestId) {
|
||||
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();
|
||||
}
|
||||
@ -97,6 +134,28 @@ struct State {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[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;
|
||||
}
|
||||
session->changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::Destroyed
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
const auto i = context->cachedRead.find(update.item);
|
||||
if (i != end(context->cachedRead)) {
|
||||
session->api().request(i->second.requestId).cancel();
|
||||
context->cachedRead.erase(i);
|
||||
}
|
||||
const auto j = context->cachedReacted.find(update.item);
|
||||
if (j != end(context->cachedReacted)) {
|
||||
session->api().request(j->second.requestId).cancel();
|
||||
context->cachedReacted.erase(j);
|
||||
}
|
||||
}, context->subscriptions[session]);
|
||||
return context;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage GenerateUserpic(Userpic &userpic, int size) {
|
||||
size *= style::DevicePixelRatio();
|
||||
auto result = userpic.peer->generateUserpicImage(userpic.view, size);
|
||||
@ -111,6 +170,14 @@ struct State {
|
||||
&& (list.front() == item->history()->session().userPeerId());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ListUnknown(
|
||||
const std::vector<PeerWithReaction> &list,
|
||||
not_null<HistoryItem*> item) {
|
||||
return (list.size() == 1)
|
||||
&& list.front().reaction.isEmpty()
|
||||
&& (list.front().peer == item->history()->session().userPeerId());
|
||||
}
|
||||
|
||||
[[nodiscard]] Ui::WhoReadType DetectType(not_null<HistoryItem*> item) {
|
||||
if (const auto media = item->media()) {
|
||||
if (!media->webpage()) {
|
||||
@ -135,20 +202,8 @@ struct State {
|
||||
if (!weak) {
|
||||
return rpl::lifetime();
|
||||
}
|
||||
const auto context = ContextAt(weak.data());
|
||||
if (!context->subscriptions.contains(session)) {
|
||||
session->changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::Destroyed
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
const auto i = context->cached.find(update.item);
|
||||
if (i == end(context->cached)) {
|
||||
return;
|
||||
}
|
||||
session->api().request(i->second.requestId).cancel();
|
||||
context->cached.erase(i);
|
||||
}, context->subscriptions[session]);
|
||||
}
|
||||
auto &entry = context->cache(item);
|
||||
const auto context = PreparedContextAt(weak.data(), session);
|
||||
auto &entry = context->cacheRead(item);
|
||||
if (!entry.requestId) {
|
||||
entry.requestId = session->api().request(
|
||||
MTPmessages_GetMessageReadParticipants(
|
||||
@ -156,7 +211,7 @@ struct State {
|
||||
MTP_int(item->id)
|
||||
)
|
||||
).done([=](const MTPVector<MTPlong> &result) {
|
||||
auto &entry = context->cache(item);
|
||||
auto &entry = context->cacheRead(item);
|
||||
entry.requestId = 0;
|
||||
auto peers = std::vector<PeerId>();
|
||||
peers.reserve(std::max(int(result.v.size()), 1));
|
||||
@ -165,7 +220,7 @@ struct State {
|
||||
}
|
||||
entry.list = std::move(peers);
|
||||
}).fail([=] {
|
||||
auto &entry = context->cache(item);
|
||||
auto &entry = context->cacheRead(item);
|
||||
entry.requestId = 0;
|
||||
if (ListUnknown(entry.list.current(), item)) {
|
||||
entry.list = std::vector<PeerId>();
|
||||
@ -176,33 +231,123 @@ struct State {
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector < PeerWithReaction> WithEmptyReactions(
|
||||
const std::vector<PeerId> &peers) {
|
||||
return peers | ranges::views::transform([](PeerId peer) {
|
||||
return PeerWithReaction{ .peer = peer };
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<std::vector<PeerWithReaction>> WhoReactedIds(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context) {
|
||||
auto unknown = item->history()->session().userPeerId();
|
||||
auto weak = QPointer<QWidget>(context.get());
|
||||
const auto session = &item->history()->session();
|
||||
return [=](auto consumer) {
|
||||
if (!weak) {
|
||||
return rpl::lifetime();
|
||||
}
|
||||
const auto context = PreparedContextAt(weak.data(), session);
|
||||
auto &entry = context->cacheReacted(item);
|
||||
if (!entry.requestId) {
|
||||
entry.requestId = session->api().request(
|
||||
MTPmessages_GetMessageReactionsList(
|
||||
MTP_flags(0),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTPstring(), // reaction
|
||||
MTPstring(), // offset
|
||||
MTP_int(kContextReactionsLimit)
|
||||
)
|
||||
).done([=](const MTPmessages_MessageReactionsList &result) {
|
||||
auto &entry = context->cacheReacted(item);
|
||||
entry.requestId = 0;
|
||||
|
||||
result.match([&](
|
||||
const MTPDmessages_messageReactionsList &data) {
|
||||
session->data().processUsers(data.vusers());
|
||||
|
||||
auto peers = std::vector<PeerWithReaction>();
|
||||
peers.reserve(data.vreactions().v.size());
|
||||
for (const auto &vote : data.vreactions().v) {
|
||||
vote.match([&](const auto &data) {
|
||||
peers.push_back(PeerWithReaction{
|
||||
.peer = peerFromUser(data.vuser_id()),
|
||||
.reaction = qs(data.vreaction()),
|
||||
});
|
||||
});
|
||||
}
|
||||
entry.list = std::move(peers);
|
||||
});
|
||||
}).fail([=] {
|
||||
auto &entry = context->cacheReacted(item);
|
||||
entry.requestId = 0;
|
||||
if (ListUnknown(entry.list.current(), item)) {
|
||||
entry.list = std::vector<PeerWithReaction>();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
return entry.list.value().start_existing(consumer);
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto WhoReadOrReactedIds(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context)
|
||||
-> rpl::producer<std::vector<PeerWithReaction>> {
|
||||
return rpl::combine(
|
||||
WhoReactedIds(item, context),
|
||||
WhoReadIds(item, context)
|
||||
) | rpl::map([=](
|
||||
std::vector<PeerWithReaction> reacted,
|
||||
std::vector<PeerId> read) {
|
||||
if (ListUnknown(reacted, item) || ListUnknown(read, item)) {
|
||||
return reacted;
|
||||
}
|
||||
for (const auto &peer : read) {
|
||||
if (!ranges::contains(reacted, peer, &PeerWithReaction::peer)) {
|
||||
reacted.push_back({ .peer = peer });
|
||||
}
|
||||
}
|
||||
return reacted;
|
||||
});
|
||||
}
|
||||
|
||||
bool UpdateUserpics(
|
||||
not_null<State*> state,
|
||||
not_null<HistoryItem*> item,
|
||||
const std::vector<PeerId> &ids) {
|
||||
const std::vector<PeerWithReaction> &ids) {
|
||||
auto &owner = item->history()->owner();
|
||||
|
||||
struct ResolvedPeer {
|
||||
PeerData *peer = nullptr;
|
||||
QString reaction;
|
||||
};
|
||||
const auto peers = ranges::views::all(
|
||||
ids
|
||||
) | ranges::views::transform([&](PeerId id) {
|
||||
return owner.peerLoaded(id);
|
||||
}) | ranges::views::filter([](PeerData *peer) {
|
||||
return peer != nullptr;
|
||||
}) | ranges::views::transform([](PeerData *peer) {
|
||||
return not_null(peer);
|
||||
) | ranges::views::transform([&](PeerWithReaction id) {
|
||||
return ResolvedPeer{
|
||||
.peer = owner.peerLoaded(id.peer),
|
||||
.reaction = id.reaction,
|
||||
};
|
||||
}) | ranges::views::filter([](ResolvedPeer resolved) {
|
||||
return resolved.peer != nullptr;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
const auto same = ranges::equal(
|
||||
state->userpics,
|
||||
peers,
|
||||
ranges::less(),
|
||||
&Userpic::peer);
|
||||
ranges::equal_to(),
|
||||
&Userpic::peer,
|
||||
[](const ResolvedPeer &r) { return not_null{ r.peer }; });
|
||||
if (same) {
|
||||
return false;
|
||||
}
|
||||
auto &was = state->userpics;
|
||||
auto now = std::vector<Userpic>();
|
||||
for (const auto &peer : peers) {
|
||||
for (const auto &resolved : peers) {
|
||||
const auto peer = not_null{ resolved.peer };
|
||||
if (ranges::contains(now, peer, &Userpic::peer)) {
|
||||
continue;
|
||||
}
|
||||
@ -213,6 +358,7 @@ bool UpdateUserpics(
|
||||
}
|
||||
now.push_back(Userpic{
|
||||
.peer = peer,
|
||||
.reaction = resolved.reaction,
|
||||
});
|
||||
auto &userpic = now.back();
|
||||
userpic.uniqueKey = peer->userpicUniqueKey(userpic.view);
|
||||
@ -261,6 +407,7 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||
}
|
||||
now.push_back({
|
||||
.name = peer->name,
|
||||
.reaction = userpic.reaction,
|
||||
.userpicLarge = GenerateUserpic(userpic, large),
|
||||
.userpicKey = userpic.uniqueKey,
|
||||
.id = id,
|
||||
@ -313,7 +460,7 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
|
||||
return item->canViewReactions() || WhoReadExists(item);
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st) {
|
||||
@ -341,10 +488,21 @@ rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
WhoReadIds(
|
||||
item,
|
||||
context
|
||||
) | rpl::start_with_next([=](const std::vector<PeerId> &peers) {
|
||||
const auto resolveWhoRead = WhoReadExists(item);
|
||||
const auto resolveWhoReacted = item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
: resolveWhoRead
|
||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||
: WhoReactedIds(item, context);
|
||||
if (resolveWhoReacted) {
|
||||
// #TODO reactions
|
||||
state->current.mostPopularReaction = item->reactions().front().first;
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
) | rpl::start_with_next([=](
|
||||
const std::vector<PeerWithReaction> &peers) {
|
||||
if (ListUnknown(peers, item)) {
|
||||
state->userpics.clear();
|
||||
consumer.put_next(Ui::WhoReadContent{
|
@ -23,7 +23,7 @@ namespace Api {
|
||||
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
|
||||
|
||||
// The context must be destroyed before the session holding this item.
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st); // Cache results for this lifetime.
|
@ -33,7 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/controls/delete_message_context_action.h"
|
||||
#include "ui/controls/who_read_context_action.h"
|
||||
#include "ui/controls/who_reacted_context_action.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/inactive_press.h"
|
||||
@ -60,7 +60,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_attached_stickers.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "api/api_who_read.h"
|
||||
#include "api/api_who_reacted.h"
|
||||
#include "api/api_views.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_session.h"
|
||||
@ -1701,11 +1701,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
isUponSelected = hasSelected;
|
||||
}
|
||||
|
||||
const auto hasWhoReadItem = _dragStateItem
|
||||
const auto hasWhoReactedItem = _dragStateItem
|
||||
&& Api::WhoReactedExists(_dragStateItem);
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
hasWhoReadItem ? st::whoReadMenu : st::popupMenuWithIcons);
|
||||
hasWhoReactedItem ? st::whoReadMenu : st::popupMenuWithIcons);
|
||||
const auto session = &this->session();
|
||||
const auto controller = _controller;
|
||||
const auto groupLeaderOrSelf = [](HistoryItem *item) -> HistoryItem* {
|
||||
@ -2079,16 +2079,16 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWhoReadItem) {
|
||||
if (hasWhoReactedItem) {
|
||||
const auto participantChosen = [=](uint64 id) {
|
||||
controller->showPeerInfo(PeerId(id));
|
||||
};
|
||||
if (!_menu->empty()) {
|
||||
_menu->addSeparator();
|
||||
}
|
||||
_menu->addAction(Ui::WhoReadContextAction(
|
||||
_menu->addAction(Ui::WhoReactedContextAction(
|
||||
_menu.get(),
|
||||
Api::WhoRead(_dragStateItem, this, st::defaultWhoRead),
|
||||
Api::WhoReacted(_dragStateItem, this, st::defaultWhoRead),
|
||||
participantChosen));
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/controls/who_read_context_action.h"
|
||||
#include "ui/controls/who_reacted_context_action.h"
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
@ -20,6 +20,7 @@ namespace {
|
||||
|
||||
struct EntryData {
|
||||
QString text;
|
||||
QString reaction;
|
||||
QImage userpic;
|
||||
Fn<void()> callback;
|
||||
};
|
||||
@ -46,6 +47,7 @@ private:
|
||||
const int _height = 0;
|
||||
|
||||
Text::String _text;
|
||||
EmojiPtr _emoji = nullptr;
|
||||
int _textWidth = 0;
|
||||
QImage _userpic;
|
||||
|
||||
@ -140,11 +142,17 @@ 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
|
||||
+ padding.right();
|
||||
+ rightSkip;
|
||||
const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
|
||||
_textWidth = w - (goodWidth - textWidth);
|
||||
setMinWidth(w);
|
||||
@ -176,6 +184,18 @@ void EntryAction::paint(Painter &&p) {
|
||||
(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(
|
||||
@ -260,6 +280,7 @@ void Action::resolveMinWidth() {
|
||||
return _st.itemStyle.font->width(text);
|
||||
};
|
||||
const auto maxTextWidth = std::max({
|
||||
width(tr::lng_context_seen_text(tr::now, lt_count_short, 999999999)),
|
||||
width(tr::lng_context_seen_text(tr::now, lt_count, 999)),
|
||||
width(tr::lng_context_seen_listened(tr::now, lt_count, 999)),
|
||||
width(tr::lng_context_seen_watched(tr::now, lt_count, 999)) });
|
||||
@ -317,6 +338,7 @@ void Action::populateSubmenu() {
|
||||
};
|
||||
auto data = EntryData{
|
||||
.text = participant.name,
|
||||
.reaction = participant.reaction,
|
||||
.userpic = participant.userpicLarge,
|
||||
.callback = chosen,
|
||||
};
|
||||
@ -345,18 +367,28 @@ void Action::paint(Painter &p) {
|
||||
if (enabled) {
|
||||
paintRipple(p, 0, 0);
|
||||
}
|
||||
const auto &icon = (_content.type == WhoReadType::Seen)
|
||||
? (!enabled
|
||||
? st::whoReadChecksDisabled
|
||||
: selected
|
||||
? st::whoReadChecksOver
|
||||
: st::whoReadChecks)
|
||||
: (!enabled
|
||||
? st::whoReadPlayedDisabled
|
||||
: selected
|
||||
? st::whoReadPlayedOver
|
||||
: st::whoReadPlayed);
|
||||
icon.paint(p, st::defaultWhoRead.iconPosition, width());
|
||||
if (const auto emoji = Emoji::Find(_content.mostPopularReaction)) {
|
||||
// #TODO reactions
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = Emoji::GetSizeNormal();
|
||||
const auto x = st::defaultWhoRead.iconPosition.x()
|
||||
+ (st::whoReadChecks.width() - (size / ratio)) / 2;
|
||||
const auto y = (_height - (size / ratio)) / 2;
|
||||
Emoji::Draw(p, emoji, size, x, y);
|
||||
} else {
|
||||
const auto &icon = (_content.type == WhoReadType::Seen)
|
||||
? (!enabled
|
||||
? st::whoReadChecksDisabled
|
||||
: selected
|
||||
? st::whoReadChecksOver
|
||||
: st::whoReadChecks)
|
||||
: (!enabled
|
||||
? st::whoReadPlayedDisabled
|
||||
: selected
|
||||
? st::whoReadPlayedOver
|
||||
: st::whoReadPlayed);
|
||||
icon.paint(p, st::defaultWhoRead.iconPosition, width());
|
||||
}
|
||||
p.setPen(!enabled
|
||||
? _st.itemFgDisabled
|
||||
: selected
|
||||
@ -457,7 +489,7 @@ bool operator!=(const WhoReadParticipant &a, const WhoReadParticipant &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
base::unique_qptr<Menu::ItemBase> WhoReadContextAction(
|
||||
base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
||||
not_null<PopupMenu*> menu,
|
||||
rpl::producer<WhoReadContent> content,
|
||||
Fn<void(uint64)> participantChosen) {
|
@ -18,6 +18,7 @@ class PopupMenu;
|
||||
|
||||
struct WhoReadParticipant {
|
||||
QString name;
|
||||
QString reaction;
|
||||
QImage userpicSmall;
|
||||
QImage userpicLarge;
|
||||
std::pair<uint64, uint64> userpicKey = {};
|
||||
@ -38,10 +39,11 @@ enum class WhoReadType {
|
||||
struct WhoReadContent {
|
||||
std::vector<WhoReadParticipant> participants;
|
||||
WhoReadType type = WhoReadType::Seen;
|
||||
QString mostPopularReaction;
|
||||
bool unknown = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReadContextAction(
|
||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
||||
not_null<PopupMenu*> menu,
|
||||
rpl::producer<WhoReadContent> content,
|
||||
Fn<void(uint64)> participantChosen);
|
@ -184,8 +184,8 @@ PRIVATE
|
||||
ui/controls/send_as_button.h
|
||||
ui/controls/send_button.cpp
|
||||
ui/controls/send_button.h
|
||||
ui/controls/who_read_context_action.cpp
|
||||
ui/controls/who_read_context_action.h
|
||||
ui/controls/who_reacted_context_action.cpp
|
||||
ui/controls/who_reacted_context_action.h
|
||||
ui/text/format_song_name.cpp
|
||||
ui/text/format_song_name.h
|
||||
ui/text/format_values.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user