Start loading users who've read an outgoing message.
This commit is contained in:
parent
8f9e394dd0
commit
2f5f100626
|
@ -130,6 +130,8 @@ PRIVATE
|
||||||
api/api_updates.h
|
api/api_updates.h
|
||||||
api/api_user_privacy.cpp
|
api/api_user_privacy.cpp
|
||||||
api/api_user_privacy.h
|
api/api_user_privacy.h
|
||||||
|
api/api_who_read.cpp
|
||||||
|
api/api_who_read.h
|
||||||
boxes/filters/edit_filter_box.cpp
|
boxes/filters/edit_filter_box.cpp
|
||||||
boxes/filters/edit_filter_box.h
|
boxes/filters/edit_filter_box.h
|
||||||
boxes/filters/edit_filter_chats_list.cpp
|
boxes/filters/edit_filter_chats_list.cpp
|
||||||
|
|
|
@ -1704,6 +1704,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_context_reschedule_selected" = "Reschedule Selected";
|
"lng_context_reschedule_selected" = "Reschedule Selected";
|
||||||
"lng_context_delete_selected" = "Delete Selected";
|
"lng_context_delete_selected" = "Delete Selected";
|
||||||
"lng_context_clear_selection" = "Clear Selection";
|
"lng_context_clear_selection" = "Clear Selection";
|
||||||
|
"lng_context_seen_loading" = "Loading...";
|
||||||
|
"lng_context_seen_text#one" = "{count} Seen";
|
||||||
|
"lng_context_seen_text#other" = "{count} Seen";
|
||||||
|
"lng_context_seen_listened#one" = "{count} Listened";
|
||||||
|
"lng_context_seen_listened#other" = "{count} Listened";
|
||||||
|
|
||||||
"lng_send_image_empty" = "Could not send an empty file: {name}";
|
"lng_send_image_empty" = "Could not send an empty file: {name}";
|
||||||
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}";
|
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}";
|
||||||
"lng_send_images_selected#one" = "{count} image selected";
|
"lng_send_images_selected#one" = "{count} image selected";
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
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 "history/history_item.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_chat.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "main/main_app_config.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "main/main_account.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
|
#include "ui/controls/who_read_context_action.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Cached {
|
||||||
|
rpl::variable<std::vector<UserId>> list;
|
||||||
|
mtpRequestId requestId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
base::flat_map<not_null<HistoryItem*>, Cached> cached;
|
||||||
|
base::flat_map<not_null<Main::Session*>, rpl::lifetime> subscriptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto Contexts()
|
||||||
|
-> base::flat_map<not_null<QWidget*>, std::unique_ptr<Context>> & {
|
||||||
|
static auto result = base::flat_map<
|
||||||
|
not_null<QWidget*>,
|
||||||
|
std::unique_ptr<Context>>();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<Context*> ContextAt(not_null<QWidget*> key) {
|
||||||
|
auto &contexts = Contexts();
|
||||||
|
const auto i = contexts.find(key);
|
||||||
|
if (i != end(contexts)) {
|
||||||
|
return i->second.get();
|
||||||
|
}
|
||||||
|
const auto result = contexts.emplace(
|
||||||
|
key,
|
||||||
|
std::make_unique<Context>()).first->second.get();
|
||||||
|
QObject::connect(key.get(), &QObject::destroyed, [=] {
|
||||||
|
auto &contexts = Contexts();
|
||||||
|
const auto i = contexts.find(key);
|
||||||
|
for (auto &[item, entry] : i->second->cached) {
|
||||||
|
if (const auto requestId = entry.requestId) {
|
||||||
|
item->history()->session().api().request(requestId).cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contexts.erase(i);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||||
|
if (!item->out() || item->unread()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto history = item->history();
|
||||||
|
const auto peer = history->peer;
|
||||||
|
const auto chat = peer->asChat();
|
||||||
|
const auto megagroup = peer->asMegagroup();
|
||||||
|
if (!chat && !megagroup) {
|
||||||
|
return false;
|
||||||
|
} else if (peer->migrateTo()) {
|
||||||
|
// They're all always marked as read.
|
||||||
|
// We don't know if there really are any readers.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto &appConfig = peer->session().account().appConfig();
|
||||||
|
const auto expirePeriod = TimeId(appConfig.get<double>(
|
||||||
|
"chat_read_mark_expire_period",
|
||||||
|
7 * 86400.));
|
||||||
|
if (item->date() + expirePeriod <= base::unixtime::now()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto maxCount = int(appConfig.get<double>(
|
||||||
|
"chat_read_mark_size_threshold",
|
||||||
|
50));
|
||||||
|
const auto count = megagroup ? megagroup->membersCount() : chat->count;
|
||||||
|
if (count <= 0 || count > maxCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<std::vector<UserId>> WhoReadIds(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
not_null<QWidget*> context) {
|
||||||
|
auto weak = QPointer<QWidget>(context.get());
|
||||||
|
const auto fullId = item->fullId();
|
||||||
|
const auto session = &item->history()->session();
|
||||||
|
return [=](auto consumer) {
|
||||||
|
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 &cache = context->cached[item];
|
||||||
|
if (!cache.requestId) {
|
||||||
|
const auto makeEmpty = [=] {
|
||||||
|
// Special value that marks a validated empty list.
|
||||||
|
return std::vector<UserId>{
|
||||||
|
item->history()->session().userId()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
cache.requestId = session->api().request(
|
||||||
|
MTPmessages_GetMessageReadParticipants(
|
||||||
|
item->history()->peer->input,
|
||||||
|
MTP_int(item->id)
|
||||||
|
)
|
||||||
|
).done([=](const MTPVector<MTPlong> &result) {
|
||||||
|
auto users = std::vector<UserId>();
|
||||||
|
users.reserve(std::max(result.v.size(), 1));
|
||||||
|
for (const auto &id : result.v) {
|
||||||
|
users.push_back(UserId(id));
|
||||||
|
}
|
||||||
|
if (users.empty()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
context->cached[item].list = users.empty()
|
||||||
|
? makeEmpty()
|
||||||
|
: std::move(users);
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
if (context->cached[item].list.current().empty()) {
|
||||||
|
context->cached[item].list = makeEmpty();
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
return cache.list.value().start_existing(consumer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
not_null<QWidget*> context) {
|
||||||
|
return WhoReadIds(
|
||||||
|
item,
|
||||||
|
context
|
||||||
|
) | rpl::map([=](const std::vector<UserId> &users) {
|
||||||
|
const auto owner = &item->history()->owner();
|
||||||
|
if (users.empty()) {
|
||||||
|
return Ui::WhoReadContent{ .unknown = true };
|
||||||
|
}
|
||||||
|
const auto nobody = (users.size() == 1)
|
||||||
|
&& (users.front() == owner->session().userId());
|
||||||
|
if (nobody) {
|
||||||
|
return Ui::WhoReadContent();
|
||||||
|
}
|
||||||
|
auto participants = ranges::views::all(
|
||||||
|
users
|
||||||
|
) | ranges::views::transform([&](UserId id) {
|
||||||
|
return owner->userLoaded(id);
|
||||||
|
}) | ranges::views::filter([](UserData *user) {
|
||||||
|
return user != nullptr;
|
||||||
|
}) | ranges::views::transform([](UserData *user) {
|
||||||
|
return Ui::WhoReadParticipant{
|
||||||
|
.name = user->name,
|
||||||
|
};
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
return Ui::WhoReadContent{
|
||||||
|
.participants = std::move(participants),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class HistoryItem;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
struct WhoReadContent;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
|
||||||
|
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
|
// The context must be destroyed before the session holding this item.
|
||||||
|
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
not_null<QWidget*> context); // Cache results for this lifetime.
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/report_box.h"
|
#include "ui/boxes/report_box.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/controls/delete_message_context_action.h"
|
#include "ui/controls/delete_message_context_action.h"
|
||||||
|
#include "ui/controls/who_read_context_action.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "ui/cached_round_corners.h"
|
#include "ui/cached_round_corners.h"
|
||||||
#include "ui/inactive_press.h"
|
#include "ui/inactive_press.h"
|
||||||
|
@ -55,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "api/api_attached_stickers.h"
|
#include "api/api_attached_stickers.h"
|
||||||
#include "api/api_toggling_media.h"
|
#include "api/api_toggling_media.h"
|
||||||
|
#include "api/api_who_read.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
|
@ -1583,6 +1585,15 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto itemId = item->fullId();
|
const auto itemId = item->fullId();
|
||||||
|
if (Api::WhoReadExists(item)) {
|
||||||
|
const auto participantChosen = [=](uint64 id) {
|
||||||
|
controller->showPeerInfo(PeerId(UserId(id)));
|
||||||
|
};
|
||||||
|
_menu->addAction(Ui::WhoReadContextAction(
|
||||||
|
_menu.get(),
|
||||||
|
Api::WhoRead(item, this),
|
||||||
|
participantChosen));
|
||||||
|
}
|
||||||
if (canSendMessages) {
|
if (canSendMessages) {
|
||||||
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
|
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
|
||||||
_widget->replyToMessage(itemId);
|
_widget->replyToMessage(itemId);
|
||||||
|
|
|
@ -862,3 +862,10 @@ ttlDividerLabelPadding: margins(22px, 10px, 22px, 19px);
|
||||||
|
|
||||||
ttlItemPadding: margins(0px, 4px, 0px, 4px);
|
ttlItemPadding: margins(0px, 4px, 0px, 4px);
|
||||||
ttlItemTimerFont: font(12px);
|
ttlItemTimerFont: font(12px);
|
||||||
|
|
||||||
|
seenItemUserpics: GroupCallUserpics {
|
||||||
|
size: 32px;
|
||||||
|
shift: 12px;
|
||||||
|
stroke: 4px;
|
||||||
|
align: align(right);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,263 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
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/widgets/menu/menu_action.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/chat/group_call_userpics.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMaxUserpics = 3;
|
||||||
|
|
||||||
|
class Action final : public Menu::ItemBase {
|
||||||
|
public:
|
||||||
|
Action(
|
||||||
|
not_null<PopupMenu*> parentMenu,
|
||||||
|
rpl::producer<WhoReadContent> content,
|
||||||
|
Fn<void(uint64)> participantChosen);
|
||||||
|
|
||||||
|
bool isEnabled() const override;
|
||||||
|
not_null<QAction*> action() const override;
|
||||||
|
|
||||||
|
void handleKeyPress(not_null<QKeyEvent*> e) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QPoint prepareRippleStartPosition() const override;
|
||||||
|
QImage prepareRippleMask() const override;
|
||||||
|
|
||||||
|
int contentHeight() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paint(Painter &p);
|
||||||
|
|
||||||
|
void updateUserpicsFromContent();
|
||||||
|
void setupSubMenu();
|
||||||
|
void resolveMinWidth();
|
||||||
|
void refreshText();
|
||||||
|
void refreshDimensions();
|
||||||
|
|
||||||
|
const not_null<PopupMenu*> _parentMenu;
|
||||||
|
const not_null<QAction*> _dummyAction;
|
||||||
|
const Fn<void(uint64)> _participantChosen;
|
||||||
|
const std::unique_ptr<GroupCallUserpics> _userpics;
|
||||||
|
const style::Menu &_st;
|
||||||
|
|
||||||
|
Text::String _text;
|
||||||
|
int _textWidth = 0;
|
||||||
|
const int _height = 0;
|
||||||
|
int _userpicsWidth = 0;
|
||||||
|
|
||||||
|
WhoReadContent _content;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TextParseOptions MenuTextOptions = {
|
||||||
|
TextParseLinks | TextParseRichText, // flags
|
||||||
|
0, // maxw
|
||||||
|
0, // maxh
|
||||||
|
Qt::LayoutDirectionAuto, // dir
|
||||||
|
};
|
||||||
|
|
||||||
|
Action::Action(
|
||||||
|
not_null<PopupMenu*> parentMenu,
|
||||||
|
rpl::producer<WhoReadContent> content,
|
||||||
|
Fn<void(uint64)> participantChosen)
|
||||||
|
: ItemBase(parentMenu->menu(), parentMenu->menu()->st())
|
||||||
|
, _parentMenu(parentMenu)
|
||||||
|
, _dummyAction(new QAction(parentMenu->menu()))
|
||||||
|
, _participantChosen(std::move(participantChosen))
|
||||||
|
, _userpics(std::make_unique<GroupCallUserpics>(
|
||||||
|
st::historyGroupCallUserpics,
|
||||||
|
rpl::never<bool>(),
|
||||||
|
[=] { update(); }))
|
||||||
|
, _st(parentMenu->menu()->st())
|
||||||
|
, _height(st::ttlItemPadding.top()
|
||||||
|
+ _st.itemStyle.font->height
|
||||||
|
+ st::ttlItemTimerFont->height
|
||||||
|
+ st::ttlItemPadding.bottom()) {
|
||||||
|
const auto parent = parentMenu->menu();
|
||||||
|
|
||||||
|
setAcceptBoth(true);
|
||||||
|
initResizeHook(parent->sizeValue());
|
||||||
|
setClickedCallback([=] {
|
||||||
|
if (!_content.participants.empty()) {
|
||||||
|
setupSubMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolveMinWidth();
|
||||||
|
|
||||||
|
auto copy = std::move(
|
||||||
|
content
|
||||||
|
) | rpl::start_spawning(lifetime());
|
||||||
|
|
||||||
|
_userpics->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
_userpicsWidth = width;
|
||||||
|
refreshDimensions();
|
||||||
|
update();
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
std::move(
|
||||||
|
content
|
||||||
|
) | rpl::start_with_next([=](WhoReadContent &&content) {
|
||||||
|
_content = content;
|
||||||
|
updateUserpicsFromContent();
|
||||||
|
refreshText();
|
||||||
|
refreshDimensions();
|
||||||
|
update();
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
Painter p(this);
|
||||||
|
paint(p);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
enableMouseSelecting();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::resolveMinWidth() {
|
||||||
|
const auto maxIconWidth = 0;
|
||||||
|
const auto width = [&](const QString &text) {
|
||||||
|
return _st.itemStyle.font->width(text);
|
||||||
|
};
|
||||||
|
const auto maxTextWidth = std::max(
|
||||||
|
width(tr::lng_context_seen_text(tr::now, lt_count, 999)),
|
||||||
|
width(tr::lng_context_seen_listened(tr::now, lt_count, 999)));
|
||||||
|
const auto maxWidth = _st.itemPadding.left()
|
||||||
|
+ maxIconWidth
|
||||||
|
+ maxTextWidth
|
||||||
|
+ _userpics->maxWidth()
|
||||||
|
+ _st.itemPadding.right();
|
||||||
|
setMinWidth(maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::updateUserpicsFromContent() {
|
||||||
|
auto users = std::vector<GroupCallUser>();
|
||||||
|
if (!_content.participants.empty()) {
|
||||||
|
const auto count = std::min(
|
||||||
|
int(_content.participants.size()),
|
||||||
|
kMaxUserpics);
|
||||||
|
users.reserve(count);
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
const auto &participant = _content.participants[i];
|
||||||
|
users.push_back({
|
||||||
|
.userpic = participant.userpic,
|
||||||
|
.userpicKey = participant.userpicKey,
|
||||||
|
.id = participant.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_userpics->update(users, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::setupSubMenu() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::paint(Painter &p) {
|
||||||
|
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 (isEnabled()) {
|
||||||
|
paintRipple(p, 0, 0);
|
||||||
|
}
|
||||||
|
p.setPen(selected ? _st.itemFgOver : _st.itemFg);
|
||||||
|
_text.drawLeftElided(
|
||||||
|
p,
|
||||||
|
_st.itemPadding.left(),
|
||||||
|
_st.itemPadding.top(),
|
||||||
|
_textWidth,
|
||||||
|
width());
|
||||||
|
_userpics->paint(
|
||||||
|
p,
|
||||||
|
width() - _st.itemPadding.right(),
|
||||||
|
_st.itemPadding.top(),
|
||||||
|
st::historyGroupCallUserpics.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::refreshText() {
|
||||||
|
const auto count = int(_content.participants.size());
|
||||||
|
_text.setMarkedText(
|
||||||
|
_st.itemStyle,
|
||||||
|
{ (_content.unknown
|
||||||
|
? tr::lng_context_seen_loading(tr::now)
|
||||||
|
: (count == 1)
|
||||||
|
? _content.participants.front().name
|
||||||
|
: _content.listened
|
||||||
|
? tr::lng_context_seen_listened(tr::now, lt_count, count)
|
||||||
|
: tr::lng_context_seen_text(tr::now, lt_count, count)) },
|
||||||
|
MenuTextOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::refreshDimensions() {
|
||||||
|
const auto textWidth = _text.maxWidth();
|
||||||
|
const auto &padding = _st.itemPadding;
|
||||||
|
|
||||||
|
const auto goodWidth = padding.left()
|
||||||
|
+ textWidth
|
||||||
|
+ _userpicsWidth
|
||||||
|
+ padding.right();
|
||||||
|
|
||||||
|
const auto w = std::clamp(
|
||||||
|
goodWidth,
|
||||||
|
_st.widthMin,
|
||||||
|
_st.widthMax);
|
||||||
|
_textWidth = w - (goodWidth - textWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Action::isEnabled() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<QAction*> Action::action() const {
|
||||||
|
return _dummyAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint Action::prepareRippleStartPosition() const {
|
||||||
|
return mapFromGlobal(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Action::prepareRippleMask() const {
|
||||||
|
return Ui::RippleAnimation::rectMask(size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int Action::contentHeight() const {
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||||
|
if (!isSelected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto key = e->key();
|
||||||
|
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||||
|
setClicked(Menu::TriggeredSource::Keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
base::unique_qptr<Menu::ItemBase> WhoReadContextAction(
|
||||||
|
not_null<PopupMenu*> menu,
|
||||||
|
rpl::producer<WhoReadContent> content,
|
||||||
|
Fn<void(uint64)> participantChosen) {
|
||||||
|
return base::make_unique_q<Action>(
|
||||||
|
menu,
|
||||||
|
std::move(content),
|
||||||
|
std::move(participantChosen));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/unique_qptr.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace Menu {
|
||||||
|
class ItemBase;
|
||||||
|
} // namespace Menu
|
||||||
|
|
||||||
|
class PopupMenu;
|
||||||
|
|
||||||
|
struct WhoReadParticipant {
|
||||||
|
QString name;
|
||||||
|
QImage userpic;
|
||||||
|
std::pair<uint64, uint64> userpicKey = {};
|
||||||
|
uint64 id = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WhoReadContent {
|
||||||
|
std::vector<WhoReadParticipant> participants;
|
||||||
|
bool listened = false;
|
||||||
|
bool unknown = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReadContextAction(
|
||||||
|
not_null<PopupMenu*> menu,
|
||||||
|
rpl::producer<WhoReadContent> content,
|
||||||
|
Fn<void(uint64)> participantChosen);
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -165,6 +165,8 @@ PRIVATE
|
||||||
ui/controls/invite_link_label.h
|
ui/controls/invite_link_label.h
|
||||||
ui/controls/send_button.cpp
|
ui/controls/send_button.cpp
|
||||||
ui/controls/send_button.h
|
ui/controls/send_button.h
|
||||||
|
ui/controls/who_read_context_action.cpp
|
||||||
|
ui/controls/who_read_context_action.h
|
||||||
ui/text/format_song_name.cpp
|
ui/text/format_song_name.cpp
|
||||||
ui/text/format_song_name.h
|
ui/text/format_song_name.h
|
||||||
ui/text/format_values.cpp
|
ui/text/format_values.cpp
|
||||||
|
|
Loading…
Reference in New Issue