Support pinned saved sublists.

This commit is contained in:
John Preston 2023-12-31 08:49:55 +04:00
parent d2565dc944
commit 9392550c01
11 changed files with 301 additions and 82 deletions

View file

@ -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"
@ -2208,6 +2209,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())) {

View file

@ -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"
@ -443,8 +444,8 @@ void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
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);
if (const auto sublist = key.sublist()) {
return MTP_inputDialogPeer(sublist->peer()->input);
}
Unexpected("Key type in pinnedDialogsOrder().");
};

View file

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

View file

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

View file

@ -62,54 +62,19 @@ not_null<SavedSublist*> SavedMessages::sublist(not_null<PeerData*> peer) {
void SavedMessages::loadMore() {
if (_loadMoreRequestId || _chatsList.loaded()) {
return;
} else if (!_pinnedLoaded) {
loadPinned();
}
_loadMoreRequestId = _owner->session().api().request(
MTPmessages_GetSavedDialogs(
MTP_flags(0),
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) {
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();
}
apply(result, false);
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
@ -119,6 +84,24 @@ void SavedMessages::loadMore() {
}).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::loadMore(not_null<SavedSublist*> sublist) {
if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) {
return;
@ -185,4 +168,109 @@ void SavedMessages::loadMore(not_null<SavedSublist*> sublist) {
_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::apply(const MTPDupdatePinnedSavedDialogs &update) {
const auto list = update.vorder();
if (!list) {
loadPinned();
return;
}
const auto &order = list->v;
const auto notLoaded = [&](const MTPDialogPeer &peer) {
return peer.match([&](const MTPDdialogPeer &data) {
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
return !_sublists.contains(peer);
}, [&](const MTPDdialogPeerFolder &data) {
LOG(("API Error: "
"updatePinnedSavedDialogs has folders."));
return false;
});
};
if (!ranges::none_of(order, notLoaded)) {
loadPinned();
} else {
_chatsList.pinned()->applyList(_owner, order);
_owner->notifyPinnedDialogsOrderUpdated();
}
}
void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
update.vpeer().match([&](const MTPDdialogPeer &data) {
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
const auto i = _sublists.find(peer);
if (i != end(_sublists)) {
const auto entry = i->second.get();
_owner->setChatPinned(entry, FilterId(), update.is_pinned());
} else {
loadPinned();
}
}, [&](const MTPDdialogPeerFolder &data) {
DEBUG_LOG(("API Error: Folder in updateSavedDialogPinned."));
});
}
} // namespace Data

View file

@ -34,7 +34,13 @@ public:
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);
const not_null<Session*> _owner;
Dialogs::MainList _chatsList;
@ -44,11 +50,13 @@ private:
base::flat_map<not_null<SavedSublist*>, mtpRequestId> _loadMoreRequests;
mtpRequestId _loadMoreRequestId = 0;
mtpRequestId _pinnedRequestId = 0;
TimeId _offsetDate = 0;
MsgId _offsetId = 0;
PeerData *_offsetPeer = nullptr;
bool _pinnedLoaded = false;
bool _unsupported = false;
};

View file

