Show saved messages sublists in profile.

This commit is contained in:
John Preston 2023-12-27 01:09:20 +00:00
parent ead40c759e
commit 18c4d210e5
41 changed files with 1030 additions and 65 deletions

View File

@ -550,6 +550,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
@ -897,6 +901,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

View File

@ -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.";

View File

@ -440,6 +440,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 history = key.history()) {
return MTP_inputDialogPeer(history->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,

View File

@ -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,

View File

@ -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();
}

View File

@ -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(

View File

@ -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,

View File

@ -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();

View File

@ -203,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);

View File

@ -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);
}

View File

@ -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;

View File

@ -0,0 +1,181 @@
/*
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)) {
}
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() {
if (_loadMoreRequestId || _chatsList.loaded()) {
return;
}
_loadMoreRequestId = _owner->session().api().request(
MTPmessages_GetSavedDialogs(
MTP_flags(0),
MTP_int(_offsetDate),
MTP_int(_offsetId),
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
MTP_int(kPerPage),
MTP_long(0)) // hash
).done([=](const MTPmessages_SavedDialogs &result) {
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;
});
_loadMoreRequestId = 0;
if (!list) {
_chatsList.setLoaded();
return;
}
auto lastValid = false;
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;
sublist(peer)->applyMaybeLast(item);
} else {
lastValid = false;
}
}
if (!lastValid) {
LOG(("API Error: Unknown message in the end of a slice."));
_chatsList.setLoaded();
} else if (result.type() == mtpc_messages_savedDialogs) {
_chatsList.setLoaded();
}
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
}
_chatsList.setLoaded();
_loadMoreRequestId = 0;
}).send();
}
void SavedMessages::loadMore(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 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;
});
_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));
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();
}
} // namespace Data

View File

@ -0,0 +1,56 @@
/*
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);
private:
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;
TimeId _offsetDate = 0;
MsgId _offsetId = 0;
PeerData *_offsetPeer = nullptr;
bool _unsupported = false;
};
} // namespace Data

View File

@ -0,0 +1,212 @@
/*
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) {
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 (_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) {
break;
} else if (before(*i, item)) {
_items.insert(i, item);
break;
}
}
}
if (_items.front() == item) {
setChatListTimeId(item->date());
resolveChatListMessageGroup();
}
}
void SavedSublist::removeOne(not_null<HistoryItem*> item) {
if (_items.empty()) {
return;
}
const auto last = (_items.front() == item);
_items.erase(ranges::remove(_items, item), end(_items));
if (last) {
if (_items.empty()) {
if (isFullLoaded()) {
updateChatListExistence();
} else {
updateChatListEntry();
crl::on_main(this, [=] {
owner().savedMessages().loadMore(this);
});
}
} else {
setChatListTimeId(_items.front()->date());
}
}
}
void SavedSublist::append(std::vector<not_null<HistoryItem*>> &&items) {
if (items.empty()) {
setFullLoaded();
} else if (!_items.empty()) {
_items.insert(end(_items), begin(items), end(items));
} else {
_items = std::move(items);
setChatListTimeId(_items.front()->date());
}
}
void SavedSublist::setFullLoaded(bool loaded) {
if (loaded != isFullLoaded()) {
if (loaded) {
_flags |= Flag::FullLoaded;
if (_items.empty()) {
updateChatListExistence();
}
} else {
_flags &= ~Flag::FullLoaded;
}
}
}
int SavedSublist::fixedOnTopIndex() const {
return 0;
}
bool SavedSublist::shouldBeInChatList() const {
return isPinnedDialog(FilterId()) || !_items.empty();
}
Dialogs::UnreadState SavedSublist::chatListUnreadState() const {
return {};
}
Dialogs::BadgesState SavedSublist::chatListBadgesState() const {
return {};
}
HistoryItem *SavedSublist::chatListMessage() const {
return _items.empty() ? nullptr : _items.front().get();
}
bool SavedSublist::chatListMessageKnown() const {
return true;
}
const QString &SavedSublist::chatListName() const {
return _history->chatListName();
}
const base::flat_set<QString> &SavedSublist::chatListNameWords() const {
return _history->chatListNameWords();
}
const base::flat_set<QChar> &SavedSublist::chatListFirstLetters() const {
return _history->chatListFirstLetters();
}
const QString &SavedSublist::chatListNameSortKey() const {
return _history->chatListNameSortKey();
}
int SavedSublist::chatListNameVersion() const {
return _history->chatListNameVersion();
}
void SavedSublist::paintUserpic(
Painter &p,
Ui::PeerUserpicView &view,
const Dialogs::Ui::PaintContext &context) const {
_history->paintUserpic(p, view, context);
}
void SavedSublist::chatListPreloadData() {
peer()->loadUserpic();
allowChatListMessageResolve();
}
void SavedSublist::allowChatListMessageResolve() {
if (_flags & Flag::ResolveChatListMessage) {
return;
}
_flags |= Flag::ResolveChatListMessage;
resolveChatListMessageGroup();
}
bool SavedSublist::hasOrphanMediaGroupPart() const {
if (isFullLoaded() || _items.size() != 1) {
return false;
}
return (_items.front()->groupId() != MessageGroupId());
}
void SavedSublist::resolveChatListMessageGroup() {
const auto item = chatListMessage();
if (!(_flags & Flag::ResolveChatListMessage)
|| !item
|| !hasOrphanMediaGroupPart()) {
return;
}
// If we set a single album part, request the full album.
const auto withImages = !item->toPreview({
.hideSender = true,
.hideCaption = true }).images.empty();
if (withImages) {
owner().histories().requestGroupAround(item);
}
}
} // namespace Data

View File

@ -0,0 +1,79 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "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);
void removeOne(not_null<HistoryItem*> item);
void append(std::vector<not_null<HistoryItem*>> &&items);
void setFullLoaded(bool loaded = true);
[[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;
Dialogs::Ui::MessageView _lastItemDialogsView;
Flags _flags;
};
} // namespace Data

View File

@ -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"
@ -261,7 +263,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());
@ -1712,6 +1715,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 {
@ -2137,6 +2145,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.
@ -2177,6 +2190,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();
@ -2192,6 +2219,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();
}
@ -4198,6 +4230,8 @@ not_null<Dialogs::MainList*> Session::chatsListFor(
const auto topic = entry->asTopic();
return topic
? topic->forum()->topicsList()
: entry->asSublist()
? _savedMessages->chatsList()
: chatsList(entry->folder());
}

View File

@ -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;
@ -352,18 +356,24 @@ public:
[[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;

View File

@ -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.

View File

@ -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;

View File

@ -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()

View File

@ -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;
};

View File

@ -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();

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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"
@ -732,6 +733,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.
@ -810,6 +812,8 @@ void RowPainter::Paint(
? nullptr
: thread
? &thread->lastItemDialogsView()
: sublist
? &sublist->lastItemDialogsView()
: nullptr;
if (view) {
const auto forum = context.st->topicsHeight

View File

@ -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"
@ -609,6 +610,11 @@ not_null<HistoryItem*> History::addNewItem(
addNewToBack(item, unread);
checkForLoadedAtTop(item);
}
if (const auto sublist = item->savedSublist()) {
sublist->applyMaybeLast(item);
}
return item;
}
@ -1427,6 +1433,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);
}

View File

@ -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;

View File

@ -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"
@ -145,6 +147,8 @@ struct HistoryItem::CreateConfig {
QString originalSenderName;
QString originalPostAuthor;
PeerId savedSublistPeer = 0;
QString forwardPsaType;
PeerId savedFromPeer = 0;
MsgId savedFromMsgId = 0;
@ -769,6 +773,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);
}
@ -1229,6 +1236,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() {
@ -3027,6 +3037,20 @@ 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;
}
TextWithEntities HistoryItem::notificationText(
NotificationTextOptions options) const {
auto result = [&] {
@ -3178,9 +3202,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)) {
@ -3474,6 +3517,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);

View File

@ -56,6 +56,7 @@ class ForumTopic;
class Thread;
struct SponsoredFrom;
class Story;
class SavedSublist;
} // namespace Data
namespace Main {
@ -485,6 +486,9 @@ public:
[[nodiscard]] QString originalPostAuthor() const;
[[nodiscard]] MsgId originalId() const;
[[nodiscard]] Data::SavedSublist *savedSublist() const;
[[nodiscard]] PeerData *savedSublistPeer() const;
[[nodiscard]] bool isEmpty() const;
[[nodiscard]] MessageGroupId groupId() const;

View File

@ -141,7 +141,7 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
};
struct HistoryMessageSaved : public RuntimeComponent<HistoryMessageSaved, HistoryItem> {
PeerData *peer = nullptr;
Data::SavedSublist *sublist = nullptr;
};
class ReplyToMessagePointer final {

View File

@ -689,11 +689,11 @@ void TopBarWidget::infoClicked() {
return;
} else if (const auto topic = key.topic()) {
_controller->showSection(std::make_shared<Info::Memento>(topic));
} else if (key.peer()->isSelf()) {
} else if (key.peer()->savedSublistsInfo()) {
_controller->showSection(std::make_shared<Info::Memento>(
key.peer(),
Info::Section(Storage::SharedMediaType::Photo)));
} else if (key.peer()->isRepliesChat()) {
Info::Section::Type::SavedSublists));
} else if (key.peer()->sharedMediaInfo()) {
_controller->showSection(std::make_shared<Info::Memento>(
key.peer(),
Info::Section(Storage::SharedMediaType::Photo)));

View File

@ -126,6 +126,7 @@ public:
Media,
CommonGroups,
SimilarChannels,
SavedSublists,
Members,
Settings,
Downloads,

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/media/info_media_widget.h"
#include "info/members/info_members_widget.h"
#include "info/common_groups/info_common_groups_widget.h"
#include "info/saved/info_saved_sublists_widget.h"
#include "info/settings/info_settings_widget.h"
#include "info/similar_channels/info_similar_channels_widget.h"
#include "info/polls/info_polls_results_widget.h"
@ -111,7 +112,9 @@ std::vector<std::shared_ptr<ContentMemento>> Memento::DefaultStack(
}
Section Memento::DefaultSection(not_null<PeerData*> peer) {
if (peer->sharedMediaInfo()) {
if (peer->savedSublistsInfo()) {
return Section(Section::Type::SavedSublists);
} else if (peer->sharedMediaInfo()) {
return Section(Section::MediaType::Photo);
}
return Section(Section::Type::Profile);
@ -145,6 +148,8 @@ std::shared_ptr<ContentMemento> Memento::DefaultContent(
case Section::Type::SimilarChannels:
return std::make_shared<SimilarChannels::Memento>(
peer->asChannel());
case Section::Type::SavedSublists:
return std::make_shared<Saved::SublistsMemento>(&peer->session());
case Section::Type::Members:
return std::make_shared<Members::Memento>(
peer,

View File

@ -188,23 +188,32 @@ void WrapWidget::injectActivePeerProfile(not_null<PeerData*> peer) {
? _historyStack.front().section->section().type()
: _controller->section().type();
const auto firstSectionMediaType = [&] {
if (firstSectionType == Section::Type::Profile) {
if (firstSectionType == Section::Type::Profile
|| firstSectionType == Section::Type::SavedSublists) {
return Section::MediaType::kCount;
}
return hasStackHistory()
? _historyStack.front().section->section().mediaType()
: _controller->section().mediaType();
}();
const auto expectedType = peer->sharedMediaInfo()
const auto savedSublistsInfo = peer->savedSublistsInfo();
const auto sharedMediaInfo = peer->sharedMediaInfo();
const auto expectedType = savedSublistsInfo
? Section::Type::SavedSublists
: sharedMediaInfo
? Section::Type::Media
: Section::Type::Profile;
const auto expectedMediaType = peer->sharedMediaInfo()
const auto expectedMediaType = savedSublistsInfo
? Section::MediaType::kCount
: sharedMediaInfo
? Section::MediaType::Photo
: Section::MediaType::kCount;
if (firstSectionType != expectedType
|| firstSectionMediaType != expectedMediaType
|| firstPeer != peer) {
auto section = peer->sharedMediaInfo()
auto section = savedSublistsInfo
? Section(Section::Type::SavedSublists)
: sharedMediaInfo
? Section(Section::MediaType::Photo)
: Section(Section::Type::Profile);
injectActiveProfileMemento(std::move(
@ -545,6 +554,8 @@ void WrapWidget::removeFromStack(const std::vector<Section> &sections) {
const auto &s = item.section->section();
if (s.type() != section.type()) {
return false;
} else if (s.type() == Section::Type::SavedSublists) {
return true;
} else if (s.type() == Section::Type::Media) {
return (s.mediaType() == section.mediaType());
} else if (s.type() == Section::Type::Settings) {

View File

@ -43,7 +43,7 @@ InnerWidget::InnerWidget(
// Allows showing additional shared media links and tabs.
// Used for shared media in Saved Messages.
void InnerWidget::setupOtherTypes() {
if (_controller->key().peer()->isSelf() && _isStackBottom) {
if (_controller->key().peer()->sharedMediaInfo() && _isStackBottom) {
createOtherTypes();
} else {
_otherTypes.destroy();

View File

@ -0,0 +1,105 @@
/*
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 "info/saved/info_saved_sublists_widget.h"
//
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "dialogs/dialogs_inner_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "main/main_session.h"
#include "lang/lang_keys.h"
namespace Info::Saved {
SublistsMemento::SublistsMemento(not_null<Main::Session*> session)
: ContentMemento(session->user(), nullptr, PeerId()) {
}
Section SublistsMemento::section() const {
return Section(Section::Type::SavedSublists);
}
object_ptr<ContentWidget> SublistsMemento::createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) {
auto result = object_ptr<SublistsWidget>(parent, controller);
result->setInternalState(geometry, this);
return result;
}
SublistsMemento::~SublistsMemento() = default;
SublistsWidget::SublistsWidget(
QWidget *parent,
not_null<Controller*> controller)
: ContentWidget(parent, controller) {
_inner = setInnerWidget(object_ptr<Dialogs::InnerWidget>(
this,
controller->parentController(),
rpl::single(Dialogs::InnerWidget::ChildListShown())));
_inner->showSavedSublists();
_inner->setNarrowRatio(0.);
const auto saved = &controller->session().data().savedMessages();
_inner->heightValue() | rpl::start_with_next([=] {
if (!saved->supported()) {
crl::on_main(controller, [=] {
controller->showSection(
Memento::Default(controller->session().user()),
Window::SectionShow::Way::Backward);
});
}
}, lifetime());
_inner->setLoadMoreCallback([=] {
saved->loadMore();
});
}
rpl::producer<QString> SublistsWidget::title() {
return tr::lng_saved_messages();
}
bool SublistsWidget::showInternal(not_null<ContentMemento*> memento) {
if (!controller()->validateMementoPeer(memento)) {
return false;
}
if (auto my = dynamic_cast<SublistsMemento*>(memento.get())) {
restoreState(my);
return true;
}
return false;
}
void SublistsWidget::setInternalState(
const QRect &geometry,
not_null<SublistsMemento*> memento) {
setGeometry(geometry);
Ui::SendPendingMoveResizeEvents(this);
restoreState(memento);
}
std::shared_ptr<ContentMemento> SublistsWidget::doCreateMemento() {
auto result = std::make_shared<SublistsMemento>(
&controller()->session());
saveState(result.get());
return result;
}
void SublistsWidget::saveState(not_null<SublistsMemento*> memento) {
memento->setScrollTop(scrollTopSave());
}
void SublistsWidget::restoreState(not_null<SublistsMemento*> memento) {
scrollTopRestore(memento->scrollTop());
}
} // namespace Info::Saved

View File

@ -0,0 +1,64 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "info/info_content_widget.h"
namespace Dialogs {
class InnerWidget;
} // namespace Dialogs
namespace Main {
class Session;
} // namespace Main
namespace Info::Saved {
class SublistsMemento final : public ContentMemento {
public:
explicit SublistsMemento(not_null<Main::Session*> session);
object_ptr<ContentWidget> createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) override;
Section section() const override;
~SublistsMemento();
private:
};
class SublistsWidget final : public ContentWidget {
public:
SublistsWidget(
QWidget *parent,
not_null<Controller*> controller);
bool showInternal(
not_null<ContentMemento*> memento) override;
void setInternalState(
const QRect &geometry,
not_null<SublistsMemento*> memento);
rpl::producer<QString> title() override;
private:
void saveState(not_null<SublistsMemento*> memento);
void restoreState(not_null<SublistsMemento*> memento);
std::shared_ptr<ContentMemento> doCreateMemento() override;
Dialogs::InnerWidget *_inner = nullptr;
};
} // namespace Info::Saved

View File

@ -513,4 +513,3 @@ void Widget::restoreState(not_null<Memento*> memento) {
}
} // namespace Info::SimilarChannels

View File

@ -68,4 +68,3 @@ private:
};
} // namespace Info::SimilarChannels

View File

@ -500,7 +500,7 @@ void Filler::addTogglePin() {
}
void Filler::addToggleMuteSubmenu(bool addSeparator) {
if (_thread->peer()->isSelf()) {
if (!_thread || _thread->peer()->isSelf()) {
return;
}
PeerMenuAddMuteSubmenuAction(_controller, _thread, _addAction);
@ -526,6 +526,8 @@ void Filler::addSupportInfo() {
void Filler::addInfo() {
if (_peer && (_peer->isSelf() || _peer->isRepliesChat())) {
return;
} else if (!_thread) {
return;
} else if (_controller->adaptive().isThreeColumn()) {
const auto thread = _controller->activeChatCurrent().thread();
if (thread && thread == _thread) {
@ -534,8 +536,6 @@ void Filler::addInfo() {
return;
}
}
} else if (!_thread) {
return;
}
const auto controller = _controller;
const auto weak = base::make_weak(_thread);