diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 1415247c6d..cb609a7ec5 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -248,6 +248,7 @@ public: not_null navigation, not_null box, not_null peer); + ~Controller(); [[nodiscard]] object_ptr createContent(); void setFocus(); @@ -388,6 +389,8 @@ Controller::Controller( _peer->updateFull(); } +Controller::~Controller() = default; + void Controller::subscribeToMigration() { SubscribeToMigration( _peer, @@ -815,7 +818,7 @@ void Controller::fillForumButton() { AddButtonWithText( _controls.buttonsLayout, - rpl::single(u"Forum"_q), // #TODO forum + rpl::single(u"Forum"_q), // #TODO forum lang rpl::single(QString()), [] {}, { &st::settingsIconGroup, Settings::kIconPurple } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 14903e079d..8234c8fbbf 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -60,13 +60,9 @@ Data::ChatBotCommands::Changed MegagroupInfo::setBotCommands( return _botCommands.update(list); } -void MegagroupInfo::setIsForum(not_null that, bool is) { - if (is == (_forum != nullptr)) { - return; - } else if (is) { +void MegagroupInfo::ensureForum(not_null that) { + if (!_forum) { _forum = std::make_unique(that->owner().history(that)); - } else { - _forum = nullptr; } } @@ -74,35 +70,15 @@ Data::Forum *MegagroupInfo::forum() const { return _forum.get(); } +std::unique_ptr MegagroupInfo::takeForumData() { + return std::move(_forum); +} + ChannelData::ChannelData(not_null owner, PeerId id) : PeerData(owner, id) , inputChannel( MTP_inputChannel(MTP_long(peerToChannel(id).bare), MTP_long(0))) , _ptsWaiter(&owner->session().updates()) { - _flags.changes( - ) | rpl::start_with_next([=](const Flags::Change &change) { - if (change.diff - & (Flag::Left | Flag::Forbidden)) { - if (const auto chat = getMigrateFromChat()) { - session().changes().peerUpdated(chat, UpdateFlag::Migration); - session().changes().peerUpdated(this, UpdateFlag::Migration); - } - } - if (change.diff & Flag::Megagroup) { - if (change.value & Flag::Megagroup) { - if (!mgInfo) { - mgInfo = std::make_unique(); - } - } else if (mgInfo) { - mgInfo = nullptr; - } - } - if (change.diff & Flag::CallNotEmpty) { - if (const auto history = this->owner().historyLoaded(this)) { - history->updateChatListEntry(); - } - } - }, _lifetime); } void ChannelData::setPhoto(const MTPChatPhoto &photo) { @@ -132,6 +108,44 @@ void ChannelData::setAccessHash(uint64 accessHash) { MTP_long(accessHash)); } +void ChannelData::setFlags(ChannelDataFlags which) { + const auto diff = flags() ^ which; + if ((which & Flag::Megagroup) && !mgInfo) { + mgInfo = std::make_unique(); + } + + // Let Data::Forum live till the end of _flags.set. + // That way the data can be used in changes handler. + // Example: render frame for forum auto-closing animation. + const auto taken = ((diff & Flag::Forum) && !(which & Flag::Forum)) + ? mgInfo->takeForumData() + : nullptr; + + if ((diff & Flag::Forum) && (which & Flag::Forum)) { + mgInfo->ensureForum(this); + } + _flags.set(which); + if (diff & (Flag::Left | Flag::Forbidden)) { + if (const auto chat = getMigrateFromChat()) { + session().changes().peerUpdated(chat, UpdateFlag::Migration); + session().changes().peerUpdated(this, UpdateFlag::Migration); + } + } + if (diff & Flag::CallNotEmpty) { + if (const auto history = this->owner().historyLoaded(this)) { + history->updateChatListEntry(); + } + } +} + +void ChannelData::addFlags(ChannelDataFlags which) { + setFlags(flags() | which); +} + +void ChannelData::removeFlags(ChannelDataFlags which) { + setFlags(flags() & ~which); +} + void ChannelData::setInviteLink(const QString &newInviteLink) { _inviteLink = newInviteLink; } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index d9cd174231..ce7b0fdc21 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -96,8 +96,9 @@ public: return _botCommands; } - void setIsForum(not_null that, bool is); + void ensureForum(not_null that); [[nodiscard]] Data::Forum *forum() const; + [[nodiscard]] std::unique_ptr takeForumData(); std::deque> lastParticipants; base::flat_map, Admin> lastAdmins; @@ -148,15 +149,9 @@ public: void setPhoto(const MTPChatPhoto &photo); void setAccessHash(uint64 accessHash); - void setFlags(ChannelDataFlags which) { - _flags.set(which); - } - void addFlags(ChannelDataFlags which) { - _flags.add(which); - } - void removeFlags(ChannelDataFlags which) { - _flags.remove(which); - } + void setFlags(ChannelDataFlags which); + void addFlags(ChannelDataFlags which); + void removeFlags(ChannelDataFlags which); [[nodiscard]] auto flags() const { return _flags.current(); } @@ -490,8 +485,6 @@ private: int _slowmodeSeconds = 0; TimeId _slowmodeLastMessage = 0; - rpl::lifetime _lifetime; - }; namespace Data { diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 13bc0ecdc3..cbba0135e3 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -29,12 +29,25 @@ constexpr auto kTopicsPerPage = 500; } // namespace -Forum::Forum(not_null forum) -: _forum(forum) -, _topicsList(&forum->session(), FilterId(0), rpl::single(1)) { +Forum::Forum(not_null history) +: _history(history) +, _topicsList(&_history->session(), FilterId(0), rpl::single(1)) { + Expects(_history->peer->isChannel()); } -Forum::~Forum() = default; +Forum::~Forum() { + if (_requestId) { + _history->session().api().request(_requestId).cancel(); + } +} + +not_null Forum::history() const { + return _history; +} + +not_null Forum::channel() const { + return _history->peer->asChannel(); +} not_null Forum::topicsList() { return &_topicsList; @@ -44,28 +57,24 @@ void Forum::requestTopics() { if (_allLoaded || _requestId) { return; } - const auto forum = _forum; const auto firstLoad = !_offsetDate; const auto loadCount = firstLoad ? kTopicsFirstLoad : kTopicsPerPage; - const auto api = &forum->session().api(); + const auto api = &_history->session().api(); _requestId = api->request(MTPchannels_GetForumTopics( MTP_flags(0), - forum->peer->asChannel()->inputChannel, + channel()->inputChannel, MTPstring(), // q MTP_int(_offsetDate), MTP_int(_offsetId), MTP_int(_offsetTopicId), MTP_int(loadCount) )).done([=](const MTPmessages_ForumTopics &result) { - if (!forum->peer->isForum()) { - return; - } const auto &data = result.data(); - const auto owner = &forum->owner(); + const auto owner = &channel()->owner(); owner->processUsers(data.vusers()); owner->processChats(data.vchats()); owner->processMessages(data.vmessages(), NewMessageType::Existing); - forum->peer->asChannel()->ptsReceived(data.vpts().v); + channel()->ptsReceived(data.vpts().v); const auto &list = data.vtopics().v; for (const auto &topic : list) { const auto rootId = MsgId(topic.data().vid().v); @@ -74,7 +83,7 @@ void Forum::requestTopics() { const auto raw = creating ? _topics.emplace( rootId, - std::make_unique(forum, rootId) + std::make_unique(_history, rootId) ).first->second.get() : i->second.get(); raw->applyTopic(topic); @@ -107,7 +116,7 @@ void Forum::applyTopicAdded(MsgId rootId, const QString &title) { } else { const auto raw = _topics.emplace( rootId, - std::make_unique(_forum, rootId) + std::make_unique(_history, rootId) ).first->second.get(); raw->applyTitle(title); raw->addToChatList(FilterId(), topicsList()); @@ -122,10 +131,18 @@ void Forum::applyTopicRemoved(MsgId rootId) { } ForumTopic *Forum::topicFor(not_null item) { - if (const auto rootId = item->replyToTop()) { + return topicFor(item->topicRootId()); +} + +ForumTopic *Forum::topicFor(MsgId rootId) { + if (rootId != ForumTopic::kGeneralId) { if (const auto i = _topics.find(rootId); i != end(_topics)) { return i->second.get(); } + } else { + // #TODO forum lang + applyTopicAdded(rootId, "General! Created."); + return _topics.find(rootId)->second.get(); } return nullptr; } @@ -148,13 +165,13 @@ void ShowAddForumTopic( object_ptr( box, st::defaultInputField, - rpl::single(u"Topic Title"_q))); // #TODO forum + rpl::single(u"Topic Title"_q))); // #TODO forum lang const auto message = box->addRow( object_ptr( box, st::newGroupDescription, Ui::InputField::Mode::MultiLine, - rpl::single(u"Message"_q))); // #TODO forum + rpl::single(u"Message"_q))); // #TODO forum lang box->setFocusCallback([=] { title->setFocusFast(); }); diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index c64512135e..e44aae7bae 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -20,9 +20,11 @@ namespace Data { class Forum final { public: - explicit Forum(not_null forum); + explicit Forum(not_null history); ~Forum(); + [[nodiscard]] not_null history() const; + [[nodiscard]] not_null channel() const; [[nodiscard]] not_null topicsList(); void requestTopics(); @@ -32,9 +34,10 @@ public: void applyTopicAdded(MsgId rootId, const QString &title); void applyTopicRemoved(MsgId rootId); [[nodiscard]] ForumTopic *topicFor(not_null item); + [[nodiscard]] ForumTopic *topicFor(MsgId rootId); private: - const not_null _forum; + const not_null _history; base::flat_map> _topics; Dialogs::MainList _topicsList; diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index b588498c85..70ed243dd7 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -198,6 +198,9 @@ void ForumTopic::requestChatListMessage() { } TimeId ForumTopic::adjustedChatListTimeId() const { + if (isGeneral()) { + return TimeId(1); + } const auto result = chatListTimeId(); #if 0 // #TODO forum if (const auto draft = cloudDraft()) { @@ -236,10 +239,10 @@ bool ForumTopic::lastServerMessageKnown() const { } void ForumTopic::applyTitle(const QString &title) { - if (_title == title) { + if (_title == title || (isGeneral() && !_title.isEmpty())) { return; } - _title = title; + _title = isGeneral() ? "General! Topic." : title; // #TODO forum lang ++_titleVersion; indexTitleParts(); updateChatListEntry(); diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 9189e19571..3d10548305 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -26,6 +26,8 @@ class Session; class ForumTopic final : public Dialogs::Entry { public: + static constexpr auto kGeneralId = 1; + ForumTopic(not_null forum, MsgId rootId); ForumTopic(const ForumTopic &) = delete; @@ -33,6 +35,9 @@ public: [[nodiscard]] not_null forum() const; [[nodiscard]] MsgId rootId() const; + [[nodiscard]] bool isGeneral() const { + return (_rootId == kGeneralId); + } void applyTopic(const MTPForumTopic &topic); @@ -88,7 +93,6 @@ private: int unreadCount, MsgId maxInboxRead, MsgId maxOutboxRead); - void applyChatListMessage(HistoryItem *item); void setLastMessage(HistoryItem *item); void setLastServerMessage(HistoryItem *item); diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index 25551575d8..d45b691ee6 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_messages.h" +#include "data/data_forum_topic.h" #include "lang/lang_keys.h" #include "apiwrap.h" @@ -125,7 +126,10 @@ void RepliesList::appendClientSideMessages(MessagesSlice &slice) { } slice.ids.reserve(messages.size()); for (const auto &item : messages) { - if (item->replyToTop() != _rootId) { + const auto checkId = (_rootId == ForumTopic::kGeneralId) + ? item->topicRootId() + : item->replyToTop(); + if (!item->inThread(_rootId)) { continue; } slice.ids.push_back(item->fullId()); @@ -143,7 +147,7 @@ void RepliesList::appendClientSideMessages(MessagesSlice &slice) { dates.push_back(message->date()); } for (const auto &item : messages) { - if (item->replyToTop() != _rootId) { + if (!item->inThread(_rootId)) { continue; } const auto date = item->date(); @@ -341,12 +345,15 @@ bool RepliesList::buildFromData(not_null viewer) { auto nearestToAround = std::optional(); slice->ids.reserve(useAfter + useBefore); for (auto j = i - useAfter, e = i + useBefore; j != e; ++j) { - if (!nearestToAround && *j < around) { + const auto id = *j; + if (id == _rootId) { + continue; + } else if (!nearestToAround && id < around) { nearestToAround = (j == i - useAfter) - ? *j + ? id : *(j - 1); } - slice->ids.emplace_back(peerId, *j); + slice->ids.emplace_back(peerId, id); } slice->nearestToAround = FullMsgId( peerId, @@ -380,7 +387,7 @@ bool RepliesList::applyUpdate( } } } - if (update.item->replyToTop() != _rootId) { + if (!update.item->inThread(_rootId)) { return false; } const auto id = update.item->id; @@ -613,7 +620,7 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { auto skipped = 0; for (const auto &message : list) { if (const auto item = owner.addNewMessage(message, localFlags, type)) { - if (item->replyToTop() == _rootId) { + if (item->inThread(_rootId)) { if (toFront && item->id > _list.front()) { refreshed.push_back(item->id); } else if (_list.empty() || item->id < _list.back()) { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 9cd9940507..f3dc1895a6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -807,12 +807,7 @@ not_null Session::processChat(const MTPChat &data) { | ((data.is_forum() && data.is_megagroup()) ? Flag::Forum : Flag()); - const auto wasForum = channel->isForum(); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); - if (const auto nowForum = channel->isForum(); nowForum != wasForum) { - Assert(channel->mgInfo != nullptr); - channel->mgInfo->setIsForum(channel, nowForum); - } channel->setName( qs(data.vtitle()), diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 0b6b665fc3..e54e6f13be 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -404,12 +404,13 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { } void InnerWidget::changeOpenedForum(ChannelData *forum) { - if (_openedForum == forum) { + const auto now = _openedForum ? _openedForum->channel().get() : nullptr; + if (now == forum) { return; } stopReorderPinned(); clearSelection(); - _openedForum = forum; + _openedForum = forum ? forum->forum() : nullptr; _openedForumLifetime.destroy(); if (forum) { @@ -1791,7 +1792,7 @@ not_null InnerWidget::shownDialogs() const { return _filterId ? session().data().chatsFilters().chatsList(_filterId)->indexed() : _openedForum - ? _openedForum->forum()->topicsList()->indexed() + ? _openedForum->topicsList()->indexed() : session().data().chatsList(_openedFolder)->indexed(); } @@ -2296,7 +2297,7 @@ Data::Folder *InnerWidget::shownFolder() const { return _openedFolder; } -ChannelData *InnerWidget::shownForum() const { +Data::Forum *InnerWidget::shownForum() const { return _openedForum; } @@ -2410,7 +2411,7 @@ void InnerWidget::refreshEmptyLabel() { } else if (_emptyState == EmptyState::EmptyFolder) { editOpenedFilter(); } else if (_emptyState == EmptyState::EmptyForum) { - Data::ShowAddForumTopic(_controller, _openedForum); + Data::ShowAddForumTopic(_controller, _openedForum->channel()); } }); _empty->setVisible(_state == WidgetState::Default); @@ -3286,7 +3287,9 @@ void InnerWidget::setupShortcuts() { }); request->check(Command::ChatSelf) && request->handle([=] { if (_openedForum) { - Data::ShowAddForumTopic(_controller, _openedForum); + Data::ShowAddForumTopic( + _controller, + _openedForum->channel()); } else { _controller->content()->choosePeer( session().userPeerId(), diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 1098952205..b078144a2f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -36,6 +36,8 @@ class SessionController; namespace Data { class CloudImageView; +class Folder; +class Forum; } // namespace Data namespace Dialogs::Ui { @@ -110,7 +112,7 @@ public: void scrollToEntry(const RowDescriptor &entry); Data::Folder *shownFolder() const; - ChannelData *shownForum() const; + Data::Forum *shownForum() const; int32 lastSearchDate() const; PeerData *lastSearchPeer() const; MsgId lastSearchId() const; @@ -351,7 +353,7 @@ private: Qt::MouseButton _pressButton = Qt::LeftButton; Data::Folder *_openedFolder = nullptr; - ChannelData *_openedForum = nullptr; + Data::Forum *_openedForum = nullptr; rpl::lifetime _openedForumLifetime; std::vector> _collapsedRows; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index fc2d076924..575fe3f5f7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -260,50 +260,7 @@ Widget::Widget( }, lifetime()); _inner->chosenRow( ) | rpl::start_with_next([=](const ChosenRow &row) { - const auto openSearchResult = !controller->selectingPeer() - && row.filteredRow; - const auto history = row.key.history(); - if (const auto topic = row.key.topic()) { - controller->showRepliesForMessage( - topic->forum(), - topic->rootId()); - } else if (history && history->peer->isForum()) { - controller->openForum(history->peer->asChannel()); - } else if (history) { - const auto peer = history->peer; - const auto showAtMsgId = controller->uniqueChatsInSearchResults() - ? ShowAtUnreadMsgId - : row.message.fullId.msg; - if (row.newWindow && controller->canShowSeparateWindow(peer)) { - const auto active = controller->activeChatCurrent(); - const auto fromActive = active.history() - ? (active.history()->peer == peer) - : false; - const auto toSeparate = [=] { - Core::App().ensureSeparateWindowForPeer( - peer, - showAtMsgId); - }; - if (fromActive) { - controller->window().preventOrInvoke([=] { - controller->content()->ui_showPeerHistory( - 0, - Window::SectionShow::Way::ClearStack, - 0); - toSeparate(); - }); - } else { - toSeparate(); - } - } else { - controller->content()->choosePeer(peer->id, showAtMsgId); - } - } else if (const auto folder = row.key.folder()) { - controller->openFolder(folder); - } - if (openSearchResult && !session().supportMode()) { - escape(); - } + chosenRow(row); }, lifetime()); _scroll->geometryChanged( @@ -422,6 +379,53 @@ Widget::Widget( setupDownloadBar(); } +void Widget::chosenRow(const ChosenRow &row) { + const auto openSearchResult = !controller()->selectingPeer() + && row.filteredRow; + const auto history = row.key.history(); + if (const auto topic = row.key.topic()) { + controller()->showRepliesForMessage( + topic->forum(), + topic->rootId()); + } else if (history && history->peer->isForum()) { + controller()->openForum(history->peer->asChannel()); + } else if (history) { + const auto peer = history->peer; + const auto showAtMsgId = controller()->uniqueChatsInSearchResults() + ? ShowAtUnreadMsgId + : row.message.fullId.msg; + if (row.newWindow && controller()->canShowSeparateWindow(peer)) { + const auto active = controller()->activeChatCurrent(); + const auto fromActive = active.history() + ? (active.history()->peer == peer) + : false; + const auto toSeparate = [=] { + Core::App().ensureSeparateWindowForPeer( + peer, + showAtMsgId); + }; + if (fromActive) { + controller()->window().preventOrInvoke([=] { + controller()->content()->ui_showPeerHistory( + 0, + Window::SectionShow::Way::ClearStack, + 0); + toSeparate(); + }); + } else { + toSeparate(); + } + } else { + controller()->content()->choosePeer(peer->id, showAtMsgId); + } + } else if (const auto folder = row.key.folder()) { + controller()->openFolder(folder); + } + if (openSearchResult && !session().supportMode()) { + escape(); + } +} + void Widget::setGeometryWithTopMoved( const QRect &newGeometry, int topDelta) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 0c6799b9ab..72334ba639 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -116,6 +116,7 @@ private: Internal, }; + void chosenRow(const ChosenRow &row); void listScrollUpdated(); void cancelSearchInChat(); void filterCursorMoved(int from = -1, int to = -1); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index d593d7dbe9..806acf6e1c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_messages.h" #include "data/data_media_types.h" #include "data/data_folder.h" +#include "data/data_forum_topic.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -1058,6 +1059,21 @@ MsgId HistoryItem::replyToTop() const { return 0; } +MsgId HistoryItem::topicRootId() const { + if (const auto reply = Get() + ; reply && reply->topicPost) { + return reply->replyToTop(); + } + return Data::ForumTopic::kGeneralId; +} + +MsgId HistoryItem::inThread(MsgId rootId) const { + const auto checkId = (rootId == Data::ForumTopic::kGeneralId) + ? topicRootId() + : replyToTop(); + return (checkId == rootId); +} + not_null HistoryItem::author() const { return (isPost() && !isSponsored()) ? history()->peer : from(); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index b6e66fece1..baa9259cbb 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -410,6 +410,8 @@ public: [[nodiscard]] MsgId replyToId() const; [[nodiscard]] MsgId replyToTop() const; + [[nodiscard]] MsgId topicRootId() const; + [[nodiscard]] MsgId inThread(MsgId rootId) const; [[nodiscard]] not_null author() const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 21f921ec86..183185e8c3 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -259,7 +259,7 @@ bool HistoryMessageReply::updateData( } else { holder->history()->owner().registerDependentMessage( holder, - replyToMsg); + replyToMsg.get()); } } } @@ -298,7 +298,7 @@ bool HistoryMessageReply::updateData( void HistoryMessageReply::setReplyToLinkFrom( not_null holder) { replyToLnk = replyToMsg - ? goToMessageClickHandler(replyToMsg, holder->fullId()) + ? goToMessageClickHandler(replyToMsg.get(), holder->fullId()) : nullptr; } @@ -307,7 +307,7 @@ void HistoryMessageReply::clearData(not_null holder) { if (replyToMsg) { holder->history()->owner().unregisterDependentMessage( holder, - replyToMsg); + replyToMsg.get()); replyToMsg = nullptr; } replyToMsgId = 0; @@ -399,7 +399,7 @@ void HistoryMessageReply::resize(int width) const { void HistoryMessageReply::itemRemoved( HistoryMessage *holder, HistoryItem *removed) { - if (replyToMsg == removed) { + if (replyToMsg.get() == removed) { clearData(holder); holder->history()->owner().requestItemResize(holder); } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index f9cf7fe756..43e0898c92 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -132,29 +132,55 @@ struct HistoryMessageSponsored : public RuntimeComponent { +class ReplyToMessagePointer final { +public: + ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) { + } + ReplyToMessagePointer(ReplyToMessagePointer &&other) + : _data(base::take(other._data)) { + } + ReplyToMessagePointer &operator=(ReplyToMessagePointer &&other) { + _data = base::take(other._data); + return *this; + } + ReplyToMessagePointer &operator=(HistoryItem *item) { + _data = item; + return *this; + } + + [[nodiscard]] bool empty() const { + return !_data; + } + [[nodiscard]] HistoryItem *get() const { + return _data; + } + explicit operator bool() const { + return !empty(); + } + + [[nodiscard]] HistoryItem *operator->() const { + return _data; + } + [[nodiscard]] HistoryItem &operator*() const { + return *_data; + } + +private: + HistoryItem *_data = nullptr; + +}; + +struct HistoryMessageReply + : public RuntimeComponent { HistoryMessageReply() = default; HistoryMessageReply(const HistoryMessageReply &other) = delete; HistoryMessageReply(HistoryMessageReply &&other) = delete; - HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete; - HistoryMessageReply &operator=(HistoryMessageReply &&other) { - replyToPeerId = other.replyToPeerId; - replyToMsgId = other.replyToMsgId; - replyToMsgTop = other.replyToMsgTop; - replyToDocumentId = other.replyToDocumentId; - replyToWebPageId = other.replyToWebPageId; - std::swap(replyToMsg, other.replyToMsg); - replyToLnk = std::move(other.replyToLnk); - replyToName = std::move(other.replyToName); - replyToText = std::move(other.replyToText); - replyToVersion = other.replyToVersion; - maxReplyWidth = other.maxReplyWidth; - replyToVia = std::move(other.replyToVia); - return *this; - } + HistoryMessageReply &operator=( + const HistoryMessageReply &other) = delete; + HistoryMessageReply &operator=(HistoryMessageReply &&other) = default; ~HistoryMessageReply() { // clearData() should be called by holder. - Expects(replyToMsg == nullptr); + Expects(replyToMsg.empty()); Expects(replyToVia == nullptr); } @@ -205,15 +231,16 @@ struct HistoryMessageReply : public RuntimeComponent replyToVia; ClickHandlerPtr replyToLnk; mutable Ui::Text::String replyToName, replyToText; mutable int replyToVersion = 0; mutable int maxReplyWidth = 0; - std::unique_ptr replyToVia; int toWidth = 0; + bool topicPost = false; }; diff --git a/Telegram/SourceFiles/history/history_item_text.cpp b/Telegram/SourceFiles/history/history_item_text.cpp index d0b5e97d8f..3bfa90330b 100644 --- a/Telegram/SourceFiles/history/history_item_text.cpp +++ b/Telegram/SourceFiles/history/history_item_text.cpp @@ -52,7 +52,7 @@ TextForMimeData WrapAsItem( not_null item, TextForMimeData &&result) { if (const auto reply = item->Get()) { - if (const auto message = reply->replyToMsg) { + if (const auto message = reply->replyToMsg.get()) { result = WrapAsReply(std::move(result), message); } } diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index e41e93702c..e7c3ef83f1 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -247,6 +247,7 @@ struct HistoryMessage::CreateConfig { PeerId replyToPeer = 0; MsgId replyTo = 0; MsgId replyToTop = 0; + bool replyIsTopicPost = false; UserId viaBotId = 0; int viewsCount = -1; QString author; @@ -963,6 +964,7 @@ void HistoryMessage::createComponents(CreateConfig &&config) { reply->replyToPeerId = config.replyToPeer; reply->replyToMsgId = config.replyTo; reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop; + reply->topicPost = config.replyIsTopicPost; if (!reply->updateData(this)) { RequestDependentMessageData( this, diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 07b32e33d5..0f947b016d 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -57,6 +57,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_chat.h" #include "data/data_channel.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" #include "data/data_replies_list.h" #include "data/data_peer_values.h" #include "data/data_changes.h" @@ -160,6 +162,7 @@ RepliesWidget::RepliesWidget( , _history(history) , _rootId(rootId) , _root(lookupRoot()) +, _topic(lookupTopic()) , _areComments(computeAreComments()) , _sendAction(history->owner().sendActionManager().repliesPainter( history, @@ -332,6 +335,7 @@ RepliesWidget::~RepliesWidget() { if (_readRequestTimer.isActive()) { sendReadTillRequest(); } + _history->session().api().request(_resolveTopicRequestId).cancel(); base::take(_sendAction); _history->owner().sendActionManager().repliesPainterRemoved( _history, @@ -437,6 +441,58 @@ HistoryItem *RepliesWidget::lookupRoot() const { return _history->owner().message(_history->peer, _rootId); } +Data::ForumTopic *RepliesWidget::lookupTopic() { + if (const auto forum = _history->peer->forum()) { + const auto result = forum->topicFor(_rootId); + if (!result && !_resolveTopicRequestId) { + const auto api = &_history->session().api(); + _resolveTopicRequestId = api->request( + MTPchannels_GetForumTopicsByID( + forum->channel()->inputChannel, + MTP_vector(1, MTP_int(_rootId.bare))) + ).done([=](const MTPmessages_ForumTopics &result) { + const auto &data = result.data(); + const auto owner = &_history->owner(); + owner->processUsers(data.vusers()); + owner->processChats(data.vchats()); + owner->processMessages(data.vmessages(), NewMessageType::Existing); + channel()->ptsReceived(data.vpts().v); + const auto &list = data.vtopics().v; + for (const auto &topic : list) { + const auto rootId = MsgId(topic.data().vid().v); + const auto i = _topics.find(rootId); + const auto creating = (i == end(_topics)); + const auto raw = creating + ? _topics.emplace( + rootId, + std::make_unique(_history, rootId) + ).first->second.get() + : i->second.get(); + raw->applyTopic(topic); + if (creating) { + raw->addToChatList(FilterId(), topicsList()); + } + if (const auto last = raw->lastServerMessage()) { + _offsetDate = last->date(); + _offsetId = last->id; + } + _offsetTopicId = rootId; + } + if (list.isEmpty() || list.size() == data.vcount().v) { + _allLoaded = true; + } + _requestId = 0; + _chatsListChanges.fire({}); + if (_allLoaded) { + _chatsListLoadedEvents.fire({}); + } + }).send(); + } + return result; + } + return nullptr; +} + bool RepliesWidget::computeAreComments() const { return _root && _root->isDiscussionPost(); } @@ -1294,7 +1350,11 @@ void RepliesWidget::refreshTopBarActiveChat() { MsgId RepliesWidget::replyToId() const { const auto custom = _composeControls->replyingToMessage().msg; - return custom ? custom : _rootId; + return custom + ? custom + : (_rootId == Data::ForumTopic::kGeneralId) + ? MsgId(0) + : _rootId; } void RepliesWidget::setupScrollDownButton() { @@ -1578,23 +1638,34 @@ bool RepliesWidget::showMessage( } const auto id = FullMsgId(_history->peer->id, messageId); const auto message = _history->owner().message(id); - if (!message || message->replyToTop() != _rootId) { + if (!message) { return false; } - - const auto originItem = [&]() -> HistoryItem* { + auto originFound = false; + const auto general = (_rootId == Data::ForumTopic::kGeneralId); + const auto originMessage = [&]() -> HistoryItem* { using OriginMessage = Window::SectionShow::OriginMessage; if (const auto origin = std::get_if(¶ms.origin)) { if (const auto returnTo = session().data().message(origin->id)) { - if (returnTo->history() == _history - && returnTo->replyToTop() == _rootId - && _replyReturn != returnTo) { + if (returnTo->history() != _history) { + return nullptr; + } else if (general + && _inner->viewByPosition(returnTo->position()) + && returnTo->replyToId() == messageId) { + return returnTo; + } else if (!general && (returnTo->replyToTop() == _rootId)) { return returnTo; } } } return nullptr; }(); + if (!originMessage) { + return false; + } + const auto originItem = (!originMessage || _replyReturn == originMessage) + ? nullptr + : originMessage; showAtPosition(message->position(), originItem); return true; } @@ -1756,8 +1827,8 @@ void RepliesWidget::updateInnerVisibleArea() { void RepliesWidget::updatePinnedVisibility() { if (!_loaded) { return; - } else if (!_root) { - setPinnedVisibility(true); + } else if (!_root || _root->isEmpty()) { + setPinnedVisibility(!_root); return; } const auto item = [&] { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 071f4ff9cf..0bbb714f57 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -49,6 +49,7 @@ class Result; namespace Data { class RepliesList; +class ForumTopic; } // namespace Data namespace HistoryView { @@ -208,6 +209,7 @@ private: [[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] MsgId replyToId() const; [[nodiscard]] HistoryItem *lookupRoot() const; + [[nodiscard]] Data::ForumTopic *lookupTopic(); [[nodiscard]] bool computeAreComments() const; [[nodiscard]] std::optional computeUnreadCount() const; void orderWidgets(); @@ -270,6 +272,8 @@ private: const MsgId _rootId = 0; std::shared_ptr _theme; HistoryItem *_root = nullptr; + Data::ForumTopic *_topic = nullptr; + std::shared_ptr _replies; rpl::variable _areComments = false; std::shared_ptr _sendAction; @@ -301,6 +305,8 @@ private: bool _readRequestPending = false; mtpRequestId _readRequestId = 0; + mtpRequestId _resolveTopicRequestId = 0; + mtpRequestId _reloadUnreadCountRequestId = 0; bool _loaded = false; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 357f0bc0ed..b76b4fbea5 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -676,19 +676,6 @@ SessionController::SessionController( closeFolder(); }, lifetime()); - _openedForum.changes( - ) | rpl::filter([](ChannelData *forum) { - return (forum != nullptr); - }) | rpl::map([](ChannelData *forum) { - return forum->flagsValue( - ) | rpl::filter([](ChannelData::Flags::Change change) { - return (change.diff & ChannelData::Flag::Forum) - && !(change.value & ChannelData::Flag::Forum); - }); - }) | rpl::flatten_latest() | rpl::start_with_next([=] { - closeForum(); - }, lifetime()); - session->data().chatsFilters().changed( ) | rpl::start_with_next([=] { checkOpenedFilter(); @@ -842,7 +829,9 @@ void SessionController::checkOpenedFilter() { const auto &list = session().data().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); if (i == end(list)) { - setActiveChatsFilter(0); + setActiveChatsFilter( + 0, + { anim::type::normal, anim::activation::background }); } } } @@ -874,18 +863,35 @@ void SessionController::closeFolder() { _openedFolder = nullptr; } -void SessionController::openForum(not_null forum) { +void SessionController::openForum( + not_null forum, + const SectionShow ¶ms) { Expects(forum->isForum()); + _openedForumLifetime.destroy(); if (_openedForum.current() != forum) { resetFakeUnreadWhileOpened(); } - setActiveChatsFilter(0); + setActiveChatsFilter(0, params); closeFolder(); _openedForum = forum.get(); + if (_openedForum.current() == forum) { + forum->flagsValue( + ) | rpl::filter([=](const ChannelData::Flags::Change &update) { + using Flag = ChannelData::Flag; + return (update.diff & Flag::Forum) + && !(update.value & Flag::Forum); + }) | rpl::start_with_next([=] { + closeForum(); + showPeerHistory( + forum, + { anim::type::normal, anim::activation::background }); + }, _openedForumLifetime); + } } void SessionController::closeForum() { + _openedForumLifetime.destroy(); _openedForum = nullptr; } @@ -926,12 +932,27 @@ void SessionController::setActiveChatEntry(Dialogs::RowDescriptor row) { const auto was = _activeChatEntry.current().key.history(); const auto now = row.key.history(); if (was && was != now) { + _activeHistoryLifetime.destroy(); was->setFakeUnreadWhileOpened(false); _invitePeekTimer.cancel(); } _activeChatEntry = row; if (now) { now->setFakeUnreadWhileOpened(true); + if (const auto channel = now->peer->asChannel() + ; channel && !channel->isForum()) { + channel->flagsValue( + ) | rpl::filter([=](const ChannelData::Flags::Change &update) { + using Flag = ChannelData::Flag; + return (update.diff & Flag::Forum) + && (update.value & Flag::Forum); + }) | rpl::start_with_next([=] { + clearSectionStack( + { anim::type::normal, anim::activation::background }); + openForum(channel, + { anim::type::normal, anim::activation::background }); + }, _openedForumLifetime); + } } if (session().supportMode()) { pushToChatEntryHistory(row); @@ -1601,7 +1622,9 @@ FilterId SessionController::activeChatsFilterCurrent() const { return _activeChatsFilter.current(); } -void SessionController::setActiveChatsFilter(FilterId id) { +void SessionController::setActiveChatsFilter( + FilterId id, + const SectionShow ¶ms) { if (activeChatsFilterCurrent() != id) { resetFakeUnreadWhileOpened(); } @@ -1610,7 +1633,7 @@ void SessionController::setActiveChatsFilter(FilterId id) { closeFolder(); } if (adaptive().isOneColumn()) { - Ui::showChatsList(&session()); + clearSectionStack(params); } } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index a4f578e74c..96eaab33e3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -345,7 +345,9 @@ public: void closeFolder(); const rpl::variable &openedFolder() const; - void openForum(not_null forum); + void openForum( + not_null forum, + const SectionShow ¶ms = SectionShow::Way::ClearStack); void closeForum(); const rpl::variable &openedForum() const; @@ -474,7 +476,9 @@ public: [[nodiscard]] int filtersWidth() const; [[nodiscard]] rpl::producer activeChatsFilter() const; [[nodiscard]] FilterId activeChatsFilterCurrent() const; - void setActiveChatsFilter(FilterId id); + void setActiveChatsFilter( + FilterId id, + const SectionShow ¶ms = SectionShow::Way::ClearStack); void toggleFiltersMenu(bool enabled); [[nodiscard]] rpl::producer<> filtersMenuChanged() const; @@ -582,6 +586,7 @@ private: const std::unique_ptr _tabbedSelector; rpl::variable _activeChatEntry; + rpl::lifetime _activeHistoryLifetime; base::Variable _dialogsListFocused = { false }; base::Variable _dialogsListDisplayForced = { false }; std::deque _chatEntryHistory; @@ -600,6 +605,7 @@ private: PeerData *_showEditPeer = nullptr; rpl::variable _openedFolder; rpl::variable _openedForum; + rpl::lifetime _openedForumLifetime; rpl::event_stream<> _filtersMenuChanged;