diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8d6715541..fed5b1ce2 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -564,6 +564,8 @@ PRIVATE history/view/history_view_message.cpp history/view/history_view_message.h history/view/history_view_object.h + history/view/history_view_pinned_tracker.cpp + history/view/history_view_pinned_tracker.h history/view/history_view_replies_section.cpp history/view/history_view_replies_section.h history/view/history_view_schedule_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 78d54b851..8cfd5b8cb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -161,6 +161,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_deleted" = "Deleted Account"; "lng_deleted_message" = "Deleted message"; "lng_pinned_message" = "Pinned message"; +"lng_pinned_previous" = "Previous message"; "lng_pinned_poll" = "Pinned poll"; "lng_pinned_quiz" = "Pinned quiz"; "lng_pinned_unpin_sure" = "Would you like to unpin this message?"; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 3cd7b45d6..27347867c 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -469,12 +469,14 @@ void PeerData::ensurePinnedMessagesCreated() { if (!_pinnedMessages) { _pinnedMessages = std::make_unique( peerToChannel(id)); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } void PeerData::removeEmptyPinnedMessages() { if (_pinnedMessages && _pinnedMessages->empty()) { _pinnedMessages = nullptr; + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } @@ -511,8 +513,8 @@ void PeerData::addPinnedMessage(MsgId messageId) { void PeerData::addPinnedSlice( std::vector &&ids, - MsgId from, - MsgId till) { + MsgRange noSkipRange, + std::optional count) { const auto min = [&] { if (const auto channel = asChannel()) { return channel->availableMinId(); @@ -522,14 +524,14 @@ void PeerData::addPinnedSlice( ids.erase( ranges::remove_if(ids, [&](MsgId id) { return id <= min; }), end(ids)); - if (from <= min) { - from = 0; + if (noSkipRange.from <= min) { + noSkipRange.from = 0; } if (ids.empty() && !_pinnedMessages) { return; } ensurePinnedMessagesCreated(); - _pinnedMessages->add(std::move(ids), from, till, std::nullopt); + _pinnedMessages->add(std::move(ids), noSkipRange, std::nullopt); } void PeerData::removePinnedMessage(MsgId messageId) { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 7aff79a44..c8bf6eac1 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -30,6 +30,7 @@ namespace Data { class Session; class PinnedMessages; +struct PinnedAroundId; int PeerColorIndex(PeerId peerId); int PeerColorIndex(int32 bareId); @@ -331,8 +332,14 @@ public: void setTopPinnedMessageId(MsgId messageId); void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId); void addPinnedMessage(MsgId messageId); - void addPinnedSlice(std::vector &&ids, MsgId from, MsgId till); + void addPinnedSlice( + std::vector &&ids, + MsgRange noSkipRange, + std::optional count); void removePinnedMessage(MsgId messageId); + Data::PinnedMessages *currentPinnedMessages() const { + return _pinnedMessages.get(); + } [[nodiscard]] bool canExportChatHistory() const; diff --git a/Telegram/SourceFiles/data/data_pinned_messages.cpp b/Telegram/SourceFiles/data/data_pinned_messages.cpp index ad7037e85..e9fc9a110 100644 --- a/Telegram/SourceFiles/data/data_pinned_messages.cpp +++ b/Telegram/SourceFiles/data/data_pinned_messages.cpp @@ -25,6 +25,25 @@ MsgId PinnedMessages::topId() const { return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg; } +rpl::producer PinnedMessages::viewer( + MsgId aroundId, + int limit) const { + return _list.viewer(MessagesQuery{ + .aroundId = position(aroundId), + .limitBefore = limit, + .limitAfter = limit + }) | rpl::map([](const MessagesResult &result) { + auto data = PinnedAroundId(); + data.fullCount = result.count; + data.skippedBefore = result.skippedBefore; + data.skippedAfter = result.skippedAfter; + data.ids = result.messageIds | ranges::view::transform( + [](MessagePosition position) { return position.fullId.msg; } + ) | ranges::to_vector; + return data; + }); +} + MessagePosition PinnedMessages::position(MsgId id) const { return MessagePosition{ .fullId = FullMsgId(_channelId, id), @@ -37,8 +56,7 @@ void PinnedMessages::add(MsgId messageId) { void PinnedMessages::add( std::vector &&ids, - MsgId from, - MsgId till, + MsgRange range, std::optional count) { auto positions = ids | ranges::view::transform([&](MsgId id) { return position(id); @@ -47,16 +65,14 @@ void PinnedMessages::add( _list.addSlice( std::move(positions), MessagesRange{ - .from = from ? position(from) : MinMessagePosition, - .till = position(till) + .from = range.from ? position(range.from) : MinMessagePosition, + .till = position(range.till) }, count); } void PinnedMessages::remove(MsgId messageId) { - _list.removeOne(MessagePosition{ - .fullId = FullMsgId(0, messageId), - }); + _list.removeOne(position(messageId)); } void PinnedMessages::setTopId(MsgId messageId) { @@ -70,19 +86,15 @@ void PinnedMessages::setTopId(MsgId messageId) { break; } } - const auto position = MessagePosition{ - .fullId = FullMsgId(0, messageId), - }; + const auto wrapped = position(messageId); _list.addSlice( - { position }, - { .from = position, .till = MaxMessagePosition }, + { wrapped }, + { .from = wrapped, .till = MaxMessagePosition }, std::nullopt); } void PinnedMessages::clearLessThanId(MsgId messageId) { - _list.removeLessThan(MessagePosition{ - .fullId = FullMsgId(0, messageId), - }); + _list.removeLessThan(position(messageId)); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_pinned_messages.h b/Telegram/SourceFiles/data/data_pinned_messages.h index 7f825a343..33cf120e5 100644 --- a/Telegram/SourceFiles/data/data_pinned_messages.h +++ b/Telegram/SourceFiles/data/data_pinned_messages.h @@ -8,21 +8,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_messages.h" +#include "base/weak_ptr.h" namespace Data { -class PinnedMessages final { +struct PinnedAroundId { + std::vector ids; + std::optional skippedBefore; + std::optional skippedAfter; + std::optional fullCount; +}; + +class PinnedMessages final : public base::has_weak_ptr { public: explicit PinnedMessages(ChannelId channelId); [[nodiscard]] bool empty() const; [[nodiscard]] MsgId topId() const; + [[nodiscard]] rpl::producer viewer( + MsgId aroundId, + int limit) const; void add(MsgId messageId); void add( std::vector &&ids, - MsgId from, - MsgId till, + MsgRange range, std::optional count); void remove(MsgId messageId); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 814252ba2..84d8d5a76 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1161,7 +1161,7 @@ void History::addEdgesToSharedMedia() { {}, { from, till })); } - peer->addPinnedSlice({}, from, till); + peer->addPinnedSlice({}, { from, till }, std::nullopt); } void History::addOlderSlice(const QVector &slice) { @@ -1358,7 +1358,7 @@ void History::addToSharedMedia( { from, till })); } } - peer->addPinnedSlice(std::move(pinned), from, till); + peer->addPinnedSlice(std::move(pinned), { from, till }, std::nullopt); } void History::calculateFirstUnreadMessage() { @@ -1785,16 +1785,19 @@ TimeId History::adjustedChatListTimeId() const { } void History::countScrollState(int top) { - countScrollTopItem(top); - if (scrollTopItem) { - scrollTopOffset = (top - scrollTopItem->block()->y() - scrollTopItem->y()); - } + std::tie(scrollTopItem, scrollTopOffset) = findItemAndOffset(top); } -void History::countScrollTopItem(int top) { +auto History::findItemAndOffset(int top) const -> std::pair { + if (const auto element = findScrollTopItem(top)) { + return { element, (top - element->block()->y() - element->y()) }; + } + return {}; +} + +auto History::findScrollTopItem(int top) const -> Element* { if (isEmpty()) { - forgetScrollState(); - return; + return nullptr; } auto itemIndex = 0; @@ -1813,8 +1816,7 @@ void History::countScrollTopItem(int top) { const auto view = block->messages[itemIndex].get(); itemTop = block->y() + view->y(); if (itemTop <= top) { - scrollTopItem = view; - return; + return view; } } if (--blockIndex >= 0) { @@ -1824,27 +1826,24 @@ void History::countScrollTopItem(int top) { } } while (true); - scrollTopItem = blocks.front()->messages.front().get(); - } else { - // go forward through history while we don't find the last item that starts above - for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { - const auto &block = blocks[blockIndex]; - for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { - itemTop = block->y() + block->messages[itemIndex]->y(); - if (itemTop > top) { - Assert(itemIndex > 0 || blockIndex > 0); - if (itemIndex > 0) { - scrollTopItem = block->messages[itemIndex - 1].get(); - } else { - scrollTopItem = blocks[blockIndex - 1]->messages.back().get(); - } - return; - } - } - itemIndex = 0; - } - scrollTopItem = blocks.back()->messages.back().get(); + return blocks.front()->messages.front().get(); } + // go forward through history while we don't find the last item that starts above + for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { + const auto &block = blocks[blockIndex]; + for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { + itemTop = block->y() + block->messages[itemIndex]->y(); + if (itemTop > top) { + Assert(itemIndex > 0 || blockIndex > 0); + if (itemIndex > 0) { + return block->messages[itemIndex - 1].get(); + } + return blocks[blockIndex - 1]->messages.back().get(); + } + } + itemIndex = 0; + } + return blocks.back()->messages.back().get(); } void History::getNextScrollTopItem(HistoryBlock *block, int32 i) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 44718865d..30b09844b 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -366,6 +366,8 @@ public: // of the displayed window relative to the history start coordinate void countScrollState(int top); + [[nodiscard]] std::pair findItemAndOffset(int top) const; + MsgId nextNonHistoryEntryId(); bool folderKnown() const override; @@ -422,7 +424,7 @@ private: void getNextScrollTopItem(HistoryBlock *block, int32 i); // helper method for countScrollState(int top) - void countScrollTopItem(int top); + [[nodiscard]] Element *findScrollTopItem(int top) const; // this method just removes a block from the blocks list // when the last item from this block was detached and diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 4700db368..288a23f89 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -88,6 +88,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_contact_status.h" +#include "history/view/history_view_pinned_tracker.h" #include "base/qthelp_regex.h" #include "ui/widgets/popup_menu.h" #include "ui/text_options.h" @@ -1697,7 +1698,8 @@ void HistoryWidget::showHistory( _history->showAtMsgId = _showAtMsgId; destroyUnreadBarOnClose(); - destroyPinnedBar(); + showPinnedMessage(FullMsgId()); + _pinnedTracker = nullptr; _membersDropdown.destroy(); _scrollToAnimation.stop(); @@ -1811,6 +1813,7 @@ void HistoryWidget::showHistory( _updateHistoryItems.stop(); + setupPinnedTracker(); pinnedMsgVisibilityUpdated(); if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) @@ -2732,6 +2735,7 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { void HistoryWidget::onScroll() { preloadHistoryIfNeeded(); visibleAreaUpdated(); + updatePinnedViewer(); if (!_synteticScrollEvent) { _lastUserScrolled = crl::now(); } @@ -3211,6 +3215,7 @@ void HistoryWidget::doneShow() { handlePendingHistoryUpdate(); } preloadHistoryIfNeeded(); + updatePinnedViewer(); checkHistoryActivation(); App::wnd()->setInnerFocus(); } @@ -5226,14 +5231,66 @@ void HistoryWidget::updatePinnedBar(bool force) { } } +void HistoryWidget::updatePinnedViewer() { + if (_firstLoadRequest + || _delayedShowAtRequest + || _scroll->isHidden() + || !_history + || !_historyInited) { + return; + } + const auto [item, offset] = [&] { + auto visibleTop = _scroll->scrollTop(); + if (_migrated + && _history->loadedAtBottom() + && _migrated->loadedAtTop()) { + visibleTop -= _migrated->height(); + } + auto [item, offset] = _history->findItemAndOffset(visibleTop); + while (item && !IsServerMsgId(item->data()->id)) { + offset -= item->height(); + item = item->nextInBlocks(); + } + return std::pair(item, offset); + }(); + const auto last = _history->peer->topPinnedMessageId(); + const auto lessThanId = item + ? (item->data()->id + (offset > 0 ? 1 : 0)) + : (last + 1); + _pinnedTracker->trackAround(lessThanId); +} + +void HistoryWidget::setupPinnedTracker() { + Expects(_history != nullptr); + + _pinnedTracker = std::make_unique(_history); + _pinnedTracker->shownMessageId( + ) | rpl::start_with_next([=](MsgId messageId) { + showPinnedMessage({ peerToChannel(_peer->id), messageId }); + }, _list->lifetime()); +} + +void HistoryWidget::showPinnedMessage(FullMsgId id) { + if (_pinnedId == id) { + return; + } + _pinnedId = id; + if (pinnedMsgVisibilityUpdated()) { + updateHistoryGeometry(); + updateControlsVisibility(); + updateControlsGeometry(); + this->update(); + } +} + bool HistoryWidget::pinnedMsgVisibilityUpdated() { auto result = false; - auto pinnedId = _peer->topPinnedMessageId(); + auto pinnedId = _pinnedId; if (pinnedId && !_peer->canPinMessages()) { const auto hiddenId = session().settings().hiddenPinnedMessageId( _peer->id); - if (hiddenId == pinnedId) { - pinnedId = 0; + if (hiddenId == pinnedId.msg) { + pinnedId = FullMsgId(); } else if (hiddenId) { session().settings().setHiddenPinnedMessageId(_peer->id, 0); session().saveSettings(); @@ -5241,7 +5298,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { } if (pinnedId) { if (!_pinnedBar) { - _pinnedBar = std::make_unique(pinnedId, this); + _pinnedBar = std::make_unique(pinnedId.msg, this); if (_a_show.animating()) { _pinnedBar->cancel->hide(); _pinnedBar->shadow->hide(); @@ -5261,8 +5318,8 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { if (!barTop || _scroll->scrollTop() != *barTop) { synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); } - } else if (_pinnedBar->msgId != pinnedId) { - _pinnedBar->msgId = pinnedId; + } else if (_pinnedBar->msgId != pinnedId.msg) { + _pinnedBar->msgId = pinnedId.msg; _pinnedBar->msg = nullptr; _pinnedBar->text.clear(); updatePinnedBar(); @@ -5565,7 +5622,7 @@ void HistoryWidget::UnpinMessage(not_null peer, MsgId msgId) { } void HistoryWidget::hidePinnedMessage() { - const auto pinnedId = _peer ? _peer->topPinnedMessageId() : MsgId(0); + const auto pinnedId = _pinnedId; if (!pinnedId) { if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); @@ -5575,11 +5632,9 @@ void HistoryWidget::hidePinnedMessage() { } if (_peer->canPinMessages()) { - unpinMessage(FullMsgId( - _peer->isChannel() ? peerToChannel(_peer->id) : NoChannel, - pinnedId)); + unpinMessage(pinnedId); } else { - session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId); + session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId.msg); session().saveSettings(); if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); @@ -6389,7 +6444,9 @@ void HistoryWidget::drawPinnedBar(Painter &p) { p.setPen(st::historyReplyNameFg); p.setFont(st::msgServiceNameFont); const auto poll = media ? media->poll() : nullptr; - const auto pinnedHeader = !poll + const auto pinnedHeader = (_pinnedBar->msgId < _peer->topPinnedMessageId()) + ? tr::lng_pinned_previous(tr::now) + : !poll ? tr::lng_pinned_message(tr::now) : poll->quiz() ? tr::lng_pinned_quiz(tr::now) diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 12f943212..e23a4de00 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -93,6 +93,7 @@ namespace HistoryView { class TopBarWidget; class ContactStatus; class Element; +class PinnedTracker; } // namespace HistoryView class DragArea; @@ -491,9 +492,12 @@ private: void updateReplyEditTexts(bool force = false); void updateReplyEditText(not_null item); + void showPinnedMessage(FullMsgId id); void updatePinnedBar(bool force = false); bool pinnedMsgVisibilityUpdated(); void destroyPinnedBar(); + void updatePinnedViewer(); + void setupPinnedTracker(); void sendInlineResult( not_null result, @@ -610,7 +614,9 @@ private: object_ptr _fieldBarCancel; + FullMsgId _pinnedId; std::unique_ptr _pinnedBar; + std::unique_ptr _pinnedTracker; mtpRequestId _saveEditMsgRequestId = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp new file mode 100644 index 000000000..ea6f076d7 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp @@ -0,0 +1,247 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_pinned_tracker.h" + +#include "data/data_changes.h" +#include "data/data_pinned_messages.h" +#include "data/data_peer.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "data/data_histories.h" +#include "main/main_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "apiwrap.h" + +namespace HistoryView { +namespace { + +constexpr auto kLoadedLimit = 4; +constexpr auto kPerPage = 40; + +} // namespace + +PinnedTracker::PinnedTracker(not_null history) : _history(history) { + _history->session().changes().peerFlagsValue( + _history->peer, + Data::PeerUpdate::Flag::PinnedMessage + ) | rpl::start_with_next([=] { + refreshData(); + }, _lifetime); +} + +PinnedTracker::~PinnedTracker() { + _history->owner().histories().cancelRequest(_beforeRequestId); + _history->owner().histories().cancelRequest(_afterRequestId); +} + +rpl::producer PinnedTracker::shownMessageId() const { + return _current.value(); +} + +void PinnedTracker::refreshData() { + const auto now = _history->peer->currentPinnedMessages(); + if (!now) { + _dataLifetime.destroy(); + _current = MsgId(0); + } else if (_data.get() != now) { + _dataLifetime.destroy(); + _data = now; + if (_aroundId) { + setupViewer(now); + } + } +} + +void PinnedTracker::trackAround(MsgId messageId) { + if (_aroundId == messageId) { + return; + } + _dataLifetime.destroy(); + _aroundId = messageId; + if (!_aroundId) { + _current = MsgId(0); + } else if (const auto now = _data.get()) { + setupViewer(now); + } +} + +void PinnedTracker::setupViewer(not_null data) { + data->viewer( + _aroundId, + kLoadedLimit + 2 + ) | rpl::start_with_next([=](const Data::PinnedAroundId &snapshot) { + const auto i = ranges::lower_bound(snapshot.ids, _aroundId); + const auto empty = snapshot.ids.empty(); + const auto before = (i - begin(snapshot.ids)); + const auto after = (end(snapshot.ids) - i); + if (before < kLoadedLimit && !snapshot.skippedBefore) { + load( + Data::LoadDirection::Before, + empty ? _aroundId : snapshot.ids.front()); + } + if (after < kLoadedLimit && !snapshot.skippedAfter) { + load( + Data::LoadDirection::After, + empty ? _aroundId : snapshot.ids.back()); + } + if (i != begin(snapshot.ids)) { + _current = *(i - 1); + } else if (snapshot.skippedBefore == 0) { + _current = 0; + } + }, _dataLifetime); +} + +void PinnedTracker::load(Data::LoadDirection direction, MsgId id) { + const auto requestId = (direction == Data::LoadDirection::Before) + ? &_beforeRequestId + : &_afterRequestId; + const auto aroundId = (direction == Data::LoadDirection::Before) + ? &_beforeId + : &_afterId; + if (*requestId) { + if (*aroundId == id) { + return; + } + _history->owner().histories().cancelRequest(*requestId); + } + *aroundId = id; + const auto send = [=](Fn finish) { + const auto offsetId = [&] { + switch (direction) { + case Data::LoadDirection::Before: return id; + case Data::LoadDirection::After: return id + 1; + } + Unexpected("Direction in PinnedTracker::load"); + }(); + const auto addOffset = [&] { + switch (direction) { + case Data::LoadDirection::Before: return 0; + case Data::LoadDirection::After: return -kPerPage; + } + Unexpected("Direction in PinnedTracker::load"); + }(); + return _history->session().api().request(MTPmessages_Search( + MTP_flags(0), + _history->peer->input, + MTP_string(QString()), + MTP_inputPeerEmpty(), + MTPint(), // top_msg_id + MTP_inputMessagesFilterPinned(), + MTP_int(0), + MTP_int(0), + MTP_int(offsetId), + MTP_int(addOffset), + MTP_int(kPerPage), + MTP_int(0), // max_id + MTP_int(0), // min_id + MTP_int(0) // hash + )).done([=](const MTPmessages_Messages &result) { + *aroundId = 0; + *requestId = 0; + finish(); + + apply(direction, id, result); + }).fail([=](const RPCError &error) { + *aroundId = 0; + *requestId = 0; + finish(); + }).send(); + }; + _beforeRequestId = _history->owner().histories().sendRequest( + _history, + Data::Histories::RequestType::History, + send); +} + +void PinnedTracker::apply( + Data::LoadDirection direction, + MsgId aroundId, + const MTPmessages_Messages &result) { + auto noSkipRange = MsgRange{ aroundId, aroundId }; + auto fullCount = std::optional(); + auto messages = [&] { + switch (result.type()) { + case mtpc_messages_messages: { + auto &d = result.c_messages_messages(); + _history->owner().processUsers(d.vusers()); + _history->owner().processChats(d.vchats()); + fullCount = d.vmessages().v.size(); + return &d.vmessages().v; + } break; + + case mtpc_messages_messagesSlice: { + auto &d = result.c_messages_messagesSlice(); + _history->owner().processUsers(d.vusers()); + _history->owner().processChats(d.vchats()); + fullCount = d.vcount().v; + return &d.vmessages().v; + } break; + + case mtpc_messages_channelMessages: { + auto &d = result.c_messages_channelMessages(); + if (auto channel = _history->peer->asChannel()) { + channel->ptsReceived(d.vpts().v); + } else { + LOG(("API Error: received messages.channelMessages when " + "no channel was passed! (PinnedTracker::apply)")); + } + _history->owner().processUsers(d.vusers()); + _history->owner().processChats(d.vchats()); + fullCount = d.vcount().v; + return &d.vmessages().v; + } break; + + case mtpc_messages_messagesNotModified: { + LOG(("API Error: received messages.messagesNotModified! " + "(PinnedTracker::apply)")); + return (const QVector*)nullptr; + } break; + } + Unexpected("messages.Messages type in PinnedTracker::apply."); + }(); + + if (!messages) { + return; + } + + const auto addType = NewMessageType::Existing; + auto list = std::vector(); + list.reserve(messages->size()); + for (const auto &message : *messages) { + const auto item = _history->owner().addNewMessage( + message, + MTPDmessage_ClientFlags(), + addType); + if (item) { + const auto itemId = item->id; + if (item->isPinned()) { + list.push_back(itemId); + } + accumulate_min(noSkipRange.from, itemId); + accumulate_max(noSkipRange.till, itemId); + } + } + if (aroundId && list.empty()) { + noSkipRange = [&]() -> MsgRange { + switch (direction) { + case Data::LoadDirection::Before: // All old loaded. + return { 0, noSkipRange.till }; + case Data::LoadDirection::Around: // All loaded. + return { 0, ServerMaxMsgId }; + case Data::LoadDirection::After: // All new loaded. + return { noSkipRange.from, ServerMaxMsgId }; + } + Unexpected("Direction in PinnedTracker::apply."); + }(); + } + _history->peer->addPinnedSlice(std::move(list), noSkipRange, fullCount); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h new file mode 100644 index 000000000..e34df6af4 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h @@ -0,0 +1,52 @@ +/* +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 + +class History; + +namespace Data { +class PinnedMessages; +enum class LoadDirection : char; +} // namespace Data + +namespace HistoryView { + +class PinnedTracker final { +public: + explicit PinnedTracker(not_null history); + ~PinnedTracker(); + + [[nodiscard]] rpl::producer shownMessageId() const; + void trackAround(MsgId messageId); + +private: + void refreshData(); + void setupViewer(not_null data); + void load(Data::LoadDirection direction, MsgId id); + void apply( + Data::LoadDirection direction, + MsgId aroundId, + const MTPmessages_Messages &result); + + const not_null _history; + + base::weak_ptr _data; + rpl::variable _current = MsgId(); + rpl::lifetime _dataLifetime; + + MsgId _aroundId = 0; + MsgId _beforeId = 0; + MsgId _afterId = 0; + MsgId _beforeRequestId = 0; + MsgId _afterRequestId = 0; + + rpl::lifetime _lifetime; + +}; + +} // namespace HistoryView