Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae261fcede | ||
|
|
c04cdff7f7 | ||
|
|
466aa5a14d | ||
|
|
4aac633413 | ||
|
|
ad328d35a2 | ||
|
|
c5140f34a7 | ||
|
|
419f6345b3 | ||
|
|
c2c53df886 | ||
|
|
b3f73bb6a9 | ||
|
|
eda5cd47ad | ||
|
|
0c906a5e6d | ||
|
|
352768053d | ||
|
|
79b1cec4f3 | ||
|
|
8d09190439 | ||
|
|
5cd0a3719e | ||
|
|
8b7cd4a0c7 | ||
|
|
937c2d3dce | ||
|
|
1fa5d273cc | ||
|
|
24fa3dbf8f | ||
|
|
c9b782fd63 | ||
|
|
e7cf560da0 | ||
|
|
86e07518ad | ||
|
|
8c71d03959 | ||
|
|
967e86f4ab | ||
|
|
730412fefe | ||
|
|
576883ddc8 | ||
|
|
992d636680 | ||
|
|
8cdd2f113f | ||
|
|
d5f935b73d | ||
|
|
84f561b251 | ||
|
|
21ac2b8f3a | ||
|
|
1790828b01 | ||
|
|
792b9090a7 | ||
|
|
8c21fad642 | ||
|
|
5136cc3c9c | ||
|
|
b78b27f517 | ||
|
|
85760ea92c | ||
|
|
c2212c719e | ||
|
|
fc8a0d0efd | ||
|
|
c052c37621 | ||
|
|
21f7cec781 | ||
|
|
64af456d29 | ||
|
|
7751c4ac1f | ||
|
|
ececdcb9c0 | ||
|
|
cb8f49aea0 | ||
|
|
e3ef7d6631 | ||
|
|
21aa1f49d7 | ||
|
|
51e80170e2 | ||
|
|
b2526ab7f6 | ||
|
|
e220447bdd | ||
|
|
ead695b101 | ||
|
|
4ea72f8f89 | ||
|
|
4ef550da9b | ||
|
|
1e660fc2a2 | ||
|
|
6adf791b3b | ||
|
|
d2a41a42e0 | ||
|
|
315549b5f8 | ||
|
|
fd4a543bab | ||
|
|
d525e56053 | ||
|
|
dab5d1f994 | ||
|
|
de3b52425c | ||
|
|
844fd58a97 | ||
|
|
de2bad51d3 | ||
|
|
1424ea3540 | ||
|
|
a8efd0ef3d | ||
|
|
1204e282d3 | ||
|
|
6588242793 | ||
|
|
b1ba9a42c6 | ||
|
|
ab0d2bf9c6 | ||
|
|
80028e41f3 | ||
|
|
2c581adc55 | ||
|
|
f0e8c1e325 | ||
|
|
a2db9de4d7 | ||
|
|
a228c62286 | ||
|
|
37d940eca6 | ||
|
|
f7c24c54a1 | ||
|
|
19ce1edc16 |
@@ -42,7 +42,10 @@ include(cmake/generate_appdata_changelog.cmake)
|
||||
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
|
||||
generate_midl(Telegram ${src_loc}
|
||||
platform/win/windows_quiethours.idl
|
||||
platform/win/windows_toastactivator.idl
|
||||
)
|
||||
|
||||
nuget_add_winrt(Telegram)
|
||||
endif()
|
||||
@@ -139,6 +142,8 @@ PRIVATE
|
||||
api/api_updates.h
|
||||
api/api_user_privacy.cpp
|
||||
api/api_user_privacy.h
|
||||
api/api_views.cpp
|
||||
api/api_views.h
|
||||
api/api_who_read.cpp
|
||||
api/api_who_read.h
|
||||
boxes/filters/edit_filter_box.cpp
|
||||
@@ -169,6 +174,8 @@ PRIVATE
|
||||
boxes/peers/edit_peer_permissions_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
boxes/about_sponsored_box.h
|
||||
boxes/abstract_box.cpp
|
||||
boxes/abstract_box.h
|
||||
boxes/add_contact_box.cpp
|
||||
@@ -415,6 +422,7 @@ PRIVATE
|
||||
data/data_media_types.h
|
||||
data/data_messages.cpp
|
||||
data/data_messages.h
|
||||
data/data_msg_id.h
|
||||
data/data_notify_settings.cpp
|
||||
data/data_notify_settings.h
|
||||
data/data_peer.cpp
|
||||
@@ -447,6 +455,8 @@ PRIVATE
|
||||
data/data_shared_media.h
|
||||
data/data_sparse_ids.cpp
|
||||
data/data_sparse_ids.h
|
||||
data/data_sponsored_messages.cpp
|
||||
data/data_sponsored_messages.h
|
||||
data/data_streaming.cpp
|
||||
data/data_streaming.h
|
||||
data/data_types.cpp
|
||||
@@ -467,8 +477,6 @@ PRIVATE
|
||||
dialogs/dialogs_inner_widget.h
|
||||
dialogs/dialogs_key.cpp
|
||||
dialogs/dialogs_key.h
|
||||
dialogs/dialogs_layout.cpp
|
||||
dialogs/dialogs_layout.h
|
||||
dialogs/dialogs_list.cpp
|
||||
dialogs/dialogs_list.h
|
||||
dialogs/dialogs_main_list.cpp
|
||||
@@ -481,6 +489,10 @@ PRIVATE
|
||||
dialogs/dialogs_search_from_controllers.h
|
||||
dialogs/dialogs_widget.cpp
|
||||
dialogs/dialogs_widget.h
|
||||
dialogs/ui/dialogs_layout.cpp
|
||||
dialogs/ui/dialogs_layout.h
|
||||
dialogs/ui/dialogs_message_view.cpp
|
||||
dialogs/ui/dialogs_message_view.h
|
||||
editor/color_picker.cpp
|
||||
editor/color_picker.h
|
||||
editor/controllers/controllers.h
|
||||
@@ -621,6 +633,8 @@ PRIVATE
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_top_bar_widget.cpp
|
||||
history/view/history_view_top_bar_widget.h
|
||||
history/view/history_view_view_button.cpp
|
||||
history/view/history_view_view_button.h
|
||||
history/view/history_view_webpage_preview.cpp
|
||||
history/view/history_view_webpage_preview.h
|
||||
history/history.cpp
|
||||
@@ -631,6 +645,8 @@ PRIVATE
|
||||
history/history_item.h
|
||||
history/history_item_components.cpp
|
||||
history/history_item_components.h
|
||||
history/history_item_reply_markup.cpp
|
||||
history/history_item_reply_markup.h
|
||||
history/history_item_text.cpp
|
||||
history/history_item_text.h
|
||||
history/history_inner_widget.cpp
|
||||
@@ -943,6 +959,8 @@ PRIVATE
|
||||
platform/win/windows_dlls.h
|
||||
platform/win/windows_event_filter.cpp
|
||||
platform/win/windows_event_filter.h
|
||||
platform/win/windows_toast_activator.cpp
|
||||
platform/win/windows_toast_activator.h
|
||||
platform/platform_audio.h
|
||||
platform/platform_file_utilities.h
|
||||
platform/platform_launcher.h
|
||||
@@ -1040,6 +1058,8 @@ PRIVATE
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
ui/chat/attach/attach_item_single_media_preview.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/effects/fireworks_animation.cpp
|
||||
ui/effects/fireworks_animation.h
|
||||
ui/effects/round_checkbox.cpp
|
||||
@@ -1368,12 +1388,14 @@ if (WIN32)
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:uxtheme.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
/DELAYLOAD:propsys.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play.png
Normal file
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 165 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@2x.png
Normal file
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@3x.png
Normal file
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 475 B |
@@ -1335,6 +1335,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forwarded_imported" = "This message was imported from another app. It may not be real.";
|
||||
"lng_signed_author" = "Author: {user}";
|
||||
"lng_in_reply_to" = "In reply to";
|
||||
"lng_sponsored" = "sponsored";
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
@@ -2871,6 +2872,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
|
||||
"lng_filters_remove_yes" = "Remove";
|
||||
|
||||
"lng_chat_theme_change" = "Change colors";
|
||||
"lng_chat_theme_none" = "No\nTheme";
|
||||
"lng_chat_theme_apply" = "Apply Theme";
|
||||
"lng_chat_theme_reset" = "Reset Theme";
|
||||
"lng_chat_theme_dont" = "Do Not Set Theme";
|
||||
"lng_chat_theme_title" = "Select theme";
|
||||
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you're having an unsent voice message.";
|
||||
|
||||
"lng_photo_editor_menu_delete" = "Delete";
|
||||
"lng_photo_editor_menu_flip" = "Flip";
|
||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||
@@ -2880,6 +2889,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_voice_speed_fast" = "Fast";
|
||||
"lng_voice_speed_very_fast" = "Very fast";
|
||||
|
||||
"lng_view_button_user" = "View user";
|
||||
"lng_view_button_bot" = "View bot";
|
||||
"lng_view_button_group" = "View group";
|
||||
"lng_view_button_channel" = "View channel";
|
||||
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to-many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.";
|
||||
"lng_sponsored_info_description2" = "Unlike other apps, Telegram doesn\'t track whether you tapped on a sponsored message and doesn\'t profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.";
|
||||
"lng_sponsored_info_description3" = "Telegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
|
||||
"lng_sponsored_info_description4" = "Ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together.";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.1.3.0" />
|
||||
Version="3.1.7.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,1,3,0
|
||||
PRODUCTVERSION 3,1,3,0
|
||||
FILEVERSION 3,1,7,0
|
||||
PRODUCTVERSION 3,1,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.1.3.0"
|
||||
VALUE "FileVersion", "3.1.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.1.3.0"
|
||||
VALUE "ProductVersion", "3.1.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,1,3,0
|
||||
PRODUCTVERSION 3,1,3,0
|
||||
FILEVERSION 3,1,7,0
|
||||
PRODUCTVERSION 3,1,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "3.1.3.0"
|
||||
VALUE "FileVersion", "3.1.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.1.3.0"
|
||||
VALUE "ProductVersion", "3.1.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -128,7 +128,7 @@ void SendExistingMedia(
|
||||
messagePostAuthor,
|
||||
media,
|
||||
caption,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -288,7 +288,7 @@ bool SendDice(Api::MessageToSend &message) {
|
||||
messagePostAuthor,
|
||||
TextWithEntities(),
|
||||
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
@@ -439,7 +439,7 @@ void SendConfirmedFile(
|
||||
| (localEntities.v.isEmpty()
|
||||
? MTPDmessage::Flag()
|
||||
: MTPDmessage::Flag::f_entities)),
|
||||
MTP_int(newId.msg),
|
||||
MTP_int(0), // Not used (would've been trimmed to 32 bits).
|
||||
peerToMTP(messageFromId),
|
||||
peerToMTP(file->to.peer),
|
||||
MTPMessageFwdHeader(),
|
||||
@@ -471,7 +471,7 @@ void SendConfirmedFile(
|
||||
messagePostAuthor,
|
||||
caption,
|
||||
media,
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupId);
|
||||
}
|
||||
|
||||
|
||||
135
Telegram/SourceFiles/api/api_views.cpp
Normal file
135
Telegram/SourceFiles/api/api_views.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
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_views.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
// Send channel views each second.
|
||||
constexpr auto kSendViewsTimeout = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
ViewsManager::ViewsManager(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance())
|
||||
, _incrementTimer([=] { viewsIncrement(); }) {
|
||||
}
|
||||
|
||||
void ViewsManager::scheduleIncrement(not_null<HistoryItem*> item) {
|
||||
auto peer = item->history()->peer;
|
||||
auto i = _incremented.find(peer);
|
||||
if (i != _incremented.cend()) {
|
||||
if (i->second.contains(item->id)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
i = _incremented.emplace(peer).first;
|
||||
}
|
||||
i->second.emplace(item->id);
|
||||
auto j = _toIncrement.find(peer);
|
||||
if (j == _toIncrement.cend()) {
|
||||
j = _toIncrement.emplace(peer).first;
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
j->second.emplace(item->id);
|
||||
}
|
||||
|
||||
void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
||||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::viewsIncrement() {
|
||||
for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) {
|
||||
if (_incrementRequests.contains(i->first)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<MTPint> ids;
|
||||
ids.reserve(i->second.size());
|
||||
for (const auto &msgId : i->second) {
|
||||
ids.push_back(MTP_int(msgId));
|
||||
}
|
||||
const auto requestId = _api.request(MTPmessages_GetMessagesViews(
|
||||
i->first->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_bool(true)
|
||||
)).done([=](
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
done(ids, result, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
fail(error, requestId);
|
||||
}).afterDelay(5).send();
|
||||
|
||||
_incrementRequests.emplace(i->first, requestId);
|
||||
i = _toIncrement.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
const auto &data = result.c_messages_messageViews();
|
||||
auto &owner = _session->data();
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
auto &v = data.vviews().v;
|
||||
if (ids.size() == v.size()) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id != requestId) {
|
||||
continue;
|
||||
}
|
||||
const auto channel = peerToChannel(peer->id);
|
||||
for (auto j = 0, l = int(ids.size()); j < l; ++j) {
|
||||
if (const auto item = owner.message(channel, ids[j].v)) {
|
||||
v[j].match([&](const MTPDmessageViews &data) {
|
||||
if (const auto views = data.vviews()) {
|
||||
item->setViewsCount(views->v);
|
||||
}
|
||||
if (const auto forwards = data.vforwards()) {
|
||||
item->setForwardsCount(forwards->v);
|
||||
}
|
||||
if (const auto replies = data.vreplies()) {
|
||||
item->setReplies(*replies);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::fail(const MTP::Error &error, mtpRequestId requestId) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id == requestId) {
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
49
Telegram/SourceFiles/api/api_views.h
Normal file
49
Telegram/SourceFiles/api/api_views.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 "mtproto/sender.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class ApiWrap;
|
||||
class PeerData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class ViewsManager final {
|
||||
public:
|
||||
explicit ViewsManager(not_null<ApiWrap*> api);
|
||||
|
||||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
private:
|
||||
void viewsIncrement();
|
||||
|
||||
void done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId);
|
||||
void fail(const MTP::Error &error, mtpRequestId requestId);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _incremented;
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _toIncrement;
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _incrementRequests;
|
||||
base::flat_map<mtpRequestId, not_null<PeerData*>> _incrementByRequest;
|
||||
base::Timer _incrementTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -355,6 +355,8 @@ rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||
} else if (UpdateUserpics(state, item, peers)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
#include "api/api_views.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -141,7 +142,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
|
||||
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
|
||||
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this)) {
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this))
|
||||
, _views(std::make_unique<Api::ViewsManager>(this)) {
|
||||
crl::on_main(session, [=] {
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
@@ -558,7 +560,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(nullptr, result, requestId);
|
||||
_session->data().processExistingMessages(nullptr, result);
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -584,7 +587,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(channel, result, requestId);
|
||||
_session->data().processExistingMessages(channel, result);
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -600,37 +604,6 @@ void ApiWrap::resolveMessageDatas() {
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
|
||||
const auto handleResult = [&](auto &&result) {
|
||||
_session->data().processUsers(result.vusers());
|
||||
_session->data().processChats(result.vchats());
|
||||
_session->data().processMessages(
|
||||
result.vmessages(),
|
||||
NewMessageType::Existing);
|
||||
};
|
||||
switch (msgs.type()) {
|
||||
case mtpc_messages_messages:
|
||||
handleResult(msgs.c_messages_messages());
|
||||
break;
|
||||
case mtpc_messages_messagesSlice:
|
||||
handleResult(msgs.c_messages_messagesSlice());
|
||||
break;
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = msgs.c_messages_channelMessages();
|
||||
if (channel) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)"));
|
||||
}
|
||||
handleResult(d);
|
||||
} break;
|
||||
case mtpc_messages_messagesNotModified:
|
||||
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotDependencyItem)"));
|
||||
break;
|
||||
}
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId) {
|
||||
@@ -662,8 +635,8 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
const auto fallback = [&] {
|
||||
auto linkChannel = channel;
|
||||
auto linkItemId = item->id;
|
||||
auto linkCommentId = 0;
|
||||
auto linkThreadId = 0;
|
||||
auto linkCommentId = MsgId();
|
||||
auto linkThreadId = MsgId();
|
||||
if (inRepliesContext) {
|
||||
if (const auto rootId = item->replyToTop()) {
|
||||
const auto root = item->history()->owner().message(
|
||||
@@ -693,11 +666,11 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto query = base
|
||||
+ '/'
|
||||
+ QString::number(linkItemId)
|
||||
+ QString::number(linkItemId.bare)
|
||||
+ (linkCommentId
|
||||
? "?comment=" + QString::number(linkCommentId)
|
||||
? "?comment=" + QString::number(linkCommentId.bare)
|
||||
: linkThreadId
|
||||
? "?thread=" + QString::number(linkThreadId)
|
||||
? "?thread=" + QString::number(linkThreadId.bare)
|
||||
: "");
|
||||
if (linkChannel->hasUsername()
|
||||
&& !linkChannel->isMegagroup()
|
||||
@@ -1759,7 +1732,7 @@ void ApiWrap::deleteAllFromUser(
|
||||
? history->collectMessagesFromUserToDelete(from)
|
||||
: QVector<MsgId>();
|
||||
const auto channelId = peerToChannel(channel->id);
|
||||
for (const auto msgId : ids) {
|
||||
for (const auto &msgId : ids) {
|
||||
if (const auto item = _session->data().message(channelId, msgId)) {
|
||||
item->destroy();
|
||||
}
|
||||
@@ -3837,7 +3810,7 @@ void ApiWrap::sendSharedContact(
|
||||
MTP_string(lastName),
|
||||
MTP_string(), // vcard
|
||||
MTP_long(userId.bare)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto media = MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
@@ -4101,7 +4074,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
messagePostAuthor,
|
||||
sending,
|
||||
media,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_SendMessage(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -4733,6 +4706,10 @@ Api::InviteLinks &ApiWrap::inviteLinks() {
|
||||
return *_inviteLinks;
|
||||
}
|
||||
|
||||
Api::ViewsManager &ApiWrap::views() {
|
||||
return *_views;
|
||||
}
|
||||
|
||||
void ApiWrap::createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
|
||||
@@ -62,6 +62,7 @@ class SensitiveContent;
|
||||
class GlobalPrivacy;
|
||||
class UserPrivacy;
|
||||
class InviteLinks;
|
||||
class ViewsManager;
|
||||
|
||||
namespace details {
|
||||
|
||||
@@ -400,6 +401,7 @@ public:
|
||||
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
|
||||
[[nodiscard]] Api::UserPrivacy &userPrivacy();
|
||||
[[nodiscard]] Api::InviteLinks &inviteLinks();
|
||||
[[nodiscard]] Api::ViewsManager &views();
|
||||
|
||||
void createPoll(
|
||||
const PollData &data,
|
||||
@@ -456,7 +458,6 @@ private:
|
||||
void saveDraftsToCloud();
|
||||
|
||||
void resolveMessageDatas();
|
||||
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId requestId);
|
||||
void finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId);
|
||||
@@ -720,6 +721,7 @@ private:
|
||||
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
|
||||
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
|
||||
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
|
||||
const std::unique_ptr<Api::ViewsManager> _views;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
|
||||
76
Telegram/SourceFiles/boxes/about_sponsored_box.cpp
Normal file
76
Telegram/SourceFiles/boxes/about_sponsored_box.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 "boxes/about_sponsored_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kUrl = "https://telegram.org/ads"_cs;
|
||||
|
||||
} // namespace
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_sponsored_title());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
|
||||
const auto addUrl = [&] {
|
||||
const auto &st = st::sponsoredUrlButton;
|
||||
const auto row = box->addRow(object_ptr<RpWidget>(box));
|
||||
row->resize(0, st.height + st.padding.top() + st.padding.bottom());
|
||||
const auto button = Ui::CreateChild<RoundButton>(
|
||||
row,
|
||||
rpl::single<QString>(kUrl.utf8()),
|
||||
st);
|
||||
button->setBrushOverride(Qt::NoBrush);
|
||||
button->setPenOverride(QPen(st::historyLinkInFg));
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
rpl::combine(
|
||||
row->sizeValue(),
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const QSize &rowSize,
|
||||
const QSize &buttonSize) {
|
||||
button->moveToLeft(
|
||||
(rowSize.width() - buttonSize.width()) / 2,
|
||||
(rowSize.height() - buttonSize.height()) / 2);
|
||||
}, row->lifetime());
|
||||
button->addClickHandler([=] {
|
||||
QDesktopServices::openUrl({ kUrl.utf8() });
|
||||
});
|
||||
};
|
||||
|
||||
const auto &stLabel = st::aboutLabel;
|
||||
const auto info1 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info1->setText(tr::lng_sponsored_info_description1(tr::now));
|
||||
box->addSkip(st::sponsoredInfoSkip);
|
||||
|
||||
const auto info2 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info2->setText(tr::lng_sponsored_info_description2(tr::now));
|
||||
box->addSkip(st::sponsoredInfoSkip);
|
||||
|
||||
const auto info3 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info3->setText(tr::lng_sponsored_info_description3(tr::now));
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
|
||||
addUrl();
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
|
||||
const auto info4 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info4->setText(tr::lng_sponsored_info_description4(tr::now));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
16
Telegram/SourceFiles/boxes/about_sponsored_box.h
Normal file
16
Telegram/SourceFiles/boxes/about_sponsored_box.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
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 "ui/layers/generic_box.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -210,7 +210,7 @@ void ServiceCheck::Generator::paintFrame(
|
||||
const auto frames = framesForStyle(st);
|
||||
auto &image = frames->image;
|
||||
const auto count = int(frames->ready.size());
|
||||
const auto index = int(std::round(toggled * (count - 1)));
|
||||
const auto index = int(base::SafeRound(toggled * (count - 1)));
|
||||
Assert(index >= 0 && index < count);
|
||||
if (!frames->ready[index]) {
|
||||
frames->ready[index] = true;
|
||||
@@ -288,7 +288,6 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
bool out) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
static auto id = ServerMaxMsgId + (ServerMaxMsgId / 3);
|
||||
const auto flags = MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| (out ? MessageFlag::Outgoing : MessageFlag(0));
|
||||
@@ -296,7 +295,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
const auto viaBotId = UserId();
|
||||
const auto groupedId = uint64();
|
||||
const auto item = history->makeMessage(
|
||||
++id,
|
||||
history->nextNonHistoryEntryId(),
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
@@ -305,7 +304,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
QString(),
|
||||
TextWithEntities{ TextUtilities::Clean(text) },
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupedId);
|
||||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
|
||||
@@ -973,3 +973,22 @@ autolockTimeField: InputField(scheduleTimeField) {
|
||||
heightMin: 20px;
|
||||
}
|
||||
autolockTimeWidth: 52px;
|
||||
|
||||
sponsoredInfoSkip: 22px;
|
||||
sponsoredUrlButtonSkip: 11px;
|
||||
sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
height: 32px;
|
||||
width: -42px;
|
||||
textBg: transparent;
|
||||
textBgOver: transparent;
|
||||
radius: roundRadiusLarge;
|
||||
padding: margins(2px, 2px, 2px, 2px);
|
||||
textFg: historyLinkInFg;
|
||||
textFgOver: historyLinkInFg;
|
||||
textTop: 7px;
|
||||
font: normalFont;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +453,14 @@ void EditAdminBox::transferOwnership() {
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
_checkTransferRequestId = 0;
|
||||
if (!handleTransferPasswordError(error)) {
|
||||
getDelegate()->show(Box<ConfirmBox>(
|
||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||
const auto callback = crl::guard(this, [=] {
|
||||
transferOwnershipChecked();
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
});
|
||||
*box = getDelegate()->show(Box<ConfirmBox>(
|
||||
tr::lng_rights_transfer_about(
|
||||
tr::now,
|
||||
lt_group,
|
||||
@@ -462,7 +469,7 @@ void EditAdminBox::transferOwnership() {
|
||||
Ui::Text::Bold(user()->shortName()),
|
||||
Ui::Text::RichLangValue),
|
||||
tr::lng_rights_transfer_sure(tr::now),
|
||||
crl::guard(this, [=] { transferOwnershipChecked(); })));
|
||||
callback));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -268,7 +268,8 @@ void Row::update(const InviteLinkData &data, TimeId now) {
|
||||
|
||||
void Row::updateExpireProgress(TimeId now) {
|
||||
const auto updated = ComputeProgress(_data, now);
|
||||
if (std::round(_progressTillExpire * 360) != std::round(updated * 360)) {
|
||||
if (base::SafeRound(_progressTillExpire * 360)
|
||||
!= base::SafeRound(updated * 360)) {
|
||||
_progressTillExpire = updated;
|
||||
const auto color = ComputeColor(_data, _progressTillExpire);
|
||||
if (_color != color) {
|
||||
@@ -291,7 +292,8 @@ crl::time Row::updateExpireIn() const {
|
||||
if (_data.expireDate <= start) {
|
||||
return 0;
|
||||
}
|
||||
return std::round((_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
return base::SafeRound(
|
||||
(_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
}
|
||||
|
||||
QString Row::generateName() {
|
||||
|
||||
@@ -1113,7 +1113,7 @@ QString AppendShareGameScoreUrl(
|
||||
auto channelAccessHash = uint64(channel ? channel->access : 0);
|
||||
shareHashDataInts[0] = session->userId().bare;
|
||||
shareHashDataInts[1] = fullId.channel.bare;
|
||||
shareHashDataInts[2] = fullId.msg;
|
||||
shareHashDataInts[2] = uint64(fullId.msg.bare);
|
||||
shareHashDataInts[3] = channelAccessHash;
|
||||
|
||||
// Count SHA1() of data.
|
||||
@@ -1200,7 +1200,6 @@ void ShareGameScoreByHash(
|
||||
//}
|
||||
|
||||
if (((hashDataInts[1] >> 40) != 0)
|
||||
|| ((hashDataInts[2] >> 32) != 0)
|
||||
|| (!hashDataInts[1] && channelAccessHash)) {
|
||||
// If there is no channel id, there should be no channel access_hash.
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
@@ -1208,7 +1207,7 @@ void ShareGameScoreByHash(
|
||||
}
|
||||
|
||||
auto channelId = ChannelId(hashDataInts[1]);
|
||||
auto msgId = MsgId(hashDataInts[2]);
|
||||
auto msgId = MsgId(int64(hashDataInts[2]));
|
||||
if (const auto item = session->data().message(channelId, msgId)) {
|
||||
FastShareMessage(item);
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/image/image.h"
|
||||
|
||||
@@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -64,7 +64,7 @@ private:
|
||||
void setCounter(int counter);
|
||||
|
||||
QString _text;
|
||||
Dialogs::Layout::UnreadBadgeStyle _st;
|
||||
Dialogs::Ui::UnreadBadgeStyle _st;
|
||||
|
||||
};
|
||||
|
||||
@@ -303,7 +303,7 @@ StickersBox::CounterWidget::CounterWidget(
|
||||
: RpWidget(parent) {
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_st.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox;
|
||||
_st.sizeId = Dialogs::Ui::UnreadBadgeInStickersBox;
|
||||
_st.textTop = st::stickersFeaturedBadgeTextTop;
|
||||
_st.size = st::stickersFeaturedBadgeSize;
|
||||
_st.padding = st::stickersFeaturedBadgePadding;
|
||||
@@ -323,7 +323,7 @@ void StickersBox::CounterWidget::setCounter(int counter) {
|
||||
Painter p(&dummy);
|
||||
|
||||
auto newWidth = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
|
||||
resize(newWidth, st::stickersFeaturedBadgeSize);
|
||||
}
|
||||
@@ -334,7 +334,7 @@ void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) {
|
||||
if (!_text.isEmpty()) {
|
||||
auto unreadRight = rtl() ? 0 : width();
|
||||
auto unreadTop = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ private:
|
||||
};
|
||||
|
||||
BoxController::Row::Row(not_null<HistoryItem*> item)
|
||||
: PeerListRow(item->history()->peer, item->id)
|
||||
: PeerListRow(item->history()->peer, item->id.bare)
|
||||
, _items(1, item)
|
||||
, _date(ItemDateTime(item).date())
|
||||
, _type(ComputeType(item))
|
||||
|
||||
@@ -209,7 +209,7 @@ void VideoBubble::updateSizeToFrame(QSize frame) {
|
||||
size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio);
|
||||
} else {
|
||||
const auto area = size.width() * size.height();
|
||||
const auto w = int(std::round(std::max(
|
||||
const auto w = int(base::SafeRound(std::max(
|
||||
std::sqrt((frame.width() * float64(area)) / (frame.height() * 1.)),
|
||||
1.)));
|
||||
const auto h = area / w;
|
||||
|
||||
@@ -2361,6 +2361,7 @@ bool GroupCall::tryCreateController() {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt");
|
||||
auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
descriptor.config.need_log = true;
|
||||
#ifdef Q_OS_WIN
|
||||
descriptor.config.logPath.data = callLogNative.toStdWString();
|
||||
#else // Q_OS_WIN
|
||||
@@ -2370,6 +2371,8 @@ bool GroupCall::tryCreateController() {
|
||||
#endif // Q_OS_WIN
|
||||
QFile(callLogPath).remove();
|
||||
QDir().mkpath(callLogFolder);
|
||||
} else {
|
||||
descriptor.config.need_log = false;
|
||||
}
|
||||
|
||||
LOG(("Call Info: Creating group instance"));
|
||||
|
||||
@@ -57,7 +57,7 @@ auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||
}
|
||||
|
||||
[[nodiscard]] QString StatusPercentString(float volume) {
|
||||
return QString::number(int(std::round(volume * 200))) + '%';
|
||||
return QString::number(int(base::SafeRound(volume * 200))) + '%';
|
||||
}
|
||||
|
||||
[[nodiscard]] int StatusPercentWidth(const QString &percent) {
|
||||
@@ -492,7 +492,7 @@ int MembersRow::statusIconWidth(bool skipIcon) const {
|
||||
const auto full = iconWidth
|
||||
+ _statusIcon->percentWidth
|
||||
+ st::normalFont->spacew;
|
||||
return int(std::round(shown * full));
|
||||
return int(base::SafeRound(shown * full));
|
||||
}
|
||||
|
||||
int MembersRow::statusIconHeight() const {
|
||||
|
||||
@@ -461,15 +461,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
|
||||
const auto columns = slices;
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(column * sizew + sizew - skip))
|
||||
- left;
|
||||
const auto rows = int(std::round((count - index)
|
||||
const auto left = int(base::SafeRound(column * sizew));
|
||||
const auto width = int(
|
||||
base::SafeRound(column * sizew + sizew - skip)) - left;
|
||||
const auto rows = int(base::SafeRound((count - index)
|
||||
/ float64(columns - column)));
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(
|
||||
const auto top = int(base::SafeRound(row * sizeh));
|
||||
const auto height = int(base::SafeRound(
|
||||
row * sizeh + sizeh - skip)) - top;
|
||||
auto &geometry = sizes[index];
|
||||
geometry.columns = {
|
||||
@@ -493,15 +493,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
|
||||
const auto rows = slices;
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(row * sizeh + sizeh - skip))
|
||||
- top;
|
||||
const auto columns = int(std::round((count - index)
|
||||
const auto top = int(base::SafeRound(row * sizeh));
|
||||
const auto height = int(
|
||||
base::SafeRound(row * sizeh + sizeh - skip)) - top;
|
||||
const auto columns = int(base::SafeRound((count - index)
|
||||
/ float64(rows - row)));
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(
|
||||
const auto left = int(base::SafeRound(column * sizew));
|
||||
const auto width = int(base::SafeRound(
|
||||
column * sizew + sizew - skip)) - left;
|
||||
auto &geometry = sizes[index];
|
||||
geometry.rows = {
|
||||
|
||||
@@ -242,7 +242,7 @@ vec4 background() {
|
||||
QSize outer,
|
||||
float factor) {
|
||||
factor *= kBlurTextureSizeFactor;
|
||||
const auto area = outer / int(std::round(factor * cScale() / 100));
|
||||
const auto area = outer / int(base::SafeRound(factor * cScale() / 100));
|
||||
const auto scaled = unscaled.scaled(area, Qt::KeepAspectRatio);
|
||||
return (scaled.width() > unscaled.width()
|
||||
|| scaled.height() > unscaled.height())
|
||||
|
||||
@@ -92,7 +92,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
|
||||
const auto volume = _localMuted
|
||||
? 0
|
||||
: std::round(_slider->value() * kMaxVolumePercent);
|
||||
: base::SafeRound(_slider->value() * kMaxVolumePercent);
|
||||
const auto muteProgress =
|
||||
_crossLineAnimation.value((!volume) ? 1. : 0.);
|
||||
|
||||
@@ -140,7 +140,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
};
|
||||
|
||||
_slider->setChangeFinishedCallback([=](float64 value) {
|
||||
const auto newVolume = std::round(value * _maxVolume);
|
||||
const auto newVolume = base::SafeRound(value * _maxVolume);
|
||||
const auto muted = (value == 0);
|
||||
|
||||
if (!_cloudMuted && muted) {
|
||||
@@ -175,7 +175,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
}
|
||||
if (_waitingForUpdateVolume) {
|
||||
const auto localVolume =
|
||||
std::round(_slider->value() * _maxVolume);
|
||||
base::SafeRound(_slider->value() * _maxVolume);
|
||||
if ((localVolume != newVolume)
|
||||
&& (_cloudVolume == newVolume)) {
|
||||
_changeVolumeRequests.fire(int(localVolume));
|
||||
|
||||
@@ -16,7 +16,7 @@ struct SendCommandRequest {
|
||||
not_null<PeerData*> peer;
|
||||
QString command;
|
||||
FullMsgId context;
|
||||
int replyTo = 0;
|
||||
MsgId replyTo = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] QString WrapCommandInChat(
|
||||
|
||||
@@ -199,9 +199,9 @@ bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (key >= Qt::Key_1 && key <= Qt::Key_2) {
|
||||
const auto index = int(key - Qt::Key_1);
|
||||
if (!markup->rows.empty()
|
||||
if (!markup->data.rows.empty()
|
||||
&& index >= 0
|
||||
&& index < int(markup->rows.front().size())) {
|
||||
&& index < int(markup->data.rows.front().size())) {
|
||||
App::activateBotCommand(_controller, item, 0, index);
|
||||
return true;
|
||||
}
|
||||
@@ -257,14 +257,14 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
|
||||
_singleUse = _forceReply || (markupFlags & ReplyMarkupFlag::SingleUse);
|
||||
|
||||
if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
_placeholder = markup->placeholder;
|
||||
_placeholder = markup->data.placeholder;
|
||||
} else {
|
||||
_placeholder = QString();
|
||||
}
|
||||
|
||||
_impl = nullptr;
|
||||
if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (!markup->rows.empty()) {
|
||||
if (!markup->data.rows.empty()) {
|
||||
_impl = std::make_unique<ReplyKeyboard>(
|
||||
to,
|
||||
std::make_unique<Style>(this, *_st));
|
||||
|
||||
@@ -186,7 +186,7 @@ void EmojiInteractions::startIncoming(
|
||||
}
|
||||
const auto now = crl::now();
|
||||
for (const auto &single : bunch.interactions) {
|
||||
const auto at = now + crl::time(std::round(single.time * 1000));
|
||||
const auto at = now + crl::time(base::SafeRound(single.time * 1000));
|
||||
if (!animations.empty() && animations.back().scheduledAt >= at) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -168,11 +168,10 @@ FieldAutocomplete::FieldAutocomplete(
|
||||
|
||||
hide();
|
||||
|
||||
connect(
|
||||
_scroll,
|
||||
&Ui::ScrollArea::geometryChanged,
|
||||
_inner,
|
||||
&Inner::onParentGeometryChanged);
|
||||
_scroll->geometryChanged(
|
||||
) | rpl::start_with_next(crl::guard(_inner, [=] {
|
||||
_inner->onParentGeometryChanged();
|
||||
}), lifetime());
|
||||
}
|
||||
|
||||
not_null<Window::SessionController*> FieldAutocomplete::controller() const {
|
||||
|
||||
@@ -68,7 +68,7 @@ QImage EmojiImageLoader::prepare(EmojiPtr emoji) const {
|
||||
{ -1, 1 },
|
||||
{ 1, 1 },
|
||||
} };
|
||||
const auto corrected = int(std::round(delta / sqrt(2.)));
|
||||
const auto corrected = int(base::SafeRound(delta / sqrt(2.)));
|
||||
for (const auto &shift : diagonal) {
|
||||
for (auto i = 0; i != corrected; ++i) {
|
||||
p.drawImage(QPoint(delta, delta) + shift * (i + 1), tinted);
|
||||
|
||||
@@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
|
||||
@@ -68,6 +68,34 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Reconnect without timeout when network availability changes.\n"
|
||||
|
||||
"- Crash fixes."
|
||||
},
|
||||
{
|
||||
3001005,
|
||||
"- Choose one of 8 new preset themes for any individual private chat.\n"
|
||||
|
||||
"- Click on '...' menu > 'Change Colors' to pick a theme.\n"
|
||||
|
||||
"- Both chat participants will see the same theme in that chat "
|
||||
"– on all their devices.\n"
|
||||
|
||||
"- Each new theme features colorful gradient message bubbles, "
|
||||
"beautifully animated backgrounds and unique background patterns.\n"
|
||||
|
||||
"- All chat themes have day and night versions and will follow "
|
||||
"your overall dark mode settings.\n"
|
||||
|
||||
"- Implement main window rounded corners on Windows 11.\n"
|
||||
|
||||
"- Fix audio capture from AirPods on macOS.\n"
|
||||
},
|
||||
{
|
||||
3001006,
|
||||
"- Show small media previews in chats list.\n"
|
||||
|
||||
"- Show media album previews and caption text in chats list.\n"
|
||||
|
||||
"- Add \"Quick Reply\" and \"Mark as Read\" "
|
||||
"to native Windows notifications.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -617,7 +617,7 @@ public:
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
|
||||
return int(std::round(std::clamp(speed, 0.5, 2.0) * 100));
|
||||
return int(base::SafeRound(std::clamp(speed, 0.5, 2.0) * 100));
|
||||
}
|
||||
[[nodiscard]] static float64 DeserializePlaybackSpeed(qint32 speed) {
|
||||
if (speed < 10) {
|
||||
|
||||
@@ -586,7 +586,7 @@ bool ParseCommonMap(
|
||||
}
|
||||
return string.toULongLong();
|
||||
} else if ((*version).isDouble()) {
|
||||
return uint64(std::round((*version).toDouble()));
|
||||
return uint64(base::SafeRound((*version).toDouble()));
|
||||
}
|
||||
return 0ULL;
|
||||
}();
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 3001003;
|
||||
constexpr auto AppVersionStr = "3.1.3";
|
||||
constexpr auto AppVersion = 3001007;
|
||||
constexpr auto AppVersionStr = "3.1.7";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -148,7 +148,7 @@ struct MessageUpdate {
|
||||
NewMaybeAdded = (1U << 7),
|
||||
RepliesUnreadCount = (1U << 8),
|
||||
|
||||
LastUsedBit = (1U << 7),
|
||||
LastUsedBit = (1U << 8),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
@@ -387,26 +387,29 @@ rpl::producer<> CloudThemes::chatThemesUpdated() const {
|
||||
}
|
||||
|
||||
std::optional<ChatTheme> CloudThemes::themeForEmoji(
|
||||
const QString &emoji) const {
|
||||
if (emoji.isEmpty()) {
|
||||
const QString &emoticon) const {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!emoji) {
|
||||
return {};
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt;
|
||||
}
|
||||
|
||||
rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
|
||||
const QString &emoji) {
|
||||
const QString &emoticon) {
|
||||
const auto testing = TestingColors();
|
||||
if (emoji.isEmpty()) {
|
||||
if (!Ui::Emoji::Find(emoticon)) {
|
||||
return rpl::single<std::optional<ChatTheme>>(std::nullopt);
|
||||
} else if (auto result = themeForEmoji(emoji)) {
|
||||
} else if (auto result = themeForEmoji(emoticon)) {
|
||||
if (testing) {
|
||||
return rpl::single(
|
||||
std::move(result)
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoji);
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}));
|
||||
@@ -419,7 +422,7 @@ rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
|
||||
std::nullopt
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoji);
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}) | rpl::take(limit));
|
||||
@@ -482,12 +485,15 @@ QString CloudThemes::prepareTestingLink(const CloudTheme &theme) const {
|
||||
}
|
||||
|
||||
std::optional<CloudTheme> CloudThemes::updateThemeFromLink(
|
||||
const QString &emoji,
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms) {
|
||||
if (!TestingColors()) {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!TestingColors() || !emoji) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
if (i == end(_chatThemes)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -552,7 +558,7 @@ void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
|
||||
for (const auto &theme : list) {
|
||||
theme.match([&](const MTPDchatTheme &data) {
|
||||
_chatThemes.push_back({
|
||||
.emoji = qs(data.vemoticon()),
|
||||
.emoticon = qs(data.vemoticon()),
|
||||
.light = CloudTheme::Parse(_session, data.vtheme(), true),
|
||||
.dark = CloudTheme::Parse(_session, data.vdark_theme(), true),
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ struct CloudTheme {
|
||||
};
|
||||
|
||||
struct ChatTheme {
|
||||
QString emoji;
|
||||
QString emoticon;
|
||||
CloudTheme light;
|
||||
CloudTheme dark;
|
||||
};
|
||||
@@ -71,15 +71,15 @@ public:
|
||||
[[nodiscard]] const std::vector<ChatTheme> &chatThemes() const;
|
||||
[[nodiscard]] rpl::producer<> chatThemesUpdated() const;
|
||||
[[nodiscard]] std::optional<ChatTheme> themeForEmoji(
|
||||
const QString &emoji) const;
|
||||
const QString &emoticon) const;
|
||||
[[nodiscard]] rpl::producer<std::optional<ChatTheme>> themeForEmojiValue(
|
||||
const QString &emoji);
|
||||
const QString &emoticon);
|
||||
|
||||
[[nodiscard]] static bool TestingColors();
|
||||
static void SetTestingColors(bool testing);
|
||||
[[nodiscard]] QString prepareTestingLink(const CloudTheme &theme) const;
|
||||
[[nodiscard]] std::optional<CloudTheme> updateThemeFromLink(
|
||||
const QString &emoji,
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms);
|
||||
|
||||
void applyUpdate(const MTPTheme &theme);
|
||||
|
||||
@@ -19,6 +19,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Data {
|
||||
|
||||
DraftKey DraftKey::FromSerializedOld(int32 value) {
|
||||
return !value
|
||||
? DraftKey::None()
|
||||
: (value == kLocalDraftIndex + kEditDraftShiftOld)
|
||||
? DraftKey::LocalEdit()
|
||||
: (value == kScheduledDraftIndex + kEditDraftShiftOld)
|
||||
? DraftKey::ScheduledEdit()
|
||||
: (value > 0 && value < 0x4000'0000)
|
||||
? DraftKey::Replies(int64(value))
|
||||
: (value > kEditDraftShiftOld
|
||||
&& value < kEditDraftShiftOld + 0x4000'000)
|
||||
? DraftKey::RepliesEdit(int64(value - kEditDraftShiftOld))
|
||||
: DraftKey::None();
|
||||
}
|
||||
|
||||
Draft::Draft(
|
||||
const TextWithTags &textWithTags,
|
||||
MsgId msgId,
|
||||
|
||||
@@ -75,19 +75,21 @@ public:
|
||||
return kScheduledDraftIndex + kEditDraftShift;
|
||||
}
|
||||
[[nodiscard]] static DraftKey Replies(MsgId rootId) {
|
||||
return rootId;
|
||||
return rootId.bare;
|
||||
}
|
||||
[[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) {
|
||||
return rootId + kEditDraftShift;
|
||||
return rootId.bare + kEditDraftShift;
|
||||
}
|
||||
|
||||
[[nodiscard]] static DraftKey FromSerialized(int32 value) {
|
||||
[[nodiscard]] static DraftKey FromSerialized(qint64 value) {
|
||||
return value;
|
||||
}
|
||||
[[nodiscard]] int32 serialize() const {
|
||||
[[nodiscard]] qint64 serialize() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
[[nodiscard]] static DraftKey FromSerializedOld(int32 value);
|
||||
|
||||
inline bool operator<(const DraftKey &other) const {
|
||||
return _value < other._value;
|
||||
}
|
||||
@@ -111,15 +113,16 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
DraftKey(int value) : _value(value) {
|
||||
DraftKey(int64 value) : _value(value) {
|
||||
}
|
||||
|
||||
static constexpr auto kLocalDraftIndex = -1;
|
||||
static constexpr auto kCloudDraftIndex = -2;
|
||||
static constexpr auto kScheduledDraftIndex = -3;
|
||||
static constexpr auto kEditDraftShift = ServerMaxMsgId;
|
||||
static constexpr auto kEditDraftShift = ServerMaxMsgId.bare;
|
||||
static constexpr auto kEditDraftShiftOld = 0x3FFF'FFFF;
|
||||
|
||||
int _value = 0;
|
||||
int64 _value = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_groups.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_session.h"
|
||||
|
||||
@@ -140,8 +142,13 @@ const Group *Groups::find(not_null<const HistoryItem*> item) const {
|
||||
}
|
||||
|
||||
void Groups::refreshViews(const HistoryItemsList &items) {
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto history = items.front()->history();
|
||||
for (const auto &item : items) {
|
||||
_data->requestItemViewRefresh(item);
|
||||
history->lastItemDialogsView.itemInvalidated(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ class Session;
|
||||
|
||||
struct Group {
|
||||
HistoryItemsList items;
|
||||
|
||||
};
|
||||
|
||||
class Groups {
|
||||
|
||||
@@ -72,13 +72,13 @@ void Histories::readInbox(not_null<History*> history) {
|
||||
if (history->lastServerMessageKnown()) {
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: last known, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
).arg(last ? last->id.bare : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
return;
|
||||
} else if (history->loadedAtBottom()) {
|
||||
if (const auto lastId = history->maxMsgId()) {
|
||||
DEBUG_LOG(("Reading: loaded at bottom, maxMsgId %1."
|
||||
).arg(lastId));
|
||||
).arg(lastId.bare));
|
||||
readInboxTill(history, lastId);
|
||||
return;
|
||||
} else if (history->loadedAtTop()) {
|
||||
@@ -93,7 +93,7 @@ void Histories::readInbox(not_null<History*> history) {
|
||||
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: got entry, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
).arg(last ? last->id.bare : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
});
|
||||
}
|
||||
@@ -147,7 +147,7 @@ void Histories::readInboxTill(
|
||||
Expects(IsServerMsgId(tillId) || (!tillId && !force));
|
||||
|
||||
DEBUG_LOG(("Reading: readInboxTill %1, force %2."
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(Logs::b(force)));
|
||||
|
||||
const auto syncGuard = gsl::finally([&] {
|
||||
@@ -156,8 +156,8 @@ void Histories::readInboxTill(
|
||||
if (history->unreadCount() > 0) {
|
||||
if (const auto last = history->lastServerMessage()) {
|
||||
DEBUG_LOG(("Reading: checking last %1 and %2."
|
||||
).arg(last->id
|
||||
).arg(tillId));
|
||||
).arg(last->id.bare
|
||||
).arg(tillId.bare));
|
||||
if (last->id == tillId) {
|
||||
DEBUG_LOG(("Reading: locally marked as read."));
|
||||
history->setUnreadCount(0);
|
||||
@@ -180,11 +180,11 @@ void Histories::readInboxTill(
|
||||
const auto maybeState = lookup(history);
|
||||
if (maybeState && maybeState->sentReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 3 with %1."
|
||||
).arg(maybeState->sentReadTill));
|
||||
).arg(maybeState->sentReadTill.bare));
|
||||
return;
|
||||
} else if (maybeState && maybeState->willReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 4 with %1 and force %2."
|
||||
).arg(maybeState->sentReadTill
|
||||
).arg(maybeState->sentReadTill.bare
|
||||
).arg(Logs::b(force)));
|
||||
if (force) {
|
||||
sendPendingReadInbox(history);
|
||||
@@ -200,7 +200,7 @@ void Histories::readInboxTill(
|
||||
&& history->unreadCountKnown()
|
||||
&& *stillUnread == history->unreadCount()) {
|
||||
DEBUG_LOG(("Reading: count didn't change so just update till %1"
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
history->setInboxReadTill(tillId);
|
||||
return;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ void Histories::readInboxTill(
|
||||
state.willReadTill = tillId;
|
||||
if (force || !stillUnread || !*stillUnread) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with still unread %2"
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(stillUnread.value_or(-666)));
|
||||
state.willReadWhen = 0;
|
||||
sendReadRequests();
|
||||
@@ -216,17 +216,18 @@ void Histories::readInboxTill(
|
||||
return;
|
||||
}
|
||||
} else if (!state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with postponed").arg(tillId));
|
||||
DEBUG_LOG(("Reading: will read till %1 with postponed"
|
||||
).arg(tillId.bare));
|
||||
state.willReadWhen = crl::now() + kReadRequestTimeout;
|
||||
if (!_readRequestsTimer.isActive()) {
|
||||
_readRequestsTimer.callOnce(kReadRequestTimeout);
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG(("Reading: will read till %1 postponed already"
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
}
|
||||
DEBUG_LOG(("Reading: marking now with till %1 and still %2"
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(*stillUnread));
|
||||
history->setInboxReadTill(tillId);
|
||||
history->setUnreadCount(*stillUnread);
|
||||
@@ -437,10 +438,53 @@ void Histories::requestFakeChatListMessage(
|
||||
});
|
||||
}
|
||||
|
||||
void Histories::requestGroupAround(not_null<HistoryItem*> item) {
|
||||
const auto history = item->history();
|
||||
const auto id = item->id;
|
||||
const auto i = _chatListGroupRequests.find(history);
|
||||
if (i != end(_chatListGroupRequests)) {
|
||||
if (i->second.aroundId == id) {
|
||||
return;
|
||||
} else {
|
||||
cancelRequest(i->second.requestId);
|
||||
_chatListGroupRequests.erase(i);
|
||||
}
|
||||
}
|
||||
constexpr auto kMaxAlbumCount = 10;
|
||||
const auto requestId = sendRequest(history, RequestType::History, [=](
|
||||
Fn<void()> finish) {
|
||||
return session().api().request(MTPmessages_GetHistory(
|
||||
history->peer->input,
|
||||
MTP_int(id),
|
||||
MTP_int(0), // offset_date
|
||||
MTP_int(-kMaxAlbumCount),
|
||||
MTP_int(2 * kMaxAlbumCount - 1),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_long(0) // hash
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_owner->processExistingMessages(
|
||||
history->peer->asChannel(),
|
||||
result);
|
||||
_chatListGroupRequests.remove(history);
|
||||
history->migrateToOrMe()->applyChatListGroup(
|
||||
history->channelId(),
|
||||
result);
|
||||
finish();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_chatListGroupRequests.remove(history);
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
_chatListGroupRequests.emplace(
|
||||
history,
|
||||
ChatListGroupRequest{ .aroundId = id, .requestId = requestId });
|
||||
}
|
||||
|
||||
void Histories::sendPendingReadInbox(not_null<History*> history) {
|
||||
if (const auto state = lookup(history)) {
|
||||
DEBUG_LOG(("Reading: send pending now with till %1 and when %2"
|
||||
).arg(state->willReadTill
|
||||
).arg(state->willReadTill.bare
|
||||
).arg(state->willReadWhen));
|
||||
if (state->willReadTill && state->willReadWhen) {
|
||||
state->willReadWhen = 0;
|
||||
@@ -462,7 +506,7 @@ void Histories::sendReadRequests() {
|
||||
continue;
|
||||
} else if (state.willReadWhen <= now) {
|
||||
DEBUG_LOG(("Reading: sending with till %1."
|
||||
).arg(state.willReadTill));
|
||||
).arg(state.willReadTill.bare));
|
||||
sendReadRequest(history, state);
|
||||
} else if (!next || *next > state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: scheduling for later send."));
|
||||
@@ -483,10 +527,10 @@ void Histories::sendReadRequest(not_null<History*> history, State &state) {
|
||||
state.willReadWhen = 0;
|
||||
state.sentReadDone = false;
|
||||
DEBUG_LOG(("Reading: sending request now with till %1."
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
sendRequest(history, RequestType::ReadInbox, [=](Fn<void()> finish) {
|
||||
DEBUG_LOG(("Reading: sending request invoked with till %1."
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
const auto finished = [=] {
|
||||
const auto state = lookup(history);
|
||||
Assert(state != nullptr);
|
||||
|
||||
@@ -59,6 +59,8 @@ public:
|
||||
void changeDialogUnreadMark(not_null<History*> history, bool unread);
|
||||
void requestFakeChatListMessage(not_null<History*> history);
|
||||
|
||||
void requestGroupAround(not_null<HistoryItem*> item);
|
||||
|
||||
void deleteMessages(
|
||||
not_null<History*> history,
|
||||
const QVector<MTPint> &ids,
|
||||
@@ -95,6 +97,10 @@ private:
|
||||
bool sentReadDone = false;
|
||||
bool postponedRequestEntry = false;
|
||||
};
|
||||
struct ChatListGroupRequest {
|
||||
MsgId aroundId = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
void readInboxTill(not_null<History*> history, MsgId tillId, bool force);
|
||||
void sendReadRequests();
|
||||
@@ -130,6 +136,10 @@ private:
|
||||
|
||||
base::flat_set<not_null<History*>> _fakeChatListRequests;
|
||||
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
ChatListGroupRequest> _chatListGroupRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_theme_document.h"
|
||||
#include "history/view/media/history_view_slot_machine.h"
|
||||
#include "history/view/media/history_view_dice.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/text/format_values.h"
|
||||
@@ -37,23 +38,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localstorage.h"
|
||||
#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_auto_download.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
constexpr auto kMaxPreviewImages = 3;
|
||||
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
|
||||
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
|
||||
auto result = Call();
|
||||
@@ -98,20 +108,23 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
|
||||
[[nodiscard]] QString WithCaptionDialogsText(
|
||||
const QString &attachType,
|
||||
const QString &caption) {
|
||||
const QString &caption,
|
||||
bool hasMiniImages) {
|
||||
if (caption.isEmpty()) {
|
||||
return textcmdLink(1, TextUtilities::Clean(attachType));
|
||||
}
|
||||
|
||||
return tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media_part,
|
||||
textcmdLink(1, tr::lng_dialogs_text_media_wrapped(
|
||||
return hasMiniImages
|
||||
? TextUtilities::Clean(caption)
|
||||
: tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media,
|
||||
TextUtilities::Clean(attachType))),
|
||||
lt_caption,
|
||||
TextUtilities::Clean(caption));
|
||||
lt_media_part,
|
||||
textcmdLink(1, tr::lng_dialogs_text_media_wrapped(
|
||||
tr::now,
|
||||
lt_media,
|
||||
TextUtilities::Clean(attachType))),
|
||||
lt_caption,
|
||||
TextUtilities::Clean(caption));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString WithCaptionNotificationText(
|
||||
@@ -132,6 +145,151 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
caption);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PreparePreviewImage(
|
||||
not_null<const Image*> image,
|
||||
ImageRoundRadius radius = ImageRoundRadius::Small) {
|
||||
const auto original = image->original();
|
||||
if (original.width() * 10 < original.height()
|
||||
|| original.height() * 10 < original.width()) {
|
||||
return QImage();
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::dialogsMiniPreview * factor;
|
||||
const auto scaled = original.scaled(
|
||||
QSize(size, size),
|
||||
Qt::KeepAspectRatioByExpanding,
|
||||
Qt::SmoothTransformation);
|
||||
auto square = scaled.copy(
|
||||
(scaled.width() - size) / 2,
|
||||
(scaled.height() - size) / 2,
|
||||
size,
|
||||
size
|
||||
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
if (radius == ImageRoundRadius::Small) {
|
||||
struct Cache {
|
||||
base::flat_map<int, std::array<QImage, 4>> all;
|
||||
std::array<QImage, 4> *lastUsed = nullptr;
|
||||
int lastUsedRadius = 0;
|
||||
};
|
||||
static auto cache = Cache();
|
||||
const auto pxRadius = st::dialogsMiniPreviewRadius;
|
||||
if (!cache.lastUsed || cache.lastUsedRadius != pxRadius) {
|
||||
cache.lastUsedRadius = pxRadius;
|
||||
const auto i = cache.all.find(pxRadius);
|
||||
if (i != end(cache.all)) {
|
||||
cache.lastUsed = &i->second;
|
||||
} else {
|
||||
cache.lastUsed = &cache.all.emplace(
|
||||
pxRadius,
|
||||
Images::CornersMask(pxRadius)).first->second;
|
||||
}
|
||||
}
|
||||
Images::prepareRound(square, *cache.lastUsed);
|
||||
} else {
|
||||
Images::prepareRound(square, radius);
|
||||
}
|
||||
square.setDevicePixelRatio(factor);
|
||||
return square;
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<PhotoMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
const auto photo = media->owner();
|
||||
const auto readyCacheKey = reinterpret_cast<uint64>(photo.get());
|
||||
if (const auto small = media->image(PhotoSize::Small)) {
|
||||
return { PreparePreviewImage(small, radius), readyCacheKey };
|
||||
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
|
||||
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
|
||||
} else if (const auto large = media->image(PhotoSize::Large)) {
|
||||
return { PreparePreviewImage(large, radius), readyCacheKey };
|
||||
}
|
||||
const auto allowedToDownload = [&] {
|
||||
const auto photo = media->owner();
|
||||
if (media->loaded() || photo->cancelled()) {
|
||||
return false;
|
||||
}
|
||||
return photo->hasExact(PhotoSize::Small)
|
||||
|| photo->hasExact(PhotoSize::Thumbnail)
|
||||
|| AutoDownload::Should(
|
||||
photo->session().settings().autoDownload(),
|
||||
item->history()->peer,
|
||||
photo);
|
||||
}();
|
||||
const auto cacheKey = allowedToDownload ? 0 : readyCacheKey;
|
||||
if (allowedToDownload) {
|
||||
media->owner()->load(PhotoSize::Small, item->fullId());
|
||||
}
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), cacheKey };
|
||||
}
|
||||
return { QImage(), allowedToDownload ? 0 : cacheKey };
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
Expects(media->owner()->hasThumbnail());
|
||||
|
||||
const auto document = media->owner();
|
||||
const auto readyCacheKey = reinterpret_cast<uint64>(document.get());
|
||||
if (const auto thumbnail = media->thumbnail()) {
|
||||
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
|
||||
}
|
||||
document->loadThumbnail(item->fullId());
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), 0 };
|
||||
}
|
||||
return { QImage(), 0 };
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
|
||||
Expects(!preview.isNull());
|
||||
|
||||
{
|
||||
QPainter p(&preview);
|
||||
st::dialogsMiniPlay.paintInCenter(
|
||||
p,
|
||||
QRect(QPoint(), preview.size() / preview.devicePixelRatio()));
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PrepareFilePreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
auto result = PrepareFilePreviewImage(item, media, radius);
|
||||
const auto document = media->owner();
|
||||
if (!result.data.isNull()
|
||||
&& (document->isVideoFile() || document->isVideoMessage())) {
|
||||
result.data = PutPlayIcon(std::move(result.data));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool TryFilePreview(not_null<DocumentData*> document) {
|
||||
return document->hasThumbnail()
|
||||
&& !document->sticker()
|
||||
&& !document->isAudioFile();
|
||||
}
|
||||
|
||||
template <typename MediaType>
|
||||
[[nodiscard]] ItemPreviewImage FindCachedPreview(
|
||||
const std::vector<ItemPreviewImage> *existing,
|
||||
not_null<MediaType*> data) {
|
||||
if (!existing) {
|
||||
return {};
|
||||
}
|
||||
const auto i = ranges::find(
|
||||
*existing,
|
||||
reinterpret_cast<uint64>(data.get()),
|
||||
&ItemPreviewImage::cacheKey);
|
||||
return (i != end(*existing)) ? *i : ItemPreviewImage();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextForMimeData WithCaptionClipboardText(
|
||||
@@ -201,11 +359,12 @@ bool Media::canBeGrouped() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Media::chatListText(DrawInDialog way) const {
|
||||
ItemPreview Media::toPreview(ToPreviewOptions options) const {
|
||||
auto result = notificationText();
|
||||
return result.isEmpty()
|
||||
auto text = result.isEmpty()
|
||||
? QString()
|
||||
: textcmdLink(1, TextUtilities::Clean(std::move(result)));
|
||||
return { .text = std::move(text) };
|
||||
}
|
||||
|
||||
bool Media::hasReplyPreview() const {
|
||||
@@ -270,6 +429,52 @@ std::unique_ptr<HistoryView::Media> Media::createView(
|
||||
return createView(message, message->data(), replacing);
|
||||
}
|
||||
|
||||
ItemPreview Media::toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const {
|
||||
const auto genericText = textcmdLink(
|
||||
1,
|
||||
TextUtilities::Clean(tr::lng_in_dlg_album(tr::now)));
|
||||
auto result = ItemPreview();
|
||||
auto loadingContext = std::vector<std::any>();
|
||||
for (const auto &item : items) {
|
||||
if (const auto media = item->media()) {
|
||||
auto copy = options;
|
||||
copy.ignoreGroup = true;
|
||||
const auto already = int(result.images.size());
|
||||
const auto left = kMaxPreviewImages - already;
|
||||
auto single = left ? media->toPreview(copy) : ItemPreview();
|
||||
if (!single.images.empty()) {
|
||||
while (single.images.size() > left) {
|
||||
single.images.pop_back();
|
||||
}
|
||||
result.images.insert(
|
||||
end(result.images),
|
||||
std::make_move_iterator(begin(single.images)),
|
||||
std::make_move_iterator(end(single.images)));
|
||||
}
|
||||
if (single.loadingContext.has_value()) {
|
||||
loadingContext.push_back(std::move(single.loadingContext));
|
||||
}
|
||||
const auto original = item->originalText().text;
|
||||
if (!original.isEmpty()) {
|
||||
if (result.text.isEmpty()) {
|
||||
result.text = TextUtilities::Clean(original);
|
||||
} else {
|
||||
result.text = genericText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.text.isEmpty()) {
|
||||
result.text = genericText;
|
||||
}
|
||||
if (!loadingContext.empty()) {
|
||||
result.loadingContext = std::move(loadingContext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MediaPhoto::MediaPhoto(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PhotoData*> photo)
|
||||
@@ -341,11 +546,40 @@ QString MediaPhoto::notificationText() const {
|
||||
parent()->originalText().text);
|
||||
}
|
||||
|
||||
QString MediaPhoto::chatListText(DrawInDialog way) const {
|
||||
const auto caption = (way == DrawInDialog::WithoutSenderAndCaption)
|
||||
ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||
const auto item = parent();
|
||||
if (!options.ignoreGroup && item->groupId()) {
|
||||
if (const auto group = item->history()->owner().groups().find(item)
|
||||
; group && group->items.size() > 1) {
|
||||
return toGroupPreview(group->items, options);
|
||||
}
|
||||
}
|
||||
auto images = std::vector<ItemPreviewImage>();
|
||||
auto context = std::any();
|
||||
if (auto cached = FindCachedPreview(options.existing, _photo)) {
|
||||
images.push_back(std::move(cached));
|
||||
} else {
|
||||
const auto media = _photo->createMediaView();
|
||||
const auto radius = _chat
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (auto prepared = PreparePhotoPreview(parent(), media, radius)
|
||||
; prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto type = tr::lng_in_dlg_photo(tr::now);
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
return WithCaptionDialogsText(tr::lng_in_dlg_photo(tr::now), caption);
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, !images.empty()),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaPhoto::pinnedTextSubstring() const {
|
||||
@@ -511,16 +745,40 @@ bool MediaFile::replyPreviewLoaded() const {
|
||||
return _document->replyPreviewLoaded();
|
||||
}
|
||||
|
||||
QString MediaFile::chatListText(DrawInDialog way) const {
|
||||
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
const auto item = parent();
|
||||
if (!options.ignoreGroup && item->groupId()) {
|
||||
if (const auto group = item->history()->owner().groups().find(item)
|
||||
; group && group->items.size() > 1) {
|
||||
return toGroupPreview(group->items, options);
|
||||
}
|
||||
}
|
||||
if (const auto sticker = _document->sticker()) {
|
||||
return Media::chatListText(way);
|
||||
return Media::toPreview(options);
|
||||
}
|
||||
auto images = std::vector<ItemPreviewImage>();
|
||||
auto context = std::any();
|
||||
if (auto cached = FindCachedPreview(options.existing, _document)) {
|
||||
images.push_back(std::move(cached));
|
||||
} else if (TryFilePreview(_document)) {
|
||||
const auto media = _document->createMediaView();
|
||||
const auto radius = _document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (auto prepared = PrepareFilePreview(parent(), media, radius)
|
||||
; prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto type = [&] {
|
||||
using namespace Ui::Text;
|
||||
if (_document->isVideoMessage()) {
|
||||
return tr::lng_in_dlg_video_message(tr::now);
|
||||
} else if (_document->isAnimation()) {
|
||||
return qsl("GIF");
|
||||
return u"GIF"_q;
|
||||
} else if (_document->isVideoFile()) {
|
||||
return tr::lng_in_dlg_video(tr::now);
|
||||
} else if (_document->isVoiceMessage()) {
|
||||
@@ -533,10 +791,14 @@ QString MediaFile::chatListText(DrawInDialog way) const {
|
||||
}
|
||||
return tr::lng_in_dlg_file(tr::now);
|
||||
}();
|
||||
const auto caption = (way == DrawInDialog::WithoutSenderAndCaption)
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
return WithCaptionDialogsText(type, caption);
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, !images.empty()),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaFile::notificationText() const {
|
||||
@@ -855,8 +1117,10 @@ Data::CloudImage *MediaLocation::location() const {
|
||||
return _location;
|
||||
}
|
||||
|
||||
QString MediaLocation::chatListText(DrawInDialog way) const {
|
||||
return WithCaptionDialogsText(tr::lng_maps_point(tr::now), _title);
|
||||
ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const {
|
||||
const auto type = tr::lng_maps_point(tr::now);
|
||||
const auto hasMiniImages = false;
|
||||
return { .text = WithCaptionDialogsText(type, _title, hasMiniImages) };
|
||||
}
|
||||
|
||||
QString MediaLocation::notificationText() const {
|
||||
@@ -1049,8 +1313,8 @@ bool MediaWebPage::replyPreviewLoaded() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MediaWebPage::chatListText(DrawInDialog way) const {
|
||||
return notificationText();
|
||||
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|
||||
return { .text = notificationText() };
|
||||
}
|
||||
|
||||
QString MediaWebPage::notificationText() const {
|
||||
|
||||
@@ -27,7 +27,9 @@ namespace HistoryView {
|
||||
enum class Context : char;
|
||||
class Element;
|
||||
class Media;
|
||||
enum class DrawInDialog;
|
||||
struct ItemPreview;
|
||||
struct ItemPreviewImage;
|
||||
struct ToPreviewOptions;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Data {
|
||||
@@ -73,7 +75,9 @@ public:
|
||||
|
||||
not_null<HistoryItem*> parent() const;
|
||||
|
||||
using DrawInDialog = HistoryView::DrawInDialog;
|
||||
using ToPreviewOptions = HistoryView::ToPreviewOptions;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0;
|
||||
|
||||
@@ -95,7 +99,7 @@ public:
|
||||
virtual bool replyPreviewLoaded() const;
|
||||
// Returns text with link-start and link-end commands for service-color highlighting.
|
||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
virtual QString chatListText(DrawInDialog way) const;
|
||||
virtual ItemPreview toPreview(ToPreviewOptions way) const;
|
||||
virtual QString notificationText() const = 0;
|
||||
virtual QString pinnedTextSubstring() const = 0;
|
||||
virtual TextForMimeData clipboardText() const = 0;
|
||||
@@ -125,6 +129,11 @@ public:
|
||||
not_null<HistoryView::Element*> message,
|
||||
HistoryView::Element *replacing = nullptr);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] ItemPreview toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const;
|
||||
|
||||
private:
|
||||
const not_null<HistoryItem*> _parent;
|
||||
|
||||
@@ -151,7 +160,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -189,7 +198,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -255,7 +264,7 @@ public:
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
Data::CloudImage *location() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -323,7 +332,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
|
||||
192
Telegram/SourceFiles/data/data_msg_id.h
Normal file
192
Telegram/SourceFiles/data/data_msg_id.h
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
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 "data/data_peer_id.h"
|
||||
|
||||
struct MsgId {
|
||||
constexpr MsgId() noexcept = default;
|
||||
constexpr MsgId(int64 value) noexcept : bare(value) {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept {
|
||||
return (bare != 0);
|
||||
}
|
||||
[[nodiscard]] constexpr bool operator!() const noexcept {
|
||||
return !bare;
|
||||
}
|
||||
[[nodiscard]] constexpr MsgId operator-() const noexcept {
|
||||
return -bare;
|
||||
}
|
||||
constexpr MsgId operator++() noexcept {
|
||||
return ++bare;
|
||||
}
|
||||
constexpr MsgId operator++(int) noexcept {
|
||||
return bare++;
|
||||
}
|
||||
constexpr MsgId operator--() noexcept {
|
||||
return --bare;
|
||||
}
|
||||
constexpr MsgId operator--(int) noexcept {
|
||||
return bare--;
|
||||
}
|
||||
|
||||
int64 bare = 0;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(MsgId);
|
||||
|
||||
[[nodiscard]] inline constexpr MsgId operator+(MsgId a, MsgId b) noexcept {
|
||||
return MsgId(a.bare + b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr MsgId operator-(MsgId a, MsgId b) noexcept {
|
||||
return MsgId(a.bare - b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare == b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare != b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare < b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare > b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare <= b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare >= b.bare);
|
||||
}
|
||||
|
||||
constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
|
||||
constexpr auto EndClientMsgId = MsgId(-(1LL << 57));
|
||||
constexpr auto ServerMaxMsgId = MsgId(1LL << 56);
|
||||
constexpr auto ShowAtUnreadMsgId = MsgId(0);
|
||||
|
||||
constexpr auto SpecialMsgIdShift = EndClientMsgId.bare;
|
||||
constexpr auto ShowAtTheEndMsgId = MsgId(SpecialMsgIdShift + 1);
|
||||
constexpr auto SwitchAtTopMsgId = MsgId(SpecialMsgIdShift + 2);
|
||||
constexpr auto ShowAtProfileMsgId = MsgId(SpecialMsgIdShift + 3);
|
||||
constexpr auto ShowAndStartBotMsgId = MsgId(SpecialMsgIdShift + 4);
|
||||
constexpr auto ShowAtGameShareMsgId = MsgId(SpecialMsgIdShift + 5);
|
||||
constexpr auto ShowForChooseMessagesMsgId = MsgId(SpecialMsgIdShift + 6);
|
||||
|
||||
static_assert(SpecialMsgIdShift + 0xFF < 0);
|
||||
static_assert(-(SpecialMsgIdShift + 0xFF) > ServerMaxMsgId);
|
||||
|
||||
[[nodiscard]] constexpr inline bool IsClientMsgId(MsgId id) noexcept {
|
||||
return (id >= StartClientMsgId && id < EndClientMsgId);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr inline bool IsServerMsgId(MsgId id) noexcept {
|
||||
return (id > 0 && id < ServerMaxMsgId);
|
||||
}
|
||||
|
||||
struct MsgRange {
|
||||
constexpr MsgRange() noexcept = default;
|
||||
constexpr MsgRange(MsgId from, MsgId till) noexcept
|
||||
: from(from)
|
||||
, till(till) {
|
||||
}
|
||||
|
||||
MsgId from = 0;
|
||||
MsgId till = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(
|
||||
MsgRange a,
|
||||
MsgRange b) noexcept {
|
||||
return (a.from == b.from) && (a.till == b.till);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(
|
||||
MsgRange a,
|
||||
MsgRange b) noexcept {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
struct FullMsgId {
|
||||
constexpr FullMsgId() noexcept = default;
|
||||
constexpr FullMsgId(ChannelId channel, MsgId msg) noexcept
|
||||
: channel(channel), msg(msg) {
|
||||
}
|
||||
|
||||
constexpr explicit operator bool() const noexcept {
|
||||
return msg != 0;
|
||||
}
|
||||
constexpr bool operator!() const noexcept {
|
||||
return msg == 0;
|
||||
}
|
||||
|
||||
ChannelId channel = NoChannel;
|
||||
MsgId msg = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
if (a.channel < b.channel) {
|
||||
return true;
|
||||
} else if (a.channel > b.channel) {
|
||||
return false;
|
||||
}
|
||||
return a.msg < b.msg;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return b < a;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(b < a);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return (a.channel == b.channel) && (a.msg == b.msg);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(FullMsgId);
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<MsgId> : private hash<int64> {
|
||||
size_t operator()(MsgId value) const noexcept {
|
||||
return hash<int64>::operator()(value.bare);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
@@ -1004,19 +1004,24 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PeerData::setThemeEmoji(const QString &emoji) {
|
||||
if (_themeEmoji == emoji) {
|
||||
void PeerData::setThemeEmoji(const QString &emoticon) {
|
||||
if (_themeEmoticon == emoticon) {
|
||||
return;
|
||||
}
|
||||
_themeEmoji = emoji;
|
||||
if (!emoji.isEmpty() && !owner().cloudThemes().themeForEmoji(emoji)) {
|
||||
if (Ui::Emoji::Find(_themeEmoticon) == Ui::Emoji::Find(emoticon)) {
|
||||
_themeEmoticon = emoticon;
|
||||
return;
|
||||
}
|
||||
_themeEmoticon = emoticon;
|
||||
if (!emoticon.isEmpty()
|
||||
&& !owner().cloudThemes().themeForEmoji(emoticon)) {
|
||||
owner().cloudThemes().refreshChatThemes();
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
|
||||
}
|
||||
|
||||
const QString &PeerData::themeEmoji() const {
|
||||
return _themeEmoji;
|
||||
return _themeEmoticon;
|
||||
}
|
||||
|
||||
void PeerData::setIsBlocked(bool is) {
|
||||
|
||||
@@ -459,7 +459,7 @@ public:
|
||||
[[nodiscard]] Data::GroupCall *groupCall() const;
|
||||
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
|
||||
|
||||
void setThemeEmoji(const QString &emoji);
|
||||
void setThemeEmoji(const QString &emoticon);
|
||||
[[nodiscard]] const QString &themeEmoji() const;
|
||||
|
||||
const PeerId id;
|
||||
@@ -506,7 +506,7 @@ private:
|
||||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||
|
||||
QString _about;
|
||||
QString _themeEmoji;
|
||||
QString _themeEmoticon;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ constexpr auto kMessagesPerPage = 50;
|
||||
TimeId date,
|
||||
const QString &text) {
|
||||
return history->makeServiceMessage(
|
||||
history->session().data().nextNonHistoryEntryId(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
MessageFlag::FakeHistoryItem,
|
||||
date,
|
||||
HistoryService::PreparedText{ text });
|
||||
@@ -101,7 +101,7 @@ rpl::producer<MessagesSlice> RepliesList::source(
|
||||
_partLoaded.events(
|
||||
) | rpl::start_with_next(pushDelayed, lifetime);
|
||||
|
||||
_history->session().data().channelDifferenceTooLong(
|
||||
_history->owner().channelDifferenceTooLong(
|
||||
) | rpl::filter([=](not_null<ChannelData*> channel) {
|
||||
if (_history->peer != channel || !_skippedAfter.has_value()) {
|
||||
return false;
|
||||
|
||||
@@ -32,18 +32,18 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
&& (item->date() > base::unixtime::now());
|
||||
}
|
||||
|
||||
MTPMessage PrepareMessage(const MTPMessage &message, MsgId id) {
|
||||
MTPMessage PrepareMessage(const MTPMessage &message) {
|
||||
return message.match([&](const MTPDmessageEmpty &data) {
|
||||
return MTP_messageEmpty(
|
||||
data.vflags(),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vpeer_id() ? *data.vpeer_id() : MTPPeer());
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
return MTP_messageService(
|
||||
MTP_flags(data.vflags().v
|
||||
| MTPDmessageService::Flag(
|
||||
MTPDmessage::Flag::f_from_scheduled)),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -53,7 +53,7 @@ MTPMessage PrepareMessage(const MTPMessage &message, MsgId id) {
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return MTP_message(
|
||||
MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
@@ -192,6 +192,7 @@ void ScheduledMessages::sendNowSimpleMessage(
|
||||
const auto views = 1;
|
||||
const auto forwards = 0;
|
||||
history->addNewMessage(
|
||||
update.vid().v,
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
update.vid(),
|
||||
@@ -269,7 +270,8 @@ void ScheduledMessages::checkEntitiesAndUpdate(const MTPDmessage &data) {
|
||||
qs(data.vmessage()),
|
||||
Api::EntitiesFromMTP(_session, data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
existing->updateReplyMarkup(data.vreply_markup());
|
||||
existing->updateReplyMarkup(
|
||||
HistoryMessageMarkupData(data.vreply_markup()));
|
||||
existing->updateForwardedInfo(data.vfwd_from());
|
||||
_session->data().requestItemTextRefresh(existing);
|
||||
|
||||
@@ -454,7 +456,8 @@ HistoryItem *ScheduledMessages::append(
|
||||
_session,
|
||||
data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
existing->updateReplyMarkup(data.vreply_markup());
|
||||
existing->updateReplyMarkup(
|
||||
HistoryMessageMarkupData(data.vreply_markup()));
|
||||
existing->updateForwardedInfo(data.vfwd_from());
|
||||
existing->updateDate(data.vdate().v);
|
||||
history->owner().requestItemTextRefresh(existing);
|
||||
@@ -463,7 +466,8 @@ HistoryItem *ScheduledMessages::append(
|
||||
}
|
||||
|
||||
const auto item = _session->data().addNewMessage(
|
||||
PrepareMessage(message, history->nextNonHistoryEntryId()),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareMessage(message),
|
||||
MessageFlags(), // localFlags
|
||||
NewMessageType::Existing);
|
||||
if (!item || item->history() != history) {
|
||||
@@ -546,7 +550,7 @@ uint64 ScheduledMessages::countListHash(const List &list) const {
|
||||
}) | ranges::views::reverse;
|
||||
for (const auto &item : serverside) {
|
||||
const auto j = list.idByItem.find(item.get());
|
||||
HashUpdate(hash, j->second);
|
||||
HashUpdate(hash, j->second.bare);
|
||||
if (const auto edited = item->Get<HistoryMessageEdited>()) {
|
||||
HashUpdate(hash, edited->date);
|
||||
} else {
|
||||
|
||||
@@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_send_action.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_media_rotation.h"
|
||||
@@ -95,7 +96,7 @@ void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
|
||||
return;
|
||||
}
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
for (const auto &row : markup->rows) {
|
||||
for (const auto &row : markup->data.rows) {
|
||||
for (const auto &button : row) {
|
||||
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||
if (button.type == ButtonType::SwitchInline) {
|
||||
@@ -239,7 +240,8 @@ Session::Session(not_null<Main::Session*> session)
|
||||
, _streaming(std::make_unique<Streaming>(this))
|
||||
, _mediaRotation(std::make_unique<MediaRotation>())
|
||||
, _histories(std::make_unique<Histories>(this))
|
||||
, _stickers(std::make_unique<Stickers>(this)) {
|
||||
, _stickers(std::make_unique<Stickers>(this))
|
||||
, _sponsoredMessages(std::make_unique<SponsoredMessages>(this)) {
|
||||
_cache->open(_session->local().cacheKey());
|
||||
_bigFileCache->open(_session->local().cacheBigFileKey());
|
||||
|
||||
@@ -280,6 +282,7 @@ void Session::clear() {
|
||||
|
||||
_histories->unloadAll();
|
||||
_scheduledMessages = nullptr;
|
||||
_sponsoredMessages = nullptr;
|
||||
_dependentMessages.clear();
|
||||
base::take(_messages);
|
||||
base::take(_channelMessages);
|
||||
@@ -1382,6 +1385,10 @@ void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto history = item->history();
|
||||
if (history->lastItemDialogsView.dependsOn(item)) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
|
||||
@@ -1838,8 +1845,8 @@ void Session::processMessages(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const auto id = IdFromMessage(message);
|
||||
indices.emplace((uint64(uint32(id)) << 32) | uint64(i), i);
|
||||
const auto id = IdFromMessage(message); // Only 32 bit values here.
|
||||
indices.emplace((uint64(uint32(id.bare)) << 32) | uint64(i), i);
|
||||
}
|
||||
for (const auto &[position, index] : indices) {
|
||||
addNewMessage(
|
||||
@@ -1855,6 +1862,26 @@ void Session::processMessages(
|
||||
processMessages(data.v, type);
|
||||
}
|
||||
|
||||
void Session::processExistingMessages(
|
||||
ChannelData *channel,
|
||||
const MTPmessages_Messages &data) {
|
||||
data.match([&](const MTPDmessages_channelMessages &data) {
|
||||
if (channel) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages!"));
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
|
||||
data.match([&](const MTPDmessages_messagesNotModified&) {
|
||||
LOG(("API Error: received messages.messagesNotModified!"));
|
||||
}, [&](const auto &data) {
|
||||
processUsers(data.vusers());
|
||||
processChats(data.vchats());
|
||||
processMessages(data.vmessages(), NewMessageType::Existing);
|
||||
});
|
||||
}
|
||||
|
||||
const Session::Messages *Session::messagesList(ChannelId channelId) const {
|
||||
if (channelId == NoChannel) {
|
||||
return &_messages;
|
||||
@@ -2166,12 +2193,21 @@ HistoryItem *Session::addNewMessage(
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
return addNewMessage(IdFromMessage(data), data, localFlags, type);
|
||||
}
|
||||
|
||||
HistoryItem *Session::addNewMessage(
|
||||
MsgId id,
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
const auto peerId = PeerFromMessage(data);
|
||||
if (!peerId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto result = history(peerId)->addNewMessage(
|
||||
id,
|
||||
data,
|
||||
localFlags,
|
||||
type);
|
||||
@@ -3948,10 +3984,12 @@ void Session::insertCheckedServiceNotification(
|
||||
| MessageFlag::LocalHistoryEntry;
|
||||
auto sending = TextWithEntities(), left = message;
|
||||
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
|
||||
const auto id = nextLocalMessageId();
|
||||
addNewMessage(
|
||||
id,
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
MTP_int(nextLocalMessageId()),
|
||||
MTP_int(0), // Not used (would've been trimmed to 32 bits).
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
MTPMessageFwdHeader(),
|
||||
|
||||
@@ -51,6 +51,7 @@ class LocationPoint;
|
||||
class WallPaper;
|
||||
class ScheduledMessages;
|
||||
class SendActionManager;
|
||||
class SponsoredMessages;
|
||||
class ChatFilters;
|
||||
class CloudThemes;
|
||||
class Streaming;
|
||||
@@ -109,6 +110,9 @@ public:
|
||||
[[nodiscard]] Stickers &stickers() const {
|
||||
return *_stickers;
|
||||
}
|
||||
[[nodiscard]] SponsoredMessages &sponsoredMessages() const {
|
||||
return *_sponsoredMessages;
|
||||
}
|
||||
[[nodiscard]] MsgId nextNonHistoryEntryId() {
|
||||
return ++_nonHistoryEntryId;
|
||||
}
|
||||
@@ -341,6 +345,9 @@ public:
|
||||
void processMessages(
|
||||
const MTPVector<MTPMessage> &data,
|
||||
NewMessageType type);
|
||||
void processExistingMessages(
|
||||
ChannelData *channel,
|
||||
const MTPmessages_Messages &data);
|
||||
void processMessagesDeleted(
|
||||
ChannelId channelId,
|
||||
const QVector<MTPint> &data);
|
||||
@@ -397,6 +404,11 @@ public:
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type);
|
||||
HistoryItem *addNewMessage( // Override message id.
|
||||
MsgId id,
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type);
|
||||
|
||||
[[nodiscard]] int unreadBadge() const;
|
||||
[[nodiscard]] bool unreadBadgeMuted() const;
|
||||
@@ -955,6 +967,7 @@ private:
|
||||
std::unique_ptr<MediaRotation> _mediaRotation;
|
||||
std::unique_ptr<Histories> _histories;
|
||||
std::unique_ptr<Stickers> _stickers;
|
||||
std::unique_ptr<SponsoredMessages> _sponsoredMessages;
|
||||
MsgId _nonHistoryEntryId = ServerMaxMsgId;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
@@ -366,7 +366,7 @@ std::optional<int> SharedMediaWithLastSlice::indexOf(Value value) const {
|
||||
: QString("-"));
|
||||
if (const auto msgId = std::get_if<FullMsgId>(&value)) {
|
||||
info.push_back("value:" + QString::number(msgId->channel.bare));
|
||||
info.push_back(QString::number(msgId->msg));
|
||||
info.push_back(QString::number(msgId->msg.bare));
|
||||
const auto index = _slice.indexOf(*std::get_if<FullMsgId>(&value));
|
||||
info.push_back("index:" + (index
|
||||
? QString::number(*index)
|
||||
|
||||
196
Telegram/SourceFiles/data/data_sponsored_messages.cpp
Normal file
196
Telegram/SourceFiles/data/data_sponsored_messages.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
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 "data/data_sponsored_messages.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] bool TooEarlyForRequest(crl::time received) {
|
||||
return (received > 0) && (received + kRequestTimeLimit > crl::now());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SponsoredMessages::SponsoredMessages(not_null<Session*> owner)
|
||||
: _session(&owner->session())
|
||||
, _clearTimer([=] { clearOldRequests(); }) {
|
||||
}
|
||||
|
||||
SponsoredMessages::~SponsoredMessages() {
|
||||
for (const auto &request : _requests) {
|
||||
_session->api().request(request.second.requestId).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void SponsoredMessages::clearOldRequests() {
|
||||
const auto now = crl::now();
|
||||
while (true) {
|
||||
const auto i = ranges::find_if(_requests, [&](const auto &value) {
|
||||
const auto &request = value.second;
|
||||
return !request.requestId
|
||||
&& (request.lastReceived + kRequestTimeLimit <= now);
|
||||
});
|
||||
if (i == end(_requests)) {
|
||||
break;
|
||||
}
|
||||
_requests.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool SponsoredMessages::append(not_null<History*> history) {
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return false;
|
||||
}
|
||||
auto &list = it->second;
|
||||
if (list.showedAll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto entryIt = ranges::find_if(list.entries, [](const Entry &e) {
|
||||
return e.item == nullptr;
|
||||
});
|
||||
if (entryIt == end(list.entries)) {
|
||||
list.showedAll = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto flags = MessageFlags(0)
|
||||
| (history->isChannel() ? MessageFlag::Post : MessageFlags(0))
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::IsSponsored
|
||||
| MessageFlag::LocalHistoryEntry;
|
||||
auto local = history->addNewLocalMessage(
|
||||
_session->data().nextLocalMessageId(),
|
||||
flags,
|
||||
UserId(0),
|
||||
MsgId(0),
|
||||
HistoryItem::NewMessageDate(0),
|
||||
entryIt->sponsored.fromId,
|
||||
QString(),
|
||||
entryIt->sponsored.textWithEntities,
|
||||
MTP_messageMediaEmpty(),
|
||||
HistoryMessageMarkupData());
|
||||
entryIt->item.reset(std::move(local));
|
||||
|
||||
// Since sponsored posts are only created on demand for display,
|
||||
// we can send a request to view immediately.
|
||||
view(entryIt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SponsoredMessages::request(not_null<History*> history) {
|
||||
auto &request = _requests[history];
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_GetSponsoredMessages(
|
||||
_session->data().channel(history->channelId())->inputChannel
|
||||
)).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
parse(history, result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requests.remove(history);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SponsoredMessages::parse(
|
||||
not_null<History*> history,
|
||||
const MTPmessages_sponsoredMessages &list) {
|
||||
auto &request = _requests[history];
|
||||
request.lastReceived = crl::now();
|
||||
request.requestId = 0;
|
||||
if (!_clearTimer.isActive()) {
|
||||
_clearTimer.callOnce(kRequestTimeLimit * 2);
|
||||
}
|
||||
|
||||
list.match([&](const MTPDmessages_sponsoredMessages &data) {
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
|
||||
const auto &messages = data.vmessages().v;
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
auto &list = _data.emplace(history, List()).first->second;
|
||||
list.entries.clear();
|
||||
for (const auto &message : messages) {
|
||||
append(history, list, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SponsoredMessages::append(
|
||||
not_null<History*> history,
|
||||
List &list,
|
||||
const MTPSponsoredMessage &message) {
|
||||
message.match([&](const MTPDsponsoredMessage &data) {
|
||||
const auto randomId = data.vrandom_id().v;
|
||||
auto sharedMessage = SponsoredMessage{
|
||||
.randomId = randomId,
|
||||
.fromId = peerFromMTP(data.vfrom_id()),
|
||||
.textWithEntities = {
|
||||
.text = qs(data.vmessage()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
_session,
|
||||
data.ventities().value_or_empty()),
|
||||
},
|
||||
.history = history,
|
||||
};
|
||||
list.entries.push_back({ nullptr, std::move(sharedMessage) });
|
||||
});
|
||||
}
|
||||
|
||||
void SponsoredMessages::clearItems(not_null<History*> history) {
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return;
|
||||
}
|
||||
auto &list = it->second;
|
||||
for (auto &entry : list.entries) {
|
||||
entry.item.reset();
|
||||
}
|
||||
list.showedAll = false;
|
||||
}
|
||||
|
||||
void SponsoredMessages::view(const std::vector<Entry>::iterator entryIt) {
|
||||
const auto randomId = entryIt->sponsored.randomId;
|
||||
auto &request = _viewRequests[randomId];
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
const auto history = entryIt->sponsored.history;
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_ViewSponsoredMessage(
|
||||
_session->data().channel(history->channelId())->inputChannel,
|
||||
MTP_bytes(randomId)
|
||||
)).done([=] {
|
||||
auto &request = _viewRequests[randomId];
|
||||
request.lastReceived = crl::now();
|
||||
request.requestId = 0;
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_viewRequests.remove(randomId);
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
79
Telegram/SourceFiles/data/data_sponsored_messages.h
Normal file
79
Telegram/SourceFiles/data/data_sponsored_messages.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 "history/history_item.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class History;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
|
||||
struct SponsoredMessage final {
|
||||
QByteArray randomId;
|
||||
PeerId fromId;
|
||||
TextWithEntities textWithEntities;
|
||||
History *history = nullptr;
|
||||
};
|
||||
|
||||
class SponsoredMessages final {
|
||||
public:
|
||||
using RandomId = QByteArray;
|
||||
explicit SponsoredMessages(not_null<Session*> owner);
|
||||
SponsoredMessages(const SponsoredMessages &other) = delete;
|
||||
SponsoredMessages &operator=(const SponsoredMessages &other) = delete;
|
||||
~SponsoredMessages();
|
||||
|
||||
void request(not_null<History*> history);
|
||||
[[nodiscard]] bool append(not_null<History*> history);
|
||||
void clearItems(not_null<History*> history);
|
||||
|
||||
private:
|
||||
using OwnedItem = std::unique_ptr<HistoryItem, HistoryItem::Destroyer>;
|
||||
struct Entry {
|
||||
OwnedItem item;
|
||||
SponsoredMessage sponsored;
|
||||
};
|
||||
struct List {
|
||||
std::vector<Entry> entries;
|
||||
bool showedAll = false;
|
||||
};
|
||||
struct Request {
|
||||
mtpRequestId requestId = 0;
|
||||
crl::time lastReceived = 0;
|
||||
};
|
||||
|
||||
void parse(
|
||||
not_null<History*> history,
|
||||
const MTPmessages_sponsoredMessages &list);
|
||||
void append(
|
||||
not_null<History*> history,
|
||||
List &list,
|
||||
const MTPSponsoredMessage &message);
|
||||
void clearOldRequests();
|
||||
|
||||
void view(const std::vector<Entry>::iterator entryIt);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::Timer _clearTimer;
|
||||
base::flat_map<not_null<History*>, List> _data;
|
||||
base::flat_map<not_null<History*>, Request> _requests;
|
||||
base::flat_map<RandomId, Request> _viewRequests;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -80,8 +80,9 @@ Storage::Cache::Key GeoPointCacheKey(const GeoPointLocation &location) {
|
||||
| (uint32(location.height) & 0xFFFFU);
|
||||
return Storage::Cache::Key{
|
||||
Data::kGeoPointCacheTag | (uint64(zoomscale) << 32) | widthheight,
|
||||
(uint64(std::round(std::abs(location.lat + 360.) * 1000000)) << 32)
|
||||
| uint64(std::round(std::abs(location.lon + 360.) * 1000000))
|
||||
(uint64(base::SafeRound(
|
||||
std::abs(location.lat + 360.) * 1000000)) << 32)
|
||||
| uint64(base::SafeRound(std::abs(location.lon + 360.) * 1000000))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/value_ordering.h"
|
||||
#include "ui/text/text.h" // For QFIXED_MAX
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_msg_id.h"
|
||||
|
||||
class HistoryItem;
|
||||
using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
|
||||
@@ -100,80 +101,6 @@ class Folder;
|
||||
|
||||
using FolderId = int32;
|
||||
using FilterId = int32;
|
||||
using MsgId = int32;
|
||||
constexpr auto StartClientMsgId = MsgId(-0x7FFFFFFF);
|
||||
constexpr auto EndClientMsgId = MsgId(-0x40000000);
|
||||
constexpr auto ShowAtTheEndMsgId = MsgId(-0x40000000);
|
||||
constexpr auto SwitchAtTopMsgId = MsgId(-0x3FFFFFFF);
|
||||
constexpr auto ShowAtProfileMsgId = MsgId(-0x3FFFFFFE);
|
||||
constexpr auto ShowAndStartBotMsgId = MsgId(-0x3FFFFFFD);
|
||||
constexpr auto ShowAtGameShareMsgId = MsgId(-0x3FFFFFFC);
|
||||
constexpr auto ShowForChooseMessagesMsgId = MsgId(-0x3FFFFFFB);
|
||||
constexpr auto ServerMaxMsgId = MsgId(0x3FFFFFFF);
|
||||
constexpr auto ShowAtUnreadMsgId = MsgId(0);
|
||||
constexpr inline bool IsClientMsgId(MsgId id) {
|
||||
return (id >= StartClientMsgId && id < EndClientMsgId);
|
||||
}
|
||||
constexpr inline bool IsServerMsgId(MsgId id) {
|
||||
return (id > 0 && id < ServerMaxMsgId);
|
||||
}
|
||||
|
||||
struct MsgRange {
|
||||
MsgRange() = default;
|
||||
MsgRange(MsgId from, MsgId till) : from(from), till(till) {
|
||||
}
|
||||
|
||||
MsgId from = 0;
|
||||
MsgId till = 0;
|
||||
};
|
||||
inline bool operator==(const MsgRange &a, const MsgRange &b) {
|
||||
return (a.from == b.from) && (a.till == b.till);
|
||||
}
|
||||
inline bool operator!=(const MsgRange &a, const MsgRange &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
struct FullMsgId {
|
||||
constexpr FullMsgId() = default;
|
||||
constexpr FullMsgId(ChannelId channel, MsgId msg)
|
||||
: channel(channel), msg(msg) {
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return msg != 0;
|
||||
}
|
||||
|
||||
|
||||
inline constexpr bool operator<(const FullMsgId &other) const {
|
||||
if (channel < other.channel) {
|
||||
return true;
|
||||
} else if (channel > other.channel) {
|
||||
return false;
|
||||
}
|
||||
return msg < other.msg;
|
||||
}
|
||||
inline constexpr bool operator>(const FullMsgId &other) const {
|
||||
return other < *this;
|
||||
}
|
||||
inline constexpr bool operator<=(const FullMsgId &other) const {
|
||||
return !(other < *this);
|
||||
}
|
||||
inline constexpr bool operator>=(const FullMsgId &other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
inline constexpr bool operator==(const FullMsgId &other) const {
|
||||
return (channel == other.channel) && (msg == other.msg);
|
||||
}
|
||||
inline constexpr bool operator!=(const FullMsgId &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
ChannelId channel = NoChannel;
|
||||
MsgId msg = 0;
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(FullMsgId);
|
||||
|
||||
using MessageIdsList = std::vector<FullMsgId>;
|
||||
|
||||
@@ -182,6 +109,10 @@ MTPDmessage::Flags FlagsFromMessage(const MTPmessage &message);
|
||||
MsgId IdFromMessage(const MTPmessage &message);
|
||||
TimeId DateFromMessage(const MTPmessage &message);
|
||||
|
||||
[[nodiscard]] inline MTPint MTP_int(MsgId id) noexcept {
|
||||
return MTP_int(id.bare);
|
||||
}
|
||||
|
||||
class DocumentData;
|
||||
class PhotoData;
|
||||
struct WebPageData;
|
||||
@@ -344,6 +275,9 @@ enum class MessageFlag : uint32 {
|
||||
|
||||
// Contact sign-up message, notification should be skipped for Silent.
|
||||
IsContactSignUp = (1U << 28),
|
||||
|
||||
// In channels.
|
||||
IsSponsored = (1U << 29),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -296,3 +296,9 @@ dialogsScamFont: font(9px semibold);
|
||||
dialogsScamSkip: 4px;
|
||||
dialogsScamRadius: 2px;
|
||||
|
||||
dialogsMiniPreviewTop: 1px;
|
||||
dialogsMiniPreview: 16px;
|
||||
dialogsMiniPreviewRadius: 2px;
|
||||
dialogsMiniPreviewSkip: 2px;
|
||||
dialogsMiniPreviewRight: 3px;
|
||||
dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }};
|
||||
|
||||
@@ -43,8 +43,7 @@ uint64 PinnedDialogPos(int pinnedIndex) {
|
||||
} // namespace
|
||||
|
||||
Entry::Entry(not_null<Data::Session*> owner, Type type)
|
||||
: lastItemTextCache(st::dialogsTextWidthMin)
|
||||
, _owner(owner)
|
||||
: _owner(owner)
|
||||
, _isFolder(type == Type::Folder) {
|
||||
}
|
||||
|
||||
|
||||
@@ -183,13 +183,10 @@ public:
|
||||
paintUserpic(p, view, rtl() ? (w - x - size) : x, y, size);
|
||||
}
|
||||
|
||||
TimeId chatListTimeId() const {
|
||||
[[nodiscard]] TimeId chatListTimeId() const {
|
||||
return _timeId;
|
||||
}
|
||||
|
||||
mutable const HistoryItem *textCachedFor = nullptr; // cache
|
||||
mutable Ui::Text::String lastItemTextCache;
|
||||
|
||||
protected:
|
||||
void notifyUnreadStateChange(const UnreadState &wasState);
|
||||
auto unreadStateChangeNotifier(bool required) {
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_inner_widget.h"
|
||||
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "history/history.h"
|
||||
@@ -154,24 +154,10 @@ InnerWidget::InnerWidget(
|
||||
dialogRowReplaced(r.old, r.now);
|
||||
}, lifetime());
|
||||
|
||||
session().data().itemRepaintRequest(
|
||||
) | rpl::start_with_next([=](auto item) {
|
||||
const auto history = item->history();
|
||||
if (history->textCachedFor == item) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
if (const auto folder = history->folder()) {
|
||||
if (folder->textCachedFor == item) {
|
||||
folder->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
session().data().sendActionManager().animationUpdated(
|
||||
) | rpl::start_with_next([=](
|
||||
const Data::SendActionManager::AnimationUpdate &update) {
|
||||
using RowPainter = Layout::RowPainter;
|
||||
const auto updateRect = RowPainter::sendActionAnimationRect(
|
||||
const auto updateRect = Ui::RowPainter::sendActionAnimationRect(
|
||||
update.left,
|
||||
update.width,
|
||||
update.height,
|
||||
@@ -241,16 +227,9 @@ InnerWidget::InnerWidget(
|
||||
}, lifetime());
|
||||
|
||||
session().changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::DialogRowRepaint
|
||||
| Data::MessageUpdate::Flag::DialogRowRefresh
|
||||
Data::MessageUpdate::Flag::DialogRowRefresh
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
const auto item = update.item;
|
||||
if (update.flags & Data::MessageUpdate::Flag::DialogRowRefresh) {
|
||||
refreshDialogRow({ item->history(), item->fullId() });
|
||||
}
|
||||
if (update.flags & Data::MessageUpdate::Flag::DialogRowRepaint) {
|
||||
repaintDialogRow({ item->history(), item->fullId() });
|
||||
}
|
||||
refreshDialogRow({ update.item->history(), update.item->fullId() });
|
||||
}, lifetime());
|
||||
|
||||
session().changes().entryUpdates(
|
||||
@@ -445,7 +424,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
const auto isActive = (row->key() == active);
|
||||
const auto isSelected = (row->key() == selected);
|
||||
Layout::RowPainter::paint(
|
||||
Ui::RowPainter::paint(
|
||||
p,
|
||||
row,
|
||||
_filterId,
|
||||
@@ -563,7 +542,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
: (from == (isPressed()
|
||||
? _filteredPressed
|
||||
: _filteredSelected));
|
||||
Layout::RowPainter::paint(
|
||||
Ui::RowPainter::paint(
|
||||
p,
|
||||
_filterResults[from],
|
||||
_filterId,
|
||||
@@ -655,7 +634,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
: (from == (isPressed()
|
||||
? _searchedPressed
|
||||
: _searchedSelected));
|
||||
Layout::RowPainter::paint(
|
||||
Ui::RowPainter::paint(
|
||||
p,
|
||||
result.get(),
|
||||
fullWidth,
|
||||
@@ -697,7 +676,7 @@ void InnerWidget::paintCollapsedRow(
|
||||
|
||||
const auto text = row->folder->chatListName();
|
||||
const auto unread = row->folder->chatListUnreadCount();
|
||||
Layout::PaintCollapsedRow(
|
||||
Ui::PaintCollapsedRow(
|
||||
p,
|
||||
row->row,
|
||||
row->folder,
|
||||
@@ -740,7 +719,7 @@ void InnerWidget::paintPeerSearchResult(
|
||||
QRect rectForName(nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height);
|
||||
|
||||
// draw chat icon
|
||||
if (auto chatTypeIcon = Layout::ChatTypeIcon(peer, active, selected)) {
|
||||
if (auto chatTypeIcon = Ui::ChatTypeIcon(peer, active, selected)) {
|
||||
chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth);
|
||||
rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip);
|
||||
}
|
||||
@@ -871,7 +850,7 @@ void InnerWidget::paintSearchInPeer(
|
||||
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, width(), size);
|
||||
};
|
||||
const auto icon = Layout::ChatTypeIcon(peer, false, false);
|
||||
const auto icon = Ui::ChatTypeIcon(peer, false, false);
|
||||
paintSearchInFilter(p, paintUserpic, top, icon, text);
|
||||
}
|
||||
|
||||
@@ -1536,7 +1515,7 @@ void InnerWidget::refreshDialogRow(RowDescriptor row) {
|
||||
if (row.fullId) {
|
||||
for (const auto &result : _searchResults) {
|
||||
if (result->item()->fullId() == row.fullId) {
|
||||
result->invalidateCache();
|
||||
result->itemView().itemInvalidated(result->item());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_list.h"
|
||||
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "data/data_session.h"
|
||||
#include "mainwidget.h"
|
||||
|
||||
|
||||
@@ -285,8 +285,7 @@ void Row::validateListEntryCache() const {
|
||||
|
||||
FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
|
||||
: _searchInChat(searchInChat)
|
||||
, _item(item)
|
||||
, _cache(st::dialogsTextWidthMin) {
|
||||
, _item(item) {
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
|
||||
class History;
|
||||
class HistoryItem;
|
||||
@@ -22,10 +23,12 @@ namespace Ui {
|
||||
class RippleAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Dialogs {
|
||||
namespace Layout {
|
||||
namespace Dialogs::Ui {
|
||||
using namespace ::Ui;
|
||||
class RowPainter;
|
||||
} // namespace Layout
|
||||
} // namespace Dialogs::Ui
|
||||
|
||||
namespace Dialogs {
|
||||
|
||||
enum class SortMode;
|
||||
|
||||
@@ -91,25 +94,25 @@ public:
|
||||
}
|
||||
Row(Key key, int pos);
|
||||
|
||||
Key key() const {
|
||||
[[nodiscard]] Key key() const {
|
||||
return _id;
|
||||
}
|
||||
History *history() const {
|
||||
[[nodiscard]] History *history() const {
|
||||
return _id.history();
|
||||
}
|
||||
Data::Folder *folder() const {
|
||||
[[nodiscard]] Data::Folder *folder() const {
|
||||
return _id.folder();
|
||||
}
|
||||
not_null<Entry*> entry() const {
|
||||
[[nodiscard]] not_null<Entry*> entry() const {
|
||||
return _id.entry();
|
||||
}
|
||||
int pos() const {
|
||||
[[nodiscard]] int pos() const {
|
||||
return _pos;
|
||||
}
|
||||
uint64 sortKey(FilterId filterId) const;
|
||||
[[nodiscard]] uint64 sortKey(FilterId filterId) const;
|
||||
|
||||
void validateListEntryCache() const;
|
||||
const Ui::Text::String &listEntryCache() const {
|
||||
[[nodiscard]] const Ui::Text::String &listEntryCache() const {
|
||||
return _listEntryCache;
|
||||
}
|
||||
|
||||
@@ -130,25 +133,22 @@ class FakeRow : public BasicRow {
|
||||
public:
|
||||
FakeRow(Key searchInChat, not_null<HistoryItem*> item);
|
||||
|
||||
Key searchInChat() const {
|
||||
[[nodiscard]] Key searchInChat() const {
|
||||
return _searchInChat;
|
||||
}
|
||||
not_null<HistoryItem*> item() const {
|
||||
[[nodiscard]] not_null<HistoryItem*> item() const {
|
||||
return _item;
|
||||
}
|
||||
|
||||
void invalidateCache() {
|
||||
_cacheFor = nullptr;
|
||||
_cache = Ui::Text::String();
|
||||
[[nodiscard]] Ui::MessageView &itemView() const {
|
||||
return _itemView;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Layout::RowPainter;
|
||||
friend class Ui::RowPainter;
|
||||
|
||||
Key _searchInChat;
|
||||
not_null<HistoryItem*> _item;
|
||||
mutable const HistoryItem *_cacheFor = nullptr;
|
||||
mutable Ui::Text::String _cache;
|
||||
mutable Ui::MessageView _itemView;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -205,7 +205,11 @@ Widget::Widget(
|
||||
) | rpl::to_empty);
|
||||
|
||||
connect(_inner, SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int)));
|
||||
connect(_inner, SIGNAL(mustScrollTo(int,int)), _scroll, SLOT(scrollToY(int,int)));
|
||||
connect(_inner, &InnerWidget::mustScrollTo, [=](int top, int bottom) {
|
||||
if (_scroll) {
|
||||
_scroll->scrollToY(top, bottom);
|
||||
}
|
||||
});
|
||||
connect(_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int)));
|
||||
connect(_inner, SIGNAL(searchMessages()), this, SLOT(onNeedSearchMessages()));
|
||||
connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString)));
|
||||
@@ -234,8 +238,14 @@ Widget::Widget(
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
|
||||
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll()));
|
||||
_scroll->geometryChanged(
|
||||
) | rpl::start_with_next(crl::guard(_inner, [=] {
|
||||
_inner->onParentGeometryChanged();
|
||||
}), lifetime());
|
||||
_scroll->scrolls(
|
||||
) | rpl::start_with_next([=] {
|
||||
onListScroll();
|
||||
}, lifetime());
|
||||
|
||||
session().data().chatsListChanges(
|
||||
) | rpl::filter([=](Data::Folder *folder) {
|
||||
|
||||
@@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/sender.h"
|
||||
#include "api/api_single_message_search.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
namespace MTP {
|
||||
class Error;
|
||||
} // namespace MTP
|
||||
|
||||
@@ -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 "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
|
||||
#include "data/data_abstract_structure.h"
|
||||
#include "data/data_drafts.h"
|
||||
@@ -31,8 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_peer_values.h"
|
||||
|
||||
namespace Dialogs {
|
||||
namespace Layout {
|
||||
namespace Dialogs::Ui {
|
||||
namespace {
|
||||
|
||||
// Show all dates that are in the last 20 hours in time format.
|
||||
@@ -260,14 +259,14 @@ void paintRow(
|
||||
const auto history = chat.history();
|
||||
|
||||
if (flags & Flag::SavedMessages) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(
|
||||
EmptyUserpic::PaintSavedMessages(
|
||||
p,
|
||||
st::dialogsPadding.x(),
|
||||
st::dialogsPadding.y(),
|
||||
fullWidth,
|
||||
st::dialogsPhotoSize);
|
||||
} else if (flags & Flag::RepliesMessages) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(
|
||||
EmptyUserpic::PaintRepliesMessages(
|
||||
p,
|
||||
st::dialogsPadding.x(),
|
||||
st::dialogsPadding.y(),
|
||||
@@ -344,7 +343,7 @@ void paintRow(
|
||||
history->cloudDraftTextCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
history->topPromotionMessage(),
|
||||
Ui::DialogTextOptions());
|
||||
DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
history->cloudDraftTextCache.drawElided(p, nameleft, texttop, availableWidth, 1);
|
||||
@@ -371,7 +370,7 @@ void paintRow(
|
||||
auto draftText = supportMode
|
||||
? textcmdLink(1, Support::ChatOccupiedString(history))
|
||||
: tr::lng_dialogs_text_with_from(tr::now, lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text));
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions());
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
if (supportMode) {
|
||||
@@ -462,7 +461,7 @@ void paintRow(
|
||||
p.drawTextLeft(rectForName.left(), rectForName.top(), fullWidth, text);
|
||||
} else if (from) {
|
||||
if (!(flags & Flag::SearchResult)) {
|
||||
const auto badgeStyle = Ui::PeerBadgeStyle{
|
||||
const auto badgeStyle = PeerBadgeStyle{
|
||||
(active
|
||||
? &st::dialogsVerifiedIconActive
|
||||
: selected
|
||||
@@ -473,7 +472,7 @@ void paintRow(
|
||||
: selected
|
||||
? &st::dialogsScamFgOver
|
||||
: &st::dialogsScamFg) };
|
||||
const auto badgeWidth = Ui::DrawPeerBadgeGetWidth(
|
||||
const auto badgeWidth = DrawPeerBadgeGetWidth(
|
||||
from,
|
||||
p,
|
||||
rectForName,
|
||||
@@ -601,9 +600,9 @@ void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st)
|
||||
if (badgeData->left[index].isNull()) {
|
||||
int imgsize = size * cIntRetinaFactor(), imgsizehalf = sizehalf * cIntRetinaFactor();
|
||||
createCircleMask(badgeData, size);
|
||||
badgeData->left[index] = Ui::PixmapFromImage(
|
||||
badgeData->left[index] = PixmapFromImage(
|
||||
colorizeCircleHalf(badgeData, imgsize, imgsizehalf, 0, bg));
|
||||
badgeData->right[index] = Ui::PixmapFromImage(colorizeCircleHalf(
|
||||
badgeData->right[index] = PixmapFromImage(colorizeCircleHalf(
|
||||
badgeData,
|
||||
imgsize,
|
||||
imgsizehalf,
|
||||
@@ -775,15 +774,14 @@ void RowPainter::paint(
|
||||
: false;
|
||||
if (const auto folder = row->folder()) {
|
||||
PaintListEntryText(p, itemRect, active, selected, row);
|
||||
} else if (!actionWasPainted) {
|
||||
item->drawInDialog(
|
||||
} else if (history && !actionWasPainted) {
|
||||
history->lastItemDialogsView.paint(
|
||||
p,
|
||||
item,
|
||||
itemRect,
|
||||
active,
|
||||
selected,
|
||||
HistoryItem::DrawInDialog::Normal,
|
||||
entry->textCachedFor,
|
||||
entry->lastItemTextCache);
|
||||
{});
|
||||
}
|
||||
};
|
||||
const auto paintCounterCallback = [&] {
|
||||
@@ -846,15 +844,15 @@ void RowPainter::paint(
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
const auto drawInDialogWay = [&] {
|
||||
const auto previewOptions = [&]() -> HistoryView::ToPreviewOptions {
|
||||
if (const auto searchChat = row->searchInChat()) {
|
||||
if (const auto peer = searchChat.peer()) {
|
||||
if (!peer->isChannel() || peer->isMegagroup()) {
|
||||
return HistoryItem::DrawInDialog::WithoutSender;
|
||||
return { .hideSender = true };
|
||||
}
|
||||
}
|
||||
}
|
||||
return HistoryItem::DrawInDialog::Normal;
|
||||
return {};
|
||||
}();
|
||||
|
||||
const auto unreadCount = displayUnreadInfo
|
||||
@@ -896,14 +894,13 @@ void RowPainter::paint(
|
||||
texttop,
|
||||
availableWidth,
|
||||
st::dialogsTextFont->height);
|
||||
item->drawInDialog(
|
||||
row->itemView().paint(
|
||||
p,
|
||||
item,
|
||||
itemRect,
|
||||
active,
|
||||
selected,
|
||||
drawInDialogWay,
|
||||
row->_cacheFor,
|
||||
row->_cache);
|
||||
previewOptions);
|
||||
};
|
||||
const auto paintCounterCallback = [&] {
|
||||
PaintNarrowCounter(
|
||||
@@ -1015,5 +1012,4 @@ void PaintCollapsedRow(
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Layout
|
||||
} // namespace Dialogs
|
||||
} // namespace Dialogs::Ui
|
||||
@@ -7,13 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Dialogs {
|
||||
namespace Ui {
|
||||
} // namespace Ui
|
||||
|
||||
namespace Dialogs {
|
||||
class Row;
|
||||
class FakeRow;
|
||||
class BasicRow;
|
||||
} // namespace Dialogs
|
||||
|
||||
namespace Layout {
|
||||
namespace Dialogs::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
|
||||
const style::icon *ChatTypeIcon(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -89,5 +94,4 @@ void paintUnreadCount(
|
||||
|
||||
void clearUnreadBadgesCache();
|
||||
|
||||
} // namespace Layout
|
||||
} // namespace Dialogs
|
||||
} // namespace Dialogs::Ui
|
||||
208
Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
Normal file
208
Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
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 "dialogs/ui/dialogs_message_view.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <ushort kTag>
|
||||
struct TextWithTagOffset {
|
||||
TextWithTagOffset(QString text) : text(text) {
|
||||
}
|
||||
static TextWithTagOffset FromString(const QString &text) {
|
||||
return { text };
|
||||
}
|
||||
|
||||
QString text;
|
||||
int offset = -1;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Lang {
|
||||
|
||||
template <ushort kTag>
|
||||
struct ReplaceTag<TextWithTagOffset<kTag>> {
|
||||
static TextWithTagOffset<kTag> Call(
|
||||
TextWithTagOffset<kTag> &&original,
|
||||
ushort tag,
|
||||
const TextWithTagOffset<kTag> &replacement);
|
||||
};
|
||||
|
||||
template <ushort kTag>
|
||||
TextWithTagOffset<kTag> ReplaceTag<TextWithTagOffset<kTag>>::Call(
|
||||
TextWithTagOffset<kTag> &&original,
|
||||
ushort tag,
|
||||
const TextWithTagOffset<kTag> &replacement) {
|
||||
const auto replacementPosition = FindTagReplacementPosition(
|
||||
original.text,
|
||||
tag);
|
||||
if (replacementPosition < 0) {
|
||||
return std::move(original);
|
||||
}
|
||||
original.text = ReplaceTag<QString>::Replace(
|
||||
std::move(original.text),
|
||||
replacement.text,
|
||||
replacementPosition);
|
||||
if (tag == kTag) {
|
||||
original.offset = replacementPosition;
|
||||
} else if (original.offset > replacementPosition) {
|
||||
constexpr auto kReplaceCommandLength = 4;
|
||||
original.offset += replacement.text.size() - kReplaceCommandLength;
|
||||
}
|
||||
return std::move(original);
|
||||
}
|
||||
|
||||
} // namespace Lang
|
||||
|
||||
namespace Dialogs::Ui {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
struct MessageView::LoadingContext {
|
||||
std::any context;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
MessageView::MessageView()
|
||||
: _senderCache(st::dialogsTextWidthMin)
|
||||
, _textCache(st::dialogsTextWidthMin) {
|
||||
}
|
||||
|
||||
MessageView::~MessageView() = default;
|
||||
|
||||
void MessageView::itemInvalidated(not_null<const HistoryItem*> item) {
|
||||
if (_textCachedFor == item.get()) {
|
||||
_textCachedFor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageView::dependsOn(not_null<const HistoryItem*> item) const {
|
||||
return (_textCachedFor == item.get());
|
||||
}
|
||||
|
||||
void MessageView::paint(
|
||||
Painter &p,
|
||||
not_null<const HistoryItem*> item,
|
||||
const QRect &geometry,
|
||||
bool active,
|
||||
bool selected,
|
||||
ToPreviewOptions options) const {
|
||||
if (geometry.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (_textCachedFor != item.get()) {
|
||||
options.existing = &_imagesCache;
|
||||
auto preview = item->toPreview(options);
|
||||
if (!preview.images.empty() && preview.imagesInTextPosition > 0) {
|
||||
_senderCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
preview.text.mid(0, preview.imagesInTextPosition).trimmed(),
|
||||
DialogTextOptions());
|
||||
preview.text = preview.text.mid(preview.imagesInTextPosition);
|
||||
} else {
|
||||
_senderCache = { st::dialogsTextWidthMin };
|
||||
}
|
||||
_textCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
preview.text.trimmed(),
|
||||
DialogTextOptions());
|
||||
_textCachedFor = item;
|
||||
_imagesCache = std::move(preview.images);
|
||||
if (preview.loadingContext.has_value()) {
|
||||
if (!_loadingContext) {
|
||||
_loadingContext = std::make_unique<LoadingContext>();
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
_textCachedFor = nullptr;
|
||||
}, _loadingContext->lifetime);
|
||||
}
|
||||
_loadingContext->context = std::move(preview.loadingContext);
|
||||
} else {
|
||||
_loadingContext = nullptr;
|
||||
}
|
||||
}
|
||||
p.setTextPalette(active
|
||||
? st::dialogsTextPaletteActive
|
||||
: selected
|
||||
? st::dialogsTextPaletteOver
|
||||
: st::dialogsTextPalette);
|
||||
p.setFont(st::dialogsTextFont);
|
||||
p.setPen(active
|
||||
? st::dialogsTextFgActive
|
||||
: selected
|
||||
? st::dialogsTextFgOver
|
||||
: st::dialogsTextFg);
|
||||
const auto guard = gsl::finally([&] {
|
||||
p.restoreTextPalette();
|
||||
});
|
||||
|
||||
auto rect = geometry;
|
||||
if (!_senderCache.isEmpty()) {
|
||||
_senderCache.drawElided(
|
||||
p,
|
||||
rect.left(),
|
||||
rect.top(),
|
||||
rect.width(),
|
||||
rect.height() / st::dialogsTextFont->height);
|
||||
const auto skip = st::dialogsMiniPreviewSkip
|
||||
+ st::dialogsMiniPreviewRight;
|
||||
rect.setLeft(rect.x() + _senderCache.maxWidth() + skip);
|
||||
}
|
||||
for (const auto &image : _imagesCache) {
|
||||
if (rect.width() < st::dialogsMiniPreview) {
|
||||
break;
|
||||
}
|
||||
p.drawImage(
|
||||
rect.x(),
|
||||
rect.y() + st::dialogsMiniPreviewTop,
|
||||
image.data);
|
||||
rect.setLeft(rect.x()
|
||||
+ st::dialogsMiniPreview
|
||||
+ st::dialogsMiniPreviewSkip);
|
||||
}
|
||||
if (!_imagesCache.empty()) {
|
||||
rect.setLeft(rect.x() + st::dialogsMiniPreviewRight);
|
||||
}
|
||||
if (rect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
_textCache.drawElided(
|
||||
p,
|
||||
rect.left(),
|
||||
rect.top(),
|
||||
rect.width(),
|
||||
rect.height() / st::dialogsTextFont->height);
|
||||
}
|
||||
|
||||
HistoryView::ItemPreview PreviewWithSender(
|
||||
HistoryView::ItemPreview &&preview,
|
||||
const QString &sender) {
|
||||
auto textWithOffset = tr::lng_dialogs_text_with_from(
|
||||
tr::now,
|
||||
lt_from_part,
|
||||
sender,
|
||||
lt_message,
|
||||
std::move(preview.text),
|
||||
TextWithTagOffset<lt_from_part>::FromString);
|
||||
preview.text = std::move(textWithOffset.text);
|
||||
preview.imagesInTextPosition = (textWithOffset.offset < 0)
|
||||
? 0
|
||||
: textWithOffset.offset + sender.size();
|
||||
return std::move(preview);
|
||||
}
|
||||
|
||||
} // namespace Dialogs::Ui
|
||||
64
Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
Normal file
64
Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 <any>
|
||||
|
||||
class Image;
|
||||
class HistoryItem;
|
||||
enum class ImageRoundRadius;
|
||||
|
||||
namespace Ui {
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
struct ToPreviewOptions;
|
||||
struct ItemPreviewImage;
|
||||
struct ItemPreview;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Dialogs::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
|
||||
class MessageView final {
|
||||
public:
|
||||
MessageView();
|
||||
~MessageView();
|
||||
|
||||
using ToPreviewOptions = HistoryView::ToPreviewOptions;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
void itemInvalidated(not_null<const HistoryItem*> item);
|
||||
[[nodiscard]] bool dependsOn(not_null<const HistoryItem*> item) const;
|
||||
|
||||
void paint(
|
||||
Painter &p,
|
||||
not_null<const HistoryItem*> item,
|
||||
const QRect &geometry,
|
||||
bool active,
|
||||
bool selected,
|
||||
ToPreviewOptions options) const;
|
||||
|
||||
private:
|
||||
struct LoadingContext;
|
||||
|
||||
mutable const HistoryItem *_textCachedFor = nullptr;
|
||||
mutable Ui::Text::String _senderCache;
|
||||
mutable Ui::Text::String _textCache;
|
||||
mutable std::vector<ItemPreviewImage> _imagesCache;
|
||||
mutable std::unique_ptr<LoadingContext> _loadingContext;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] HistoryView::ItemPreview PreviewWithSender(
|
||||
HistoryView::ItemPreview &&preview,
|
||||
const QString &sender);
|
||||
|
||||
} // namespace Dialogs::Ui
|
||||
@@ -46,7 +46,7 @@ void Scene::cancelDrawing() {
|
||||
_canvas->cancelDrawing();
|
||||
}
|
||||
|
||||
void Scene::addItem(std::shared_ptr<NumberedItem> item) {
|
||||
void Scene::addItem(ItemPtr item) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ std::vector<ItemPtr> Scene::items(
|
||||
return copyItems;
|
||||
}
|
||||
|
||||
std::vector<MTPInputDocument> Scene::attachedStickers() const {
|
||||
std::vector<not_null<DocumentData*>> Scene::attachedStickers() const {
|
||||
const auto allItems = items();
|
||||
|
||||
return ranges::views::all(
|
||||
|
||||
@@ -39,7 +39,8 @@ public:
|
||||
[[nodiscard]] rpl::producer<> addsItem() const;
|
||||
[[nodiscard]] rpl::producer<> removesItem() const;
|
||||
|
||||
[[nodiscard]] std::vector<MTPInputDocument> attachedStickers() const;
|
||||
[[nodiscard]] auto attachedStickers() const
|
||||
-> std::vector<not_null<DocumentData*>>;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<float64> lastZ() const;
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ void ItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
const auto angle = Normalized((isLeft ? 180 : 0)
|
||||
+ (std::atan2(diff.y(), diff.x()) * 180 / M_PI));
|
||||
setRotation(shift
|
||||
? (std::round(angle / kSnapAngle) * kSnapAngle) // Snap rotation.
|
||||
? (base::SafeRound(angle / kSnapAngle) * kSnapAngle)
|
||||
: angle);
|
||||
} else {
|
||||
QGraphicsItem::mouseMoveEvent(event);
|
||||
|
||||
@@ -95,8 +95,8 @@ void ItemSticker::paint(
|
||||
ItemBase::paint(p, option, w);
|
||||
}
|
||||
|
||||
MTPInputDocument ItemSticker::sticker() const {
|
||||
return _document->mtpInput();
|
||||
not_null<DocumentData*> ItemSticker::sticker() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
int ItemSticker::type() const {
|
||||
|
||||
@@ -30,11 +30,13 @@ public:
|
||||
QPainter *p,
|
||||
const QStyleOptionGraphicsItem *option,
|
||||
QWidget *widget) override;
|
||||
MTPInputDocument sticker() const;
|
||||
[[nodiscard]] not_null<DocumentData*> sticker() const;
|
||||
int type() const override;
|
||||
|
||||
protected:
|
||||
void performFlip() override;
|
||||
std::shared_ptr<ItemBase> duplicate(ItemBase::Data data) const override;
|
||||
|
||||
private:
|
||||
const not_null<DocumentData*> _document;
|
||||
const std::shared_ptr<::Data::DocumentMedia> _mediaView;
|
||||
|
||||
@@ -56,14 +56,11 @@ TimeId ExtractSentDate(const MTPMessage &message) {
|
||||
});
|
||||
}
|
||||
|
||||
MTPMessage PrepareLogMessage(
|
||||
const MTPMessage &message,
|
||||
MsgId newId,
|
||||
TimeId newDate) {
|
||||
MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) {
|
||||
return message.match([&](const MTPDmessageEmpty &data) {
|
||||
return MTP_messageEmpty(
|
||||
data.vflags(),
|
||||
MTP_int(newId),
|
||||
data.vid(),
|
||||
data.vpeer_id() ? *data.vpeer_id() : MTPPeer());
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
const auto removeFlags = MTPDmessageService::Flag::f_out
|
||||
@@ -72,7 +69,7 @@ MTPMessage PrepareLogMessage(
|
||||
| MTPDmessageService::Flag::f_ttl_period;
|
||||
return MTP_messageService(
|
||||
MTP_flags(data.vflags().v & ~removeFlags),
|
||||
MTP_int(newId),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
MTPMessageReplyHeader(),
|
||||
@@ -93,7 +90,7 @@ MTPMessage PrepareLogMessage(
|
||||
| MTPDmessage::Flag::f_ttl_period;
|
||||
return MTP_message(
|
||||
MTP_flags(data.vflags().v & ~removeFlags),
|
||||
MTP_int(newId),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
@@ -559,7 +556,7 @@ void GenerateItems(
|
||||
QString(),
|
||||
std::move(text),
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
bodyGroupedId);
|
||||
};
|
||||
|
||||
@@ -669,10 +666,8 @@ void GenerateItems(
|
||||
auto detachExistingItem = false;
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vmessage(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
@@ -698,10 +693,8 @@ void GenerateItems(
|
||||
auto oldValue = ExtractEditedText(session, action.vprev_message());
|
||||
auto detachExistingItem = false;
|
||||
auto body = history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vnew_message(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vnew_message(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem);
|
||||
if (oldValue.text.isEmpty()) {
|
||||
@@ -724,10 +717,8 @@ void GenerateItems(
|
||||
auto detachExistingItem = false;
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vmessage(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
@@ -821,10 +812,8 @@ void GenerateItems(
|
||||
auto detachExistingItem = false;
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vmessage(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
|
||||
@@ -322,8 +322,10 @@ Widget::Widget(
|
||||
|
||||
_scroll->move(0, _fixedBar->height());
|
||||
_scroll->show();
|
||||
|
||||
connect(_scroll, &Ui::ScrollArea::scrolled, this, [this] { onScroll(); });
|
||||
_scroll->scrolls(
|
||||
) | rpl::start_with_next([=] {
|
||||
onScroll();
|
||||
}, lifetime());
|
||||
|
||||
_whatIsThis->setClickedCallback([=] {
|
||||
controller->show(Box<InformBox>(channel->isMegagroup()
|
||||
|
||||
@@ -360,21 +360,17 @@ void History::setForwardDraft(Data::ForwardDraft &&draft) {
|
||||
}
|
||||
|
||||
HistoryItem *History::createItem(
|
||||
MsgId id,
|
||||
const MTPMessage &message,
|
||||
MessageFlags localFlags,
|
||||
bool detachExistingItem) {
|
||||
const auto messageId = IdFromMessage(message);
|
||||
if (!messageId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (const auto result = owner().message(channelId(), messageId)) {
|
||||
if (const auto result = owner().message(channelId(), id)) {
|
||||
if (detachExistingItem) {
|
||||
result->removeMainView();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return HistoryItem::Create(this, message, localFlags);
|
||||
return HistoryItem::Create(this, id, message, localFlags);
|
||||
}
|
||||
|
||||
std::vector<not_null<HistoryItem*>> History::createItems(
|
||||
@@ -382,9 +378,14 @@ std::vector<not_null<HistoryItem*>> History::createItems(
|
||||
auto result = std::vector<not_null<HistoryItem*>>();
|
||||
result.reserve(data.size());
|
||||
const auto localFlags = MessageFlags();
|
||||
const auto detachExistingItem = true;
|
||||
for (auto i = data.cend(), e = data.cbegin(); i != e;) {
|
||||
const auto detachExistingItem = true;
|
||||
const auto item = createItem(*--i, localFlags, detachExistingItem);
|
||||
const auto &data = *--i;
|
||||
const auto item = createItem(
|
||||
IdFromMessage(data),
|
||||
data,
|
||||
localFlags,
|
||||
detachExistingItem);
|
||||
if (item) {
|
||||
result.emplace_back(item);
|
||||
}
|
||||
@@ -393,11 +394,12 @@ std::vector<not_null<HistoryItem*>> History::createItems(
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewMessage(
|
||||
MsgId id,
|
||||
const MTPMessage &msg,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
const auto detachExistingItem = (type == NewMessageType::Unread);
|
||||
const auto item = createItem(msg, localFlags, detachExistingItem);
|
||||
const auto item = createItem(id, msg, localFlags, detachExistingItem);
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -548,7 +550,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
const QString &postAuthor,
|
||||
const TextWithEntities &text,
|
||||
const MTPMessageMedia &media,
|
||||
const MTPReplyMarkup &markup,
|
||||
HistoryMessageMarkupData &&markup,
|
||||
uint64 groupedId) {
|
||||
return addNewItem(
|
||||
makeMessage(
|
||||
@@ -561,7 +563,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
postAuthor,
|
||||
text,
|
||||
media,
|
||||
markup,
|
||||
std::move(markup),
|
||||
groupedId),
|
||||
true);
|
||||
}
|
||||
@@ -594,7 +596,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
const QString &postAuthor,
|
||||
not_null<DocumentData*> document,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup) {
|
||||
HistoryMessageMarkupData &&markup) {
|
||||
return addNewItem(
|
||||
makeMessage(
|
||||
id,
|
||||
@@ -606,7 +608,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
postAuthor,
|
||||
document,
|
||||
caption,
|
||||
markup),
|
||||
std::move(markup)),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -620,7 +622,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
const QString &postAuthor,
|
||||
not_null<PhotoData*> photo,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup) {
|
||||
HistoryMessageMarkupData &&markup) {
|
||||
return addNewItem(
|
||||
makeMessage(
|
||||
id,
|
||||
@@ -632,7 +634,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
postAuthor,
|
||||
photo,
|
||||
caption,
|
||||
markup),
|
||||
std::move(markup)),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -645,7 +647,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
PeerId from,
|
||||
const QString &postAuthor,
|
||||
not_null<GameData*> game,
|
||||
const MTPReplyMarkup &markup) {
|
||||
HistoryMessageMarkupData &&markup) {
|
||||
return addNewItem(
|
||||
makeMessage(
|
||||
id,
|
||||
@@ -656,7 +658,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
from,
|
||||
postAuthor,
|
||||
game,
|
||||
markup),
|
||||
std::move(markup)),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -744,11 +746,14 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
|
||||
const auto localFlags = MessageFlags();
|
||||
const auto type = NewMessageType::Existing;
|
||||
for (const auto &message : *messages) {
|
||||
if (const auto item = addNewMessage(message, localFlags, type)) {
|
||||
if (item->isUnreadMention()) {
|
||||
_unreadMentions.insert(item->id);
|
||||
added = true;
|
||||
}
|
||||
const auto item = addNewMessage(
|
||||
IdFromMessage(message),
|
||||
message,
|
||||
localFlags,
|
||||
type);
|
||||
if (item && item->isUnreadMention()) {
|
||||
_unreadMentions.insert(item->id);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1173,7 +1178,7 @@ HistoryItem *History::latestSendingMessage() const {
|
||||
});
|
||||
const auto i = ranges::max_element(sending, ranges::less(), [](
|
||||
not_null<HistoryItem*> item) {
|
||||
return uint64(item->date()) << 32 | uint32(item->id);
|
||||
return std::pair(item->date(), item->id.bare);
|
||||
});
|
||||
return (i == sending.end()) ? nullptr : i->get();
|
||||
}
|
||||
@@ -1248,27 +1253,31 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
|
||||
if (const auto added = createItems(slice); !added.empty()) {
|
||||
startBuildingFrontBlock(added.size());
|
||||
for (const auto &item : added) {
|
||||
addItemToBlock(item);
|
||||
}
|
||||
finishBuildingFrontBlock();
|
||||
|
||||
if (loadedAtBottom()) {
|
||||
// Add photos to overview and authors to lastAuthors.
|
||||
addItemsToLists(added);
|
||||
}
|
||||
addToSharedMedia(added);
|
||||
addCreatedOlderSlice(added);
|
||||
} else {
|
||||
// If no items were added it means we've loaded everything old.
|
||||
_loadedAtTop = true;
|
||||
addEdgesToSharedMedia();
|
||||
}
|
||||
|
||||
checkLocalMessages();
|
||||
checkLastMessage();
|
||||
}
|
||||
|
||||
void History::addCreatedOlderSlice(
|
||||
const std::vector<not_null<HistoryItem*>> &items) {
|
||||
startBuildingFrontBlock(items.size());
|
||||
for (const auto &item : items) {
|
||||
addItemToBlock(item);
|
||||
}
|
||||
finishBuildingFrontBlock();
|
||||
|
||||
if (loadedAtBottom()) {
|
||||
// Add photos to overview and authors to lastAuthors.
|
||||
addItemsToLists(items);
|
||||
}
|
||||
addToSharedMedia(items);
|
||||
}
|
||||
|
||||
void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
bool wasLoadedAtBottom = loadedAtBottom();
|
||||
|
||||
@@ -1466,7 +1475,7 @@ bool History::readInboxTillNeedsRequest(MsgId tillId) {
|
||||
}
|
||||
DEBUG_LOG(("Reading: readInboxTillNeedsRequest is_server %1, before %2."
|
||||
).arg(Logs::b(IsServerMsgId(tillId))
|
||||
).arg(_inboxReadBefore.value_or(-666)));
|
||||
).arg(_inboxReadBefore.value_or(-666).bare));
|
||||
return IsServerMsgId(tillId) && (_inboxReadBefore.value_or(1) <= tillId);
|
||||
}
|
||||
|
||||
@@ -1492,9 +1501,9 @@ std::optional<int> History::countStillUnreadLocal(MsgId readTillId) const {
|
||||
if (_inboxReadBefore) {
|
||||
const auto before = *_inboxReadBefore;
|
||||
DEBUG_LOG(("Reading: check before %1 with min %2 and max %3."
|
||||
).arg(before
|
||||
).arg(minMsgId()
|
||||
).arg(maxMsgId()));
|
||||
).arg(before.bare
|
||||
).arg(minMsgId().bare
|
||||
).arg(maxMsgId().bare));
|
||||
if (minMsgId() <= before && maxMsgId() >= readTillId) {
|
||||
auto result = 0;
|
||||
for (const auto &block : blocks) {
|
||||
@@ -1520,7 +1529,7 @@ std::optional<int> History::countStillUnreadLocal(MsgId readTillId) const {
|
||||
}
|
||||
const auto minimalServerId = minMsgId();
|
||||
DEBUG_LOG(("Reading: check at end loaded from %1 loaded %2 - %3").arg(
|
||||
QString::number(minimalServerId),
|
||||
QString::number(minimalServerId.bare),
|
||||
Logs::b(loadedAtBottom()),
|
||||
Logs::b(loadedAtTop())));
|
||||
if (!loadedAtBottom()
|
||||
@@ -2253,6 +2262,9 @@ void History::setChatListMessage(HistoryItem *item) {
|
||||
}
|
||||
const auto was = _chatListMessage.value_or(nullptr);
|
||||
if (item) {
|
||||
if (item->isSponsored()) {
|
||||
return;
|
||||
}
|
||||
if (_chatListMessage
|
||||
&& *_chatListMessage
|
||||
&& !IsServerMsgId((*_chatListMessage)->id)
|
||||
@@ -2261,6 +2273,14 @@ void History::setChatListMessage(HistoryItem *item) {
|
||||
}
|
||||
_chatListMessage = item;
|
||||
setChatListTimeId(item->date());
|
||||
|
||||
// If we have a single message from a group, request the full album.
|
||||
if (hasOrphanMediaGroupPart()
|
||||
&& !item->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = true }).images.empty()) {
|
||||
owner().histories().requestGroupAround(item);
|
||||
}
|
||||
} else if (!_chatListMessage || *_chatListMessage) {
|
||||
_chatListMessage = nullptr;
|
||||
updateChatListEntry();
|
||||
@@ -2417,6 +2437,44 @@ void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) {
|
||||
setChatListMessage(item);
|
||||
}
|
||||
|
||||
void History::applyChatListGroup(
|
||||
ChannelId channelId,
|
||||
const MTPmessages_Messages &data) {
|
||||
if (!isEmpty()
|
||||
|| !_chatListMessage
|
||||
|| !*_chatListMessage
|
||||
|| (*_chatListMessage)->history()->channelId() != channelId
|
||||
|| (*_chatListMessage)->history() != this
|
||||
|| !_lastMessage
|
||||
|| !*_lastMessage) {
|
||||
return;
|
||||
}
|
||||
// Apply loaded album as a last slice.
|
||||
const auto processMessages = [&](const MTPVector<MTPMessage> &messages) {
|
||||
auto items = std::vector<not_null<HistoryItem*>>();
|
||||
items.reserve(messages.v.size());
|
||||
for (const auto &message : messages.v) {
|
||||
const auto id = IdFromMessage(message);
|
||||
if (const auto message = owner().message(channelId, id)) {
|
||||
items.push_back(message);
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(items, not_null(*_lastMessage))
|
||||
|| !ranges::contains(items, not_null(*_chatListMessage))) {
|
||||
return;
|
||||
}
|
||||
_loadedAtBottom = true;
|
||||
ranges::sort(items, ranges::less{}, &HistoryItem::id);
|
||||
addCreatedOlderSlice(items);
|
||||
checkLocalMessages();
|
||||
checkLastMessage();
|
||||
};
|
||||
data.match([&](const MTPDmessages_messagesNotModified &) {
|
||||
}, [&](const auto &data) {
|
||||
processMessages(data.vmessages());
|
||||
});
|
||||
}
|
||||
|
||||
HistoryItem *History::lastMessage() const {
|
||||
return _lastMessage.value_or(nullptr);
|
||||
}
|
||||
@@ -2617,6 +2675,10 @@ QString History::topPromotionMessage() const {
|
||||
return _topPromotedMessage;
|
||||
}
|
||||
|
||||
bool History::canHaveSponsoredMessages() const {
|
||||
return isChannel();
|
||||
}
|
||||
|
||||
bool History::clearUnreadOnClientSide() const {
|
||||
if (!session().supportMode()) {
|
||||
return false;
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "history/view/history_view_send_action.h"
|
||||
#include "base/observer.h"
|
||||
#include "base/timer.h"
|
||||
@@ -23,6 +24,7 @@ class HistoryBlock;
|
||||
class HistoryItem;
|
||||
class HistoryMessage;
|
||||
class HistoryService;
|
||||
struct HistoryMessageMarkupData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
@@ -134,12 +136,10 @@ public:
|
||||
void unpinAllMessages();
|
||||
|
||||
HistoryItem *addNewMessage(
|
||||
MsgId id,
|
||||
const MTPMessage &msg,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type);
|
||||
HistoryItem *addToHistory(
|
||||
const MTPMessage &msg,
|
||||
MessageFlags localFlags);
|
||||
not_null<HistoryItem*> addNewLocalMessage(
|
||||
MsgId id,
|
||||
MessageFlags flags,
|
||||
@@ -150,7 +150,7 @@ public:
|
||||
const QString &postAuthor,
|
||||
const TextWithEntities &text,
|
||||
const MTPMessageMedia &media,
|
||||
const MTPReplyMarkup &markup,
|
||||
HistoryMessageMarkupData &&markup,
|
||||
uint64 groupedId = 0);
|
||||
not_null<HistoryItem*> addNewLocalMessage(
|
||||
MsgId id,
|
||||
@@ -169,7 +169,7 @@ public:
|
||||
const QString &postAuthor,
|
||||
not_null<DocumentData*> document,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup);
|
||||
HistoryMessageMarkupData &&markup);
|
||||
not_null<HistoryItem*> addNewLocalMessage(
|
||||
MsgId id,
|
||||
MessageFlags flags,
|
||||
@@ -180,7 +180,7 @@ public:
|
||||
const QString &postAuthor,
|
||||
not_null<PhotoData*> photo,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup);
|
||||
HistoryMessageMarkupData &&markup);
|
||||
not_null<HistoryItem*> addNewLocalMessage(
|
||||
MsgId id,
|
||||
MessageFlags flags,
|
||||
@@ -190,10 +190,11 @@ public:
|
||||
PeerId from,
|
||||
const QString &postAuthor,
|
||||
not_null<GameData*> game,
|
||||
const MTPReplyMarkup &markup);
|
||||
HistoryMessageMarkupData &&markup);
|
||||
|
||||
// Used only internally and for channel admin log.
|
||||
HistoryItem *createItem(
|
||||
MsgId id,
|
||||
const MTPMessage &message,
|
||||
MessageFlags localFlags,
|
||||
bool detachExistingItem);
|
||||
@@ -281,6 +282,8 @@ public:
|
||||
[[nodiscard]] bool topPromotionAboutShown() const;
|
||||
void markTopPromotionAboutShown();
|
||||
|
||||
[[nodiscard]] bool canHaveSponsoredMessages() const;
|
||||
|
||||
MsgId minMsgId() const;
|
||||
MsgId maxMsgId() const;
|
||||
MsgId msgIdForRead() const;
|
||||
@@ -408,6 +411,10 @@ public:
|
||||
void setFakeChatListMessageFrom(const MTPmessages_Messages &data);
|
||||
void checkChatListMessageRemoved(not_null<HistoryItem*> item);
|
||||
|
||||
void applyChatListGroup(
|
||||
ChannelId channelId,
|
||||
const MTPmessages_Messages &data);
|
||||
|
||||
void forgetScrollState() {
|
||||
scrollTopItem = nullptr;
|
||||
}
|
||||
@@ -418,7 +425,7 @@ public:
|
||||
|
||||
[[nodiscard]] std::pair<Element*, int> findItemAndOffset(int top) const;
|
||||
|
||||
MsgId nextNonHistoryEntryId();
|
||||
[[nodiscard]] MsgId nextNonHistoryEntryId();
|
||||
|
||||
bool folderKnown() const override;
|
||||
Data::Folder *folder() const override;
|
||||
@@ -458,6 +465,7 @@ public:
|
||||
mtpRequestId sendRequestId = 0;
|
||||
|
||||
Ui::Text::String cloudDraftTextCache;
|
||||
Dialogs::Ui::MessageView lastItemDialogsView;
|
||||
|
||||
private:
|
||||
friend class HistoryBlock;
|
||||
@@ -514,6 +522,9 @@ private:
|
||||
return _buildingFrontBlock != nullptr;
|
||||
}
|
||||
|
||||
void addCreatedOlderSlice(
|
||||
const std::vector<not_null<HistoryItem*>> &items);
|
||||
|
||||
void checkForLoadedAtTop(not_null<HistoryItem*> added);
|
||||
void mainViewRemoved(
|
||||
not_null<HistoryBlock*> block,
|
||||
|
||||
@@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "history/history_widget.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
@@ -22,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_emoji_interactions.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/image/image.h"
|
||||
@@ -41,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "boxes/about_sponsored_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
@@ -49,7 +49,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
#include "layout/layout_selection.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
@@ -58,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_attached_stickers.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "api/api_who_read.h"
|
||||
#include "api/api_views.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_media_types.h"
|
||||
@@ -168,6 +168,8 @@ HistoryInner::HistoryInner(
|
||||
HistoryView::MakePathShiftGradient(
|
||||
controller->chatStyle(),
|
||||
[=] { update(); }))
|
||||
, _touchSelectTimer([=] { onTouchSelect(); })
|
||||
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
||||
, _scrollDateCheck([this] { scrollDateCheck(); })
|
||||
, _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) {
|
||||
Instance = this;
|
||||
@@ -180,13 +182,7 @@ HistoryInner::HistoryInner(
|
||||
controller->setChatStyleTheme(_theme);
|
||||
}, lifetime());
|
||||
|
||||
_touchSelectTimer.setSingleShot(true);
|
||||
connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect()));
|
||||
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer()));
|
||||
|
||||
_trippleClickTimer.setSingleShot(true);
|
||||
|
||||
notifyIsBotChanged();
|
||||
|
||||
@@ -614,8 +610,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
|
||||
&& (!_migrated || _migrated->isDisplayedEmpty());
|
||||
bool noHistoryDisplayed = _firstLoading || historyDisplayedEmpty;
|
||||
if (!_firstLoading && _botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
|
||||
bool noHistoryDisplayed = historyDisplayedEmpty;
|
||||
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
|
||||
const auto st = context.st;
|
||||
const auto stm = &st->messageStyle(false, false);
|
||||
if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) {
|
||||
@@ -677,7 +673,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
view->draw(p, context);
|
||||
|
||||
if (item->hasViews()) {
|
||||
_controller->content()->scheduleViewIncrement(item);
|
||||
session().api().views().scheduleIncrement(item);
|
||||
}
|
||||
if (item->isUnreadMention() && !item->isUnreadMedia()) {
|
||||
readMentions.insert(item);
|
||||
@@ -737,7 +733,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
if (_visibleAreaBottom >= middle
|
||||
&& _visibleAreaTop <= middle) {
|
||||
if (item->hasViews()) {
|
||||
_controller->content()->scheduleViewIncrement(item);
|
||||
session().api().views().scheduleIncrement(item);
|
||||
}
|
||||
if (item->isUnreadMention() && !item->isUnreadMedia()) {
|
||||
readMentions.insert(item);
|
||||
@@ -891,7 +887,7 @@ void HistoryInner::onTouchScrollTimer() {
|
||||
if (_touchSpeed.isNull() || !hasScrolled) {
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
_touchScroll = false;
|
||||
_touchScrollTimer.stop();
|
||||
_touchScrollTimer.cancel();
|
||||
} else {
|
||||
_touchTime = nowTime;
|
||||
}
|
||||
@@ -966,7 +962,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
if (e->type() == QEvent::TouchCancel) { // cancel
|
||||
if (!_touchInProgress) return;
|
||||
_touchInProgress = false;
|
||||
_touchSelectTimer.stop();
|
||||
_touchSelectTimer.cancel();
|
||||
_touchScroll = _touchSelect = false;
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
mouseActionCancel();
|
||||
@@ -996,7 +992,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
_touchStart = _touchPos;
|
||||
} else {
|
||||
_touchScroll = false;
|
||||
_touchSelectTimer.start(QApplication::startDragTime());
|
||||
_touchSelectTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
_touchSelect = false;
|
||||
_touchStart = _touchPrevPos = _touchPos;
|
||||
@@ -1007,7 +1003,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
if (_touchSelect) {
|
||||
mouseActionUpdate(_touchPos);
|
||||
} else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
|
||||
_touchSelectTimer.stop();
|
||||
_touchSelectTimer.cancel();
|
||||
_touchScroll = true;
|
||||
touchUpdateSpeed();
|
||||
}
|
||||
@@ -1037,7 +1033,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
if (_touchScrollState == Ui::TouchScrollState::Manual) {
|
||||
_touchScrollState = Ui::TouchScrollState::Auto;
|
||||
_touchPrevPosValid = false;
|
||||
_touchScrollTimer.start(15);
|
||||
_touchScrollTimer.callEach(15);
|
||||
_touchTime = crl::now();
|
||||
} else if (_touchScrollState == Ui::TouchScrollState::Auto) {
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
@@ -1053,7 +1049,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
mouseActionFinish(_touchPos, Qt::LeftButton);
|
||||
}
|
||||
if (weak) {
|
||||
_touchSelectTimer.stop();
|
||||
_touchSelectTimer.cancel();
|
||||
_touchSelect = false;
|
||||
}
|
||||
} break;
|
||||
@@ -1161,7 +1157,8 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
|
||||
_mouseAction = MouseAction::Selecting;
|
||||
_mouseSelectType = TextSelectType::Paragraphs;
|
||||
mouseActionUpdate(_mousePosition);
|
||||
_trippleClickTimer.start(QApplication::doubleClickInterval());
|
||||
_trippleClickTimer.callOnce(
|
||||
QApplication::doubleClickInterval());
|
||||
}
|
||||
}
|
||||
} else if (App::pressedItem()) {
|
||||
@@ -1530,7 +1527,7 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
|
||||
mouseMoveEvent(e);
|
||||
|
||||
_trippleClickPoint = e->globalPos();
|
||||
_trippleClickTimer.start(QApplication::doubleClickInterval());
|
||||
_trippleClickTimer.callOnce(QApplication::doubleClickInterval());
|
||||
}
|
||||
}
|
||||
if (!ClickHandler::getActive()
|
||||
@@ -1615,16 +1612,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
return;
|
||||
}
|
||||
const auto itemId = item->fullId();
|
||||
if (hasWhoReadItem) {
|
||||
const auto participantChosen = [=](uint64 id) {
|
||||
controller->showPeerInfo(PeerId(id));
|
||||
};
|
||||
_menu->addAction(Ui::WhoReadContextAction(
|
||||
_menu.get(),
|
||||
Api::WhoRead(_dragStateItem, this, st::defaultWhoRead),
|
||||
participantChosen));
|
||||
_menu->addSeparator();
|
||||
}
|
||||
if (canSendMessages) {
|
||||
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
|
||||
_widget->replyToMessage(itemId);
|
||||
@@ -1733,6 +1720,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (hasWhoReadItem) {
|
||||
const auto participantChosen = [=](uint64 id) {
|
||||
controller->showPeerInfo(PeerId(id));
|
||||
};
|
||||
_menu->addAction(Ui::WhoReadContextAction(
|
||||
_menu.get(),
|
||||
Api::WhoRead(_dragStateItem, this, st::defaultWhoRead),
|
||||
participantChosen));
|
||||
_menu->addSeparator();
|
||||
}
|
||||
|
||||
const auto link = ClickHandler::getActive();
|
||||
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(link.get());
|
||||
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(link.get());
|
||||
@@ -1872,6 +1871,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
}
|
||||
if (msg && view && !link && (view->hasVisibleText() || mediaHasTextForCopy)) {
|
||||
if (item->isSponsored()) {
|
||||
_menu->addAction(tr::lng_sponsored_title({}), [=] {
|
||||
_controller->show(Box(Ui::AboutSponsoredBox));
|
||||
});
|
||||
}
|
||||
_menu->addAction(tr::lng_context_copy_text(tr::now), [=] {
|
||||
copyContextText(itemId);
|
||||
});
|
||||
@@ -2328,11 +2332,6 @@ bool HistoryInner::wasSelectedText() const {
|
||||
return _wasSelectedText;
|
||||
}
|
||||
|
||||
void HistoryInner::setFirstLoading(bool loading) {
|
||||
_firstLoading = loading;
|
||||
update();
|
||||
}
|
||||
|
||||
void HistoryInner::visibleAreaUpdated(int top, int bottom) {
|
||||
auto scrolledUp = (top < _visibleAreaTop);
|
||||
_visibleAreaTop = top;
|
||||
@@ -2657,7 +2656,7 @@ void HistoryInner::elementStartStickerLoop(
|
||||
|
||||
crl::time HistoryInner::elementHighlightTime(
|
||||
not_null<const HistoryItem*> item) {
|
||||
const auto fullAnimMs = _controller->content()->highlightStartTime(item);
|
||||
const auto fullAnimMs = _widget->highlightStartTime(item);
|
||||
if (fullAnimMs > 0) {
|
||||
const auto now = crl::now();
|
||||
if (fullAnimMs < now) {
|
||||
|
||||
@@ -121,7 +121,6 @@ public:
|
||||
void updateBotInfo(bool recount = true);
|
||||
|
||||
bool wasSelectedText() const;
|
||||
void setFirstLoading(bool loading);
|
||||
|
||||
// updates history->scrollTopItem/scrollTopOffset
|
||||
void visibleAreaUpdated(int top, int bottom);
|
||||
@@ -151,6 +150,8 @@ public:
|
||||
QPoint tooltipPos() const override;
|
||||
bool tooltipWindowActive() const override;
|
||||
|
||||
void onParentGeometryChanged();
|
||||
|
||||
// HistoryView::ElementDelegate interface.
|
||||
static not_null<HistoryView::ElementDelegate*> ElementDelegate();
|
||||
|
||||
@@ -172,13 +173,10 @@ protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void onParentGeometryChanged();
|
||||
|
||||
private:
|
||||
void onTouchSelect();
|
||||
void onTouchScrollTimer();
|
||||
|
||||
private:
|
||||
class BotAbout;
|
||||
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
|
||||
enum class MouseAction {
|
||||
@@ -378,8 +376,6 @@ private:
|
||||
mutable int _curBlock = 0;
|
||||
mutable int _curItem = 0;
|
||||
|
||||
bool _firstLoading = false;
|
||||
|
||||
style::cursor _cursor = style::cur_default;
|
||||
SelectedItems _selected;
|
||||
std::optional<Ui::ReportReason> _chooseForReportReason;
|
||||
@@ -403,7 +399,7 @@ private:
|
||||
bool _pressWasInactive = false;
|
||||
|
||||
QPoint _trippleClickPoint;
|
||||
QTimer _trippleClickTimer;
|
||||
base::Timer _trippleClickTimer;
|
||||
|
||||
Element *_dragSelFrom = nullptr;
|
||||
Element *_dragSelTo = nullptr;
|
||||
@@ -415,7 +411,7 @@ private:
|
||||
bool _touchSelect = false;
|
||||
bool _touchInProgress = false;
|
||||
QPoint _touchStart, _touchPrevPos, _touchPos;
|
||||
QTimer _touchSelectTimer;
|
||||
base::Timer _touchSelectTimer;
|
||||
|
||||
Ui::TouchScrollState _touchScrollState = Ui::TouchScrollState::Manual;
|
||||
bool _touchPrevPosValid = false;
|
||||
@@ -424,7 +420,7 @@ private:
|
||||
crl::time _touchSpeedTime = 0;
|
||||
crl::time _touchAccelerationTime = 0;
|
||||
crl::time _touchTime = 0;
|
||||
QTimer _touchScrollTimer;
|
||||
base::Timer _touchScrollTimer;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/crash_reports.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -49,6 +50,8 @@ namespace {
|
||||
|
||||
constexpr auto kNotificationTextLimit = 255;
|
||||
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
enum class MediaCheckResult {
|
||||
Good,
|
||||
Unsupported,
|
||||
@@ -84,7 +87,7 @@ not_null<HistoryItem*> CreateUnsupportedMessage(
|
||||
QString(),
|
||||
text,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupedId);
|
||||
}
|
||||
|
||||
@@ -202,14 +205,14 @@ void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) {
|
||||
}
|
||||
|
||||
void HistoryItem::finishEdition(int oldKeyboardTop) {
|
||||
_history->owner().requestItemViewRefresh(this);
|
||||
invalidateChatListEntry();
|
||||
if (const auto group = _history->owner().groups().find(this)) {
|
||||
const auto leader = group->items.front();
|
||||
if (leader != this) {
|
||||
_history->owner().requestItemViewRefresh(leader);
|
||||
leader->invalidateChatListEntry();
|
||||
for (const auto &item : group->items) {
|
||||
_history->owner().requestItemViewRefresh(item);
|
||||
item->invalidateChatListEntry();
|
||||
}
|
||||
} else {
|
||||
_history->owner().requestItemViewRefresh(this);
|
||||
invalidateChatListEntry();
|
||||
}
|
||||
|
||||
// Should be completely redesigned as the oldTop no longer exists.
|
||||
@@ -231,7 +234,7 @@ void HistoryItem::setGroupId(MessageGroupId groupId) {
|
||||
|
||||
HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
|
||||
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
if (markup->flags & ReplyMarkupFlag::Inline) {
|
||||
if (markup->data.flags & ReplyMarkupFlag::Inline) {
|
||||
return markup;
|
||||
}
|
||||
}
|
||||
@@ -292,17 +295,7 @@ void HistoryItem::invalidateChatListEntry() {
|
||||
history()->session().changes().messageUpdated(
|
||||
this,
|
||||
Data::MessageUpdate::Flag::DialogRowRefresh);
|
||||
|
||||
// invalidate cache for drawInDialog
|
||||
if (history()->textCachedFor == this) {
|
||||
history()->textCachedFor = nullptr;
|
||||
}
|
||||
//if (const auto feed = history()->peer->feed()) { // #TODO archive
|
||||
// if (feed->textCachedFor == this) {
|
||||
// feed->textCachedFor = nullptr;
|
||||
// feed->updateChatListEntry();
|
||||
// }
|
||||
//}
|
||||
history()->lastItemDialogsView.itemInvalidated(this);
|
||||
}
|
||||
|
||||
void HistoryItem::finishEditionToEmpty() {
|
||||
@@ -379,7 +372,7 @@ void HistoryItem::setIsPinned(bool pinned) {
|
||||
|
||||
bool HistoryItem::definesReplyKeyboard() const {
|
||||
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
if (markup->flags & ReplyMarkupFlag::Inline) {
|
||||
if (markup->data.flags & ReplyMarkupFlag::Inline) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -394,7 +387,7 @@ ReplyMarkupFlags HistoryItem::replyKeyboardFlags() const {
|
||||
Expects(definesReplyKeyboard());
|
||||
|
||||
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
return markup->flags;
|
||||
return markup->data.flags;
|
||||
}
|
||||
|
||||
// optimization: don't create markup component for the case
|
||||
@@ -459,6 +452,10 @@ bool HistoryItem::isScheduled() const {
|
||||
&& (_flags & MessageFlag::IsOrWasScheduled);
|
||||
}
|
||||
|
||||
bool HistoryItem::isSponsored() const {
|
||||
return (_flags & MessageFlag::IsSponsored);
|
||||
}
|
||||
|
||||
bool HistoryItem::skipNotification() const {
|
||||
if (isSilent() && (_flags & MessageFlag::IsContactSignUp)) {
|
||||
return true;
|
||||
@@ -516,7 +513,7 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) {
|
||||
&history()->session(),
|
||||
data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
updateReplyMarkup(data.vreply_markup());
|
||||
updateReplyMarkup(HistoryMessageMarkupData(data.vreply_markup()));
|
||||
updateForwardedInfo(data.vfwd_from());
|
||||
setViewsCount(data.vviews().value_or(-1));
|
||||
if (const auto replies = data.vreplies()) {
|
||||
@@ -756,7 +753,7 @@ MsgId HistoryItem::replyToTop() const {
|
||||
}
|
||||
|
||||
not_null<PeerData*> HistoryItem::author() const {
|
||||
return isPost() ? history()->peer : from();
|
||||
return (isPost() && !isSponsored()) ? history()->peer : from();
|
||||
}
|
||||
|
||||
TimeId HistoryItem::dateOriginal() const {
|
||||
@@ -934,7 +931,7 @@ bool HistoryItem::isEmpty() const {
|
||||
|
||||
QString HistoryItem::notificationText() const {
|
||||
const auto result = [&] {
|
||||
if (_media) {
|
||||
if (_media && !serviceMsg()) {
|
||||
return _media->notificationText();
|
||||
} else if (!emptyText()) {
|
||||
return _text.toString();
|
||||
@@ -946,20 +943,17 @@ QString HistoryItem::notificationText() const {
|
||||
: result.mid(0, kNotificationTextLimit) + qsl("...");
|
||||
}
|
||||
|
||||
QString HistoryItem::inDialogsText(DrawInDialog way) const {
|
||||
const auto plainText = [&] {
|
||||
ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
|
||||
auto result = [&]() -> ItemPreview {
|
||||
if (_media) {
|
||||
if (_groupId) {
|
||||
return textcmdLink(1, TextUtilities::Clean(tr::lng_in_dlg_album(tr::now)));
|
||||
}
|
||||
return _media->chatListText(way);
|
||||
return _media->toPreview(options);
|
||||
} else if (!emptyText()) {
|
||||
return TextUtilities::Clean(_text.toString());
|
||||
return { .text = TextUtilities::Clean(_text.toString()) };
|
||||
}
|
||||
return QString();
|
||||
return {};
|
||||
}();
|
||||
const auto sender = [&]() -> PeerData* {
|
||||
if (isPost() || isEmpty() || (way != DrawInDialog::Normal)) {
|
||||
if (options.hideSender || isPost() || isEmpty()) {
|
||||
return nullptr;
|
||||
} else if (!_history->peer->isUser() || out()) {
|
||||
return displayFrom();
|
||||
@@ -968,40 +962,25 @@ QString HistoryItem::inDialogsText(DrawInDialog way) const {
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
if (sender) {
|
||||
auto fromText = sender->isSelf() ? tr::lng_from_you(tr::now) : sender->shortName();
|
||||
auto fromWrapped = textcmdLink(1, tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, TextUtilities::Clean(fromText)));
|
||||
return tr::lng_dialogs_text_with_from(tr::now, lt_from_part, fromWrapped, lt_message, plainText);
|
||||
if (!sender) {
|
||||
return result;
|
||||
}
|
||||
return plainText;
|
||||
const auto fromText = sender->isSelf()
|
||||
? tr::lng_from_you(tr::now)
|
||||
: sender->shortName();
|
||||
const auto fromWrapped = textcmdLink(
|
||||
1,
|
||||
tr::lng_dialogs_text_from_wrapped(
|
||||
tr::now,
|
||||
lt_from,
|
||||
TextUtilities::Clean(fromText)));
|
||||
return Dialogs::Ui::PreviewWithSender(std::move(result), fromWrapped);
|
||||
}
|
||||
|
||||
Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
|
||||
return Ui::Text::IsolatedEmoji();
|
||||
}
|
||||
|
||||
void HistoryItem::drawInDialog(
|
||||
Painter &p,
|
||||
const QRect &r,
|
||||
bool active,
|
||||
bool selected,
|
||||
DrawInDialog way,
|
||||
const HistoryItem *&cacheFor,
|
||||
Ui::Text::String &cache) const {
|
||||
if (r.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (cacheFor != this) {
|
||||
cacheFor = this;
|
||||
cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
|
||||
}
|
||||
p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
|
||||
p.setFont(st::dialogsTextFont);
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
|
||||
p.restoreTextPalette();
|
||||
}
|
||||
|
||||
HistoryItem::~HistoryItem() {
|
||||
applyTTL(0);
|
||||
}
|
||||
@@ -1089,6 +1068,7 @@ MessageFlags FlagsFromMTP(MTPDmessageService::Flags flags) {
|
||||
|
||||
not_null<HistoryItem*> HistoryItem::Create(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPMessage &message,
|
||||
MessageFlags localFlags) {
|
||||
return message.match([&](const MTPDmessage &data) -> HistoryItem* {
|
||||
@@ -1099,7 +1079,7 @@ not_null<HistoryItem*> HistoryItem::Create(
|
||||
if (checked == MediaCheckResult::Unsupported) {
|
||||
return CreateUnsupportedMessage(
|
||||
history,
|
||||
data.vid().v,
|
||||
id,
|
||||
FlagsFromMTP(data.vflags().v) | localFlags,
|
||||
MsgId(0), // No need to pass reply_to data here.
|
||||
data.vvia_bot_id().value_or_empty(),
|
||||
@@ -1110,28 +1090,24 @@ not_null<HistoryItem*> HistoryItem::Create(
|
||||
tr::lng_message_empty(tr::now)
|
||||
};
|
||||
return history->makeServiceMessage(
|
||||
data.vid().v,
|
||||
id,
|
||||
FlagsFromMTP(data.vflags().v) | localFlags,
|
||||
data.vdate().v,
|
||||
text,
|
||||
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
|
||||
} else if (checked == MediaCheckResult::HasTimeToLive) {
|
||||
return history->makeServiceMessage(data, localFlags);
|
||||
return history->makeServiceMessage(id, data, localFlags);
|
||||
}
|
||||
return history->makeMessage(data, localFlags);
|
||||
return history->makeMessage(id, data, localFlags);
|
||||
}, [&](const MTPDmessageService &data) -> HistoryItem* {
|
||||
if (data.vaction().type() == mtpc_messageActionPhoneCall) {
|
||||
return history->makeMessage(data, localFlags);
|
||||
return history->makeMessage(id, data, localFlags);
|
||||
}
|
||||
return history->makeServiceMessage(data, localFlags);
|
||||
return history->makeServiceMessage(id, data, localFlags);
|
||||
}, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
|
||||
const auto text = HistoryService::PreparedText{
|
||||
tr::lng_message_empty(tr::now)
|
||||
};
|
||||
return history->makeServiceMessage(
|
||||
data.vid().v,
|
||||
localFlags,
|
||||
TimeId(0),
|
||||
text);
|
||||
return history->makeServiceMessage(id, localFlags, TimeId(0), text);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/flags.h"
|
||||
#include "base/value_ordering.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "history/history_item_reply_markup.h"
|
||||
|
||||
#include <any>
|
||||
|
||||
enum class UnreadMentionType;
|
||||
struct HistoryMessageReplyMarkup;
|
||||
@@ -46,34 +49,43 @@ class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
struct TextState;
|
||||
struct StateRequest;
|
||||
enum class CursorState : char;
|
||||
enum class PointState : char;
|
||||
enum class Context : char;
|
||||
class ElementDelegate;
|
||||
enum class DrawInDialog {
|
||||
Normal,
|
||||
WithoutSender,
|
||||
WithoutSenderAndCaption,
|
||||
|
||||
struct ItemPreviewImage {
|
||||
QImage data;
|
||||
uint64 cacheKey = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !data.isNull();
|
||||
}
|
||||
};
|
||||
|
||||
struct ItemPreview {
|
||||
QString text;
|
||||
std::vector<ItemPreviewImage> images;
|
||||
int imagesInTextPosition = 0;
|
||||
std::any loadingContext;
|
||||
};
|
||||
|
||||
struct ToPreviewOptions {
|
||||
const std::vector<ItemPreviewImage> *existing = nullptr;
|
||||
bool hideSender = false;
|
||||
bool hideCaption = false;
|
||||
bool generateImages = true;
|
||||
bool ignoreGroup = false;
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
struct HiddenSenderInfo;
|
||||
class History;
|
||||
|
||||
enum class ReplyMarkupFlag : uint32 {
|
||||
None = (1U << 0),
|
||||
ForceReply = (1U << 1),
|
||||
HasSwitchInlineButton = (1U << 2),
|
||||
Inline = (1U << 3),
|
||||
Resize = (1U << 4),
|
||||
SingleUse = (1U << 5),
|
||||
Selective = (1U << 6),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ReplyMarkupFlag) { return true; }
|
||||
using ReplyMarkupFlags = base::flags<ReplyMarkupFlag>;
|
||||
|
||||
[[nodiscard]] MessageFlags FlagsFromMTP(MTPDmessage::Flags flags);
|
||||
[[nodiscard]] MessageFlags FlagsFromMTP(MTPDmessageService::Flags flags);
|
||||
|
||||
@@ -81,6 +93,7 @@ class HistoryItem : public RuntimeComposer<HistoryItem> {
|
||||
public:
|
||||
static not_null<HistoryItem*> Create(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPMessage &message,
|
||||
MessageFlags localFlags);
|
||||
|
||||
@@ -109,6 +122,7 @@ public:
|
||||
[[nodiscard]] bool isAdminLogEntry() const;
|
||||
[[nodiscard]] bool isFromScheduled() const;
|
||||
[[nodiscard]] bool isScheduled() const;
|
||||
[[nodiscard]] bool isSponsored() const;
|
||||
[[nodiscard]] bool skipNotification() const;
|
||||
|
||||
void addLogEntryOriginal(
|
||||
@@ -272,7 +286,7 @@ public:
|
||||
const TextWithEntities &textWithEntities,
|
||||
const MTPMessageMedia *media) {
|
||||
}
|
||||
virtual void updateReplyMarkup(const MTPReplyMarkup *markup) {
|
||||
virtual void updateReplyMarkup(HistoryMessageMarkupData &&markup) {
|
||||
}
|
||||
virtual void updateForwardedInfo(const MTPMessageFwdHeader *fwd) {
|
||||
}
|
||||
@@ -297,13 +311,18 @@ public:
|
||||
}
|
||||
[[nodiscard]] virtual QString notificationText() const;
|
||||
|
||||
using DrawInDialog = HistoryView::DrawInDialog;
|
||||
using ToPreviewOptions = HistoryView::ToPreviewOptions;
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
// Returns text with link-start and link-end commands for service-color highlighting.
|
||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
[[nodiscard]] virtual QString inDialogsText(DrawInDialog way) const;
|
||||
[[nodiscard]] virtual ItemPreview toPreview(
|
||||
ToPreviewOptions options) const;
|
||||
[[nodiscard]] virtual QString inReplyText() const {
|
||||
return inDialogsText(DrawInDialog::WithoutSender);
|
||||
return toPreview({
|
||||
.hideSender = true,
|
||||
.generateImages = false,
|
||||
}).text;
|
||||
}
|
||||
[[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
|
||||
[[nodiscard]] virtual TextWithEntities originalText() const {
|
||||
@@ -334,15 +353,6 @@ public:
|
||||
virtual void incrementReplyToTopCounter() {
|
||||
}
|
||||
|
||||
void drawInDialog(
|
||||
Painter &p,
|
||||
const QRect &r,
|
||||
bool active,
|
||||
bool selected,
|
||||
DrawInDialog way,
|
||||
const HistoryItem *&cacheFor,
|
||||
Ui::Text::String &cache) const;
|
||||
|
||||
[[nodiscard]] bool emptyText() const {
|
||||
return _text.isEmpty();
|
||||
}
|
||||
|
||||
@@ -116,6 +116,17 @@ int HistoryMessageEdited::maxWidth() const {
|
||||
return text.maxWidth();
|
||||
}
|
||||
|
||||
HistoryMessageSponsored::HistoryMessageSponsored() {
|
||||
text.setText(
|
||||
st::msgDateTextStyle,
|
||||
tr::lng_sponsored(tr::now),
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
int HistoryMessageSponsored::maxWidth() const {
|
||||
return text.maxWidth();
|
||||
}
|
||||
|
||||
HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external)
|
||||
: name(name)
|
||||
, colorPeerId(Data::FakePeerIdForJustName(name))
|
||||
@@ -504,10 +515,10 @@ ReplyKeyboard::ReplyKeyboard(
|
||||
if (const auto markup = _item->Get<HistoryMessageReplyMarkup>()) {
|
||||
const auto owner = &_item->history()->owner();
|
||||
const auto context = _item->fullId();
|
||||
const auto rowCount = int(markup->rows.size());
|
||||
const auto rowCount = int(markup->data.rows.size());
|
||||
_rows.reserve(rowCount);
|
||||
for (auto i = 0; i != rowCount; ++i) {
|
||||
const auto &row = markup->rows.at(i);
|
||||
const auto &row = markup->data.rows[i];
|
||||
const auto rowSize = int(row.size());
|
||||
auto newRow = std::vector<Button>();
|
||||
newRow.reserve(rowSize);
|
||||
@@ -831,174 +842,17 @@ void ReplyKeyboard::Style::paintButton(
|
||||
button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
|
||||
}
|
||||
|
||||
HistoryMessageMarkupButton::HistoryMessageMarkupButton(
|
||||
Type type,
|
||||
const QString &text,
|
||||
const QByteArray &data,
|
||||
const QString &forwardText,
|
||||
int32 buttonId)
|
||||
: type(type)
|
||||
, text(text)
|
||||
, forwardText(forwardText)
|
||||
, data(data)
|
||||
, buttonId(buttonId) {
|
||||
void HistoryMessageReplyMarkup::createForwarded(
|
||||
const HistoryMessageReplyMarkup &original) {
|
||||
Expects(!inlineKeyboard);
|
||||
|
||||
data.fillForwardedData(original.data);
|
||||
}
|
||||
|
||||
HistoryMessageMarkupButton *HistoryMessageMarkupButton::Get(
|
||||
not_null<Data::Session*> owner,
|
||||
FullMsgId itemId,
|
||||
int row,
|
||||
int column) {
|
||||
if (const auto item = owner->message(itemId)) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (row < markup->rows.size()) {
|
||||
auto &buttons = markup->rows[row];
|
||||
if (column < buttons.size()) {
|
||||
return &buttons[column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HistoryMessageReplyMarkup::createFromButtonRows(
|
||||
const QVector<MTPKeyboardButtonRow> &list) {
|
||||
rows.clear();
|
||||
if (list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
rows.reserve(list.size());
|
||||
for (const auto &row : list) {
|
||||
row.match([&](const MTPDkeyboardButtonRow &data) {
|
||||
auto row = std::vector<Button>();
|
||||
row.reserve(data.vbuttons().v.size());
|
||||
for (const auto &button : data.vbuttons().v) {
|
||||
using Type = Button::Type;
|
||||
button.match([&](const MTPDkeyboardButton &data) {
|
||||
row.emplace_back(Type::Default, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonCallback &data) {
|
||||
row.emplace_back(
|
||||
(data.is_requires_password()
|
||||
? Type::CallbackWithPassword
|
||||
: Type::Callback),
|
||||
qs(data.vtext()),
|
||||
qba(data.vdata()));
|
||||
}, [&](const MTPDkeyboardButtonRequestGeoLocation &data) {
|
||||
row.emplace_back(Type::RequestLocation, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonRequestPhone &data) {
|
||||
row.emplace_back(Type::RequestPhone, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonUrl &data) {
|
||||
row.emplace_back(
|
||||
Type::Url,
|
||||
qs(data.vtext()),
|
||||
qba(data.vurl()));
|
||||
}, [&](const MTPDkeyboardButtonSwitchInline &data) {
|
||||
const auto type = data.is_same_peer()
|
||||
? Type::SwitchInlineSame
|
||||
: Type::SwitchInline;
|
||||
row.emplace_back(type, qs(data.vtext()), qba(data.vquery()));
|
||||
if (type == Type::SwitchInline) {
|
||||
// Optimization flag.
|
||||
// Fast check on all new messages if there is a switch button to auto-click it.
|
||||
flags |= ReplyMarkupFlag::HasSwitchInlineButton;
|
||||
}
|
||||
}, [&](const MTPDkeyboardButtonGame &data) {
|
||||
row.emplace_back(Type::Game, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonBuy &data) {
|
||||
row.emplace_back(Type::Buy, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonUrlAuth &data) {
|
||||
row.emplace_back(
|
||||
Type::Auth,
|
||||
qs(data.vtext()),
|
||||
qba(data.vurl()),
|
||||
qs(data.vfwd_text().value_or_empty()),
|
||||
data.vbutton_id().v);
|
||||
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
|
||||
LOG(("API Error: inputKeyboardButtonUrlAuth received."));
|
||||
// Should not get those for the users.
|
||||
}, [&](const MTPDkeyboardButtonRequestPoll &data) {
|
||||
const auto quiz = [&] {
|
||||
if (!data.vquiz()) {
|
||||
return QByteArray();
|
||||
}
|
||||
return data.vquiz()->match([&](const MTPDboolTrue&) {
|
||||
return QByteArray(1, 1);
|
||||
}, [&](const MTPDboolFalse&) {
|
||||
return QByteArray(1, 0);
|
||||
});
|
||||
}();
|
||||
row.emplace_back(
|
||||
Type::RequestPoll,
|
||||
qs(data.vtext()),
|
||||
quiz);
|
||||
});
|
||||
}
|
||||
if (!row.empty()) {
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) {
|
||||
flags = 0;
|
||||
rows.clear();
|
||||
void HistoryMessageReplyMarkup::updateData(
|
||||
HistoryMessageMarkupData &&markup) {
|
||||
data = std::move(markup);
|
||||
inlineKeyboard = nullptr;
|
||||
|
||||
using Flag = ReplyMarkupFlag;
|
||||
markup.match([&](const MTPDreplyKeyboardMarkup &data) {
|
||||
flags = (data.is_resize() ? Flag::Resize : Flag())
|
||||
| (data.is_selective() ? Flag::Selective : Flag())
|
||||
| (data.is_single_use() ? Flag::SingleUse : Flag());
|
||||
placeholder = qs(data.vplaceholder().value_or_empty());
|
||||
createFromButtonRows(data.vrows().v);
|
||||
}, [&](const MTPDreplyInlineMarkup &data) {
|
||||
flags = Flag::Inline;
|
||||
placeholder = QString();
|
||||
createFromButtonRows(data.vrows().v);
|
||||
}, [&](const MTPDreplyKeyboardHide &data) {
|
||||
flags = Flag::None | (data.is_selective() ? Flag::Selective : Flag());
|
||||
placeholder = QString();
|
||||
}, [&](const MTPDreplyKeyboardForceReply &data) {
|
||||
flags = Flag::ForceReply
|
||||
| (data.is_selective() ? Flag::Selective : Flag())
|
||||
| (data.is_single_use() ? Flag::SingleUse : Flag());
|
||||
placeholder = qs(data.vplaceholder().value_or_empty());
|
||||
});
|
||||
}
|
||||
|
||||
void HistoryMessageReplyMarkup::create(
|
||||
const HistoryMessageReplyMarkup &markup) {
|
||||
flags = markup.flags;
|
||||
placeholder = markup.placeholder;
|
||||
inlineKeyboard = nullptr;
|
||||
|
||||
rows.clear();
|
||||
rows.reserve(markup.rows.size());
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
for (const auto &existing : markup.rows) {
|
||||
auto row = std::vector<Button>();
|
||||
row.reserve(existing.size());
|
||||
for (const auto &button : existing) {
|
||||
const auto newType = (button.type != Type::SwitchInlineSame)
|
||||
? button.type
|
||||
: Type::SwitchInline;
|
||||
const auto text = button.forwardText.isEmpty()
|
||||
? button.text
|
||||
: button.forwardText;
|
||||
row.emplace_back(
|
||||
newType,
|
||||
text,
|
||||
button.data,
|
||||
QString(),
|
||||
button.buttonId);
|
||||
}
|
||||
if (!row.empty()) {
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default;
|
||||
|
||||
@@ -77,6 +77,15 @@ struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited, Hist
|
||||
Ui::Text::String text;
|
||||
};
|
||||
|
||||
struct HistoryMessageSponsored : public RuntimeComponent<
|
||||
HistoryMessageSponsored,
|
||||
HistoryItem> {
|
||||
HistoryMessageSponsored();
|
||||
int maxWidth() const;
|
||||
|
||||
Ui::Text::String text;
|
||||
};
|
||||
|
||||
struct HiddenSenderInfo {
|
||||
HiddenSenderInfo(const QString &name, bool external);
|
||||
|
||||
@@ -189,63 +198,16 @@ struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, Histor
|
||||
|
||||
};
|
||||
|
||||
struct HistoryMessageMarkupButton {
|
||||
enum class Type {
|
||||
Default,
|
||||
Url,
|
||||
Callback,
|
||||
CallbackWithPassword,
|
||||
RequestPhone,
|
||||
RequestLocation,
|
||||
RequestPoll,
|
||||
SwitchInline,
|
||||
SwitchInlineSame,
|
||||
Game,
|
||||
Buy,
|
||||
Auth,
|
||||
};
|
||||
|
||||
HistoryMessageMarkupButton(
|
||||
Type type,
|
||||
const QString &text,
|
||||
const QByteArray &data = QByteArray(),
|
||||
const QString &forwardText = QString(),
|
||||
int32 buttonId = 0);
|
||||
|
||||
static HistoryMessageMarkupButton *Get(
|
||||
not_null<Data::Session*> owner,
|
||||
FullMsgId itemId,
|
||||
int row,
|
||||
int column);
|
||||
|
||||
Type type;
|
||||
QString text, forwardText;
|
||||
QByteArray data;
|
||||
int32 buttonId = 0;
|
||||
mutable mtpRequestId requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
struct HistoryMessageReplyMarkup
|
||||
: public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> {
|
||||
using Button = HistoryMessageMarkupButton;
|
||||
|
||||
HistoryMessageReplyMarkup() = default;
|
||||
HistoryMessageReplyMarkup(ReplyMarkupFlags flags) : flags(flags) {
|
||||
}
|
||||
|
||||
void create(const MTPReplyMarkup &markup);
|
||||
void create(const HistoryMessageReplyMarkup &markup);
|
||||
|
||||
std::vector<std::vector<Button>> rows;
|
||||
ReplyMarkupFlags flags = 0;
|
||||
QString placeholder;
|
||||
void createForwarded(const HistoryMessageReplyMarkup &original);
|
||||
void updateData(HistoryMessageMarkupData &&markup);
|
||||
|
||||
HistoryMessageMarkupData data;
|
||||
std::unique_ptr<ReplyKeyboard> inlineKeyboard;
|
||||
|
||||
private:
|
||||
void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v);
|
||||
|
||||
};
|
||||
|
||||
class ReplyMarkupClickHandler : public ClickHandler {
|
||||
|
||||
197
Telegram/SourceFiles/history/history_item_reply_markup.cpp
Normal file
197
Telegram/SourceFiles/history/history_item_reply_markup.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
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 "history/history_item_reply_markup.h"
|
||||
|
||||
#include "data/data_session.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
|
||||
HistoryMessageMarkupButton::HistoryMessageMarkupButton(
|
||||
Type type,
|
||||
const QString &text,
|
||||
const QByteArray &data,
|
||||
const QString &forwardText,
|
||||
int32 buttonId)
|
||||
: type(type)
|
||||
, text(text)
|
||||
, forwardText(forwardText)
|
||||
, data(data)
|
||||
, buttonId(buttonId) {
|
||||
}
|
||||
|
||||
HistoryMessageMarkupButton *HistoryMessageMarkupButton::Get(
|
||||
not_null<Data::Session*> owner,
|
||||
FullMsgId itemId,
|
||||
int row,
|
||||
int column) {
|
||||
if (const auto item = owner->message(itemId)) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (row < markup->data.rows.size()) {
|
||||
auto &buttons = markup->data.rows[row];
|
||||
if (column < buttons.size()) {
|
||||
return &buttons[column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HistoryMessageMarkupData::fillRows(
|
||||
const QVector<MTPKeyboardButtonRow> &list) {
|
||||
rows.clear();
|
||||
if (list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
rows.reserve(list.size());
|
||||
for (const auto &row : list) {
|
||||
row.match([&](const MTPDkeyboardButtonRow &data) {
|
||||
auto row = std::vector<Button>();
|
||||
row.reserve(data.vbuttons().v.size());
|
||||
for (const auto &button : data.vbuttons().v) {
|
||||
using Type = Button::Type;
|
||||
button.match([&](const MTPDkeyboardButton &data) {
|
||||
row.emplace_back(Type::Default, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonCallback &data) {
|
||||
row.emplace_back(
|
||||
(data.is_requires_password()
|
||||
? Type::CallbackWithPassword
|
||||
: Type::Callback),
|
||||
qs(data.vtext()),
|
||||
qba(data.vdata()));
|
||||
}, [&](const MTPDkeyboardButtonRequestGeoLocation &data) {
|
||||
row.emplace_back(Type::RequestLocation, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonRequestPhone &data) {
|
||||
row.emplace_back(Type::RequestPhone, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonUrl &data) {
|
||||
row.emplace_back(
|
||||
Type::Url,
|
||||
qs(data.vtext()),
|
||||
qba(data.vurl()));
|
||||
}, [&](const MTPDkeyboardButtonSwitchInline &data) {
|
||||
const auto type = data.is_same_peer()
|
||||
? Type::SwitchInlineSame
|
||||
: Type::SwitchInline;
|
||||
row.emplace_back(type, qs(data.vtext()), qba(data.vquery()));
|
||||
if (type == Type::SwitchInline) {
|
||||
// Optimization flag.
|
||||
// Fast check on all new messages if there is a switch button to auto-click it.
|
||||
flags |= ReplyMarkupFlag::HasSwitchInlineButton;
|
||||
}
|
||||
}, [&](const MTPDkeyboardButtonGame &data) {
|
||||
row.emplace_back(Type::Game, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonBuy &data) {
|
||||
row.emplace_back(Type::Buy, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonUrlAuth &data) {
|
||||
row.emplace_back(
|
||||
Type::Auth,
|
||||
qs(data.vtext()),
|
||||
qba(data.vurl()),
|
||||
qs(data.vfwd_text().value_or_empty()),
|
||||
data.vbutton_id().v);
|
||||
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
|
||||
LOG(("API Error: inputKeyboardButtonUrlAuth received."));
|
||||
// Should not get those for the users.
|
||||
}, [&](const MTPDkeyboardButtonRequestPoll &data) {
|
||||
const auto quiz = [&] {
|
||||
if (!data.vquiz()) {
|
||||
return QByteArray();
|
||||
}
|
||||
return data.vquiz()->match([&](const MTPDboolTrue&) {
|
||||
return QByteArray(1, 1);
|
||||
}, [&](const MTPDboolFalse&) {
|
||||
return QByteArray(1, 0);
|
||||
});
|
||||
}();
|
||||
row.emplace_back(
|
||||
Type::RequestPoll,
|
||||
qs(data.vtext()),
|
||||
quiz);
|
||||
});
|
||||
}
|
||||
if (!row.empty()) {
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HistoryMessageMarkupData::HistoryMessageMarkupData(
|
||||
const MTPReplyMarkup *data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
using Flag = ReplyMarkupFlag;
|
||||
data->match([&](const MTPDreplyKeyboardMarkup &data) {
|
||||
flags = (data.is_resize() ? Flag::Resize : Flag())
|
||||
| (data.is_selective() ? Flag::Selective : Flag())
|
||||
| (data.is_single_use() ? Flag::SingleUse : Flag());
|
||||
placeholder = qs(data.vplaceholder().value_or_empty());
|
||||
fillRows(data.vrows().v);
|
||||
}, [&](const MTPDreplyInlineMarkup &data) {
|
||||
flags = Flag::Inline;
|
||||
placeholder = QString();
|
||||
fillRows(data.vrows().v);
|
||||
}, [&](const MTPDreplyKeyboardHide &data) {
|
||||
flags = Flag::None | (data.is_selective() ? Flag::Selective : Flag());
|
||||
placeholder = QString();
|
||||
}, [&](const MTPDreplyKeyboardForceReply &data) {
|
||||
flags = Flag::ForceReply
|
||||
| (data.is_selective() ? Flag::Selective : Flag())
|
||||
| (data.is_single_use() ? Flag::SingleUse : Flag());
|
||||
placeholder = qs(data.vplaceholder().value_or_empty());
|
||||
});
|
||||
}
|
||||
|
||||
void HistoryMessageMarkupData::fillForwardedData(
|
||||
const HistoryMessageMarkupData &original) {
|
||||
Expects(isNull());
|
||||
Expects(!original.isNull());
|
||||
|
||||
flags = original.flags;
|
||||
placeholder = original.placeholder;
|
||||
|
||||
rows.reserve(original.rows.size());
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
for (const auto &existing : original.rows) {
|
||||
auto row = std::vector<Button>();
|
||||
row.reserve(existing.size());
|
||||
for (const auto &button : existing) {
|
||||
const auto newType = (button.type != Type::SwitchInlineSame)
|
||||
? button.type
|
||||
: Type::SwitchInline;
|
||||
const auto text = button.forwardText.isEmpty()
|
||||
? button.text
|
||||
: button.forwardText;
|
||||
row.emplace_back(
|
||||
newType,
|
||||
text,
|
||||
button.data,
|
||||
QString(),
|
||||
button.buttonId);
|
||||
}
|
||||
if (!row.empty()) {
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryMessageMarkupData::isNull() const {
|
||||
if (flags & ReplyMarkupFlag::IsNull) {
|
||||
Assert(isTrivial());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HistoryMessageMarkupData::isTrivial() const {
|
||||
return rows.empty()
|
||||
&& placeholder.isEmpty()
|
||||
&& !(flags & ~ReplyMarkupFlag::IsNull);
|
||||
}
|
||||
83
Telegram/SourceFiles/history/history_item_reply_markup.h
Normal file
83
Telegram/SourceFiles/history/history_item_reply_markup.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
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/flags.h"
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
} // namespace Data
|
||||
|
||||
enum class ReplyMarkupFlag : uint32 {
|
||||
None = (1U << 0),
|
||||
ForceReply = (1U << 1),
|
||||
HasSwitchInlineButton = (1U << 2),
|
||||
Inline = (1U << 3),
|
||||
Resize = (1U << 4),
|
||||
SingleUse = (1U << 5),
|
||||
Selective = (1U << 6),
|
||||
IsNull = (1U << 7),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ReplyMarkupFlag) { return true; }
|
||||
using ReplyMarkupFlags = base::flags<ReplyMarkupFlag>;
|
||||
|
||||
struct HistoryMessageMarkupButton {
|
||||
enum class Type {
|
||||
Default,
|
||||
Url,
|
||||
Callback,
|
||||
CallbackWithPassword,
|
||||
RequestPhone,
|
||||
RequestLocation,
|
||||
RequestPoll,
|
||||
SwitchInline,
|
||||
SwitchInlineSame,
|
||||
Game,
|
||||
Buy,
|
||||
Auth,
|
||||
};
|
||||
|
||||
HistoryMessageMarkupButton(
|
||||
Type type,
|
||||
const QString &text,
|
||||
const QByteArray &data = QByteArray(),
|
||||
const QString &forwardText = QString(),
|
||||
int32 buttonId = 0);
|
||||
|
||||
static HistoryMessageMarkupButton *Get(
|
||||
not_null<Data::Session*> owner,
|
||||
FullMsgId itemId,
|
||||
int row,
|
||||
int column);
|
||||
|
||||
Type type;
|
||||
QString text, forwardText;
|
||||
QByteArray data;
|
||||
int32 buttonId = 0;
|
||||
mutable mtpRequestId requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
struct HistoryMessageMarkupData {
|
||||
HistoryMessageMarkupData() = default;
|
||||
explicit HistoryMessageMarkupData(const MTPReplyMarkup *data);
|
||||
|
||||
void fillForwardedData(const HistoryMessageMarkupData &original);
|
||||
|
||||
[[nodiscard]] bool isNull() const;
|
||||
[[nodiscard]] bool isTrivial() const;
|
||||
|
||||
using Button = HistoryMessageMarkupButton;
|
||||
std::vector<std::vector<Button>> rows;
|
||||
ReplyMarkupFlags flags = ReplyMarkupFlag::IsNull;
|
||||
QString placeholder;
|
||||
|
||||
private:
|
||||
void fillRows(const QVector<MTPKeyboardButtonRow> &v);
|
||||
|
||||
};
|
||||
@@ -89,7 +89,7 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
for (const auto &row : markup->rows) {
|
||||
for (const auto &row : markup->data.rows) {
|
||||
for (const auto &button : row) {
|
||||
const auto switchInline = (button.type == Type::SwitchInline)
|
||||
|| (button.type == Type::SwitchInlineSame);
|
||||
@@ -422,10 +422,11 @@ struct HistoryMessage::CreateConfig {
|
||||
TimeId originalDate = 0;
|
||||
TimeId editDate = 0;
|
||||
bool imported = false;
|
||||
HistoryMessageMarkupData markup;
|
||||
bool sponsored = false;
|
||||
|
||||
// For messages created from MTP structs.
|
||||
const MTPMessageReplies *mtpReplies = nullptr;
|
||||
const MTPReplyMarkup *mtpMarkup = nullptr;
|
||||
|
||||
// For messages created from existing messages (forwarded).
|
||||
const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
|
||||
@@ -453,11 +454,12 @@ void HistoryMessage::FillForwardedInfo(
|
||||
|
||||
HistoryMessage::HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessage &data,
|
||||
MessageFlags localFlags)
|
||||
: HistoryItem(
|
||||
history,
|
||||
data.vid().v,
|
||||
id,
|
||||
FlagsFromMTP(data.vflags().v) | localFlags,
|
||||
data.vdate().v,
|
||||
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
|
||||
@@ -477,17 +479,16 @@ HistoryMessage::HistoryMessage(
|
||||
}
|
||||
config.replyTo = data.vreply_to_msg_id().v;
|
||||
config.replyToTop = data.vreply_to_top_id().value_or(
|
||||
config.replyTo);
|
||||
data.vreply_to_msg_id().v);
|
||||
});
|
||||
}
|
||||
config.viaBotId = data.vvia_bot_id().value_or_empty();
|
||||
config.viewsCount = data.vviews().value_or(-1);
|
||||
config.mtpReplies = isScheduled() ? nullptr : data.vreplies();
|
||||
config.mtpMarkup = data.vreply_markup();
|
||||
config.markup = HistoryMessageMarkupData(data.vreply_markup());
|
||||
config.editDate = data.vedit_date().value_or_empty();
|
||||
config.author = qs(data.vpost_author().value_or_empty());
|
||||
|
||||
createComponents(config);
|
||||
createComponents(std::move(config));
|
||||
|
||||
if (const auto media = data.vmedia()) {
|
||||
setMedia(*media);
|
||||
@@ -509,16 +510,16 @@ HistoryMessage::HistoryMessage(
|
||||
|
||||
HistoryMessage::HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessageService &data,
|
||||
MessageFlags localFlags)
|
||||
: HistoryItem(
|
||||
history,
|
||||
data.vid().v,
|
||||
id,
|
||||
FlagsFromMTP(data.vflags().v) | localFlags,
|
||||
data.vdate().v,
|
||||
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
|
||||
auto config = CreateConfig();
|
||||
|
||||
if (const auto reply = data.vreply_to()) {
|
||||
reply->match([&](const MTPDmessageReplyHeader &data) {
|
||||
const auto peer = data.vreply_to_peer_id()
|
||||
@@ -527,12 +528,11 @@ HistoryMessage::HistoryMessage(
|
||||
if (!peer || peer == history->peer->id) {
|
||||
config.replyTo = data.vreply_to_msg_id().v;
|
||||
config.replyToTop = data.vreply_to_top_id().value_or(
|
||||
config.replyTo);
|
||||
data.vreply_to_msg_id().v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createComponents(config);
|
||||
createComponents(std::move(config));
|
||||
|
||||
data.vaction().match([&](const MTPDmessageActionPhoneCall &data) {
|
||||
_media = std::make_unique<Data::MediaCall>(this, data);
|
||||
@@ -623,8 +623,7 @@ HistoryMessage::HistoryMessage(
|
||||
if (CopyMarkupToForward(original)) {
|
||||
config.inlineMarkup = original->inlineReplyMarkup();
|
||||
}
|
||||
|
||||
createComponents(config);
|
||||
createComponents(std::move(config));
|
||||
|
||||
const auto ignoreMedia = [&] {
|
||||
if (mediaOriginal && mediaOriginal->webpage()) {
|
||||
@@ -651,7 +650,7 @@ HistoryMessage::HistoryMessage(
|
||||
const QString &postAuthor,
|
||||
const TextWithEntities &textWithEntities,
|
||||
const MTPMessageMedia &media,
|
||||
const MTPReplyMarkup &markup,
|
||||
HistoryMessageMarkupData &&markup,
|
||||
uint64 groupedId)
|
||||
: HistoryItem(
|
||||
history,
|
||||
@@ -659,7 +658,12 @@ HistoryMessage::HistoryMessage(
|
||||
flags,
|
||||
date,
|
||||
(flags & MessageFlag::HasFromId) ? from : 0) {
|
||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
||||
createComponentsHelper(
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
postAuthor,
|
||||
std::move(markup));
|
||||
setMedia(media);
|
||||
setText(textWithEntities);
|
||||
if (groupedId) {
|
||||
@@ -678,14 +682,19 @@ HistoryMessage::HistoryMessage(
|
||||
const QString &postAuthor,
|
||||
not_null<DocumentData*> document,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup)
|
||||
HistoryMessageMarkupData &&markup)
|
||||
: HistoryItem(
|
||||
history,
|
||||
id,
|
||||
flags,
|
||||
date,
|
||||
(flags & MessageFlag::HasFromId) ? from : 0) {
|
||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
||||
createComponentsHelper(
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
postAuthor,
|
||||
std::move(markup));
|
||||
|
||||
_media = std::make_unique<Data::MediaFile>(this, document);
|
||||
setText(caption);
|
||||
@@ -702,14 +711,19 @@ HistoryMessage::HistoryMessage(
|
||||
const QString &postAuthor,
|
||||
not_null<PhotoData*> photo,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup)
|
||||
HistoryMessageMarkupData &&markup)
|
||||
: HistoryItem(
|
||||
history,
|
||||
id,
|
||||
flags,
|
||||
date,
|
||||
(flags & MessageFlag::HasFromId) ? from : 0) {
|
||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
||||
createComponentsHelper(
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
postAuthor,
|
||||
std::move(markup));
|
||||
|
||||
_media = std::make_unique<Data::MediaPhoto>(this, photo);
|
||||
setText(caption);
|
||||
@@ -725,14 +739,19 @@ HistoryMessage::HistoryMessage(
|
||||
PeerId from,
|
||||
const QString &postAuthor,
|
||||
not_null<GameData*> game,
|
||||
const MTPReplyMarkup &markup)
|
||||
HistoryMessageMarkupData &&markup)
|
||||
: HistoryItem(
|
||||
history,
|
||||
id,
|
||||
flags,
|
||||
date,
|
||||
(flags & MessageFlag::HasFromId) ? from : 0) {
|
||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
||||
createComponentsHelper(
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
postAuthor,
|
||||
std::move(markup));
|
||||
|
||||
_media = std::make_unique<Data::MediaGame>(this, game);
|
||||
setEmptyText();
|
||||
@@ -743,20 +762,22 @@ void HistoryMessage::createComponentsHelper(
|
||||
MsgId replyTo,
|
||||
UserId viaBotId,
|
||||
const QString &postAuthor,
|
||||
const MTPReplyMarkup &markup) {
|
||||
HistoryMessageMarkupData &&markup) {
|
||||
auto config = CreateConfig();
|
||||
|
||||
if (flags & MessageFlag::HasViaBot) config.viaBotId = viaBotId;
|
||||
if (flags & MessageFlag::HasReplyInfo) {
|
||||
config.replyTo = replyTo;
|
||||
const auto replyToTop = LookupReplyToTop(history(), replyTo);
|
||||
config.replyToTop = replyToTop ? replyToTop : replyTo;
|
||||
}
|
||||
if (flags & MessageFlag::HasReplyMarkup) config.mtpMarkup = &markup;
|
||||
config.markup = std::move(markup);
|
||||
if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor;
|
||||
if (flags & MessageFlag::HasViews) config.viewsCount = 1;
|
||||
if (flags & MessageFlag::IsSponsored) {
|
||||
config.sponsored = true;
|
||||
}
|
||||
|
||||
createComponents(config);
|
||||
createComponents(std::move(config));
|
||||
}
|
||||
|
||||
int HistoryMessage::viewsCount() const {
|
||||
@@ -820,7 +841,7 @@ void HistoryMessage::setRepliesInboxReadTill(
|
||||
MsgId readTillId,
|
||||
std::optional<int> unreadCount) {
|
||||
if (const auto views = Get<HistoryMessageViews>()) {
|
||||
const auto newReadTillId = std::max(readTillId, 1);
|
||||
const auto newReadTillId = std::max(readTillId.bare, int64(1));
|
||||
const auto ignore = (newReadTillId < views->repliesInboxReadTillId);
|
||||
if (ignore) {
|
||||
return;
|
||||
@@ -870,7 +891,7 @@ MsgId HistoryMessage::repliesOutboxReadTill() const {
|
||||
|
||||
void HistoryMessage::setRepliesOutboxReadTill(MsgId readTillId) {
|
||||
if (const auto views = Get<HistoryMessageViews>()) {
|
||||
const auto newReadTillId = std::max(readTillId, 1);
|
||||
const auto newReadTillId = std::max(readTillId.bare, int64(1));
|
||||
if (newReadTillId > views->repliesOutboxReadTillId) {
|
||||
views->repliesOutboxReadTillId = newReadTillId;
|
||||
if (!repliesAreComments()) {
|
||||
@@ -1031,7 +1052,7 @@ bool HistoryMessage::uploading() const {
|
||||
return _media && _media->uploading();
|
||||
}
|
||||
|
||||
void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||
void HistoryMessage::createComponents(CreateConfig &&config) {
|
||||
uint64 mask = 0;
|
||||
if (config.replyTo) {
|
||||
mask |= HistoryMessageReply::Bit();
|
||||
@@ -1059,15 +1080,14 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||
if (config.editDate != TimeId(0)) {
|
||||
mask |= HistoryMessageEdited::Bit();
|
||||
}
|
||||
if (config.sponsored) {
|
||||
mask |= HistoryMessageSponsored::Bit();
|
||||
}
|
||||
if (config.originalDate != 0) {
|
||||
mask |= HistoryMessageForwarded::Bit();
|
||||
}
|
||||
if (config.mtpMarkup) {
|
||||
// optimization: don't create markup component for the case
|
||||
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
|
||||
if (config.mtpMarkup->type() != mtpc_replyKeyboardHide || config.mtpMarkup->c_replyKeyboardHide().vflags().v != 0) {
|
||||
mask |= HistoryMessageReplyMarkup::Bit();
|
||||
}
|
||||
if (!config.markup.isTrivial()) {
|
||||
mask |= HistoryMessageReplyMarkup::Bit();
|
||||
} else if (config.inlineMarkup) {
|
||||
mask |= HistoryMessageReplyMarkup::Bit();
|
||||
}
|
||||
@@ -1095,7 +1115,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||
setViewsCount(config.viewsCount);
|
||||
if (config.mtpReplies) {
|
||||
setReplies(*config.mtpReplies);
|
||||
} else if (isSending() && !config.mtpMarkup) {
|
||||
} else if (isSending() && config.markup.isNull()) {
|
||||
if (const auto broadcast = history()->peer->asBroadcast()) {
|
||||
if (const auto linked = broadcast->linkedChat()) {
|
||||
setReplies(MTP_messageReplies(
|
||||
@@ -1123,14 +1143,18 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||
}
|
||||
setupForwardedComponent(config);
|
||||
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
if (config.mtpMarkup) {
|
||||
markup->create(*config.mtpMarkup);
|
||||
if (!config.markup.isTrivial()) {
|
||||
markup->updateData(std::move(config.markup));
|
||||
} else if (config.inlineMarkup) {
|
||||
markup->create(*config.inlineMarkup);
|
||||
markup->createForwarded(*config.inlineMarkup);
|
||||
}
|
||||
if (markup->flags & ReplyMarkupFlag::HasSwitchInlineButton) {
|
||||
if (markup->data.flags & ReplyMarkupFlag::HasSwitchInlineButton) {
|
||||
_flags |= MessageFlag::HasSwitchInlineButton;
|
||||
}
|
||||
} else if (!config.markup.isNull()) {
|
||||
_flags |= MessageFlag::HasReplyMarkup;
|
||||
} else {
|
||||
_flags &= ~MessageFlag::HasReplyMarkup;
|
||||
}
|
||||
const auto from = displayFrom();
|
||||
_fromNameVersion = from ? from->nameVersion : 1;
|
||||
@@ -1341,7 +1365,7 @@ std::unique_ptr<Data::Media> HistoryMessage::CreateMedia(
|
||||
|
||||
void HistoryMessage::replaceBuyWithReceiptInMarkup() {
|
||||
if (const auto markup = inlineReplyMarkup()) {
|
||||
for (auto &row : markup->rows) {
|
||||
for (auto &row : markup->data.rows) {
|
||||
for (auto &button : row) {
|
||||
if (button.type == HistoryMessageMarkupButton::Type::Buy) {
|
||||
const auto receipt = tr::lng_payments_receipt_button(tr::now);
|
||||
@@ -1388,7 +1412,7 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
|
||||
&history()->session(),
|
||||
message.ventities().value_or_empty())
|
||||
};
|
||||
setReplyMarkup(message.vreply_markup());
|
||||
setReplyMarkup(HistoryMessageMarkupData(message.vreply_markup()));
|
||||
if (!isLocalUpdateMedia()) {
|
||||
refreshMedia(message.vmedia());
|
||||
}
|
||||
@@ -1415,7 +1439,7 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
|
||||
void HistoryMessage::applyEdition(const MTPDmessageService &message) {
|
||||
if (message.vaction().type() == mtpc_messageActionHistoryClear) {
|
||||
const auto wasGrouped = history()->owner().groups().isGrouped(this);
|
||||
setReplyMarkup(nullptr);
|
||||
setReplyMarkup({});
|
||||
refreshMedia(nullptr);
|
||||
setEmptyText();
|
||||
setViewsCount(-1);
|
||||
@@ -1464,6 +1488,10 @@ void HistoryMessage::updateForwardedInfo(const MTPMessageFwdHeader *fwd) {
|
||||
});
|
||||
}
|
||||
|
||||
void HistoryMessage::updateReplyMarkup(HistoryMessageMarkupData &&markup) {
|
||||
setReplyMarkup(std::move(markup));
|
||||
}
|
||||
|
||||
void HistoryMessage::contributeToSlowmode(TimeId realDate) {
|
||||
if (const auto channel = history()->peer->asChannel()) {
|
||||
if (out() && IsServerMsgId(id)) {
|
||||
@@ -1610,14 +1638,14 @@ void HistoryMessage::checkIsolatedEmoji() {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
|
||||
void HistoryMessage::setReplyMarkup(HistoryMessageMarkupData &&markup) {
|
||||
const auto requestUpdate = [&] {
|
||||
history()->owner().requestItemResize(this);
|
||||
history()->session().changes().messageUpdated(
|
||||
this,
|
||||
Data::MessageUpdate::Flag::ReplyMarkup);
|
||||
};
|
||||
if (!markup) {
|
||||
if (markup.isNull()) {
|
||||
if (_flags & MessageFlag::HasReplyMarkup) {
|
||||
_flags &= ~MessageFlag::HasReplyMarkup;
|
||||
if (Has<HistoryMessageReplyMarkup>()) {
|
||||
@@ -1630,8 +1658,7 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
|
||||
|
||||
// optimization: don't create markup component for the case
|
||||
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
|
||||
if (markup->type() == mtpc_replyKeyboardHide
|
||||
&& markup->c_replyKeyboardHide().vflags().v == 0) {
|
||||
if (markup.isTrivial()) {
|
||||
bool changed = false;
|
||||
if (Has<HistoryMessageReplyMarkup>()) {
|
||||
RemoveComponents(HistoryMessageReplyMarkup::Bit());
|
||||
@@ -1651,7 +1678,7 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
|
||||
if (!Has<HistoryMessageReplyMarkup>()) {
|
||||
AddComponents(HistoryMessageReplyMarkup::Bit());
|
||||
}
|
||||
Get<HistoryMessageReplyMarkup>()->create(*markup);
|
||||
Get<HistoryMessageReplyMarkup>()->updateData(std::move(markup));
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
@@ -1747,10 +1774,15 @@ void HistoryMessage::setReplies(const MTPMessageReplies &data) {
|
||||
const auto channelId = ChannelId(
|
||||
data.vchannel_id().value_or_empty());
|
||||
const auto readTillId = data.vread_max_id()
|
||||
? std::max(
|
||||
{ views->repliesInboxReadTillId, data.vread_max_id()->v, 1 })
|
||||
? std::max({
|
||||
views->repliesInboxReadTillId.bare,
|
||||
int64(data.vread_max_id()->v),
|
||||
int64(1),
|
||||
})
|
||||
: views->repliesInboxReadTillId;
|
||||
const auto maxId = data.vmax_id().value_or(views->repliesMaxId);
|
||||
const auto maxId = data.vmax_id()
|
||||
? data.vmax_id()->v
|
||||
: views->repliesMaxId;
|
||||
const auto countsChanged = (views->replies.count != count)
|
||||
|| (views->repliesInboxReadTillId != readTillId)
|
||||
|| (views->repliesMaxId != maxId);
|
||||
|
||||
@@ -21,6 +21,7 @@ class Message;
|
||||
struct HistoryMessageEdited;
|
||||
struct HistoryMessageReply;
|
||||
struct HistoryMessageViews;
|
||||
struct HistoryMessageMarkupData;
|
||||
|
||||
[[nodiscard]] Fn<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
|
||||
not_null<HistoryItem*> item);
|
||||
@@ -48,10 +49,12 @@ class HistoryMessage final : public HistoryItem {
|
||||
public:
|
||||
HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessage &data,
|
||||
MessageFlags localFlags);
|
||||
HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessageService &data,
|
||||
MessageFlags localFlags);
|
||||
HistoryMessage(
|
||||
@@ -73,7 +76,7 @@ public:
|
||||
const QString &postAuthor,
|
||||
const TextWithEntities &textWithEntities,
|
||||
const MTPMessageMedia &media,
|
||||
const MTPReplyMarkup &markup,
|
||||
HistoryMessageMarkupData &&markup,
|
||||
uint64 groupedId); // local message
|
||||
HistoryMessage(
|
||||
not_null<History*> history,
|
||||
@@ -86,7 +89,7 @@ public:
|
||||
const QString &postAuthor,
|
||||
not_null<DocumentData*> document,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup); // local document
|
||||
HistoryMessageMarkupData &&markup); // local document
|
||||
HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
@@ -98,7 +101,7 @@ public:
|
||||
const QString &postAuthor,
|
||||
not_null<PhotoData*> photo,
|
||||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup); // local photo
|
||||
HistoryMessageMarkupData &&markup); // local photo
|
||||
HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
@@ -109,7 +112,7 @@ public:
|
||||
PeerId from,
|
||||
const QString &postAuthor,
|
||||
not_null<GameData*> game,
|
||||
const MTPReplyMarkup &markup); // local game
|
||||
HistoryMessageMarkupData &&markup); // local game
|
||||
|
||||
void refreshMedia(const MTPMessageMedia *media);
|
||||
void refreshSentMedia(const MTPMessageMedia *media);
|
||||
@@ -162,9 +165,7 @@ public:
|
||||
void updateSentContent(
|
||||
const TextWithEntities &textWithEntities,
|
||||
const MTPMessageMedia *media) override;
|
||||
void updateReplyMarkup(const MTPReplyMarkup *markup) override {
|
||||
setReplyMarkup(markup);
|
||||
}
|
||||
void updateReplyMarkup(HistoryMessageMarkupData &&markup) override;
|
||||
void updateForwardedInfo(const MTPMessageFwdHeader *fwd) override;
|
||||
void contributeToSlowmode(TimeId realDate = 0) override;
|
||||
|
||||
@@ -238,7 +239,7 @@ private:
|
||||
// It should show the receipt for the payed invoice. Still let mobile apps do that.
|
||||
void replaceBuyWithReceiptInMarkup();
|
||||
|
||||
void setReplyMarkup(const MTPReplyMarkup *markup);
|
||||
void setReplyMarkup(HistoryMessageMarkupData &&markup);
|
||||
|
||||
struct CreateConfig;
|
||||
void createComponentsHelper(
|
||||
@@ -246,8 +247,8 @@ private:
|
||||
MsgId replyTo,
|
||||
UserId viaBotId,
|
||||
const QString &postAuthor,
|
||||
const MTPReplyMarkup &markup);
|
||||
void createComponents(const CreateConfig &config);
|
||||
HistoryMessageMarkupData &&markup);
|
||||
void createComponents(CreateConfig &&config);
|
||||
void setupForwardedComponent(const CreateConfig &config);
|
||||
void changeReplyToTopCounter(
|
||||
not_null<HistoryMessageReply*> reply,
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace {
|
||||
|
||||
constexpr auto kPinnedMessageTextLimit = 16;
|
||||
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
[[nodiscard]] bool PeerCallKnown(not_null<PeerData*> peer) {
|
||||
if (peer->groupCall() != nullptr) {
|
||||
return true;
|
||||
@@ -866,11 +868,12 @@ HistoryService::PreparedText HistoryService::prepareCallScheduledText(
|
||||
|
||||
HistoryService::HistoryService(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessage &data,
|
||||
MessageFlags localFlags)
|
||||
: HistoryItem(
|
||||
history,
|
||||
data.vid().v,
|
||||
id,
|
||||
FlagsFromMTP(data.vflags().v) | localFlags,
|
||||
data.vdate().v,
|
||||
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
|
||||
@@ -880,11 +883,12 @@ HistoryService::HistoryService(
|
||||
|
||||
HistoryService::HistoryService(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessageService &data,
|
||||
MessageFlags localFlags)
|
||||
: HistoryItem(
|
||||
history,
|
||||
data.vid().v,
|
||||
id,
|
||||
FlagsFromMTP(data.vflags().v) | localFlags,
|
||||
data.vdate().v,
|
||||
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
|
||||
@@ -921,14 +925,22 @@ bool HistoryService::needCheck() const {
|
||||
return out() && !isEmpty();
|
||||
}
|
||||
|
||||
QString HistoryService::inDialogsText(DrawInDialog way) const {
|
||||
return textcmdLink(1, TextUtilities::Clean(notificationText()));
|
||||
ItemPreview HistoryService::toPreview(ToPreviewOptions options) const {
|
||||
// Don't show for service messages (chat photo changed).
|
||||
// Because larger version is shown exactly to the left of the preview.
|
||||
//auto media = _media ? _media->toPreview(options) : ItemPreview();
|
||||
return {
|
||||
.text = textcmdLink(1, TextUtilities::Clean(notificationText())),
|
||||
//.images = std::move(media.images),
|
||||
//.loadingContext = std::move(media.loadingContext),
|
||||
};
|
||||
}
|
||||
|
||||
QString HistoryService::inReplyText() const {
|
||||
const auto result = HistoryService::notificationText();
|
||||
const auto text = result.trimmed().startsWith(author()->name)
|
||||
? result.trimmed().mid(author()->name.size()).trimmed()
|
||||
const auto &name = author()->name;
|
||||
const auto text = result.trimmed().startsWith(name)
|
||||
? result.trimmed().mid(name.size()).trimmed()
|
||||
: result;
|
||||
return textcmdLink(1, text);
|
||||
}
|
||||
@@ -1188,19 +1200,7 @@ void HistoryService::updateDependentText() {
|
||||
void HistoryService::updateText(PreparedText &&text) {
|
||||
setServiceText(text);
|
||||
history()->owner().requestItemResize(this);
|
||||
const auto inDialogsHistory = history()->migrateToOrMe();
|
||||
if (inDialogsHistory->textCachedFor == this) {
|
||||
inDialogsHistory->textCachedFor = nullptr;
|
||||
}
|
||||
//if (const auto feed = history()->peer->feed()) { // #TODO archive
|
||||
// if (feed->textCachedFor == this) {
|
||||
// feed->textCachedFor = nullptr;
|
||||
// feed->updateChatListEntry();
|
||||
// }
|
||||
//}
|
||||
history()->session().changes().messageUpdated(
|
||||
this,
|
||||
Data::MessageUpdate::Flag::DialogRowRepaint);
|
||||
invalidateChatListEntry();
|
||||
history()->owner().updateDependentMessages(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,12 @@ public:
|
||||
|
||||
HistoryService(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessage &data,
|
||||
MessageFlags localFlags);
|
||||
HistoryService(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
const MTPDmessageService &data,
|
||||
MessageFlags localFlags);
|
||||
HistoryService(
|
||||
@@ -109,7 +111,7 @@ public:
|
||||
bool serviceMsg() const override {
|
||||
return true;
|
||||
}
|
||||
QString inDialogsText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString inReplyText() const override;
|
||||
|
||||
std::unique_ptr<HistoryView::Element> createView(
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/chat/choose_theme_controller.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/inner_dropdown.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
@@ -55,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_group_call.h"
|
||||
@@ -108,6 +110,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/continuous_scroll.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/item_text_options.h"
|
||||
#include "ui/unread_badge.h"
|
||||
@@ -251,9 +254,22 @@ HistoryWidget::HistoryWidget(
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
connect(_scroll, &Ui::ScrollArea::scrolled, [=] {
|
||||
_scroll->scrolls(
|
||||
) | rpl::start_with_next([=] {
|
||||
handleScroll();
|
||||
});
|
||||
}, lifetime());
|
||||
_scroll->geometryChanged(
|
||||
) | rpl::start_with_next(crl::guard(_list, [=] {
|
||||
_list->onParentGeometryChanged();
|
||||
}), lifetime());
|
||||
_scroll->addContentRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_history->loadedAtBottom()
|
||||
&& session().data().sponsoredMessages().append(_history)) {
|
||||
_scroll->contentAdded();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_historyDown->addClickHandler([=] { historyDownClicked(); });
|
||||
_unreadMentions->addClickHandler([=] { showNextUnreadMention(); });
|
||||
_fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); });
|
||||
@@ -1171,7 +1187,7 @@ void HistoryWidget::checkNextHighlight() {
|
||||
return msgId;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return MsgId();
|
||||
}();
|
||||
if (!nextHighlight) {
|
||||
return;
|
||||
@@ -1282,11 +1298,39 @@ void HistoryWidget::insertHashtagOrBotCommand(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
|
||||
return (isChoosingTheme() || _editMsgId)
|
||||
? InlineBotQuery()
|
||||
: ParseInlineBotQuery(&session(), _field);
|
||||
}
|
||||
|
||||
AutocompleteQuery HistoryWidget::parseMentionHashtagBotCommandQuery() const {
|
||||
const auto result = (isChoosingTheme()
|
||||
|| (_inlineBot && !_inlineLookingUpBot))
|
||||
? AutocompleteQuery()
|
||||
: ParseMentionHashtagBotCommandQuery(_field);
|
||||
if (result.query.isEmpty()) {
|
||||
return result;
|
||||
} else if (result.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (result.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (result.query[0] == '/'
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot()) || _editMsgId)) {
|
||||
return AutocompleteQuery();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HistoryWidget::updateInlineBotQuery() {
|
||||
if (!_history) {
|
||||
return;
|
||||
}
|
||||
const auto query = ParseInlineBotQuery(&session(), _field);
|
||||
const auto query = parseInlineBotQuery();
|
||||
if (_inlineBotUsername != query.username) {
|
||||
_inlineBotUsername = query.username;
|
||||
if (_inlineBotResolveRequestId) {
|
||||
@@ -1369,10 +1413,11 @@ void HistoryWidget::orderWidgets() {
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->raise();
|
||||
}
|
||||
_topShadow->raise();
|
||||
if (_fieldAutocomplete) {
|
||||
_fieldAutocomplete->raise();
|
||||
if (_chooseTheme) {
|
||||
_chooseTheme->raise();
|
||||
}
|
||||
_topShadow->raise();
|
||||
_fieldAutocomplete->raise();
|
||||
if (_membersDropdown) {
|
||||
_membersDropdown->raise();
|
||||
}
|
||||
@@ -1410,6 +1455,37 @@ bool HistoryWidget::updateStickersByEmoji() {
|
||||
return (emoji != nullptr);
|
||||
}
|
||||
|
||||
void HistoryWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
|
||||
const auto update = [=] {
|
||||
updateInlineBotQuery();
|
||||
updateControlsGeometry();
|
||||
updateControlsVisibility();
|
||||
};
|
||||
if (peer.get() != _peer) {
|
||||
return;
|
||||
} else if (_chooseTheme) {
|
||||
if (isChoosingTheme()) {
|
||||
const auto was = base::take(_chooseTheme);
|
||||
if (Ui::InFocusChain(this)) {
|
||||
setInnerFocus();
|
||||
}
|
||||
update();
|
||||
}
|
||||
return;
|
||||
} else if (_voiceRecordBar->isActive()) {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { tr::lng_chat_theme_cant_voice(tr::now) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
_chooseTheme = std::make_unique<Ui::ChooseThemeController>(
|
||||
this,
|
||||
controller(),
|
||||
peer);
|
||||
_chooseTheme->shouldBeShownValue(
|
||||
) | rpl::start_with_next(update, _chooseTheme->lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::fieldChanged() {
|
||||
const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping);
|
||||
|
||||
@@ -1556,7 +1632,9 @@ void HistoryWidget::setInnerFocus() {
|
||||
if (_scroll->isHidden()) {
|
||||
setFocus();
|
||||
} else if (_list) {
|
||||
if (_nonEmptySelection
|
||||
if (_chooseTheme && _chooseTheme->shouldBeShown()) {
|
||||
_chooseTheme->setFocus();
|
||||
} else if (_nonEmptySelection
|
||||
|| (_list && _list->wasSelectedText())
|
||||
|| isRecording()
|
||||
|| isBotStart()
|
||||
@@ -1898,6 +1976,8 @@ void HistoryWidget::showHistory(
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
session().data().sponsoredMessages().clearItems(_history);
|
||||
}
|
||||
session().sendProgressManager().update(
|
||||
_history,
|
||||
@@ -1925,6 +2005,7 @@ void HistoryWidget::showHistory(
|
||||
_pinnedTracker = nullptr;
|
||||
_groupCallBar = nullptr;
|
||||
_groupCallTracker = nullptr;
|
||||
_chooseTheme = nullptr;
|
||||
_membersDropdown.destroy();
|
||||
_scrollToAnimation.stop();
|
||||
|
||||
@@ -2069,8 +2150,6 @@ void HistoryWidget::showHistory(
|
||||
|
||||
updateControlsGeometry();
|
||||
|
||||
connect(_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));
|
||||
|
||||
if (const auto user = _peer->asUser()) {
|
||||
if (const auto &info = user->botInfo) {
|
||||
if (startBot) {
|
||||
@@ -2102,6 +2181,14 @@ void HistoryWidget::showHistory(
|
||||
}
|
||||
unreadCountUpdated(); // set _historyDown badge.
|
||||
showAboutTopPromotion();
|
||||
|
||||
{
|
||||
const auto hasSponsored = _history->canHaveSponsoredMessages();
|
||||
_scroll->setTrackingContent(hasSponsored);
|
||||
if (hasSponsored) {
|
||||
session().data().sponsoredMessages().request(_history);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_chooseForReport = nullptr;
|
||||
refreshTopBarActiveChat();
|
||||
@@ -2322,52 +2409,42 @@ void HistoryWidget::updateControlsVisibility() {
|
||||
if (_contactStatus) {
|
||||
_contactStatus->show();
|
||||
}
|
||||
if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart() || isReportMessages())) {
|
||||
if (isReportMessages()) {
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_reportMessages->isHidden()) {
|
||||
_reportMessages->clearState();
|
||||
_reportMessages->show();
|
||||
}
|
||||
if (isChoosingTheme()
|
||||
|| (!editingMessage()
|
||||
&& (isBlocked()
|
||||
|| isJoinChannel()
|
||||
|| isMuteUnmute()
|
||||
|| isBotStart()
|
||||
|| isReportMessages()))) {
|
||||
const auto toggle = [&](Ui::FlatButton *shown) {
|
||||
const auto toggleOne = [&](not_null<Ui::FlatButton*> button) {
|
||||
if (button.get() != shown) {
|
||||
button->hide();
|
||||
} else if (button->isHidden()) {
|
||||
button->clearState();
|
||||
button->show();
|
||||
}
|
||||
};
|
||||
toggleOne(_reportMessages);
|
||||
toggleOne(_joinChannel);
|
||||
toggleOne(_muteUnmute);
|
||||
toggleOne(_botStart);
|
||||
toggleOne(_unblock);
|
||||
};
|
||||
if (isChoosingTheme()) {
|
||||
_chooseTheme->show();
|
||||
setInnerFocus();
|
||||
toggle(nullptr);
|
||||
} else if (isReportMessages()) {
|
||||
toggle(_reportMessages);
|
||||
} else if (isBlocked()) {
|
||||
_reportMessages->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_unblock->isHidden()) {
|
||||
_unblock->clearState();
|
||||
_unblock->show();
|
||||
}
|
||||
toggle(_unblock);
|
||||
} else if (isJoinChannel()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_joinChannel->isHidden()) {
|
||||
_joinChannel->clearState();
|
||||
_joinChannel->show();
|
||||
}
|
||||
toggle(_joinChannel);
|
||||
} else if (isMuteUnmute()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_botStart->hide();
|
||||
if (_muteUnmute->isHidden()) {
|
||||
_muteUnmute->clearState();
|
||||
_muteUnmute->show();
|
||||
}
|
||||
toggle(_muteUnmute);
|
||||
} else if (isBotStart()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
if (_botStart->isHidden()) {
|
||||
_botStart->clearState();
|
||||
_botStart->show();
|
||||
}
|
||||
toggle(_botStart);
|
||||
}
|
||||
_kbShown = false;
|
||||
_fieldAutocomplete->hide();
|
||||
@@ -2559,6 +2636,16 @@ void HistoryWidget::newItemAdded(not_null<HistoryItem*> item) {
|
||||
|| item->isScheduled()) {
|
||||
return;
|
||||
}
|
||||
if (item->isSponsored()) {
|
||||
if (const auto view = item->mainView()) {
|
||||
view->resizeGetHeight(width());
|
||||
updateHistoryGeometry(
|
||||
false,
|
||||
true,
|
||||
{ ScrollChangeNoJumpToBottom, 0 });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get here in non-resized state we can't rely on results of
|
||||
// doWeReadServerHistory() and mark chat as read.
|
||||
@@ -2790,7 +2877,7 @@ void HistoryWidget::firstLoadMessages() {
|
||||
}
|
||||
|
||||
auto from = _history;
|
||||
auto offsetId = 0;
|
||||
auto offsetId = MsgId();
|
||||
auto offset = 0;
|
||||
auto loadCount = kMessagesPerPage;
|
||||
if (_showAtMsgId == ShowAtUnreadMsgId) {
|
||||
@@ -2964,7 +3051,7 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
|
||||
_delayedShowAtMsgId = showAtMsgId;
|
||||
|
||||
auto from = _history;
|
||||
auto offsetId = 0;
|
||||
auto offsetId = MsgId();
|
||||
auto offset = 0;
|
||||
auto loadCount = kMessagesPerPage;
|
||||
if (_delayedShowAtMsgId == ShowAtUnreadMsgId) {
|
||||
@@ -3289,6 +3376,9 @@ void HistoryWidget::hideChildWidgets() {
|
||||
if (_voiceRecordBar) {
|
||||
_voiceRecordBar->hideFast();
|
||||
}
|
||||
if (_chooseTheme) {
|
||||
_chooseTheme->hide();
|
||||
}
|
||||
hideChildren();
|
||||
}
|
||||
|
||||
@@ -3908,7 +3998,7 @@ void HistoryWidget::inlineBotResolveDone(
|
||||
}();
|
||||
session().data().processChats(data.vchats());
|
||||
|
||||
const auto query = ParseInlineBotQuery(&session(), _field);
|
||||
const auto query = parseInlineBotQuery();
|
||||
if (_inlineBotUsername == query.username) {
|
||||
applyInlineBotQuery(
|
||||
query.lookingUpBot ? resolvedBot : query.bot,
|
||||
@@ -3953,6 +4043,10 @@ bool HistoryWidget::isJoinChannel() const {
|
||||
return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
|
||||
}
|
||||
|
||||
bool HistoryWidget::isChoosingTheme() const {
|
||||
return _chooseTheme && _chooseTheme->shouldBeShown();
|
||||
}
|
||||
|
||||
bool HistoryWidget::isMuteUnmute() const {
|
||||
return _peer
|
||||
&& ((_peer->isBroadcast() && !_peer->asChannel()->canPublish())
|
||||
@@ -4337,24 +4431,7 @@ void HistoryWidget::checkFieldAutocomplete() {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto isInlineBot = _inlineBot && !_inlineLookingUpBot;
|
||||
const auto autocomplete = isInlineBot
|
||||
? AutocompleteQuery()
|
||||
: ParseMentionHashtagBotCommandQuery(_field);
|
||||
if (!autocomplete.query.isEmpty()) {
|
||||
if (autocomplete.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '/'
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|
||||
|| _editMsgId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto autocomplete = parseMentionHashtagBotCommandQuery();
|
||||
_fieldAutocomplete->showFiltered(
|
||||
_peer,
|
||||
autocomplete.query,
|
||||
@@ -4922,7 +4999,14 @@ void HistoryWidget::updateHistoryGeometry(
|
||||
if (_contactStatus) {
|
||||
newScrollHeight -= _contactStatus->height();
|
||||
}
|
||||
if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute() || isReportMessages())) {
|
||||
if (isChoosingTheme()) {
|
||||
newScrollHeight -= _chooseTheme->height();
|
||||
} else if (!editingMessage()
|
||||
&& (isBlocked()
|
||||
|| isBotStart()
|
||||
|| isJoinChannel()
|
||||
|| isMuteUnmute()
|
||||
|| isReportMessages())) {
|
||||
newScrollHeight -= _unblock->height();
|
||||
} else {
|
||||
if (editingMessage() || _canSendMessages) {
|
||||
@@ -6136,16 +6220,17 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
|
||||
}
|
||||
|
||||
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
controller()->show(
|
||||
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
if (const auto media = item->media()) {
|
||||
if (media->allowsEditCaption()) {
|
||||
controller()->show(Box<EditCaptionBox>(controller(), item));
|
||||
return;
|
||||
}
|
||||
} else if (_chooseTheme) {
|
||||
toggleChooseChatTheme(_peer);
|
||||
} else if (_voiceRecordBar->isActive()) {
|
||||
controller()->show(
|
||||
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRecording()) {
|
||||
@@ -6798,11 +6883,15 @@ void HistoryWidget::updateForwardingTexts() {
|
||||
}
|
||||
|
||||
if (count < 2) {
|
||||
text = _toForward.items.front()->inDialogsText(keepCaptions
|
||||
? HistoryItem::DrawInDialog::WithoutSender
|
||||
: HistoryItem::DrawInDialog::WithoutSenderAndCaption);
|
||||
text = _toForward.items.front()->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = !keepCaptions,
|
||||
.generateImages = false,
|
||||
}).text;
|
||||
} else {
|
||||
text = textcmdLink(1, tr::lng_forward_messages(tr::now, lt_count, count));
|
||||
text = textcmdLink(
|
||||
1,
|
||||
tr::lng_forward_messages(tr::now, lt_count, count));
|
||||
}
|
||||
}
|
||||
_toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions());
|
||||
|
||||
@@ -25,6 +25,8 @@ struct FileLoadResult;
|
||||
struct SendingAlbum;
|
||||
enum class SendMediaType;
|
||||
class MessageLinksParser;
|
||||
struct InlineBotQuery;
|
||||
struct AutocompleteQuery;
|
||||
|
||||
namespace MTP {
|
||||
class Error;
|
||||
@@ -77,6 +79,8 @@ enum class ReportReason;
|
||||
namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
class ChooseThemeController;
|
||||
class ContinuousScroll;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
@@ -237,6 +241,8 @@ public:
|
||||
void clearDelayedShowAt();
|
||||
void saveFieldToHistoryLocalDraft();
|
||||
|
||||
void toggleChooseChatTheme(not_null<PeerData*> peer);
|
||||
|
||||
void applyCloudDraft(History *history);
|
||||
|
||||
void updateHistoryDownPosition();
|
||||
@@ -454,6 +460,10 @@ private:
|
||||
std::optional<QString> writeRestriction() const;
|
||||
void orderWidgets();
|
||||
|
||||
[[nodiscard]] InlineBotQuery parseInlineBotQuery() const;
|
||||
[[nodiscard]] auto parseMentionHashtagBotCommandQuery() const
|
||||
-> AutocompleteQuery;
|
||||
|
||||
void clearInlineBot();
|
||||
void inlineBotChanged();
|
||||
|
||||
@@ -585,19 +595,21 @@ private:
|
||||
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
|
||||
void inlineBotResolveFail(const MTP::Error &error, const QString &username);
|
||||
|
||||
bool isRecording() const;
|
||||
[[nodiscard]] bool isRecording() const;
|
||||
|
||||
bool isBotStart() const;
|
||||
bool isBlocked() const;
|
||||
bool isJoinChannel() const;
|
||||
bool isMuteUnmute() const;
|
||||
bool isReportMessages() const;
|
||||
[[nodiscard]] bool isBotStart() const;
|
||||
[[nodiscard]] bool isBlocked() const;
|
||||
[[nodiscard]] bool isJoinChannel() const;
|
||||
[[nodiscard]] bool isMuteUnmute() const;
|
||||
[[nodiscard]] bool isReportMessages() const;
|
||||
bool updateCmdStartShown();
|
||||
void updateSendButtonType();
|
||||
bool showRecordButton() const;
|
||||
bool showInlineBotCancel() const;
|
||||
[[nodiscard]] bool showRecordButton() const;
|
||||
[[nodiscard]] bool showInlineBotCancel() const;
|
||||
void refreshSilentToggle();
|
||||
|
||||
[[nodiscard]] bool isChoosingTheme() const;
|
||||
|
||||
void setupScheduledToggle();
|
||||
void refreshScheduledToggle();
|
||||
|
||||
@@ -664,7 +676,7 @@ private:
|
||||
int _delayedShowAtRequest = 0; // Not real mtpRequestId.
|
||||
|
||||
object_ptr<HistoryView::TopBarWidget> _topBar;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
object_ptr<Ui::ContinuousScroll> _scroll;
|
||||
QPointer<HistoryInner> _list;
|
||||
History *_migrated = nullptr;
|
||||
History *_history = nullptr;
|
||||
@@ -689,7 +701,7 @@ private:
|
||||
bool _unreadMentionsIsShown = false;
|
||||
object_ptr<Ui::HistoryDownButton> _unreadMentions;
|
||||
|
||||
object_ptr<FieldAutocomplete> _fieldAutocomplete;
|
||||
const object_ptr<FieldAutocomplete> _fieldAutocomplete;
|
||||
object_ptr<Support::Autocomplete> _supportAutocomplete;
|
||||
std::unique_ptr<MessageLinksParser> _fieldLinksParser;
|
||||
|
||||
@@ -726,6 +738,8 @@ private:
|
||||
object_ptr<Ui::ScrollArea> _kbScroll;
|
||||
const not_null<BotKeyboard*> _keyboard;
|
||||
|
||||
std::unique_ptr<Ui::ChooseThemeController> _chooseTheme;
|
||||
|
||||
object_ptr<Ui::InnerDropdown> _membersDropdown = { nullptr };
|
||||
base::Timer _membersDropdownShowTimer;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user