Implement dates of who read your message list.
Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 414 B |
Before Width: | Height: | Size: 720 B After Width: | Height: | Size: 812 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/menu/read_ticks_s.png
Normal file
After Width: | Height: | Size: 348 B |
BIN
Telegram/Resources/icons/menu/read_ticks_s@2x.png
Normal file
After Width: | Height: | Size: 695 B |
BIN
Telegram/Resources/icons/menu/read_ticks_s@3x.png
Normal file
After Width: | Height: | Size: 1,008 B |
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_message_reaction_id.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
|
@ -36,37 +37,33 @@ constexpr auto kContextReactionsLimit = 50;
|
|||
using Data::ReactionId;
|
||||
|
||||
struct Peers {
|
||||
std::vector<PeerId> list;
|
||||
std::vector<WhoReadPeer> list;
|
||||
bool unknown = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const Peers &a,
|
||||
const Peers &b) noexcept = default;
|
||||
};
|
||||
inline bool operator==(const Peers &a, const Peers &b) noexcept {
|
||||
return (a.list == b.list) && (a.unknown == b.unknown);
|
||||
}
|
||||
|
||||
struct PeerWithReaction {
|
||||
PeerId peer = 0;
|
||||
WhoReadPeer peerWithDate;
|
||||
ReactionId reaction;
|
||||
};
|
||||
inline bool operator==(
|
||||
|
||||
friend inline bool operator==(
|
||||
const PeerWithReaction &a,
|
||||
const PeerWithReaction &b) noexcept {
|
||||
return (a.peer == b.peer) && (a.reaction == b.reaction);
|
||||
}
|
||||
const PeerWithReaction &b) noexcept = default;
|
||||
};
|
||||
|
||||
struct PeersWithReactions {
|
||||
std::vector<PeerWithReaction> list;
|
||||
std::vector<PeerId> read;
|
||||
std::vector<WhoReadPeer> read;
|
||||
int fullReactionsCount = 0;
|
||||
bool unknown = false;
|
||||
};
|
||||
inline bool operator==(
|
||||
|
||||
friend inline bool operator==(
|
||||
const PeersWithReactions &a,
|
||||
const PeersWithReactions &b) noexcept {
|
||||
return (a.fullReactionsCount == b.fullReactionsCount)
|
||||
&& (a.list == b.list)
|
||||
&& (a.read == b.read)
|
||||
&& (a.unknown == b.unknown);
|
||||
}
|
||||
const PeersWithReactions &b) noexcept = default;
|
||||
};
|
||||
|
||||
struct CachedRead {
|
||||
CachedRead()
|
||||
|
@ -113,6 +110,7 @@ struct Context {
|
|||
|
||||
struct Userpic {
|
||||
not_null<PeerData*> peer;
|
||||
TimeId date = 0;
|
||||
QString customEntityData;
|
||||
mutable Ui::PeerUserpicView view;
|
||||
mutable InMemoryKey uniqueKey;
|
||||
|
@ -234,7 +232,10 @@ struct State {
|
|||
auto parsed = Peers();
|
||||
parsed.list.reserve(result.v.size());
|
||||
for (const auto &id : result.v) {
|
||||
parsed.list.push_back(UserId(id.data().vuser_id()));
|
||||
parsed.list.push_back({
|
||||
.peer = UserId(id.data().vuser_id()),
|
||||
.date = id.data().vdate().v,
|
||||
});
|
||||
}
|
||||
entry.data = std::move(parsed);
|
||||
}).fail([=] {
|
||||
|
@ -252,8 +253,8 @@ struct State {
|
|||
[[nodiscard]] PeersWithReactions WithEmptyReactions(
|
||||
Peers &&peers) {
|
||||
auto result = PeersWithReactions{
|
||||
.list = peers.list | ranges::views::transform([](PeerId peer) {
|
||||
return PeerWithReaction{ .peer = peer };
|
||||
.list = peers.list | ranges::views::transform([](WhoReadPeer peer) {
|
||||
return PeerWithReaction{ .peerWithDate = peer };
|
||||
}) | ranges::to_vector,
|
||||
.unknown = peers.unknown,
|
||||
};
|
||||
|
@ -302,7 +303,9 @@ struct State {
|
|||
for (const auto &vote : data.vreactions().v) {
|
||||
vote.match([&](const auto &data) {
|
||||
parsed.list.push_back(PeerWithReaction{
|
||||
.peer = peerFromMTP(data.vpeer_id()),
|
||||
.peerWithDate = {
|
||||
.peer = peerFromMTP(data.vpeer_id()),
|
||||
},
|
||||
.reaction = Data::ReactionFromMTP(
|
||||
data.vreaction()),
|
||||
});
|
||||
|
@ -334,9 +337,16 @@ struct State {
|
|||
return PeersWithReactions{ .unknown = true };
|
||||
}
|
||||
auto &list = reacted.list;
|
||||
for (const auto &peer : read.list) {
|
||||
if (!ranges::contains(list, peer, &PeerWithReaction::peer)) {
|
||||
list.push_back({ .peer = peer });
|
||||
for (const auto &peerWithDate : read.list) {
|
||||
const auto i = ranges::find(
|
||||
list,
|
||||
peerWithDate.peer,
|
||||
[](const PeerWithReaction &p) {
|
||||
return p.peerWithDate.peer; });
|
||||
if (i != end(list)) {
|
||||
i->peerWithDate.date = peerWithDate.date;
|
||||
} else {
|
||||
list.push_back({ .peerWithDate = peerWithDate });
|
||||
}
|
||||
}
|
||||
reacted.read = std::move(read.list);
|
||||
|
@ -344,6 +354,37 @@ struct State {
|
|||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FormatReadDate(TimeId date, const QDateTime &now) {
|
||||
if (!date) {
|
||||
return {};
|
||||
}
|
||||
const auto parsed = base::unixtime::parse(date);
|
||||
const auto readDate = parsed.date();
|
||||
const auto nowDate = now.date();
|
||||
if (readDate == nowDate) {
|
||||
return tr::lng_mediaview_today(
|
||||
tr::now,
|
||||
lt_time,
|
||||
QLocale().toString(parsed.time(), QLocale::ShortFormat));
|
||||
} else if (readDate.addDays(1) == nowDate) {
|
||||
return tr::lng_mediaview_yesterday(
|
||||
tr::now,
|
||||
lt_time,
|
||||
QLocale().toString(parsed.time(), QLocale::ShortFormat));
|
||||
}
|
||||
return tr::lng_mediaview_date_time(
|
||||
tr::now,
|
||||
lt_date,
|
||||
tr::lng_month_day(
|
||||
tr::now,
|
||||
lt_month,
|
||||
Lang::MonthDay(readDate.month())(tr::now),
|
||||
lt_day,
|
||||
QString::number(readDate.day())),
|
||||
lt_time,
|
||||
QLocale().toString(parsed.time(), QLocale::ShortFormat));
|
||||
}
|
||||
|
||||
bool UpdateUserpics(
|
||||
not_null<State*> state,
|
||||
not_null<HistoryItem*> item,
|
||||
|
@ -352,13 +393,15 @@ bool UpdateUserpics(
|
|||
|
||||
struct ResolvedPeer {
|
||||
PeerData *peer = nullptr;
|
||||
TimeId date = 0;
|
||||
ReactionId reaction;
|
||||
};
|
||||
const auto peers = ranges::views::all(
|
||||
ids
|
||||
) | ranges::views::transform([&](PeerWithReaction id) {
|
||||
return ResolvedPeer{
|
||||
.peer = owner.peerLoaded(id.peer),
|
||||
.peer = owner.peerLoaded(id.peerWithDate.peer),
|
||||
.date = id.peerWithDate.date,
|
||||
.reaction = id.reaction,
|
||||
};
|
||||
}) | ranges::views::filter([](ResolvedPeer resolved) {
|
||||
|
@ -369,8 +412,8 @@ bool UpdateUserpics(
|
|||
state->userpics,
|
||||
peers,
|
||||
ranges::equal_to(),
|
||||
&Userpic::peer,
|
||||
[](const ResolvedPeer &r) { return not_null{ r.peer }; });
|
||||
[](const Userpic &u) { return std::pair(u.peer.get(), u.date); },
|
||||
[](const ResolvedPeer &r) { return std::pair(r.peer, r.date); });
|
||||
if (same) {
|
||||
return false;
|
||||
}
|
||||
|
@ -381,12 +424,14 @@ bool UpdateUserpics(
|
|||
const auto &data = ReactionEntityData(resolved.reaction);
|
||||
const auto i = ranges::find(was, peer, &Userpic::peer);
|
||||
if (i != end(was) && i->view.cloud) {
|
||||
i->date = resolved.date;
|
||||
now.push_back(std::move(*i));
|
||||
now.back().customEntityData = data;
|
||||
continue;
|
||||
}
|
||||
now.push_back(Userpic{
|
||||
.peer = peer,
|
||||
.date = resolved.date,
|
||||
.customEntityData = data,
|
||||
});
|
||||
auto &userpic = now.back();
|
||||
|
@ -422,20 +467,24 @@ void RegenerateUserpics(not_null<State*> state, int small, int large) {
|
|||
}
|
||||
|
||||
void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||
const auto currentDate = QDateTime::currentDateTime();
|
||||
auto old = base::take(state->current.participants);
|
||||
auto &now = state->current.participants;
|
||||
now.reserve(state->userpics.size());
|
||||
for (auto &userpic : state->userpics) {
|
||||
const auto peer = userpic.peer;
|
||||
const auto date = userpic.date;
|
||||
const auto id = peer->id.value;
|
||||
const auto was = ranges::find(old, id, &Ui::WhoReadParticipant::id);
|
||||
if (was != end(old)) {
|
||||
was->name = peer->name();
|
||||
was->date = FormatReadDate(date, currentDate);
|
||||
now.push_back(std::move(*was));
|
||||
continue;
|
||||
}
|
||||
now.push_back({
|
||||
.name = peer->name(),
|
||||
.date = FormatReadDate(date, currentDate),
|
||||
.customEntityData = userpic.customEntityData,
|
||||
.userpicLarge = GenerateUserpic(userpic, large),
|
||||
.userpicKey = userpic.uniqueKey,
|
||||
|
@ -522,7 +571,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
|||
&PeerWithReaction::reaction);
|
||||
whoReadIds->list = (peers.read.size() > reacted)
|
||||
? std::move(peers.read)
|
||||
: std::vector<PeerId>();
|
||||
: std::vector<WhoReadPeer>();
|
||||
}
|
||||
if (UpdateUserpics(state, item, peers.list)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
|
|
|
@ -34,8 +34,17 @@ enum class WhoReactedList {
|
|||
not_null<HistoryItem*> item,
|
||||
WhoReactedList list);
|
||||
|
||||
struct WhoReadPeer {
|
||||
PeerId peer = 0;
|
||||
TimeId date = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const WhoReadPeer &a,
|
||||
const WhoReadPeer &b) noexcept = default;
|
||||
};
|
||||
|
||||
struct WhoReadList {
|
||||
std::vector<PeerId> list;
|
||||
std::vector<WhoReadPeer> list;
|
||||
Ui::WhoReadType type = {};
|
||||
};
|
||||
|
||||
|
|
|
@ -250,8 +250,8 @@ uint64 Controller::id(
|
|||
void Controller::fillWhoRead() {
|
||||
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
|
||||
auto &owner = _window->session().data();
|
||||
for (const auto &peerId : _whoReadIds->list) {
|
||||
if (const auto peer = owner.peerLoaded(peerId)) {
|
||||
for (const auto &peerWithDate : _whoReadIds->list) {
|
||||
if (const auto peer = owner.peerLoaded(peerWithDate.peer)) {
|
||||
_whoRead.push_back(peer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,72 +52,7 @@ inline QString langDateMaybeWithYear(
|
|||
return withoutYear(month, year);
|
||||
}
|
||||
|
||||
tr::phrase<> Month(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_month1;
|
||||
case 2: return tr::lng_month2;
|
||||
case 3: return tr::lng_month3;
|
||||
case 4: return tr::lng_month4;
|
||||
case 5: return tr::lng_month5;
|
||||
case 6: return tr::lng_month6;
|
||||
case 7: return tr::lng_month7;
|
||||
case 8: return tr::lng_month8;
|
||||
case 9: return tr::lng_month9;
|
||||
case 10: return tr::lng_month10;
|
||||
case 11: return tr::lng_month11;
|
||||
case 12: return tr::lng_month12;
|
||||
}
|
||||
Unexpected("Index in MonthSmall.");
|
||||
}
|
||||
|
||||
tr::phrase<> MonthSmall(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_month1_small;
|
||||
case 2: return tr::lng_month2_small;
|
||||
case 3: return tr::lng_month3_small;
|
||||
case 4: return tr::lng_month4_small;
|
||||
case 5: return tr::lng_month5_small;
|
||||
case 6: return tr::lng_month6_small;
|
||||
case 7: return tr::lng_month7_small;
|
||||
case 8: return tr::lng_month8_small;
|
||||
case 9: return tr::lng_month9_small;
|
||||
case 10: return tr::lng_month10_small;
|
||||
case 11: return tr::lng_month11_small;
|
||||
case 12: return tr::lng_month12_small;
|
||||
}
|
||||
Unexpected("Index in MonthSmall.");
|
||||
}
|
||||
|
||||
tr::phrase<> MonthDay(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_month_day1;
|
||||
case 2: return tr::lng_month_day2;
|
||||
case 3: return tr::lng_month_day3;
|
||||
case 4: return tr::lng_month_day4;
|
||||
case 5: return tr::lng_month_day5;
|
||||
case 6: return tr::lng_month_day6;
|
||||
case 7: return tr::lng_month_day7;
|
||||
case 8: return tr::lng_month_day8;
|
||||
case 9: return tr::lng_month_day9;
|
||||
case 10: return tr::lng_month_day10;
|
||||
case 11: return tr::lng_month_day11;
|
||||
case 12: return tr::lng_month_day12;
|
||||
}
|
||||
Unexpected("Index in MonthDay.");
|
||||
}
|
||||
|
||||
tr::phrase<> Weekday(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_weekday1;
|
||||
case 2: return tr::lng_weekday2;
|
||||
case 3: return tr::lng_weekday3;
|
||||
case 4: return tr::lng_weekday4;
|
||||
case 5: return tr::lng_weekday5;
|
||||
case 6: return tr::lng_weekday6;
|
||||
case 7: return tr::lng_weekday7;
|
||||
}
|
||||
Unexpected("Index in Weekday.");
|
||||
}
|
||||
using namespace Lang;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
@ -245,4 +180,71 @@ QString LanguageIdOrDefault(const QString &id) {
|
|||
return !id.isEmpty() ? id : DefaultLanguageId();
|
||||
}
|
||||
|
||||
tr::phrase<> Month(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_month1;
|
||||
case 2: return tr::lng_month2;
|
||||
case 3: return tr::lng_month3;
|
||||
case 4: return tr::lng_month4;
|
||||
case 5: return tr::lng_month5;
|
||||
case 6: return tr::lng_month6;
|
||||
case 7: return tr::lng_month7;
|
||||
case 8: return tr::lng_month8;
|
||||
case 9: return tr::lng_month9;
|
||||
case 10: return tr::lng_month10;
|
||||
case 11: return tr::lng_month11;
|
||||
case 12: return tr::lng_month12;
|
||||
}
|
||||
Unexpected("Index in MonthSmall.");
|
||||
}
|
||||
|
||||
tr::phrase<> MonthSmall(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_month1_small;
|
||||
case 2: return tr::lng_month2_small;
|
||||
case 3: return tr::lng_month3_small;
|
||||
case 4: return tr::lng_month4_small;
|
||||
case 5: return tr::lng_month5_small;
|
||||
case 6: return tr::lng_month6_small;
|
||||
case 7: return tr::lng_month7_small;
|
||||
case 8: return tr::lng_month8_small;
|
||||
case 9: return tr::lng_month9_small;
|
||||
case 10: return tr::lng_month10_small;
|
||||
case 11: return tr::lng_month11_small;
|
||||
case 12: return tr::lng_month12_small;
|
||||
}
|
||||
Unexpected("Index in MonthSmall.");
|
||||
}
|
||||
|
||||
tr::phrase<> MonthDay(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_month_day1;
|
||||
case 2: return tr::lng_month_day2;
|
||||
case 3: return tr::lng_month_day3;
|
||||
case 4: return tr::lng_month_day4;
|
||||
case 5: return tr::lng_month_day5;
|
||||
case 6: return tr::lng_month_day6;
|
||||
case 7: return tr::lng_month_day7;
|
||||
case 8: return tr::lng_month_day8;
|
||||
case 9: return tr::lng_month_day9;
|
||||
case 10: return tr::lng_month_day10;
|
||||
case 11: return tr::lng_month_day11;
|
||||
case 12: return tr::lng_month_day12;
|
||||
}
|
||||
Unexpected("Index in MonthDay.");
|
||||
}
|
||||
|
||||
tr::phrase<> Weekday(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_weekday1;
|
||||
case 2: return tr::lng_weekday2;
|
||||
case 3: return tr::lng_weekday3;
|
||||
case 4: return tr::lng_weekday4;
|
||||
case 5: return tr::lng_weekday5;
|
||||
case 6: return tr::lng_weekday6;
|
||||
case 7: return tr::lng_weekday7;
|
||||
}
|
||||
Unexpected("Index in Weekday.");
|
||||
}
|
||||
|
||||
} // namespace Lang
|
||||
|
|
|
@ -37,4 +37,9 @@ namespace Lang {
|
|||
[[nodiscard]] QString DefaultLanguageId();
|
||||
[[nodiscard]] QString LanguageIdOrDefault(const QString &id);
|
||||
|
||||
[[nodiscard]] tr::phrase<> Month(int index);
|
||||
[[nodiscard]] tr::phrase<> MonthSmall(int index);
|
||||
[[nodiscard]] tr::phrase<> MonthDay(int index);
|
||||
[[nodiscard]] tr::phrase<> Weekday(int index);
|
||||
|
||||
} // namespace Lang
|
||||
|
|
|
@ -23,29 +23,11 @@ namespace {
|
|||
|
||||
constexpr auto kMinimalSchedule = TimeId(10);
|
||||
|
||||
tr::phrase<> MonthDay(int index) {
|
||||
switch (index) {
|
||||
case 1: return tr::lng_month_day1;
|
||||
case 2: return tr::lng_month_day2;
|
||||
case 3: return tr::lng_month_day3;
|
||||
case 4: return tr::lng_month_day4;
|
||||
case 5: return tr::lng_month_day5;
|
||||
case 6: return tr::lng_month_day6;
|
||||
case 7: return tr::lng_month_day7;
|
||||
case 8: return tr::lng_month_day8;
|
||||
case 9: return tr::lng_month_day9;
|
||||
case 10: return tr::lng_month_day10;
|
||||
case 11: return tr::lng_month_day11;
|
||||
case 12: return tr::lng_month_day12;
|
||||
}
|
||||
Unexpected("Index in MonthDay.");
|
||||
}
|
||||
|
||||
QString DayString(const QDate &date) {
|
||||
return tr::lng_month_day(
|
||||
tr::now,
|
||||
lt_month,
|
||||
MonthDay(date.month())(tr::now),
|
||||
Lang::MonthDay(date.month())(tr::now),
|
||||
lt_day,
|
||||
QString::number(date.day()));
|
||||
}
|
||||
|
|
|
@ -1095,6 +1095,17 @@ whoReadMenu: PopupMenu(popupMenuExpandedSeparator) {
|
|||
scrollPadding: margins(0px, 6px, 0px, 4px);
|
||||
maxHeight: 400px;
|
||||
}
|
||||
whoReadNameWithDateTop: 3px;
|
||||
whoReadDateTop: 20px;
|
||||
whoReadDateSkip: 15px;
|
||||
whoReadDateChecks: icon{{ "menu/read_ticks_s", windowSubTextFg }};
|
||||
whoReadDateChecksOver: icon{{ "menu/read_ticks_s", windowSubTextFgOver }};
|
||||
whoReadDateChecksPosition: point(-7px, -4px);
|
||||
whoReadDateStyle: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
linkFont: font(12px);
|
||||
linkFontOver: font(12px underline);
|
||||
}
|
||||
whoReadChecks: icon{{ "menu/read_ticks", windowBoldFg }};
|
||||
whoReadChecksOver: icon{{ "menu/read_ticks", windowBoldFg }};
|
||||
whoReadChecksDisabled: icon{{ "menu/read_ticks", menuFgDisabled }};
|
||||
|
|
|
@ -73,6 +73,7 @@ using Text::CustomEmojiFactory;
|
|||
|
||||
struct EntryData {
|
||||
QString text;
|
||||
QString date;
|
||||
QString customEntityData;
|
||||
QImage userpic;
|
||||
Fn<void()> callback;
|
||||
|
@ -287,7 +288,7 @@ void Action::updateUserpicsFromContent() {
|
|||
}
|
||||
|
||||
void Action::populateSubmenu() {
|
||||
if (_content.participants.size() < 2) {
|
||||
if (_content.participants.size() < 1) {
|
||||
_submenu.clear();
|
||||
_parentMenu->removeSubmenu(action());
|
||||
if (!isEnabled()) {
|
||||
|
@ -487,6 +488,7 @@ private:
|
|||
const int _height = 0;
|
||||
|
||||
Text::String _text;
|
||||
Text::String _date;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
|
||||
QImage _userpic;
|
||||
int _textWidth = 0;
|
||||
|
@ -533,11 +535,21 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) {
|
|||
setClickedCallback(std::move(data.callback));
|
||||
_userpic = std::move(data.userpic);
|
||||
_text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions);
|
||||
if (data.date.isEmpty()) {
|
||||
_date = Text::String();
|
||||
} else {
|
||||
_date.setMarkedText(
|
||||
st::whoReadDateStyle,
|
||||
{ data.date },
|
||||
MenuTextOptions);
|
||||
}
|
||||
_custom = _customEmojiFactory(data.customEntityData, [=] { update(); });
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = Emoji::GetSizeNormal() / ratio;
|
||||
_customSize = Text::AdjustCustomEmojiSize(size);
|
||||
const auto textWidth = _text.maxWidth();
|
||||
const auto textWidth = std::max(
|
||||
_text.maxWidth(),
|
||||
st::whoReadDateSkip + _date.maxWidth());
|
||||
const auto &padding = _st.itemPadding;
|
||||
const auto rightSkip = padding.right()
|
||||
+ (_custom ? (size + padding.right()) : 0);
|
||||
|
@ -571,6 +583,10 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
|
|||
QRect(photoLeft, photoTop, photoSize, photoSize));
|
||||
}
|
||||
|
||||
const auto withDate = !_date.isEmpty();
|
||||
const auto textTop = withDate
|
||||
? st::whoReadNameWithDateTop
|
||||
: (height() - _st.itemStyle.font->height) / 2;
|
||||
p.setPen(selected
|
||||
? _st.itemFgOver
|
||||
: enabled
|
||||
|
@ -579,10 +595,25 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
|
|||
_text.drawLeftElided(
|
||||
p,
|
||||
st::defaultWhoRead.nameLeft,
|
||||
(height() - _st.itemStyle.font->height) / 2,
|
||||
textTop,
|
||||
_textWidth,
|
||||
width());
|
||||
|
||||
if (withDate) {
|
||||
const auto iconPosition = QPoint(
|
||||
st::defaultWhoRead.nameLeft,
|
||||
st::whoReadDateTop) + st::whoReadDateChecksPosition;
|
||||
const auto &icon = selected
|
||||
? st::whoReadDateChecksOver
|
||||
: st::whoReadDateChecks;
|
||||
icon.paint(p, iconPosition, width());
|
||||
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
|
||||
_date.drawLeftElided(
|
||||
p,
|
||||
st::defaultWhoRead.nameLeft + st::whoReadDateSkip,
|
||||
st::whoReadDateTop,
|
||||
_textWidth - st::whoReadDateSkip,
|
||||
width());
|
||||
}
|
||||
if (_custom) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = Emoji::GetSizeNormal() / ratio;
|
||||
|
@ -600,6 +631,7 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
|
|||
bool operator==(const WhoReadParticipant &a, const WhoReadParticipant &b) {
|
||||
return (a.id == b.id)
|
||||
&& (a.name == b.name)
|
||||
&& (a.date == b.date)
|
||||
&& (a.userpicKey == b.userpicKey);
|
||||
}
|
||||
|
||||
|
@ -680,6 +712,7 @@ void WhoReactedListMenu::populate(
|
|||
};
|
||||
append({
|
||||
.text = participant.name,
|
||||
.date = participant.date,
|
||||
.customEntityData = participant.customEntityData,
|
||||
.userpic = participant.userpicLarge,
|
||||
.callback = chosen,
|
||||
|
|
|
@ -19,6 +19,7 @@ class PopupMenu;
|
|||
|
||||
struct WhoReadParticipant {
|
||||
QString name;
|
||||
QString date;
|
||||
QString customEntityData;
|
||||
QImage userpicSmall;
|
||||
QImage userpicLarge;
|
||||
|
|