Merge tag 'v4.14.1' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/data/data_stories.cpp
#	Telegram/SourceFiles/history/history_item.cpp
#	Telegram/SourceFiles/main/main_session.cpp
#	Telegram/lib_ui
This commit is contained in:
ZavaruKitsu 2024-01-02 01:02:43 +03:00
commit 7cdbbc318a
200 changed files with 3475 additions and 551 deletions

View file

@ -94,6 +94,7 @@ jobs:
-D CMAKE_CXX_FLAGS="-Werror" \ -D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_EXE_LINKER_FLAGS="-s" \ -D CMAKE_EXE_LINKER_FLAGS="-s" \
-D TDESKTOP_API_TEST=ON \ -D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE $DEFINE
@ -115,7 +116,7 @@ jobs:
run: | run: |
cd $REPO_NAME/out/Debug cd $REPO_NAME/out/Debug
mkdir artifact mkdir artifact
mv Telegram artifact/ mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@master - uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true' if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact. name: Upload artifact.

View file

@ -115,6 +115,7 @@ jobs:
-D CMAKE_CXX_FLAGS="-Werror" \ -D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \ -D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
-D TDESKTOP_API_TEST=ON \ -D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE $DEFINE

View file

@ -169,6 +169,7 @@ jobs:
%TDESKTOP_BUILD_GENERATOR% ^ %TDESKTOP_BUILD_GENERATOR% ^
%TDESKTOP_BUILD_ARCH% ^ %TDESKTOP_BUILD_ARCH% ^
%TDESKTOP_BUILD_API% ^ %TDESKTOP_BUILD_API% ^
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
-D DESKTOP_APP_NO_PDB=ON ^ -D DESKTOP_APP_NO_PDB=ON ^
%TDESKTOP_BUILD_DEFINE% %TDESKTOP_BUILD_DEFINE%
@ -178,8 +179,10 @@ jobs:
- name: Move artifact. - name: Move artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly') if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
run: | run: |
set OUT=%TBUILD%\%REPO_NAME%\out\Debug
mkdir artifact mkdir artifact
move %TBUILD%\%REPO_NAME%\out\Debug\Telegram.exe artifact/ move %OUT%\Telegram.exe artifact/
move %OUT%\Updater.exe artifact/
- uses: actions/upload-artifact@master - uses: actions/upload-artifact@master
name: Upload artifact. name: Upload artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly') if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')

2
LEGAL
View file

@ -1,7 +1,7 @@
This file is part of Telegram Desktop, This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service. the official desktop application for the Telegram messaging service.
Copyright (c) 2014-2023 The Telegram Desktop Authors. Copyright (c) 2014-2024 The Telegram Desktop Authors.
Telegram Desktop is free software: you can redistribute it and/or modify Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View file

@ -620,6 +620,10 @@ PRIVATE
data/data_replies_list.h data/data_replies_list.h
data/data_reply_preview.cpp data/data_reply_preview.cpp
data/data_reply_preview.h data/data_reply_preview.h
data/data_saved_messages.cpp
data/data_saved_messages.h
data/data_saved_sublist.cpp
data/data_saved_sublist.h
data/data_search_controller.cpp data/data_search_controller.cpp
data/data_search_controller.h data/data_search_controller.h
data/data_send_action.cpp data/data_send_action.cpp
@ -866,6 +870,8 @@ PRIVATE
history/view/history_view_sponsored_click_handler.h history/view/history_view_sponsored_click_handler.h
history/view/history_view_sticker_toast.cpp history/view/history_view_sticker_toast.cpp
history/view/history_view_sticker_toast.h history/view/history_view_sticker_toast.h
history/view/history_view_sublist_section.cpp
history/view/history_view_sublist_section.h
history/view/history_view_transcribe_button.cpp history/view/history_view_transcribe_button.cpp
history/view/history_view_transcribe_button.h history/view/history_view_transcribe_button.h
history/view/history_view_translate_bar.cpp history/view/history_view_translate_bar.cpp
@ -967,6 +973,8 @@ PRIVATE
info/profile/info_profile_values.h info/profile/info_profile_values.h
info/profile/info_profile_widget.cpp info/profile/info_profile_widget.cpp
info/profile/info_profile_widget.h info/profile/info_profile_widget.h
info/saved/info_saved_sublists_widget.cpp
info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp info/settings/info_settings_widget.cpp
info/settings/info_settings_widget.h info/settings/info_settings_widget.h
info/similar_channels/info_similar_channels_widget.cpp info/similar_channels/info_similar_channels_widget.cpp

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

View file

@ -395,6 +395,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dlg_new_bot_name" = "Bot name"; "lng_dlg_new_bot_name" = "Bot name";
"lng_no_chats" = "Your chats will be here"; "lng_no_chats" = "Your chats will be here";
"lng_no_chats_filter" = "No chats currently belong to this folder."; "lng_no_chats_filter" = "No chats currently belong to this folder.";
"lng_no_saved_sublists" = "You can save messages from other chats here.";
"lng_contacts_loading" = "Loading..."; "lng_contacts_loading" = "Loading...";
"lng_contacts_not_found" = "No contacts found"; "lng_contacts_not_found" = "No contacts found";
"lng_topics_not_found" = "No topics found."; "lng_topics_not_found" = "No topics found.";
@ -1187,6 +1188,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_common_groups#other" = "{count} groups in common";
"lng_profile_similar_channels#one" = "{count} similar channel"; "lng_profile_similar_channels#one" = "{count} similar channel";
"lng_profile_similar_channels#other" = "{count} similar channels"; "lng_profile_similar_channels#other" = "{count} similar channels";
"lng_profile_saved_messages#one" = "{count} saved message";
"lng_profile_saved_messages#other" = "{count} saved messages";
"lng_profile_participants_section" = "Members"; "lng_profile_participants_section" = "Members";
"lng_profile_subscribers_section" = "Subscribers"; "lng_profile_subscribers_section" = "Subscribers";
"lng_profile_add_contact" = "Add Contact"; "lng_profile_add_contact" = "Add Contact";
@ -1712,6 +1715,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile."; "lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile.";
"lng_ttl_video_sent" = "You sent a self-destructing video."; "lng_ttl_video_sent" = "You sent a self-destructing video.";
"lng_ttl_video_expired" = "Video has expired"; "lng_ttl_video_expired" = "Video has expired";
"lng_ttl_voice_sent" = "You sent a self-destructing voice messsage.";
"lng_ttl_voice_expired" = "Voice message expired";
"lng_ttl_round_sent" = "You sent a self-destructing video message.";
"lng_ttl_round_expired" = "Round message expired";
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group."; "lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
"lng_profile_camera_title" = "Capture yourself"; "lng_profile_camera_title" = "Capture yourself";
@ -2492,6 +2499,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_saved_short" = "Save"; "lng_saved_short" = "Save";
"lng_saved_forward_here" = "Forward messages here for quick access"; "lng_saved_forward_here" = "Forward messages here for quick access";
"lng_saved_quote_here" = "Quote here to save"; "lng_saved_quote_here" = "Quote here to save";
"lng_saved_open_chat" = "Open Chat";
"lng_saved_open_channel" = "Open Channel";
"lng_saved_open_group" = "Open Group";
"lng_saved_about_hidden" = "Senders of this messages restricted to link their name when forwarding.";
"lng_scheduled_messages" = "Scheduled Messages"; "lng_scheduled_messages" = "Scheduled Messages";
"lng_scheduled_messages_empty" = "No scheduled messages here yet..."; "lng_scheduled_messages_empty" = "No scheduled messages here yet...";
@ -2518,6 +2529,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_comments_open_none" = "Leave a comment"; "lng_comments_open_none" = "Leave a comment";
"lng_replies_view_original" = "View in chat"; "lng_replies_view_original" = "View in chat";
"lng_replies_messages" = "Replies"; "lng_replies_messages" = "Replies";
"lng_hidden_author_messages" = "Author Hidden";
"lng_my_notes" = "My Notes";
"lng_replies_discussion_started" = "Discussion started"; "lng_replies_discussion_started" = "Discussion started";
"lng_replies_no_comments" = "No comments here yet..."; "lng_replies_no_comments" = "No comments here yet...";

View file

@ -11,5 +11,7 @@
<file alias="ttl.tgs">../../animations/ttl.tgs</file> <file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file> <file alias="discussion.tgs">../../animations/discussion.tgs</file>
<file alias="stats.tgs">../../animations/stats.tgs</file> <file alias="stats.tgs">../../animations/stats.tgs</file>
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.13.1.0" /> Version="4.14.1.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,13,1,0 FILEVERSION 4,14,1,0
PRODUCTVERSION 4,13,1,0 PRODUCTVERSION 4,14,1,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "4.13.1.0" VALUE "FileVersion", "4.14.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.13.1.0" VALUE "ProductVersion", "4.14.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,13,1,0 FILEVERSION 4,14,1,0
PRODUCTVERSION 4,13,1,0 PRODUCTVERSION 4,14,1,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "4.13.1.0" VALUE "FileVersion", "4.14.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.13.1.0" VALUE "ProductVersion", "4.14.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -6,6 +6,7 @@ For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include <windows.h> #include <windows.h>
#include <shellapi.h>
#include <array> #include <array>
#include <string> #include <string>

View file

