From 8dc27339b4a8d61e0c79deb35196d5653e25b14b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Oct 2022 09:19:27 +0400 Subject: [PATCH] Support pinned messages bar in topics. --- Telegram/Resources/langs/lang.strings | 6 +- Telegram/SourceFiles/apiwrap.cpp | 14 +- Telegram/SourceFiles/data/data_changes.h | 8 +- Telegram/SourceFiles/data/data_channel.cpp | 2 +- Telegram/SourceFiles/data/data_chat.cpp | 2 +- Telegram/SourceFiles/data/data_forum.cpp | 11 + .../SourceFiles/data/data_forum_topic.cpp | 4 +- Telegram/SourceFiles/data/data_forum_topic.h | 1 + Telegram/SourceFiles/data/data_peer.cpp | 9 +- Telegram/SourceFiles/data/data_peer.h | 5 +- Telegram/SourceFiles/data/data_thread.cpp | 24 ++ Telegram/SourceFiles/data/data_thread.h | 5 + Telegram/SourceFiles/data/data_user.cpp | 2 +- Telegram/SourceFiles/history/history.cpp | 88 +++-- Telegram/SourceFiles/history/history.h | 10 +- Telegram/SourceFiles/history/history_item.cpp | 22 +- .../SourceFiles/history/history_service.cpp | 3 +- .../history_view_highlight_manager.cpp | 8 + .../SourceFiles/history/history_widget.cpp | 42 +-- .../view/history_view_context_menu.cpp | 10 +- .../history/view/history_view_list_widget.cpp | 60 ++- .../history/view/history_view_list_widget.h | 9 +- .../view/history_view_pinned_section.cpp | 46 +-- .../view/history_view_pinned_section.h | 15 +- .../view/history_view_pinned_tracker.cpp | 35 +- .../view/history_view_pinned_tracker.h | 5 +- .../view/history_view_replies_section.cpp | 346 +++++++++++++++++- .../view/history_view_replies_section.h | 21 +- .../view/history_view_scheduled_section.cpp | 2 +- .../view/history_view_scheduled_section.h | 2 +- .../view/history_view_top_bar_widget.cpp | 5 +- .../main/main_session_settings.cpp | 107 ++++-- .../SourceFiles/main/main_session_settings.h | 29 +- .../details/storage_settings_scheme.cpp | 1 + .../SourceFiles/storage/storage_facade.cpp | 9 + Telegram/SourceFiles/storage/storage_facade.h | 2 + .../storage/storage_shared_media.cpp | 4 + .../storage/storage_shared_media.h | 13 + .../SourceFiles/window/window_peer_menu.cpp | 26 +- .../SourceFiles/window/window_peer_menu.h | 4 +- 40 files changed, 811 insertions(+), 206 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6b3af9cb7..6bc448614 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1511,9 +1511,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_topic_created_inside" = "Topic created"; "lng_action_topic_closed_inside" = "Topic closed"; "lng_action_topic_reopened_inside" = "Topic reopened"; -"lng_action_topic_created" = "{topic} — was created"; -"lng_action_topic_closed" = "{topic} — was closed"; -"lng_action_topic_reopened" = "{topic} — was reopened"; +"lng_action_topic_created" = "«{topic}» was created"; +"lng_action_topic_closed" = "«{topic}» was closed"; +"lng_action_topic_reopened" = "«{topic}» was reopened"; "lng_action_topic_placeholder" = "topic"; "lng_action_topic_renamed" = "{from} renamed the {link} to «{title}»"; "lng_action_topic_icon_changed" = "{from} changed the {link} icon to {emoji}"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index af662f42d..73814a433 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3011,6 +3011,10 @@ void ApiWrap::sharedMediaDone( MsgId topicRootId, SharedMediaType type, Api::SearchResult &&parsed) { + const auto topic = peer->forumTopicFor(topicRootId); + if (topicRootId && !topic) { + return; + } _session->storage().add(Storage::SharedMediaAddSlice( peer->id, topicRootId, @@ -3021,6 +3025,9 @@ void ApiWrap::sharedMediaDone( )); if (type == SharedMediaType::Pinned && !parsed.messageIds.empty()) { peer->owner().history(peer)->setHasPinnedMessages(true); + if (topic) { + topic->setHasPinnedMessages(true); + } } } @@ -3451,11 +3458,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) { action.generateLocal = true; sendAction(action); - const auto replyToId = message.action.replyTo; + const auto replyToId = action.replyTo; const auto replyTo = replyToId ? peer->owner().message(peer, replyToId) : nullptr; - const auto topic = replyTo ? replyTo->topic() : nullptr; + const auto topicRootId = replyTo ? replyTo->topicRootId() : replyToId; + const auto topic = topicRootId + ? peer->forumTopicFor(topicRootId) + : nullptr; if (!(topic ? topic->canWrite() : peer->canWrite()) || Api::SendDice(message)) { return; diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 9a8a303ac..75e8d8bd5 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -130,9 +130,8 @@ struct HistoryUpdate { BotKeyboard = (1U << 12), CloudDraft = (1U << 13), LocalDraftSet = (1U << 14), - PinnedMessages = (1U << 15), - LastUsedBit = (1U << 15), + LastUsedBit = (1U << 14), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -156,7 +155,7 @@ struct TopicUpdate { CloudDraft = (1U << 8), Closed = (1U << 9), - LastUsedBit = (1U << 8), + LastUsedBit = (1U << 9), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -196,8 +195,9 @@ struct EntryUpdate { None = 0, Repaint = (1U << 0), + HasPinnedMessages = (1U << 1), - LastUsedBit = (1U << 0), + LastUsedBit = (1U << 1), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 6b99cb392..4f35f4080 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -994,7 +994,7 @@ void ApplyChannelUpdate( } } if (const auto pinned = update.vpinned_msg_id()) { - SetTopPinnedMessageId(channel, MsgId(0), pinned->v); + SetTopPinnedMessageId(channel, pinned->v); } if (channel->isMegagroup()) { auto commands = ranges::views::all( diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 8d67ccff8..0315c0aed 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -475,7 +475,7 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { chat->session().api().inviteLinks().clearMyPermanent(chat); } if (const auto pinned = update.vpinned_msg_id()) { - SetTopPinnedMessageId(chat, MsgId(0), pinned->v); + SetTopPinnedMessageId(chat, pinned->v); } chat->checkFolder(update.vfolder_id().value_or_empty()); chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 43aa89c3c..aa8e04509 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "ui/layers/generic_box.h" #include "ui/widgets/input_fields.h" +#include "storage/storage_facade.h" +#include "storage/storage_shared_media.h" #include "window/window_session_controller.h" #include "window/notifications_manager.h" #include "styles/style_boxes.h" @@ -63,6 +65,12 @@ Forum::~Forum() { if (_requestId) { session().api().request(_requestId).cancel(); } + const auto peerId = _history->peer->id; + for (const auto &[rootId, topic] : _topics) { + session().storage().unload(Storage::SharedMediaUnloadThread( + peerId, + rootId)); + } } Session &Forum::owner() const { @@ -170,6 +178,9 @@ void Forum::applyTopicDeleted(MsgId rootId) { _topics.erase(i); _history->destroyMessagesByTopic(rootId); + session().storage().unload(Storage::SharedMediaUnloadThread( + _history->peer->id, + rootId)); } } diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 640bb03d3..a504cf9d8 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -657,9 +657,7 @@ void ForumTopic::setMuted(bool muted) { const auto notify = state.unread || state.reaction; const auto notifier = unreadStateChangeNotifier(notify); Thread::setMuted(muted); - session().changes().topicUpdated( - this, - Data::TopicUpdate::Flag::Notifications); + session().changes().topicUpdated(this, UpdateFlag::Notifications); } not_null ForumTopic::sendActionPainter() { diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index faf3dbf18..a2bd454f2 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -139,6 +139,7 @@ private: enum class Flag : uchar { Closed = (1 << 0), My = (1 << 1), + HasPinnedMessages = (1 << 2), }; friend inline constexpr bool is_flag_type(Flag) { return true; } using Flags = base::flags; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 8dbc5770b..8b005e31e 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_folder.h" #include "data/data_forum.h" +#include "data/data_forum_topic.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_histories.h" @@ -1255,7 +1256,6 @@ std::optional RestrictionError( void SetTopPinnedMessageId( not_null peer, - MsgId topicRootId, MsgId messageId) { if (const auto channel = peer->asChannel()) { if (messageId <= channel->availableMinId()) { @@ -1265,12 +1265,15 @@ void SetTopPinnedMessageId( auto &session = peer->session(); const auto hiddenId = session.settings().hiddenPinnedMessageId(peer->id); if (hiddenId != 0 && hiddenId != messageId) { - session.settings().setHiddenPinnedMessageId(peer->id, 0); + session.settings().setHiddenPinnedMessageId( + peer->id, + MsgId(0), // topicRootId + 0); session.saveSettingsDelayed(); } session.storage().add(Storage::SharedMediaAddExisting( peer->id, - topicRootId, + MsgId(0), // topicRootId Storage::SharedMediaType::Pinned, messageId, { messageId, ServerMaxMsgId })); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 9c3e992c2..22f3fdd8d 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -482,15 +482,14 @@ std::optional RestrictionError( void SetTopPinnedMessageId( not_null peer, - MsgId topicRootId, MsgId messageId); [[nodiscard]] FullMsgId ResolveTopPinnedId( not_null peer, MsgId topicRootId, - PeerData *migrated); + PeerData *migrated = nullptr); [[nodiscard]] FullMsgId ResolveMinPinnedId( not_null peer, MsgId topicRootId, - PeerData *migrated); + PeerData *migrated = nullptr); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_thread.cpp b/Telegram/SourceFiles/data/data_thread.cpp index 1e48ef0a7..7f86892c9 100644 --- a/Telegram/SourceFiles/data/data_thread.cpp +++ b/Telegram/SourceFiles/data/data_thread.cpp @@ -8,15 +8,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_thread.h" #include "data/data_forum_topic.h" +#include "data/data_changes.h" #include "data/data_peer.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_unread_things.h" +#include "main/main_session.h" namespace Data { Thread::~Thread() = default; +not_null Thread::migrateToOrMe() const { + const auto history = asHistory(); + return history ? history->migrateToOrMe() : const_cast(this); +} + MsgId Thread::topicRootId() const { if (const auto topic = asTopic()) { return topic->rootId(); @@ -157,4 +164,21 @@ void Thread::setUnreadMarkFlag(bool unread) { } } +[[nodiscard]] bool Thread::hasPinnedMessages() const { + return (_flags & Flag::HasPinnedMessages); +} + +void Thread::setHasPinnedMessages(bool has) { + if (hasPinnedMessages() == has) { + return; + } else if (has) { + _flags |= Flag::HasPinnedMessages; + } else { + _flags &= ~Flag::HasPinnedMessages; + } + session().changes().entryUpdated( + this, + EntryUpdate::Flag::HasPinnedMessages); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_thread.h b/Telegram/SourceFiles/data/data_thread.h index e4f29ebe4..f0808a5a2 100644 --- a/Telegram/SourceFiles/data/data_thread.h +++ b/Telegram/SourceFiles/data/data_thread.h @@ -58,6 +58,7 @@ public: [[nodiscard]] virtual not_null owningHistory() = 0; + [[nodiscard]] not_null migrateToOrMe() const; [[nodiscard]] not_null owningHistory() const { return const_cast(this)->owningHistory(); } @@ -110,6 +111,9 @@ public: [[nodiscard]] virtual auto sendActionPainter() -> not_null = 0; + [[nodiscard]] bool hasPinnedMessages() const; + void setHasPinnedMessages(bool has); + protected: void setUnreadMarkFlag(bool unread); @@ -118,6 +122,7 @@ private: UnreadMark = (1 << 0), Muted = (1 << 1), UnreadThingsKnown = (1 << 2), + HasPinnedMessages = (1 << 3), }; friend inline constexpr bool is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 33b84a85f..d568987de 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -363,7 +363,7 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { user->setBotInfoVersion(-1); } if (const auto pinned = update.vpinned_msg_id()) { - SetTopPinnedMessageId(user, MsgId(0), pinned->v); + SetTopPinnedMessageId(user, pinned->v); } const auto canReceiveGifts = (update.vflags().v & MTPDuserFull::Flag::f_premium_gifts) diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 87cc62102..8a845d769 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -508,16 +508,25 @@ void History::destroyMessagesByTopic(MsgId topicRootId) { } } -void History::unpinAllMessages() { - session().storage().remove( - Storage::SharedMediaRemoveAll( - peer->id, - Storage::SharedMediaType::Pinned)); - setHasPinnedMessages(false); - for (const auto &message : _messages) { - if (message->isPinned()) { - message->setIsPinned(false); +void History::unpinMessagesFor(MsgId topicRootId) { + if (!topicRootId) { + session().storage().remove( + Storage::SharedMediaRemoveAll( + peer->id, + Storage::SharedMediaType::Pinned)); + setHasPinnedMessages(false); + if (const auto forum = peer->forum()) { + forum->enumerateTopics([](not_null topic) { + topic->setHasPinnedMessages(false); + }); } + for (const auto &message : _messages) { + if (message->isPinned()) { + message->setIsPinned(false); + } + } + } else { + // #TODO forum pinned } } @@ -792,15 +801,29 @@ not_null History::addNewToBack( if (const auto sharedMediaTypes = item->sharedMediaTypes()) { auto from = loadedAtTop() ? 0 : minMsgId(); auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); - session().storage().add(Storage::SharedMediaAddExisting( + auto &storage = session().storage(); + storage.add(Storage::SharedMediaAddExisting( peer->id, - MsgId(0), + MsgId(0), // topicRootId sharedMediaTypes, item->id, { from, till })); - if (sharedMediaTypes.test(Storage::SharedMediaType::Pinned)) { + const auto pinned = sharedMediaTypes.test( + Storage::SharedMediaType::Pinned); + if (pinned) { setHasPinnedMessages(true); } + if (const auto topic = item->topic()) { + storage.add(Storage::SharedMediaAddExisting( + peer->id, + topic->rootId(), + sharedMediaTypes, + item->id, + { item->id, item->id})); + if (pinned) { + topic->setHasPinnedMessages(true); + } + } } } if (item->from()->id) { @@ -1064,11 +1087,20 @@ void History::applyServiceChanges( if (item) { session().storage().add(Storage::SharedMediaAddSlice( peer->id, - MsgId(0), // topicRootId + MsgId(0), Storage::SharedMediaType::Pinned, { id }, { id, ServerMaxMsgId })); setHasPinnedMessages(true); + if (const auto topic = item->topic()) { + session().storage().add(Storage::SharedMediaAddSlice( + peer->id, + topic->rootId(), + Storage::SharedMediaType::Pinned, + { id }, + { id, ServerMaxMsgId })); + topic->setHasPinnedMessages(true); + } } }); } @@ -1464,6 +1496,7 @@ void History::checkAddAllToUnreadMentions() { void History::addToSharedMedia( const std::vector> &items) { std::vector medias[Storage::kSharedMediaTypeCount]; + auto topicsWithPinned = base::flat_set>(); for (const auto &item : items) { if (const auto types = item->sharedMediaTypes()) { for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) { @@ -1473,6 +1506,13 @@ void History::addToSharedMedia( medias[i].reserve(items.size()); } medias[i].push_back(item->id); + if (type == Storage::SharedMediaType::Pinned) { + if (const auto topic = item->topic()) { + if (!topic->hasPinnedMessages()) { + topicsWithPinned.emplace(topic); + } + } + } } } } @@ -1493,6 +1533,9 @@ void History::addToSharedMedia( } } } + for (const auto &topic : topicsWithPinned) { + topic->setHasPinnedMessages(true); + } } void History::calculateFirstUnreadMessage() { @@ -1750,7 +1793,7 @@ void History::setUnreadMark(bool unread) { } void History::setFakeUnreadWhileOpened(bool enabled) { - if (_fakeUnreadWhileOpened == enabled) { + if (fakeUnreadWhileOpened() == enabled) { return; } else if (enabled) { if (!inChatList()) { @@ -1761,12 +1804,16 @@ void History::setFakeUnreadWhileOpened(bool enabled) { return; } } - _fakeUnreadWhileOpened = enabled; + if (enabled) { + _flags |= Flag::FakeUnreadWhileOpened; + } else { + _flags &= ~Flag::FakeUnreadWhileOpened; + } owner().chatsFilters().refreshHistory(this); } [[nodiscard]] bool History::fakeUnreadWhileOpened() const { - return _fakeUnreadWhileOpened; + return (_flags & Flag::FakeUnreadWhileOpened); } void History::setMuted(bool muted) { @@ -3316,15 +3363,6 @@ void History::removeBlock(not_null block) { } } -bool History::hasPinnedMessages() const { - return _hasPinnedMessages; -} - -void History::setHasPinnedMessages(bool has) { - _hasPinnedMessages = has; - session().changes().historyUpdated(this, UpdateFlag::PinnedMessages); -} - void History::cacheTopPromoted(bool promoted) { if (isTopPromoted() == promoted) { return; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index c81766e73..94702744d 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -140,7 +140,7 @@ public: void destroyMessagesByDates(TimeId minDate, TimeId maxDate); void destroyMessagesByTopic(MsgId topicRootId); - void unpinAllMessages(); + void unpinMessagesFor(MsgId topicRootId); not_null addNewMessage( MsgId id, @@ -426,9 +426,6 @@ public: void setInboxReadTill(MsgId upTo); std::optional countStillUnreadLocal(MsgId readTillId) const; - [[nodiscard]] bool hasPinnedMessages() const; - void setHasPinnedMessages(bool has); - [[nodiscard]] bool isTopPromoted() const; const not_null peer; @@ -461,6 +458,8 @@ private: HasPendingResizedItems = (1 << 0), IsTopPromoted = (1 << 1), IsForum = (1 << 2), + FakeUnreadWhileOpened = (1 << 3), + HasPinnedMessages = (1 << 4), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { @@ -610,9 +609,6 @@ private: QString _chatListNameSortKey; - bool _fakeUnreadWhileOpened = false; - bool _hasPinnedMessages = false; - // A pointer to the block that is currently being built. // We hold this pointer so we can destroy it while building // and then create a new one if it is necessary. diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 61e82e580..d5ad83ad9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -480,13 +480,23 @@ void HistoryItem::setIsPinned(bool pinned) { const auto changed = (isPinned() != pinned); if (pinned) { _flags |= MessageFlag::Pinned; - history()->session().storage().add(Storage::SharedMediaAddExisting( + auto &storage = history()->session().storage(); + storage.add(Storage::SharedMediaAddExisting( history()->peer->id, MsgId(0), // topicRootId Storage::SharedMediaType::Pinned, id, { id, id })); history()->setHasPinnedMessages(true); + if (const auto topic = this->topic()) { + storage.add(Storage::SharedMediaAddExisting( + history()->peer->id, + topic->rootId(), + Storage::SharedMediaType::Pinned, + id, + { id, id })); + topic->setHasPinnedMessages(true); + } } else { _flags &= ~MessageFlag::Pinned; history()->session().storage().remove(Storage::SharedMediaRemoveOne( @@ -734,6 +744,9 @@ void HistoryItem::indexAsNewItem() { id)); if (types.test(Storage::SharedMediaType::Pinned)) { _history->setHasPinnedMessages(true); + if (const auto topic = this->topic()) { + topic->setHasPinnedMessages(true); + } } } } @@ -1423,7 +1436,12 @@ ClickHandlerPtr goToMessageClickHandler( params.origin = Window::SectionShow::OriginMessage{ returnToId }; - controller->showPeerHistory(peer, params, msgId); + const auto item = peer->owner().message(peer, msgId); + if (const auto topic = item ? item->topic() : nullptr) { + controller->showTopic(topic, msgId, params); + } else { + controller->showPeerHistory(peer, params, msgId); + } } }); } diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index d32d954a7..a594913a2 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -859,7 +859,8 @@ bool HistoryService::updateDependent(bool force) { (dependent->peerId ? history()->owner().peer(dependent->peerId) : history()->peer), - dependent->msgId); + dependent->msgId, + fullId()); } auto gotDependencyItem = false; if (!dependent->msg) { diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp index 23bb9a6b1..137cd30e0 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp @@ -73,6 +73,14 @@ float64 ElementHighlighter::progress( void ElementHighlighter::highlight(FullMsgId itemId) { if (const auto item = _data->message(itemId)) { if (const auto view = _viewForItem(item)) { + if (_highlightedMessageId + && _highlightedMessageId != itemId) { + if (const auto was = _data->message(_highlightedMessageId)) { + if (const auto view = _viewForItem(was)) { + repaintHighlightedItem(view); + } + } + } _highlightedMessageId = itemId; _animation.start(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1bcd8be9f..1a0979805 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -603,6 +603,18 @@ HistoryWidget::HistoryWidget( } }, lifetime()); + using EntryUpdateFlag = Data::EntryUpdate::Flag; + session().changes().entryUpdates( + EntryUpdateFlag::HasPinnedMessages + ) | rpl::start_with_next([=](const Data::EntryUpdate &update) { + if (_pinnedTracker + && (update.flags & EntryUpdateFlag::HasPinnedMessages) + && (_migrated && update.entry.get() == _migrated) + || (update.entry.get() == _history)) { + checkPinnedBarState(); + } + }, lifetime()); + using HistoryUpdateFlag = Data::HistoryUpdate::Flag; session().changes().historyUpdates( HistoryUpdateFlag::MessageSent @@ -614,14 +626,7 @@ HistoryWidget::HistoryWidget( | HistoryUpdateFlag::UnreadView | HistoryUpdateFlag::TopPromoted | HistoryUpdateFlag::ClientSideMessages - | HistoryUpdateFlag::PinnedMessages ) | rpl::filter([=](const Data::HistoryUpdate &update) { - if (_migrated && update.history.get() == _migrated) { - if (_pinnedTracker - && (update.flags & HistoryUpdateFlag::PinnedMessages)) { - checkPinnedBarState(); - } - } return (_history == update.history.get()); }) | rpl::start_with_next([=](const Data::HistoryUpdate &update) { const auto flags = update.flags; @@ -647,9 +652,6 @@ HistoryWidget::HistoryWidget( if (flags & HistoryUpdateFlag::UnreadView) { unreadCountUpdated(); } - if (_pinnedTracker && (flags & HistoryUpdateFlag::PinnedMessages)) { - checkPinnedBarState(); - } if (flags & HistoryUpdateFlag::TopPromoted) { updateHistoryGeometry(); updateControlsVisibility(); @@ -3365,15 +3367,16 @@ void HistoryWidget::handleScroll() { if (!_itemsRevealHeight) { updatePinnedViewer(); } + const auto now = crl::now(); if (!_synteticScrollEvent) { - _lastUserScrolled = crl::now(); + _lastUserScrolled = now; } const auto scrollTop = _scroll->scrollTop(); if (scrollTop != _lastScrollTop) { if (!_synteticScrollEvent) { checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop); } - _lastScrolled = crl::now(); + _lastScrolled = now; _lastScrollTop = scrollTop; } } @@ -6892,6 +6895,7 @@ void HistoryWidget::hidePinnedMessage() { Window::HidePinnedBar( controller(), _peer, + MsgId(0), // topicRootId crl::guard(this, callback)); } } @@ -7765,20 +7769,6 @@ void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int } } -// -//void HistoryWidget::drawPinnedBar(Painter &p) { -// //if (_pinnedBar->msg) { -// // const auto media = _pinnedBar->msg->media(); -// // if (media && media->hasReplyPreview()) { -// // if (const auto image = media->replyPreview()) { -// // QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); -// // p.drawPixmap(to.x(), to.y(), image->pixSingle(image->width() / cIntRetinaFactor(), image->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small)); -// // } -// // left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); -// // } -// //} -//} - bool HistoryWidget::paintShowAnimationFrame() { auto progress = _a_show.value(1.); if (!_a_show.animating()) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index e36c400e6..cf8906ac9 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -680,11 +680,15 @@ bool AddPinMessageAction( not_null list) { const auto context = list->elementContext(); const auto item = request.item; - if (!item - || !item->isRegular() - || (context != Context::History && context != Context::Pinned)) { + if (!item || !item->isRegular()) { return false; } + const auto topic = item->topic(); + if (context != Context::History && context != Context::Pinned) { + if (context != Context::Replies || !topic) { + return false; + } + } const auto group = item->history()->owner().groups().find(item); const auto pinItem = ((item->canPin() && item->isPinned()) || !group) ? item diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 54f0d5c68..0b1b7c315 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -411,7 +411,7 @@ ListWidget::ListWidget( _selectScroll.scrolls( ) | rpl::start_with_next([=](int d) { - delegate->listScrollTo(_visibleTop + d); + delegate->listScrollTo(_visibleTop + d, false); }, lifetime()); } @@ -1378,6 +1378,37 @@ bool ListWidget::hasSelectRestriction() const { != CopyRestrictionType::None; } +auto ListWidget::findViewForPinnedTracking(int top) const +-> std::pair { + const auto findScrollTopItem = [&](int top) + -> std::vector>::const_iterator { + if (!width() || _items.empty()) { + return end(_items); + } + const auto first = ranges::lower_bound( + _items, + top, + std::less<>(), + &Element::y); + return (first == end(_items) || (*first)->y() > top) + ? first - 1 + : first; + }; + const auto findView = [&](int top) + -> std::pair>::const_iterator, int> { + if (const auto i = findScrollTopItem(top); i != end(_items)) { + return { i, top - (*i)->y() }; + } + return { end(_items), 0 }; + }; + auto [view, offset] = findView(top); + while (view != end(_items) && !(*view)->data()->isRegular()) { + offset -= (*view)->height(); + ++view; + } + return { (view != end(_items)) ? view->get() : nullptr, offset }; +} + int ListWidget::itemMinimalHeight() const { return st::msgMarginTopAttached + st::msgPhotoSize @@ -2712,7 +2743,9 @@ void ListWidget::mouseReleaseEvent(QMouseEvent *e) { void ListWidget::touchScrollUpdated(const QPoint &screenPos) { _touchPos = screenPos; - _delegate->listScrollTo(_visibleTop - (_touchPos - _touchPrevPos).y()); + _delegate->listScrollTo( + _visibleTop - (_touchPos - _touchPrevPos).y(), + false); touchUpdateSpeed(); } @@ -3083,15 +3116,8 @@ void ListWidget::mouseActionFinish( mouseActionCancel(); ActivateClickHandler(window(), activated, { button, - QVariant::fromValue(ClickHandlerContext{ - .itemId = pressState.itemId, - .elementDelegate = [weak = Ui::MakeWeak(this)] { - return weak - ? (ElementDelegate*)weak - : nullptr; - }, - .sessionWindow = base::make_weak(_controller), - }) + QVariant::fromValue( + prepareClickHandlerContext(pressState.itemId)) }); return; } @@ -3139,6 +3165,18 @@ void ListWidget::mouseActionFinish( } } +ClickHandlerContext ListWidget::prepareClickHandlerContext(FullMsgId id) { + return { + .itemId = id, + .elementDelegate = [weak = Ui::MakeWeak(this)] { + return weak + ? (ElementDelegate*)weak + : nullptr; + }, + .sessionWindow = base::make_weak(_controller), + }; +} + void ListWidget::mouseActionUpdate() { auto mousePosition = mapFromGlobal(_mousePosition); auto point = QPoint( diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 2920dae07..0a9e7ff98 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_view_highlight_manager.h" #include "history/history_view_top_toast.h" +struct ClickHandlerContext; + namespace Main { class Session; } // namespace Main @@ -87,7 +89,7 @@ using SelectedItems = std::vector; class ListDelegate { public: virtual Context listContext() = 0; - virtual bool listScrollTo(int top) = 0; // true if scroll was changed. + virtual bool listScrollTo(int top, bool syntetic = true) = 0; virtual void listCancelRequest() = 0; virtual void listDeleteRequest() = 0; virtual rpl::producer listSource( @@ -246,6 +248,11 @@ public: [[nodiscard]] bool showCopyRestrictionForSelected(); [[nodiscard]] bool hasSelectRestriction() const; + [[nodiscard]] std::pair findViewForPinnedTracking( + int top) const; + [[nodiscard]] ClickHandlerContext prepareClickHandlerContext( + FullMsgId id); + // AbstractTooltipShower interface QString tooltipText() const override; QPoint tooltipPos() const override; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 676c20fa6..3f38978b3 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -59,12 +59,12 @@ namespace { } // namespace PinnedMemento::PinnedMemento( - not_null history, + not_null thread, UniversalMsgId highlightId) -: _history(history) +: _thread(thread) , _highlightId(highlightId) { _list.setAroundPosition({ - .fullId = FullMsgId(history->peer->id, highlightId), + .fullId = FullMsgId(_thread->owningHistory()->peer->id, highlightId), .date = TimeId(0), }); } @@ -80,7 +80,7 @@ object_ptr PinnedMemento::createWidget( auto result = object_ptr( parent, controller, - _history); + _thread); result->setInternalState(geometry, this); return result; } @@ -88,10 +88,13 @@ object_ptr PinnedMemento::createWidget( PinnedWidget::PinnedWidget( QWidget *parent, not_null controller, - not_null history) -: Window::SectionWidget(parent, controller, history->peer) -, _history(history->migrateToOrMe()) -, _migratedPeer(_history->peer->migrateFrom()) + not_null thread) +: Window::SectionWidget(parent, controller, thread->owningHistory()->peer) +, _thread(thread->migrateToOrMe()) +, _history(thread->owningHistory()) +, _migratedPeer(thread->asHistory() + ? thread->asHistory()->peer->migrateFrom() + : nullptr) , _topBar(this, controller) , _topBarShadow(this) , _scroll(std::make_unique( @@ -113,7 +116,7 @@ PinnedWidget::PinnedWidget( Window::ChatThemeValueFromPeer( controller, - history->peer + thread->owningHistory()->peer ) | rpl::start_with_next([=](std::shared_ptr &&theme) { _theme = std::move(theme); controller->setChatStyleTheme(_theme); @@ -121,7 +124,7 @@ PinnedWidget::PinnedWidget( _topBar->setActiveChat( TopBarWidget::ActiveChat{ - .key = _history, + .key = _thread, .section = Dialogs::EntryState::Section::Pinned, }, nullptr); @@ -181,9 +184,10 @@ void PinnedWidget::setupClearButton() { Window::HidePinnedBar( controller(), _history->peer, + _thread->topicRootId(), crl::guard(this, callback)); } else { - Window::UnpinAllMessages(controller(), _history); + Window::UnpinAllMessages(controller(), _thread); } }); } @@ -194,7 +198,7 @@ void PinnedWidget::cornerButtonsShowAtPosition( } Data::Thread *PinnedWidget::cornerButtonsThread() { - return _history; + return _thread; } FullMsgId PinnedWidget::cornerButtonsCurrentId() { @@ -238,13 +242,13 @@ void PinnedWidget::updateAdaptiveLayout() { _topBar->height()); } -not_null PinnedWidget::history() const { - return _history; +not_null PinnedWidget::thread() const { + return _thread; } Dialogs::RowDescriptor PinnedWidget::activeChat() const { return { - _history, + _thread, FullMsgId(_history->peer->id, ShowAtUnreadMsgId) }; } @@ -265,8 +269,8 @@ bool PinnedWidget::showInternal( not_null memento, const Window::SectionShow ¶ms) { if (auto logMemento = dynamic_cast(memento.get())) { - if (logMemento->getHistory() == history() - || logMemento->getHistory()->migrateToOrMe() == history()) { + if (logMemento->getThread() == thread() + || logMemento->getThread()->migrateToOrMe() == thread()) { restoreState(logMemento); return true; } @@ -283,7 +287,7 @@ void PinnedWidget::setInternalState( } std::shared_ptr PinnedWidget::createMemento() { - auto result = std::make_shared(history()); + auto result = std::make_shared(thread()); saveState(result.get()); return result; } @@ -431,7 +435,7 @@ Context PinnedWidget::listContext() { return Context::Pinned; } -bool PinnedWidget::listScrollTo(int top) { +bool PinnedWidget::listScrollTo(int top, bool syntetic) { top = std::clamp(top, 0, _scroll->scrollTopMax()); if (_scroll->scrollTop() == top) { updateInnerVisibleArea(); @@ -462,11 +466,11 @@ rpl::producer PinnedWidget::listSource( : (ServerMaxMsgId - 1); return SharedMediaMergedViewer( - &_history->session(), + &_thread->session(), SharedMediaMergedKey( SparseIdsMergedSlice::Key( _history->peer->id, - MsgId(0), // topicRootId + _thread->topicRootId(), _migratedPeer ? _migratedPeer->id : 0, messageId), Storage::SharedMediaType::Pinned), diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 4ac17ebdb..5d0280663 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -42,10 +42,10 @@ public: PinnedWidget( QWidget *parent, not_null controller, - not_null history); + not_null thread); ~PinnedWidget(); - [[nodiscard]] not_null history() const; + [[nodiscard]] not_null thread() const; Dialogs::RowDescriptor activeChat() const override; bool hasTopBarShadow() const override { @@ -79,7 +79,7 @@ public: // ListDelegate interface. Context listContext() override; - bool listScrollTo(int top) override; + bool listScrollTo(int top, bool syntetic = true) override; void listCancelRequest() override; void listDeleteRequest() override; rpl::producer listSource( @@ -165,6 +165,7 @@ private: void setMessagesCount(int count); void refreshClearButtonText(); + const not_null _thread; const not_null _history; std::shared_ptr _theme; PeerData *_migratedPeer = nullptr; @@ -187,7 +188,7 @@ public: using UniversalMsgId = MsgId; explicit PinnedMemento( - not_null history, + not_null thread, UniversalMsgId highlightId = 0); object_ptr createWidget( @@ -196,8 +197,8 @@ public: Window::Column column, const QRect &geometry) override; - [[nodiscard]] not_null getHistory() const { - return _history; + [[nodiscard]] not_null getThread() const { + return _thread; } [[nodiscard]] not_null list() { @@ -208,7 +209,7 @@ public: } private: - const not_null _history; + const not_null _thread; const UniversalMsgId _highlightId = 0; ListMemento _list; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp index 8f62964af..f7105a1cd 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp @@ -27,24 +27,26 @@ constexpr auto kChangeViewerLimit = 2; } // namespace -PinnedTracker::PinnedTracker(not_null history) -: _history(history->migrateToOrMe()) -, _migratedPeer(_history->peer->migrateFrom()) { +PinnedTracker::PinnedTracker(not_null thread) +: _thread(thread->migrateToOrMe()) +, _migratedPeer(_thread->asHistory() + ? _thread->asHistory()->peer->migrateFrom() + : nullptr) { using namespace rpl::mappers; - const auto has = [&](History *history) -> rpl::producer { - auto &changes = _history->session().changes(); - const auto flag = Data::HistoryUpdate::Flag::PinnedMessages; - if (!history) { + const auto has = [&](Data::Thread *thread) -> rpl::producer { + auto &changes = _thread->session().changes(); + const auto flag = Data::EntryUpdate::Flag::HasPinnedMessages; + if (!thread) { return rpl::single(false); } - return changes.historyFlagsValue(history, flag) | rpl::map([=] { - return history->hasPinnedMessages(); + return changes.entryFlagsValue(thread, flag) | rpl::map([=] { + return thread->hasPinnedMessages(); }); }; rpl::combine( - has(_history), + has(_thread), has(_migratedPeer - ? _history->owner().history(_migratedPeer).get() + ? _thread->owner().history(_migratedPeer).get() : nullptr), _1 || _2 ) | rpl::distinct_until_changed( @@ -77,12 +79,13 @@ void PinnedTracker::refreshViewer() { } _dataLifetime.destroy(); _viewerAroundId = _aroundId; + const auto peer = _thread->owningHistory()->peer; SharedMediaMergedViewer( - &_history->peer->session(), + &peer->session(), SharedMediaMergedKey( SparseIdsMergedSlice::Key( - _history->peer->id, - MsgId(0), // topicRootId + peer->id, + _thread->topicRootId(), _migratedPeer ? _migratedPeer->id : 0, _viewerAroundId), Storage::SharedMediaType::Pinned), @@ -100,9 +103,9 @@ void PinnedTracker::refreshViewer() { } refreshCurrentFromSlice(); if (_slice.fullCount == 0) { - _history->setHasPinnedMessages(false); + _thread->setHasPinnedMessages(false); if (_migratedPeer) { - const auto to = _history->owner().history(_migratedPeer); + const auto to = _thread->owner().history(_migratedPeer); to->setHasPinnedMessages(false); } } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h index 65ff734f3..85a8249ad 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h @@ -13,6 +13,7 @@ class History; namespace Data { enum class LoadDirection : char; +class Thread; } // namespace Data namespace HistoryView { @@ -21,7 +22,7 @@ class PinnedTracker final { public: using UniversalMsgId = MsgId; - explicit PinnedTracker(not_null history); + explicit PinnedTracker(not_null thread); ~PinnedTracker(); [[nodiscard]] rpl::producer shownMessageId() const; @@ -44,7 +45,7 @@ private: void refreshViewer(); void refreshCurrentFromSlice(); - const not_null _history; + const not_null _thread; PeerData *_migratedPeer = nullptr; rpl::variable _current; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index dd408159f..083a8e7f5 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_contact_status.h" #include "history/view/history_view_service_message.h" +#include "history/view/history_view_pinned_tracker.h" +#include "history/view/history_view_pinned_section.h" #include "history/history.h" #include "history/history_drag_area.h" #include "history/history_item_components.h" @@ -27,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" +#include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/layers/generic_box.h" #include "ui/item_text_options.h" @@ -38,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" #include "base/timer_rpl.h" +#include "api/api_bot.h" #include "api/api_common.h" #include "api/api_editing.h" #include "api/api_sending.h" @@ -57,7 +61,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "core/application.h" #include "core/shortcuts.h" +#include "core/click_handler_types.h" #include "main/main_session.h" +#include "main/main_session_settings.h" #include "mainwidget.h" #include "data/data_session.h" #include "data/data_user.h" @@ -68,10 +74,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_replies_list.h" #include "data/data_peer_values.h" #include "data/data_changes.h" +#include "data/data_shared_media.h" #include "data/data_send_action.h" #include "storage/storage_media_prepare.h" +#include "storage/storage_shared_media.h" #include "storage/storage_account.h" #include "inline_bots/inline_bot_result.h" +#include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "facades.h" #include "styles/style_chat.h" @@ -270,6 +279,9 @@ RepliesWidget::RepliesWidget( if (_rootView) { _rootView->raise(); } + if (_pinnedBar) { + _pinnedBar->raise(); + } if (_topicReopenBar) { _topicReopenBar->bar().raise(); } @@ -358,9 +370,13 @@ RepliesWidget::RepliesWidget( }, lifetime()); } - setupComposeControls(); setupTopicViewer(); + setupComposeControls(); orderWidgets(); + + if (_pinnedBar) { + _pinnedBar->finishAnimating(); + } } RepliesWidget::~RepliesWidget() { @@ -384,6 +400,9 @@ void RepliesWidget::orderWidgets() { if (_rootView) { _rootView->raise(); } + if (_pinnedBar) { + _pinnedBar->raise(); + } _topBarShadow->raise(); _composeControls->raisePanels(); } @@ -483,9 +502,11 @@ void RepliesWidget::subscribeToTopic() { ) | rpl::start_with_next([=] { const auto height = _topicReopenBar->bar().height(); _scrollTopDelta = (height - _topicReopenBarHeight); - _topicReopenBarHeight = height; - updateControlsGeometry(); - _scrollTopDelta = 0; + if (_scrollTopDelta) { + _topicReopenBarHeight = height; + updateControlsGeometry(); + _scrollTopDelta = 0; + } }, _topicReopenBar->bar().lifetime()); using Flag = Data::TopicUpdate::Flag; @@ -510,6 +531,21 @@ void RepliesWidget::subscribeToTopic() { anim::activation::background)); }, _topicLifetime); + if (!_topic->creating()) { + using EntryUpdateFlag = Data::EntryUpdate::Flag; + session().changes().entryUpdates( + EntryUpdateFlag::HasPinnedMessages + ) | rpl::start_with_next([=](const Data::EntryUpdate &update) { + if (_pinnedTracker + && (update.flags & EntryUpdateFlag::HasPinnedMessages) + && (_topic == update.entry.get())) { + checkPinnedBarState(); + } + }, lifetime()); + + setupPinnedTracker(); + } + _cornerButtons.updateUnreadThingsVisibility(); } @@ -1408,6 +1444,275 @@ void RepliesWidget::refreshUnreadCountBadge(std::optional count) { } } +void RepliesWidget::updatePinnedViewer() { + if (_scroll->isHidden() || !_topic) { + return; + } + const auto visibleBottom = _scroll->scrollTop() + _scroll->height(); + auto [view, offset] = _inner->findViewForPinnedTracking(visibleBottom); + const auto lessThanId = !view + ? (ServerMaxMsgId - 1) + : (view->data()->id + (offset > 0 ? 1 : 0)); + const auto lastClickedId = !_pinnedClickedId + ? (ServerMaxMsgId - 1) + : _pinnedClickedId.msg; + if (_pinnedClickedId + && lessThanId <= lastClickedId + && !_inner->animatedScrolling()) { + _pinnedClickedId = FullMsgId(); + } + if (_pinnedClickedId && !_minPinnedId) { + _minPinnedId = Data::ResolveMinPinnedId(_history->peer, _rootId); + } + if (_pinnedClickedId && _minPinnedId && _minPinnedId >= _pinnedClickedId) { + // After click on the last pinned message we should the top one. + _pinnedTracker->trackAround(ServerMaxMsgId - 1); + } else { + _pinnedTracker->trackAround(std::min(lessThanId, lastClickedId)); + } +} + +void RepliesWidget::checkLastPinnedClickedIdReset( + int wasScrollTop, + int nowScrollTop) { + if (_scroll->isHidden() || !_topic) { + return; + } + if (wasScrollTop < nowScrollTop && _pinnedClickedId) { + // User scrolled down. + _pinnedClickedId = FullMsgId(); + _minPinnedId = std::nullopt; + updatePinnedViewer(); + } +} + +void RepliesWidget::setupPinnedTracker() { + Expects(_topic != nullptr); + + _pinnedTracker = std::make_unique(_topic); + _pinnedBar = nullptr; + + SharedMediaViewer( + &_topic->session(), + Storage::SharedMediaKey( + _topic->channel()->id, + _rootId, + Storage::SharedMediaType::Pinned, + ServerMaxMsgId - 1), + 1, + 1 + ) | rpl::filter([=](const SparseIdsSlice &result) { + return result.fullCount().has_value(); + }) | rpl::start_with_next([=](const SparseIdsSlice &result) { + _topic->setHasPinnedMessages(*result.fullCount() != 0); + checkPinnedBarState(); + }, _topicLifetime); +} + +void RepliesWidget::checkPinnedBarState() { + Expects(_pinnedTracker != nullptr); + Expects(_inner != nullptr); + + const auto peer = _history->peer; + const auto hiddenId = peer->canPinMessages() + ? MsgId(0) + : peer->session().settings().hiddenPinnedMessageId( + peer->id, + _rootId); + const auto currentPinnedId = Data::ResolveTopPinnedId(peer, _rootId); + const auto universalPinnedId = !currentPinnedId + ? int32(0) + : currentPinnedId.msg; + if (universalPinnedId == hiddenId) { + if (_pinnedBar) { + _pinnedTracker->reset(); + auto qobject = base::unique_qptr{ + Ui::WrapAsQObject(this, std::move(_pinnedBar)).get() + }; + auto destroyer = [this, object = std::move(qobject)]() mutable { + object = nullptr; + updateControlsGeometry(); + }; + base::call_delayed( + st::defaultMessageBar.duration, + this, + std::move(destroyer)); + } + return; + } + if (_pinnedBar || !universalPinnedId) { + return; + } + + _pinnedBar = std::make_unique(this, [=] { + return controller()->isGifPausedAtLeastFor( + Window::GifPauseReason::Any); + }); + auto pinnedRefreshed = Info::Profile::SharedMediaCountValue( + _history->peer, + _rootId, + nullptr, + Storage::SharedMediaType::Pinned + ) | rpl::distinct_until_changed( + ) | rpl::map([=](int count) { + if (_pinnedClickedId) { + _pinnedClickedId = FullMsgId(); + _minPinnedId = std::nullopt; + updatePinnedViewer(); + } + return (count > 1); + }) | rpl::distinct_until_changed(); + auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup( + &session(), + _pinnedTracker->shownMessageId()); + rpl::combine( + rpl::duplicate(pinnedRefreshed), + rpl::duplicate(markupRefreshed) + ) | rpl::start_with_next([=](bool many, HistoryItem *item) { + refreshPinnedBarButton(many, item); + }, _pinnedBar->lifetime()); + + _pinnedBar->setContent(rpl::combine( + HistoryView::PinnedBarContent( + &session(), + _pinnedTracker->shownMessageId(), + [bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }), + std::move(pinnedRefreshed), + std::move(markupRefreshed) + ) | rpl::map([](Ui::MessageBarContent &&content, bool, HistoryItem*) { + return std::move(content); + })); + + controller()->adaptive().oneColumnValue( + ) | rpl::start_with_next([=](bool one) { + _pinnedBar->setShadowGeometryPostprocess([=](QRect geometry) { + if (!one) { + geometry.setLeft(geometry.left() + st::lineWidth); + } + return geometry; + }); + }, _pinnedBar->lifetime()); + + _pinnedBar->barClicks( + ) | rpl::start_with_next([=] { + const auto id = _pinnedTracker->currentMessageId(); + if (const auto item = session().data().message(id.message)) { + showAtPosition(item->position()); + if (const auto group = session().data().groups().find(item)) { + // Hack for the case when a non-first item of an album + // is pinned and we still want the 'show last after first'. + _pinnedClickedId = group->items.front()->fullId(); + } else { + _pinnedClickedId = id.message; + } + _minPinnedId = std::nullopt; + updatePinnedViewer(); + } + }, _pinnedBar->lifetime()); + + _pinnedBarHeight = 0; + _pinnedBar->heightValue( + ) | rpl::start_with_next([=](int height) { + if (const auto delta = height - _pinnedBarHeight) { + _pinnedBarHeight = height; + setGeometryWithTopMoved(geometry(), delta); + } + }, _pinnedBar->lifetime()); + + orderWidgets(); + + if (animatingShow()) { + _pinnedBar->hide(); + } +} + +void RepliesWidget::refreshPinnedBarButton(bool many, HistoryItem *item) { + const auto openSection = [=] { + const auto id = _pinnedTracker + ? _pinnedTracker->currentMessageId() + : HistoryView::PinnedId(); + if (!id.message) { + return; + } + controller()->showSection( + std::make_shared(_topic, id.message.msg)); + }; + if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) { + const auto &rows = replyMarkup->data.rows; + if ((rows.size() == 1) && (rows.front().size() == 1)) { + const auto text = rows.front().front().text; + if (!text.isEmpty()) { + auto button = object_ptr( + this, + rpl::single(text), + st::historyPinnedBotButton); + button->setTextTransform( + Ui::RoundButton::TextTransform::NoTransform); + button->setFullRadius(true); + button->setClickedCallback([=] { + Api::ActivateBotCommand( + _inner->prepareClickHandlerContext(item->fullId()), + 0, + 0); + }); + if (button->width() > st::historyPinnedBotButtonMaxWidth) { + button->setFullWidth(st::historyPinnedBotButtonMaxWidth); + } + struct State { + base::unique_qptr menu; + }; + const auto state = button->lifetime().make_state(); + _pinnedBar->contextMenuRequested( + ) | rpl::start_with_next([=, raw = button.data()] { + state->menu = base::make_unique_q(raw); + state->menu->addAction( + tr::lng_settings_events_pinned(tr::now), + openSection); + state->menu->popup(QCursor::pos()); + }, button->lifetime()); + _pinnedBar->setRightButton(std::move(button)); + return; + } + } + } + const auto close = !many; + auto button = object_ptr( + this, + close ? st::historyReplyCancel : st::historyPinnedShowAll); + button->clicks( + ) | rpl::start_with_next([=] { + if (close) { + hidePinnedMessage(); + } else { + openSection(); + } + }, button->lifetime()); + _pinnedBar->setRightButton(std::move(button)); +} + +void RepliesWidget::hidePinnedMessage() { + Expects(_pinnedBar != nullptr); + + const auto id = _pinnedTracker->currentMessageId(); + if (!id.message) { + return; + } + if (_history->peer->canPinMessages()) { + Window::ToggleMessagePinned(controller(), id.message, false); + } else { + const auto callback = [=] { + if (_pinnedTracker) { + checkPinnedBarState(); + } + }; + Window::HidePinnedBar( + controller(), + _history->peer, + _rootId, + crl::guard(this, callback)); + } +} + void RepliesWidget::cornerButtonsShowAtPosition( Data::MessagePosition position) { showAtPosition(position); @@ -1538,6 +1843,9 @@ QPixmap RepliesWidget::grabForShowAnimation(const Window::SectionSlideParams &pa if (_rootView) { _rootView->hide(); } + if (_pinnedBar) { + _pinnedBar->hide(); + } return result; } @@ -1734,13 +2042,18 @@ void RepliesWidget::updateControlsGeometry() { if (_rootView) { _rootView->resizeToWidth(contentWidth); } + if (_pinnedBar) { + _pinnedBar->move(0, _topBar->height()); + _pinnedBar->resizeToWidth(contentWidth); + } const auto bottom = height(); const auto controlsHeight = _joinGroup ? _joinGroup->height() : _composeControls->heightCurrent(); auto top = _topBar->height() - + _rootViewHeight; + + _rootViewHeight + + _pinnedBarHeight; if (_topicReopenBar) { _topicReopenBar->bar().move(0, top); top += _topicReopenBar->bar().height(); @@ -1807,8 +2120,15 @@ void RepliesWidget::updateInnerVisibleArea() { const auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); updatePinnedVisibility(); + updatePinnedViewer(); _cornerButtons.updateJumpDownVisibility(); _cornerButtons.updateUnreadThingsVisibility(); + if (_lastScrollTop != scrollTop) { + if (!_synteticScrollEvent) { + checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop); + } + _lastScrollTop = scrollTop; + } } void RepliesWidget::updatePinnedVisibility() { @@ -1875,6 +2195,9 @@ void RepliesWidget::showFinishedHook() { if (_rootView) { _rootView->show(); } + if (_pinnedBar) { + _pinnedBar->show(); + } // We should setup the drag area only after // the section animation is finished, @@ -1895,14 +2218,17 @@ Context RepliesWidget::listContext() { return Context::Replies; } -bool RepliesWidget::listScrollTo(int top) { +bool RepliesWidget::listScrollTo(int top, bool syntetic) { top = std::clamp(top, 0, _scroll->scrollTopMax()); - if (_scroll->scrollTop() == top) { + const auto scrolled = (_scroll->scrollTop() != top); + _synteticScrollEvent = syntetic; + if (scrolled) { + _scroll->scrollToY(top); + } else if (syntetic) { updateInnerVisibleArea(); - return false; } - _scroll->scrollToY(top); - return true; + _synteticScrollEvent = false; + return syntetic; } void RepliesWidget::listCancelRequest() { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index c83e77398..0b791757d 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -67,6 +67,7 @@ class SendActionPainter; class StickerToast; class TopicReopenBar; class EmptyPainter; +class PinnedTracker; class RepliesWidget final : public Window::SectionWidget @@ -119,7 +120,7 @@ public: // ListDelegate interface. Context listContext() override; - bool listScrollTo(int top) override; + bool listScrollTo(int top, bool syntetic = true) override; void listCancelRequest() override; void listDeleteRequest() override; rpl::producer listSource( @@ -242,7 +243,15 @@ private: void replyToMessage(FullMsgId itemId); void refreshTopBarActiveChat(); void refreshUnreadCountBadge(std::optional count); - void reloadUnreadCountIfNeeded(); + + void hidePinnedMessage(); + void updatePinnedViewer(); + void setupPinnedTracker(); + void checkPinnedBarState(); + void refreshPinnedBarButton(bool many, HistoryItem *item); + void checkLastPinnedClickedIdReset( + int wasScrollTop, + int nowScrollTop); void uploadFile(const QByteArray &fileContent, SendMediaType type); bool confirmSendingFiles( @@ -308,6 +317,13 @@ private: std::unique_ptr _topicReopenBar; std::unique_ptr _emptyPainter; bool _skipScrollEvent = false; + bool _synteticScrollEvent = false; + + std::unique_ptr _pinnedTracker; + std::unique_ptr _pinnedBar; + int _pinnedBarHeight = 0; + FullMsgId _pinnedClickedId; + std::optional _minPinnedId; std::unique_ptr _rootView; int _rootViewHeight = 0; @@ -321,6 +337,7 @@ private: HistoryView::CornerButtons _cornerButtons; rpl::lifetime _topicLifetime; + int _lastScrollTop = 0; int _topicReopenBarHeight = 0; int _scrollTopDelta = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 929899e6d..e476ba7be 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -1050,7 +1050,7 @@ Context ScheduledWidget::listContext() { return Context::History; } -bool ScheduledWidget::listScrollTo(int top) { +bool ScheduledWidget::listScrollTo(int top, bool syntetic) { top = std::clamp(top, 0, _scroll->scrollTopMax()); if (_scroll->scrollTop() == top) { updateInnerVisibleArea(); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 9e4edc65b..48e82455e 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -101,7 +101,7 @@ public: // ListDelegate interface. Context listContext() override; - bool listScrollTo(int top) override; + bool listScrollTo(int top, bool syntetic = true) override; void listCancelRequest() override; void listDeleteRequest() override; rpl::producer listSource( diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 6b5383b23..a2bf10117 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -489,9 +489,10 @@ void TopBarWidget::paintTopBar(Painter &p) { } const auto now = crl::now(); - const auto history = _activeChat.key.history(); + const auto history = _activeChat.key.owningHistory(); const auto folder = _activeChat.key.folder(); - if (const auto topic = _activeChat.key.topic()) { + const auto topic = _activeChat.key.topic(); + if (topic && _activeChat.section == Section::Replies) { p.setPen(st::dialogsNameFg); topic->chatListNameText().drawElided( p, diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 47b036b9f..136ee2035 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -41,48 +41,56 @@ QByteArray SessionSettings::serialize() const { + sizeof(qint32) * 5 + _mediaLastPlaybackPosition.size() * 2 * sizeof(quint64) + sizeof(qint32) * 5 - + _hiddenPinnedMessages.size() * (sizeof(quint64) + sizeof(qint32)) + sizeof(qint32) + (_mutePeriods.size() * sizeof(quint64)) - + sizeof(qint32); + + sizeof(qint32) * 2 + + _hiddenPinnedMessages.size() * (sizeof(quint64) * 3); auto result = QByteArray(); result.reserve(size); { QDataStream stream(&result, QIODevice::WriteOnly); stream.setVersion(QDataStream::Qt_5_1); - stream << qint32(kVersionTag) << qint32(kVersion); - stream << static_cast(_selectorTab); - stream << qint32(_groupStickersSectionHidden.size()); + stream + << qint32(kVersionTag) << qint32(kVersion) + << static_cast(_selectorTab) + << qint32(_groupStickersSectionHidden.size()); for (const auto &peerId : _groupStickersSectionHidden) { stream << SerializePeerId(peerId); } - stream << qint32(_supportSwitch); - stream << qint32(_supportFixChatsOrder ? 1 : 0); - stream << qint32(_supportTemplatesAutocomplete ? 1 : 0); - stream << qint32(_supportChatsTimeSlice.current()); - stream << autoDownload; - stream << qint32(_supportAllSearchResults.current() ? 1 : 0); - stream << qint32(_archiveCollapsed.current() ? 1 : 0); - stream << qint32(_archiveInMainMenu.current() ? 1 : 0); - stream << qint32(_skipArchiveInSearch.current() ? 1 : 0); - stream << qint32(_mediaLastPlaybackPosition.size()); + stream + << qint32(_supportSwitch) + << qint32(_supportFixChatsOrder ? 1 : 0) + << qint32(_supportTemplatesAutocomplete ? 1 : 0) + << qint32(_supportChatsTimeSlice.current()) + << autoDownload + << qint32(_supportAllSearchResults.current() ? 1 : 0) + << qint32(_archiveCollapsed.current() ? 1 : 0) + << qint32(_archiveInMainMenu.current() ? 1 : 0) + << qint32(_skipArchiveInSearch.current() ? 1 : 0) + << qint32(_mediaLastPlaybackPosition.size()); for (const auto &[id, time] : _mediaLastPlaybackPosition) { stream << quint64(id) << qint64(time); } - stream << qint32(0); - stream << qint32(_dialogsFiltersEnabled ? 1 : 0); - stream << qint32(_supportAllSilent ? 1 : 0); - stream << qint32(_photoEditorHintShowsCount); - stream << qint32(_hiddenPinnedMessages.size()); - for (const auto &[key, value] : _hiddenPinnedMessages) { - stream << SerializePeerId(key) << qint64(value.bare); - } - stream << qint32(_mutePeriods.size()); + stream + << qint32(0) // very old _hiddenPinnedMessages.size()); + << qint32(_dialogsFiltersEnabled ? 1 : 0) + << qint32(_supportAllSilent ? 1 : 0) + << qint32(_photoEditorHintShowsCount) + << qint32(0) // old _hiddenPinnedMessages.size()); + << qint32(_mutePeriods.size()); for (const auto &period : _mutePeriods) { stream << quint64(period); } - stream << qint32(_skipPremiumStickersSet ? 1 : 0); + stream + << qint32(_skipPremiumStickersSet ? 1 : 0) + << qint32(_hiddenPinnedMessages.size()); + for (const auto &[key, value] : _hiddenPinnedMessages) { + stream + << SerializePeerId(key.peerId) + << qint64(key.topicRootId.bare) + << qint64(value.bare); + } } return result; } @@ -139,7 +147,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { QByteArray appVideoPipGeometry = app.videoPipGeometry(); std::vector appDictionariesEnabled; qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0; - base::flat_map hiddenPinnedMessages; + base::flat_map hiddenPinnedMessages; qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0; qint32 supportAllSilent = _supportAllSilent ? 1 : 0; qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount; @@ -334,7 +342,9 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { "Bad data for SessionSettings::addFromSerialized()")); return; } - hiddenPinnedMessages.emplace(DeserializePeerId(key), value); + hiddenPinnedMessages.emplace( + ThreadId{ DeserializePeerId(key), MsgId(0) }, + value); } } } @@ -360,7 +370,9 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { "Bad data for SessionSettings::addFromSerialized()")); return; } - hiddenPinnedMessages.emplace(DeserializePeerId(key), value); + hiddenPinnedMessages.emplace( + ThreadId{ DeserializePeerId(key), MsgId(0) }, + value); } } } @@ -378,6 +390,26 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> skipPremiumStickersSet; } + if (!stream.atEnd()) { + auto count = qint32(0); + stream >> count; + if (stream.status() == QDataStream::Ok) { + for (auto i = 0; i != count; ++i) { + auto keyPeerId = quint64(); + auto keyTopicRootId = qint64(); + auto value = qint64(); + stream >> keyPeerId >> keyTopicRootId >> value; + if (stream.status() != QDataStream::Ok) { + LOG(("App Error: " + "Bad data for SessionSettings::addFromSerialized()")); + return; + } + hiddenPinnedMessages.emplace( + ThreadId{ DeserializePeerId(keyPeerId), keyTopicRootId }, + value); + } + } + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -561,6 +593,25 @@ rpl::producer SessionSettings::skipArchiveInSearchChanges() const { return _skipArchiveInSearch.changes(); } +MsgId SessionSettings::hiddenPinnedMessageId( + PeerId peerId, + MsgId topicRootId) const { + const auto i = _hiddenPinnedMessages.find({ peerId, topicRootId }); + return (i != end(_hiddenPinnedMessages)) ? i->second : 0; +} + +void SessionSettings::setHiddenPinnedMessageId( + PeerId peerId, + MsgId topicRootId, + MsgId msgId) { + const auto id = ThreadId{ peerId, topicRootId }; + if (msgId) { + _hiddenPinnedMessages[id] = msgId; + } else { + _hiddenPinnedMessages.remove(id); + } +} + bool SessionSettings::photoEditorHintShown() const { return _photoEditorHintShowsCount < kPhotoEditorHintMaxShowsCount; } diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index fb50ea0b3..8ccbb7761 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -101,17 +101,13 @@ public: return _hadLegacyCallsPeerToPeerNobody; } - [[nodiscard]] MsgId hiddenPinnedMessageId(PeerId peerId) const { - const auto i = _hiddenPinnedMessages.find(peerId); - return (i != end(_hiddenPinnedMessages)) ? i->second : 0; - } - void setHiddenPinnedMessageId(PeerId peerId, MsgId msgId) { - if (msgId) { - _hiddenPinnedMessages[peerId] = msgId; - } else { - _hiddenPinnedMessages.remove(peerId); - } - } + [[nodiscard]] MsgId hiddenPinnedMessageId( + PeerId peerId, + MsgId topicRootId = 0) const; + void setHiddenPinnedMessageId( + PeerId peerId, + MsgId topicRootId, + MsgId msgId); [[nodiscard]] bool dialogsFiltersEnabled() const { return _dialogsFiltersEnabled; @@ -137,6 +133,15 @@ private: static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; static constexpr auto kPhotoEditorHintMaxShowsCount = 5; + struct ThreadId { + PeerId peerId; + MsgId topicRootId; + + friend inline constexpr auto operator<=>( + ThreadId, + ThreadId) = default; + }; + ChatHelpers::SelectorTab _selectorTab; // per-window base::flat_set _groupStickersSectionHidden; bool _hadLegacyCallsPeerToPeerNobody = false; @@ -145,7 +150,7 @@ private: rpl::variable _archiveInMainMenu = false; rpl::variable _skipArchiveInSearch = false; std::vector> _mediaLastPlaybackPosition; - base::flat_map _hiddenPinnedMessages; + base::flat_map _hiddenPinnedMessages; bool _dialogsFiltersEnabled = false; int _photoEditorHintShowsCount = 0; std::vector _mutePeriods; diff --git a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp index 20458563e..bba67a1b9 100644 --- a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp +++ b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp @@ -1082,6 +1082,7 @@ bool ReadSetting( for (auto i = v.begin(), e = v.end(); i != e; ++i) { context.sessionSettings().setHiddenPinnedMessageId( DeserializePeerId(i.key()), + MsgId(0), // topicRootId MsgId(i.value())); } context.legacyRead = true; diff --git a/Telegram/SourceFiles/storage/storage_facade.cpp b/Telegram/SourceFiles/storage/storage_facade.cpp index 3891139a4..5ddb39781 100644 --- a/Telegram/SourceFiles/storage/storage_facade.cpp +++ b/Telegram/SourceFiles/storage/storage_facade.cpp @@ -20,6 +20,7 @@ public: void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveAll &&query); void invalidate(SharedMediaInvalidateBottom &&query); + void unload(SharedMediaUnloadThread &&query); rpl::producer query(SharedMediaQuery &&query) const; SharedMediaResult snapshot(const SharedMediaQuery &query) const; bool empty(const SharedMediaKey &key) const; @@ -65,6 +66,10 @@ void Facade::Impl::invalidate(SharedMediaInvalidateBottom &&query) { _sharedMedia.invalidate(std::move(query)); } +void Facade::Impl::unload(SharedMediaUnloadThread &&query) { + _sharedMedia.unload(std::move(query)); +} + rpl::producer Facade::Impl::query(SharedMediaQuery &&query) const { return _sharedMedia.query(std::move(query)); } @@ -144,6 +149,10 @@ void Facade::invalidate(SharedMediaInvalidateBottom &&query) { _impl->invalidate(std::move(query)); } +void Facade::unload(SharedMediaUnloadThread &&query) { + _impl->unload(std::move(query)); +} + rpl::producer Facade::query(SharedMediaQuery &&query) const { return _impl->query(std::move(query)); } diff --git a/Telegram/SourceFiles/storage/storage_facade.h b/Telegram/SourceFiles/storage/storage_facade.h index 56456373e..68cfdde27 100644 --- a/Telegram/SourceFiles/storage/storage_facade.h +++ b/Telegram/SourceFiles/storage/storage_facade.h @@ -24,6 +24,7 @@ struct SharedMediaAddSlice; struct SharedMediaRemoveOne; struct SharedMediaRemoveAll; struct SharedMediaInvalidateBottom; +struct SharedMediaUnloadThread; struct SharedMediaQuery; struct SharedMediaKey; using SharedMediaResult = SparseIdsListResult; @@ -47,6 +48,7 @@ public: void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveAll &&query); void invalidate(SharedMediaInvalidateBottom &&query); + void unload(SharedMediaUnloadThread &&query); rpl::producer query(SharedMediaQuery &&query) const; SharedMediaResult snapshot(const SharedMediaQuery &query) const; diff --git a/Telegram/SourceFiles/storage/storage_shared_media.cpp b/Telegram/SourceFiles/storage/storage_shared_media.cpp index 60ea325d4..a27d0cba2 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.cpp +++ b/Telegram/SourceFiles/storage/storage_shared_media.cpp @@ -109,6 +109,10 @@ void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) { _bottomInvalidated.fire(std::move(query)); } +void SharedMedia::unload(SharedMediaUnloadThread &&query) { + _lists.erase({ query.peerId, query.topicRootId }); +} + rpl::producer SharedMedia::query(SharedMediaQuery &&query) const { Expects(IsValidSharedMediaType(query.key.type)); diff --git a/Telegram/SourceFiles/storage/storage_shared_media.h b/Telegram/SourceFiles/storage/storage_shared_media.h index c2d5aafdc..cf285a6df 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.h +++ b/Telegram/SourceFiles/storage/storage_shared_media.h @@ -197,6 +197,18 @@ struct SharedMediaSliceUpdate { SparseIdsSliceUpdate data; }; +struct SharedMediaUnloadThread { + SharedMediaUnloadThread( + PeerId peerId, + MsgId topicRootId) + : peerId(peerId) + , topicRootId(topicRootId) { + } + + PeerId peerId = 0; + MsgId topicRootId = 0; +}; + class SharedMedia { public: using Type = SharedMediaType; @@ -207,6 +219,7 @@ public: void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveAll &&query); void invalidate(SharedMediaInvalidateBottom &&query); + void unload(SharedMediaUnloadThread &&query); rpl::producer query(SharedMediaQuery &&query) const; SharedMediaResult snapshot(const SharedMediaQuery &query) const; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 06be3da9a..3515d7d44 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1651,19 +1651,26 @@ void ToggleMessagePinned( void HidePinnedBar( not_null navigation, not_null peer, + MsgId topicRootId, Fn onHidden) { const auto callback = crl::guard(navigation, [=](Fn &&close) { close(); auto &session = peer->session(); - const auto migrated = peer->migrateFrom(); - const auto top = Data::ResolveTopPinnedId(peer, MsgId(0), migrated); + const auto migrated = topicRootId ? nullptr : peer->migrateFrom(); + const auto top = Data::ResolveTopPinnedId( + peer, + topicRootId, + migrated); const auto universal = !top ? MsgId(0) : (migrated && !peerIsChannel(top.peer)) ? (top.msg - ServerMaxMsgId) : top.msg; if (universal) { - session.settings().setHiddenPinnedMessageId(peer->id, universal); + session.settings().setHiddenPinnedMessageId( + peer->id, + topicRootId, + universal); session.saveSettingsDelayed(); if (onHidden) { onHidden(); @@ -1683,11 +1690,18 @@ void HidePinnedBar( void UnpinAllMessages( not_null navigation, - not_null history) { + not_null thread) { + const auto weak = base::make_weak(thread); const auto callback = crl::guard(navigation, [=](Fn &&close) { close(); - const auto api = &history->session().api(); + const auto strong = weak.get(); + if (!strong || !strong->asHistory()) { // #TODO forum pinned + return; + } + const auto api = &strong->session().api(); const auto sendRequest = [=](auto self) -> void { + const auto history = strong->owningHistory(); + const auto topicRootId = strong->topicRootId(); api->request(MTPmessages_UnpinAllMessages( history->peer->input )).done([=](const MTPmessages_AffectedHistory &result) { @@ -1696,7 +1710,7 @@ void UnpinAllMessages( if (offset > 0) { self(self); } else { - history->unpinAllMessages(); + history->unpinMessagesFor(topicRootId); } }).send(); }; diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index f834d237f..b3257d62b 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -25,6 +25,7 @@ class Folder; class Session; struct ForwardDraft; class ForumTopic; +class Thread; } // namespace Data namespace Dialogs { @@ -130,9 +131,10 @@ void ToggleMessagePinned( void HidePinnedBar( not_null navigation, not_null peer, + MsgId topicRootId, Fn onHidden); void UnpinAllMessages( not_null navigation, - not_null history); + not_null thread); } // namespace Window