diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 88778fc72..6426c6446 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -539,6 +539,8 @@ PRIVATE data/data_groups.h data/data_histories.cpp data/data_histories.h + data/data_history_messages.cpp + data/data_history_messages.h data/data_lastseen_status.h data/data_location.cpp data/data_location.h diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index aeed9e1b3..32e97c77f 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_chat_filters.h" #include "data/data_histories.h" +#include "data/data_history_messages.h" #include "core/core_cloud_password.h" #include "core/application.h" #include "base/unixtime.h" @@ -3078,6 +3079,46 @@ void ApiWrap::resolveJumpToHistoryDate( } } +void ApiWrap::requestHistory( + not_null history, + MsgId messageId, + SliceType slice) { + const auto peer = history->peer; + const auto key = HistoryRequest{ + peer, + messageId, + slice, + }; + if (_historyRequests.contains(key)) { + return; + } + + const auto prepared = Api::PrepareHistoryRequest(peer, messageId, slice); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::History; + histories.sendRequest(history, requestType, [=](Fn finish) { + return request( + std::move(prepared) + ).done([=](const Api::HistoryRequestResult &result) { + _historyRequests.remove(key); + auto parsed = Api::ParseHistoryResult( + peer, + messageId, + slice, + result); + history->messages().addSlice( + std::move(parsed.messageIds), + parsed.noSkipRange, + parsed.fullCount); + finish(); + }).fail([=] { + _historyRequests.remove(key); + finish(); + }).send(); + }); + _historyRequests.emplace(key); +} + void ApiWrap::requestSharedMedia( not_null peer, MsgId topicRootId, diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index b1c844702..9710ee1bd 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -274,6 +274,10 @@ public: Fn, MsgId)> callback); using SliceType = Data::LoadDirection; + void requestHistory( + not_null history, + MsgId messageId, + SliceType slice); void requestSharedMedia( not_null peer, MsgId topicRootId, @@ -511,7 +515,8 @@ private: not_null peer, bool justClear, bool revoke); - void applyAffectedMessages(const MTPmessages_AffectedMessages &result) const; + void applyAffectedMessages( + const MTPmessages_AffectedMessages &result) const; void deleteAllFromParticipantSend( not_null channel, @@ -645,6 +650,17 @@ private: }; base::flat_set _sharedMediaRequests; + struct HistoryRequest { + not_null peer; + MsgId aroundId = 0; + SliceType sliceType = {}; + + friend inline auto operator<=>( + const HistoryRequest&, + const HistoryRequest&) = default; + }; + base::flat_set _historyRequests; + std::unique_ptr _dialogsLoadState; TimeId _dialogsLoadTill = 0; rpl::variable _dialogsLoadMayBlockByDate = false; diff --git a/Telegram/SourceFiles/data/data_history_messages.cpp b/Telegram/SourceFiles/data/data_history_messages.cpp new file mode 100644 index 000000000..7c29daf9e --- /dev/null +++ b/Telegram/SourceFiles/data/data_history_messages.cpp @@ -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_history_messages.h" + +#include "apiwrap.h" +#include "data/data_chat.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_sparse_ids.h" +#include "history/history.h" +#include "main/main_session.h" + +namespace Data { + +void HistoryMessages::addNew(MsgId messageId) { + _chat.addNew(messageId); +} + +void HistoryMessages::addExisting(MsgId messageId, MsgRange noSkipRange) { + _chat.addExisting(messageId, noSkipRange); +} + +void HistoryMessages::addSlice( + std::vector &&messageIds, + MsgRange noSkipRange, + std::optional count) { + _chat.addSlice(std::move(messageIds), noSkipRange, count); +} + +void HistoryMessages::removeOne(MsgId messageId) { + _chat.removeOne(messageId); + _oneRemoved.fire_copy(messageId); +} + +void HistoryMessages::removeAll() { + _chat.removeAll(); + _allRemoved.fire({}); +} + +void HistoryMessages::invalidateBottom() { + _chat.invalidateBottom(); + _bottomInvalidated.fire({}); +} + +Storage::SparseIdsListResult HistoryMessages::snapshot( + const Storage::SparseIdsListQuery &query) const { + return _chat.snapshot(query); +} + +auto HistoryMessages::sliceUpdated() const +-> rpl::producer { + return _chat.sliceUpdated(); +} + +rpl::producer HistoryMessages::oneRemoved() const { + return _oneRemoved.events(); +} + +rpl::producer<> HistoryMessages::allRemoved() const { + return _allRemoved.events(); +} + +rpl::producer<> HistoryMessages::bottomInvalidated() const { + return _bottomInvalidated.events(); +} + +rpl::producer HistoryViewer( + not_null history, + MsgId aroundId, + int limitBefore, + int limitAfter) { + Expects(IsServerMsgId(aroundId) || (aroundId == 0)); + Expects((aroundId != 0) || (limitBefore == 0 && limitAfter == 0)); + + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + const auto messages = &history->messages(); + + auto builder = lifetime.make_state( + aroundId, + limitBefore, + limitAfter); + using RequestAroundInfo = SparseIdsSliceBuilder::AroundData; + builder->insufficientAround( + ) | rpl::start_with_next([=](const RequestAroundInfo &info) { + history->session().api().requestHistory( + history, + info.aroundId, + info.direction); + }, lifetime); + + auto pushNextSnapshot = [=] { + consumer.put_next(builder->snapshot()); + }; + + using SliceUpdate = Storage::SparseIdsSliceUpdate; + messages->sliceUpdated( + ) | rpl::filter([=](const SliceUpdate &update) { + return builder->applyUpdate(update); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + messages->oneRemoved( + ) | rpl::filter([=](MsgId messageId) { + return builder->removeOne(messageId); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + messages->allRemoved( + ) | rpl::filter([=] { + return builder->removeAll(); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + messages->bottomInvalidated( + ) | rpl::filter([=] { + return builder->invalidateBottom(); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + const auto snapshot = messages->snapshot({ + aroundId, + limitBefore, + limitAfter, + }); + if (snapshot.count || !snapshot.messageIds.empty()) { + if (builder->applyInitial(snapshot)) { + pushNextSnapshot(); + } + } + builder->checkInsufficient(); + + return lifetime; + }; +} + +rpl::producer HistoryMergedViewer( + not_null history, + /*Universal*/MsgId universalAroundId, + int limitBefore, + int limitAfter) { + const auto migrateFrom = history->peer->migrateFrom(); + auto createSimpleViewer = [=]( + PeerId peerId, + MsgId topicRootId, + SparseIdsSlice::Key simpleKey, + int limitBefore, + int limitAfter) { + const auto chosen = (history->peer->id == peerId) + ? history + : history->owner().history(peerId); + return HistoryViewer(history, simpleKey, limitBefore, limitAfter); + }; + const auto peerId = history->peer->id; + const auto topicRootId = MsgId(); + const auto migratedPeerId = migrateFrom ? migrateFrom->id : peerId; + using Key = SparseIdsMergedSlice::Key; + return SparseIdsMergedSlice::CreateViewer( + Key(peerId, topicRootId, migratedPeerId, universalAroundId), + limitBefore, + limitAfter, + std::move(createSimpleViewer)); +} + +rpl::producer HistoryMessagesViewer( + not_null history, + MessagePosition aroundId, + int limitBefore, + int limitAfter) { + const auto computeUnreadAroundId = [&] { + if (const auto migrated = history->migrateFrom()) { + if (const auto around = migrated->loadAroundId()) { + return MsgId(around - ServerMaxMsgId); + } + } + if (const auto around = history->loadAroundId()) { + return around; + } + return MsgId(ServerMaxMsgId - 1); + }; + const auto messageId = (aroundId.fullId.msg == ShowAtUnreadMsgId) + ? computeUnreadAroundId() + : (aroundId.fullId.msg == ShowAtTheEndMsgId) + ? (ServerMaxMsgId - 1) + : (aroundId.fullId.peer == history->peer->id) + ? aroundId.fullId.msg + : (aroundId.fullId.msg - ServerMaxMsgId); + return HistoryMergedViewer( + history, + messageId, + limitBefore, + limitAfter + ) | rpl::map([=](SparseIdsMergedSlice &&slice) { + auto result = Data::MessagesSlice(); + result.fullCount = slice.fullCount(); + result.skippedAfter = slice.skippedAfter(); + result.skippedBefore = slice.skippedBefore(); + const auto count = slice.size(); + result.ids.reserve(count); + if (const auto msgId = slice.nearest(messageId)) { + result.nearestToAround = *msgId; + } + for (auto i = 0; i != count; ++i) { + result.ids.push_back(slice[i]); + } + return result; + }); +} + +} // namespace Data \ No newline at end of file diff --git a/Telegram/SourceFiles/data/data_history_messages.h b/Telegram/SourceFiles/data/data_history_messages.h new file mode 100644 index 000000000..2b68462fb --- /dev/null +++ b/Telegram/SourceFiles/data/data_history_messages.h @@ -0,0 +1,67 @@ +/* +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 "storage/storage_sparse_ids_list.h" + +class History; +class SparseIdsSlice; +class SparseIdsMergedSlice; + +namespace Data { + +struct MessagesSlice; +struct MessagePosition; + +class HistoryMessages final { +public: + void addNew(MsgId messageId); + void addExisting(MsgId messageId, MsgRange noSkipRange); + void addSlice( + std::vector &&messageIds, + MsgRange noSkipRange, + std::optional count); + void removeOne(MsgId messageId); + void removeAll(); + void invalidateBottom(); + + [[nodiscard]] Storage::SparseIdsListResult snapshot( + const Storage::SparseIdsListQuery &query) const; + [[nodiscard]] auto sliceUpdated() const + -> rpl::producer; + [[nodiscard]] rpl::producer oneRemoved() const; + [[nodiscard]] rpl::producer<> allRemoved() const; + [[nodiscard]] rpl::producer<> bottomInvalidated() const; + +private: + Storage::SparseIdsList _chat; + rpl::event_stream _oneRemoved; + rpl::event_stream<> _allRemoved; + rpl::event_stream<> _bottomInvalidated; + +}; + +[[nodiscard]] rpl::producer HistoryViewer( + not_null history, + MsgId aroundId, + int limitBefore, + int limitAfter); + +[[nodiscard]] rpl::producer HistoryMergedViewer( + not_null history, + /*Universal*/MsgId universalAroundId, + int limitBefore, + int limitAfter); + +[[nodiscard]] rpl::producer HistoryMessagesViewer( + not_null history, + MessagePosition aroundId, + int limitBefore, + int limitAfter); + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index a33dee56b..42f56c1ae 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -78,10 +78,6 @@ private: [[nodiscard]] Histories &histories(); void subscribeToUpdates(); - [[nodiscard]] rpl::producer sourceFromServer( - MessagePosition aroundId, - int limitBefore, - int limitAfter); void appendClientSideMessages(MessagesSlice &slice); [[nodiscard]] bool buildFromData(not_null viewer); diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 43c5efb66..4e33a9eaa 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -21,6 +21,7 @@ namespace { constexpr auto kSharedMediaLimit = 100; constexpr auto kFirstSharedMediaLimit = 0; +constexpr auto kHistoryLimit = 50; constexpr auto kDefaultSearchTimeoutMs = crl::time(200); } // namespace @@ -199,6 +200,60 @@ SearchResult ParseSearchResult( return result; } +HistoryRequest PrepareHistoryRequest( + not_null peer, + MsgId messageId, + Data::LoadDirection direction) { + const auto minId = 0; + const auto maxId = 0; + const auto limit = kHistoryLimit; + const auto offsetId = [&] { + switch (direction) { + case Data::LoadDirection::Before: + case Data::LoadDirection::Around: return messageId; + case Data::LoadDirection::After: return messageId + 1; + } + Unexpected("Direction in PrepareSearchRequest"); + }(); + const auto addOffset = [&] { + switch (direction) { + case Data::LoadDirection::Before: return 0; + case Data::LoadDirection::Around: return -limit / 2; + case Data::LoadDirection::After: return -limit; + } + Unexpected("Direction in PrepareSearchRequest"); + }(); + const auto hash = uint64(0); + const auto offsetDate = int32(0); + + const auto mtpOffsetId = int(std::clamp( + offsetId.bare, + int64(0), + int64(0x3FFFFFFF))); + return MTPmessages_GetHistory( + peer->input, + MTP_int(mtpOffsetId), + MTP_int(offsetDate), + MTP_int(addOffset), + MTP_int(limit), + MTP_int(maxId), + MTP_int(minId), + MTP_long(hash)); +} + +HistoryResult ParseHistoryResult( + not_null peer, + MsgId messageId, + Data::LoadDirection direction, + const HistoryRequestResult &data) { + return ParseSearchResult( + peer, + Storage::SharedMediaType::kCount, + messageId, + direction, + data); +} + SearchController::CacheEntry::CacheEntry( not_null session, const Query &query) diff --git a/Telegram/SourceFiles/data/data_search_controller.h b/Telegram/SourceFiles/data/data_search_controller.h index a938df6ef..26b930b9b 100644 --- a/Telegram/SourceFiles/data/data_search_controller.h +++ b/Telegram/SourceFiles/data/data_search_controller.h @@ -32,7 +32,11 @@ struct SearchResult { using SearchRequest = MTPmessages_Search; using SearchRequestResult = MTPmessages_Messages; -std::optional PrepareSearchRequest( +using HistoryResult = SearchResult; +using HistoryRequest = MTPmessages_GetHistory; +using HistoryRequestResult = MTPmessages_Messages; + +[[nodiscard]] std::optional PrepareSearchRequest( not_null peer, MsgId topicRootId, Storage::SharedMediaType type, @@ -40,13 +44,24 @@ std::optional PrepareSearchRequest( MsgId messageId, Data::LoadDirection direction); -SearchResult ParseSearchResult( +[[nodiscard]] SearchResult ParseSearchResult( not_null peer, Storage::SharedMediaType type, MsgId messageId, Data::LoadDirection direction, const SearchRequestResult &data); +[[nodiscard]] HistoryRequest PrepareHistoryRequest( + not_null peer, + MsgId messageId, + Data::LoadDirection direction); + +[[nodiscard]] HistoryResult ParseHistoryResult( + not_null peer, + MsgId messageId, + Data::LoadDirection direction, + const HistoryRequestResult &data); + class SearchController final { public: using IdsList = Storage::SparseIdsList; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index dca53737d..3643ed726 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_document.h" #include "data/data_histories.h" +#include "data/data_history_messages.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "api/api_chat_participants.h" @@ -483,7 +484,7 @@ not_null History::insertItem( std::unique_ptr item) { Expects(item != nullptr); - const auto &[i, ok] = _messages.insert(std::move(item)); + const auto &[i, ok] = _items.insert(std::move(item)); const auto result = i->get(); owner().registerMessage(result); @@ -500,6 +501,9 @@ void History::destroyMessage(not_null item) { // All this must be done for all items manually in History::clear()! item->destroyHistoryEntry(); if (item->isRegular()) { + if (const auto messages = _messages.get()) { + messages->removeOne(item->id); + } if (const auto types = item->sharedMediaTypes()) { session().storage().remove(Storage::SharedMediaRemoveOne( peerId, @@ -524,11 +528,11 @@ void History::destroyMessage(not_null item) { Core::App().notifications().clearFromItem(item); auto hack = std::unique_ptr(item.get()); - const auto i = _messages.find(hack); + const auto i = _items.find(hack); hack.release(); - Assert(i != end(_messages)); - _messages.erase(i); + Assert(i != end(_items)); + _items.erase(i); if (documentToCancel) { session().data().documentMessageRemoved(documentToCancel); @@ -537,8 +541,8 @@ void History::destroyMessage(not_null item) { void History::destroyMessagesByDates(TimeId minDate, TimeId maxDate) { auto toDestroy = std::vector>(); - toDestroy.reserve(_messages.size()); - for (const auto &message : _messages) { + toDestroy.reserve(_items.size()); + for (const auto &message : _items) { if (message->isRegular() && message->date() > minDate && message->date() < maxDate) { @@ -552,8 +556,8 @@ void History::destroyMessagesByDates(TimeId minDate, TimeId maxDate) { void History::destroyMessagesByTopic(MsgId topicRootId) { auto toDestroy = std::vector>(); - toDestroy.reserve(_messages.size()); - for (const auto &message : _messages) { + toDestroy.reserve(_items.size()); + for (const auto &message : _items) { if (message->topicRootId() == topicRootId) { toDestroy.push_back(message.get()); } @@ -575,7 +579,7 @@ void History::unpinMessagesFor(MsgId topicRootId) { topic->setHasPinnedMessages(false); }); } - for (const auto &item : _messages) { + for (const auto &item : _items) { if (item->isPinned()) { item->setIsPinned(false); } @@ -589,7 +593,7 @@ void History::unpinMessagesFor(MsgId topicRootId) { if (const auto topic = peer->forumTopicFor(topicRootId)) { topic->setHasPinnedMessages(false); } - for (const auto &item : _messages) { + for (const auto &item : _items) { if (item->isPinned() && item->topicRootId() == topicRootId) { item->setIsPinned(false); } @@ -781,9 +785,12 @@ not_null History::addNewToBack( addItemToBlock(item); if (!unread && item->isRegular()) { + const auto from = loadedAtTop() ? 0 : minMsgId(); + const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); + if (_messages) { + _messages->addExisting(item->id, { from, till }); + } if (const auto types = item->sharedMediaTypes()) { - auto from = loadedAtTop() ? 0 : minMsgId(); - auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); auto &storage = session().storage(); storage.add(Storage::SharedMediaAddExisting( peer->id, @@ -1190,6 +1197,7 @@ void History::mainViewRemoved( void History::newItemAdded(not_null item) { item->indexAsNewItem(); + item->addToMessagesIndex(); if (const auto from = item->from() ? item->from()->asUser() : nullptr) { if (from == item->author()) { _sendActionPainter.clear(from); @@ -2385,6 +2393,9 @@ void History::setNotLoadedAtBottom() { session().storage().invalidate( Storage::SharedMediaInvalidateBottom(peer->id)); + if (const auto messages = _messages.get()) { + messages->invalidateBottom(); + } } void History::clearSharedMedia() { @@ -3092,6 +3103,46 @@ MsgRange History::rangeForDifferenceRequest() const { return MsgRange(); } +Data::HistoryMessages &History::messages() { + if (!_messages) { + _messages = std::make_unique(); + + const auto from = loadedAtTop() ? 0 : minMsgId(); + const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); + auto list = std::vector(); + list.reserve(std::min( + int(_items.size()), + int(blocks.size()) * kNewBlockEachMessage)); + auto sort = false; + for (const auto &block : blocks) { + for (const auto &view : block->messages) { + const auto item = view->data(); + if (item->isRegular()) { + const auto id = item->id; + if (!list.empty() && list.back() >= id) { + sort = true; + } + } + } + } + if (sort) { + ranges::sort(list); + } + if (till) { + _messages->addSlice(std::move(list), { from, till }, {}); + } + } + return *_messages; +} + +const Data::HistoryMessages &History::messages() const { + return const_cast(this)->messages(); +} + +Data::HistoryMessages *History::maybeMessages() { + return _messages.get(); +} + HistoryItem *History::insertJoinedMessage() { const auto channel = peer->asChannel(); if (!channel @@ -3194,11 +3245,11 @@ void History::removeJoinedMessage() { void History::reactionsEnabledChanged(bool enabled) { if (!enabled) { - for (const auto &item : _messages) { + for (const auto &item : _items) { item->updateReactions(nullptr); } } else { - for (const auto &item : _messages) { + for (const auto &item : _items) { item->updateReactionsUnknown(); } } @@ -3372,6 +3423,9 @@ void History::clear(ClearType type) { } _loadedAtTop = _loadedAtBottom = _lastMessage.has_value(); clearSharedMedia(); + if (const auto messages = _messages.get()) { + messages->removeAll(); + } clearLastKeyboard(); } @@ -3388,8 +3442,8 @@ void History::clear(ClearType type) { void History::clearUpTill(MsgId availableMinId) { auto remove = std::vector>(); - remove.reserve(_messages.size()); - for (const auto &item : _messages) { + remove.reserve(_items.size()); + for (const auto &item : _items) { const auto itemId = item->id; if (!item->isRegular()) { continue; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index e00c23b8d..729b66045 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -26,12 +26,14 @@ class HistoryMainElementDelegateMixin; struct LanguageId; namespace Data { + struct Draft; class Session; class Folder; class ChatFilter; struct SponsoredFrom; class SponsoredMessages; +class HistoryMessages; enum class ForwardOptions { PreserveInfo, @@ -79,7 +81,7 @@ public: History(not_null owner, PeerId peerId); ~History(); - not_null owningHistory() override { + [[nodiscard]] not_null owningHistory() override { return this; } [[nodiscard]] Data::Thread *threadFor(MsgId topicRootId); @@ -93,23 +95,27 @@ public: void forumChanged(Data::Forum *old); [[nodiscard]] bool isForum() const; - not_null migrateToOrMe() const; - History *migrateFrom() const; - MsgRange rangeForDifferenceRequest() const; + [[nodiscard]] not_null migrateToOrMe() const; + [[nodiscard]] History *migrateFrom() const; + [[nodiscard]] MsgRange rangeForDifferenceRequest() const; - HistoryItem *joinedMessageInstance() const; + [[nodiscard]] Data::HistoryMessages &messages(); + [[nodiscard]] const Data::HistoryMessages &messages() const; + [[nodiscard]] Data::HistoryMessages *maybeMessages(); + + [[nodiscard]] HistoryItem *joinedMessageInstance() const; void checkLocalMessages(); void removeJoinedMessage(); void reactionsEnabledChanged(bool enabled); - bool isEmpty() const; - bool isDisplayedEmpty() const; - Element *findFirstNonEmpty() const; - Element *findFirstDisplayed() const; - Element *findLastNonEmpty() const; - Element *findLastDisplayed() const; - bool hasOrphanMediaGroupPart() const; + [[nodiscard]] bool isEmpty() const; + [[nodiscard]] bool isDisplayedEmpty() const; + [[nodiscard]] Element *findFirstNonEmpty() const; + [[nodiscard]] Element *findFirstDisplayed() const; + [[nodiscard]] Element *findLastNonEmpty() const; + [[nodiscard]] Element *findLastDisplayed() const; + [[nodiscard]] bool hasOrphanMediaGroupPart() const; [[nodiscard]] std::vector collectMessagesFromParticipantToDelete( not_null participant) const; @@ -590,7 +596,9 @@ private: std::optional _lastMessage; std::optional _lastServerMessage; base::flat_set> _clientSideMessages; - std::unordered_set> _messages; + std::unordered_set> _items; + + std::unique_ptr _messages; // This almost always is equal to _lastMessage. The only difference is // for a group that migrated to a supergroup. Then _lastMessage can diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index cdc57d00d..53ba1c67c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_game.h" +#include "data/data_history_messages.h" #include "data/data_user.h" #include "data/data_group_call.h" // Data::GroupCall::id(). #include "data/data_poll.h" // PollData::publicVotes. @@ -1791,6 +1792,7 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { setIsPinned(data.is_pinned()); contributeToSlowmode(data.vdate().v); addToSharedMediaIndex(); + addToMessagesIndex(); invalidateChatListEntry(); if (const auto period = data.vttl_period(); period && period->v > 0) { applyTTL(data.vdate().v + period->v); @@ -1815,6 +1817,7 @@ void HistoryItem::applySentMessage( contributeToSlowmode(data.vdate().v); if (!wasAlready) { addToSharedMediaIndex(); + addToMessagesIndex(); } invalidateChatListEntry(); if (const auto period = data.vttl_period(); period && period->v > 0) { @@ -2011,6 +2014,14 @@ void HistoryItem::removeFromSharedMediaIndex() { } } +void HistoryItem::addToMessagesIndex() { + if (isRegular()) { + if (const auto messages = _history->maybeMessages()) { + messages->addNew(id); + } + } +} + void HistoryItem::incrementReplyToTopCounter() { if (isRegular() && _history->peer->isMegagroup()) { _history->session().changes().messageUpdated( diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c8963b707..c7d3e59aa 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -359,6 +359,7 @@ public: void indexAsNewItem(); void addToSharedMediaIndex(); + void addToMessagesIndex(); void removeFromSharedMediaIndex(); struct NotificationTextOptions { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index c394af68b..caca93da1 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_chat_preview.h" #include "data/data_forum_topic.h" +#include "data/data_history_messages.h" #include "data/data_peer.h" #include "data/data_replies_list.h" #include "data/data_thread.h" @@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_list_widget.h" #include "history/history.h" #include "history/history_item.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" @@ -136,6 +138,7 @@ private: const not_null _dummyAction; const not_null _session; const not_null _thread; + const std::shared_ptr _replies; const not_null _history; const not_null _peer; const std::shared_ptr _theme; @@ -153,6 +156,7 @@ Item::Item(not_null parent, not_null thread) , _dummyAction(new QAction(parent)) , _session(&thread->session()) , _thread(thread) +, _replies(thread->asTopic() ? thread->asTopic()->replies() : nullptr) , _history(thread->owningHistory()) , _peer(thread->peer()) , _theme(Window::Theme::DefaultChatThemeOn(lifetime())) @@ -253,52 +257,13 @@ rpl::producer Item::listSource( Data::MessagePosition aroundId, int limitBefore, int limitAfter) { - if (const auto topic = _thread->asTopic()) { - return topic->replies()->source( + return _replies + ? _replies->source(aroundId, limitBefore, limitAfter) + : Data::HistoryMessagesViewer( + _thread->asHistory(), aroundId, limitBefore, - limitAfter - ) | rpl::before_next([=] { // after_next makes a copy of value. - //if (!_loaded) { - // _loaded = true; - // crl::on_main(this, [=] { - // updatePinnedVisibility(); - // }); - //} - }); - } - // #TODO - //const auto messageId = aroundId.fullId.msg - // ? aroundId.fullId.msg - // : (ServerMaxMsgId - 1); - - //return SharedMediaMergedViewer( - // &_thread->session(), - // SharedMediaMergedKey( - // SparseIdsMergedSlice::Key( - // _history->peer->id, - // _thread->topicRootId(), - // _migratedPeer ? _migratedPeer->id : 0, - // messageId), - // Storage::SharedMediaType::Pinned), - // limitBefore, - // limitAfter - //) | rpl::map([=](SparseIdsMergedSlice &&slice) { - // auto result = Data::MessagesSlice(); - // result.fullCount = slice.fullCount(); - // result.skippedAfter = slice.skippedAfter(); - // result.skippedBefore = slice.skippedBefore(); - // const auto count = slice.size(); - // result.ids.reserve(count); - // if (const auto msgId = slice.nearest(messageId)) { - // result.nearestToAround = *msgId; - // } - // for (auto i = 0; i != count; ++i) { - // result.ids.push_back(slice[i]); - // } - // return result; - //}); - return rpl::single(Data::MessagesSlice()); + limitAfter); } bool Item::listAllowsMultiSelect() { @@ -341,7 +306,44 @@ void Item::listMarkContentsRead( MessagesBarData Item::listMessagesBar( const std::vector> &elements) { - return {};// #TODO + if (elements.empty()) { + return {}; + } else if (!_replies && !_history->unreadCount()) { + return {}; + } + const auto repliesTill = _replies + ? _replies->computeInboxReadTillFull() + : MsgId(); + const auto migrated = _replies ? nullptr : _history->migrateFrom(); + const auto migratedTill = migrated ? migrated->inboxReadTillId() : 0; + const auto historyTill = _replies ? 0 : _history->inboxReadTillId(); + if (!_replies && !migratedTill && !historyTill) { + return {}; + } + + const auto hidden = _replies && (repliesTill < 2); + for (auto i = 0, count = int(elements.size()); i != count; ++i) { + const auto item = elements[i]->data(); + if (!item->isRegular() + || item->out() + || (_replies && !item->replyToId())) { + continue; + } + const auto inHistory = (item->history() == _history); + if ((_replies && item->id > repliesTill) + || (migratedTill && (inHistory || item->id > migratedTill)) + || (historyTill && inHistory && item->id > historyTill)) { + return { + .bar = { + .element = elements[i], + .hidden = hidden, + .focus = true, + }, + .text = tr::lng_unread_bar_some(), + }; + } + } + return {}; } void Item::listContentRefreshed() { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 538606339..bdcf93a03 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -939,7 +939,9 @@ void ListWidget::restoreScrollState() { } _scrollInited = true; _scrollTopState.item = _bar.element->data()->position(); - _scrollTopState.shift = st::lineWidth + st::historyUnreadBarMargin; + _scrollTopState.shift = st::lineWidth + + st::historyUnreadBarMargin + + _bar.element->displayedDateHeight(); } const auto index = findNearestItem(_scrollTopState.item); if (index >= 0) { diff --git a/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp index c3845ddca..33ea5ec5c 100644 --- a/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp +++ b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp @@ -187,20 +187,9 @@ void SparseIdsList::invalidateBottom() { rpl::producer SparseIdsList::query( SparseIdsListQuery &&query) const { return [this, query = std::move(query)](auto consumer) { - auto slice = query.aroundId - ? ranges::lower_bound( - _slices, - query.aroundId, - std::less<>(), - [](const Slice &slice) { return slice.range.till; }) - : _slices.end(); - if (slice != _slices.end() - && slice->range.from <= query.aroundId) { - consumer.put_next(queryFromSlice(query, *slice)); - } else if (_count) { - auto result = SparseIdsListResult {}; - result.count = _count; - consumer.put_next(std::move(result)); + auto now = snapshot(query); + if (!now.messageIds.empty() || now.count) { + consumer.put_next(std::move(now)); } consumer.put_done(); return rpl::lifetime(); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 5c2cbf99d..36e3b5947 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1086,4 +1086,7 @@ previewMenu: PopupMenu(defaultPopupMenu) { maxHeight: 420px; radius: boxRadius; shadow: boxRoundShadow; + animation: PanelAnimation(defaultPanelAnimation) { + shadow: boxRoundShadow; + } }