@ -90,6 +90,7 @@ void MessagesSearch::searchRequest() {
(_from (_from
? _from->input ? _from->input
: MTP_inputPeerEmpty()), : MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_dc_options.h" #include "mtproto/mtproto_dc_options.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_chat.h" #include "data/data_chat.h"
@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_cloud_manager.h" #include "lang/lang_cloud_manager.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/history_unread_things.h" #include "history/history_unread_things.h"
#include "core/application.h" #include "core/application.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
@ -1143,6 +1145,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
? peerToMTP(_session->userPeerId()) ? peerToMTP(_session->userPeerId())
: MTP_peerUser(d.vuser_id())), : MTP_peerUser(d.vuser_id())),
MTP_peerUser(d.vuser_id()), MTP_peerUser(d.vuser_id()),
MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(d.vvia_bot_id().value_or_empty()), MTP_long(d.vvia_bot_id().value_or_empty()),
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
@ -1174,6 +1177,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
d.vid(), d.vid(),
MTP_peerUser(d.vfrom_id()), MTP_peerUser(d.vfrom_id()),
MTP_peerChat(d.vchat_id()), MTP_peerChat(d.vchat_id()),
MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(d.vvia_bot_id().value_or_empty()), MTP_long(d.vvia_bot_id().value_or_empty()),
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
@ -1234,11 +1238,12 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
item->markMediaAndMentionRead(); item->markMediaAndMentionRead();
_session->data().requestItemRepaint(item); _session->data().requestItemRepaint(item);
if (item->out() if (item->out()) {
&& item->history()->peer->isUser() const auto user = item->history()->peer->asUser();
&& !requestingDifference()) { if (user && !requestingDifference()) {
item->history()->peer->asUser()->madeAction( user->madeAction(base::unixtime::now());
base::unixtime::now()); }
ClearMediaAsExpired(item);
} }
} }
} else { } else {
@ -2236,6 +2241,16 @@ void Updates::feedUpdate(const MTPUpdate &update) {
} }
} break; } break;
case mtpc_updatePinnedSavedDialogs: {
session().data().savedMessages().apply(
update.c_updatePinnedSavedDialogs());
} break;
case mtpc_updateSavedDialogPinned: {
session().data().savedMessages().apply(
update.c_updateSavedDialogPinned());
} break;
case mtpc_updateChannel: { case mtpc_updateChannel: {
auto &d = update.c_updateChannel(); auto &d = update.c_updateChannel();
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) { if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {

View file

@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_saved_sublist.h"
#include "data/data_search_controller.h" #include "data/data_search_controller.h"
#include "data/data_scheduled_messages.h" #include "data/data_scheduled_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -444,6 +445,26 @@ void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
}).send(); }).send();
} }
void ApiWrap::savePinnedOrder(not_null<Data::SavedMessages*> saved) {
const auto &order = _session->data().pinnedChatsOrder(saved);
const auto input = [](Dialogs::Key key) {
if (const auto sublist = key.sublist()) {
return MTP_inputDialogPeer(sublist->peer()->input);
}
Unexpected("Key type in pinnedDialogsOrder().");
};
auto peers = QVector<MTPInputDialogPeer>();
peers.reserve(order.size());
ranges::transform(
order,
ranges::back_inserter(peers),
input);
request(MTPmessages_ReorderPinnedSavedDialogs(
MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force),
MTP_vector(peers)
)).send();
}
void ApiWrap::toggleHistoryArchived( void ApiWrap::toggleHistoryArchived(
not_null<History*> history, not_null<History*> history,
bool archived, bool archived,

View file

@ -34,6 +34,7 @@ class Forum;
class ForumTopic; class ForumTopic;
class Thread; class Thread;
class Story; class Story;
class SavedMessages;
} // namespace Data } // namespace Data
namespace InlineBots { namespace InlineBots {
@ -152,6 +153,7 @@ public:
void savePinnedOrder(Data::Folder *folder); void savePinnedOrder(Data::Folder *folder);
void savePinnedOrder(not_null<Data::Forum*> forum); void savePinnedOrder(not_null<Data::Forum*> forum);
void savePinnedOrder(not_null<Data::SavedMessages*> saved);
void toggleHistoryArchived( void toggleHistoryArchived(
not_null<History*> history, not_null<History*> history,
bool archived, bool archived,

View file

@ -57,7 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kMaxWallPaperSlugLength = 255; constexpr auto kMaxWallPaperSlugLength = 255;
constexpr auto kDefaultDimming = 50;
[[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) { [[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) {
if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) { if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) {

View file

@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput(
rpl::producer<QString> placeholder, rpl::producer<QString> placeholder,
const QString &val) const QString &val)
: MaskedInputField(parent, st, std::move(placeholder), val) { : MaskedInputField(parent, st, std::move(placeholder), val) {
if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) { static const auto RegExp = QRegularExpression("^[a-zA-Z0-9_\\-]+$");
if (!RegExp.match(val).hasMatch()) {
setText(QString()); setText(QString());
} }
} }
@ -831,8 +832,9 @@ void ProxyBox::prepare() {
connect(_host.data(), &HostInput::changed, [=] { connect(_host.data(), &HostInput::changed, [=] {
Ui::PostponeCall(_host, [=] { Ui::PostponeCall(_host, [=] {
const auto host = _host->getLastText().trimmed(); const auto host = _host->getLastText().trimmed();
static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q; static const auto mask = QRegularExpression(
const auto match = QRegularExpression(mask).match(host); u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q);
const auto match = mask.match(host);
if (_host->cursorPosition() == host.size() if (_host->cursorPosition() == host.size()
&& match.hasMatch()) { && match.hasMatch()) {
const auto port = match.captured(1); const auto port = match.captured(1);
@ -1107,6 +1109,10 @@ void ProxiesBoxController::ShowApplyConfirmation(
proxy.password = fields.value(u"secret"_q); proxy.password = fields.value(u"secret"_q);
} }
if (proxy) { if (proxy) {
static const auto UrlStartRegExp = QRegularExpression(
"^https://",
QRegularExpression::CaseInsensitiveOption);
static const auto UrlEndRegExp = QRegularExpression("/$");
const auto displayed = "https://" + server + "/"; const auto displayed = "https://" + server + "/";
const auto parsed = QUrl::fromUserInput(displayed); const auto parsed = QUrl::fromUserInput(displayed);
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed) const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
@ -1117,11 +1123,9 @@ void ProxiesBoxController::ShowApplyConfirmation(
const auto displayServer = QString( const auto displayServer = QString(
displayUrl displayUrl
).replace( ).replace(
QRegularExpression( UrlStartRegExp,
"^https://",
QRegularExpression::CaseInsensitiveOption),
QString() QString()
).replace(QRegularExpression("/$"), QString()); ).replace(UrlEndRegExp, QString());
const auto text = tr::lng_sure_enable_socks( const auto text = tr::lng_sure_enable_socks(
tr::now, tr::now,
lt_server, lt_server,

View file

@ -62,7 +62,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kDiscountDivider = 5.;
constexpr auto kUserpicsMax = size_t(3); constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::SubscriptionOption; using GiftOption = Data::SubscriptionOption;
@ -383,7 +382,8 @@ void GiftsBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
std::vector<not_null<UserData*>> users, std::vector<not_null<UserData*>> users,
not_null<Api::PremiumGiftCodeOptions*> api) { not_null<Api::PremiumGiftCodeOptions*> api,
const QString &ref) {
Expects(!users.empty()); Expects(!users.empty());
const auto boxWidth = st::boxWideWidth; const auto boxWidth = st::boxWideWidth;
@ -592,7 +592,7 @@ void GiftsBox(
Settings::AddSummaryPremium( Settings::AddSummaryPremium(
content, content,
controller, controller,
u"gift"_q, ref,
std::move(buttonCallback)); std::move(buttonCallback));
} }
@ -615,7 +615,7 @@ void GiftsBox(
auto raw = Settings::CreateSubscribeButton({ auto raw = Settings::CreateSubscribeButton({
controller, controller,
box, box,
[] { return u"gift"_q; }, [=] { return ref; },
rpl::combine( rpl::combine(
state->buttonText.events(), state->buttonText.events(),
state->confirmButtonBusy.value(), state->confirmButtonBusy.value(),
@ -877,7 +877,7 @@ void GiftPremiumValidator::cancel() {
_requestId = 0; _requestId = 0;
} }
void GiftPremiumValidator::showChoosePeerBox() { void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
if (_manyGiftsLifetime) { if (_manyGiftsLifetime) {
return; return;
} }
@ -937,7 +937,7 @@ void GiftPremiumValidator::showChoosePeerBox() {
}) | ranges::to<std::vector<not_null<UserData*>>>(); }) | ranges::to<std::vector<not_null<UserData*>>>();
if (!users.empty()) { if (!users.empty()) {
const auto giftBox = show->show( const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api)); Box(GiftsBox, _controller, users, api, ref));
giftBox->boxClosing( giftBox->boxClosing(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy(); _manyGiftsLifetime.destroy();

View file

@ -34,7 +34,7 @@ public:
GiftPremiumValidator(not_null<Window::SessionController*> controller); GiftPremiumValidator(not_null<Window::SessionController*> controller);
void showBox(not_null<UserData*> user); void showBox(not_null<UserData*> user);
void showChoosePeerBox(); void showChoosePeerBox(const QString &ref);
void cancel(); void cancel();
private: private:

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_premium_limits.h" #include "data/data_premium_limits.h"
@ -882,6 +883,18 @@ void PinsLimitBox(
limits.dialogsPinnedPremium(), limits.dialogsPinnedPremium(),
PinsCount(session->data().chatsList())); PinsCount(session->data().chatsList()));
} }
void SublistsPinsLimitBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session) {
const auto limits = Data::PremiumLimits(session);
SimplePinsLimitBox(
box,
session,
"saved_dialog_pinned",
limits.savedSublistsPinnedDefault(),
limits.savedSublistsPinnedPremium(),
PinsCount(session->data().savedMessages().chatsList()));
}
void ForumPinsLimitBox( void ForumPinsLimitBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,

View file

@ -60,6 +60,9 @@ void PinsLimitBox(
void ForumPinsLimitBox( void ForumPinsLimitBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Data::Forum*> forum); not_null<Data::Forum*> forum);
void SublistsPinsLimitBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session);
void CaptionLimitBox( void CaptionLimitBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session, not_null<Main::Session*> session,

View file

@ -51,8 +51,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kPremiumShift = 21. / 240; constexpr auto kPremiumShift = 21. / 240;
constexpr auto kReactionsPerRow = 5;
constexpr auto kDisabledOpacity = 0.5;
constexpr auto kToggleStickerTimeout = 2 * crl::time(1000); constexpr auto kToggleStickerTimeout = 2 * crl::time(1000);
constexpr auto kStarOpacityOff = 0.1; constexpr auto kStarOpacityOff = 0.1;
constexpr auto kStarOpacityOn = 1.; constexpr auto kStarOpacityOn = 1.;

View file

@ -848,7 +848,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
if (!_chatsIndexed->empty()) { if (!_chatsIndexed->empty()) {
const auto index = yFrom / _rowHeight; const auto index = yFrom / _rowHeight;
auto i = _chatsIndexed->begin() auto i = _chatsIndexed->begin()
+ std::min(index, _chatsIndexed->size());; + std::min(index, _chatsIndexed->size());
for (auto end = _chatsIndexed->cend(); i != end; ++i) { for (auto end = _chatsIndexed->cend(); i != end; ++i) {
if (((*i)->index() * _rowHeight) >= yTo) { if (((*i)->index() * _rowHeight) >= yTo) {
break; break;

View file

@ -520,6 +520,7 @@ void BoxController::loadMoreRows() {
MTP_inputPeerEmpty(), MTP_inputPeerEmpty(),
MTP_string(), // q MTP_string(), // q
MTP_inputPeerEmpty(), MTP_inputPeerEmpty(),
MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id MTPint(), // top_msg_id
MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)), MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)),
MTP_int(0), // min_date MTP_int(0), // min_date

View file

@ -321,9 +321,6 @@ void Viewport::RendererGL::init(
_frameBuffer->bind(); _frameBuffer->bind();
_frameBuffer->allocate(kValues * sizeof(GLfloat)); _frameBuffer->allocate(kValues * sizeof(GLfloat));
_downscaleProgram.yuv420.emplace(); _downscaleProgram.yuv420.emplace();
const auto downscaleVertexSource = VertexShader({
VertexPassTextureCoord(),
});
_downscaleVertexShader = LinkProgram( _downscaleVertexShader = LinkProgram(
&*_downscaleProgram.yuv420, &*_downscaleProgram.yuv420,
VertexShader({ VertexShader({

View file

@ -454,8 +454,10 @@ void ChooseSourceProcess::fillSources() {
auto screenIndex = 0; auto screenIndex = 0;
auto windowIndex = 0; auto windowIndex = 0;
auto firstScreenSelected = false;
const auto active = _delegate->chooseSourceActiveDeviceId(); const auto active = _delegate->chooseSourceActiveDeviceId();
const auto append = [&](const tgcalls::DesktopCaptureSource &source) { const auto append = [&](const tgcalls::DesktopCaptureSource &source) {
const auto firstScreen = !source.isWindow() && !screenIndex;
const auto title = !source.isWindow() const auto title = !source.isWindow()
? tr::lng_group_call_screen_title( ? tr::lng_group_call_screen_title(
tr::now, tr::now,
@ -471,6 +473,10 @@ void ChooseSourceProcess::fillSources() {
if (!active.isEmpty() && active.toStdString() == id) { if (!active.isEmpty() && active.toStdString() == id) {
_selected = raw; _selected = raw;
raw->setActive(true); raw->setActive(true);
} else if (active.isEmpty() && firstScreen) {
_selected = raw;
raw->setActive(true);
firstScreenSelected = true;
} }
_sources.back()->activations( _sources.back()->activations(
) | rpl::filter([=] { ) | rpl::filter([=] {
@ -489,6 +495,9 @@ void ChooseSourceProcess::fillSources() {
for (const auto &source : windowsManager.sources()) { for (const auto &source : windowsManager.sources()) {
append(source); append(source);
} }
if (firstScreenSelected) {
updateButtonsVisibility();
}
} }
void ChooseSourceProcess::updateButtonsVisibility() { void ChooseSourceProcess::updateButtonsVisibility() {

View file

@ -683,7 +683,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
} break; } break;
case QEvent::ThemeChange: { case QEvent::ThemeChange: {
if (Platform::IsLinux() && object == QGuiApplication::allWindows().first()) { if (Platform::IsLinux()
&& object == QGuiApplication::allWindows().constFirst()) {
Core::App().refreshApplicationIcon(); Core::App().refreshApplicationIcon();
Core::App().tray().updateIconCounters(); Core::App().tray().updateIconCounters();
} }

View file

@ -499,7 +499,7 @@ uint64 Launcher::installationTag() const {
} }
void Launcher::processArguments() { void Launcher::processArguments() {
enum class KeyFormat { enum class KeyFormat {
NoValues, NoValues,
OneValue, OneValue,
AllLeftValues, AllLeftValues,
@ -542,9 +542,13 @@ void Launcher::processArguments() {
} }
} }
static const auto RegExp = QRegularExpression("[^a-z0-9\\-_]");
gDebugMode = parseResult.contains("-debug"); gDebugMode = parseResult.contains("-debug");
gKeyFile = parseResult.value("-key", {}).join(QString()).toLower(); gKeyFile = parseResult
gKeyFile = gKeyFile.replace(QRegularExpression("[^a-z0-9\\-_]"), {}); .value("-key", {})
.join(QString())
.toLower()
.replace(RegExp, {});
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious : parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
: parseResult.contains("-cleanup") ? LaunchModeCleanup : parseResult.contains("-cleanup") ? LaunchModeCleanup

View file

@ -845,6 +845,21 @@ bool ResolvePremiumOffer(
return true; return true;
} }
bool ResolvePremiumMultigift(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto params = url_parse_params(
match->captured(1).mid(1),
qthelp::UrlParamNameTransform::ToLower);
controller->showGiftPremiumsBox(params.value(u"ref"_q, u"gift_url"_q));
controller->window().activate();
return true;
}
bool ResolveLoginCode( bool ResolveLoginCode(
Window::SessionController *controller, Window::SessionController *controller,
const Match &match, const Match &match,
@ -968,6 +983,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
u"premium_offer/?(\\?.+)?(#|$)"_q, u"premium_offer/?(\\?.+)?(#|$)"_q,
ResolvePremiumOffer, ResolvePremiumOffer,
}, },
{
u"^premium_multigift/?\\?(.+)(#|$)"_q,
ResolvePremiumMultigift,
},
{ {
u"^login/?(\\?code=([0-9]+))(&|$)"_q, u"^login/?(\\?code=([0-9]+))(&|$)"_q,
ResolveLoginCode ResolveLoginCode
@ -1092,7 +1111,7 @@ QString TryConvertUrlToLocal(QString url) {
const auto base = u"tg://privatepost?channel="_q + channel; const auto base = u"tg://privatepost?channel="_q + channel;
auto added = QString(); auto added = QString();
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) { if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2)); added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
} else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) { } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
added = u"&post="_q + postMatch->captured(1); added = u"&post="_q + postMatch->captured(1);
} }
@ -1122,7 +1141,7 @@ QString TryConvertUrlToLocal(QString url) {
const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1)); const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1));
auto added = QString(); auto added = QString();
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2)); added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
} else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&post="_q + postMatch->captured(1); added = u"&post="_q + postMatch->captured(1);
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { } else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {

View file

@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/local_url_handlers.h" #include "core/local_url_handlers.h"
#include "core/update_checker.h" #include "core/update_checker.h"
#include "core/deadlock_detector.h" #include "core/deadlock_detector.h"
#include "base/options.h"
#include "base/timer.h" #include "base/timer.h"
#include "base/concurrent_timer.h" #include "base/concurrent_timer.h"
#include "base/invoke_queued.h" #include "base/invoke_queued.h"
@ -38,24 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Core { namespace Core {
namespace { namespace {
base::options::toggle OptionForceWaylandFractionalScaling({
.id = kOptionForceWaylandFractionalScaling,
.name = "Enable xdg-output fractional scaling",
.description = "Enable xdg-output based fractional scaling on Wayland. "
"This works without fractional-scale-v1 and without "
"precise High DPI scaling. "
"Requires Qt with Desktop App Toolkit patches.",
.defaultValue = true,
.scope = [] {
#ifdef DESKTOP_APP_QT_PATCHED
return Platform::IsWayland();
#else // DESKTOP_APP_QT_PATCHED
return false;
#endif // !DESKTOP_APP_QT_PATCHED
},
.restartRequired = true,
});
QChar _toHex(ushort v) { QChar _toHex(ushort v) {
v = v & 0x000F; v = v & 0x000F;
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
@ -96,17 +77,12 @@ QString _escapeFrom7bit(const QString &str) {
} // namespace } // namespace
const char kOptionForceWaylandFractionalScaling[] = "force-wayland-fractional-scaling";
bool Sandbox::QuitOnStartRequested = false; bool Sandbox::QuitOnStartRequested = false;
Sandbox::Sandbox(int &argc, char **argv) Sandbox::Sandbox(int &argc, char **argv)
: QApplication(argc, argv) : QApplication(argc, argv)
, _mainThreadId(QThread::currentThreadId()) { , _mainThreadId(QThread::currentThreadId()) {
setQuitOnLastWindowClosed(false); setQuitOnLastWindowClosed(false);
if (OptionForceWaylandFractionalScaling.value()) {
setProperty("_q_force_wayland_fractional_scale", true);
}
} }
int Sandbox::start() { int Sandbox::start() {
@ -240,7 +216,7 @@ void Sandbox::setupScreenScale() {
const auto logEnv = [](const char *name) { const auto logEnv = [](const char *name) {
const auto value = qEnvironmentVariable(name); const auto value = qEnvironmentVariable(name);
if (!value.isEmpty()) { if (!value.isEmpty()) {
LOG(("%1: %2").arg(name).arg(value)); LOG(("%1: %2").arg(name, value));
} }
}; };
logEnv("QT_DEVICE_PIXEL_RATIO"); logEnv("QT_DEVICE_PIXEL_RATIO");

View file

@ -19,8 +19,6 @@ class QLockFile;
namespace Core { namespace Core {
extern const char kOptionForceWaylandFractionalScaling[];
class UpdateChecker; class UpdateChecker;
class Application; class Application;

View file

@ -241,7 +241,7 @@ QString FindUpdateFile() {
} }
const auto list = updates.entryInfoList(QDir::Files); const auto list = updates.entryInfoList(QDir::Files);
for (const auto &info : list) { for (const auto &info : list) {
if (QRegularExpression( static const auto RegExp = QRegularExpression(
"^(" "^("
"tupdate|" "tupdate|"
"tx64upd|" "tx64upd|"
@ -250,7 +250,8 @@ QString FindUpdateFile() {
"tlinuxupd|" "tlinuxupd|"
")\\d+(_[a-z\\d]+)?$", ")\\d+(_[a-z\\d]+)?$",
QRegularExpression::CaseInsensitiveOption QRegularExpression::CaseInsensitiveOption
).match(info.fileName()).hasMatch()) { );
if (RegExp.match(info.fileName()).hasMatch()) {
return info.absoluteFilePath(); return info.absoluteFilePath();
} }
} }

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4013001; constexpr auto AppVersion = 4014001;
constexpr auto AppVersionStr = "4.13.1"; constexpr auto AppVersionStr = "4.14.1";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -889,7 +889,6 @@ not_null<HistoryItem*> DownloadManager::generateItem(
const auto replyTo = FullReplyTo(); const auto replyTo = FullReplyTo();
const auto viaBotId = UserId(); const auto viaBotId = UserId();
const auto date = base::unixtime::now(); const auto date = base::unixtime::now();
const auto postAuthor = QString();
const auto caption = TextWithEntities(); const auto caption = TextWithEntities();
const auto make = [&](const auto media) { const auto make = [&](const auto media) {
return history->makeMessage( return history->makeMessage(

View file

@ -343,12 +343,6 @@ int Folder::storiesUnreadCount() const {
return _storiesUnreadCount; return _storiesUnreadCount;
} }
void Folder::requestChatListMessage() {
if (!chatListMessageKnown()) {
owner().histories().requestDialogEntry(this);
}
}
TimeId Folder::adjustedChatListTimeId() const { TimeId Folder::adjustedChatListTimeId() const {
return chatListTimeId(); return chatListTimeId();
} }

View file

@ -49,9 +49,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override; Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override; HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override; bool chatListMessageKnown() const override;
void requestChatListMessage() override;
const QString &chatListName() const override; const QString &chatListName() const override;
const QString &chatListNameSortKey() const override; const QString &chatListNameSortKey() const override;
int chatListNameVersion() const override;
const base::flat_set<QString> &chatListNameWords() const override; const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override; const base::flat_set<QChar> &chatListFirstLetters() const override;
@ -82,8 +82,6 @@ public:
private: private:
void indexNameParts(); void indexNameParts();
int chatListNameVersion() const override;
void reorderLastHistories(); void reorderLastHistories();
void paintUserpic( void paintUserpic(

View file

@ -15,13 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
namespace Data { namespace Data {
namespace {
constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000);
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000);
} // namespace
ForumIcons::ForumIcons(not_null<Session*> owner) ForumIcons::ForumIcons(not_null<Session*> owner)
: _owner(owner) : _owner(owner)

View file

@ -98,6 +98,7 @@ public:
void setRealRootId(MsgId realId); void setRealRootId(MsgId realId);
void readTillEnd(); void readTillEnd();
void requestChatListMessage();
void applyTopic(const MTPDforumTopic &data); void applyTopic(const MTPDforumTopic &data);
@ -109,9 +110,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override; Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override; HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override; bool chatListMessageKnown() const override;
void requestChatListMessage() override;
const QString &chatListName() const override; const QString &chatListName() const override;
const QString &chatListNameSortKey() const override; const QString &chatListNameSortKey() const override;
int chatListNameVersion() const override;
const base::flat_set<QString> &chatListNameWords() const override; const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override; const base::flat_set<QChar> &chatListFirstLetters() const override;
@ -187,8 +188,6 @@ private:
void allowChatListMessageResolve(); void allowChatListMessageResolve();
void resolveChatListMessageGroup(); void resolveChatListMessageGroup();
int chatListNameVersion() const override;
void subscribeToUnreadChanges(); void subscribeToUnreadChanges();
[[nodiscard]] Dialogs::UnreadState unreadStateFor( [[nodiscard]] Dialogs::UnreadState unreadStateFor(
int count, int count,

View file

@ -555,6 +555,10 @@ bool Media::hasSpoiler() const {
return false; return false;
} }
crl::time Media::ttlSeconds() const {
return 0;
}
bool Media::consumeMessageText(const TextWithEntities &text) { bool Media::consumeMessageText(const TextWithEntities &text) {
return false; return false;
} }
@ -849,12 +853,14 @@ MediaFile::MediaFile(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool skipPremiumEffect, bool skipPremiumEffect,
bool spoiler) bool spoiler,
crl::time ttlSeconds)
: Media(parent) : Media(parent)
, _document(document) , _document(document)
, _emoji(document->sticker() ? document->sticker()->alt : QString()) , _emoji(document->sticker() ? document->sticker()->alt : QString())
, _skipPremiumEffect(skipPremiumEffect) , _skipPremiumEffect(skipPremiumEffect)
, _spoiler(spoiler) { , _spoiler(spoiler)
, _ttlSeconds(ttlSeconds) {
parent->history()->owner().registerDocumentItem(_document, parent); parent->history()->owner().registerDocumentItem(_document, parent);
if (!_emoji.isEmpty()) { if (!_emoji.isEmpty()) {
@ -882,7 +888,8 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
parent, parent,
_document, _document,
!_document->session().premium(), !_document->session().premium(),
_spoiler); _spoiler,
_ttlSeconds);
} }
DocumentData *MediaFile::document() const { DocumentData *MediaFile::document() const {
@ -1129,6 +1136,14 @@ bool MediaFile::hasSpoiler() const {
return _spoiler; return _spoiler;
} }
crl::time MediaFile::ttlSeconds() const {
return _ttlSeconds;
}
bool MediaFile::allowsForward() const {
return !ttlSeconds();
}
bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) {
if (media.type() != mtpc_messageMediaDocument) { if (media.type() != mtpc_messageMediaDocument) {
return false; return false;

View file

@ -174,6 +174,7 @@ public:
virtual bool dropForwardedInfo() const; virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const; virtual bool forceForwardedInfo() const;
[[nodiscard]] virtual bool hasSpoiler() const; [[nodiscard]] virtual bool hasSpoiler() const;
[[nodiscard]] virtual crl::time ttlSeconds() const;
[[nodiscard]] virtual bool consumeMessageText( [[nodiscard]] virtual bool consumeMessageText(
const TextWithEntities &text); const TextWithEntities &text);
@ -256,7 +257,8 @@ public:
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool skipPremiumEffect, bool skipPremiumEffect,
bool spoiler); bool spoiler,
crl::time ttlSeconds);
~MediaFile(); ~MediaFile();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
@ -278,6 +280,8 @@ public:
bool forwardedBecomesUnread() const override; bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override; bool dropForwardedInfo() const override;
bool hasSpoiler() const override; bool hasSpoiler() const override;
crl::time ttlSeconds() const override;
bool allowsForward() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override;
@ -292,6 +296,9 @@ private:
bool _skipPremiumEffect = false; bool _skipPremiumEffect = false;
bool _spoiler = false; bool _spoiler = false;
// Video (unsupported) / Voice / Round.
crl::time _ttlSeconds = 0;
}; };
class MediaContact final : public Media { class MediaContact final : public Media {

View file

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_histories.h" #include "data/data_histories.h"
@ -1029,6 +1030,10 @@ bool PeerData::sharedMediaInfo() const {
return isSelf() || isRepliesChat(); return isSelf() || isRepliesChat();
} }
bool PeerData::savedSublistsInfo() const {
return isSelf() && owner().savedMessages().supported();
}
bool PeerData::hasStoriesHidden() const { bool PeerData::hasStoriesHidden() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return user->hasStoriesHidden(); return user->hasStoriesHidden();

View file

@ -165,6 +165,7 @@ public:
virtual ~PeerData(); virtual ~PeerData();
static constexpr auto kServiceNotificationsId = peerFromUser(777000); static constexpr auto kServiceNotificationsId = peerFromUser(777000);
static constexpr auto kSavedHiddenAuthorId = peerFromUser(2666000);
[[nodiscard]] Data::Session &owner() const; [[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
@ -202,6 +203,7 @@ public:
[[nodiscard]] bool isGigagroup() const; [[nodiscard]] bool isGigagroup() const;
[[nodiscard]] bool isRepliesChat() const; [[nodiscard]] bool isRepliesChat() const;
[[nodiscard]] bool sharedMediaInfo() const; [[nodiscard]] bool sharedMediaInfo() const;
[[nodiscard]] bool savedSublistsInfo() const;
[[nodiscard]] bool hasStoriesHidden() const; [[nodiscard]] bool hasStoriesHidden() const;
void setStoriesHidden(bool hidden); void setStoriesHidden(bool hidden);
@ -212,6 +214,9 @@ public:
[[nodiscard]] bool isServiceUser() const { [[nodiscard]] bool isServiceUser() const {
return isUser() && !(id.value % 1000); return isUser() && !(id.value % 1000);
} }
[[nodiscard]] bool isSavedHiddenAuthor() const {
return (id == kSavedHiddenAuthorId);
}
[[nodiscard]] Data::Forum *forum() const; [[nodiscard]] Data::Forum *forum() const;
[[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const; [[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const;

View file

@ -226,7 +226,6 @@ bool PhotoMedia::setToClipboard() {
if (fallback.isNull()) { if (fallback.isNull()) {
return false; return false;
} }
const auto bytes = imageBytes(large);
auto mime = std::make_unique<QMimeData>(); auto mime = std::make_unique<QMimeData>();
mime->setImageData(std::move(fallback)); mime->setImageData(std::move(fallback));
if (auto bytes = imageBytes(large); !bytes.isEmpty()) { if (auto bytes = imageBytes(large); !bytes.isEmpty()) {

View file

@ -141,6 +141,18 @@ int PremiumLimits::topicsPinnedCurrent() const {
return appConfigLimit("topics_pinned_limit", 5); return appConfigLimit("topics_pinned_limit", 5);
} }
int PremiumLimits::savedSublistsPinnedDefault() const {
return appConfigLimit("saved_dialogs_pinned_limit_default", 5);
}
int PremiumLimits::savedSublistsPinnedPremium() const {
return appConfigLimit("saved_dialogs_pinned_limit_premium", 100);
}
int PremiumLimits::savedSublistsPinnedCurrent() const {
return isPremium()
? savedSublistsPinnedPremium()
: savedSublistsPinnedDefault();
}
int PremiumLimits::channelsPublicDefault() const { int PremiumLimits::channelsPublicDefault() const {
return appConfigLimit("channels_public_limit_default", 10); return appConfigLimit("channels_public_limit_default", 10);
} }

View file

@ -59,6 +59,10 @@ public:
[[nodiscard]] int topicsPinnedCurrent() const; [[nodiscard]] int topicsPinnedCurrent() const;
[[nodiscard]] int savedSublistsPinnedDefault() const;
[[nodiscard]] int savedSublistsPinnedPremium() const;
[[nodiscard]] int savedSublistsPinnedCurrent() const;
[[nodiscard]] int channelsPublicDefault() const; [[nodiscard]] int channelsPublicDefault() const;
[[nodiscard]] int channelsPublicPremium() const; [[nodiscard]] int channelsPublicPremium() const;
[[nodiscard]] int channelsPublicCurrent() const; [[nodiscard]] int channelsPublicCurrent() const;

View file

@ -0,0 +1,296 @@
/*
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_saved_messages.h"
#include "apiwrap.h"
#include "data/data_peer.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
namespace Data {
namespace {
constexpr auto kPerPage = 50;
constexpr auto kFirstPerPage = 10;
} // namespace
SavedMessages::SavedMessages(not_null<Session*> owner)
: _owner(owner)
, _chatsList(
&owner->session(),
FilterId(),
owner->maxPinnedChatsLimitValue(this))
, _loadMore([=] { sendLoadMoreRequests(); }) {
}
SavedMessages::~SavedMessages() = default;
bool SavedMessages::supported() const {
return !_unsupported;
}
Session &SavedMessages::owner() const {
return *_owner;
}
Main::Session &SavedMessages::session() const {
return _owner->session();
}
not_null<Dialogs::MainList*> SavedMessages::chatsList() {
return &_chatsList;
}
not_null<SavedSublist*> SavedMessages::sublist(not_null<PeerData*> peer) {
const auto i = _sublists.find(peer);
if (i != end(_sublists)) {
return i->second.get();
}
return _sublists.emplace(
peer,
std::make_unique<SavedSublist>(peer)).first->second.get();
}
void SavedMessages::loadMore() {
_loadMoreScheduled = true;
_loadMore.call();
}
void SavedMessages::loadMore(not_null<SavedSublist*> sublist) {
_loadMoreSublistsScheduled.emplace(sublist);
_loadMore.call();
}
void SavedMessages::sendLoadMore() {
if (_loadMoreRequestId || _chatsList.loaded()) {
return;
} else if (!_pinnedLoaded) {
loadPinned();
}
_loadMoreRequestId = _owner->session().api().request(
MTPmessages_GetSavedDialogs(
MTP_flags(MTPmessages_GetSavedDialogs::Flag::f_exclude_pinned),
MTP_int(_offsetDate),
MTP_int(_offsetId),
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
MTP_int(kPerPage),
MTP_long(0)) // hash
).done([=](const MTPmessages_SavedDialogs &result) {
apply(result, false);
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
}
_chatsList.setLoaded();
_loadMoreRequestId = 0;
}).send();
}
void SavedMessages::loadPinned() {
if (_pinnedRequestId) {
return;
}
_pinnedRequestId = _owner->session().api().request(
MTPmessages_GetPinnedSavedDialogs()
).done([=](const MTPmessages_SavedDialogs &result) {
apply(result, true);
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
} else {
_pinnedLoaded = true;
}
_pinnedRequestId = 0;
}).send();
}
void SavedMessages::sendLoadMore(not_null<SavedSublist*> sublist) {
if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) {
return;
}
const auto &list = sublist->messages();
const auto offsetId = list.empty() ? MsgId(0) : list.back()->id;
const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date();
const auto limit = offsetId ? kPerPage : kFirstPerPage;
const auto requestId = _owner->session().api().request(
MTPmessages_GetSavedHistory(
sublist->peer()->input,
MTP_int(offsetId),
MTP_int(offsetDate),
MTP_int(0), // add_offset
MTP_int(limit),
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_long(0)) // hash
).done([=](const MTPmessages_Messages &result) {
auto count = 0;
auto list = (const QVector<MTPMessage>*)nullptr;
result.match([](const MTPDmessages_channelMessages &) {
LOG(("API Error: messages.channelMessages in sublist."));
}, [](const MTPDmessages_messagesNotModified &) {
LOG(("API Error: messages.messagesNotModified in sublist."));
}, [&](const auto &data) {
owner().processUsers(data.vusers());
owner().processChats(data.vchats());
list = &data.vmessages().v;
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
count = int(list->size());
} else {
count = data.vcount().v;
}
});
_loadMoreRequests.remove(sublist);
if (!list) {
sublist->setFullLoaded();
return;
}
auto items = std::vector<not_null<HistoryItem*>>();
items.reserve(list->size());
for (const auto &message : *list) {
const auto item = owner().addNewMessage(
message,
{},
NewMessageType::Existing);
if (item) {
items.push_back(item);
}
}
sublist->append(std::move(items), count);
if (result.type() == mtpc_messages_messages) {
sublist->setFullLoaded();
}
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
}
sublist->setFullLoaded();
_loadMoreRequests.remove(sublist);
}).send();
_loadMoreRequests[sublist] = requestId;
}
void SavedMessages::apply(
const MTPmessages_SavedDialogs &result,
bool pinned) {
auto list = (const QVector<MTPSavedDialog>*)nullptr;
result.match([](const MTPDmessages_savedDialogsNotModified &) {
LOG(("API Error: messages.savedDialogsNotModified."));
}, [&](const auto &data) {
_owner->processUsers(data.vusers());
_owner->processChats(data.vchats());
_owner->processMessages(
data.vmessages(),
NewMessageType::Existing);
list = &data.vdialogs().v;
});
if (pinned) {
_pinnedRequestId = 0;
_pinnedLoaded = true;
} else {
_loadMoreRequestId = 0;
}
if (!list) {
if (!pinned) {
_chatsList.setLoaded();
}
return;
}
auto lastValid = false;
auto offsetDate = TimeId();
auto offsetId = MsgId();
auto offsetPeer = (PeerData*)nullptr;
const auto selfId = _owner->session().userPeerId();
for (const auto &dialog : *list) {
const auto &data = dialog.data();
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
const auto topId = MsgId(data.vtop_message().v);
if (const auto item = _owner->message(selfId, topId)) {
offsetPeer = peer;
offsetDate = item->date();
offsetId = topId;
lastValid = true;
const auto entry = sublist(peer);
const auto entryPinned = pinned || data.is_pinned();
entry->applyMaybeLast(item);
_owner->setPinnedFromEntryList(entry, entryPinned);
} else {
lastValid = false;
}
}
if (pinned) {
} else if (!lastValid) {
LOG(("API Error: Unknown message in the end of a slice."));
_chatsList.setLoaded();
} else if (result.type() == mtpc_messages_savedDialogs) {
_chatsList.setLoaded();
} else if (offsetDate < _offsetDate
|| (offsetDate == _offsetDate && offsetId == _offsetId && offsetPeer == _offsetPeer)) {
LOG(("API Error: Bad order in messages.savedDialogs."));
_chatsList.setLoaded();
} else {
_offsetDate = offsetDate;
_offsetId = offsetId;
_offsetPeer = offsetPeer;
}
}
void SavedMessages::sendLoadMoreRequests() {
if (_loadMoreScheduled) {
sendLoadMore();
}
for (const auto sublist : base::take(_loadMoreSublistsScheduled)) {
sendLoadMore(sublist);
}
}
void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) {
const auto list = update.vorder();
if (!list) {
loadPinned();
return;
}
const auto &order = list->v;
const auto notLoaded = [&](const MTPDialogPeer &peer) {
return peer.match([&](const MTPDdialogPeer &data) {
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
return !_sublists.contains(peer);
}, [&](const MTPDdialogPeerFolder &data) {
LOG(("API Error: "
"updatePinnedSavedDialogs has folders."));
return false;
});
};
if (!ranges::none_of(order, notLoaded)) {
loadPinned();
} else {
_chatsList.pinned()->applyList(_owner, order);
_owner->notifyPinnedDialogsOrderUpdated();
}
}
void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
update.vpeer().match([&](const MTPDdialogPeer &data) {
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
const auto i = _sublists.find(peer);
if (i != end(_sublists)) {
const auto entry = i->second.get();
_owner->setChatPinned(entry, FilterId(), update.is_pinned());
} else {
loadPinned();
}
}, [&](const MTPDdialogPeerFolder &data) {
DEBUG_LOG(("API Error: Folder in updateSavedDialogPinned."));
});
}
} // namespace Data

View file

@ -0,0 +1,72 @@
/*
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 "dialogs/dialogs_main_list.h"
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Session;
class SavedSublist;
class SavedMessages final {
public:
explicit SavedMessages(not_null<Session*> owner);
~SavedMessages();
[[nodiscard]] bool supported() const;
[[nodiscard]] Session &owner() const;
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<Dialogs::MainList*> chatsList();
[[nodiscard]] not_null<SavedSublist*> sublist(not_null<PeerData*> peer);
void loadMore();
void loadMore(not_null<SavedSublist*> sublist);
void apply(const MTPDupdatePinnedSavedDialogs &update);
void apply(const MTPDupdateSavedDialogPinned &update);
private:
void loadPinned();
void apply(const MTPmessages_SavedDialogs &result, bool pinned);
void sendLoadMore();
void sendLoadMore(not_null<SavedSublist*> sublist);
void sendLoadMoreRequests();
const not_null<Session*> _owner;
Dialogs::MainList _chatsList;
base::flat_map<
not_null<PeerData*>,
std::unique_ptr<SavedSublist>> _sublists;
base::flat_map<not_null<SavedSublist*>, mtpRequestId> _loadMoreRequests;
mtpRequestId _loadMoreRequestId = 0;
mtpRequestId _pinnedRequestId = 0;
TimeId _offsetDate = 0;
MsgId _offsetId = 0;
PeerData *_offsetPeer = nullptr;
SingleQueuedInvokation _loadMore;
base::flat_set<not_null<SavedSublist*>> _loadMoreSublistsScheduled;
bool _loadMoreScheduled = false;
bool _pinnedLoaded = false;
bool _unsupported = false;
};
} // namespace Data

View file

@ -0,0 +1,256 @@
/*
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_saved_sublist.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "history/view/history_view_item_preview.h"
#include "history/history.h"
#include "history/history_item.h"
namespace Data {
SavedSublist::SavedSublist(not_null<PeerData*> peer)
: Entry(&peer->owner(), Dialogs::Entry::Type::SavedSublist)
, _history(peer->owner().history(peer)) {
}
SavedSublist::~SavedSublist() = default;
not_null<History*> SavedSublist::history() const {
return _history;
}
not_null<PeerData*> SavedSublist::peer() const {
return _history->peer;
}
bool SavedSublist::isHiddenAuthor() const {
return peer()->isSavedHiddenAuthor();
}
bool SavedSublist::isFullLoaded() const {
return (_flags & Flag::FullLoaded) != 0;
}
auto SavedSublist::messages() const
-> const std::vector<not_null<HistoryItem*>> & {
return _items;
}
void SavedSublist::applyMaybeLast(not_null<HistoryItem*> item, bool added) {
const auto before = [](
not_null<HistoryItem*> a,
not_null<HistoryItem*> b) {
return IsServerMsgId(a->id)
? (IsServerMsgId(b->id) ? (a->id < b->id) : true)
: (IsServerMsgId(b->id) ? false : (a->id < b->id));
};
if (_items.empty()) {
_items.push_back(item);
} else if (_items.front() == item) {
return;
} else if (!isFullLoaded()
&& _items.size() == 1
&& before(_items.front(), item)) {
_items[0] = item;
} else if (before(_items.back(), item)) {
for (auto i = begin(_items); i != end(_items); ++i) {
if (item == *i) {
return;
} else if (before(*i, item)) {
_items.insert(i, item);
break;
}
}
}
if (added && _fullCount) {
++*_fullCount;
}
if (_items.front() == item) {
setChatListTimeId(item->date());
resolveChatListMessageGroup();
}
_changed.fire({});
}
void SavedSublist::removeOne(not_null<HistoryItem*> item) {
if (_items.empty()) {
return;
}
const auto last = (_items.front() == item);
const auto from = ranges::remove(_items, item);
const auto removed = end(_items) - from;
if (removed) {
_items.erase(from, end(_items));
}
if (_fullCount) {
--*_fullCount;
}
if (last) {
if (_items.empty()) {
if (isFullLoaded()) {
updateChatListExistence();
} else {
updateChatListEntry();
crl::on_main(this, [=] {
owner().savedMessages().loadMore(this);
});
}
} else {
setChatListTimeId(_items.front()->date());
}
}
if (removed || _fullCount) {
_changed.fire({});
}
}
rpl::producer<> SavedSublist::changes() const {
return _changed.events();
}
std::optional<int> SavedSublist::fullCount() const {
return isFullLoaded() ? int(_items.size()) : _fullCount;
}
rpl::producer<int> SavedSublist::fullCountValue() const {
return _changed.events_starting_with({}) | rpl::map([=] {
return fullCount();
}) | rpl::filter_optional();
}
void SavedSublist::append(
std::vector<not_null<HistoryItem*>> &&items,
int fullCount) {
_fullCount = fullCount;
if (items.empty()) {
setFullLoaded();
} else if (_items.empty()) {
_items = std::move(items);
setChatListTimeId(_items.front()->date());
_changed.fire({});
} else if (_items.back()->id > items.front()->id) {
_items.insert(end(_items), begin(items), end(items));
_changed.fire({});
} else {
_items.insert(end(_items), begin(items), end(items));
ranges::stable_sort(
_items,
ranges::greater(),
&HistoryItem::id);
ranges::unique(_items, ranges::greater(), &HistoryItem::id);
_changed.fire({});
}
}
void SavedSublist::setFullLoaded(bool loaded) {
if (loaded != isFullLoaded()) {
if (loaded) {
_flags |= Flag::FullLoaded;
if (_items.empty()) {
updateChatListExistence();
}
} else {
_flags &= ~Flag::FullLoaded;
}
_changed.fire({});
}
}
int SavedSublist::fixedOnTopIndex() const {
return 0;
}
bool SavedSublist::shouldBeInChatList() const {
return isPinnedDialog(FilterId()) || !_items.empty();
}
Dialogs::UnreadState SavedSublist::chatListUnreadState() const {
return {};
}
Dialogs::BadgesState SavedSublist::chatListBadgesState() const {
return {};
}
HistoryItem *SavedSublist::chatListMessage() const {
return _items.empty() ? nullptr : _items.front().get();
}
bool SavedSublist::chatListMessageKnown() const {
return true;
}
const QString &SavedSublist::chatListName() const {
return _history->chatListName();
}
const base::flat_set<QString> &SavedSublist::chatListNameWords() const {
return _history->chatListNameWords();
}
const base::flat_set<QChar> &SavedSublist::chatListFirstLetters() const {
return _history->chatListFirstLetters();
}
const QString &SavedSublist::chatListNameSortKey() const {
return _history->chatListNameSortKey();
}
int SavedSublist::chatListNameVersion() const {
return _history->chatListNameVersion();
}
void SavedSublist::paintUserpic(
Painter &p,
Ui::PeerUserpicView &view,
const Dialogs::Ui::PaintContext &context) const {
_history->paintUserpic(p, view, context);
}
void SavedSublist::chatListPreloadData() {
peer()->loadUserpic();
allowChatListMessageResolve();
}
void SavedSublist::allowChatListMessageResolve() {
if (_flags & Flag::ResolveChatListMessage) {
return;
}
_flags |= Flag::ResolveChatListMessage;
resolveChatListMessageGroup();
}
bool SavedSublist::hasOrphanMediaGroupPart() const {
if (isFullLoaded() || _items.size() != 1) {
return false;
}
return (_items.front()->groupId() != MessageGroupId());
}
void SavedSublist::resolveChatListMessageGroup() {
const auto item = chatListMessage();
if (!(_flags & Flag::ResolveChatListMessage)
|| !item
|| !hasOrphanMediaGroupPart()) {
return;
}
// If we set a single album part, request the full album.
const auto withImages = !item->toPreview({
.hideSender = true,
.hideCaption = true }).images.empty();
if (withImages) {
owner().histories().requestGroupAround(item);
}
}
} // namespace Data

View file

@ -0,0 +1,85 @@
/*
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 "dialogs/ui/dialogs_message_view.h"
#include "dialogs/dialogs_entry.h"
class PeerData;
class History;
namespace Data {
class Session;
class SavedSublist final : public Dialogs::Entry {
public:
explicit SavedSublist(not_null<PeerData*> peer);
~SavedSublist();
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] bool isHiddenAuthor() const;
[[nodiscard]] bool isFullLoaded() const;
[[nodiscard]] auto messages() const
-> const std::vector<not_null<HistoryItem*>> &;
void applyMaybeLast(not_null<HistoryItem*> item, bool added = false);
void removeOne(not_null<HistoryItem*> item);
void append(std::vector<not_null<HistoryItem*>> &&items, int fullCount);
void setFullLoaded(bool loaded = true);
[[nodiscard]] rpl::producer<> changes() const;
[[nodiscard]] std::optional<int> fullCount() const;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
[[nodiscard]] Dialogs::Ui::MessageView &lastItemDialogsView() {
return _lastItemDialogsView;
}
int fixedOnTopIndex() const override;
bool shouldBeInChatList() const override;
Dialogs::UnreadState chatListUnreadState() const override;
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
int chatListNameVersion() const override;
const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override;
void chatListPreloadData() override;
void paintUserpic(
Painter &p,
Ui::PeerUserpicView &view,
const Dialogs::Ui::PaintContext &context) const override;
private:
enum class Flag : uchar {
ResolveChatListMessage = (1 << 0),
FullLoaded = (1 << 1),
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>;
bool hasOrphanMediaGroupPart() const;
void allowChatListMessageResolve();
void resolveChatListMessageGroup();
const not_null<History*> _history;
std::vector<not_null<HistoryItem*>> _items;
std::optional<int> _fullCount;
rpl::event_stream<> _changed;
Dialogs::Ui::MessageView _lastItemDialogsView;
Flags _flags;
};
} // namespace Data

View file

@ -68,6 +68,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
data.vid(), data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
data.vpeer_id(), data.vpeer_id(),
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(data.vvia_bot_id().value_or_empty()), MTP_long(data.vvia_bot_id().value_or_empty()),
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(), data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage(
update.vid(), update.vid(),
peerToMTP(local->from()->id), peerToMTP(local->from()->id),
peerToMTP(history->peer->id), peerToMTP(history->peer->id),
MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(), MTPMessageFwdHeader(),
MTPlong(), // via_bot_id MTPlong(), // via_bot_id
replyHeader, replyHeader,

View file

@ -97,6 +97,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
peer->input, peer->input,
MTP_string(query), MTP_string(query),
MTP_inputPeerEmpty(), MTP_inputPeerEmpty(),
MTPInputPeer(), // saved_peer_id
MTP_int(topicRootId), MTP_int(topicRootId),
filter, filter,
MTP_int(0), // min_date MTP_int(0), // min_date

View file

@ -60,6 +60,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h" #include "data/data_emoji_statuses.h"
#include "data/data_forum_icons.h" #include "data/data_forum_icons.h"
#include "data/data_cloud_themes.h" #include "data/data_cloud_themes.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_streaming.h" #include "data/data_streaming.h"
#include "data/data_media_rotation.h" #include "data/data_media_rotation.h"
@ -268,7 +270,8 @@ Session::Session(not_null<Main::Session*> session)
, _forumIcons(std::make_unique<ForumIcons>(this)) , _forumIcons(std::make_unique<ForumIcons>(this))
, _notifySettings(std::make_unique<NotifySettings>(this)) , _notifySettings(std::make_unique<NotifySettings>(this))
, _customEmojiManager(std::make_unique<CustomEmojiManager>(this)) , _customEmojiManager(std::make_unique<CustomEmojiManager>(this))
, _stories(std::make_unique<Stories>(this)) { , _stories(std::make_unique<Stories>(this))
, _savedMessages(std::make_unique<SavedMessages>(this)) {
_cache->open(_session->local().cacheKey()); _cache->open(_session->local().cacheKey());
_bigFileCache->open(_session->local().cacheBigFileKey()); _bigFileCache->open(_session->local().cacheBigFileKey());
@ -1724,6 +1727,11 @@ void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
topic->updateChatListEntry(); topic->updateChatListEntry();
} }
} }
if (const auto sublist = item->savedSublist()) {
if (sublist->lastItemDialogsView().dependsOn(item)) {
sublist->updateChatListEntry();
}
}
} }
rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const { rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
@ -2111,13 +2119,17 @@ void Session::applyDialog(
setPinnedFromEntryList(folder, data.is_pinned()); setPinnedFromEntryList(folder, data.is_pinned());
} }
bool Session::pinnedCanPin(not_null<Data::Thread*> thread) const { bool Session::pinnedCanPin(not_null<Dialogs::Entry*> entry) const {
if (const auto topic = thread->asTopic()) { if (const auto sublist = entry->asSublist()) {
const auto saved = &savedMessages();
return pinnedChatsOrder(saved).size() < pinnedChatsLimit(saved);
} else if (const auto topic = entry->asTopic()) {
const auto forum = topic->forum(); const auto forum = topic->forum();
return pinnedChatsOrder(forum).size() < pinnedChatsLimit(forum); return pinnedChatsOrder(forum).size() < pinnedChatsLimit(forum);
} else {
const auto folder = entry->folder();
return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder);
} }
const auto folder = thread->folder();
return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder);
} }
bool Session::pinnedCanPin( bool Session::pinnedCanPin(
@ -2149,6 +2161,11 @@ int Session::pinnedChatsLimit(not_null<Data::Forum*> forum) const {
return limits.topicsPinnedCurrent(); return limits.topicsPinnedCurrent();
} }
int Session::pinnedChatsLimit(not_null<Data::SavedMessages*> saved) const {
const auto limits = Data::PremiumLimits(_session);
return limits.savedSublistsPinnedCurrent();
}
rpl::producer<int> Session::maxPinnedChatsLimitValue( rpl::producer<int> Session::maxPinnedChatsLimitValue(
Data::Folder *folder) const { Data::Folder *folder) const {
// Premium limit from appconfig. // Premium limit from appconfig.
@ -2189,6 +2206,20 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
}); });
} }
rpl::producer<int> Session::maxPinnedChatsLimitValue(
not_null<SavedMessages*> saved) const {
// Premium limit from appconfig.
// We always use premium limit in the MainList limit producer,
// because it slices the list to that limit. We don't want to slice
// premium-ly added chats from the pinned list because of sync issues.
return rpl::single(rpl::empty_value()) | rpl::then(
_session->account().appConfig().refreshed()
) | rpl::map([=] {
const auto limits = Data::PremiumLimits(_session);
return limits.savedSublistsPinnedPremium();
});
}
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder( const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
Data::Folder *folder) const { Data::Folder *folder) const {
return chatsList(folder)->pinned()->order(); return chatsList(folder)->pinned()->order();
@ -2204,6 +2235,11 @@ const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
return forum->topicsList()->pinned()->order(); return forum->topicsList()->pinned()->order();
} }
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
not_null<Data::SavedMessages*> saved) const {
return saved->chatsList()->pinned()->order();
}
void Session::clearPinnedChats(Data::Folder *folder) { void Session::clearPinnedChats(Data::Folder *folder) {
chatsList(folder)->pinned()->clear(); chatsList(folder)->pinned()->clear();
} }
@ -2220,7 +2256,7 @@ void Session::reorderTwoPinnedChats(
? topic->forum()->topicsList() ? topic->forum()->topicsList()
: filterId : filterId
? chatsFilters().chatsList(filterId) ? chatsFilters().chatsList(filterId)
: chatsList(key1.entry()->folder()); : chatsListFor(key1.entry());
list->pinned()->reorder(key1, key2); list->pinned()->reorder(key1, key2);
notifyPinnedDialogsOrderUpdated(); notifyPinnedDialogsOrderUpdated();
} }
@ -4262,6 +4298,8 @@ not_null<Dialogs::MainList*> Session::chatsListFor(
const auto topic = entry->asTopic(); const auto topic = entry->asTopic();
return topic return topic
? topic->forum()->topicsList() ? topic->forum()->topicsList()
: entry->asSublist()
? _savedMessages->chatsList()
: chatsList(entry->folder()); : chatsList(entry->folder());
} }
@ -4458,6 +4496,7 @@ void Session::insertCheckedServiceNotification(
MTP_int(0), // Not used (would've been trimmed to 32 bits). MTP_int(0), // Not used (would've been trimmed to 32 bits).
peerToMTP(PeerData::kServiceNotificationsId), peerToMTP(PeerData::kServiceNotificationsId),
peerToMTP(PeerData::kServiceNotificationsId), peerToMTP(PeerData::kServiceNotificationsId),
MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(), MTPMessageFwdHeader(),
MTPlong(), // via_bot_id MTPlong(), // via_bot_id
MTPMessageReplyHeader(), MTPMessageReplyHeader(),

View file

@ -61,6 +61,7 @@ class GroupCall;
class NotifySettings; class NotifySettings;
class CustomEmojiManager; class CustomEmojiManager;
class Stories; class Stories;
class SavedMessages;
struct RepliesReadTillUpdate { struct RepliesReadTillUpdate {
FullMsgId id; FullMsgId id;
@ -137,6 +138,9 @@ public:
[[nodiscard]] Stories &stories() const { [[nodiscard]] Stories &stories() const {
return *_stories; return *_stories;
} }
[[nodiscard]] SavedMessages &savedMessages() const {
return *_savedMessages;
}
[[nodiscard]] MsgId nextNonHistoryEntryId() { [[nodiscard]] MsgId nextNonHistoryEntryId() {
return ++_nonHistoryEntryId; return ++_nonHistoryEntryId;
@ -345,25 +349,31 @@ public:
const QVector<MTPDialog> &dialogs, const QVector<MTPDialog> &dialogs,
std::optional<int> count = std::nullopt); std::optional<int> count = std::nullopt);
[[nodiscard]] bool pinnedCanPin(not_null<Thread*> thread) const; [[nodiscard]] bool pinnedCanPin(not_null<Dialogs::Entry*> entry) const;
[[nodiscard]] bool pinnedCanPin( [[nodiscard]] bool pinnedCanPin(
FilterId filterId, FilterId filterId,
not_null<History*> history) const; not_null<History*> history) const;
[[nodiscard]] int pinnedChatsLimit(Folder *folder) const; [[nodiscard]] int pinnedChatsLimit(Folder *folder) const;
[[nodiscard]] int pinnedChatsLimit(FilterId filterId) const; [[nodiscard]] int pinnedChatsLimit(FilterId filterId) const;
[[nodiscard]] int pinnedChatsLimit(not_null<Forum*> forum) const; [[nodiscard]] int pinnedChatsLimit(not_null<Forum*> forum) const;
[[nodiscard]] int pinnedChatsLimit(
not_null<SavedMessages*> saved) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue( [[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
Folder *folder) const; Folder *folder) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue( [[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
FilterId filterId) const; FilterId filterId) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue( [[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
not_null<Forum*> forum) const; not_null<Forum*> forum) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
not_null<SavedMessages*> saved) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder( [[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
Folder *folder) const; Folder *folder) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder( [[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
not_null<Forum*> forum) const; not_null<Forum*> forum) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder( [[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
FilterId filterId) const; FilterId filterId) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
not_null<Data::SavedMessages*> saved) const;
void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned); void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
void setPinnedFromEntryList(Dialogs::Key key, bool pinned); void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
void clearPinnedChats(Folder *folder); void clearPinnedChats(Folder *folder);
@ -1041,6 +1051,7 @@ private:
const std::unique_ptr<NotifySettings> _notifySettings; const std::unique_ptr<NotifySettings> _notifySettings;
const std::unique_ptr<CustomEmojiManager> _customEmojiManager; const std::unique_ptr<CustomEmojiManager> _customEmojiManager;
const std::unique_ptr<Stories> _stories; const std::unique_ptr<Stories> _stories;
const std::unique_ptr<SavedMessages> _savedMessages;
MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange; MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange;

View file

@ -1360,7 +1360,6 @@ void Stories::sendIncrementViewsRequests() {
return; return;
} }
auto ids = QVector<MTPint>();
struct Prepared { struct Prepared {
PeerId peer = 0; PeerId peer = 0;
QVector<MTPint> ids; QVector<MTPint> ids;

View file

@ -385,7 +385,7 @@ QString UserData::username() const {
} }
QString UserData::editableUsername() const { QString UserData::editableUsername() const {
return _username.editableUsername();; return _username.editableUsername();
} }
const std::vector<QString> &UserData::usernames() const { const std::vector<QString> &UserData::usernames() const {

View file

@ -349,7 +349,6 @@ void CustomEmojiLoader::check() {
const auto tag = _tag; const auto tag = _tag;
const auto sizeOverride = int(_sizeOverride); const auto sizeOverride = int(_sizeOverride);
const auto size = FrameSizeFromTag(_tag, _sizeOverride); const auto size = FrameSizeFromTag(_tag, _sizeOverride);
auto bytes = Lottie::ReadContent(data, filepath);
auto loader = [=] { auto loader = [=] {
return std::make_unique<CustomEmojiLoader>( return std::make_unique<CustomEmojiLoader>(
document, document,

View file

@ -347,6 +347,8 @@ dialogsForumIcon: ThreeStateIcon {
dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }}; dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }};
dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }}; dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }};
dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }}; dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }};
dialogsHiddenAuthorUserpic: icon {{ "dialogs/avatar_hidden", premiumButtonFg }};
dialogsMyNotesUserpic: icon {{ "dialogs/avatar_notes", historyPeerUserpicFg }};
dialogsSendStateSkip: 20px; dialogsSendStateSkip: 20px;
dialogsSendingIcon: ThreeStateIcon { dialogsSendingIcon: ThreeStateIcon {

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "data/data_saved_sublist.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "mainwidget.h" #include "mainwidget.h"
@ -83,6 +84,8 @@ Entry::Entry(not_null<Data::Session*> owner, Type type)
? (Flag::IsThread | Flag::IsHistory) ? (Flag::IsThread | Flag::IsHistory)
: (type == Type::ForumTopic) : (type == Type::ForumTopic)
? Flag::IsThread ? Flag::IsThread
: (type == Type::SavedSublist)
? Flag::IsSavedSublist
: Flag(0)) { : Flag(0)) {
} }
@ -109,7 +112,7 @@ Data::Forum *Entry::asForum() {
} }
Data::Folder *Entry::asFolder() { Data::Folder *Entry::asFolder() {
return (_flags & Flag::IsThread) return (_flags & (Flag::IsThread | Flag::IsSavedSublist))
? nullptr ? nullptr
: static_cast<Data::Folder*>(this); : static_cast<Data::Folder*>(this);
} }
@ -126,6 +129,12 @@ Data::ForumTopic *Entry::asTopic() {
: nullptr; : nullptr;
} }
Data::SavedSublist *Entry::asSublist() {
return (_flags & Flag::IsSavedSublist)
? static_cast<Data::SavedSublist*>(this)
: nullptr;
}
const History *Entry::asHistory() const { const History *Entry::asHistory() const {
return const_cast<Entry*>(this)->asHistory(); return const_cast<Entry*>(this)->asHistory();
} }
@ -146,6 +155,10 @@ const Data::ForumTopic *Entry::asTopic() const {
return const_cast<Entry*>(this)->asTopic(); return const_cast<Entry*>(this)->asTopic();
} }
const Data::SavedSublist *Entry::asSublist() const {
return const_cast<Entry*>(this)->asSublist();
}
void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) { void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) {
if (!filterId && session().supportMode()) { if (!filterId && session().supportMode()) {
// Force reorder in support mode. // Force reorder in support mode.

View file

@ -25,6 +25,7 @@ class Session;
class Forum; class Forum;
class Folder; class Folder;
class ForumTopic; class ForumTopic;
class SavedSublist;
} // namespace Data } // namespace Data
namespace Ui { namespace Ui {
@ -151,6 +152,7 @@ public:
History, History,
Folder, Folder,
ForumTopic, ForumTopic,
SavedSublist,
}; };
Entry(not_null<Data::Session*> owner, Type type); Entry(not_null<Data::Session*> owner, Type type);
virtual ~Entry(); virtual ~Entry();
@ -163,12 +165,14 @@ public:
Data::Folder *asFolder(); Data::Folder *asFolder();
Data::Thread *asThread(); Data::Thread *asThread();
Data::ForumTopic *asTopic(); Data::ForumTopic *asTopic();
Data::SavedSublist *asSublist();
const History *asHistory() const; const History *asHistory() const;
const Data::Forum *asForum() const; const Data::Forum *asForum() const;
const Data::Folder *asFolder() const; const Data::Folder *asFolder() const;
const Data::Thread *asThread() const; const Data::Thread *asThread() const;
const Data::ForumTopic *asTopic() const; const Data::ForumTopic *asTopic() const;
const Data::SavedSublist *asSublist() const;
PositionChange adjustByPosInChatList( PositionChange adjustByPosInChatList(
FilterId filterId, FilterId filterId,
@ -206,27 +210,29 @@ public:
void setChatListTimeId(TimeId date); void setChatListTimeId(TimeId date);
virtual void updateChatListExistence(); virtual void updateChatListExistence();
bool needUpdateInChatList() const; bool needUpdateInChatList() const;
virtual TimeId adjustedChatListTimeId() const; [[nodiscard]] virtual TimeId adjustedChatListTimeId() const;
virtual int fixedOnTopIndex() const = 0; [[nodiscard]] virtual int fixedOnTopIndex() const = 0;
static constexpr auto kArchiveFixOnTopIndex = 1; static constexpr auto kArchiveFixOnTopIndex = 1;
static constexpr auto kTopPromotionFixOnTopIndex = 2; static constexpr auto kTopPromotionFixOnTopIndex = 2;
virtual bool shouldBeInChatList() const = 0; [[nodiscard]] virtual bool shouldBeInChatList() const = 0;
virtual UnreadState chatListUnreadState() const = 0; [[nodiscard]] virtual UnreadState chatListUnreadState() const = 0;
virtual BadgesState chatListBadgesState() const = 0; [[nodiscard]] virtual BadgesState chatListBadgesState() const = 0;
virtual HistoryItem *chatListMessage() const = 0; [[nodiscard]] virtual HistoryItem *chatListMessage() const = 0;
virtual bool chatListMessageKnown() const = 0; [[nodiscard]] virtual bool chatListMessageKnown() const = 0;
virtual void requestChatListMessage() = 0; [[nodiscard]] virtual const QString &chatListName() const = 0;
virtual const QString &chatListName() const = 0; [[nodiscard]] virtual const QString &chatListNameSortKey() const = 0;
virtual const QString &chatListNameSortKey() const = 0; [[nodiscard]] virtual int chatListNameVersion() const = 0;
virtual const base::flat_set<QString> &chatListNameWords() const = 0; [[nodiscard]] virtual auto chatListNameWords() const
virtual const base::flat_set<QChar> &chatListFirstLetters() const = 0; -> const base::flat_set<QString> & = 0;
[[nodiscard]] virtual auto chatListFirstLetters() const
-> const base::flat_set<QChar> & = 0;
virtual bool folderKnown() const { [[nodiscard]] virtual bool folderKnown() const {
return true; return true;
} }
virtual Data::Folder *folder() const { [[nodiscard]] virtual Data::Folder *folder() const {
return nullptr; return nullptr;
} }
@ -255,8 +261,9 @@ private:
enum class Flag : uchar { enum class Flag : uchar {
IsThread = (1 << 0), IsThread = (1 << 0),
IsHistory = (1 << 1), IsHistory = (1 << 1),
UpdatePostponed = (1 << 2), IsSavedSublist = (1 << 2),
InUnreadChangeBlock = (1 << 3), UpdatePostponed = (1 << 3),
InUnreadChangeBlock = (1 << 4),
}; };
friend inline constexpr bool is_flag_type(Flag) { return true; } friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
@ -265,8 +272,6 @@ private:
void pinnedIndexChanged(FilterId filterId, int was, int now); void pinnedIndexChanged(FilterId filterId, int was, int now);
[[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const;
[[nodiscard]] virtual int chatListNameVersion() const = 0;
void setChatListExistence(bool exists); void setChatListExistence(bool exists);
not_null<Row*> mainChatListLink(FilterId filterId) const; not_null<Row*> mainChatListLink(FilterId filterId) const;
Row *maybeMainChatListLink(FilterId filterId) const; Row *maybeMainChatListLink(FilterId filterId) const;

View file

@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "data/data_cloud_file.h" #include "data/data_cloud_file.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_saved_messages.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_send_action.h" #include "data/data_send_action.h"
@ -219,7 +220,9 @@ InnerWidget::InnerWidget(
session().data().chatsListChanges(), session().data().chatsListChanges(),
session().data().chatsListLoadedEvents() session().data().chatsListLoadedEvents()
) | rpl::filter([=](Data::Folder *folder) { ) | rpl::filter([=](Data::Folder *folder) {
return !_openedForum && (folder == _openedFolder); return !_savedSublists
&& !_openedForum
&& (folder == _openedFolder);
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=] {
refresh(); refresh();
}, lifetime()); }, lifetime());
@ -499,6 +502,8 @@ int InnerWidget::searchInChatSkip() const {
} }
void InnerWidget::changeOpenedFolder(Data::Folder *folder) { void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
Expects(!folder || !_savedSublists);
if (_openedFolder == folder) { if (_openedFolder == folder) {
return; return;
} }
@ -513,6 +518,8 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
} }
void InnerWidget::changeOpenedForum(Data::Forum *forum) { void InnerWidget::changeOpenedForum(Data::Forum *forum) {
Expects(!forum || !_savedSublists);
if (_openedForum == forum) { if (_openedForum == forum) {
return; return;
} }
@ -553,12 +560,39 @@ void InnerWidget::changeOpenedForum(Data::Forum *forum) {
} }
} }
void InnerWidget::showSavedSublists() {
Expects(!_geometryInited);
Expects(!_savedSublists);
_savedSublists = true;
stopReorderPinned();
clearSelection();
_filterId = 0;
_openedForum = nullptr;
_st = &st::defaultDialogRow;
refreshShownList();
_openedForumLifetime.destroy();
//session().data().savedMessages().chatsListChanges(
//) | rpl::start_with_next([=] {
// refresh();
//}, lifetime());
refreshWithCollapsedRows(true);
if (_loadMoreCallback) {
_loadMoreCallback();
}
}
void InnerWidget::paintEvent(QPaintEvent *e) { void InnerWidget::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
p.setInactive( p.setInactive(
_controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any)); _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
if (_controller->contentOverlapped(this, e)) { if (!_savedSublists && _controller->contentOverlapped(this, e)) {
return; return;
} }
const auto activeEntry = _controller->activeChatEntryCurrent(); const auto activeEntry = _controller->activeChatEntryCurrent();
@ -1416,11 +1450,14 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
} }
} }
const std::vector<Key> &InnerWidget::pinnedChatsOrder() const { const std::vector<Key> &InnerWidget::pinnedChatsOrder() const {
return _openedForum const auto owner = &session().data();
? session().data().pinnedChatsOrder(_openedForum) return _savedSublists
? owner->pinnedChatsOrder(&owner->savedMessages())
: _openedForum
? owner->pinnedChatsOrder(_openedForum)
: _filterId : _filterId
? session().data().pinnedChatsOrder(_filterId) ? owner->pinnedChatsOrder(_filterId)
: session().data().pinnedChatsOrder(_openedFolder); : owner->pinnedChatsOrder(_openedFolder);
} }
void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
@ -1473,7 +1510,9 @@ void InnerWidget::savePinnedOrder() {
return; // Something has changed in the set of pinned chats. return; // Something has changed in the set of pinned chats.
} }
} }
if (_openedForum) { if (_savedSublists) {
session().api().savePinnedOrder(&session().data().savedMessages());
} else if (_openedForum) {
session().api().savePinnedOrder(_openedForum); session().api().savePinnedOrder(_openedForum);
} else if (_filterId) { } else if (_filterId) {
Api::SaveNewFilterPinned(&session(), _filterId); Api::SaveNewFilterPinned(&session(), _filterId);
@ -1577,7 +1616,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) {
const auto delta = [&] { const auto delta = [&] {
if (localPosition.y() < _visibleTop) { if (localPosition.y() < _visibleTop) {
return localPosition.y() - _visibleTop; return localPosition.y() - _visibleTop;
} else if ((_openedFolder || _openedForum || _filterId) } else if ((_savedSublists || _openedFolder || _openedForum || _filterId)
&& localPosition.y() > _visibleBottom) { && localPosition.y() > _visibleBottom) {
return localPosition.y() - _visibleBottom; return localPosition.y() - _visibleBottom;
} }
@ -1832,6 +1871,8 @@ void InnerWidget::handleChatListEntryRefreshes() {
return false; return false;
} else if (const auto topic = event.key.topic()) { } else if (const auto topic = event.key.topic()) {
return (topic->forum() == _openedForum); return (topic->forum() == _openedForum);
} else if (event.key.sublist()) {
return _savedSublists;
} else { } else {
return !_openedForum; return !_openedForum;
} }
@ -1848,6 +1889,8 @@ void InnerWidget::handleChatListEntryRefreshes() {
&& (_state == WidgetState::Default) && (_state == WidgetState::Default)
&& (key.topic() && (key.topic()
? (key.topic()->forum() == _openedForum) ? (key.topic()->forum() == _openedForum)
: key.sublist()
? _savedSublists
: (entry->folder() == _openedFolder))) { : (entry->folder() == _openedFolder))) {
_dialogMoved.fire({ from, to }); _dialogMoved.fire({ from, to });
} }
@ -2051,7 +2094,11 @@ void InnerWidget::enterEventHook(QEnterEvent *e) {
Row *InnerWidget::shownRowByKey(Key key) { Row *InnerWidget::shownRowByKey(Key key) {
const auto entry = key.entry(); const auto entry = key.entry();
if (_openedForum) { if (_savedSublists) {
if (!entry->asSublist()) {
return nullptr;
}
} else if (_openedForum) {
const auto topic = entry->asTopic(); const auto topic = entry->asTopic();
if (!topic || topic->forum() != _openedForum) { if (!topic || topic->forum() != _openedForum) {
return nullptr; return nullptr;
@ -2114,7 +2161,9 @@ void InnerWidget::updateSelectedRow(Key key) {
} }
void InnerWidget::refreshShownList() { void InnerWidget::refreshShownList() {
const auto list = _openedForum const auto list = _savedSublists
? session().data().savedMessages().chatsList()->indexed()
: _openedForum
? _openedForum->topicsList()->indexed() ? _openedForum->topicsList()->indexed()
: _filterId : _filterId
? session().data().chatsFilters().chatsList(_filterId)->indexed() ? session().data().chatsFilters().chatsList(_filterId)->indexed()
@ -2294,15 +2343,19 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
} }
}; };
if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) { if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) {
if (_openedForum) { if (_savedSublists) {
const auto owner = &session().data();
append(owner->savedMessages().chatsList()->indexed());
} else if (_openedForum) {
append(_openedForum->topicsList()->indexed()); append(_openedForum->topicsList()->indexed());
} else { } else {
append(session().data().chatsList()->indexed()); const auto owner = &session().data();
append(owner->chatsList()->indexed());
const auto id = Data::Folder::kId; const auto id = Data::Folder::kId;
if (const auto add = session().data().folderLoaded(id)) { if (const auto add = owner->folderLoaded(id)) {
append(add->chatsList()->indexed()); append(add->chatsList()->indexed());
} }
append(session().data().contactsNoChatsList()); append(owner->contactsNoChatsList());
} }
} }
refresh(true); refresh(true);
@ -2759,6 +2812,10 @@ void InnerWidget::refreshEmptyLabel() {
const auto data = &session().data(); const auto data = &session().data();
const auto state = !_shownList->empty() const auto state = !_shownList->empty()
? EmptyState::None ? EmptyState::None
: _savedSublists
? (data->savedMessages().chatsList()->loaded()
? EmptyState::EmptySavedSublists
: EmptyState::Loading)
: _openedForum : _openedForum
? (_openedForum->topicsList()->loaded() ? (_openedForum->topicsList()->loaded()
? EmptyState::EmptyForum ? EmptyState::EmptyForum
@ -2783,6 +2840,8 @@ void InnerWidget::refreshEmptyLabel() {
? tr::lng_no_chats_filter() ? tr::lng_no_chats_filter()
: (state == EmptyState::EmptyForum) : (state == EmptyState::EmptyForum)
? tr::lng_forum_no_topics() ? tr::lng_forum_no_topics()
: (state == EmptyState::EmptySavedSublists)
? tr::lng_no_saved_sublists()
: tr::lng_contacts_loading(); : tr::lng_contacts_loading();
auto link = (state == EmptyState::NoContacts) auto link = (state == EmptyState::NoContacts)
? tr::lng_add_contact_button() ? tr::lng_add_contact_button()

View file

@ -107,6 +107,7 @@ public:
void changeOpenedFolder(Data::Folder *folder); void changeOpenedFolder(Data::Folder *folder);
void changeOpenedForum(Data::Forum *forum); void changeOpenedForum(Data::Forum *forum);
void showSavedSublists();
void selectSkip(int32 direction); void selectSkip(int32 direction);
void selectSkipPage(int32 pixels, int32 direction); void selectSkipPage(int32 pixels, int32 direction);
@ -198,6 +199,7 @@ private:
NoContacts, NoContacts,
EmptyFolder, EmptyFolder,
EmptyForum, EmptyForum,
EmptySavedSublists,
}; };
struct PinnedRow { struct PinnedRow {
@ -503,6 +505,8 @@ private:
float64 _narrowRatio = 0.; float64 _narrowRatio = 0.;
bool _geometryInited = false; bool _geometryInited = false;
bool _savedSublists = false;
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;
}; };

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_saved_sublist.h"
#include "history/history.h" #include "history/history.h"
namespace Dialogs { namespace Dialogs {
@ -25,6 +26,9 @@ Key::Key(Data::Thread *thread) : _value(thread) {
Key::Key(Data::ForumTopic *topic) : _value(topic) { Key::Key(Data::ForumTopic *topic) : _value(topic) {
} }
Key::Key(Data::SavedSublist *sublist) : _value(sublist) {
}
Key::Key(not_null<History*> history) : _value(history) { Key::Key(not_null<History*> history) : _value(history) {
} }
@ -37,6 +41,9 @@ Key::Key(not_null<Data::Folder*> folder) : _value(folder) {
Key::Key(not_null<Data::ForumTopic*> topic) : _value(topic) { Key::Key(not_null<Data::ForumTopic*> topic) : _value(topic) {
} }
Key::Key(not_null<Data::SavedSublist*> sublist) : _value(sublist) {
}
not_null<Entry*> Key::entry() const { not_null<Entry*> Key::entry() const {
Expects(_value != nullptr); Expects(_value != nullptr);
@ -59,6 +66,10 @@ Data::Thread *Key::thread() const {
return _value ? _value->asThread() : nullptr; return _value ? _value->asThread() : nullptr;
} }
Data::SavedSublist *Key::sublist() const {
return _value ? _value->asSublist() : nullptr;
}
History *Key::owningHistory() const { History *Key::owningHistory() const {
if (const auto thread = this->thread()) { if (const auto thread = this->thread()) {
return thread->owningHistory(); return thread->owningHistory();

View file

@ -14,6 +14,7 @@ namespace Data {
class Thread; class Thread;
class Folder; class Folder;
class ForumTopic; class ForumTopic;
class SavedSublist;
} // namespace Data } // namespace Data
namespace Dialogs { namespace Dialogs {
@ -29,12 +30,14 @@ public:
Key(Data::Folder *folder); Key(Data::Folder *folder);
Key(Data::Thread *thread); Key(Data::Thread *thread);
Key(Data::ForumTopic *topic); Key(Data::ForumTopic *topic);
Key(Data::SavedSublist *sublist);
Key(not_null<Entry*> entry) : _value(entry) { Key(not_null<Entry*> entry) : _value(entry) {
} }
Key(not_null<History*> history); Key(not_null<History*> history);
Key(not_null<Data::Thread*> thread); Key(not_null<Data::Thread*> thread);
Key(not_null<Data::Folder*> folder); Key(not_null<Data::Folder*> folder);
Key(not_null<Data::ForumTopic*> topic); Key(not_null<Data::ForumTopic*> topic);
Key(not_null<Data::SavedSublist*> sublist);
explicit operator bool() const { explicit operator bool() const {
return (_value != nullptr); return (_value != nullptr);
@ -46,6 +49,7 @@ public:
[[nodiscard]] Data::Thread *thread() const; [[nodiscard]] Data::Thread *thread() const;
[[nodiscard]] History *owningHistory() const; [[nodiscard]] History *owningHistory() const;
[[nodiscard]] PeerData *peer() const; [[nodiscard]] PeerData *peer() const;
[[nodiscard]] Data::SavedSublist *sublist() const;
friend inline constexpr auto operator<=>(Key, Key) noexcept = default; friend inline constexpr auto operator<=>(Key, Key) noexcept = default;
@ -102,6 +106,7 @@ struct EntryState {
Scheduled, Scheduled,
Pinned, Pinned,
Replies, Replies,
SavedSublist,
ContextMenu, ContextMenu,
}; };

View file

@ -261,8 +261,10 @@ void Row::recountHeight(float64 narrowRatio) {
: st::defaultDialogRow.height; : st::defaultDialogRow.height;
} else if (_id.folder()) { } else if (_id.folder()) {
_height = st::defaultDialogRow.height; _height = st::defaultDialogRow.height;
} else { } else if (_id.topic()) {
_height = st::forumTopicRow.height; _height = st::forumTopicRow.height;
} else {
_height = st::defaultDialogRow.height;
} }
} }

View file

@ -131,6 +131,9 @@ public:
[[nodiscard]] Data::Thread *thread() const { [[nodiscard]] Data::Thread *thread() const {
return _id.thread(); return _id.thread();
} }
[[nodiscard]] Data::SavedSublist *sublist() const {
return _id.sublist();
}
[[nodiscard]] not_null<Entry*> entry() const { [[nodiscard]] not_null<Entry*> entry() const {
return _id.entry(); return _id.entry();
} }

View file

@ -1774,6 +1774,7 @@ bool Widget::searchMessages(bool searchCache) {
(_searchQueryFrom (_searchQueryFrom
? _searchQueryFrom->input ? _searchQueryFrom->input
: MTP_inputPeerEmpty()), : MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTP_int(topic ? topic->rootId() : 0), MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date
@ -2020,6 +2021,7 @@ void Widget::searchMore() {
(_searchQueryFrom (_searchQueryFrom
? _searchQueryFrom->input ? _searchQueryFrom->input
: MTP_inputPeerEmpty()), : MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTP_int(topic ? topic->rootId() : 0), MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date
@ -2092,6 +2094,7 @@ void Widget::searchMore() {
(_searchQueryFrom (_searchQueryFrom
? _searchQueryFrom->input ? _searchQueryFrom->input
: MTP_inputPeerEmpty()), : MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date
@ -2708,7 +2711,7 @@ void Widget::filterCursorMoved() {
} }
void Widget::completeHashtag(QString tag) { void Widget::completeHashtag(QString tag) {
const auto t = _filter->getLastText();; const auto t = _filter->getLastText();
auto cur = _filter->textCursor().position(); auto cur = _filter->textCursor().position();
auto hashtag = QString(); auto hashtag = QString();
for (int start = cur; start > 0;) { for (int start = cur; start > 0;) {

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_drafts.h" #include "data/data_drafts.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "dialogs/dialogs_list.h" #include "dialogs/dialogs_list.h"
#include "dialogs/dialogs_three_state_icon.h" #include "dialogs/dialogs_three_state_icon.h"
@ -265,10 +266,12 @@ void PaintFolderEntryText(
} }
enum class Flag { enum class Flag {
SavedMessages = 0x08, SavedMessages = 0x008,
RepliesMessages = 0x10, RepliesMessages = 0x010,
AllowUserOnline = 0x20, AllowUserOnline = 0x020,
TopicJumpRipple = 0x40, TopicJumpRipple = 0x040,
HiddenAuthor = 0x080,
MyNotes = 0x100,
}; };
inline constexpr bool is_flag_type(Flag) { return true; } inline constexpr bool is_flag_type(Flag) { return true; }
@ -311,6 +314,7 @@ void PaintRow(
const auto history = entry->asHistory(); const auto history = entry->asHistory();
const auto thread = entry->asThread(); const auto thread = entry->asThread();
const auto sublist = entry->asSublist();
if (flags & Flag::SavedMessages) { if (flags & Flag::SavedMessages) {
EmptyUserpic::PaintSavedMessages( EmptyUserpic::PaintSavedMessages(
@ -326,6 +330,20 @@ void PaintRow(
context.st->padding.top(), context.st->padding.top(),
context.width, context.width,
context.st->photoSize); context.st->photoSize);
} else if (flags & Flag::HiddenAuthor) {
EmptyUserpic::PaintHiddenAuthor(
p,
context.st->padding.left(),
context.st->padding.top(),
context.width,
context.st->photoSize);
} else if (flags & Flag::MyNotes) {
EmptyUserpic::PaintMyNotes(
p,
context.st->padding.left(),
context.st->padding.top(),
context.width,
context.st->photoSize);
} else if (!from && hiddenSenderInfo) { } else if (!from && hiddenSenderInfo) {
hiddenSenderInfo->emptyUserpic.paintCircle( hiddenSenderInfo->emptyUserpic.paintCircle(
p, p,
@ -547,7 +565,7 @@ void PaintRow(
// Empty history // Empty history
} }
} else if (!item->isEmpty()) { } else if (!item->isEmpty()) {
if (thread && !promoted) { if ((thread || sublist) && !promoted) {
PaintRowDate(p, date, rectForName, context); PaintRowDate(p, date, rectForName, context);
} }
@ -606,10 +624,18 @@ void PaintRow(
} }
p.setFont(st::semiboldFont); p.setFont(st::semiboldFont);
if (flags & (Flag::SavedMessages | Flag::RepliesMessages)) { if (flags
& (Flag::SavedMessages
| Flag::RepliesMessages
| Flag::HiddenAuthor
| Flag::MyNotes)) {
auto text = (flags & Flag::SavedMessages) auto text = (flags & Flag::SavedMessages)
? tr::lng_saved_messages(tr::now) ? tr::lng_saved_messages(tr::now)
: tr::lng_replies_messages(tr::now); : (flags & Flag::RepliesMessages)
? tr::lng_replies_messages(tr::now)
: (flags & Flag::MyNotes)
? tr::lng_my_notes(tr::now)
: tr::lng_hidden_author_messages(tr::now);
const auto textWidth = st::semiboldFont->width(text); const auto textWidth = st::semiboldFont->width(text);
if (textWidth > rectForName.width()) { if (textWidth > rectForName.width()) {
text = st::semiboldFont->elided(text, rectForName.width()); text = st::semiboldFont->elided(text, rectForName.width());
@ -621,7 +647,7 @@ void PaintRow(
: st::dialogsNameFg); : st::dialogsNameFg);
p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text); p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text);
} else if (from) { } else if (from) {
if (history && !context.search) { if ((history || sublist) && !context.search) {
const auto badgeWidth = fromBadge.drawGetWidth( const auto badgeWidth = fromBadge.drawGetWidth(
p, p,
rectForName, rectForName,
@ -732,6 +758,7 @@ void RowPainter::Paint(
const auto entry = row->entry(); const auto entry = row->entry();
const auto history = row->history(); const auto history = row->history();
const auto thread = row->thread(); const auto thread = row->thread();
const auto sublist = row->sublist();
const auto peer = history ? history->peer.get() : nullptr; const auto peer = history ? history->peer.get() : nullptr;
const auto badgesState = entry->chatListBadgesState(); const auto badgesState = entry->chatListBadgesState();
entry->chatListPreloadData(); // Allow chat list message resolve. entry->chatListPreloadData(); // Allow chat list message resolve.
@ -771,11 +798,22 @@ void RowPainter::Paint(
? (history->peer->migrateTo() ? (history->peer->migrateTo()
? history->peer->migrateTo() ? history->peer->migrateTo()
: history->peer.get()) : history->peer.get())
: sublist
? sublist->peer().get()
: nullptr; : nullptr;
const auto allowUserOnline = true;// !context.narrow || badgesState.empty(); const auto allowUserOnline = true;// !context.narrow || badgesState.empty();
const auto flags = (allowUserOnline ? Flag::AllowUserOnline : Flag(0)) const auto flags = (allowUserOnline ? Flag::AllowUserOnline : Flag(0))
| (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0)) | ((sublist && from->isSelf())
| (peer && peer->isRepliesChat() ? Flag::RepliesMessages : Flag(0)) ? Flag::MyNotes
: (peer && peer->isSelf())
? Flag::SavedMessages
: Flag(0))
| ((from && from->isRepliesChat())
? Flag::RepliesMessages
: Flag(0))
| ((sublist && from->isSavedHiddenAuthor())
? Flag::HiddenAuthor
: Flag(0))
| (row->topicJumpRipple() ? Flag::TopicJumpRipple : Flag(0)); | (row->topicJumpRipple() ? Flag::TopicJumpRipple : Flag(0));
const auto paintItemCallback = [&](int nameleft, int namewidth) { const auto paintItemCallback = [&](int nameleft, int namewidth) {
const auto texttop = context.st->textTop; const auto texttop = context.st->textTop;
@ -810,6 +848,8 @@ void RowPainter::Paint(
? nullptr ? nullptr
: thread : thread
? &thread->lastItemDialogsView() ? &thread->lastItemDialogsView()
: sublist
? &sublist->lastItemDialogsView()
: nullptr; : nullptr;
if (view) { if (view) {
const auto forum = context.st->topicsHeight const auto forum = context.st->topicsHeight
@ -872,7 +912,9 @@ void RowPainter::Paint(
if (const auto peer = searchChat.peer()) { if (const auto peer = searchChat.peer()) {
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) { if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
if (peer->isSelf() || forwarded->imported) { if (peer->isSelf() || forwarded->imported) {
return forwarded->hiddenSenderInfo.get(); return forwarded->savedFromHiddenSenderInfo.get()
? forwarded->savedFromHiddenSenderInfo.get()
: forwarded->originalHiddenSenderInfo.get();
} }
} }
} }

View file

@ -36,7 +36,6 @@ constexpr auto kCollapseAfterRatio = 0.68;
constexpr auto kFrictionRatio = 0.15; constexpr auto kFrictionRatio = 0.15;
constexpr auto kExpandCatchUpDuration = crl::time(200); constexpr auto kExpandCatchUpDuration = crl::time(200);
constexpr auto kMaxTooltipNames = 3; constexpr auto kMaxTooltipNames = 3;
constexpr auto kStoriesTooltipHideBgOpacity = 0.2;
[[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) { [[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) {
const auto &full = st.full; const auto &full = st.full;

View file

@ -1624,6 +1624,7 @@ void ApiWrap::requestChatMessages(
realPeerInput, realPeerInput,
MTP_string(), // query MTP_string(), // query
MTP_inputPeerSelf(), MTP_inputPeerSelf(),
MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date

View file

@ -446,7 +446,6 @@ QByteArray SerializeMessage(
pushAction("send_payment"); pushAction("send_payment");
push("amount", data.amount); push("amount", data.amount);
push("currency", data.currency); push("currency", data.currency);
const auto amount = FormatMoneyAmount(data.amount, data.currency);
pushReplyToMsgId("invoice_message_id"); pushReplyToMsgId("invoice_message_id");
if (data.recurringUsed) { if (data.recurringUsed) {
push("recurring", "used"); push("recurring", "used");

View file

@ -134,8 +134,9 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) {
} }
DEBUG_LOG(("Video Info: " DEBUG_LOG(("Video Info: "
"Trying \"%1\" hardware acceleration for \"%2\" decoder." "Trying \"%1\" hardware acceleration for \"%2\" decoder."
).arg(av_hwdevice_get_type_name(type) ).arg(
).arg(context->codec->name)); av_hwdevice_get_type_name(type),
context->codec->name));
if (parent->hw_device_ctx) { if (parent->hw_device_ctx) {
av_buffer_unref(&parent->hw_device_ctx); av_buffer_unref(&parent->hw_device_ctx);
} }

View file

@ -442,7 +442,6 @@ void InnerWidget::applyFilter(FilterValue &&value) {
} }
void InnerWidget::applySearch(const QString &query) { void InnerWidget::applySearch(const QString &query) {
auto clearQuery = query.trimmed();
if (_searchQuery != query) { if (_searchQuery != query) {
_searchQuery = query; _searchQuery = query;
clearAndRequestLog(); clearAndRequestLog();

View file

@ -117,6 +117,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) {
data.vid(), data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
data.vpeer_id(), data.vpeer_id(),
MTPPeer(), // saved_peer_id
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(data.vvia_bot_id().value_or_empty()), MTP_long(data.vvia_bot_id().value_or_empty()),
MTPMessageReplyHeader(), MTPMessageReplyHeader(),

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_drafts.h" #include "data/data_drafts.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_channel_admins.h" #include "data/data_channel_admins.h"
@ -614,6 +615,11 @@ not_null<HistoryItem*> History::addNewItem(
addNewToBack(item, unread); addNewToBack(item, unread);
checkForLoadedAtTop(item); checkForLoadedAtTop(item);
} }
if (const auto sublist = item->savedSublist()) {
sublist->applyMaybeLast(item, unread);
}
return item; return item;
} }
@ -1436,6 +1442,12 @@ void History::addCreatedOlderSlice(
if (loadedAtBottom()) { if (loadedAtBottom()) {
// Add photos to overview and authors to lastAuthors. // Add photos to overview and authors to lastAuthors.
addItemsToLists(items); addItemsToLists(items);
for (const auto &item : items) {
if (const auto sublist = item->savedSublist()) {
sublist->applyMaybeLast(item);
}
}
} }
addToSharedMedia(items); addToSharedMedia(items);
} }

View file

@ -365,6 +365,7 @@ public:
void takeLocalDraft(not_null<History*> from); void takeLocalDraft(not_null<History*> from);
void applyCloudDraft(MsgId topicRootId); void applyCloudDraft(MsgId topicRootId);
void draftSavedToCloud(MsgId topicRootId); void draftSavedToCloud(MsgId topicRootId);
void requestChatListMessage();
[[nodiscard]] const Data::ForwardDraft &forwardDraft( [[nodiscard]] const Data::ForwardDraft &forwardDraft(
MsgId topicRootId) const; MsgId topicRootId) const;
@ -383,9 +384,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override; Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override; HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override; bool chatListMessageKnown() const override;
void requestChatListMessage() override;
const QString &chatListName() const override; const QString &chatListName() const override;
const QString &chatListNameSortKey() const override; const QString &chatListNameSortKey() const override;
int chatListNameVersion() const override;
const base::flat_set<QString> &chatListNameWords() const override; const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override; const base::flat_set<QChar> &chatListFirstLetters() const override;
void chatListPreloadData() override; void chatListPreloadData() override;
@ -589,8 +590,6 @@ private:
[[nodiscard]] Dialogs::UnreadState computeUnreadState() const; [[nodiscard]] Dialogs::UnreadState computeUnreadState() const;
void setFolderPointer(Data::Folder *folder); void setFolderPointer(Data::Folder *folder);
int chatListNameVersion() const override;
void hasUnreadMentionChanged(bool has) override; void hasUnreadMentionChanged(bool has) override;
void hasUnreadReactionChanged(bool has) override; void hasUnreadReactionChanged(bool has) override;

View file

@ -1223,7 +1223,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
width(), width(),
st::msgPhotoSize, st::msgPhotoSize,
context.paused); context.paused);
} else if (const auto info = item->hiddenSenderInfo()) { } else if (const auto info = item->displayHiddenSenderInfo()) {
if (info->customUserpic.empty()) { if (info->customUserpic.empty()) {
info->emptyUserpic.paintCircle( info->emptyUserpic.paintCircle(
p, p,

View file

@ -41,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_updates.h" #include "api/api_updates.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/data_bot_app.h" #include "data/data_bot_app.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_scheduled_messages.h" #include "data/data_scheduled_messages.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -150,10 +152,16 @@ struct HistoryItem::CreateConfig {
QString originalSenderName; QString originalSenderName;
QString originalPostAuthor; QString originalPostAuthor;
PeerId savedSublistPeer = 0;
QString forwardPsaType; QString forwardPsaType;
PeerId savedFromPeer = 0; PeerId savedFromPeer = 0;
MsgId savedFromMsgId = 0; MsgId savedFromMsgId = 0;
PeerId savedFromSenderId = 0;
QString savedFromSenderName;
bool savedFromOutgoing = false;
TimeId editDate = 0; TimeId editDate = 0;
HistoryMessageMarkupData markup; HistoryMessageMarkupData markup;
HistoryMessageRepliesData replies; HistoryMessageRepliesData replies;
@ -180,6 +188,13 @@ void HistoryItem::FillForwardedInfo(
config.savedFromPeer = peerFromMTP(*savedFromPeer); config.savedFromPeer = peerFromMTP(*savedFromPeer);
config.savedFromMsgId = savedFromMsgId->v; config.savedFromMsgId = savedFromMsgId->v;
} }
config.savedFromSenderId = data.vsaved_from_id()
? peerFromMTP(*data.vsaved_from_id())
: PeerId();
config.savedFromSenderName = qs(
data.vsaved_from_name().value_or_empty());
config.savedFromOutgoing = data.is_saved_out();
config.imported = data.is_imported(); config.imported = data.is_imported();
} }
@ -259,7 +274,8 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
item, item,
item->history()->owner().processDocument(document), item->history()->owner().processDocument(document),
media.is_nopremium(), media.is_nopremium(),
media.is_spoiler()); media.is_spoiler(),
media.vttl_seconds().value_or_empty());
}, [](const MTPDdocumentEmpty &) -> Result { }, [](const MTPDdocumentEmpty &) -> Result {
return nullptr; return nullptr;
}); });
@ -491,7 +507,7 @@ HistoryItem::HistoryItem(
} }
if (!dropForwardInfo) { if (!dropForwardInfo) {
config.originalDate = original->originalDate(); config.originalDate = original->originalDate();
if (const auto info = original->hiddenSenderInfo()) { if (const auto info = original->originalHiddenSenderInfo()) {
config.originalSenderName = info->name; config.originalSenderName = info->name;
} else if (const auto originalSender = original->originalSender()) { } else if (const auto originalSender = original->originalSender()) {
config.originalSenderId = originalSender->id; config.originalSenderId = originalSender->id;
@ -515,6 +531,11 @@ HistoryItem::HistoryItem(
config.savedFromPeer = original->history()->peer->id; config.savedFromPeer = original->history()->peer->id;
config.savedFromMsgId = original->id; config.savedFromMsgId = original->id;
//} //}
config.savedFromOutgoing = original->out();
config.savedFromSenderId = original->Get<HistoryMessageForwarded>()
? original->author()->id
: PeerId();
} }
if (flags & MessageFlag::HasPostAuthor) { if (flags & MessageFlag::HasPostAuthor) {
config.postAuthor = postAuthor; config.postAuthor = postAuthor;
@ -634,7 +655,8 @@ HistoryItem::HistoryItem(
this, this,
document, document,
skipPremiumEffect, skipPremiumEffect,
spoiler); spoiler,
/*ttlSeconds = */0);
setText(caption); setText(caption);
} }
@ -790,6 +812,9 @@ HistoryItem::~HistoryItem() {
if (const auto reply = Get<HistoryMessageReply>()) { if (const auto reply = Get<HistoryMessageReply>()) {
reply->clearData(this); reply->clearData(this);
} }
if (const auto saved = Get<HistoryMessageSaved>()) {
saved->sublist->removeOne(this);
}
clearDependencyMessage(); clearDependencyMessage();
applyTTL(0); applyTTL(0);
} }
@ -1214,10 +1239,10 @@ PeerData *HistoryItem::computeDisplayFrom() const {
if (const auto sender = discussionPostOriginalSender()) { if (const auto sender = discussionPostOriginalSender()) {
return sender; return sender;
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) { } else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
if (_history->peer->isSelf() if (showForwardsFromSender(forwarded)) {
|| _history->peer->isRepliesChat() return forwarded->forwardOfForward()
|| forwarded->imported) { ? forwarded->savedFromSender
return forwarded->originalSender; : forwarded->originalSender;
} }
} }
return author().get(); return author().get();
@ -1234,10 +1259,10 @@ PeerData *HistoryItem::displayFrom() const {
uint8 HistoryItem::colorIndex() const { uint8 HistoryItem::colorIndex() const {
if (const auto from = displayFrom()) { if (const auto from = displayFrom()) {
return from->colorIndex(); return from->colorIndex();
} else if (const auto info = hiddenSenderInfo()) { } else if (const auto info = displayHiddenSenderInfo()) {
return info->colorIndex; return info->colorIndex;
} }
Unexpected("No displayFrom and no hiddenSenderInfo."); Unexpected("No displayFrom and no displayHiddenSenderInfo.");
} }
std::unique_ptr<HistoryView::Element> HistoryItem::createView( std::unique_ptr<HistoryView::Element> HistoryItem::createView(
@ -1260,6 +1285,9 @@ void HistoryItem::invalidateChatListEntry() {
if (const auto topic = this->topic()) { if (const auto topic = this->topic()) {
topic->lastItemDialogsView().itemInvalidated(this); topic->lastItemDialogsView().itemInvalidated(this);
} }
if (const auto sublist = savedSublist()) {
sublist->lastItemDialogsView().itemInvalidated(this);
}
} }
void HistoryItem::customEmojiRepaint() { void HistoryItem::customEmojiRepaint() {
@ -1680,7 +1708,8 @@ void HistoryItem::setStoryFields(not_null<Data::Story*> story) {
this, this,
document, document,
/*skipPremiumEffect=*/false, /*skipPremiumEffect=*/false,
spoiler); spoiler,
/*ttlSeconds = */0);
} }
setText(story->caption()); setText(story->caption());
} }
@ -2510,16 +2539,32 @@ PeerData *HistoryItem::originalSender() const {
return forwarded->originalSender; return forwarded->originalSender;
} }
const auto peer = _history->peer; const auto peer = _history->peer;
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from(); return peer->isBroadcast() ? peer : from();
} }
const HiddenSenderInfo *HistoryItem::hiddenSenderInfo() const { const HiddenSenderInfo *HistoryItem::originalHiddenSenderInfo() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) { if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->hiddenSenderInfo.get(); return forwarded->originalHiddenSenderInfo.get();
} }
return nullptr; return nullptr;
} }
const HiddenSenderInfo *HistoryItem::displayHiddenSenderInfo() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->savedFromHiddenSenderInfo
? forwarded->savedFromHiddenSenderInfo.get()
: forwarded->originalHiddenSenderInfo.get();
}
return nullptr;
}
bool HistoryItem::showForwardsFromSender(
not_null<const HistoryMessageForwarded*> forwarded) const {
const auto peer = history()->peer;
return !forwarded->story
&& (peer->isSelf() || peer->isRepliesChat() || forwarded->imported);
}
not_null<PeerData*> HistoryItem::fromOriginal() const { not_null<PeerData*> HistoryItem::fromOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) { if (const auto forwarded = Get<HistoryMessageForwarded>()) {
if (forwarded->originalSender) { if (forwarded->originalSender) {
@ -3104,6 +3149,34 @@ bool HistoryItem::isEmpty() const {
&& !Has<HistoryMessageLogEntryOriginal>(); && !Has<HistoryMessageLogEntryOriginal>();
} }
Data::SavedSublist *HistoryItem::savedSublist() const {
if (const auto saved = Get<HistoryMessageSaved>()) {
return saved->sublist;
}
return nullptr;
}
PeerData *HistoryItem::savedSublistPeer() const {
if (const auto sublist = savedSublist()) {
return sublist->peer();
}
return nullptr;
}
PeerData *HistoryItem::savedFromSender() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->savedFromSender;
}
return nullptr;
}
const HiddenSenderInfo *HistoryItem::savedFromHiddenSenderInfo() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->savedFromHiddenSenderInfo.get();
}
return nullptr;
}
TextWithEntities HistoryItem::notificationText( TextWithEntities HistoryItem::notificationText(
NotificationTextOptions options) const { NotificationTextOptions options) const {
auto result = [&] { auto result = [&] {
@ -3156,16 +3229,25 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
? tr::lng_from_you(tr::now) ? tr::lng_from_you(tr::now)
: sender->shortName(); : sender->shortName();
}; };
result.icon = (Get<HistoryMessageForwarded>() != nullptr) const auto forwarded = Get<HistoryMessageForwarded>();
const auto forwardFromSender = forwarded
&& showForwardsFromSender(forwarded);
result.icon = (forwarded
&& (!forwardFromSender || forwarded->forwardOfForward()))
? ItemPreview::Icon::ForwardedMessage ? ItemPreview::Icon::ForwardedMessage
: replyToStory().valid() : replyToStory().valid()
? ItemPreview::Icon::ReplyToStory ? ItemPreview::Icon::ReplyToStory
: ItemPreview::Icon::None; : ItemPreview::Icon::None;
const auto fromForwarded = [&]() -> std::optional<QString> { const auto fromForwarded = [&]() -> std::optional<QString> {
if (const auto forwarded = Get<HistoryMessageForwarded>()) { if (forwarded) {
return forwarded->originalSender const auto sender = forwarded->forwardOfForward()
? fromSender(forwarded->originalSender) ? forwarded->savedFromSender
: forwarded->hiddenSenderInfo->name; : forwarded->originalSender;
return sender
? fromSender(sender)
: forwarded->savedFromHiddenSenderInfo
? forwarded->savedFromHiddenSenderInfo->name
: forwarded->originalHiddenSenderInfo->name;
} }
return {}; return {};
}; };
@ -3255,9 +3337,28 @@ void HistoryItem::createComponents(CreateConfig &&config) {
} else if (config.inlineMarkup) { } else if (config.inlineMarkup) {
mask |= HistoryMessageReplyMarkup::Bit(); mask |= HistoryMessageReplyMarkup::Bit();
} }
if (_history->peer->isSelf()) {
mask |= HistoryMessageSaved::Bit();
}
UpdateComponents(mask); UpdateComponents(mask);
if (const auto saved = Get<HistoryMessageSaved>()) {
if (!config.savedSublistPeer) {
if (config.savedFromPeer) {
config.savedSublistPeer = config.savedFromPeer;
} else if (config.originalSenderId) {
config.savedSublistPeer = config.originalSenderId;
} else if (!config.originalSenderName.isEmpty()) {
config.savedSublistPeer = PeerData::kSavedHiddenAuthorId;
} else {
config.savedSublistPeer = _history->session().userPeerId();
}
}
const auto peer = _history->owner().peer(config.savedSublistPeer);
saved->sublist = _history->owner().savedMessages().sublist(peer);
}
if (const auto reply = Get<HistoryMessageReply>()) { if (const auto reply = Get<HistoryMessageReply>()) {
reply->set(std::move(config.reply)); reply->set(std::move(config.reply));
if (!reply->updateData(this)) { if (!reply->updateData(this)) {
@ -3343,9 +3444,10 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
? _history->owner().peer(originalSender).get() ? _history->owner().peer(originalSender).get()
: nullptr; : nullptr;
if (!forwarded->originalSender) { if (!forwarded->originalSender) {
forwarded->hiddenSenderInfo = std::make_unique<HiddenSenderInfo>( forwarded->originalHiddenSenderInfo
config.originalSenderName, = std::make_unique<HiddenSenderInfo>(
config.imported); config.originalSenderName,
config.imported);
} }
forwarded->originalId = config.originalId; forwarded->originalId = config.originalId;
forwarded->originalPostAuthor = config.originalPostAuthor; forwarded->originalPostAuthor = config.originalPostAuthor;
@ -3353,6 +3455,14 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
forwarded->savedFromPeer = _history->owner().peerLoaded( forwarded->savedFromPeer = _history->owner().peerLoaded(
config.savedFromPeer); config.savedFromPeer);
forwarded->savedFromMsgId = config.savedFromMsgId; forwarded->savedFromMsgId = config.savedFromMsgId;
forwarded->savedFromSender = _history->owner().peerLoaded(
config.savedFromSenderId);
forwarded->savedFromOutgoing = config.savedFromOutgoing;
if (!forwarded->savedFromSender
&& !config.savedFromSenderName.isEmpty()) {
forwarded->savedFromHiddenSenderInfo
= std::make_unique<HiddenSenderInfo>(config.savedFromSenderName, false);
}
forwarded->imported = config.imported; forwarded->imported = config.imported;
} }
@ -3551,6 +3661,9 @@ void HistoryItem::applyTTL(const MTPDmessageService &data) {
void HistoryItem::createComponents(const MTPDmessage &data) { void HistoryItem::createComponents(const MTPDmessage &data) {
auto config = CreateConfig(); auto config = CreateConfig();
config.savedSublistPeer = data.vsaved_peer_id()
? peerFromMTP(*data.vsaved_peer_id())
: PeerId();
if (const auto forwarded = data.vfwd_from()) { if (const auto forwarded = data.vfwd_from()) {
forwarded->match([&](const MTPDmessageFwdHeader &data) { forwarded->match([&](const MTPDmessageFwdHeader &data) {
FillForwardedInfo(config, data); FillForwardedInfo(config, data);
@ -3636,25 +3749,43 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
const auto ttl = data.vttl_seconds(); const auto ttl = data.vttl_seconds();
Assert(ttl != nullptr); Assert(ttl != nullptr);
setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, *ttl); if (data.is_video()) {
if (out()) { setSelfDestruct(
setServiceText({ HistoryServiceSelfDestruct::Type::Video,
tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) *ttl);
}); if (out()) {
} else { setServiceText({
auto result = PreparedServiceText(); tr::lng_ttl_video_sent(
result.links.push_back(fromLink()); tr::now,
result.text = tr::lng_ttl_video_received( Ui::Text::WithEntities)
tr::now, });
lt_from, } else {
fromLinkText(), // Link 1. auto result = PreparedServiceText();
Ui::Text::WithEntities); result.links.push_back(fromLink());
setServiceText(std::move(result)); result.text = tr::lng_ttl_video_received(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
setServiceText(std::move(result));
}
} else if (out()) {
auto text = (data.is_voice()
? tr::lng_ttl_voice_sent
: data.is_round()
? tr::lng_ttl_round_sent
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
setServiceText({ std::move(text) });
} }
} else { } else {
setServiceText({ auto text = (data.is_video()
tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities) ? tr::lng_ttl_video_expired
}); : data.is_voice()
? tr::lng_ttl_voice_expired
: data.is_round()
? tr::lng_ttl_round_expired
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
setServiceText({ std::move(text) });
} }
}, [&](const MTPDmessageMediaStory &data) { }, [&](const MTPDmessageMediaStory &data) {
setServiceText(prepareStoryMentionText()); setServiceText(prepareStoryMentionText());

View file

@ -20,6 +20,7 @@ struct HistoryMessageViews;
struct HistoryMessageMarkupData; struct HistoryMessageMarkupData;
struct HistoryMessageReplyMarkup; struct HistoryMessageReplyMarkup;
struct HistoryMessageTranslation; struct HistoryMessageTranslation;
struct HistoryMessageForwarded;
struct HistoryServiceDependentData; struct HistoryServiceDependentData;
enum class HistorySelfDestructType; enum class HistorySelfDestructType;
struct PreparedServiceText; struct PreparedServiceText;
@ -56,6 +57,7 @@ class ForumTopic;
class Thread; class Thread;
struct SponsoredFrom; struct SponsoredFrom;
class Story; class Story;
class SavedSublist;
} // namespace Data } // namespace Data
namespace Main { namespace Main {
@ -481,11 +483,21 @@ public:
[[nodiscard]] TimeId originalDate() const; [[nodiscard]] TimeId originalDate() const;
[[nodiscard]] PeerData *originalSender() const; [[nodiscard]] PeerData *originalSender() const;
[[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const; [[nodiscard]] const HiddenSenderInfo *originalHiddenSenderInfo() const;
[[nodiscard]] not_null<PeerData*> fromOriginal() const; [[nodiscard]] not_null<PeerData*> fromOriginal() const;
[[nodiscard]] QString originalPostAuthor() const; [[nodiscard]] QString originalPostAuthor() const;
[[nodiscard]] MsgId originalId() const; [[nodiscard]] MsgId originalId() const;
[[nodiscard]] Data::SavedSublist *savedSublist() const;
[[nodiscard]] PeerData *savedSublistPeer() const;
[[nodiscard]] PeerData *savedFromSender() const;
[[nodiscard]] const HiddenSenderInfo *savedFromHiddenSenderInfo() const;
[[nodiscard]] const HiddenSenderInfo *displayHiddenSenderInfo() const;
[[nodiscard]] bool showForwardsFromSender(
not_null<const HistoryMessageForwarded*> forwarded) const;
[[nodiscard]] bool isEmpty() const; [[nodiscard]] bool isEmpty() const;
[[nodiscard]] MessageGroupId groupId() const; [[nodiscard]] MessageGroupId groupId() const;

View file

@ -190,7 +190,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
const auto name = TextWithEntities{ const auto name = TextWithEntities{
.text = (originalSender .text = (originalSender
? originalSender->name() ? originalSender->name()
: hiddenSenderInfo->name) : originalHiddenSenderInfo->name)
}; };
if (!originalPostAuthor.isEmpty()) { if (!originalPostAuthor.isEmpty()) {
phrase = tr::lng_forwarded_signed( phrase = tr::lng_forwarded_signed(

View file

@ -126,9 +126,13 @@ private:
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded, HistoryItem> { struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded, HistoryItem> {
void create(const HistoryMessageVia *via) const; void create(const HistoryMessageVia *via) const;
[[nodiscard]] bool forwardOfForward() const {
return savedFromSender || savedFromHiddenSenderInfo;
}
TimeId originalDate = 0; TimeId originalDate = 0;
PeerData *originalSender = nullptr; PeerData *originalSender = nullptr;
std::unique_ptr<HiddenSenderInfo> hiddenSenderInfo; std::unique_ptr<HiddenSenderInfo> originalHiddenSenderInfo;
QString originalPostAuthor; QString originalPostAuthor;
QString psaType; QString psaType;
MsgId originalId = 0; MsgId originalId = 0;
@ -136,10 +140,19 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
PeerData *savedFromPeer = nullptr; PeerData *savedFromPeer = nullptr;
MsgId savedFromMsgId = 0; MsgId savedFromMsgId = 0;
PeerData *savedFromSender = nullptr;
std::unique_ptr<HiddenSenderInfo> savedFromHiddenSenderInfo;
bool savedFromOutgoing = false;
bool imported = false; bool imported = false;
bool story = false; bool story = false;
}; };
struct HistoryMessageSaved : public RuntimeComponent<HistoryMessageSaved, HistoryItem> {
Data::SavedSublist *sublist = nullptr;
};
class ReplyToMessagePointer final { class ReplyToMessagePointer final {
public: public:
ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) { ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) {

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_document.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
@ -452,7 +453,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
}, [](const MTPDmessageMediaPhoto &data) { }, [](const MTPDmessageMediaPhoto &data) {
const auto photo = data.vphoto(); const auto photo = data.vphoto();
if (data.vttl_seconds()) { if (data.vttl_seconds()) {
return Result::HasTimeToLive; return Result::HasUnsupportedTimeToLive;
} else if (!photo) { } else if (!photo) {
return Result::Empty; return Result::Empty;
} }
@ -464,7 +465,11 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
}, [](const MTPDmessageMediaDocument &data) { }, [](const MTPDmessageMediaDocument &data) {
const auto document = data.vdocument(); const auto document = data.vdocument();
if (data.vttl_seconds()) { if (data.vttl_seconds()) {
return Result::HasTimeToLive; if (data.is_video()) {
return Result::HasUnsupportedTimeToLive;
} else if (!document) {
return Result::HasExpiredMediaTimeToLive;
}
} else if (!document) { } else if (!document) {
return Result::Empty; return Result::Empty;
} }
@ -780,3 +785,31 @@ void ShowTrialTranscribesToast(int left, TimeId until) {
.filter = filter, .filter = filter,
}); });
} }
void ClearMediaAsExpired(not_null<HistoryItem*> item) {
if (const auto media = item->media()) {
if (!media->ttlSeconds()) {
return;
}
if (const auto document = media->document()) {
item->applyEditionToHistoryCleared();
auto text = (document->isVideoFile()
? tr::lng_ttl_video_expired
: document->isVoiceMessage()
? tr::lng_ttl_voice_expired
: document->isVideoMessage()
? tr::lng_ttl_round_expired
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
item->updateServiceText(PreparedServiceText{ std::move(text) });
} else if (const auto photo = media->photo()) {
item->applyEditionToHistoryCleared();
item->updateServiceText(PreparedServiceText{
tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
});
}
}
}
[[nodiscard]] bool IsVoiceOncePlayable(not_null<HistoryItem*> item) {
return !item->out() && item->media()->ttlSeconds();
}

View file

@ -43,7 +43,8 @@ enum class MediaCheckResult {
Good, Good,
Unsupported, Unsupported,
Empty, Empty,
HasTimeToLive, HasExpiredMediaTimeToLive,
HasUnsupportedTimeToLive,
HasStoryMention, HasStoryMention,
}; };
[[nodiscard]] MediaCheckResult CheckMessageMedia( [[nodiscard]] MediaCheckResult CheckMessageMedia(
@ -155,3 +156,6 @@ ClickHandlerPtr JumpToStoryClickHandler(
CallId callId); CallId callId);
void ShowTrialTranscribesToast(int left, TimeId until); void ShowTrialTranscribesToast(int left, TimeId until);
void ClearMediaAsExpired(not_null<HistoryItem*> item);
[[nodiscard]] bool IsVoiceOncePlayable(not_null<HistoryItem*> item);

View file

@ -281,7 +281,7 @@ private:
base::unique_qptr<Ui::IconButton> _cancel; base::unique_qptr<Ui::IconButton> _cancel;
base::unique_qptr<Ui::MultiSelect> _select; base::unique_qptr<Ui::MultiSelect> _select;
rpl::variable<PeerData*> _from = nullptr;; rpl::variable<PeerData*> _from = nullptr;
base::Timer _searchTimer; base::Timer _searchTimer;

View file

@ -376,7 +376,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
userpicTop, userpicTop,
width(), width(),
st::msgPhotoSize); st::msgPhotoSize);
} else if (const auto info = item->hiddenSenderInfo()) { } else if (const auto info = item->originalHiddenSenderInfo()) {
if (info->customUserpic.empty()) { if (info->customUserpic.empty()) {
info->emptyUserpic.paintCircle( info->emptyUserpic.paintCircle(
p, p,

View file

@ -127,7 +127,7 @@ void ForwardPanel::checkTexts() {
for (const auto item : _data.items) { for (const auto item : _data.items) {
if (const auto from = item->originalSender()) { if (const auto from = item->originalSender()) {
version += from->nameVersion(); version += from->nameVersion();
} else if (const auto info = item->hiddenSenderInfo()) { } else if (const auto info = item->originalHiddenSenderInfo()) {
++version; ++version;
} else { } else {
Unexpected("Corrupt forwarded information in message."); Unexpected("Corrupt forwarded information in message.");
@ -168,7 +168,7 @@ void ForwardPanel::updateTexts() {
names.push_back(from->shortName()); names.push_back(from->shortName());
fullname = from->name(); fullname = from->name();
} }
} else if (const auto info = item->hiddenSenderInfo()) { } else if (const auto info = item->originalHiddenSenderInfo()) {
if (!insertedNames.contains(info->name)) { if (!insertedNames.contains(info->name)) {
insertedNames.emplace(info->name); insertedNames.emplace(info->name);
names.push_back(info->firstName); names.push_back(info->firstName);

View file

@ -88,8 +88,7 @@ enum class FilterType {
const auto durationString = Ui::FormatDurationText(duration / kPrecision); const auto durationString = Ui::FormatDurationText(duration / kPrecision);
const auto decimalPart = duration % kPrecision; const auto decimalPart = duration % kPrecision;
return QString("%1%2%3") return QString("%1%2%3")
.arg(durationString) .arg(durationString, QLocale().decimalPoint())
.arg(QLocale().decimalPoint())
.arg(decimalPart); .arg(decimalPart);
} }

View file

@ -118,7 +118,7 @@ void SavePhotoToFile(not_null<PhotoData*> photo) {
return; return;
} }
const auto image = media->image(Data::PhotoSize::Large)->original(); const auto image = media->image(Data::PhotoSize::Large)->original(); // clazy:exclude=unused-non-trivial-variable
FileDialog::GetWritePath( FileDialog::GetWritePath(
Core::App().getFileDialogParent(), Core::App().getFileDialogParent(),
tr::lng_save_photo(tr::now), tr::lng_save_photo(tr::now),

View file

@ -64,16 +64,20 @@ Element *MousedElement/* = nullptr*/;
HistoryMessageForwarded *prevForwarded, HistoryMessageForwarded *prevForwarded,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
HistoryMessageForwarded *forwarded) { HistoryMessageForwarded *forwarded) {
const auto sender = previous->originalSender(); const auto sender = previous->displayFrom();
if ((prevForwarded != nullptr) != (forwarded != nullptr)) { if ((prevForwarded != nullptr) != (forwarded != nullptr)) {
return false; return false;
} else if (sender != item->originalSender()) { } else if (sender != item->displayFrom()) {
return false; return false;
} else if (!prevForwarded || sender) { } else if (!prevForwarded || sender) {
return true; return true;
} }
const auto previousInfo = prevForwarded->hiddenSenderInfo.get(); const auto previousInfo = prevForwarded->savedFromHiddenSenderInfo
const auto itemInfo = forwarded->hiddenSenderInfo.get(); ? prevForwarded->savedFromHiddenSenderInfo.get()
: prevForwarded->originalHiddenSenderInfo.get();
const auto itemInfo = forwarded->savedFromHiddenSenderInfo
? forwarded->savedFromHiddenSenderInfo.get()
: forwarded->originalHiddenSenderInfo.get();
Assert(previousInfo != nullptr); Assert(previousInfo != nullptr);
Assert(itemInfo != nullptr); Assert(itemInfo != nullptr);
return (*previousInfo == *itemInfo); return (*previousInfo == *itemInfo);
@ -1343,6 +1347,10 @@ bool Element::hasOutLayout() const {
return false; return false;
} }
bool Element::hasRightLayout() const {
return hasOutLayout() && !_delegate->elementIsChatWide();
}
bool Element::drawBubble() const { bool Element::drawBubble() const {
return false; return false;
} }

View file

@ -56,7 +56,8 @@ enum class Context : char {
Replies, Replies,
Pinned, Pinned,
AdminLog, AdminLog,
ContactPreview ContactPreview,
SavedSublist,
}; };
enum class OnlyEmojiAndSpaces : char { enum class OnlyEmojiAndSpaces : char {
@ -438,6 +439,7 @@ public:
[[nodiscard]] virtual TopicButton *displayedTopicButton() const; [[nodiscard]] virtual TopicButton *displayedTopicButton() const;
[[nodiscard]] virtual bool displayForwardedFrom() const; [[nodiscard]] virtual bool displayForwardedFrom() const;
[[nodiscard]] virtual bool hasOutLayout() const; [[nodiscard]] virtual bool hasOutLayout() const;
[[nodiscard]] bool hasRightLayout() const;
[[nodiscard]] virtual bool drawBubble() const; [[nodiscard]] virtual bool drawBubble() const;
[[nodiscard]] virtual bool hasBubble() const; [[nodiscard]] virtual bool hasBubble() const;
[[nodiscard]] virtual bool unwrapped() const; [[nodiscard]] virtual bool unwrapped() const;

Some files were not shown because too many files have changed in this diff Show more