@ -2107,13 +2107,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(
@ -2240,7 +2244,7 @@ void Session::reorderTwoPinnedChats(
? topic->forum()->topicsList()
: filterId
? chatsFilters().chatsList(filterId)
: chatsList(key1.entry()->folder());
: chatsListFor(key1.entry());
list->pinned()->reorder(key1, key2);
notifyPinnedDialogsOrderUpdated();
}

View file

@ -349,7 +349,7 @@ 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;

View file

@ -12,10 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "dialogs/dialogs_inner_widget.h"
#include "history/view/history_view_sublist_section.h"
#include "info/media/info_media_buttons.h"
#include "info/profile/info_profile_icon.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "main/main_session.h"
#include "lang/lang_keys.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_info.h"
namespace Info::Saved {
@ -41,24 +47,27 @@ SublistsMemento::~SublistsMemento() = default;
SublistsWidget::SublistsWidget(
QWidget *parent,
not_null<Controller*> controller)
: ContentWidget(parent, controller) {
_inner = setInnerWidget(object_ptr<Dialogs::InnerWidget>(
: ContentWidget(parent, controller)
, _layout(setInnerWidget(object_ptr<Ui::VerticalLayout>(this))) {
setupOtherTypes();
_list = _layout->add(object_ptr<Dialogs::InnerWidget>(
this,
controller->parentController(),
rpl::single(Dialogs::InnerWidget::ChildListShown())));
_inner->showSavedSublists();
_inner->setNarrowRatio(0.);
_list->showSavedSublists();
_list->setNarrowRatio(0.);
_inner->chosenRow() | rpl::start_with_next([=](Dialogs::ChosenRow row) {
_list->chosenRow() | rpl::start_with_next([=](Dialogs::ChosenRow row) {
if (const auto sublist = row.key.sublist()) {
controller->showSection(
std::make_shared<HistoryView::SublistMemento>(sublist),
Window::SectionShow::Way::Forward);
}
}, _inner->lifetime());
}, _list->lifetime());
const auto saved = &controller->session().data().savedMessages();
_inner->heightValue() | rpl::start_with_next([=] {
_list->heightValue() | rpl::start_with_next([=] {
if (!saved->supported()) {
crl::on_main(controller, [=] {
controller->showSection(
@ -68,11 +77,61 @@ SublistsWidget::SublistsWidget(
}
}, lifetime());
_inner->setLoadMoreCallback([=] {
_list->setLoadMoreCallback([=] {
saved->loadMore();
});
}
void SublistsWidget::setupOtherTypes() {
auto wrap = _layout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_layout,
object_ptr<Ui::VerticalLayout>(_layout)));
auto content = wrap->entity();
content->add(object_ptr<Ui::FixedHeightWidget>(
content,
st::infoProfileSkip));
using Type = Media::Type;
auto tracker = Ui::MultiSlideTracker();
const auto peer = controller()->session().user();
const auto addMediaButton = [&](
Type buttonType,
const style::icon &icon) {
auto result = Media::AddButton(
content,
controller(),
peer,
MsgId(), // topicRootId
nullptr, // migrated
buttonType,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition)->show();
};
addMediaButton(Type::Photo, st::infoIconMediaPhoto);
addMediaButton(Type::Video, st::infoIconMediaVideo);
addMediaButton(Type::File, st::infoIconMediaFile);
addMediaButton(Type::MusicFile, st::infoIconMediaAudio);
addMediaButton(Type::Link, st::infoIconMediaLink);
addMediaButton(Type::RoundVoiceFile, st::infoIconMediaVoice);
addMediaButton(Type::GIF, st::infoIconMediaGif);
content->add(object_ptr<Ui::FixedHeightWidget>(
content,
st::infoProfileSkip));
wrap->toggleOn(tracker.atLeastOneShownValue());
wrap->finishAnimating();
_layout->add(object_ptr<Ui::BoxContentDivider>(_layout));
_layout->add(object_ptr<Ui::FixedHeightWidget>(
content,
st::infoProfileSkip));
}
rpl::producer<QString> SublistsWidget::title() {
return tr::lng_saved_messages();
}

View file

@ -17,6 +17,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Ui {
class VerticalLayout;
} // namespace Ui
namespace Info::Saved {
class SublistsMemento final : public ContentMemento {
@ -58,7 +62,10 @@ private:
std::shared_ptr<ContentMemento> doCreateMemento() override;
Dialogs::InnerWidget *_inner = nullptr;
void setupOtherTypes();
const not_null<Ui::VerticalLayout*> _layout;
Dialogs::InnerWidget *_list = nullptr;
};

View file

@ -76,6 +76,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_user.h"
#include "data/data_saved_sublist.h"
#include "data/data_scheduled_messages.h"
#include "data/data_histories.h"
#include "data/data_chat_filters.h"
@ -244,6 +245,7 @@ private:
void fillRepliesActions();
void fillScheduledActions();
void fillArchiveActions();
void fillSavedSublistActions();
void fillContextMenuActions();
void addHidePromotion();
@ -293,6 +295,7 @@ private:
Data::ForumTopic *_topic = nullptr;
PeerData *_peer = nullptr;
Data::Folder *_folder = nullptr;
Data::SavedSublist *_sublist = nullptr;
const PeerMenuCallback &_addAction;
};
@ -319,17 +322,21 @@ void AddChatMembers(
bool PinnedLimitReached(
not_null<Window::SessionController*> controller,
not_null<Data::Thread*> thread) {
const auto owner = &thread->owner();
if (owner->pinnedCanPin(thread)) {
not_null<Dialogs::Entry*> entry) {
const auto owner = &entry->owner();
if (owner->pinnedCanPin(entry)) {
return false;
}
// Some old chat, that was converted, maybe is still pinned.
const auto history = thread->asHistory();
if (!history) {
controller->show(Box(ForumPinsLimitBox, thread->asTopic()->forum()));
if (const auto sublist = entry->asSublist()) {
controller->show(Box(SublistsPinsLimitBox, &sublist->session()));
return true;
} else if (const auto topic = entry->asTopic()) {
controller->show(Box(ForumPinsLimitBox, topic->forum()));
return true;
}
const auto history = entry->asHistory();
Assert(history != nullptr);
const auto folder = history->folder();
const auto wasted = FindWastedPin(owner, folder);
if (wasted) {
@ -359,18 +366,18 @@ bool PinnedLimitReached(
void TogglePinnedThread(
not_null<Window::SessionController*> controller,
not_null<Data::Thread*> thread) {
if (!thread->folderKnown()) {
not_null<Dialogs::Entry*> entry) {
if (!entry->folderKnown()) {
return;
}
const auto owner = &thread->owner();
const auto isPinned = !thread->isPinnedDialog(FilterId());
if (isPinned && PinnedLimitReached(controller, thread)) {
const auto owner = &entry->owner();
const auto isPinned = !entry->isPinnedDialog(FilterId());
if (isPinned && PinnedLimitReached(controller, entry)) {
return;
}
owner->setChatPinned(thread, FilterId(), isPinned);
if (const auto history = thread->asHistory()) {
owner->setChatPinned(entry, FilterId(), isPinned);
if (const auto history = entry->asHistory()) {
const auto flags = isPinned
? MTPmessages_ToggleDialogPin::Flag::f_pinned
: MTPmessages_ToggleDialogPin::Flag(0);
@ -383,7 +390,7 @@ void TogglePinnedThread(
if (isPinned) {
controller->content()->dialogsToUp();
}
} else if (const auto topic = thread->asTopic()) {
} else if (const auto topic = entry->asTopic()) {
owner->session().api().request(MTPchannels_UpdatePinnedForumTopic(
topic->channel()->inputChannel,
MTP_int(topic->rootId()),
@ -391,17 +398,30 @@ void TogglePinnedThread(
)).done([=](const MTPUpdates &result) {
owner->session().api().applyUpdates(result);
}).send();
} else if (const auto sublist = entry->asSublist()) {
const auto flags = isPinned
? MTPmessages_ToggleSavedDialogPin::Flag::f_pinned
: MTPmessages_ToggleSavedDialogPin::Flag(0);
owner->session().api().request(MTPmessages_ToggleSavedDialogPin(
MTP_flags(flags),
MTP_inputDialogPeer(sublist->peer()->input)
)).done([=] {
owner->notifyPinnedDialogsOrderUpdated();
}).send();
//if (isPinned) {
// controller->content()->dialogsToUp();
//}
}
}
void TogglePinnedThread(
not_null<Window::SessionController*> controller,
not_null<Data::Thread*> thread,
not_null<Dialogs::Entry*> entry,
FilterId filterId) {
if (!filterId) {
return TogglePinnedThread(controller, thread);
return TogglePinnedThread(controller, entry);
}
const auto history = thread->asHistory();
const auto history = entry->asHistory();
if (!history) {
return;
}
@ -438,6 +458,7 @@ Filler::Filler(
, _topic(request.key.topic())
, _peer(request.key.peer())
, _folder(request.key.folder())
, _sublist(request.key.sublist())
, _addAction(addAction) {
}
@ -471,21 +492,21 @@ void Filler::addToggleTopicClosed() {
}
void Filler::addTogglePin() {
if (!_peer || (_topic && !_topic->canTogglePinned())) {
if ((!_sublist && !_peer) || (_topic && !_topic->canTogglePinned())) {
return;
}
const auto controller = _controller;
const auto filterId = _request.filterId;
const auto thread = _request.key.thread();
if (!thread || thread->fixedOnTopIndex()) {
const auto entry = _thread ? (Dialogs::Entry*)_thread : _sublist;
if (!entry || entry->fixedOnTopIndex()) {
return;
}
const auto pinText = [=] {
return thread->isPinnedDialog(filterId)
return entry->isPinnedDialog(filterId)
? tr::lng_context_unpin_from_top(tr::now)
: tr::lng_context_pin_to_top(tr::now);
};
const auto weak = base::make_weak(thread);
const auto weak = base::make_weak(entry);
const auto pinToggle = [=] {
if (const auto strong = weak.get()) {
TogglePinnedThread(controller, strong, filterId);
@ -494,7 +515,7 @@ void Filler::addTogglePin() {
_addAction(
pinText(),
pinToggle,
(thread->isPinnedDialog(filterId)
(entry->isPinnedDialog(filterId)
? &st::menuIconUnpin
: &st::menuIconPin));
}
@ -1116,9 +1137,9 @@ void Filler::addGiftPremium() {
void Filler::fill() {
if (_folder) {
fillArchiveActions();
return;
}
switch (_request.section) {
} else if (_sublist) {
fillSavedSublistActions();
} else switch (_request.section) {
case Section::ChatsList: fillChatsListActions(); break;
case Section::History: fillHistoryActions(); break;
case Section::Profile: fillProfileActions(); break;
@ -1353,6 +1374,10 @@ void Filler::fillArchiveActions() {
}, &st::menuIconManage);
}
void Filler::fillSavedSublistActions() {
addTogglePin();
}
} // namespace
void PeerMenuExportChat(not_null<PeerData*> peer) {