diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 5ded5a040..6ac646b3c 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -94,6 +94,7 @@ jobs:
-D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_EXE_LINKER_FLAGS="-s" \
-D TDESKTOP_API_TEST=ON \
+ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE
@@ -115,7 +116,7 @@ jobs:
run: |
cd $REPO_NAME/out/Debug
mkdir artifact
- mv Telegram artifact/
+ mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.
diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml
index 96bfe6e13..2a570aef7 100644
--- a/.github/workflows/mac.yml
+++ b/.github/workflows/mac.yml
@@ -115,6 +115,7 @@ jobs:
-D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
-D TDESKTOP_API_TEST=ON \
+ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE
diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml
index 53b517939..b42906c7c 100644
--- a/.github/workflows/win.yml
+++ b/.github/workflows/win.yml
@@ -169,6 +169,7 @@ jobs:
%TDESKTOP_BUILD_GENERATOR% ^
%TDESKTOP_BUILD_ARCH% ^
%TDESKTOP_BUILD_API% ^
+ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
-D DESKTOP_APP_NO_PDB=ON ^
%TDESKTOP_BUILD_DEFINE%
@@ -178,8 +179,10 @@ jobs:
- name: Move artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
run: |
+ set OUT=%TBUILD%\%REPO_NAME%\out\Debug
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
name: Upload artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
diff --git a/LEGAL b/LEGAL
index e7c429315..5d0f5e8a7 100644
--- a/LEGAL
+++ b/LEGAL
@@ -1,7 +1,7 @@
This file is part of Telegram Desktop,
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
it under the terms of the GNU General Public License as published by
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 5f3eb6ea2..806084ada 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -620,6 +620,10 @@ PRIVATE
data/data_replies_list.h
data/data_reply_preview.cpp
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.h
data/data_send_action.cpp
@@ -866,6 +870,8 @@ PRIVATE
history/view/history_view_sponsored_click_handler.h
history/view/history_view_sticker_toast.cpp
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.h
history/view/history_view_translate_bar.cpp
@@ -967,6 +973,8 @@ PRIVATE
info/profile/info_profile_values.h
info/profile/info_profile_widget.cpp
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.h
info/similar_channels/info_similar_channels_widget.cpp
diff --git a/Telegram/Resources/animations/voice_ttl_idle.tgs b/Telegram/Resources/animations/voice_ttl_idle.tgs
new file mode 100644
index 000000000..016611298
Binary files /dev/null and b/Telegram/Resources/animations/voice_ttl_idle.tgs differ
diff --git a/Telegram/Resources/animations/voice_ttl_start.tgs b/Telegram/Resources/animations/voice_ttl_start.tgs
new file mode 100644
index 000000000..835556dd0
Binary files /dev/null and b/Telegram/Resources/animations/voice_ttl_start.tgs differ
diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden.png b/Telegram/Resources/icons/dialogs/avatar_hidden.png
new file mode 100644
index 000000000..e032148c8
Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden.png differ
diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png b/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png
new file mode 100644
index 000000000..ef143054e
Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png differ
diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png b/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png
new file mode 100644
index 000000000..f6ef6b733
Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png differ
diff --git a/Telegram/Resources/icons/dialogs/avatar_notes.png b/Telegram/Resources/icons/dialogs/avatar_notes.png
new file mode 100644
index 000000000..740d38910
Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes.png differ
diff --git a/Telegram/Resources/icons/dialogs/avatar_notes@2x.png b/Telegram/Resources/icons/dialogs/avatar_notes@2x.png
new file mode 100644
index 000000000..98dc7c89c
Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes@2x.png differ
diff --git a/Telegram/Resources/icons/dialogs/avatar_notes@3x.png b/Telegram/Resources/icons/dialogs/avatar_notes@3x.png
new file mode 100644
index 000000000..735961bd9
Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes@3x.png differ
diff --git a/Telegram/Resources/icons/info/info_media_saved.png b/Telegram/Resources/icons/info/info_media_saved.png
new file mode 100644
index 000000000..0b3ebc5dd
Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved.png differ
diff --git a/Telegram/Resources/icons/info/info_media_saved@2x.png b/Telegram/Resources/icons/info/info_media_saved@2x.png
new file mode 100644
index 000000000..aa91e6fb8
Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved@2x.png differ
diff --git a/Telegram/Resources/icons/info/info_media_saved@3x.png b/Telegram/Resources/icons/info/info_media_saved@3x.png
new file mode 100644
index 000000000..6c9814fe1
Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 9709b376a..db9eeb97e 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -395,6 +395,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dlg_new_bot_name" = "Bot name";
"lng_no_chats" = "Your chats will be here";
"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_not_found" = "No contacts 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_similar_channels#one" = "{count} similar channel";
"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_subscribers_section" = "Subscribers";
"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_sent" = "You sent a self-destructing video.";
"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_camera_title" = "Capture yourself";
@@ -2492,6 +2499,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_saved_short" = "Save";
"lng_saved_forward_here" = "Forward messages here for quick access";
"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_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_replies_view_original" = "View in chat";
"lng_replies_messages" = "Replies";
+"lng_hidden_author_messages" = "Author Hidden";
+"lng_my_notes" = "My Notes";
"lng_replies_discussion_started" = "Discussion started";
"lng_replies_no_comments" = "No comments here yet...";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index 705b508a2..76ea1ef4a 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -11,5 +11,7 @@
../../animations/ttl.tgs
../../animations/discussion.tgs
../../animations/stats.tgs
+ ../../animations/voice_ttl_idle.tgs
+ ../../animations/voice_ttl_start.tgs
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index b0314d210..14a581ce3 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="4.14.1.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 02853f16f..5c7f838b6 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,13,1,0
- PRODUCTVERSION 4,13,1,0
+ FILEVERSION 4,14,1,0
+ PRODUCTVERSION 4,14,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "4.13.1.0"
- VALUE "LegalCopyright", "Copyright (C) 2014-2023"
+ VALUE "FileVersion", "4.14.1.0"
+ VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.13.1.0"
+ VALUE "ProductVersion", "4.14.1.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 4859e694e..e91f35158 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,13,1,0
- PRODUCTVERSION 4,13,1,0
+ FILEVERSION 4,14,1,0
+ PRODUCTVERSION 4,14,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "4.13.1.0"
- VALUE "LegalCopyright", "Copyright (C) 2014-2023"
+ VALUE "FileVersion", "4.14.1.0"
+ VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.13.1.0"
+ VALUE "ProductVersion", "4.14.1.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/_other/startup_task_win.cpp b/Telegram/SourceFiles/_other/startup_task_win.cpp
index ece42d938..7207f77e7 100644
--- a/Telegram/SourceFiles/_other/startup_task_win.cpp
+++ b/Telegram/SourceFiles/_other/startup_task_win.cpp
@@ -6,6 +6,7 @@ For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include
+#include
#include
#include
diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp
index 09f9d6a21..15119b906 100644
--- a/Telegram/SourceFiles/api/api_messages_search.cpp
+++ b/Telegram/SourceFiles/api/api_messages_search.cpp
@@ -90,6 +90,7 @@ void MessagesSearch::searchRequest() {
(_from
? _from->input
: MTP_inputPeerEmpty()),
+ MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index 0f4cbd894..897a78d72 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_dc_options.h"
#include "data/notify/data_notify_settings.h"
#include "data/stickers/data_stickers.h"
+#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_user.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 "history/history.h"
#include "history/history_item.h"
+#include "history/history_item_helpers.h"
#include "history/history_unread_things.h"
#include "core/application.h"
#include "storage/storage_account.h"
@@ -1143,6 +1145,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
? peerToMTP(_session->userPeerId())
: MTP_peerUser(d.vuser_id())),
MTP_peerUser(d.vuser_id()),
+ MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(d.vvia_bot_id().value_or_empty()),
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
@@ -1174,6 +1177,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
d.vid(),
MTP_peerUser(d.vfrom_id()),
MTP_peerChat(d.vchat_id()),
+ MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(d.vvia_bot_id().value_or_empty()),
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
@@ -1234,11 +1238,12 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
item->markMediaAndMentionRead();
_session->data().requestItemRepaint(item);
- if (item->out()
- && item->history()->peer->isUser()
- && !requestingDifference()) {
- item->history()->peer->asUser()->madeAction(
- base::unixtime::now());
+ if (item->out()) {
+ const auto user = item->history()->peer->asUser();
+ if (user && !requestingDifference()) {
+ user->madeAction(base::unixtime::now());
+ }
+ ClearMediaAsExpired(item);
}
}
} else {
@@ -2236,6 +2241,16 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
} 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: {
auto &d = update.c_updateChannel();
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 35522ae5f..fb74e03f6 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_forum.h"
+#include "data/data_saved_sublist.h"
#include "data/data_search_controller.h"
#include "data/data_scheduled_messages.h"
#include "data/data_session.h"
@@ -444,6 +445,26 @@ void ApiWrap::savePinnedOrder(not_null forum) {
}).send();
}
+void ApiWrap::savePinnedOrder(not_null 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();
+ 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(
not_null history,
bool archived,
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 70ed1affa..7e44d460c 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -34,6 +34,7 @@ class Forum;
class ForumTopic;
class Thread;
class Story;
+class SavedMessages;
} // namespace Data
namespace InlineBots {
@@ -152,6 +153,7 @@ public:
void savePinnedOrder(Data::Folder *folder);
void savePinnedOrder(not_null forum);
+ void savePinnedOrder(not_null saved);
void toggleHistoryArchived(
not_null history,
bool archived,
diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp
index 4f46697c1..db72d5690 100644
--- a/Telegram/SourceFiles/boxes/background_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp
@@ -57,7 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kMaxWallPaperSlugLength = 255;
-constexpr auto kDefaultDimming = 50;
[[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) {
if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) {
diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp
index 144582a53..3bdaa932d 100644
--- a/Telegram/SourceFiles/boxes/connection_box.cpp
+++ b/Telegram/SourceFiles/boxes/connection_box.cpp
@@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput(
rpl::producer placeholder,
const QString &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());
}
}
@@ -831,8 +832,9 @@ void ProxyBox::prepare() {
connect(_host.data(), &HostInput::changed, [=] {
Ui::PostponeCall(_host, [=] {
const auto host = _host->getLastText().trimmed();
- static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q;
- const auto match = QRegularExpression(mask).match(host);
+ static const auto mask = QRegularExpression(
+ u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q);
+ const auto match = mask.match(host);
if (_host->cursorPosition() == host.size()
&& match.hasMatch()) {
const auto port = match.captured(1);
@@ -1107,6 +1109,10 @@ void ProxiesBoxController::ShowApplyConfirmation(
proxy.password = fields.value(u"secret"_q);
}
if (proxy) {
+ static const auto UrlStartRegExp = QRegularExpression(
+ "^https://",
+ QRegularExpression::CaseInsensitiveOption);
+ static const auto UrlEndRegExp = QRegularExpression("/$");
const auto displayed = "https://" + server + "/";
const auto parsed = QUrl::fromUserInput(displayed);
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
@@ -1117,11 +1123,9 @@ void ProxiesBoxController::ShowApplyConfirmation(
const auto displayServer = QString(
displayUrl
).replace(
- QRegularExpression(
- "^https://",
- QRegularExpression::CaseInsensitiveOption),
+ UrlStartRegExp,
QString()
- ).replace(QRegularExpression("/$"), QString());
+ ).replace(UrlEndRegExp, QString());
const auto text = tr::lng_sure_enable_socks(
tr::now,
lt_server,
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index 92c6862fd..b01d980d9 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -62,7 +62,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
-constexpr auto kDiscountDivider = 5.;
constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::SubscriptionOption;
@@ -383,7 +382,8 @@ void GiftsBox(
not_null box,
not_null controller,
std::vector> users,
- not_null api) {
+ not_null api,
+ const QString &ref) {
Expects(!users.empty());
const auto boxWidth = st::boxWideWidth;
@@ -592,7 +592,7 @@ void GiftsBox(
Settings::AddSummaryPremium(
content,
controller,
- u"gift"_q,
+ ref,
std::move(buttonCallback));
}
@@ -615,7 +615,7 @@ void GiftsBox(
auto raw = Settings::CreateSubscribeButton({
controller,
box,
- [] { return u"gift"_q; },
+ [=] { return ref; },
rpl::combine(
state->buttonText.events(),
state->confirmButtonBusy.value(),
@@ -877,7 +877,7 @@ void GiftPremiumValidator::cancel() {
_requestId = 0;
}
-void GiftPremiumValidator::showChoosePeerBox() {
+void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
if (_manyGiftsLifetime) {
return;
}
@@ -937,7 +937,7 @@ void GiftPremiumValidator::showChoosePeerBox() {
}) | ranges::to>>();
if (!users.empty()) {
const auto giftBox = show->show(
- Box(GiftsBox, _controller, users, api));
+ Box(GiftsBox, _controller, users, api, ref));
giftBox->boxClosing(
) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy();
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h
index 5a0bfb659..ed001200e 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.h
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.h
@@ -34,7 +34,7 @@ public:
GiftPremiumValidator(not_null controller);
void showBox(not_null user);
- void showChoosePeerBox();
+ void showChoosePeerBox(const QString &ref);
void cancel();
private:
diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
index 64452a906..60eb5ecde 100644
--- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_forum.h"
+#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_folder.h"
#include "data/data_premium_limits.h"
@@ -882,6 +883,18 @@ void PinsLimitBox(
limits.dialogsPinnedPremium(),
PinsCount(session->data().chatsList()));
}
+void SublistsPinsLimitBox(
+ not_null box,
+ not_null session) {
+ const auto limits = Data::PremiumLimits(session);
+ SimplePinsLimitBox(
+ box,
+ session,
+ "saved_dialog_pinned",
+ limits.savedSublistsPinnedDefault(),
+ limits.savedSublistsPinnedPremium(),
+ PinsCount(session->data().savedMessages().chatsList()));
+}
void ForumPinsLimitBox(
not_null box,
diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.h b/Telegram/SourceFiles/boxes/premium_limits_box.h
index 5a4fa8a0a..8640dc235 100644
--- a/Telegram/SourceFiles/boxes/premium_limits_box.h
+++ b/Telegram/SourceFiles/boxes/premium_limits_box.h
@@ -60,6 +60,9 @@ void PinsLimitBox(
void ForumPinsLimitBox(
not_null box,
not_null forum);
+void SublistsPinsLimitBox(
+ not_null box,
+ not_null session);
void CaptionLimitBox(
not_null box,
not_null session,
diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
index 92240b424..06835d603 100644
--- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
@@ -51,8 +51,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kPremiumShift = 21. / 240;
-constexpr auto kReactionsPerRow = 5;
-constexpr auto kDisabledOpacity = 0.5;
constexpr auto kToggleStickerTimeout = 2 * crl::time(1000);
constexpr auto kStarOpacityOff = 0.1;
constexpr auto kStarOpacityOn = 1.;
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index 1ffcc1ddf..d1b3dbb37 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -848,7 +848,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
if (!_chatsIndexed->empty()) {
const auto index = yFrom / _rowHeight;
auto i = _chatsIndexed->begin()
- + std::min(index, _chatsIndexed->size());;
+ + std::min(index, _chatsIndexed->size());
for (auto end = _chatsIndexed->cend(); i != end; ++i) {
if (((*i)->index() * _rowHeight) >= yTo) {
break;
diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp
index 66a4e01f5..9eb53580f 100644
--- a/Telegram/SourceFiles/calls/calls_box_controller.cpp
+++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp
@@ -520,6 +520,7 @@ void BoxController::loadMoreRows() {
MTP_inputPeerEmpty(),
MTP_string(), // q
MTP_inputPeerEmpty(),
+ MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id
MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)),
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
index 26a9cbed0..946c6dca8 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
@@ -321,9 +321,6 @@ void Viewport::RendererGL::init(
_frameBuffer->bind();
_frameBuffer->allocate(kValues * sizeof(GLfloat));
_downscaleProgram.yuv420.emplace();
- const auto downscaleVertexSource = VertexShader({
- VertexPassTextureCoord(),
- });
_downscaleVertexShader = LinkProgram(
&*_downscaleProgram.yuv420,
VertexShader({
diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
index 85fc07502..87ac643d3 100644
--- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
+++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
@@ -454,8 +454,10 @@ void ChooseSourceProcess::fillSources() {
auto screenIndex = 0;
auto windowIndex = 0;
+ auto firstScreenSelected = false;
const auto active = _delegate->chooseSourceActiveDeviceId();
const auto append = [&](const tgcalls::DesktopCaptureSource &source) {
+ const auto firstScreen = !source.isWindow() && !screenIndex;
const auto title = !source.isWindow()
? tr::lng_group_call_screen_title(
tr::now,
@@ -471,6 +473,10 @@ void ChooseSourceProcess::fillSources() {
if (!active.isEmpty() && active.toStdString() == id) {
_selected = raw;
raw->setActive(true);
+ } else if (active.isEmpty() && firstScreen) {
+ _selected = raw;
+ raw->setActive(true);
+ firstScreenSelected = true;
}
_sources.back()->activations(
) | rpl::filter([=] {
@@ -489,6 +495,9 @@ void ChooseSourceProcess::fillSources() {
for (const auto &source : windowsManager.sources()) {
append(source);
}
+ if (firstScreenSelected) {
+ updateButtonsVisibility();
+ }
}
void ChooseSourceProcess::updateButtonsVisibility() {
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index cc8ab9064..57747616b 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -683,7 +683,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
} break;
case QEvent::ThemeChange: {
- if (Platform::IsLinux() && object == QGuiApplication::allWindows().first()) {
+ if (Platform::IsLinux()
+ && object == QGuiApplication::allWindows().constFirst()) {
Core::App().refreshApplicationIcon();
Core::App().tray().updateIconCounters();
}
diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp
index fa0a728fa..8c4f2c1af 100644
--- a/Telegram/SourceFiles/core/launcher.cpp
+++ b/Telegram/SourceFiles/core/launcher.cpp
@@ -499,7 +499,7 @@ uint64 Launcher::installationTag() const {
}
void Launcher::processArguments() {
- enum class KeyFormat {
+ enum class KeyFormat {
NoValues,
OneValue,
AllLeftValues,
@@ -542,9 +542,13 @@ void Launcher::processArguments() {
}
}
+ static const auto RegExp = QRegularExpression("[^a-z0-9\\-_]");
gDebugMode = parseResult.contains("-debug");
- gKeyFile = parseResult.value("-key", {}).join(QString()).toLower();
- gKeyFile = gKeyFile.replace(QRegularExpression("[^a-z0-9\\-_]"), {});
+ gKeyFile = parseResult
+ .value("-key", {})
+ .join(QString())
+ .toLower()
+ .replace(RegExp, {});
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
: parseResult.contains("-cleanup") ? LaunchModeCleanup
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index 582a695fe..68844669d 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -845,6 +845,21 @@ bool ResolvePremiumOffer(
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(
Window::SessionController *controller,
const Match &match,
@@ -968,6 +983,10 @@ const std::vector &LocalUrlHandlers() {
u"premium_offer/?(\\?.+)?(#|$)"_q,
ResolvePremiumOffer,
},
+ {
+ u"^premium_multigift/?\\?(.+)(#|$)"_q,
+ ResolvePremiumMultigift,
+ },
{
u"^login/?(\\?code=([0-9]+))(&|$)"_q,
ResolveLoginCode
@@ -1092,7 +1111,7 @@ QString TryConvertUrlToLocal(QString url) {
const auto base = u"tg://privatepost?channel="_q + channel;
auto added = QString();
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))) {
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));
auto added = QString();
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))) {
added = u"&post="_q + postMatch->captured(1);
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp
index 0b92376cd..6feacdb5b 100644
--- a/Telegram/SourceFiles/core/sandbox.cpp
+++ b/Telegram/SourceFiles/core/sandbox.cpp
@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/local_url_handlers.h"
#include "core/update_checker.h"
#include "core/deadlock_detector.h"
-#include "base/options.h"
#include "base/timer.h"
#include "base/concurrent_timer.h"
#include "base/invoke_queued.h"
@@ -38,24 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Core {
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) {
v = v & 0x000F;
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
@@ -96,17 +77,12 @@ QString _escapeFrom7bit(const QString &str) {
} // namespace
-const char kOptionForceWaylandFractionalScaling[] = "force-wayland-fractional-scaling";
-
bool Sandbox::QuitOnStartRequested = false;
Sandbox::Sandbox(int &argc, char **argv)
: QApplication(argc, argv)
, _mainThreadId(QThread::currentThreadId()) {
setQuitOnLastWindowClosed(false);
- if (OptionForceWaylandFractionalScaling.value()) {
- setProperty("_q_force_wayland_fractional_scale", true);
- }
}
int Sandbox::start() {
@@ -240,7 +216,7 @@ void Sandbox::setupScreenScale() {
const auto logEnv = [](const char *name) {
const auto value = qEnvironmentVariable(name);
if (!value.isEmpty()) {
- LOG(("%1: %2").arg(name).arg(value));
+ LOG(("%1: %2").arg(name, value));
}
};
logEnv("QT_DEVICE_PIXEL_RATIO");
diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h
index 39d16296c..4c15c2828 100644
--- a/Telegram/SourceFiles/core/sandbox.h
+++ b/Telegram/SourceFiles/core/sandbox.h
@@ -19,8 +19,6 @@ class QLockFile;
namespace Core {
-extern const char kOptionForceWaylandFractionalScaling[];
-
class UpdateChecker;
class Application;
diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp
index caeabb21a..45a4fe1a7 100644
--- a/Telegram/SourceFiles/core/update_checker.cpp
+++ b/Telegram/SourceFiles/core/update_checker.cpp
@@ -241,7 +241,7 @@ QString FindUpdateFile() {
}
const auto list = updates.entryInfoList(QDir::Files);
for (const auto &info : list) {
- if (QRegularExpression(
+ static const auto RegExp = QRegularExpression(
"^("
"tupdate|"
"tx64upd|"
@@ -250,7 +250,8 @@ QString FindUpdateFile() {
"tlinuxupd|"
")\\d+(_[a-z\\d]+)?$",
QRegularExpression::CaseInsensitiveOption
- ).match(info.fileName()).hasMatch()) {
+ );
+ if (RegExp.match(info.fileName()).hasMatch()) {
return info.absoluteFilePath();
}
}
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 112f7da3f..38b22e247 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 4013001;
-constexpr auto AppVersionStr = "4.13.1";
+constexpr auto AppVersion = 4014001;
+constexpr auto AppVersionStr = "4.14.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp
index 1d7ea12af..cd736e9e7 100644
--- a/Telegram/SourceFiles/data/data_download_manager.cpp
+++ b/Telegram/SourceFiles/data/data_download_manager.cpp
@@ -889,7 +889,6 @@ not_null DownloadManager::generateItem(
const auto replyTo = FullReplyTo();
const auto viaBotId = UserId();
const auto date = base::unixtime::now();
- const auto postAuthor = QString();
const auto caption = TextWithEntities();
const auto make = [&](const auto media) {
return history->makeMessage(
diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp
index bbd4ce987..76f599943 100644
--- a/Telegram/SourceFiles/data/data_folder.cpp
+++ b/Telegram/SourceFiles/data/data_folder.cpp
@@ -343,12 +343,6 @@ int Folder::storiesUnreadCount() const {
return _storiesUnreadCount;
}
-void Folder::requestChatListMessage() {
- if (!chatListMessageKnown()) {
- owner().histories().requestDialogEntry(this);
- }
-}
-
TimeId Folder::adjustedChatListTimeId() const {
return chatListTimeId();
}
diff --git a/Telegram/SourceFiles/data/data_folder.h b/Telegram/SourceFiles/data/data_folder.h
index 7008adac0..7f6347e93 100644
--- a/Telegram/SourceFiles/data/data_folder.h
+++ b/Telegram/SourceFiles/data/data_folder.h
@@ -49,9 +49,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
- void requestChatListMessage() override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
+ int chatListNameVersion() const override;
const base::flat_set &chatListNameWords() const override;
const base::flat_set &chatListFirstLetters() const override;
@@ -82,8 +82,6 @@ public:
private:
void indexNameParts();
- int chatListNameVersion() const override;
-
void reorderLastHistories();
void paintUserpic(
diff --git a/Telegram/SourceFiles/data/data_forum_icons.cpp b/Telegram/SourceFiles/data/data_forum_icons.cpp
index 1469cf559..c47c8e669 100644
--- a/Telegram/SourceFiles/data/data_forum_icons.cpp
+++ b/Telegram/SourceFiles/data/data_forum_icons.cpp
@@ -15,13 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
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 owner)
: _owner(owner)
diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h
index 66430a0a3..275f38fcb 100644
--- a/Telegram/SourceFiles/data/data_forum_topic.h
+++ b/Telegram/SourceFiles/data/data_forum_topic.h
@@ -98,6 +98,7 @@ public:
void setRealRootId(MsgId realId);
void readTillEnd();
+ void requestChatListMessage();
void applyTopic(const MTPDforumTopic &data);
@@ -109,9 +110,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
- void requestChatListMessage() override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
+ int chatListNameVersion() const override;
const base::flat_set &chatListNameWords() const override;
const base::flat_set &chatListFirstLetters() const override;
@@ -187,8 +188,6 @@ private:
void allowChatListMessageResolve();
void resolveChatListMessageGroup();
- int chatListNameVersion() const override;
-
void subscribeToUnreadChanges();
[[nodiscard]] Dialogs::UnreadState unreadStateFor(
int count,
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index f716984ad..e9c4dd66e 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -555,6 +555,10 @@ bool Media::hasSpoiler() const {
return false;
}
+crl::time Media::ttlSeconds() const {
+ return 0;
+}
+
bool Media::consumeMessageText(const TextWithEntities &text) {
return false;
}
@@ -849,12 +853,14 @@ MediaFile::MediaFile(
not_null parent,
not_null document,
bool skipPremiumEffect,
- bool spoiler)
+ bool spoiler,
+ crl::time ttlSeconds)
: Media(parent)
, _document(document)
, _emoji(document->sticker() ? document->sticker()->alt : QString())
, _skipPremiumEffect(skipPremiumEffect)
-, _spoiler(spoiler) {
+, _spoiler(spoiler)
+, _ttlSeconds(ttlSeconds) {
parent->history()->owner().registerDocumentItem(_document, parent);
if (!_emoji.isEmpty()) {
@@ -882,7 +888,8 @@ std::unique_ptr MediaFile::clone(not_null parent) {
parent,
_document,
!_document->session().premium(),
- _spoiler);
+ _spoiler,
+ _ttlSeconds);
}
DocumentData *MediaFile::document() const {
@@ -1129,6 +1136,14 @@ bool MediaFile::hasSpoiler() const {
return _spoiler;
}
+crl::time MediaFile::ttlSeconds() const {
+ return _ttlSeconds;
+}
+
+bool MediaFile::allowsForward() const {
+ return !ttlSeconds();
+}
+
bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) {
if (media.type() != mtpc_messageMediaDocument) {
return false;
diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h
index 2a4e18e90..b2323af8a 100644
--- a/Telegram/SourceFiles/data/data_media_types.h
+++ b/Telegram/SourceFiles/data/data_media_types.h
@@ -174,6 +174,7 @@ public:
virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const;
[[nodiscard]] virtual bool hasSpoiler() const;
+ [[nodiscard]] virtual crl::time ttlSeconds() const;
[[nodiscard]] virtual bool consumeMessageText(
const TextWithEntities &text);
@@ -256,7 +257,8 @@ public:
not_null parent,
not_null document,
bool skipPremiumEffect,
- bool spoiler);
+ bool spoiler,
+ crl::time ttlSeconds);
~MediaFile();
std::unique_ptr clone(not_null parent) override;
@@ -278,6 +280,8 @@ public:
bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override;
bool hasSpoiler() const override;
+ crl::time ttlSeconds() const override;
+ bool allowsForward() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
@@ -292,6 +296,9 @@ private:
bool _skipPremiumEffect = false;
bool _spoiler = false;
+ // Video (unsupported) / Voice / Round.
+ crl::time _ttlSeconds = 0;
+
};
class MediaContact final : public Media {
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 746407a5f..a6f033924 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
+#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
@@ -1029,6 +1030,10 @@ bool PeerData::sharedMediaInfo() const {
return isSelf() || isRepliesChat();
}
+bool PeerData::savedSublistsInfo() const {
+ return isSelf() && owner().savedMessages().supported();
+}
+
bool PeerData::hasStoriesHidden() const {
if (const auto user = asUser()) {
return user->hasStoriesHidden();
diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h
index 8f553f9db..0252063f4 100644
--- a/Telegram/SourceFiles/data/data_peer.h
+++ b/Telegram/SourceFiles/data/data_peer.h
@@ -165,6 +165,7 @@ public:
virtual ~PeerData();
static constexpr auto kServiceNotificationsId = peerFromUser(777000);
+ static constexpr auto kSavedHiddenAuthorId = peerFromUser(2666000);
[[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const;
@@ -202,6 +203,7 @@ public:
[[nodiscard]] bool isGigagroup() const;
[[nodiscard]] bool isRepliesChat() const;
[[nodiscard]] bool sharedMediaInfo() const;
+ [[nodiscard]] bool savedSublistsInfo() const;
[[nodiscard]] bool hasStoriesHidden() const;
void setStoriesHidden(bool hidden);
@@ -212,6 +214,9 @@ public:
[[nodiscard]] bool isServiceUser() const {
return isUser() && !(id.value % 1000);
}
+ [[nodiscard]] bool isSavedHiddenAuthor() const {
+ return (id == kSavedHiddenAuthorId);
+ }
[[nodiscard]] Data::Forum *forum() const;
[[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const;
diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp
index 0b66e192a..af01588f4 100644
--- a/Telegram/SourceFiles/data/data_photo_media.cpp
+++ b/Telegram/SourceFiles/data/data_photo_media.cpp
@@ -226,7 +226,6 @@ bool PhotoMedia::setToClipboard() {
if (fallback.isNull()) {
return false;
}
- const auto bytes = imageBytes(large);
auto mime = std::make_unique();
mime->setImageData(std::move(fallback));
if (auto bytes = imageBytes(large); !bytes.isEmpty()) {
diff --git a/Telegram/SourceFiles/data/data_premium_limits.cpp b/Telegram/SourceFiles/data/data_premium_limits.cpp
index 07a5124a6..443474040 100644
--- a/Telegram/SourceFiles/data/data_premium_limits.cpp
+++ b/Telegram/SourceFiles/data/data_premium_limits.cpp
@@ -141,6 +141,18 @@ int PremiumLimits::topicsPinnedCurrent() const {
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 {
return appConfigLimit("channels_public_limit_default", 10);
}
diff --git a/Telegram/SourceFiles/data/data_premium_limits.h b/Telegram/SourceFiles/data/data_premium_limits.h
index f9bd416d2..bc3c86d9f 100644
--- a/Telegram/SourceFiles/data/data_premium_limits.h
+++ b/Telegram/SourceFiles/data/data_premium_limits.h
@@ -59,6 +59,10 @@ public:
[[nodiscard]] int topicsPinnedCurrent() const;
+ [[nodiscard]] int savedSublistsPinnedDefault() const;
+ [[nodiscard]] int savedSublistsPinnedPremium() const;
+ [[nodiscard]] int savedSublistsPinnedCurrent() const;
+
[[nodiscard]] int channelsPublicDefault() const;
[[nodiscard]] int channelsPublicPremium() const;
[[nodiscard]] int channelsPublicCurrent() const;
diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp
new file mode 100644
index 000000000..695622bbe
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_saved_messages.cpp
@@ -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 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 SavedMessages::chatsList() {
+ return &_chatsList;
+}
+
+not_null SavedMessages::sublist(not_null peer) {
+ const auto i = _sublists.find(peer);
+ if (i != end(_sublists)) {
+ return i->second.get();
+ }
+ return _sublists.emplace(
+ peer,
+ std::make_unique(peer)).first->second.get();
+}
+
+void SavedMessages::loadMore() {
+ _loadMoreScheduled = true;
+ _loadMore.call();
+}
+
+void SavedMessages::loadMore(not_null 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 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*)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()) {
+ count = int(list->size());
+ } else {
+ count = data.vcount().v;
+ }
+ });
+
+ _loadMoreRequests.remove(sublist);
+ if (!list) {
+ sublist->setFullLoaded();
+ return;
+ }
+ auto items = std::vector>();
+ 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*)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
diff --git a/Telegram/SourceFiles/data/data_saved_messages.h b/Telegram/SourceFiles/data/data_saved_messages.h
new file mode 100644
index 000000000..3e09f4db0
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_saved_messages.h
@@ -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 owner);
+ ~SavedMessages();
+
+ [[nodiscard]] bool supported() const;
+
+ [[nodiscard]] Session &owner() const;
+ [[nodiscard]] Main::Session &session() const;
+
+ [[nodiscard]] not_null chatsList();
+ [[nodiscard]] not_null sublist(not_null peer);
+
+ void loadMore();
+ void loadMore(not_null 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 sublist);
+ void sendLoadMoreRequests();
+
+ const not_null _owner;
+
+ Dialogs::MainList _chatsList;
+ base::flat_map<
+ not_null,
+ std::unique_ptr> _sublists;
+
+ base::flat_map, mtpRequestId> _loadMoreRequests;
+ mtpRequestId _loadMoreRequestId = 0;
+ mtpRequestId _pinnedRequestId = 0;
+
+ TimeId _offsetDate = 0;
+ MsgId _offsetId = 0;
+ PeerData *_offsetPeer = nullptr;
+
+ SingleQueuedInvokation _loadMore;
+ base::flat_set> _loadMoreSublistsScheduled;
+ bool _loadMoreScheduled = false;
+
+ bool _pinnedLoaded = false;
+ bool _unsupported = false;
+
+};
+
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp
new file mode 100644
index 000000000..2d129a5f6
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp
@@ -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 peer)
+: Entry(&peer->owner(), Dialogs::Entry::Type::SavedSublist)
+, _history(peer->owner().history(peer)) {
+}
+
+SavedSublist::~SavedSublist() = default;
+
+not_null SavedSublist::history() const {
+ return _history;
+}
+
+not_null 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> & {
+ return _items;
+}
+
+void SavedSublist::applyMaybeLast(not_null item, bool added) {
+ const auto before = [](
+ not_null a,
+ not_null 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 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 SavedSublist::fullCount() const {
+ return isFullLoaded() ? int(_items.size()) : _fullCount;
+}
+
+rpl::producer SavedSublist::fullCountValue() const {
+ return _changed.events_starting_with({}) | rpl::map([=] {
+ return fullCount();
+ }) | rpl::filter_optional();
+}
+
+void SavedSublist::append(
+ std::vector> &&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 &SavedSublist::chatListNameWords() const {
+ return _history->chatListNameWords();
+}
+
+const base::flat_set &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
diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h
new file mode 100644
index 000000000..15c3428ff
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_saved_sublist.h
@@ -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 peer);
+ ~SavedSublist();
+
+ [[nodiscard]] not_null history() const;
+ [[nodiscard]] not_null peer() const;
+ [[nodiscard]] bool isHiddenAuthor() const;
+ [[nodiscard]] bool isFullLoaded() const;
+
+ [[nodiscard]] auto messages() const
+ -> const std::vector> &;
+ void applyMaybeLast(not_null item, bool added = false);
+ void removeOne(not_null item);
+ void append(std::vector> &&items, int fullCount);
+ void setFullLoaded(bool loaded = true);
+
+ [[nodiscard]] rpl::producer<> changes() const;
+ [[nodiscard]] std::optional fullCount() const;
+ [[nodiscard]] rpl::producer 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 &chatListNameWords() const override;
+ const base::flat_set &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;
+
+ bool hasOrphanMediaGroupPart() const;
+ void allowChatListMessageResolve();
+ void resolveChatListMessageGroup();
+
+ const not_null _history;
+
+ std::vector> _items;
+ std::optional _fullCount;
+ rpl::event_stream<> _changed;
+ Dialogs::Ui::MessageView _lastItemDialogsView;
+ Flags _flags;
+
+};
+
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp
index 7f5821b7c..f122f05b9 100644
--- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp
+++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp
@@ -68,6 +68,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
data.vpeer_id(),
+ data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(data.vvia_bot_id().value_or_empty()),
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
@@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage(
update.vid(),
peerToMTP(local->from()->id),
peerToMTP(history->peer->id),
+ MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(),
MTPlong(), // via_bot_id
replyHeader,
diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp
index 08fae4983..688ce4ae9 100644
--- a/Telegram/SourceFiles/data/data_search_controller.cpp
+++ b/Telegram/SourceFiles/data/data_search_controller.cpp
@@ -97,6 +97,7 @@ std::optional PrepareSearchRequest(
peer->input,
MTP_string(query),
MTP_inputPeerEmpty(),
+ MTPInputPeer(), // saved_peer_id
MTP_int(topicRootId),
filter,
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 1185501f0..3885b9155 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -60,6 +60,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h"
#include "data/data_forum_icons.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_streaming.h"
#include "data/data_media_rotation.h"
@@ -268,7 +270,8 @@ Session::Session(not_null session)
, _forumIcons(std::make_unique(this))
, _notifySettings(std::make_unique(this))
, _customEmojiManager(std::make_unique(this))
-, _stories(std::make_unique(this)) {
+, _stories(std::make_unique(this))
+, _savedMessages(std::make_unique(this)) {
_cache->open(_session->local().cacheKey());
_bigFileCache->open(_session->local().cacheBigFileKey());
@@ -1724,6 +1727,11 @@ void Session::requestItemRepaint(not_null item) {
topic->updateChatListEntry();
}
}
+ if (const auto sublist = item->savedSublist()) {
+ if (sublist->lastItemDialogsView().dependsOn(item)) {
+ sublist->updateChatListEntry();
+ }
+ }
}
rpl::producer> Session::itemRepaintRequest() const {
@@ -2111,13 +2119,17 @@ void Session::applyDialog(
setPinnedFromEntryList(folder, data.is_pinned());
}
-bool Session::pinnedCanPin(not_null thread) const {
- if (const auto topic = thread->asTopic()) {
+bool Session::pinnedCanPin(not_null entry) const {
+ 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();
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(
@@ -2149,6 +2161,11 @@ int Session::pinnedChatsLimit(not_null forum) const {
return limits.topicsPinnedCurrent();
}
+int Session::pinnedChatsLimit(not_null saved) const {
+ const auto limits = Data::PremiumLimits(_session);
+ return limits.savedSublistsPinnedCurrent();
+}
+
rpl::producer Session::maxPinnedChatsLimitValue(
Data::Folder *folder) const {
// Premium limit from appconfig.
@@ -2189,6 +2206,20 @@ rpl::producer Session::maxPinnedChatsLimitValue(
});
}
+rpl::producer Session::maxPinnedChatsLimitValue(
+ not_null 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 &Session::pinnedChatsOrder(
Data::Folder *folder) const {
return chatsList(folder)->pinned()->order();
@@ -2204,6 +2235,11 @@ const std::vector &Session::pinnedChatsOrder(
return forum->topicsList()->pinned()->order();
}
+const std::vector &Session::pinnedChatsOrder(
+ not_null saved) const {
+ return saved->chatsList()->pinned()->order();
+}
+
void Session::clearPinnedChats(Data::Folder *folder) {
chatsList(folder)->pinned()->clear();
}
@@ -2220,7 +2256,7 @@ void Session::reorderTwoPinnedChats(
? topic->forum()->topicsList()
: filterId
? chatsFilters().chatsList(filterId)
- : chatsList(key1.entry()->folder());
+ : chatsListFor(key1.entry());
list->pinned()->reorder(key1, key2);
notifyPinnedDialogsOrderUpdated();
}
@@ -4262,6 +4298,8 @@ not_null Session::chatsListFor(
const auto topic = entry->asTopic();
return topic
? topic->forum()->topicsList()
+ : entry->asSublist()
+ ? _savedMessages->chatsList()
: chatsList(entry->folder());
}
@@ -4458,6 +4496,7 @@ void Session::insertCheckedServiceNotification(
MTP_int(0), // Not used (would've been trimmed to 32 bits).
peerToMTP(PeerData::kServiceNotificationsId),
peerToMTP(PeerData::kServiceNotificationsId),
+ MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(),
MTPlong(), // via_bot_id
MTPMessageReplyHeader(),
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index ea93cc178..244fa1524 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -61,6 +61,7 @@ class GroupCall;
class NotifySettings;
class CustomEmojiManager;
class Stories;
+class SavedMessages;
struct RepliesReadTillUpdate {
FullMsgId id;
@@ -137,6 +138,9 @@ public:
[[nodiscard]] Stories &stories() const {
return *_stories;
}
+ [[nodiscard]] SavedMessages &savedMessages() const {
+ return *_savedMessages;
+ }
[[nodiscard]] MsgId nextNonHistoryEntryId() {
return ++_nonHistoryEntryId;
@@ -345,25 +349,31 @@ public:
const QVector &dialogs,
std::optional count = std::nullopt);
- [[nodiscard]] bool pinnedCanPin(not_null thread) const;
+ [[nodiscard]] bool pinnedCanPin(not_null entry) const;
[[nodiscard]] bool pinnedCanPin(
FilterId filterId,
not_null history) const;
[[nodiscard]] int pinnedChatsLimit(Folder *folder) const;
[[nodiscard]] int pinnedChatsLimit(FilterId filterId) const;
[[nodiscard]] int pinnedChatsLimit(not_null forum) const;
+ [[nodiscard]] int pinnedChatsLimit(
+ not_null saved) const;
[[nodiscard]] rpl::producer maxPinnedChatsLimitValue(
Folder *folder) const;
[[nodiscard]] rpl::producer maxPinnedChatsLimitValue(
FilterId filterId) const;
[[nodiscard]] rpl::producer maxPinnedChatsLimitValue(
not_null forum) const;
+ [[nodiscard]] rpl::producer maxPinnedChatsLimitValue(
+ not_null saved) const;
[[nodiscard]] const std::vector &pinnedChatsOrder(
Folder *folder) const;
[[nodiscard]] const std::vector &pinnedChatsOrder(
not_null forum) const;
[[nodiscard]] const std::vector &pinnedChatsOrder(
FilterId filterId) const;
+ [[nodiscard]] const std::vector &pinnedChatsOrder(
+ not_null saved) const;
void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
void clearPinnedChats(Folder *folder);
@@ -1041,6 +1051,7 @@ private:
const std::unique_ptr _notifySettings;
const std::unique_ptr _customEmojiManager;
const std::unique_ptr _stories;
+ const std::unique_ptr _savedMessages;
MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange;
diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp
index 3073003a1..3e8ab6ac5 100644
--- a/Telegram/SourceFiles/data/data_stories.cpp
+++ b/Telegram/SourceFiles/data/data_stories.cpp
@@ -1360,7 +1360,6 @@ void Stories::sendIncrementViewsRequests() {
return;
}
- auto ids = QVector();
struct Prepared {
PeerId peer = 0;
QVector ids;
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index 8dd712d02..e8c9a33df 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -385,7 +385,7 @@ QString UserData::username() const {
}
QString UserData::editableUsername() const {
- return _username.editableUsername();;
+ return _username.editableUsername();
}
const std::vector &UserData::usernames() const {
diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp
index 123621b37..1f80c53f6 100644
--- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp
+++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp
@@ -349,7 +349,6 @@ void CustomEmojiLoader::check() {
const auto tag = _tag;
const auto sizeOverride = int(_sizeOverride);
const auto size = FrameSizeFromTag(_tag, _sizeOverride);
- auto bytes = Lottie::ReadContent(data, filepath);
auto loader = [=] {
return std::make_unique(
document,
diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style
index d324ca44a..b42c490a3 100644
--- a/Telegram/SourceFiles/dialogs/dialogs.style
+++ b/Telegram/SourceFiles/dialogs/dialogs.style
@@ -347,6 +347,8 @@ dialogsForumIcon: ThreeStateIcon {
dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }};
dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }};
dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }};
+dialogsHiddenAuthorUserpic: icon {{ "dialogs/avatar_hidden", premiumButtonFg }};
+dialogsMyNotesUserpic: icon {{ "dialogs/avatar_notes", historyPeerUserpicFg }};
dialogsSendStateSkip: 20px;
dialogsSendingIcon: ThreeStateIcon {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
index f2d5ebcd4..26f6e1fcb 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_chat_filters.h"
+#include "data/data_saved_sublist.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "mainwidget.h"
@@ -83,6 +84,8 @@ Entry::Entry(not_null owner, Type type)
? (Flag::IsThread | Flag::IsHistory)
: (type == Type::ForumTopic)
? Flag::IsThread
+ : (type == Type::SavedSublist)
+ ? Flag::IsSavedSublist
: Flag(0)) {
}
@@ -109,7 +112,7 @@ Data::Forum *Entry::asForum() {
}
Data::Folder *Entry::asFolder() {
- return (_flags & Flag::IsThread)
+ return (_flags & (Flag::IsThread | Flag::IsSavedSublist))
? nullptr
: static_cast(this);
}
@@ -126,6 +129,12 @@ Data::ForumTopic *Entry::asTopic() {
: nullptr;
}
+Data::SavedSublist *Entry::asSublist() {
+ return (_flags & Flag::IsSavedSublist)
+ ? static_cast(this)
+ : nullptr;
+}
+
const History *Entry::asHistory() const {
return const_cast(this)->asHistory();
}
@@ -146,6 +155,10 @@ const Data::ForumTopic *Entry::asTopic() const {
return const_cast(this)->asTopic();
}
+const Data::SavedSublist *Entry::asSublist() const {
+ return const_cast(this)->asSublist();
+}
+
void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) {
if (!filterId && session().supportMode()) {
// Force reorder in support mode.
diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h
index 3ad4281f3..56e84f48c 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_entry.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h
@@ -25,6 +25,7 @@ class Session;
class Forum;
class Folder;
class ForumTopic;
+class SavedSublist;
} // namespace Data
namespace Ui {
@@ -151,6 +152,7 @@ public:
History,
Folder,
ForumTopic,
+ SavedSublist,
};
Entry(not_null owner, Type type);
virtual ~Entry();
@@ -163,12 +165,14 @@ public:
Data::Folder *asFolder();
Data::Thread *asThread();
Data::ForumTopic *asTopic();
+ Data::SavedSublist *asSublist();
const History *asHistory() const;
const Data::Forum *asForum() const;
const Data::Folder *asFolder() const;
const Data::Thread *asThread() const;
const Data::ForumTopic *asTopic() const;
+ const Data::SavedSublist *asSublist() const;
PositionChange adjustByPosInChatList(
FilterId filterId,
@@ -206,27 +210,29 @@ public:
void setChatListTimeId(TimeId date);
virtual void updateChatListExistence();
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 kTopPromotionFixOnTopIndex = 2;
- virtual bool shouldBeInChatList() const = 0;
- virtual UnreadState chatListUnreadState() const = 0;
- virtual BadgesState chatListBadgesState() const = 0;
- virtual HistoryItem *chatListMessage() const = 0;
- virtual bool chatListMessageKnown() const = 0;
- virtual void requestChatListMessage() = 0;
- virtual const QString &chatListName() const = 0;
- virtual const QString &chatListNameSortKey() const = 0;
- virtual const base::flat_set &chatListNameWords() const = 0;
- virtual const base::flat_set &chatListFirstLetters() const = 0;
+ [[nodiscard]] virtual bool shouldBeInChatList() const = 0;
+ [[nodiscard]] virtual UnreadState chatListUnreadState() const = 0;
+ [[nodiscard]] virtual BadgesState chatListBadgesState() const = 0;
+ [[nodiscard]] virtual HistoryItem *chatListMessage() const = 0;
+ [[nodiscard]] virtual bool chatListMessageKnown() const = 0;
+ [[nodiscard]] virtual const QString &chatListName() const = 0;
+ [[nodiscard]] virtual const QString &chatListNameSortKey() const = 0;
+ [[nodiscard]] virtual int chatListNameVersion() const = 0;
+ [[nodiscard]] virtual auto chatListNameWords() const
+ -> const base::flat_set & = 0;
+ [[nodiscard]] virtual auto chatListFirstLetters() const
+ -> const base::flat_set & = 0;
- virtual bool folderKnown() const {
+ [[nodiscard]] virtual bool folderKnown() const {
return true;
}
- virtual Data::Folder *folder() const {
+ [[nodiscard]] virtual Data::Folder *folder() const {
return nullptr;
}
@@ -255,8 +261,9 @@ private:
enum class Flag : uchar {
IsThread = (1 << 0),
IsHistory = (1 << 1),
- UpdatePostponed = (1 << 2),
- InUnreadChangeBlock = (1 << 3),
+ IsSavedSublist = (1 << 2),
+ UpdatePostponed = (1 << 3),
+ InUnreadChangeBlock = (1 << 4),
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags;
@@ -265,8 +272,6 @@ private:
void pinnedIndexChanged(FilterId filterId, int was, int now);
[[nodiscard]] uint64 computeSortPosition(FilterId filterId) const;
- [[nodiscard]] virtual int chatListNameVersion() const = 0;
-
void setChatListExistence(bool exists);
not_null mainChatListLink(FilterId filterId) const;
Row *maybeMainChatListLink(FilterId filterId) const;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 5a9e98a69..c1a630b78 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_filters.h"
#include "data/data_cloud_file.h"
#include "data/data_changes.h"
+#include "data/data_saved_messages.h"
#include "data/data_stories.h"
#include "data/stickers/data_stickers.h"
#include "data/data_send_action.h"
@@ -219,7 +220,9 @@ InnerWidget::InnerWidget(
session().data().chatsListChanges(),
session().data().chatsListLoadedEvents()
) | rpl::filter([=](Data::Folder *folder) {
- return !_openedForum && (folder == _openedFolder);
+ return !_savedSublists
+ && !_openedForum
+ && (folder == _openedFolder);
}) | rpl::start_with_next([=] {
refresh();
}, lifetime());
@@ -499,6 +502,8 @@ int InnerWidget::searchInChatSkip() const {
}
void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
+ Expects(!folder || !_savedSublists);
+
if (_openedFolder == folder) {
return;
}
@@ -513,6 +518,8 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
}
void InnerWidget::changeOpenedForum(Data::Forum *forum) {
+ Expects(!forum || !_savedSublists);
+
if (_openedForum == forum) {
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) {
Painter p(this);
p.setInactive(
_controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
- if (_controller->contentOverlapped(this, e)) {
+ if (!_savedSublists && _controller->contentOverlapped(this, e)) {
return;
}
const auto activeEntry = _controller->activeChatEntryCurrent();
@@ -1416,11 +1450,14 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
}
}
const std::vector &InnerWidget::pinnedChatsOrder() const {
- return _openedForum
- ? session().data().pinnedChatsOrder(_openedForum)
+ const auto owner = &session().data();
+ return _savedSublists
+ ? owner->pinnedChatsOrder(&owner->savedMessages())
+ : _openedForum
+ ? owner->pinnedChatsOrder(_openedForum)
: _filterId
- ? session().data().pinnedChatsOrder(_filterId)
- : session().data().pinnedChatsOrder(_openedFolder);
+ ? owner->pinnedChatsOrder(_filterId)
+ : owner->pinnedChatsOrder(_openedFolder);
}
void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
@@ -1473,7 +1510,9 @@ void InnerWidget::savePinnedOrder() {
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);
} else if (_filterId) {
Api::SaveNewFilterPinned(&session(), _filterId);
@@ -1577,7 +1616,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) {
const auto delta = [&] {
if (localPosition.y() < _visibleTop) {
return localPosition.y() - _visibleTop;
- } else if ((_openedFolder || _openedForum || _filterId)
+ } else if ((_savedSublists || _openedFolder || _openedForum || _filterId)
&& localPosition.y() > _visibleBottom) {
return localPosition.y() - _visibleBottom;
}
@@ -1832,6 +1871,8 @@ void InnerWidget::handleChatListEntryRefreshes() {
return false;
} else if (const auto topic = event.key.topic()) {
return (topic->forum() == _openedForum);
+ } else if (event.key.sublist()) {
+ return _savedSublists;
} else {
return !_openedForum;
}
@@ -1848,6 +1889,8 @@ void InnerWidget::handleChatListEntryRefreshes() {
&& (_state == WidgetState::Default)
&& (key.topic()
? (key.topic()->forum() == _openedForum)
+ : key.sublist()
+ ? _savedSublists
: (entry->folder() == _openedFolder))) {
_dialogMoved.fire({ from, to });
}
@@ -2051,7 +2094,11 @@ void InnerWidget::enterEventHook(QEnterEvent *e) {
Row *InnerWidget::shownRowByKey(Key key) {
const auto entry = key.entry();
- if (_openedForum) {
+ if (_savedSublists) {
+ if (!entry->asSublist()) {
+ return nullptr;
+ }
+ } else if (_openedForum) {
const auto topic = entry->asTopic();
if (!topic || topic->forum() != _openedForum) {
return nullptr;
@@ -2114,7 +2161,9 @@ void InnerWidget::updateSelectedRow(Key key) {
}
void InnerWidget::refreshShownList() {
- const auto list = _openedForum
+ const auto list = _savedSublists
+ ? session().data().savedMessages().chatsList()->indexed()
+ : _openedForum
? _openedForum->topicsList()->indexed()
: _filterId
? session().data().chatsFilters().chatsList(_filterId)->indexed()
@@ -2294,15 +2343,19 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
}
};
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());
} else {
- append(session().data().chatsList()->indexed());
+ const auto owner = &session().data();
+ append(owner->chatsList()->indexed());
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(session().data().contactsNoChatsList());
+ append(owner->contactsNoChatsList());
}
}
refresh(true);
@@ -2759,6 +2812,10 @@ void InnerWidget::refreshEmptyLabel() {
const auto data = &session().data();
const auto state = !_shownList->empty()
? EmptyState::None
+ : _savedSublists
+ ? (data->savedMessages().chatsList()->loaded()
+ ? EmptyState::EmptySavedSublists
+ : EmptyState::Loading)
: _openedForum
? (_openedForum->topicsList()->loaded()
? EmptyState::EmptyForum
@@ -2783,6 +2840,8 @@ void InnerWidget::refreshEmptyLabel() {
? tr::lng_no_chats_filter()
: (state == EmptyState::EmptyForum)
? tr::lng_forum_no_topics()
+ : (state == EmptyState::EmptySavedSublists)
+ ? tr::lng_no_saved_sublists()
: tr::lng_contacts_loading();
auto link = (state == EmptyState::NoContacts)
? tr::lng_add_contact_button()
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index 608513c85..55d257bb5 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -107,6 +107,7 @@ public:
void changeOpenedFolder(Data::Folder *folder);
void changeOpenedForum(Data::Forum *forum);
+ void showSavedSublists();
void selectSkip(int32 direction);
void selectSkipPage(int32 pixels, int32 direction);
@@ -198,6 +199,7 @@ private:
NoContacts,
EmptyFolder,
EmptyForum,
+ EmptySavedSublists,
};
struct PinnedRow {
@@ -503,6 +505,8 @@ private:
float64 _narrowRatio = 0.;
bool _geometryInited = false;
+ bool _savedSublists = false;
+
base::unique_qptr _menu;
};
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
index fe96a5a7d..dfa728141 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
+#include "data/data_saved_sublist.h"
#include "history/history.h"
namespace Dialogs {
@@ -25,6 +26,9 @@ Key::Key(Data::Thread *thread) : _value(thread) {
Key::Key(Data::ForumTopic *topic) : _value(topic) {
}
+Key::Key(Data::SavedSublist *sublist) : _value(sublist) {
+}
+
Key::Key(not_null history) : _value(history) {
}
@@ -37,6 +41,9 @@ Key::Key(not_null folder) : _value(folder) {
Key::Key(not_null topic) : _value(topic) {
}
+Key::Key(not_null sublist) : _value(sublist) {
+}
+
not_null Key::entry() const {
Expects(_value != nullptr);
@@ -59,6 +66,10 @@ Data::Thread *Key::thread() const {
return _value ? _value->asThread() : nullptr;
}
+Data::SavedSublist *Key::sublist() const {
+ return _value ? _value->asSublist() : nullptr;
+}
+
History *Key::owningHistory() const {
if (const auto thread = this->thread()) {
return thread->owningHistory();
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h
index 34fd9aa29..2396b216f 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_key.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.h
@@ -14,6 +14,7 @@ namespace Data {
class Thread;
class Folder;
class ForumTopic;
+class SavedSublist;
} // namespace Data
namespace Dialogs {
@@ -29,12 +30,14 @@ public:
Key(Data::Folder *folder);
Key(Data::Thread *thread);
Key(Data::ForumTopic *topic);
+ Key(Data::SavedSublist *sublist);
Key(not_null entry) : _value(entry) {
}
Key(not_null history);
Key(not_null thread);
Key(not_null folder);
Key(not_null topic);
+ Key(not_null sublist);
explicit operator bool() const {
return (_value != nullptr);
@@ -46,6 +49,7 @@ public:
[[nodiscard]] Data::Thread *thread() const;
[[nodiscard]] History *owningHistory() const;
[[nodiscard]] PeerData *peer() const;
+ [[nodiscard]] Data::SavedSublist *sublist() const;
friend inline constexpr auto operator<=>(Key, Key) noexcept = default;
@@ -102,6 +106,7 @@ struct EntryState {
Scheduled,
Pinned,
Replies,
+ SavedSublist,
ContextMenu,
};
diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp
index aaa03035f..54247193c 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp
@@ -261,8 +261,10 @@ void Row::recountHeight(float64 narrowRatio) {
: st::defaultDialogRow.height;
} else if (_id.folder()) {
_height = st::defaultDialogRow.height;
- } else {
+ } else if (_id.topic()) {
_height = st::forumTopicRow.height;
+ } else {
+ _height = st::defaultDialogRow.height;
}
}
diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h
index e814e5ff1..738c9faa4 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_row.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_row.h
@@ -131,6 +131,9 @@ public:
[[nodiscard]] Data::Thread *thread() const {
return _id.thread();
}
+ [[nodiscard]] Data::SavedSublist *sublist() const {
+ return _id.sublist();
+ }
[[nodiscard]] not_null entry() const {
return _id.entry();
}
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 85a50038c..30c7ddd88 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -1774,6 +1774,7 @@ bool Widget::searchMessages(bool searchCache) {
(_searchQueryFrom
? _searchQueryFrom->input
: MTP_inputPeerEmpty()),
+ MTPInputPeer(), // saved_peer_id
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@@ -2020,6 +2021,7 @@ void Widget::searchMore() {
(_searchQueryFrom
? _searchQueryFrom->input
: MTP_inputPeerEmpty()),
+ MTPInputPeer(), // saved_peer_id
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@@ -2092,6 +2094,7 @@ void Widget::searchMore() {
(_searchQueryFrom
? _searchQueryFrom->input
: MTP_inputPeerEmpty()),
+ MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@@ -2708,7 +2711,7 @@ void Widget::filterCursorMoved() {
}
void Widget::completeHashtag(QString tag) {
- const auto t = _filter->getLastText();;
+ const auto t = _filter->getLastText();
auto cur = _filter->textCursor().position();
auto hashtag = QString();
for (int start = cur; start > 0;) {
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
index 5299b648c..91362c73e 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_drafts.h"
#include "data/data_forum_topic.h"
+#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "dialogs/dialogs_list.h"
#include "dialogs/dialogs_three_state_icon.h"
@@ -265,10 +266,12 @@ void PaintFolderEntryText(
}
enum class Flag {
- SavedMessages = 0x08,
- RepliesMessages = 0x10,
- AllowUserOnline = 0x20,
- TopicJumpRipple = 0x40,
+ SavedMessages = 0x008,
+ RepliesMessages = 0x010,
+ AllowUserOnline = 0x020,
+ TopicJumpRipple = 0x040,
+ HiddenAuthor = 0x080,
+ MyNotes = 0x100,
};
inline constexpr bool is_flag_type(Flag) { return true; }
@@ -311,6 +314,7 @@ void PaintRow(
const auto history = entry->asHistory();
const auto thread = entry->asThread();
+ const auto sublist = entry->asSublist();
if (flags & Flag::SavedMessages) {
EmptyUserpic::PaintSavedMessages(
@@ -326,6 +330,20 @@ void PaintRow(
context.st->padding.top(),
context.width,
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) {
hiddenSenderInfo->emptyUserpic.paintCircle(
p,
@@ -547,7 +565,7 @@ void PaintRow(
// Empty history
}
} else if (!item->isEmpty()) {
- if (thread && !promoted) {
+ if ((thread || sublist) && !promoted) {
PaintRowDate(p, date, rectForName, context);
}
@@ -606,10 +624,18 @@ void PaintRow(
}
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)
? 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);
if (textWidth > rectForName.width()) {
text = st::semiboldFont->elided(text, rectForName.width());
@@ -621,7 +647,7 @@ void PaintRow(
: st::dialogsNameFg);
p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text);
} else if (from) {
- if (history && !context.search) {
+ if ((history || sublist) && !context.search) {
const auto badgeWidth = fromBadge.drawGetWidth(
p,
rectForName,
@@ -732,6 +758,7 @@ void RowPainter::Paint(
const auto entry = row->entry();
const auto history = row->history();
const auto thread = row->thread();
+ const auto sublist = row->sublist();
const auto peer = history ? history->peer.get() : nullptr;
const auto badgesState = entry->chatListBadgesState();
entry->chatListPreloadData(); // Allow chat list message resolve.
@@ -771,11 +798,22 @@ void RowPainter::Paint(
? (history->peer->migrateTo()
? history->peer->migrateTo()
: history->peer.get())
+ : sublist
+ ? sublist->peer().get()
: nullptr;
const auto allowUserOnline = true;// !context.narrow || badgesState.empty();
const auto flags = (allowUserOnline ? Flag::AllowUserOnline : Flag(0))
- | (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0))
- | (peer && peer->isRepliesChat() ? Flag::RepliesMessages : Flag(0))
+ | ((sublist && from->isSelf())
+ ? 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));
const auto paintItemCallback = [&](int nameleft, int namewidth) {
const auto texttop = context.st->textTop;
@@ -810,6 +848,8 @@ void RowPainter::Paint(
? nullptr
: thread
? &thread->lastItemDialogsView()
+ : sublist
+ ? &sublist->lastItemDialogsView()
: nullptr;
if (view) {
const auto forum = context.st->topicsHeight
@@ -872,7 +912,9 @@ void RowPainter::Paint(
if (const auto peer = searchChat.peer()) {
if (const auto forwarded = item->Get()) {
if (peer->isSelf() || forwarded->imported) {
- return forwarded->hiddenSenderInfo.get();
+ return forwarded->savedFromHiddenSenderInfo.get()
+ ? forwarded->savedFromHiddenSenderInfo.get()
+ : forwarded->originalHiddenSenderInfo.get();
}
}
}
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp
index cda8da94d..e6a93af3a 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp
@@ -36,7 +36,6 @@ constexpr auto kCollapseAfterRatio = 0.68;
constexpr auto kFrictionRatio = 0.15;
constexpr auto kExpandCatchUpDuration = crl::time(200);
constexpr auto kMaxTooltipNames = 3;
-constexpr auto kStoriesTooltipHideBgOpacity = 0.2;
[[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) {
const auto &full = st.full;
diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp
index 5ed8addd9..61f29b16c 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.cpp
+++ b/Telegram/SourceFiles/export/export_api_wrap.cpp
@@ -1624,6 +1624,7 @@ void ApiWrap::requestChatMessages(
realPeerInput,
MTP_string(), // query
MTP_inputPeerSelf(),
+ MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp
index 9a5f5b3d6..dc23fd38f 100644
--- a/Telegram/SourceFiles/export/output/export_output_json.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_json.cpp
@@ -446,7 +446,6 @@ QByteArray SerializeMessage(
pushAction("send_payment");
push("amount", data.amount);
push("currency", data.currency);
- const auto amount = FormatMoneyAmount(data.amount, data.currency);
pushReplyToMsgId("invoice_message_id");
if (data.recurringUsed) {
push("recurring", "used");
diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp
index b59bf86ba..378d9b4d1 100644
--- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp
+++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp
@@ -134,8 +134,9 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) {
}
DEBUG_LOG(("Video Info: "
"Trying \"%1\" hardware acceleration for \"%2\" decoder."
- ).arg(av_hwdevice_get_type_name(type)
- ).arg(context->codec->name));
+ ).arg(
+ av_hwdevice_get_type_name(type),
+ context->codec->name));
if (parent->hw_device_ctx) {
av_buffer_unref(&parent->hw_device_ctx);
}
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index 84be6934d..fb6f950e0 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -442,7 +442,6 @@ void InnerWidget::applyFilter(FilterValue &&value) {
}
void InnerWidget::applySearch(const QString &query) {
- auto clearQuery = query.trimmed();
if (_searchQuery != query) {
_searchQuery = query;
clearAndRequestLog();
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
index 6f4f61be5..f9ece93b0 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
@@ -117,6 +117,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) {
data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
data.vpeer_id(),
+ MTPPeer(), // saved_peer_id
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(data.vvia_bot_id().value_or_empty()),
MTPMessageReplyHeader(),
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 3e998600b..629fae9c9 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/notify/data_notify_settings.h"
#include "data/stickers/data_stickers.h"
#include "data/data_drafts.h"
+#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_channel_admins.h"
@@ -614,6 +615,11 @@ not_null History::addNewItem(
addNewToBack(item, unread);
checkForLoadedAtTop(item);
}
+
+ if (const auto sublist = item->savedSublist()) {
+ sublist->applyMaybeLast(item, unread);
+ }
+
return item;
}
@@ -1436,6 +1442,12 @@ void History::addCreatedOlderSlice(
if (loadedAtBottom()) {
// Add photos to overview and authors to lastAuthors.
addItemsToLists(items);
+
+ for (const auto &item : items) {
+ if (const auto sublist = item->savedSublist()) {
+ sublist->applyMaybeLast(item);
+ }
+ }
}
addToSharedMedia(items);
}
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index 909549684..adeb63fc2 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -365,6 +365,7 @@ public:
void takeLocalDraft(not_null from);
void applyCloudDraft(MsgId topicRootId);
void draftSavedToCloud(MsgId topicRootId);
+ void requestChatListMessage();
[[nodiscard]] const Data::ForwardDraft &forwardDraft(
MsgId topicRootId) const;
@@ -383,9 +384,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
- void requestChatListMessage() override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
+ int chatListNameVersion() const override;
const base::flat_set &chatListNameWords() const override;
const base::flat_set &chatListFirstLetters() const override;
void chatListPreloadData() override;
@@ -589,8 +590,6 @@ private:
[[nodiscard]] Dialogs::UnreadState computeUnreadState() const;
void setFolderPointer(Data::Folder *folder);
- int chatListNameVersion() const override;
-
void hasUnreadMentionChanged(bool has) override;
void hasUnreadReactionChanged(bool has) override;
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index d78aec6ac..7de4a765f 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -1223,7 +1223,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
width(),
st::msgPhotoSize,
context.paused);
- } else if (const auto info = item->hiddenSenderInfo()) {
+ } else if (const auto info = item->displayHiddenSenderInfo()) {
if (info->customUserpic.empty()) {
info->emptyUserpic.paintCircle(
p,
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 80733f204..43fe722a0 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -41,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_updates.h"
#include "data/notify/data_notify_settings.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_changes.h"
#include "data/data_session.h"
@@ -150,10 +152,16 @@ struct HistoryItem::CreateConfig {
QString originalSenderName;
QString originalPostAuthor;
+ PeerId savedSublistPeer = 0;
+
QString forwardPsaType;
PeerId savedFromPeer = 0;
MsgId savedFromMsgId = 0;
+ PeerId savedFromSenderId = 0;
+ QString savedFromSenderName;
+ bool savedFromOutgoing = false;
+
TimeId editDate = 0;
HistoryMessageMarkupData markup;
HistoryMessageRepliesData replies;
@@ -180,6 +188,13 @@ void HistoryItem::FillForwardedInfo(
config.savedFromPeer = peerFromMTP(*savedFromPeer);
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();
}
@@ -259,7 +274,8 @@ std::unique_ptr HistoryItem::CreateMedia(
item,
item->history()->owner().processDocument(document),
media.is_nopremium(),
- media.is_spoiler());
+ media.is_spoiler(),
+ media.vttl_seconds().value_or_empty());
}, [](const MTPDdocumentEmpty &) -> Result {
return nullptr;
});
@@ -491,7 +507,7 @@ HistoryItem::HistoryItem(
}
if (!dropForwardInfo) {
config.originalDate = original->originalDate();
- if (const auto info = original->hiddenSenderInfo()) {
+ if (const auto info = original->originalHiddenSenderInfo()) {
config.originalSenderName = info->name;
} else if (const auto originalSender = original->originalSender()) {
config.originalSenderId = originalSender->id;
@@ -515,6 +531,11 @@ HistoryItem::HistoryItem(
config.savedFromPeer = original->history()->peer->id;
config.savedFromMsgId = original->id;
//}
+
+ config.savedFromOutgoing = original->out();
+ config.savedFromSenderId = original->Get()
+ ? original->author()->id
+ : PeerId();
}
if (flags & MessageFlag::HasPostAuthor) {
config.postAuthor = postAuthor;
@@ -634,7 +655,8 @@ HistoryItem::HistoryItem(
this,
document,
skipPremiumEffect,
- spoiler);
+ spoiler,
+ /*ttlSeconds = */0);
setText(caption);
}
@@ -790,6 +812,9 @@ HistoryItem::~HistoryItem() {
if (const auto reply = Get()) {
reply->clearData(this);
}
+ if (const auto saved = Get()) {
+ saved->sublist->removeOne(this);
+ }
clearDependencyMessage();
applyTTL(0);
}
@@ -1214,10 +1239,10 @@ PeerData *HistoryItem::computeDisplayFrom() const {
if (const auto sender = discussionPostOriginalSender()) {
return sender;
} else if (const auto forwarded = Get()) {
- if (_history->peer->isSelf()
- || _history->peer->isRepliesChat()
- || forwarded->imported) {
- return forwarded->originalSender;
+ if (showForwardsFromSender(forwarded)) {
+ return forwarded->forwardOfForward()
+ ? forwarded->savedFromSender
+ : forwarded->originalSender;
}
}
return author().get();
@@ -1234,10 +1259,10 @@ PeerData *HistoryItem::displayFrom() const {
uint8 HistoryItem::colorIndex() const {
if (const auto from = displayFrom()) {
return from->colorIndex();
- } else if (const auto info = hiddenSenderInfo()) {
+ } else if (const auto info = displayHiddenSenderInfo()) {
return info->colorIndex;
}
- Unexpected("No displayFrom and no hiddenSenderInfo.");
+ Unexpected("No displayFrom and no displayHiddenSenderInfo.");
}
std::unique_ptr HistoryItem::createView(
@@ -1260,6 +1285,9 @@ void HistoryItem::invalidateChatListEntry() {
if (const auto topic = this->topic()) {
topic->lastItemDialogsView().itemInvalidated(this);
}
+ if (const auto sublist = savedSublist()) {
+ sublist->lastItemDialogsView().itemInvalidated(this);
+ }
}
void HistoryItem::customEmojiRepaint() {
@@ -1680,7 +1708,8 @@ void HistoryItem::setStoryFields(not_null story) {
this,
document,
/*skipPremiumEffect=*/false,
- spoiler);
+ spoiler,
+ /*ttlSeconds = */0);
}
setText(story->caption());
}
@@ -2510,16 +2539,32 @@ PeerData *HistoryItem::originalSender() const {
return forwarded->originalSender;
}
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()) {
- return forwarded->hiddenSenderInfo.get();
+ return forwarded->originalHiddenSenderInfo.get();
}
return nullptr;
}
+const HiddenSenderInfo *HistoryItem::displayHiddenSenderInfo() const {
+ if (const auto forwarded = Get()) {
+ return forwarded->savedFromHiddenSenderInfo
+ ? forwarded->savedFromHiddenSenderInfo.get()
+ : forwarded->originalHiddenSenderInfo.get();
+ }
+ return nullptr;
+}
+
+bool HistoryItem::showForwardsFromSender(
+ not_null forwarded) const {
+ const auto peer = history()->peer;
+ return !forwarded->story
+ && (peer->isSelf() || peer->isRepliesChat() || forwarded->imported);
+}
+
not_null HistoryItem::fromOriginal() const {
if (const auto forwarded = Get()) {
if (forwarded->originalSender) {
@@ -3104,6 +3149,34 @@ bool HistoryItem::isEmpty() const {
&& !Has();
}
+Data::SavedSublist *HistoryItem::savedSublist() const {
+ if (const auto saved = Get()) {
+ 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()) {
+ return forwarded->savedFromSender;
+ }
+ return nullptr;
+}
+
+const HiddenSenderInfo *HistoryItem::savedFromHiddenSenderInfo() const {
+ if (const auto forwarded = Get()) {
+ return forwarded->savedFromHiddenSenderInfo.get();
+ }
+ return nullptr;
+}
+
TextWithEntities HistoryItem::notificationText(
NotificationTextOptions options) const {
auto result = [&] {
@@ -3156,16 +3229,25 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
? tr::lng_from_you(tr::now)
: sender->shortName();
};
- result.icon = (Get() != nullptr)
+ const auto forwarded = Get();
+ const auto forwardFromSender = forwarded
+ && showForwardsFromSender(forwarded);
+ result.icon = (forwarded
+ && (!forwardFromSender || forwarded->forwardOfForward()))
? ItemPreview::Icon::ForwardedMessage
: replyToStory().valid()
? ItemPreview::Icon::ReplyToStory
: ItemPreview::Icon::None;
const auto fromForwarded = [&]() -> std::optional {
- if (const auto forwarded = Get()) {
- return forwarded->originalSender
- ? fromSender(forwarded->originalSender)
- : forwarded->hiddenSenderInfo->name;
+ if (forwarded) {
+ const auto sender = forwarded->forwardOfForward()
+ ? forwarded->savedFromSender
+ : forwarded->originalSender;
+ return sender
+ ? fromSender(sender)
+ : forwarded->savedFromHiddenSenderInfo
+ ? forwarded->savedFromHiddenSenderInfo->name
+ : forwarded->originalHiddenSenderInfo->name;
}
return {};
};
@@ -3255,9 +3337,28 @@ void HistoryItem::createComponents(CreateConfig &&config) {
} else if (config.inlineMarkup) {
mask |= HistoryMessageReplyMarkup::Bit();
}
+ if (_history->peer->isSelf()) {
+ mask |= HistoryMessageSaved::Bit();
+ }
UpdateComponents(mask);
+ if (const auto saved = Get()) {
+ 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()) {
reply->set(std::move(config.reply));
if (!reply->updateData(this)) {
@@ -3343,9 +3444,10 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
? _history->owner().peer(originalSender).get()
: nullptr;
if (!forwarded->originalSender) {
- forwarded->hiddenSenderInfo = std::make_unique(
- config.originalSenderName,
- config.imported);
+ forwarded->originalHiddenSenderInfo
+ = std::make_unique(
+ config.originalSenderName,
+ config.imported);
}
forwarded->originalId = config.originalId;
forwarded->originalPostAuthor = config.originalPostAuthor;
@@ -3353,6 +3455,14 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
forwarded->savedFromPeer = _history->owner().peerLoaded(
config.savedFromPeer);
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(config.savedFromSenderName, false);
+ }
forwarded->imported = config.imported;
}
@@ -3551,6 +3661,9 @@ void HistoryItem::applyTTL(const MTPDmessageService &data) {
void HistoryItem::createComponents(const MTPDmessage &data) {
auto config = CreateConfig();
+ config.savedSublistPeer = data.vsaved_peer_id()
+ ? peerFromMTP(*data.vsaved_peer_id())
+ : PeerId();
if (const auto forwarded = data.vfwd_from()) {
forwarded->match([&](const MTPDmessageFwdHeader &data) {
FillForwardedInfo(config, data);
@@ -3636,25 +3749,43 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
const auto ttl = data.vttl_seconds();
Assert(ttl != nullptr);
- setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, *ttl);
- if (out()) {
- setServiceText({
- tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities)
- });
- } else {
- auto result = PreparedServiceText();
- result.links.push_back(fromLink());
- result.text = tr::lng_ttl_video_received(
- tr::now,
- lt_from,
- fromLinkText(), // Link 1.
- Ui::Text::WithEntities);
- setServiceText(std::move(result));
+ if (data.is_video()) {
+ setSelfDestruct(
+ HistoryServiceSelfDestruct::Type::Video,
+ *ttl);
+ if (out()) {
+ setServiceText({
+ tr::lng_ttl_video_sent(
+ tr::now,
+ Ui::Text::WithEntities)
+ });
+ } else {
+ auto result = PreparedServiceText();
+ result.links.push_back(fromLink());
+ 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 {
- setServiceText({
- tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities)
- });
+ auto text = (data.is_video()
+ ? 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) {
setServiceText(prepareStoryMentionText());
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index 413973d5f..a89d5d30d 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -20,6 +20,7 @@ struct HistoryMessageViews;
struct HistoryMessageMarkupData;
struct HistoryMessageReplyMarkup;
struct HistoryMessageTranslation;
+struct HistoryMessageForwarded;
struct HistoryServiceDependentData;
enum class HistorySelfDestructType;
struct PreparedServiceText;
@@ -56,6 +57,7 @@ class ForumTopic;
class Thread;
struct SponsoredFrom;
class Story;
+class SavedSublist;
} // namespace Data
namespace Main {
@@ -481,11 +483,21 @@ public:
[[nodiscard]] TimeId originalDate() const;
[[nodiscard]] PeerData *originalSender() const;
- [[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const;
+ [[nodiscard]] const HiddenSenderInfo *originalHiddenSenderInfo() const;
[[nodiscard]] not_null fromOriginal() const;
[[nodiscard]] QString originalPostAuthor() 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 forwarded) const;
+
[[nodiscard]] bool isEmpty() const;
[[nodiscard]] MessageGroupId groupId() const;
diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp
index 4d93f95e7..82a861c81 100644
--- a/Telegram/SourceFiles/history/history_item_components.cpp
+++ b/Telegram/SourceFiles/history/history_item_components.cpp
@@ -190,7 +190,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
const auto name = TextWithEntities{
.text = (originalSender
? originalSender->name()
- : hiddenSenderInfo->name)
+ : originalHiddenSenderInfo->name)
};
if (!originalPostAuthor.isEmpty()) {
phrase = tr::lng_forwarded_signed(
diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h
index d2ada01e1..b4a6981bb 100644
--- a/Telegram/SourceFiles/history/history_item_components.h
+++ b/Telegram/SourceFiles/history/history_item_components.h
@@ -126,9 +126,13 @@ private:
struct HistoryMessageForwarded : public RuntimeComponent {
void create(const HistoryMessageVia *via) const;
+ [[nodiscard]] bool forwardOfForward() const {
+ return savedFromSender || savedFromHiddenSenderInfo;
+ }
+
TimeId originalDate = 0;
PeerData *originalSender = nullptr;
- std::unique_ptr hiddenSenderInfo;
+ std::unique_ptr originalHiddenSenderInfo;
QString originalPostAuthor;
QString psaType;
MsgId originalId = 0;
@@ -136,10 +140,19 @@ struct HistoryMessageForwarded : public RuntimeComponent savedFromHiddenSenderInfo;
+
+ bool savedFromOutgoing = false;
bool imported = false;
bool story = false;
};
+struct HistoryMessageSaved : public RuntimeComponent {
+ Data::SavedSublist *sublist = nullptr;
+};
+
class ReplyToMessagePointer final {
public:
ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) {
diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp
index 0c59ed76f..989c8c5ee 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.cpp
+++ b/Telegram/SourceFiles/history/history_item_helpers.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_changes.h"
+#include "data/data_document.h"
#include "data/data_group_call.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
@@ -452,7 +453,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
}, [](const MTPDmessageMediaPhoto &data) {
const auto photo = data.vphoto();
if (data.vttl_seconds()) {
- return Result::HasTimeToLive;
+ return Result::HasUnsupportedTimeToLive;
} else if (!photo) {
return Result::Empty;
}
@@ -464,7 +465,11 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
}, [](const MTPDmessageMediaDocument &data) {
const auto document = data.vdocument();
if (data.vttl_seconds()) {
- return Result::HasTimeToLive;
+ if (data.is_video()) {
+ return Result::HasUnsupportedTimeToLive;
+ } else if (!document) {
+ return Result::HasExpiredMediaTimeToLive;
+ }
} else if (!document) {
return Result::Empty;
}
@@ -780,3 +785,31 @@ void ShowTrialTranscribesToast(int left, TimeId until) {
.filter = filter,
});
}
+
+void ClearMediaAsExpired(not_null 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 item) {
+ return !item->out() && item->media()->ttlSeconds();
+}
diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h
index 661efe652..f96d9c092 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.h
+++ b/Telegram/SourceFiles/history/history_item_helpers.h
@@ -43,7 +43,8 @@ enum class MediaCheckResult {
Good,
Unsupported,
Empty,
- HasTimeToLive,
+ HasExpiredMediaTimeToLive,
+ HasUnsupportedTimeToLive,
HasStoryMention,
};
[[nodiscard]] MediaCheckResult CheckMessageMedia(
@@ -155,3 +156,6 @@ ClickHandlerPtr JumpToStoryClickHandler(
CallId callId);
void ShowTrialTranscribesToast(int left, TimeId until);
+
+void ClearMediaAsExpired(not_null item);
+[[nodiscard]] bool IsVoiceOncePlayable(not_null item);
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp
index 0786b0745..d1dbbd0e3 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp
@@ -281,7 +281,7 @@ private:
base::unique_qptr _cancel;
base::unique_qptr _select;
- rpl::variable _from = nullptr;;
+ rpl::variable _from = nullptr;
base::Timer _searchTimer;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp
index 9c7ce70c5..4eef16970 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp
@@ -376,7 +376,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
userpicTop,
width(),
st::msgPhotoSize);
- } else if (const auto info = item->hiddenSenderInfo()) {
+ } else if (const auto info = item->originalHiddenSenderInfo()) {
if (info->customUserpic.empty()) {
info->emptyUserpic.paintCircle(
p,
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
index ff3347cf5..868e55383 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
@@ -127,7 +127,7 @@ void ForwardPanel::checkTexts() {
for (const auto item : _data.items) {
if (const auto from = item->originalSender()) {
version += from->nameVersion();
- } else if (const auto info = item->hiddenSenderInfo()) {
+ } else if (const auto info = item->originalHiddenSenderInfo()) {
++version;
} else {
Unexpected("Corrupt forwarded information in message.");
@@ -168,7 +168,7 @@ void ForwardPanel::updateTexts() {
names.push_back(from->shortName());
fullname = from->name();
}
- } else if (const auto info = item->hiddenSenderInfo()) {
+ } else if (const auto info = item->originalHiddenSenderInfo()) {
if (!insertedNames.contains(info->name)) {
insertedNames.emplace(info->name);
names.push_back(info->firstName);
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index 771555441..028286ca8 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -88,8 +88,7 @@ enum class FilterType {
const auto durationString = Ui::FormatDurationText(duration / kPrecision);
const auto decimalPart = duration % kPrecision;
return QString("%1%2%3")
- .arg(durationString)
- .arg(QLocale().decimalPoint())
+ .arg(durationString, QLocale().decimalPoint())
.arg(decimalPart);
}
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index a599e7f2c..8d787b017 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -118,7 +118,7 @@ void SavePhotoToFile(not_null photo) {
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(
Core::App().getFileDialogParent(),
tr::lng_save_photo(tr::now),
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index 34b7a39e0..4f82e7578 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -64,16 +64,20 @@ Element *MousedElement/* = nullptr*/;
HistoryMessageForwarded *prevForwarded,
not_null item,
HistoryMessageForwarded *forwarded) {
- const auto sender = previous->originalSender();
+ const auto sender = previous->displayFrom();
if ((prevForwarded != nullptr) != (forwarded != nullptr)) {
return false;
- } else if (sender != item->originalSender()) {
+ } else if (sender != item->displayFrom()) {
return false;
} else if (!prevForwarded || sender) {
return true;
}
- const auto previousInfo = prevForwarded->hiddenSenderInfo.get();
- const auto itemInfo = forwarded->hiddenSenderInfo.get();
+ const auto previousInfo = prevForwarded->savedFromHiddenSenderInfo
+ ? prevForwarded->savedFromHiddenSenderInfo.get()
+ : prevForwarded->originalHiddenSenderInfo.get();
+ const auto itemInfo = forwarded->savedFromHiddenSenderInfo
+ ? forwarded->savedFromHiddenSenderInfo.get()
+ : forwarded->originalHiddenSenderInfo.get();
Assert(previousInfo != nullptr);
Assert(itemInfo != nullptr);
return (*previousInfo == *itemInfo);
@@ -1343,6 +1347,10 @@ bool Element::hasOutLayout() const {
return false;
}
+bool Element::hasRightLayout() const {
+ return hasOutLayout() && !_delegate->elementIsChatWide();
+}
+
bool Element::drawBubble() const {
return false;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h
index b0d0a8fc6..3fbaa598f 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.h
+++ b/Telegram/SourceFiles/history/view/history_view_element.h
@@ -56,7 +56,8 @@ enum class Context : char {
Replies,
Pinned,
AdminLog,
- ContactPreview
+ ContactPreview,
+ SavedSublist,
};
enum class OnlyEmojiAndSpaces : char {
@@ -438,6 +439,7 @@ public:
[[nodiscard]] virtual TopicButton *displayedTopicButton() const;
[[nodiscard]] virtual bool displayForwardedFrom() const;
[[nodiscard]] virtual bool hasOutLayout() const;
+ [[nodiscard]] bool hasRightLayout() const;
[[nodiscard]] virtual bool drawBubble() const;
[[nodiscard]] virtual bool hasBubble() const;
[[nodiscard]] virtual bool unwrapped() const;
diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp
index aeafdf156..91e18f9d8 100644
--- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp
@@ -220,8 +220,7 @@ QRect EmojiInteractions::computeRect(const Play &play) const {
? int(sticker.width() * kPremiumShift)
: (size.width() / 40);
const auto inner = view->innerGeometry();
- const auto rightAligned = view->hasOutLayout()
- && !view->delegate()->elementIsChatWide();
+ const auto rightAligned = view->hasRightLayout();
const auto left = rightAligned
? (inner.x() + inner.width() + shift - size.width())
: (inner.x() - shift);
@@ -241,8 +240,7 @@ void EmojiInteractions::paint(QPainter &p) {
}
auto request = Lottie::FrameRequest();
request.box = play.outer * factor;
- const auto rightAligned = play.view->hasOutLayout()
- && !play.view->delegate()->elementIsChatWide();
+ const auto rightAligned = play.view->hasRightLayout();
if (!rightAligned) {
request.mirrorHorizontal = true;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 08332097f..50822615c 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -2181,7 +2181,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
userpicTop,
view->width(),
st::msgPhotoSize);
- } else if (const auto info = item->hiddenSenderInfo()) {
+ } else if (const auto info = item->displayHiddenSenderInfo()) {
if (info->customUserpic.empty()) {
info->emptyUserpic.paintCircle(
p,
@@ -3665,7 +3665,7 @@ void ListWidget::performDrag() {
_reactionsManager->updateButton({});
_controller->widget()->launchDrag(
std::move(mimeData),
- crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); }));;
+ crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); }));
}
}
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 50d066f4c..54e03343c 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -738,7 +738,7 @@ QSize Message::performCountOptimalSize() {
validateFromNameText(from);
const auto &name = from
? _fromName
- : item->hiddenSenderInfo()->nameText();
+ : item->displayHiddenSenderInfo()->nameText();
auto namew = st::msgPadding.left()
+ name.maxWidth()
+ (_fromNameStatus
@@ -1186,7 +1186,9 @@ void Message::draw(Painter &p, const PaintContext &context) const {
(g.height() - size->height()) / 2,
0,
st::historyFastShareBottom);
- const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft;
+ const auto fastShareLeft = hasRightLayout()
+ ? (g.left() - size->width() - st::historyFastShareLeft)
+ : (g.left() + g.width() + st::historyFastShareLeft);
const auto fastShareTop = data()->isSponsored()
? g.top() + fastShareSkip
: g.top() + g.height() - fastShareSkip - size->height();
@@ -1389,7 +1391,7 @@ void Message::paintFromName(
const auto stm = context.messageStyle();
const auto from = item->displayFrom();
- const auto info = from ? nullptr : item->hiddenSenderInfo();
+ const auto info = from ? nullptr : item->displayHiddenSenderInfo();
Assert(from || info);
const auto nameFg = !context.outbg
? FromNameFg(context, colorIndex())
@@ -1974,13 +1976,6 @@ void Message::unloadHeavyPart() {
}
}
-bool Message::showForwardsFromSender(
- not_null forwarded) const {
- const auto peer = data()->history()->peer;
- return !forwarded->story
- && (peer->isSelf() || peer->isRepliesChat() || forwarded->imported);
-}
-
bool Message::hasFromPhoto() const {
if (isHidden()) {
return false;
@@ -1990,7 +1985,8 @@ bool Message::hasFromPhoto() const {
return true;
case Context::History:
case Context::Pinned:
- case Context::Replies: {
+ case Context::Replies:
+ case Context::SavedSublist: {
const auto item = data();
if (item->isPost()) {
return false;
@@ -2003,7 +1999,7 @@ bool Message::hasFromPhoto() const {
} else if (const auto forwarded = item->Get()) {
const auto peer = item->history()->peer;
if (peer->isSelf() || peer->isRepliesChat()) {
- return true;
+ return !hasOutLayout();
}
}
return !item->out() && !item->history()->peer->isUser();
@@ -2205,7 +2201,9 @@ TextState Message::textState(
(g.height() - size->height()) / 2,
0,
st::historyFastShareBottom);
- const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft;
+ const auto fastShareLeft = hasRightLayout()
+ ? (g.left() - size->width() - st::historyFastShareLeft)
+ : (g.left() + g.width() + st::historyFastShareLeft);
const auto fastShareTop = data()->isSponsored()
? g.top() + fastShareSkip
: g.top() + g.height() - fastShareSkip - size->height();
@@ -2328,7 +2326,7 @@ bool Message::getStateFromName(
if (from) {
validateFromNameText(from);
return &_fromName;
- } else if (const auto info = item->hiddenSenderInfo()) {
+ } else if (const auto info = item->displayHiddenSenderInfo()) {
return &info->nameText();
} else {
Unexpected("Corrupt forwarded information in message.");
@@ -2767,11 +2765,10 @@ Reactions::ButtonParameters Message::reactionButtonParameters(
const TextState &reactionState) const {
using namespace Reactions;
auto result = ButtonParameters{ .context = data()->fullId() };
- const auto outbg = hasOutLayout();
const auto outsideBubble = (!_comments && !embedReactionsInBubble());
const auto geometry = countGeometry();
result.pointer = position;
- const auto onTheLeft = (outbg && !delegate()->elementIsChatWide());
+ const auto onTheLeft = hasRightLayout();
const auto keyboard = data()->inlineReplyKeyboard();
const auto keyboardHeight = keyboard
@@ -3017,7 +3014,9 @@ void Message::validateFromNameText(PeerData *from) const {
Ui::NameTextOptions());
}
if (from->isPremium()
- || (from->isChannel() && from != history()->peer)) {
+ || (from->isChannel()
+ && from->emojiStatusId()
+ && from != history()->peer)) {
if (!_fromNameStatus) {
_fromNameStatus = std::make_unique();
const auto size = st::emojiSize;
@@ -3162,10 +3161,17 @@ bool Message::hasFromName() const {
return true;
case Context::History:
case Context::Pinned:
- case Context::Replies: {
+ case Context::Replies:
+ case Context::SavedSublist: {
const auto item = data();
const auto peer = item->history()->peer;
if (hasOutLayout() && !item->from()->isChannel()) {
+ if (peer->isSelf()) {
+ if (const auto forwarded = item->Get()) {
+ return forwarded->savedFromSender
+ && forwarded->savedFromSender->isChannel();
+ }
+ }
return false;
} else if (!peer->isUser()) {
if (const auto media = this->media()) {
@@ -3177,7 +3183,7 @@ bool Message::hasFromName() const {
if (forwarded->imported
&& peer.get() == forwarded->originalSender) {
return false;
- } else if (showForwardsFromSender(forwarded)) {
+ } else if (item->showForwardsFromSender(forwarded)) {
return true;
}
}
@@ -3201,8 +3207,9 @@ bool Message::displayForwardedFrom() const {
if (const auto forwarded = item->Get()) {
if (forwarded->story) {
return true;
- } else if (showForwardsFromSender(forwarded)) {
- return false;
+ } else if (item->showForwardsFromSender(forwarded)) {
+ return forwarded->savedFromSender
+ && (forwarded->savedFromSender != forwarded->originalSender);
}
if (const auto sender = item->discussionPostOriginalSender()) {
if (sender == forwarded->originalSender) {
@@ -3218,12 +3225,21 @@ bool Message::displayForwardedFrom() const {
bool Message::hasOutLayout() const {
const auto item = data();
if (item->history()->peer->isSelf()) {
- return !item->Has();
+ if (const auto forwarded = item->Get()) {
+ return (context() == Context::SavedSublist)
+ && (!forwarded->forwardOfForward()
+ ? (forwarded->originalSender
+ && forwarded->originalSender->isSelf())
+ : ((forwarded->savedFromSender
+ && forwarded->savedFromSender->isSelf())
+ || forwarded->savedFromOutgoing));
+ }
+ return true;
} else if (const auto forwarded = item->Get()) {
if (!forwarded->imported
|| !forwarded->originalSender
|| !forwarded->originalSender->isSelf()) {
- if (showForwardsFromSender(forwarded)) {
+ if (item->showForwardsFromSender(forwarded)) {
return false;
}
}
@@ -3323,6 +3339,7 @@ bool Message::displayFastReply() const {
bool Message::displayRightActionComments() const {
return !isPinnedContext()
+ && (context() != Context::SavedSublist)
&& data()->repliesAreComments()
&& media()
&& media()->isDisplayed()
@@ -3357,11 +3374,10 @@ bool Message::displayFastShare() const {
return !peer->isMegagroup();
} else if (const auto user = peer->asUser()) {
if (const auto forwarded = item->Get()) {
- return !showForwardsFromSender(forwarded)
- && !item->out()
+ return !item->out()
&& forwarded->originalSender
- && forwarded->originalSender->isChannel()
- && !forwarded->originalSender->isMegagroup();
+ && forwarded->originalSender->isBroadcast()
+ && !item->showForwardsFromSender(forwarded);
} else if (user->isBot() && !item->out()) {
if (const auto media = this->media()) {
return media->allowsFastShare();
@@ -3380,7 +3396,7 @@ bool Message::displayGoToOriginal() const {
return forwarded->savedFromPeer
&& forwarded->savedFromMsgId
&& (!item->externalReply() || !hasBubble())
- && !(context() == Context::Replies);
+ && (context() != Context::Replies);
}
return false;
}
@@ -3449,7 +3465,9 @@ void Message::drawRightAction(
} else {
const auto &icon = data()->isSponsored()
? st->historyFastCloseIcon()
- : (displayFastShare() && !isPinnedContext())
+ : (displayFastShare()
+ && !isPinnedContext()
+ && this->context() != Context::SavedSublist)
? st->historyFastShareIcon()
: st->historyGoToOriginalIcon();
icon.paintInCenter(p, { left, top, size->width(), size->height() });
@@ -3481,7 +3499,8 @@ ClickHandlerPtr Message::prepareRightActionLink() const {
return HideSponsoredClickHandler();
} else if (isPinnedContext()) {
return JumpToMessageClickHandler(data());
- } else if (displayRightActionComments()) {
+ } else if ((context() != Context::SavedSublist)
+ && displayRightActionComments()) {
return createGoToCommentsLink();
}
const auto sessionId = data()->history()->session().uniqueId();
@@ -3653,7 +3672,7 @@ void Message::fromNameUpdated(int width) const {
const auto nameText = [&]() -> const Ui::Text::String * {
if (from) {
return &_fromName;
- } else if (const auto info = item->hiddenSenderInfo()) {
+ } else if (const auto info= item->originalHiddenSenderInfo()) {
return &info->nameText();
} else {
Unexpected("Corrupted forwarded information in message.");
@@ -3737,7 +3756,7 @@ QRect Message::countGeometry() const {
const auto availableWidth = width()
- st::msgMargin.left()
- (centeredView ? st::msgMargin.left() : st::msgMargin.right());
- auto contentLeft = (outbg && !delegate()->elementIsChatWide())
+ auto contentLeft = hasRightLayout()
? st::msgMargin.right()
: st::msgMargin.left();
auto contentWidth = availableWidth;
@@ -3791,7 +3810,7 @@ Ui::BubbleRounding Message::countMessageRounding() const {
|| (keyboard != nullptr)
|| item->isFakeBotAbout()
|| (context() == Context::Replies && item->isDiscussionPost());
- const auto right = !delegate()->elementIsChatWide() && hasOutLayout();
+ const auto right = hasRightLayout();
using Corner = Ui::BubbleCornerRounding;
return Ui::BubbleRounding{
.topLeft = (smallTop && !right) ? Corner::Small : Corner::Large,
@@ -3991,7 +4010,7 @@ int Message::resizeContentGetHeight(int newWidth) {
: contentWidth;
newHeight += st::mediaInBubbleSkip
+ _reactions->resizeGetHeight(reactionsWidth);
- if (hasOutLayout() && !delegate()->elementIsChatWide()) {
+ if (hasRightLayout()) {
_reactions->flipToRight();
}
}
diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h
index 4b75f21fa..e849d5f63 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.h
+++ b/Telegram/SourceFiles/history/view/history_view_message.h
@@ -173,8 +173,6 @@ private:
void initPsa();
void fromNameUpdated(int width) const;
- [[nodiscard]] bool showForwardsFromSender(
- not_null forwarded) const;
[[nodiscard]] TextSelection skipTextSelection(
TextSelection selection) const;
[[nodiscard]] TextSelection unskipTextSelection(
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h
index 68f95177b..a4bfdabb6 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h
@@ -374,7 +374,6 @@ private:
};
-
class RepliesMemento final : public Window::SectionMemento {
public:
RepliesMemento(
diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp
index 47be99c52..794ad323e 100644
--- a/Telegram/SourceFiles/history/view/history_view_reply.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp
@@ -212,7 +212,7 @@ void Reply::update(
? _externalSender
: nullptr;
_hiddenSenderColorIndexPlusOne = (!_colorPeer && message)
- ? (message->hiddenSenderInfo()->colorIndex + 1)
+ ? (message->originalHiddenSenderInfo()->colorIndex + 1)
: 0;
const auto hasPreview = (story && story->hasReplyPreview())
@@ -380,8 +380,8 @@ QString Reply::senderName(
const auto forwarded
= data->resolvedMessage->Get();
if (forwarded) {
- Assert(forwarded->hiddenSenderInfo != nullptr);
- return forwarded->hiddenSenderInfo->name;
+ Assert(forwarded->originalHiddenSenderInfo != nullptr);
+ return forwarded->originalHiddenSenderInfo->name;
}
}
return QString();
diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
new file mode 100644
index 000000000..5664029bb
--- /dev/null
+++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
@@ -0,0 +1,657 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/view/history_view_sublist_section.h"
+
+#include "main/main_session.h"
+#include "data/data_saved_messages.h"
+#include "data/data_saved_sublist.h"
+#include "data/data_session.h"
+#include "data/data_peer_values.h"
+#include "data/data_user.h"
+#include "history/view/history_view_top_bar_widget.h"
+#include "history/view/history_view_translate_bar.h"
+#include "history/view/history_view_list_widget.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "lang/lang_keys.h"
+#include "ui/chat/chat_style.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/scroll_area.h"
+#include "ui/widgets/shadow.h"
+#include "window/window_session_controller.h"
+#include "styles/style_chat.h"
+#include "styles/style_chat_helpers.h"
+#include "styles/style_window.h"
+
+namespace HistoryView {
+namespace {
+
+} // namespace
+
+SublistMemento::SublistMemento(not_null sublist)
+: _sublist(sublist) {
+ const auto selfId = sublist->session().userPeerId();
+ _list.setAroundPosition({
+ .fullId = FullMsgId(selfId, ShowAtUnreadMsgId),
+ .date = TimeId(0),
+ });
+}
+
+object_ptr SublistMemento::createWidget(
+ QWidget *parent,
+ not_null controller,
+ Window::Column column,
+ const QRect &geometry) {
+ if (column == Window::Column::Third) {
+ return nullptr;
+ }
+ auto result = object_ptr(
+ parent,
+ controller,
+ _sublist);
+ result->setInternalState(geometry, this);
+ return result;
+}
+
+SublistWidget::SublistWidget(
+ QWidget *parent,
+ not_null controller,
+ not_null sublist)
+: Window::SectionWidget(parent, controller, sublist->peer())
+, _sublist(sublist)
+, _history(sublist->owner().history(sublist->session().user()))
+, _topBar(this, controller)
+, _topBarShadow(this)
+, _translateBar(std::make_unique(this, controller, _history))
+, _scroll(std::make_unique(
+ this,
+ controller->chatStyle()->value(lifetime(), st::historyScroll),
+ false))
+, _cornerButtons(
+ _scroll.get(),
+ controller->chatStyle(),
+ static_cast(this)) {
+ controller->chatStyle()->paletteChanged(
+ ) | rpl::start_with_next([=] {
+ _scroll->updateBars();
+ }, _scroll->lifetime());
+
+ setupOpenChatButton();
+ setupAboutHiddenAuthor();
+
+ Window::ChatThemeValueFromPeer(
+ controller,
+ sublist->peer()
+ ) | rpl::start_with_next([=](std::shared_ptr &&theme) {
+ _theme = std::move(theme);
+ controller->setChatStyleTheme(_theme);
+ }, lifetime());
+
+ _topBar->setActiveChat(
+ TopBarWidget::ActiveChat{
+ .key = sublist,
+ .section = Dialogs::EntryState::Section::SavedSublist,
+ },
+ nullptr);
+
+ _topBar->move(0, 0);
+ _topBar->resizeToWidth(width());
+ _topBar->show();
+
+ _topBar->deleteSelectionRequest(
+ ) | rpl::start_with_next([=] {
+ confirmDeleteSelected();
+ }, _topBar->lifetime());
+ _topBar->forwardSelectionRequest(
+ ) | rpl::start_with_next([=] {
+ confirmForwardSelected();
+ }, _topBar->lifetime());
+ _topBar->clearSelectionRequest(
+ ) | rpl::start_with_next([=] {
+ clearSelected();
+ }, _topBar->lifetime());
+
+ _translateBar->raise();
+ _topBarShadow->raise();
+ controller->adaptive().value(
+ ) | rpl::start_with_next([=] {
+ updateAdaptiveLayout();
+ }, lifetime());
+
+ _inner = _scroll->setOwnedWidget(object_ptr(
+ this,
+ controller,
+ static_cast(this)));
+ _scroll->move(0, _topBar->height());
+ _scroll->show();
+ _scroll->scrolls(
+ ) | rpl::start_with_next([=] {
+ onScroll();
+ }, lifetime());
+
+ setupTranslateBar();
+}
+
+SublistWidget::~SublistWidget() = default;
+
+void SublistWidget::setupOpenChatButton() {
+ if (_sublist->peer()->isSavedHiddenAuthor()) {
+ return;
+ }
+ _openChatButton = std::make_unique(
+ this,
+ (_sublist->peer()->isBroadcast()
+ ? tr::lng_saved_open_channel(tr::now)
+ : _sublist->peer()->isUser()
+ ? tr::lng_saved_open_chat(tr::now)
+ : tr::lng_saved_open_group(tr::now)),
+ st::historyComposeButton);
+
+ _openChatButton->setClickedCallback([=] {
+ controller()->showPeerHistory(
+ _sublist->peer(),
+ Window::SectionShow::Way::Forward);
+ });
+}
+
+void SublistWidget::setupAboutHiddenAuthor() {
+ if (!_sublist->peer()->isSavedHiddenAuthor()) {
+ return;
+ }
+ _aboutHiddenAuthor = std::make_unique(this);
+ _aboutHiddenAuthor->paintRequest() | rpl::start_with_next([=] {
+ auto p = QPainter(_aboutHiddenAuthor.get());
+ auto rect = _aboutHiddenAuthor->rect();
+
+ p.fillRect(rect, st::historyReplyBg);
+
+ p.setFont(st::normalFont);
+ p.setPen(st::windowSubTextFg);
+ p.drawText(
+ rect.marginsRemoved(
+ QMargins(st::historySendPadding, 0, st::historySendPadding, 0)),
+ tr::lng_saved_about_hidden(tr::now),
+ style::al_center);
+ }, _aboutHiddenAuthor->lifetime());
+}
+
+void SublistWidget::setupTranslateBar() {
+ controller()->adaptive().oneColumnValue(
+ ) | rpl::start_with_next([=, raw = _translateBar.get()](bool one) {
+ raw->setShadowGeometryPostprocess([=](QRect geometry) {
+ if (!one) {
+ geometry.setLeft(geometry.left() + st::lineWidth);
+ }
+ return geometry;
+ });
+ }, _translateBar->lifetime());
+
+ _translateBarHeight = 0;
+ _translateBar->heightValue(
+ ) | rpl::start_with_next([=](int height) {
+ if (const auto delta = height - _translateBarHeight) {
+ _translateBarHeight = height;
+ setGeometryWithTopMoved(geometry(), delta);
+ }
+ }, _translateBar->lifetime());
+
+ _translateBar->finishAnimating();
+}
+
+void SublistWidget::cornerButtonsShowAtPosition(
+ Data::MessagePosition position) {
+ showAtPosition(position);
+}
+
+Data::Thread *SublistWidget::cornerButtonsThread() {
+ return nullptr;
+}
+
+FullMsgId SublistWidget::cornerButtonsCurrentId() {
+ return {};
+}
+
+bool SublistWidget::cornerButtonsIgnoreVisibility() {
+ return animatingShow();
+}
+
+std::optional SublistWidget::cornerButtonsDownShown() {
+ const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
+ if (top < _scroll->scrollTopMax() || _cornerButtons.replyReturn()) {
+ return true;
+ } else if (_inner->loadedAtBottomKnown()) {
+ return !_inner->loadedAtBottom();
+ }
+ return std::nullopt;
+}
+
+bool SublistWidget::cornerButtonsUnreadMayBeShown() {
+ return _inner->loadedAtBottomKnown();
+}
+
+bool SublistWidget::cornerButtonsHas(CornerButtonType type) {
+ return (type == CornerButtonType::Down);
+}
+
+void SublistWidget::showAtPosition(
+ Data::MessagePosition position,
+ FullMsgId originId) {
+ _inner->showAtPosition(
+ position,
+ {},
+ _cornerButtons.doneJumpFrom(position.fullId, originId));
+}
+
+void SublistWidget::updateAdaptiveLayout() {
+ _topBarShadow->moveToLeft(
+ controller()->adaptive().isOneColumn() ? 0 : st::lineWidth,
+ _topBar->height());
+}
+
+not_null SublistWidget::sublist() const {
+ return _sublist;
+}
+
+Dialogs::RowDescriptor SublistWidget::activeChat() const {
+ return {
+ _history,
+ FullMsgId(_history->peer->id, ShowAtUnreadMsgId)
+ };
+}
+
+QPixmap SublistWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) {
+ _topBar->updateControlsVisibility();
+ if (params.withTopBarShadow) _topBarShadow->hide();
+ auto result = Ui::GrabWidget(this);
+ if (params.withTopBarShadow) _topBarShadow->show();
+ _translateBar->hide();
+ return result;
+}
+
+void SublistWidget::checkActivation() {
+ _inner->checkActivation();
+}
+
+void SublistWidget::doSetInnerFocus() {
+ _inner->setFocus();
+}
+
+bool SublistWidget::showInternal(
+ not_null memento,
+ const Window::SectionShow ¶ms) {
+ if (auto logMemento = dynamic_cast(memento.get())) {
+ if (logMemento->getSublist() == sublist()) {
+ restoreState(logMemento);
+ return true;
+ }
+ }
+ return false;
+}
+
+void SublistWidget::setInternalState(
+ const QRect &geometry,
+ not_null memento) {
+ setGeometry(geometry);
+ Ui::SendPendingMoveResizeEvents(this);
+ restoreState(memento);
+}
+
+std::shared_ptr SublistWidget::createMemento() {
+ auto result = std::make_shared(sublist());
+ saveState(result.get());
+ return result;
+}
+
+bool SublistWidget::showMessage(
+ PeerId peerId,
+ const Window::SectionShow ¶ms,
+ MsgId messageId) {
+ return false; // We want 'Go to original' to work.
+}
+
+void SublistWidget::saveState(not_null memento) {
+ _inner->saveState(memento->list());
+}
+
+void SublistWidget::restoreState(not_null memento) {
+ _inner->restoreState(memento->list());
+}
+
+void SublistWidget::resizeEvent(QResizeEvent *e) {
+ if (!width() || !height()) {
+ return;
+ }
+ recountChatWidth();
+ updateControlsGeometry();
+}
+
+void SublistWidget::recountChatWidth() {
+ auto layout = (width() < st::adaptiveChatWideWidth)
+ ? Window::Adaptive::ChatLayout::Normal
+ : Window::Adaptive::ChatLayout::Wide;
+ controller()->adaptive().setChatLayout(layout);
+}
+
+void SublistWidget::updateControlsGeometry() {
+ const auto contentWidth = width();
+
+ const auto newScrollTop = _scroll->isHidden()
+ ? std::nullopt
+ : base::make_optional(_scroll->scrollTop() + topDelta());
+ _topBar->resizeToWidth(contentWidth);
+ _topBarShadow->resize(contentWidth, st::lineWidth);
+
+ auto bottom = height();
+ if (_openChatButton) {
+ _openChatButton->resizeToWidth(width());
+ bottom -= _openChatButton->height();
+ _openChatButton->move(0, bottom);
+ }
+ if (_aboutHiddenAuthor) {
+ _aboutHiddenAuthor->resize(width(), st::historyUnblock.height);
+ bottom -= _aboutHiddenAuthor->height();
+ _aboutHiddenAuthor->move(0, bottom);
+ }
+ const auto controlsHeight = 0;
+ auto top = _topBar->height();
+ _translateBar->move(0, top);
+ _translateBar->resizeToWidth(contentWidth);
+ top += _translateBarHeight;
+ const auto scrollHeight = bottom - top - controlsHeight;
+ const auto scrollSize = QSize(contentWidth, scrollHeight);
+ if (_scroll->size() != scrollSize) {
+ _skipScrollEvent = true;
+ _scroll->resize(scrollSize);
+ _inner->resizeToWidth(scrollSize.width(), _scroll->height());
+ _skipScrollEvent = false;
+ }
+ _scroll->move(0, top);
+ if (!_scroll->isHidden()) {
+ if (newScrollTop) {
+ _scroll->scrollToY(*newScrollTop);
+ }
+ updateInnerVisibleArea();
+ }
+
+ _cornerButtons.updatePositions();
+}
+
+void SublistWidget::paintEvent(QPaintEvent *e) {
+ if (animatingShow()) {
+ SectionWidget::paintEvent(e);
+ return;
+ } else if (controller()->contentOverlapped(this, e)) {
+ return;
+ }
+
+ const auto aboveHeight = _topBar->height();
+ const auto bg = e->rect().intersected(
+ QRect(0, aboveHeight, width(), height() - aboveHeight));
+ SectionWidget::PaintBackground(controller(), _theme.get(), this, bg);
+}
+
+void SublistWidget::onScroll() {
+ if (_skipScrollEvent) {
+ return;
+ }
+ updateInnerVisibleArea();
+}
+
+void SublistWidget::updateInnerVisibleArea() {
+ const auto scrollTop = _scroll->scrollTop();
+ _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
+ _cornerButtons.updateJumpDownVisibility();
+ _cornerButtons.updateUnreadThingsVisibility();
+}
+
+void SublistWidget::showAnimatedHook(
+ const Window::SectionSlideParams ¶ms) {
+ _topBar->setAnimatingMode(true);
+ if (params.withTopBarShadow) {
+ _topBarShadow->show();
+ }
+}
+
+void SublistWidget::showFinishedHook() {
+ _topBar->setAnimatingMode(false);
+ _inner->showFinished();
+ _translateBar->show();
+}
+
+bool SublistWidget::floatPlayerHandleWheelEvent(QEvent *e) {
+ return _scroll->viewportEvent(e);
+}
+
+QRect SublistWidget::floatPlayerAvailableRect() {
+ return mapToGlobal(_scroll->geometry());
+}
+
+Context SublistWidget::listContext() {
+ return Context::SavedSublist;
+}
+
+bool SublistWidget::listScrollTo(int top, bool syntetic) {
+ top = std::clamp(top, 0, _scroll->scrollTopMax());
+ if (_scroll->scrollTop() == top) {
+ updateInnerVisibleArea();
+ return false;
+ }
+ _scroll->scrollToY(top);
+ return true;
+}
+
+void SublistWidget::listCancelRequest() {
+ if (_inner && !_inner->getSelectedIds().empty()) {
+ clearSelected();
+ return;
+ }
+ controller()->showBackFromStack();
+}
+
+void SublistWidget::listDeleteRequest() {
+ confirmDeleteSelected();
+}
+
+void SublistWidget::listTryProcessKeyInput(not_null