diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 51d5689ac..8d6715541 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -435,6 +435,8 @@ PRIVATE data/data_photo.h data/data_photo_media.cpp data/data_photo_media.h + data/data_pinned_messages.cpp + data/data_pinned_messages.h data/data_poll.cpp data/data_poll.h data/data_pts_waiter.cpp diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 7865403d6..4b853a1e2 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1083,6 +1083,17 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { _session->data().updateEditedMessage(d.vmessage()); } break; + case mtpc_updatePinnedChannelMessages: { + const auto &d = update.c_updatePinnedChannelMessages(); + const auto channelId = d.vchannel_id().v; + for (const auto &msgId : d.vmessages().v) { + const auto item = session().data().message(channelId, msgId.v); + if (item) { + item->setIsPinned(d.is_pinned()); + } + } + } break; + case mtpc_updateEditMessage: { auto &d = update.c_updateEditMessage(); _session->data().updateEditedMessage(d.vmessage()); @@ -1098,6 +1109,17 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { _session->data().processMessagesDeleted(d.vchannel_id().v, d.vmessages().v); } break; + case mtpc_updatePinnedMessages: { + const auto &d = update.c_updatePinnedMessages(); + const auto peerId = peerFromMTP(d.vpeer()); + for (const auto &msgId : d.vmessages().v) { + const auto item = session().data().message(0, msgId.v); + if (item) { + item->setIsPinned(d.is_pinned()); + } + } + } break; + default: Unexpected("Type in applyUpdateNoPtsCheck()"); } } @@ -1393,6 +1415,21 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updatePinnedChannelMessages: { + auto &d = update.c_updatePinnedChannelMessages(); + auto channel = session().data().channelLoaded(d.vchannel_id().v); + + if (channel && !_handlingChannelDifference) { + if (channel->ptsRequesting()) { // skip global updates while getting channel difference + return; + } else { + channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); + } + } else { + applyUpdateNoPtsCheck(update); + } + } break; + // Messages being read. case mtpc_updateReadHistoryInbox: { auto &d = update.c_updateReadHistoryInbox(); @@ -1998,6 +2035,12 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + // Pinned message. + case mtpc_updatePinnedMessages: { + const auto &d = update.c_updatePinnedMessages(); + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + ////// Cloud sticker sets case mtpc_updateNewStickerSet: { const auto &d = update.c_updateNewStickerSet(); diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index fb13a8d5d..a534f0400 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -444,14 +444,20 @@ PinMessageBox::PinMessageBox( : _peer(peer) , _api(&peer->session().mtp()) , _msgId(msgId) -, _text(this, tr::lng_pinned_pin_sure(tr::now), st::boxLabel) { +, _pinningOld(msgId < peer->topPinnedMessageId()) +, _text( + this, + (_pinningOld + ? "Do you want to pin an older message while leaving a more recent one pinned?" // #TODO pinned + : tr::lng_pinned_pin_sure(tr::now)), + st::boxLabel) { } void PinMessageBox::prepare() { addButton(tr::lng_pinned_pin(), [this] { pinMessage(); }); addButton(tr::lng_cancel(), [this] { closeBox(); }); - if (_peer->isChat() || _peer->isMegagroup()) { + if (!_pinningOld && (_peer->isChat() || _peer->isMegagroup())) { _notify.create(this, tr::lng_pinned_notify(tr::now), true, st::defaultBoxCheckbox); } diff --git a/Telegram/SourceFiles/boxes/confirm_box.h b/Telegram/SourceFiles/boxes/confirm_box.h index 3b8f80e05..585d8ec52 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.h +++ b/Telegram/SourceFiles/boxes/confirm_box.h @@ -183,7 +183,8 @@ private: const not_null _peer; MTP::Sender _api; - MsgId _msgId; + MsgId _msgId = 0; + bool _pinningOld = false; object_ptr _text; object_ptr _notify = { nullptr }; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 6164c94d6..1af259a67 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -383,9 +383,7 @@ void ChannelData::setUnavailableReasons( void ChannelData::setAvailableMinId(MsgId availableMinId) { if (_availableMinId != availableMinId) { _availableMinId = availableMinId; - if (pinnedMessageId() <= _availableMinId) { - clearPinnedMessage(); - } + clearPinnedMessages(_availableMinId + 1); } } @@ -772,9 +770,9 @@ void ApplyChannelUpdate( } } if (const auto pinned = update.vpinned_msg_id()) { - channel->setPinnedMessageId(pinned->v); + channel->setTopPinnedMessageId(pinned->v); } else { - channel->clearPinnedMessage(); + channel->clearPinnedMessages(); } if (channel->isMegagroup()) { const auto stickerSet = update.vstickerset(); diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 5cabfff25..37930f103 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -333,9 +333,9 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { return QString(); })); if (const auto pinned = update.vpinned_msg_id()) { - chat->setPinnedMessageId(pinned->v); + chat->setTopPinnedMessageId(pinned->v); } else { - chat->clearPinnedMessage(); + chat->clearPinnedMessages(); } chat->checkFolder(update.vfolder_id().value_or_empty()); chat->fullUpdated(); diff --git a/Telegram/SourceFiles/data/data_messages.cpp b/Telegram/SourceFiles/data/data_messages.cpp index 857442270..65ef717c9 100644 --- a/Telegram/SourceFiles/data/data_messages.cpp +++ b/Telegram/SourceFiles/data/data_messages.cpp @@ -117,6 +117,11 @@ void MessagesList::addRange( _sliceUpdated.fire(std::move(update)); } +void MessagesList::addOne(MessagePosition messageId) { + auto range = { messageId }; + addRange(range, { messageId, messageId }, std::nullopt, true); +} + void MessagesList::addNew(MessagePosition messageId) { auto range = { messageId }; addRange(range, { messageId, MaxMessagePosition }, std::nullopt, true); @@ -165,6 +170,31 @@ void MessagesList::removeAll(ChannelId channelId) { } } +void MessagesList::removeLessThan(MessagePosition messageId) { + auto removed = 0; + for (auto i = begin(_slices); i != end(_slices);) { + if (i->range.till <= messageId) { + removed += i->messages.size(); + i = _slices.erase(i); + continue; + } else if (i->range.from <= messageId) { + _slices.modify(i, [&](Slice &slice) { + slice.range.from = MinMessagePosition; + auto from = begin(slice.messages); + auto till = ranges::lower_bound(slice.messages, messageId); + if (from != till) { + removed += till - from; + slice.messages.erase(from, till); + } + }); + break; + } + } + if (removed && _count) { + *_count -= removed; + } +} + void MessagesList::invalidate() { _slices.clear(); _count = std::nullopt; @@ -184,19 +214,26 @@ void MessagesList::invalidateBottom() { _count = std::nullopt; } +MessagesResult MessagesList::queryCurrent(const MessagesQuery &query) const { + if (!query.aroundId) { + return MessagesResult(); + } + const auto slice = ranges::lower_bound( + _slices, + query.aroundId, + std::less<>(), + [](const Slice &slice) { return slice.range.till; }); + return (slice != _slices.end() && slice->range.from <= query.aroundId) + ? queryFromSlice(query, *slice) + : MessagesResult(); +} + rpl::producer MessagesList::query( MessagesQuery &&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)); + auto current = queryCurrent(query); + if (current.count.has_value() || !current.messageIds.empty()) { + consumer.put_next(std::move(current)); } consumer.put_done(); return rpl::lifetime(); @@ -207,6 +244,31 @@ rpl::producer MessagesList::sliceUpdated() const { return _sliceUpdated.events(); } +MessagesResult MessagesList::snapshot(MessagesQuery &&query) const { + return queryCurrent(query); +} + +bool MessagesList::empty() const { + for (const auto &slice : _slices) { + if (!slice.messages.empty()) { + return false; + } + } + return true; +} + +rpl::producer MessagesList::viewer( + MessagesQuery &&query) const { + auto copy = query; + return rpl::single( + queryCurrent(query) + ) | rpl::then(sliceUpdated() | rpl::map([=] { + return queryCurrent(query); + })) | rpl::filter([=](const MessagesResult &value) { + return value.count.has_value() || !value.messageIds.empty(); + }); +} + MessagesResult MessagesList::queryFromSlice( const MessagesQuery &query, const Slice &slice) const { @@ -372,7 +434,7 @@ void MessagesSliceBuilder::mergeSliceData( if (count) { _fullCount = count; } - const auto impossible = MessagePosition(-1, FullMsgId()); + const auto impossible = MessagePosition{ .fullId = {}, .date = -1 }; auto wasMinId = _ids.empty() ? impossible : _ids.front(); auto wasMaxId = _ids.empty() ? impossible : _ids.back(); _ids.merge(messageIds.begin(), messageIds.end()); diff --git a/Telegram/SourceFiles/data/data_messages.h b/Telegram/SourceFiles/data/data_messages.h index 083baf641..baf73af75 100644 --- a/Telegram/SourceFiles/data/data_messages.h +++ b/Telegram/SourceFiles/data/data_messages.h @@ -16,11 +16,8 @@ enum class LoadDirection : char { }; struct MessagePosition { - constexpr MessagePosition() = default; - constexpr MessagePosition(TimeId date, FullMsgId fullId) - : fullId(fullId) - , date(date) { - } + FullMsgId fullId; + TimeId date = 0; explicit operator bool() const { return (fullId.msg != 0); @@ -50,18 +47,11 @@ struct MessagePosition { inline constexpr bool operator!=(const MessagePosition &other) const { return !(*this == other); } - - FullMsgId fullId; - TimeId date = 0; - }; struct MessagesRange { - constexpr MessagesRange() = default; - constexpr MessagesRange(MessagePosition from, MessagePosition till) - : from(from) - , till(till) { - } + MessagePosition from; + MessagePosition till; inline constexpr bool operator==(const MessagesRange &other) const { return (from == other.from) @@ -70,26 +60,26 @@ struct MessagesRange { inline constexpr bool operator!=(const MessagesRange &other) const { return !(*this == other); } - - MessagePosition from; - MessagePosition till; - }; constexpr auto MinDate = TimeId(0); constexpr auto MaxDate = std::numeric_limits::max(); -constexpr auto MinMessagePosition = MessagePosition( - MinDate, - FullMsgId(NoChannel, 1)); -constexpr auto MaxMessagePosition = MessagePosition( - MaxDate, - FullMsgId(NoChannel, ServerMaxMsgId - 1)); -constexpr auto FullMessagesRange = MessagesRange( - MinMessagePosition, - MaxMessagePosition); -constexpr auto UnreadMessagePosition = MessagePosition( - MinDate, - FullMsgId(NoChannel, ShowAtUnreadMsgId)); +constexpr auto MinMessagePosition = MessagePosition{ + .fullId = FullMsgId(NoChannel, 1), + .date = MinDate, +}; +constexpr auto MaxMessagePosition = MessagePosition{ + .fullId = FullMsgId(NoChannel, ServerMaxMsgId - 1), + .date = MaxDate, +}; +constexpr auto FullMessagesRange = MessagesRange{ + .from = MinMessagePosition, + .till = MaxMessagePosition, +}; +constexpr auto UnreadMessagePosition = MessagePosition{ + .fullId = FullMsgId(NoChannel, ShowAtUnreadMsgId), + .date = MinDate, +}; struct MessagesSlice { std::vector ids; @@ -100,15 +90,6 @@ struct MessagesSlice { }; struct MessagesQuery { - MessagesQuery( - MessagePosition aroundId, - int limitBefore, - int limitAfter) - : aroundId(aroundId) - , limitBefore(limitBefore) - , limitAfter(limitAfter) { - } - MessagePosition aroundId; int limitBefore = 0; int limitAfter = 0; @@ -129,6 +110,7 @@ struct MessagesSliceUpdate { class MessagesList { public: + void addOne(MessagePosition messageId); void addNew(MessagePosition messageId); void addSlice( std::vector &&messageIds, @@ -136,10 +118,18 @@ public: std::optional count); void removeOne(MessagePosition messageId); void removeAll(ChannelId channelId); + void removeLessThan(MessagePosition messageId); void invalidate(); void invalidateBottom(); - rpl::producer query(MessagesQuery &&query) const; - rpl::producer sliceUpdated() const; + [[nodiscard]] rpl::producer query( + MessagesQuery &&query) const; + [[nodiscard]] rpl::producer sliceUpdated() const; + + [[nodiscard]] MessagesResult snapshot(MessagesQuery &&query) const; + [[nodiscard]] rpl::producer viewer( + MessagesQuery &&query) const; + + [[nodiscard]] bool empty() const; private: struct Slice { @@ -183,6 +173,7 @@ private: MessagesResult queryFromSlice( const MessagesQuery &query, const Slice &slice) const; + MessagesResult queryCurrent(const MessagesQuery &query) const; std::optional _count; base::flat_set _slices; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 59e41781c..3cd7b45d6 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_histories.h" +#include "data/data_pinned_messages.h" #include "base/unixtime.h" #include "base/crc32hash.h" #include "lang/lang_keys.h" @@ -460,18 +461,83 @@ bool PeerData::canEditMessagesIndefinitely() const { Unexpected("Peer type in PeerData::canEditMessagesIndefinitely."); } -void PeerData::setPinnedMessageId(MsgId messageId) { +MsgId PeerData::topPinnedMessageId() const { + return _pinnedMessages ? _pinnedMessages->topId() : 0; +} + +void PeerData::ensurePinnedMessagesCreated() { + if (!_pinnedMessages) { + _pinnedMessages = std::make_unique( + peerToChannel(id)); + } +} + +void PeerData::removeEmptyPinnedMessages() { + if (_pinnedMessages && _pinnedMessages->empty()) { + _pinnedMessages = nullptr; + } +} + +bool PeerData::messageIdTooSmall(MsgId messageId) const { + if (const auto channel = asChannel()) { + return (messageId <= channel->availableMinId()); + } + return false; +} + +void PeerData::setTopPinnedMessageId(MsgId messageId) { + if (messageIdTooSmall(messageId)) { + return; + } + ensurePinnedMessagesCreated(); + _pinnedMessages->setTopId(messageId); +} + +void PeerData::clearPinnedMessages(MsgId lessThanId) { + if (!_pinnedMessages) { + return; + } + _pinnedMessages->clearLessThanId(lessThanId); + removeEmptyPinnedMessages(); +} + +void PeerData::addPinnedMessage(MsgId messageId) { + if (messageIdTooSmall(messageId)) { + return; + } + ensurePinnedMessagesCreated(); + _pinnedMessages->add(messageId); +} + +void PeerData::addPinnedSlice( + std::vector &&ids, + MsgId from, + MsgId till) { const auto min = [&] { if (const auto channel = asChannel()) { return channel->availableMinId(); } - return MsgId(0); + return 0; }(); - messageId = (messageId > min) ? messageId : MsgId(0); - if (_pinnedMessageId != messageId) { - _pinnedMessageId = messageId; - session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); + ids.erase( + ranges::remove_if(ids, [&](MsgId id) { return id <= min; }), + end(ids)); + if (from <= min) { + from = 0; } + if (ids.empty() && !_pinnedMessages) { + return; + } + ensurePinnedMessagesCreated(); + _pinnedMessages->add(std::move(ids), from, till, std::nullopt); +} + +void PeerData::removePinnedMessage(MsgId messageId) { + if (!_pinnedMessages) { + return; + } + _pinnedMessages->remove(messageId); + removeEmptyPinnedMessages(); } bool PeerData::canExportChatHistory() const { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 3712adca9..7aff79a44 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -29,6 +29,7 @@ class Session; namespace Data { class Session; +class PinnedMessages; int PeerColorIndex(PeerId peerId); int PeerColorIndex(int32 bareId); @@ -326,13 +327,12 @@ public: [[nodiscard]] bool canPinMessages() const; [[nodiscard]] bool canEditMessagesIndefinitely() const; - [[nodiscard]] MsgId pinnedMessageId() const { - return _pinnedMessageId; - } - void setPinnedMessageId(MsgId messageId); - void clearPinnedMessage() { - setPinnedMessageId(0); - } + [[nodiscard]] MsgId topPinnedMessageId() const; + void setTopPinnedMessageId(MsgId messageId); + void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId); + void addPinnedMessage(MsgId messageId); + void addPinnedSlice(std::vector &&ids, MsgId from, MsgId till); + void removePinnedMessage(MsgId messageId); [[nodiscard]] bool canExportChatHistory() const; @@ -411,6 +411,10 @@ private: -> const std::vector &; void setUserpicChecked(PhotoId photoId, const ImageLocation &location); + void ensurePinnedMessagesCreated(); + void removeEmptyPinnedMessages(); + + [[nodiscard]] bool messageIdTooSmall(MsgId messageId) const; static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL); @@ -428,7 +432,7 @@ private: base::flat_set _nameFirstLetters; crl::time _lastFullUpdate = 0; - MsgId _pinnedMessageId = 0; + std::unique_ptr _pinnedMessages; Settings _settings = { kSettingsUnknown }; BlockStatus _blockStatus = BlockStatus::Unknown; diff --git a/Telegram/SourceFiles/data/data_pinned_messages.cpp b/Telegram/SourceFiles/data/data_pinned_messages.cpp new file mode 100644 index 000000000..ad7037e85 --- /dev/null +++ b/Telegram/SourceFiles/data/data_pinned_messages.cpp @@ -0,0 +1,88 @@ +/* +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_pinned_messages.h" + +namespace Data { + +PinnedMessages::PinnedMessages(ChannelId channelId) : _channelId(channelId) { +} + +bool PinnedMessages::empty() const { + return _list.empty(); +} + +MsgId PinnedMessages::topId() const { + const auto slice = _list.snapshot(MessagesQuery{ + .aroundId = MaxMessagePosition, + .limitBefore = 1, + .limitAfter = 1 + }); + return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg; +} + +MessagePosition PinnedMessages::position(MsgId id) const { + return MessagePosition{ + .fullId = FullMsgId(_channelId, id), + }; +} + +void PinnedMessages::add(MsgId messageId) { + _list.addOne(position(messageId)); +} + +void PinnedMessages::add( + std::vector &&ids, + MsgId from, + MsgId till, + std::optional count) { + auto positions = ids | ranges::view::transform([&](MsgId id) { + return position(id); + }) | ranges::to_vector; + + _list.addSlice( + std::move(positions), + MessagesRange{ + .from = from ? position(from) : MinMessagePosition, + .till = position(till) + }, + count); +} + +void PinnedMessages::remove(MsgId messageId) { + _list.removeOne(MessagePosition{ + .fullId = FullMsgId(0, messageId), + }); +} + +void PinnedMessages::setTopId(MsgId messageId) { + while (true) { + auto top = topId(); + if (top > messageId) { + remove(top); + } else if (top == messageId) { + return; + } else { + break; + } + } + const auto position = MessagePosition{ + .fullId = FullMsgId(0, messageId), + }; + _list.addSlice( + { position }, + { .from = position, .till = MaxMessagePosition }, + std::nullopt); +} + +void PinnedMessages::clearLessThanId(MsgId messageId) { + _list.removeLessThan(MessagePosition{ + .fullId = FullMsgId(0, messageId), + }); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_pinned_messages.h b/Telegram/SourceFiles/data/data_pinned_messages.h new file mode 100644 index 000000000..7f825a343 --- /dev/null +++ b/Telegram/SourceFiles/data/data_pinned_messages.h @@ -0,0 +1,41 @@ +/* +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 "data/data_messages.h" + +namespace Data { + +class PinnedMessages final { +public: + explicit PinnedMessages(ChannelId channelId); + + [[nodiscard]] bool empty() const; + [[nodiscard]] MsgId topId() const; + + void add(MsgId messageId); + void add( + std::vector &&ids, + MsgId from, + MsgId till, + std::optional count); + void remove(MsgId messageId); + + void setTopId(MsgId messageId); + + void clearLessThanId(MsgId messageId); + +private: + [[nodiscard]] MessagePosition position(MsgId id) const; + + MessagesList _list; + ChannelId _channelId = 0; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 6c36ea279..84b3b510a 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -262,9 +262,9 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { user->setBotInfoVersion(-1); } if (const auto pinned = update.vpinned_msg_id()) { - user->setPinnedMessageId(pinned->v); + user->setTopPinnedMessageId(pinned->v); } else { - user->clearPinnedMessage(); + user->clearPinnedMessages(); } user->setFullFlags(update.vflags().v); user->setIsBlocked(update.is_blocked()); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 7c625f9e7..814252ba2 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -177,9 +177,7 @@ void History::itemVanished(not_null item) { && unreadCount() > 0) { setUnreadCount(unreadCount() - 1); } - if (peer->pinnedMessageId() == item->id) { - peer->clearPinnedMessage(); - } + peer->removePinnedMessage(item->id); } void History::setLocalDraft(std::unique_ptr &&draft) { @@ -709,6 +707,9 @@ not_null History::addNewToBack( item->id, { from, till })); } + if (item->isPinned()) { + item->history()->peer->addPinnedMessage(item->id); + } } if (item->from()->id) { if (auto user = item->from()->asUser()) { @@ -1005,8 +1006,8 @@ void History::applyServiceChanges( if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { if (item) { - item->history()->peer->setPinnedMessageId( - data.vreply_to_msg_id().v); + //item->history()->peer->setTopPinnedMessageId( // #TODO pinned + // data.vreply_to_msg_id().v); } }); } @@ -1160,6 +1161,7 @@ void History::addEdgesToSharedMedia() { {}, { from, till })); } + peer->addPinnedSlice({}, from, till); } void History::addOlderSlice(const QVector &slice) { @@ -1323,6 +1325,7 @@ void History::checkAddAllToUnreadMentions() { void History::addToSharedMedia( const std::vector> &items) { + auto pinned = std::vector(); std::vector medias[Storage::kSharedMediaTypeCount]; for (const auto item : items) { if (const auto types = item->sharedMediaTypes()) { @@ -1336,6 +1339,12 @@ void History::addToSharedMedia( } } } + if (item->isPinned()) { + if (pinned.empty()) { + pinned.reserve(items.size()); + } + pinned.push_back(item->id); + } } const auto from = loadedAtTop() ? 0 : minMsgId(); const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); @@ -1349,6 +1358,7 @@ void History::addToSharedMedia( { from, till })); } } + peer->addPinnedSlice(std::move(pinned), from, till); } void History::calculateFirstUnreadMessage() { @@ -3017,7 +3027,7 @@ void History::clear(ClearType type) { clearSharedMedia(); clearLastKeyboard(); if (const auto channel = peer->asChannel()) { - channel->clearPinnedMessage(); + channel->clearPinnedMessages(); //if (const auto feed = channel->feed()) { // #feed // // Should be after resetting the _lastMessage. // feed->historyCleared(this); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ad038352a..e75ed612c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -347,6 +347,16 @@ void HistoryItem::markMediaRead() { } } +void HistoryItem::setIsPinned(bool pinned) { + if (pinned) { + _flags |= MTPDmessage::Flag::f_pinned; + history()->peer->addPinnedMessage(id); + } else { + _flags &= ~MTPDmessage::Flag::f_pinned; + history()->peer->removePinnedMessage(id); + } +} + bool HistoryItem::definesReplyKeyboard() const { if (const auto markup = Get()) { if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { @@ -476,6 +486,9 @@ void HistoryItem::indexAsNewItem() { types, id)); } + if (isPinned()) { + _history->peer->setTopPinnedMessageId(id); + } //if (const auto channel = history()->peer->asChannel()) { // #feed // if (const auto feed = channel->feed()) { // _history->session().storage().add(Storage::FeedMessagesAddNew( @@ -509,10 +522,6 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().requestItemRepaint(this); } -bool HistoryItem::isPinned() const { - return (_history->peer->pinnedMessageId() == id); -} - bool HistoryItem::canPin() const { if (id < 0 || !toHistoryMessage()) { return false; @@ -656,7 +665,7 @@ ChannelId HistoryItem::channelId() const { } Data::MessagePosition HistoryItem::position() const { - return Data::MessagePosition(date(), fullId()); + return { .fullId = fullId(), .date = date() }; } MsgId HistoryItem::replyToId() const { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 82b99f50c..280afbed2 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -113,6 +113,9 @@ public: [[nodiscard]] bool out() const { return _flags & MTPDmessage::Flag::f_out; } + [[nodiscard]] bool isPinned() const { + return _flags & MTPDmessage::Flag::f_pinned; + } [[nodiscard]] bool unread() const; [[nodiscard]] bool showNotification() const; void markClientSideAsRead(); @@ -121,6 +124,7 @@ public: [[nodiscard]] bool isUnreadMedia() const; [[nodiscard]] bool hasUnreadMediaFlag() const; void markMediaRead(); + void setIsPinned(bool isPinned); // For edit media in history_message. virtual void returnSavedMedia() {}; @@ -312,7 +316,6 @@ public: return _text.isEmpty(); } - [[nodiscard]] bool isPinned() const; [[nodiscard]] bool canPin() const; [[nodiscard]] bool canStopPoll() const; [[nodiscard]] virtual bool allowsSendNow() const; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 960238c68..4700db368 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5200,6 +5200,7 @@ void HistoryWidget::updatePinnedBar(bool force) { if (!_pinnedBar) { return; } + const auto messageId = _pinnedBar->msgId; if (!force) { if (_pinnedBar->msg) { return; @@ -5208,7 +5209,9 @@ void HistoryWidget::updatePinnedBar(bool force) { Assert(_history != nullptr); if (!_pinnedBar->msg) { - _pinnedBar->msg = session().data().message(_history->channelId(), _pinnedBar->msgId); + _pinnedBar->msg = session().data().message( + _history->channelId(), + messageId); } if (_pinnedBar->msg) { _pinnedBar->text.setText( @@ -5217,17 +5220,15 @@ void HistoryWidget::updatePinnedBar(bool force) { Ui::DialogTextOptions()); update(); } else if (force) { - if (auto channel = _peer ? _peer->asChannel() : nullptr) { - channel->clearPinnedMessage(); - } destroyPinnedBar(); + _history->peer->removePinnedMessage(messageId); updateControlsGeometry(); } } bool HistoryWidget::pinnedMsgVisibilityUpdated() { auto result = false; - auto pinnedId = _peer->pinnedMessageId(); + auto pinnedId = _peer->topPinnedMessageId(); if (pinnedId && !_peer->canPinMessages()) { const auto hiddenId = session().settings().hiddenPinnedMessageId( _peer->id); @@ -5540,23 +5541,23 @@ void HistoryWidget::unpinMessage(FullMsgId itemId) { if (!_peer) { return; } - UnpinMessage(_peer); + UnpinMessage(_peer, itemId.msg); } -void HistoryWidget::UnpinMessage(not_null peer) { +void HistoryWidget::UnpinMessage(not_null peer, MsgId msgId) { if (!peer) { return; } const auto session = &peer->session(); Ui::show(Box(tr::lng_pinned_unpin_sure(tr::now), tr::lng_pinned_unpin(tr::now), crl::guard(session, [=] { - peer->clearPinnedMessage(); + peer->removePinnedMessage(msgId); Ui::hideLayer(); session->api().request(MTPmessages_UpdatePinnedMessage( - MTP_flags(0), + MTP_flags(MTPmessages_UpdatePinnedMessage::Flag::f_unpin), peer->input, - MTP_int(0) + MTP_int(msgId) )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); }).send(); @@ -5564,7 +5565,7 @@ void HistoryWidget::UnpinMessage(not_null peer) { } void HistoryWidget::hidePinnedMessage() { - const auto pinnedId = _peer ? _peer->pinnedMessageId() : MsgId(0); + const auto pinnedId = _peer ? _peer->topPinnedMessageId() : MsgId(0); if (!pinnedId) { if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 759b5a0fc..12f943212 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -530,7 +530,7 @@ private: void addMessagesToFront(PeerData *peer, const QVector &messages); void addMessagesToBack(PeerData *peer, const QVector &messages); - static void UnpinMessage(not_null peer); + static void UnpinMessage(not_null peer, MsgId msgId); void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateListSize(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 26d9b0c4f..f5db39172 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -85,8 +85,9 @@ RepliesMemento::RepliesMemento( : RepliesMemento(commentsItem->history(), commentsItem->id, commentId) { if (commentId) { _list.setAroundPosition({ - TimeId(0), - FullMsgId(commentsItem->history()->channelId(), commentId) + .fullId = FullMsgId(commentsItem->history()->channelId(), commentId), + .date = TimeId(0), + }); } else if (commentsItem->computeRepliesInboxReadTillFull() == MsgId(1)) { _list.setAroundPosition(Data::MinMessagePosition); @@ -1430,7 +1431,9 @@ bool RepliesWidget::showMessage( } return nullptr; }(); - showAtPosition(Data::MessagePosition(message->date(), id), originItem); + showAtPosition( + Data::MessagePosition{ .fullId = id, .date = message->date() }, + originItem); return true; } @@ -1479,8 +1482,8 @@ void RepliesWidget::restoreState(not_null memento) { _inner->restoreState(memento->list()); if (const auto highlight = memento->getHighlightId()) { const auto position = Data::MessagePosition{ - TimeId(0), - FullMsgId(_history->channelId(), highlight) + .fullId = FullMsgId(_history->channelId(), highlight), + .date = TimeId(0), }; _inner->showAroundPosition(position, [=] { return showAtPositionNow(position, nullptr);