Merge tag 'v4.14.1' into dev
# Conflicts: # Telegram/Resources/winrc/Telegram.rc # Telegram/Resources/winrc/Updater.rc # Telegram/SourceFiles/core/version.h # Telegram/SourceFiles/data/data_stories.cpp # Telegram/SourceFiles/history/history_item.cpp # Telegram/SourceFiles/main/main_session.cpp # Telegram/lib_ui
3
.github/workflows/linux.yml
vendored
|
@ -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.
|
||||
|
|
1
.github/workflows/mac.yml
vendored
|
@ -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
|
||||
|
||||
|
|
5
.github/workflows/win.yml
vendored
|
@ -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')
|
||||
|
|
2
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
|
||||
|
|
|
@ -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
|
||||
|
|
BIN
Telegram/Resources/animations/voice_ttl_idle.tgs
Normal file
BIN
Telegram/Resources/animations/voice_ttl_start.tgs
Normal file
BIN
Telegram/Resources/icons/dialogs/avatar_hidden.png
Normal file
After Width: | Height: | Size: 919 B |
BIN
Telegram/Resources/icons/dialogs/avatar_hidden@2x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/dialogs/avatar_hidden@3x.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Telegram/Resources/icons/dialogs/avatar_notes.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
Telegram/Resources/icons/dialogs/avatar_notes@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/dialogs/avatar_notes@3x.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/info/info_media_saved.png
Normal file
After Width: | Height: | Size: 380 B |
BIN
Telegram/Resources/icons/info/info_media_saved@2x.png
Normal file
After Width: | Height: | Size: 664 B |
BIN
Telegram/Resources/icons/info/info_media_saved@3x.png
Normal file
After Width: | Height: | Size: 954 B |
|
@ -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...";
|
||||
|
||||
|
|
|
@ -11,5 +11,7 @@
|
|||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
<file alias="stats.tgs">../../animations/stats.tgs</file>
|
||||
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
|
||||
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.13.1.0" />
|
||||
Version="4.14.1.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -6,6 +6,7 @@ For license and copyright information please follow this link:
|
|||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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<Data::Forum*> forum) {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::savePinnedOrder(not_null<Data::SavedMessages*> saved) {
|
||||
const auto &order = _session->data().pinnedChatsOrder(saved);
|
||||
const auto input = [](Dialogs::Key key) {
|
||||
if (const auto sublist = key.sublist()) {
|
||||
return MTP_inputDialogPeer(sublist->peer()->input);
|
||||
}
|
||||
Unexpected("Key type in pinnedDialogsOrder().");
|
||||
};
|
||||
auto peers = QVector<MTPInputDialogPeer>();
|
||||
peers.reserve(order.size());
|
||||
ranges::transform(
|
||||
order,
|
||||
ranges::back_inserter(peers),
|
||||
input);
|
||||
request(MTPmessages_ReorderPinnedSavedDialogs(
|
||||
MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force),
|
||||
MTP_vector(peers)
|
||||
)).send();
|
||||
}
|
||||
|
||||
void ApiWrap::toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
|
|
|
@ -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<Data::Forum*> forum);
|
||||
void savePinnedOrder(not_null<Data::SavedMessages*> saved);
|
||||
void toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput(
|
|||
rpl::producer<QString> 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,
|
||||
|
|
|
@ -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<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::vector<not_null<UserData*>> users,
|
||||
not_null<Api::PremiumGiftCodeOptions*> api) {
|
||||
not_null<Api::PremiumGiftCodeOptions*> 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<std::vector<not_null<UserData*>>>();
|
||||
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();
|
||||
|
|
|
@ -34,7 +34,7 @@ public:
|
|||
GiftPremiumValidator(not_null<Window::SessionController*> controller);
|
||||
|
||||
void showBox(not_null<UserData*> user);
|
||||
void showChoosePeerBox();
|
||||
void showChoosePeerBox(const QString &ref);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
|
|
|
@ -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<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session) {
|
||||
const auto limits = Data::PremiumLimits(session);
|
||||
SimplePinsLimitBox(
|
||||
box,
|
||||
session,
|
||||
"saved_dialog_pinned",
|
||||
limits.savedSublistsPinnedDefault(),
|
||||
limits.savedSublistsPinnedPremium(),
|
||||
PinsCount(session->data().savedMessages().chatsList()));
|
||||
}
|
||||
|
||||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
|
|
|
@ -60,6 +60,9 @@ void PinsLimitBox(
|
|||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::Forum*> forum);
|
||||
void SublistsPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session);
|
||||
void CaptionLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
|
|
|
@ -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.;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<LocalUrlHandler> &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))) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -19,8 +19,6 @@ class QLockFile;
|
|||
|
||||
namespace Core {
|
||||
|
||||
extern const char kOptionForceWaylandFractionalScaling[];
|
||||
|
||||
class UpdateChecker;
|
||||
class Application;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -889,7 +889,6 @@ not_null<HistoryItem*> 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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
|
@ -82,8 +82,6 @@ public:
|
|||
private:
|
||||
void indexNameParts();
|
||||
|
||||
int chatListNameVersion() const override;
|
||||
|
||||
void reorderLastHistories();
|
||||
|
||||
void paintUserpic(
|
||||
|
|
|
@ -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<Session*> owner)
|
||||
: _owner(owner)
|
||||
|
|
|
@ -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<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
|
@ -187,8 +188,6 @@ private:
|
|||
void allowChatListMessageResolve();
|
||||
void resolveChatListMessageGroup();
|
||||
|
||||
int chatListNameVersion() const override;
|
||||
|
||||
void subscribeToUnreadChanges();
|
||||
[[nodiscard]] Dialogs::UnreadState unreadStateFor(
|
||||
int count,
|
||||
|
|
|
@ -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<HistoryItem*> parent,
|
||||
not_null<DocumentData*> 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<Media> MediaFile::clone(not_null<HistoryItem*> 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;
|
||||
|
|
|
@ -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<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool spoiler);
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds);
|
||||
~MediaFile();
|
||||
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> 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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -226,7 +226,6 @@ bool PhotoMedia::setToClipboard() {
|
|||
if (fallback.isNull()) {
|
||||
return false;
|
||||
}
|
||||
const auto bytes = imageBytes(large);
|
||||
auto mime = std::make_unique<QMimeData>();
|
||||
mime->setImageData(std::move(fallback));
|
||||
if (auto bytes = imageBytes(large); !bytes.isEmpty()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
296
Telegram/SourceFiles/data/data_saved_messages.cpp
Normal file
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_saved_messages.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPerPage = 50;
|
||||
constexpr auto kFirstPerPage = 10;
|
||||
|
||||
} // namespace
|
||||
|
||||
SavedMessages::SavedMessages(not_null<Session*> owner)
|
||||
: _owner(owner)
|
||||
, _chatsList(
|
||||
&owner->session(),
|
||||
FilterId(),
|
||||
owner->maxPinnedChatsLimitValue(this))
|
||||
, _loadMore([=] { sendLoadMoreRequests(); }) {
|
||||
}
|
||||
|
||||
SavedMessages::~SavedMessages() = default;
|
||||
|
||||
bool SavedMessages::supported() const {
|
||||
return !_unsupported;
|
||||
}
|
||||
|
||||
Session &SavedMessages::owner() const {
|
||||
return *_owner;
|
||||
}
|
||||
|
||||
Main::Session &SavedMessages::session() const {
|
||||
return _owner->session();
|
||||
}
|
||||
|
||||
not_null<Dialogs::MainList*> SavedMessages::chatsList() {
|
||||
return &_chatsList;
|
||||
}
|
||||
|
||||
not_null<SavedSublist*> SavedMessages::sublist(not_null<PeerData*> peer) {
|
||||
const auto i = _sublists.find(peer);
|
||||
if (i != end(_sublists)) {
|
||||
return i->second.get();
|
||||
}
|
||||
return _sublists.emplace(
|
||||
peer,
|
||||
std::make_unique<SavedSublist>(peer)).first->second.get();
|
||||
}
|
||||
|
||||
void SavedMessages::loadMore() {
|
||||
_loadMoreScheduled = true;
|
||||
_loadMore.call();
|
||||
}
|
||||
|
||||
void SavedMessages::loadMore(not_null<SavedSublist*> sublist) {
|
||||
_loadMoreSublistsScheduled.emplace(sublist);
|
||||
_loadMore.call();
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMore() {
|
||||
if (_loadMoreRequestId || _chatsList.loaded()) {
|
||||
return;
|
||||
} else if (!_pinnedLoaded) {
|
||||
loadPinned();
|
||||
}
|
||||
_loadMoreRequestId = _owner->session().api().request(
|
||||
MTPmessages_GetSavedDialogs(
|
||||
MTP_flags(MTPmessages_GetSavedDialogs::Flag::f_exclude_pinned),
|
||||
MTP_int(_offsetDate),
|
||||
MTP_int(_offsetId),
|
||||
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
|
||||
MTP_int(kPerPage),
|
||||
MTP_long(0)) // hash
|
||||
).done([=](const MTPmessages_SavedDialogs &result) {
|
||||
apply(result, false);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
}
|
||||
_chatsList.setLoaded();
|
||||
_loadMoreRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SavedMessages::loadPinned() {
|
||||
if (_pinnedRequestId) {
|
||||
return;
|
||||
}
|
||||
_pinnedRequestId = _owner->session().api().request(
|
||||
MTPmessages_GetPinnedSavedDialogs()
|
||||
).done([=](const MTPmessages_SavedDialogs &result) {
|
||||
apply(result, true);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
} else {
|
||||
_pinnedLoaded = true;
|
||||
}
|
||||
_pinnedRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMore(not_null<SavedSublist*> sublist) {
|
||||
if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) {
|
||||
return;
|
||||
}
|
||||
const auto &list = sublist->messages();
|
||||
const auto offsetId = list.empty() ? MsgId(0) : list.back()->id;
|
||||
const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date();
|
||||
const auto limit = offsetId ? kPerPage : kFirstPerPage;
|
||||
const auto requestId = _owner->session().api().request(
|
||||
MTPmessages_GetSavedHistory(
|
||||
sublist->peer()->input,
|
||||
MTP_int(offsetId),
|
||||
MTP_int(offsetDate),
|
||||
MTP_int(0), // add_offset
|
||||
MTP_int(limit),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_long(0)) // hash
|
||||
).done([=](const MTPmessages_Messages &result) {
|
||||
auto count = 0;
|
||||
auto list = (const QVector<MTPMessage>*)nullptr;
|
||||
result.match([](const MTPDmessages_channelMessages &) {
|
||||
LOG(("API Error: messages.channelMessages in sublist."));
|
||||
}, [](const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: messages.messagesNotModified in sublist."));
|
||||
}, [&](const auto &data) {
|
||||
owner().processUsers(data.vusers());
|
||||
owner().processChats(data.vchats());
|
||||
list = &data.vmessages().v;
|
||||
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
|
||||
count = int(list->size());
|
||||
} else {
|
||||
count = data.vcount().v;
|
||||
}
|
||||
});
|
||||
|
||||
_loadMoreRequests.remove(sublist);
|
||||
if (!list) {
|
||||
sublist->setFullLoaded();
|
||||
return;
|
||||
}
|
||||
auto items = std::vector<not_null<HistoryItem*>>();
|
||||
items.reserve(list->size());
|
||||
for (const auto &message : *list) {
|
||||
const auto item = owner().addNewMessage(
|
||||
message,
|
||||
{},
|
||||
NewMessageType::Existing);
|
||||
if (item) {
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
sublist->append(std::move(items), count);
|
||||
if (result.type() == mtpc_messages_messages) {
|
||||
sublist->setFullLoaded();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
}
|
||||
sublist->setFullLoaded();
|
||||
_loadMoreRequests.remove(sublist);
|
||||
}).send();
|
||||
_loadMoreRequests[sublist] = requestId;
|
||||
}
|
||||
|
||||
void SavedMessages::apply(
|
||||
const MTPmessages_SavedDialogs &result,
|
||||
bool pinned) {
|
||||
auto list = (const QVector<MTPSavedDialog>*)nullptr;
|
||||
result.match([](const MTPDmessages_savedDialogsNotModified &) {
|
||||
LOG(("API Error: messages.savedDialogsNotModified."));
|
||||
}, [&](const auto &data) {
|
||||
_owner->processUsers(data.vusers());
|
||||
_owner->processChats(data.vchats());
|
||||
_owner->processMessages(
|
||||
data.vmessages(),
|
||||
NewMessageType::Existing);
|
||||
list = &data.vdialogs().v;
|
||||
});
|
||||
if (pinned) {
|
||||
_pinnedRequestId = 0;
|
||||
_pinnedLoaded = true;
|
||||
} else {
|
||||
_loadMoreRequestId = 0;
|
||||
}
|
||||
if (!list) {
|
||||
if (!pinned) {
|
||||
_chatsList.setLoaded();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto lastValid = false;
|
||||
auto offsetDate = TimeId();
|
||||
auto offsetId = MsgId();
|
||||
auto offsetPeer = (PeerData*)nullptr;
|
||||
const auto selfId = _owner->session().userPeerId();
|
||||
for (const auto &dialog : *list) {
|
||||
const auto &data = dialog.data();
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto topId = MsgId(data.vtop_message().v);
|
||||
if (const auto item = _owner->message(selfId, topId)) {
|
||||
offsetPeer = peer;
|
||||
offsetDate = item->date();
|
||||
offsetId = topId;
|
||||
lastValid = true;
|
||||
const auto entry = sublist(peer);
|
||||
const auto entryPinned = pinned || data.is_pinned();
|
||||
entry->applyMaybeLast(item);
|
||||
_owner->setPinnedFromEntryList(entry, entryPinned);
|
||||
} else {
|
||||
lastValid = false;
|
||||
}
|
||||
}
|
||||
if (pinned) {
|
||||
} else if (!lastValid) {
|
||||
LOG(("API Error: Unknown message in the end of a slice."));
|
||||
_chatsList.setLoaded();
|
||||
} else if (result.type() == mtpc_messages_savedDialogs) {
|
||||
_chatsList.setLoaded();
|
||||
} else if (offsetDate < _offsetDate
|
||||
|| (offsetDate == _offsetDate && offsetId == _offsetId && offsetPeer == _offsetPeer)) {
|
||||
LOG(("API Error: Bad order in messages.savedDialogs."));
|
||||
_chatsList.setLoaded();
|
||||
} else {
|
||||
_offsetDate = offsetDate;
|
||||
_offsetId = offsetId;
|
||||
_offsetPeer = offsetPeer;
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMoreRequests() {
|
||||
if (_loadMoreScheduled) {
|
||||
sendLoadMore();
|
||||
}
|
||||
for (const auto sublist : base::take(_loadMoreSublistsScheduled)) {
|
||||
sendLoadMore(sublist);
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) {
|
||||
const auto list = update.vorder();
|
||||
if (!list) {
|
||||
loadPinned();
|
||||
return;
|
||||
}
|
||||
const auto &order = list->v;
|
||||
const auto notLoaded = [&](const MTPDialogPeer &peer) {
|
||||
return peer.match([&](const MTPDdialogPeer &data) {
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
return !_sublists.contains(peer);
|
||||
}, [&](const MTPDdialogPeerFolder &data) {
|
||||
LOG(("API Error: "
|
||||
"updatePinnedSavedDialogs has folders."));
|
||||
return false;
|
||||
});
|
||||
};
|
||||
if (!ranges::none_of(order, notLoaded)) {
|
||||
loadPinned();
|
||||
} else {
|
||||
_chatsList.pinned()->applyList(_owner, order);
|
||||
_owner->notifyPinnedDialogsOrderUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
|
||||
update.vpeer().match([&](const MTPDdialogPeer &data) {
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto i = _sublists.find(peer);
|
||||
if (i != end(_sublists)) {
|
||||
const auto entry = i->second.get();
|
||||
_owner->setChatPinned(entry, FilterId(), update.is_pinned());
|
||||
} else {
|
||||
loadPinned();
|
||||
}
|
||||
}, [&](const MTPDdialogPeerFolder &data) {
|
||||
DEBUG_LOG(("API Error: Folder in updateSavedDialogPinned."));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
72
Telegram/SourceFiles/data/data_saved_messages.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
class SavedSublist;
|
||||
|
||||
class SavedMessages final {
|
||||
public:
|
||||
explicit SavedMessages(not_null<Session*> owner);
|
||||
~SavedMessages();
|
||||
|
||||
[[nodiscard]] bool supported() const;
|
||||
|
||||
[[nodiscard]] Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
[[nodiscard]] not_null<Dialogs::MainList*> chatsList();
|
||||
[[nodiscard]] not_null<SavedSublist*> sublist(not_null<PeerData*> peer);
|
||||
|
||||
void loadMore();
|
||||
void loadMore(not_null<SavedSublist*> sublist);
|
||||
|
||||
void apply(const MTPDupdatePinnedSavedDialogs &update);
|
||||
void apply(const MTPDupdateSavedDialogPinned &update);
|
||||
|
||||
private:
|
||||
void loadPinned();
|
||||
void apply(const MTPmessages_SavedDialogs &result, bool pinned);
|
||||
|
||||
void sendLoadMore();
|
||||
void sendLoadMore(not_null<SavedSublist*> sublist);
|
||||
void sendLoadMoreRequests();
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
Dialogs::MainList _chatsList;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
std::unique_ptr<SavedSublist>> _sublists;
|
||||
|
||||
base::flat_map<not_null<SavedSublist*>, mtpRequestId> _loadMoreRequests;
|
||||
mtpRequestId _loadMoreRequestId = 0;
|
||||
mtpRequestId _pinnedRequestId = 0;
|
||||
|
||||
TimeId _offsetDate = 0;
|
||||
MsgId _offsetId = 0;
|
||||
PeerData *_offsetPeer = nullptr;
|
||||
|
||||
SingleQueuedInvokation _loadMore;
|
||||
base::flat_set<not_null<SavedSublist*>> _loadMoreSublistsScheduled;
|
||||
bool _loadMoreScheduled = false;
|
||||
|
||||
bool _pinnedLoaded = false;
|
||||
bool _unsupported = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
256
Telegram/SourceFiles/data/data_saved_sublist.cpp
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_saved_sublist.h"
|
||||
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
SavedSublist::SavedSublist(not_null<PeerData*> peer)
|
||||
: Entry(&peer->owner(), Dialogs::Entry::Type::SavedSublist)
|
||||
, _history(peer->owner().history(peer)) {
|
||||
}
|
||||
|
||||
SavedSublist::~SavedSublist() = default;
|
||||
|
||||
not_null<History*> SavedSublist::history() const {
|
||||
return _history;
|
||||
}
|
||||
|
||||
not_null<PeerData*> SavedSublist::peer() const {
|
||||
return _history->peer;
|
||||
}
|
||||
|
||||
bool SavedSublist::isHiddenAuthor() const {
|
||||
return peer()->isSavedHiddenAuthor();
|
||||
}
|
||||
|
||||
bool SavedSublist::isFullLoaded() const {
|
||||
return (_flags & Flag::FullLoaded) != 0;
|
||||
}
|
||||
|
||||
auto SavedSublist::messages() const
|
||||
-> const std::vector<not_null<HistoryItem*>> & {
|
||||
return _items;
|
||||
}
|
||||
|
||||
void SavedSublist::applyMaybeLast(not_null<HistoryItem*> item, bool added) {
|
||||
const auto before = [](
|
||||
not_null<HistoryItem*> a,
|
||||
not_null<HistoryItem*> b) {
|
||||
return IsServerMsgId(a->id)
|
||||
? (IsServerMsgId(b->id) ? (a->id < b->id) : true)
|
||||
: (IsServerMsgId(b->id) ? false : (a->id < b->id));
|
||||
};
|
||||
|
||||
if (_items.empty()) {
|
||||
_items.push_back(item);
|
||||
} else if (_items.front() == item) {
|
||||
return;
|
||||
} else if (!isFullLoaded()
|
||||
&& _items.size() == 1
|
||||
&& before(_items.front(), item)) {
|
||||
_items[0] = item;
|
||||
} else if (before(_items.back(), item)) {
|
||||
for (auto i = begin(_items); i != end(_items); ++i) {
|
||||
if (item == *i) {
|
||||
return;
|
||||
} else if (before(*i, item)) {
|
||||
_items.insert(i, item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (added && _fullCount) {
|
||||
++*_fullCount;
|
||||
}
|
||||
if (_items.front() == item) {
|
||||
setChatListTimeId(item->date());
|
||||
resolveChatListMessageGroup();
|
||||
}
|
||||
_changed.fire({});
|
||||
}
|
||||
|
||||
void SavedSublist::removeOne(not_null<HistoryItem*> item) {
|
||||
if (_items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto last = (_items.front() == item);
|
||||
const auto from = ranges::remove(_items, item);
|
||||
const auto removed = end(_items) - from;
|
||||
if (removed) {
|
||||
_items.erase(from, end(_items));
|
||||
}
|
||||
if (_fullCount) {
|
||||
--*_fullCount;
|
||||
}
|
||||
if (last) {
|
||||
if (_items.empty()) {
|
||||
if (isFullLoaded()) {
|
||||
updateChatListExistence();
|
||||
} else {
|
||||
updateChatListEntry();
|
||||
crl::on_main(this, [=] {
|
||||
owner().savedMessages().loadMore(this);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setChatListTimeId(_items.front()->date());
|
||||
}
|
||||
}
|
||||
if (removed || _fullCount) {
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SavedSublist::changes() const {
|
||||
return _changed.events();
|
||||
}
|
||||
|
||||
std::optional<int> SavedSublist::fullCount() const {
|
||||
return isFullLoaded() ? int(_items.size()) : _fullCount;
|
||||
}
|
||||
|
||||
rpl::producer<int> SavedSublist::fullCountValue() const {
|
||||
return _changed.events_starting_with({}) | rpl::map([=] {
|
||||
return fullCount();
|
||||
}) | rpl::filter_optional();
|
||||
}
|
||||
|
||||
void SavedSublist::append(
|
||||
std::vector<not_null<HistoryItem*>> &&items,
|
||||
int fullCount) {
|
||||
_fullCount = fullCount;
|
||||
if (items.empty()) {
|
||||
setFullLoaded();
|
||||
} else if (_items.empty()) {
|
||||
_items = std::move(items);
|
||||
setChatListTimeId(_items.front()->date());
|
||||
_changed.fire({});
|
||||
} else if (_items.back()->id > items.front()->id) {
|
||||
_items.insert(end(_items), begin(items), end(items));
|
||||
_changed.fire({});
|
||||
} else {
|
||||
_items.insert(end(_items), begin(items), end(items));
|
||||
ranges::stable_sort(
|
||||
_items,
|
||||
ranges::greater(),
|
||||
&HistoryItem::id);
|
||||
ranges::unique(_items, ranges::greater(), &HistoryItem::id);
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
void SavedSublist::setFullLoaded(bool loaded) {
|
||||
if (loaded != isFullLoaded()) {
|
||||
if (loaded) {
|
||||
_flags |= Flag::FullLoaded;
|
||||
if (_items.empty()) {
|
||||
updateChatListExistence();
|
||||
}
|
||||
} else {
|
||||
_flags &= ~Flag::FullLoaded;
|
||||
}
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
int SavedSublist::fixedOnTopIndex() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SavedSublist::shouldBeInChatList() const {
|
||||
return isPinnedDialog(FilterId()) || !_items.empty();
|
||||
}
|
||||
|
||||
Dialogs::UnreadState SavedSublist::chatListUnreadState() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Dialogs::BadgesState SavedSublist::chatListBadgesState() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
HistoryItem *SavedSublist::chatListMessage() const {
|
||||
return _items.empty() ? nullptr : _items.front().get();
|
||||
}
|
||||
|
||||
bool SavedSublist::chatListMessageKnown() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString &SavedSublist::chatListName() const {
|
||||
return _history->chatListName();
|
||||
}
|
||||
|
||||
const base::flat_set<QString> &SavedSublist::chatListNameWords() const {
|
||||
return _history->chatListNameWords();
|
||||
}
|
||||
|
||||
const base::flat_set<QChar> &SavedSublist::chatListFirstLetters() const {
|
||||
return _history->chatListFirstLetters();
|
||||
}
|
||||
|
||||
const QString &SavedSublist::chatListNameSortKey() const {
|
||||
return _history->chatListNameSortKey();
|
||||
}
|
||||
|
||||
int SavedSublist::chatListNameVersion() const {
|
||||
return _history->chatListNameVersion();
|
||||
}
|
||||
|
||||
void SavedSublist::paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
const Dialogs::Ui::PaintContext &context) const {
|
||||
_history->paintUserpic(p, view, context);
|
||||
}
|
||||
|
||||
void SavedSublist::chatListPreloadData() {
|
||||
peer()->loadUserpic();
|
||||
allowChatListMessageResolve();
|
||||
}
|
||||
|
||||
void SavedSublist::allowChatListMessageResolve() {
|
||||
if (_flags & Flag::ResolveChatListMessage) {
|
||||
return;
|
||||
}
|
||||
_flags |= Flag::ResolveChatListMessage;
|
||||
resolveChatListMessageGroup();
|
||||
}
|
||||
|
||||
bool SavedSublist::hasOrphanMediaGroupPart() const {
|
||||
if (isFullLoaded() || _items.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return (_items.front()->groupId() != MessageGroupId());
|
||||
}
|
||||
|
||||
void SavedSublist::resolveChatListMessageGroup() {
|
||||
const auto item = chatListMessage();
|
||||
if (!(_flags & Flag::ResolveChatListMessage)
|
||||
|| !item
|
||||
|| !hasOrphanMediaGroupPart()) {
|
||||
return;
|
||||
}
|
||||
// If we set a single album part, request the full album.
|
||||
const auto withImages = !item->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = true }).images.empty();
|
||||
if (withImages) {
|
||||
owner().histories().requestGroupAround(item);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Data
|
85
Telegram/SourceFiles/data/data_saved_sublist.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
|
||||
class PeerData;
|
||||
class History;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
|
||||
class SavedSublist final : public Dialogs::Entry {
|
||||
public:
|
||||
explicit SavedSublist(not_null<PeerData*> peer);
|
||||
~SavedSublist();
|
||||
|
||||
[[nodiscard]] not_null<History*> history() const;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
[[nodiscard]] bool isHiddenAuthor() const;
|
||||
[[nodiscard]] bool isFullLoaded() const;
|
||||
|
||||
[[nodiscard]] auto messages() const
|
||||
-> const std::vector<not_null<HistoryItem*>> &;
|
||||
void applyMaybeLast(not_null<HistoryItem*> item, bool added = false);
|
||||
void removeOne(not_null<HistoryItem*> item);
|
||||
void append(std::vector<not_null<HistoryItem*>> &&items, int fullCount);
|
||||
void setFullLoaded(bool loaded = true);
|
||||
|
||||
[[nodiscard]] rpl::producer<> changes() const;
|
||||
[[nodiscard]] std::optional<int> fullCount() const;
|
||||
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
||||
|
||||
[[nodiscard]] Dialogs::Ui::MessageView &lastItemDialogsView() {
|
||||
return _lastItemDialogsView;
|
||||
}
|
||||
|
||||
int fixedOnTopIndex() const override;
|
||||
bool shouldBeInChatList() const override;
|
||||
Dialogs::UnreadState chatListUnreadState() const override;
|
||||
Dialogs::BadgesState chatListBadgesState() const override;
|
||||
HistoryItem *chatListMessage() const override;
|
||||
bool chatListMessageKnown() const override;
|
||||
const QString &chatListName() const override;
|
||||
const QString &chatListNameSortKey() const override;
|
||||
int chatListNameVersion() const override;
|
||||
const base::flat_set<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
void chatListPreloadData() override;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
const Dialogs::Ui::PaintContext &context) const override;
|
||||
|
||||
private:
|
||||
enum class Flag : uchar {
|
||||
ResolveChatListMessage = (1 << 0),
|
||||
FullLoaded = (1 << 1),
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; }
|
||||
using Flags = base::flags<Flag>;
|
||||
|
||||
bool hasOrphanMediaGroupPart() const;
|
||||
void allowChatListMessageResolve();
|
||||
void resolveChatListMessageGroup();
|
||||
|
||||
const not_null<History*> _history;
|
||||
|
||||
std::vector<not_null<HistoryItem*>> _items;
|
||||
std::optional<int> _fullCount;
|
||||
rpl::event_stream<> _changed;
|
||||
Dialogs::Ui::MessageView _lastItemDialogsView;
|
||||
Flags _flags;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
|
@ -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,
|
||||
|
|
|
@ -97,6 +97,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
|
|||
peer->input,
|
||||
MTP_string(query),
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPInputPeer(), // saved_peer_id
|
||||
MTP_int(topicRootId),
|
||||
filter,
|
||||
MTP_int(0), // min_date
|
||||
|
|
|
@ -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<Main::Session*> session)
|
|||
, _forumIcons(std::make_unique<ForumIcons>(this))
|
||||
, _notifySettings(std::make_unique<NotifySettings>(this))
|
||||
, _customEmojiManager(std::make_unique<CustomEmojiManager>(this))
|
||||
, _stories(std::make_unique<Stories>(this)) {
|
||||
, _stories(std::make_unique<Stories>(this))
|
||||
, _savedMessages(std::make_unique<SavedMessages>(this)) {
|
||||
_cache->open(_session->local().cacheKey());
|
||||
_bigFileCache->open(_session->local().cacheBigFileKey());
|
||||
|
||||
|
@ -1724,6 +1727,11 @@ void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
|
|||
topic->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
if (const auto sublist = item->savedSublist()) {
|
||||
if (sublist->lastItemDialogsView().dependsOn(item)) {
|
||||
sublist->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
|
||||
|
@ -2111,13 +2119,17 @@ void Session::applyDialog(
|
|||
setPinnedFromEntryList(folder, data.is_pinned());
|
||||
}
|
||||
|
||||
bool Session::pinnedCanPin(not_null<Data::Thread*> thread) const {
|
||||
if (const auto topic = thread->asTopic()) {
|
||||
bool Session::pinnedCanPin(not_null<Dialogs::Entry*> 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<Data::Forum*> forum) const {
|
|||
return limits.topicsPinnedCurrent();
|
||||
}
|
||||
|
||||
int Session::pinnedChatsLimit(not_null<Data::SavedMessages*> saved) const {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.savedSublistsPinnedCurrent();
|
||||
}
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
Data::Folder *folder) const {
|
||||
// Premium limit from appconfig.
|
||||
|
@ -2189,6 +2206,20 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
|||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
not_null<SavedMessages*> saved) const {
|
||||
// Premium limit from appconfig.
|
||||
// We always use premium limit in the MainList limit producer,
|
||||
// because it slices the list to that limit. We don't want to slice
|
||||
// premium-ly added chats from the pinned list because of sync issues.
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_session->account().appConfig().refreshed()
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.savedSublistsPinnedPremium();
|
||||
});
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
Data::Folder *folder) const {
|
||||
return chatsList(folder)->pinned()->order();
|
||||
|
@ -2204,6 +2235,11 @@ const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
|||
return forum->topicsList()->pinned()->order();
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
not_null<Data::SavedMessages*> saved) const {
|
||||
return saved->chatsList()->pinned()->order();
|
||||
}
|
||||
|
||||
void Session::clearPinnedChats(Data::Folder *folder) {
|
||||
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<Dialogs::MainList*> 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(),
|
||||
|
|
|
@ -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<MTPDialog> &dialogs,
|
||||
std::optional<int> count = std::nullopt);
|
||||
|
||||
[[nodiscard]] bool pinnedCanPin(not_null<Thread*> thread) const;
|
||||
[[nodiscard]] bool pinnedCanPin(not_null<Dialogs::Entry*> entry) const;
|
||||
[[nodiscard]] bool pinnedCanPin(
|
||||
FilterId filterId,
|
||||
not_null<History*> history) const;
|
||||
[[nodiscard]] int pinnedChatsLimit(Folder *folder) const;
|
||||
[[nodiscard]] int pinnedChatsLimit(FilterId filterId) const;
|
||||
[[nodiscard]] int pinnedChatsLimit(not_null<Forum*> forum) const;
|
||||
[[nodiscard]] int pinnedChatsLimit(
|
||||
not_null<SavedMessages*> saved) const;
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
Folder *folder) const;
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
FilterId filterId) const;
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
not_null<Forum*> forum) const;
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
not_null<SavedMessages*> saved) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
Folder *folder) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
not_null<Forum*> forum) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
FilterId filterId) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
not_null<Data::SavedMessages*> saved) const;
|
||||
void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
|
||||
void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
|
||||
void clearPinnedChats(Folder *folder);
|
||||
|
@ -1041,6 +1051,7 @@ private:
|
|||
const std::unique_ptr<NotifySettings> _notifySettings;
|
||||
const std::unique_ptr<CustomEmojiManager> _customEmojiManager;
|
||||
const std::unique_ptr<Stories> _stories;
|
||||
const std::unique_ptr<SavedMessages> _savedMessages;
|
||||
|
||||
MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange;
|
||||
|
||||
|
|
|
@ -1360,7 +1360,6 @@ void Stories::sendIncrementViewsRequests() {
|
|||
return;
|
||||
}
|
||||
|
||||
auto ids = QVector<MTPint>();
|
||||
struct Prepared {
|
||||
PeerId peer = 0;
|
||||
QVector<MTPint> ids;
|
||||
|
|
|
@ -385,7 +385,7 @@ QString UserData::username() const {
|
|||
}
|
||||
|
||||
QString UserData::editableUsername() const {
|
||||
return _username.editableUsername();;
|
||||
return _username.editableUsername();
|
||||
}
|
||||
|
||||
const std::vector<QString> &UserData::usernames() const {
|
||||
|
|
|
@ -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<CustomEmojiLoader>(
|
||||
document,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Data::Session*> 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<Data::Folder*>(this);
|
||||
}
|
||||
|
@ -126,6 +129,12 @@ Data::ForumTopic *Entry::asTopic() {
|
|||
: nullptr;
|
||||
}
|
||||
|
||||
Data::SavedSublist *Entry::asSublist() {
|
||||
return (_flags & Flag::IsSavedSublist)
|
||||
? static_cast<Data::SavedSublist*>(this)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const History *Entry::asHistory() const {
|
||||
return const_cast<Entry*>(this)->asHistory();
|
||||
}
|
||||
|
@ -146,6 +155,10 @@ const Data::ForumTopic *Entry::asTopic() const {
|
|||
return const_cast<Entry*>(this)->asTopic();
|
||||
}
|
||||
|
||||
const Data::SavedSublist *Entry::asSublist() const {
|
||||
return const_cast<Entry*>(this)->asSublist();
|
||||
}
|
||||
|
||||
void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) {
|
||||
if (!filterId && session().supportMode()) {
|
||||
// Force reorder in support mode.
|
||||
|
|
|
@ -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<Data::Session*> 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<QString> &chatListNameWords() const = 0;
|
||||
virtual const base::flat_set<QChar> &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<QString> & = 0;
|
||||
[[nodiscard]] virtual auto chatListFirstLetters() const
|
||||
-> const base::flat_set<QChar> & = 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<Flag>;
|
||||
|
@ -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<Row*> mainChatListLink(FilterId filterId) const;
|
||||
Row *maybeMainChatListLink(FilterId filterId) const;
|
||||
|
|
|
@ -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<Key> &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()
|
||||
|
|
|
@ -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<Ui::PopupMenu> _menu;
|
||||
|
||||
};
|
||||
|
|
|
@ -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*> history) : _value(history) {
|
||||
}
|
||||
|
||||
|
@ -37,6 +41,9 @@ Key::Key(not_null<Data::Folder*> folder) : _value(folder) {
|
|||
Key::Key(not_null<Data::ForumTopic*> topic) : _value(topic) {
|
||||
}
|
||||
|
||||
Key::Key(not_null<Data::SavedSublist*> sublist) : _value(sublist) {
|
||||
}
|
||||
|
||||
not_null<Entry*> 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();
|
||||
|
|
|
@ -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*> entry) : _value(entry) {
|
||||
}
|
||||
Key(not_null<History*> history);
|
||||
Key(not_null<Data::Thread*> thread);
|
||||
Key(not_null<Data::Folder*> folder);
|
||||
Key(not_null<Data::ForumTopic*> topic);
|
||||
Key(not_null<Data::SavedSublist*> 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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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*> entry() const {
|
||||
return _id.entry();
|
||||
}
|
||||
|
|
|
@ -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;) {
|
||||
|
|
|
@ -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<HistoryMessageForwarded>()) {
|
||||
if (peer->isSelf() || forwarded->imported) {
|
||||
return forwarded->hiddenSenderInfo.get();
|
||||
return forwarded->savedFromHiddenSenderInfo.get()
|
||||
? forwarded->savedFromHiddenSenderInfo.get()
|
||||
: forwarded->originalHiddenSenderInfo.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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<HistoryItem*> 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);
|
||||
}
|
||||
|
|
|
@ -365,6 +365,7 @@ public:
|
|||
void takeLocalDraft(not_null<History*> 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<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Data::Media> 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<HistoryMessageForwarded>()
|
||||
? 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<HistoryMessageReply>()) {
|
||||
reply->clearData(this);
|
||||
}
|
||||
if (const auto saved = Get<HistoryMessageSaved>()) {
|
||||
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<HistoryMessageForwarded>()) {
|
||||
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<HistoryView::Element> 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<Data::Story*> 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<HistoryMessageForwarded>()) {
|
||||
return forwarded->hiddenSenderInfo.get();
|
||||
return forwarded->originalHiddenSenderInfo.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const HiddenSenderInfo *HistoryItem::displayHiddenSenderInfo() const {
|
||||
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->savedFromHiddenSenderInfo
|
||||
? forwarded->savedFromHiddenSenderInfo.get()
|
||||
: forwarded->originalHiddenSenderInfo.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool HistoryItem::showForwardsFromSender(
|
||||
not_null<const HistoryMessageForwarded*> forwarded) const {
|
||||
const auto peer = history()->peer;
|
||||
return !forwarded->story
|
||||
&& (peer->isSelf() || peer->isRepliesChat() || forwarded->imported);
|
||||
}
|
||||
|
||||
not_null<PeerData*> HistoryItem::fromOriginal() const {
|
||||
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
if (forwarded->originalSender) {
|
||||
|
@ -3104,6 +3149,34 @@ bool HistoryItem::isEmpty() const {
|
|||
&& !Has<HistoryMessageLogEntryOriginal>();
|
||||
}
|
||||
|
||||
Data::SavedSublist *HistoryItem::savedSublist() const {
|
||||
if (const auto saved = Get<HistoryMessageSaved>()) {
|
||||
return saved->sublist;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PeerData *HistoryItem::savedSublistPeer() const {
|
||||
if (const auto sublist = savedSublist()) {
|
||||
return sublist->peer();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PeerData *HistoryItem::savedFromSender() const {
|
||||
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->savedFromSender;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const HiddenSenderInfo *HistoryItem::savedFromHiddenSenderInfo() const {
|
||||
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->savedFromHiddenSenderInfo.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TextWithEntities HistoryItem::notificationText(
|
||||
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<HistoryMessageForwarded>() != nullptr)
|
||||
const auto forwarded = Get<HistoryMessageForwarded>();
|
||||
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<QString> {
|
||||
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
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<HistoryMessageSaved>()) {
|
||||
if (!config.savedSublistPeer) {
|
||||
if (config.savedFromPeer) {
|
||||
config.savedSublistPeer = config.savedFromPeer;
|
||||
} else if (config.originalSenderId) {
|
||||
config.savedSublistPeer = config.originalSenderId;
|
||||
} else if (!config.originalSenderName.isEmpty()) {
|
||||
config.savedSublistPeer = PeerData::kSavedHiddenAuthorId;
|
||||
} else {
|
||||
config.savedSublistPeer = _history->session().userPeerId();
|
||||
}
|
||||
}
|
||||
const auto peer = _history->owner().peer(config.savedSublistPeer);
|
||||
saved->sublist = _history->owner().savedMessages().sublist(peer);
|
||||
}
|
||||
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
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<HiddenSenderInfo>(
|
||||
config.originalSenderName,
|
||||
config.imported);
|
||||
forwarded->originalHiddenSenderInfo
|
||||
= std::make_unique<HiddenSenderInfo>(
|
||||
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<HiddenSenderInfo>(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());
|
||||
|
|
|
@ -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<PeerData*> 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<const HistoryMessageForwarded*> forwarded) const;
|
||||
|
||||
[[nodiscard]] bool isEmpty() const;
|
||||
|
||||
[[nodiscard]] MessageGroupId groupId() const;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -126,9 +126,13 @@ private:
|
|||
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded, HistoryItem> {
|
||||
void create(const HistoryMessageVia *via) const;
|
||||
|
||||
[[nodiscard]] bool forwardOfForward() const {
|
||||
return savedFromSender || savedFromHiddenSenderInfo;
|
||||
}
|
||||
|
||||
TimeId originalDate = 0;
|
||||
PeerData *originalSender = nullptr;
|
||||
std::unique_ptr<HiddenSenderInfo> hiddenSenderInfo;
|
||||
std::unique_ptr<HiddenSenderInfo> originalHiddenSenderInfo;
|
||||
QString originalPostAuthor;
|
||||
QString psaType;
|
||||
MsgId originalId = 0;
|
||||
|
@ -136,10 +140,19 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
|
|||
|
||||
PeerData *savedFromPeer = nullptr;
|
||||
MsgId savedFromMsgId = 0;
|
||||
|
||||
PeerData *savedFromSender = nullptr;
|
||||
std::unique_ptr<HiddenSenderInfo> savedFromHiddenSenderInfo;
|
||||
|
||||
bool savedFromOutgoing = false;
|
||||
bool imported = false;
|
||||
bool story = false;
|
||||
};
|
||||
|
||||
struct HistoryMessageSaved : public RuntimeComponent<HistoryMessageSaved, HistoryItem> {
|
||||
Data::SavedSublist *sublist = nullptr;
|
||||
};
|
||||
|
||||
class ReplyToMessagePointer final {
|
||||
public:
|
||||
ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) {
|
||||
|
|
|
@ -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<HistoryItem*> item) {
|
||||
if (const auto media = item->media()) {
|
||||
if (!media->ttlSeconds()) {
|
||||
return;
|
||||
}
|
||||
if (const auto document = media->document()) {
|
||||
item->applyEditionToHistoryCleared();
|
||||
auto text = (document->isVideoFile()
|
||||
? tr::lng_ttl_video_expired
|
||||
: document->isVoiceMessage()
|
||||
? tr::lng_ttl_voice_expired
|
||||
: document->isVideoMessage()
|
||||
? tr::lng_ttl_round_expired
|
||||
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
|
||||
item->updateServiceText(PreparedServiceText{ std::move(text) });
|
||||
} else if (const auto photo = media->photo()) {
|
||||
item->applyEditionToHistoryCleared();
|
||||
item->updateServiceText(PreparedServiceText{
|
||||
tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsVoiceOncePlayable(not_null<HistoryItem*> item) {
|
||||
return !item->out() && item->media()->ttlSeconds();
|
||||
}
|
||||
|
|
|
@ -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<HistoryItem*> item);
|
||||
[[nodiscard]] bool IsVoiceOncePlayable(not_null<HistoryItem*> item);
|
||||
|
|
|
@ -281,7 +281,7 @@ private:
|
|||
base::unique_qptr<Ui::IconButton> _cancel;
|
||||
base::unique_qptr<Ui::MultiSelect> _select;
|
||||
|
||||
rpl::variable<PeerData*> _from = nullptr;;
|
||||
rpl::variable<PeerData*> _from = nullptr;
|
||||
|
||||
base::Timer _searchTimer;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ void SavePhotoToFile(not_null<PhotoData*> 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),
|
||||
|
|
|
@ -64,16 +64,20 @@ Element *MousedElement/* = nullptr*/;
|
|||
HistoryMessageForwarded *prevForwarded,
|
||||
not_null<HistoryItem*> 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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|