From 3722e55b678b43353f6334e03c5a7461aa6c72c4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 Oct 2022 19:34:45 +0400 Subject: [PATCH] Support topic on-the-fly creation. --- Telegram/SourceFiles/api/api_polls.cpp | 4 +- Telegram/SourceFiles/api/api_sending.cpp | 22 +-- Telegram/SourceFiles/api/api_updates.cpp | 5 +- Telegram/SourceFiles/apiwrap.cpp | 16 +-- .../boxes/peers/add_bot_to_chat_box.cpp | 4 +- .../boxes/peers/edit_forum_topic_box.cpp | 25 +--- Telegram/SourceFiles/data/data_forum.cpp | 39 ++++-- Telegram/SourceFiles/data/data_forum.h | 2 + .../SourceFiles/data/data_forum_topic.cpp | 6 + Telegram/SourceFiles/data/data_forum_topic.h | 5 +- Telegram/SourceFiles/data/data_histories.cpp | 127 +++++++++++++++++- Telegram/SourceFiles/data/data_histories.h | 47 ++++++- .../SourceFiles/data/data_replies_list.cpp | 3 - Telegram/SourceFiles/data/data_session.cpp | 33 +++-- Telegram/SourceFiles/data/data_session.h | 4 +- Telegram/SourceFiles/history/history_item.cpp | 8 +- Telegram/SourceFiles/history/history_item.h | 5 +- .../SourceFiles/history/history_message.cpp | 37 +++-- .../SourceFiles/history/history_message.h | 5 +- .../SourceFiles/history/history_service.cpp | 10 +- .../SourceFiles/history/history_service.h | 5 +- .../view/history_view_replies_section.cpp | 92 +++++++++---- .../view/history_view_replies_section.h | 11 +- .../SourceFiles/info/info_content_widget.cpp | 7 + .../SourceFiles/info/info_content_widget.h | 4 +- Telegram/SourceFiles/info/info_controller.cpp | 13 ++ Telegram/SourceFiles/info/info_controller.h | 1 + .../info/media/info_media_widget.h | 2 +- .../media/view/media_view_overlay_widget.cpp | 6 +- .../media/view/media_view_overlay_widget.h | 2 +- .../ui/chat/attach/attach_album_thumbnail.cpp | 4 +- .../window/window_session_controller.cpp | 23 ++++ 32 files changed, 447 insertions(+), 130 deletions(-) diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 68aae6797..4d4727470 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -71,10 +71,10 @@ void Polls::create( history, replyTo, randomId, - MTPmessages_SendMedia( + Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, - MTP_int(replyTo), + Data::Histories::ReplyToPlaceholder(), PollDataToInputMedia(&data), MTP_string(), MTP_long(randomId), diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index d23d9493e..791bd2166 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -148,10 +148,10 @@ void SendExistingMedia( history, replyTo, randomId, - MTPmessages_SendMedia( + Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, - MTP_int(replyTo), + Data::Histories::ReplyToPlaceholder(), inputMedia(), MTP_string(captionText), MTP_long(randomId), @@ -314,10 +314,10 @@ bool SendDice(MessageToSend &message) { history, replyTo, randomId, - MTPmessages_SendMedia( + Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, - MTP_int(replyTo), + Data::Histories::ReplyToPlaceholder(), MTP_inputMediaDice(MTP_string(emoji)), MTP_string(), MTP_long(randomId), @@ -347,9 +347,9 @@ void SendConfirmedFile( && (file->to.replaceMediaOf != 0); const auto newId = FullMsgId( file->to.peer, - isEditing + (isEditing ? file->to.replaceMediaOf - : session->data().nextLocalMessageId()); + : session->data().nextLocalMessageId())); const auto groupId = file->album ? file->album->groupId : uint64(0); if (file->album) { const auto proj = [](const SendingAlbum::Item &item) { @@ -360,15 +360,21 @@ void SendConfirmedFile( it->msgId = newId; } - session->uploader().upload(newId, file); const auto itemToEdit = isEditing ? session->data().message(newId) : nullptr; - const auto history = session->data().history(file->to.peer); const auto peer = history->peer; + if (!isEditing) { + file->to.replyTo = session->data().histories().convertTopicReplyTo( + history, + file->to.replyTo); + } + + session->uploader().upload(newId, file); + auto action = SendAction(history, file->to.options); action.clearDraft = false; action.replyTo = file->to.replyTo; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index e27a521c9..9b6f12d76 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1534,7 +1534,8 @@ void Updates::feedUpdate(const MTPUpdate &update) { const auto randomId = d.vrandom_id().v; if (const auto id = session().data().messageIdByRandomId(randomId)) { const auto newId = d.vid().v; - if (const auto local = session().data().message(id)) { + auto &owner = session().data(); + if (const auto local = owner.message(id)) { if (local->isScheduled()) { session().data().scheduledMessages().apply(d, local); } else { @@ -1552,6 +1553,8 @@ void Updates::feedUpdate(const MTPUpdate &update) { local->setRealId(d.vid().v); } } + } else { + owner.histories().checkTopicCreated(id, newId); } session().data().unregisterMessageRandomId(randomId); } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index c2bae883c..11a7e3ded 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3494,10 +3494,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) { history, replyTo, randomId, - MTPmessages_SendMessage( + Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, - MTP_int(replyTo), + Data::Histories::ReplyToPlaceholder(), msgText, MTP_long(randomId), MTPReplyMarkup(), @@ -3637,10 +3637,10 @@ void ApiWrap::sendInlineResult( history, replyTo, randomId, - MTPmessages_SendInlineBotResult( + Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, - MTP_int(replyTo), + Data::Histories::ReplyToPlaceholder(), MTP_long(randomId), MTP_long(data->getQueryId()), MTP_string(data->getId()), @@ -3784,10 +3784,10 @@ void ApiWrap::sendMediaWithRandomId( history, replyTo, randomId, - MTPmessages_SendMedia( + Data::Histories::PrepareMessage( MTP_flags(flags), peer->input, - MTP_int(replyTo), + Data::Histories::ReplyToPlaceholder(), media, MTP_string(caption.text), MTP_long(randomId), @@ -3889,10 +3889,10 @@ void ApiWrap::sendAlbumIfReady(not_null album) { history, replyTo, uint64(0), // randomId - MTPmessages_SendMultiMedia( + Data::Histories::PrepareMessage( MTP_flags(flags), peer->input, - MTP_int(replyTo), + Data::Histories::ReplyToPlaceholder(), MTP_vector(medias), MTP_int(album->options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()) diff --git a/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp index 273e5156d..b220fa84d 100644 --- a/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp @@ -69,10 +69,10 @@ void ShareBotGame( history, replyTo, randomId, - MTPmessages_SendMedia( + Data::Histories::PrepareMessage( MTP_flags(0), chat->input, - MTP_int(0), + Data::Histories::ReplyToPlaceholder(), MTP_inputMediaGame( MTP_inputGameShortName( bot->inputUser, diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 0c541bee4..476754d96 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -139,7 +139,8 @@ void EditForumTopicBox( const auto requestId = std::make_shared(); const auto create = [=] { - if (!forum->peer->isForum()) { + const auto channel = forum->peer->asChannel(); + if (!channel || !channel->isForum()) { box->closeBox(); return; } else if (title->getLastText().trimmed().isEmpty()) { @@ -149,30 +150,10 @@ void EditForumTopicBox( controller->showSection( std::make_shared( forum, - forum->peer->forum()->reserveCreatingId( + channel->forum()->reserveCreatingId( title->getLastText().trimmed(), state->iconId)), Window::SectionShow::Way::ClearStack); -#if 0 // #TODO forum create - const auto randomId = base::RandomValue(); - const auto api = &forum->session().api(); - api->request(MTPchannels_CreateForumTopic( - MTP_flags(0), - forum->inputChannel, - MTP_string(title->getLastText().trimmed()), - MTPlong(), // icon_emoji_id - MTPInputMedia(), - MTP_string(message->getLastText().trimmed()), - MTP_long(randomId), - MTPVector(), - MTPInputPeer() // send_as - )).done([=](const MTPUpdates &result) { - api->applyUpdates(result, randomId); - box->closeBox(); - }).fail([=](const MTP::Error &error) { - api->sendMessageFail(error, forum, randomId); - }).send(); -#endif }; const auto save = [=] { diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 73de5760f..e6df600f9 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -124,20 +124,18 @@ void Forum::applyTopicAdded( MsgId rootId, const QString &title, DocumentId iconId) { - if (const auto i = _topics.find(rootId); i != end(_topics)) { - i->second->applyTitle(title); - i->second->applyIconId(iconId); - } else { - const auto raw = _topics.emplace( + const auto i = _topics.find(rootId); + const auto raw = (i != end(_topics)) + ? i->second.get() + : _topics.emplace( rootId, std::make_unique(_history, rootId) ).first->second.get(); - raw->applyTitle(title); - raw->applyIconId(iconId); - if (!creating(rootId)) { - raw->addToChatList(FilterId(), topicsList()); - _chatsListChanges.fire({}); - } + raw->applyTitle(title); + raw->applyIconId(iconId); + if (!creating(rootId)) { + raw->addToChatList(FilterId(), topicsList()); + _chatsListChanges.fire({}); } } @@ -171,6 +169,25 @@ bool Forum::creating(MsgId rootId) const { return _creatingRootIds.contains(rootId); } +void Forum::created(MsgId rootId, MsgId realId) { + if (rootId == realId) { + return; + } + _creatingRootIds.remove(rootId); + const auto i = _topics.find(rootId); + Assert(i != end(_topics)); + auto topic = std::move(i->second); + _topics.erase(i); + const auto id = FullMsgId(_history->peer->id, realId); + if (!_topics.contains(realId)) { + _topics.emplace( + realId, + std::move(topic) + ).first->second->setRealRootId(realId); + } + _history->owner().notifyItemIdChange({ id, rootId }); +} + ForumTopic *Forum::topicFor(not_null item) { const auto maybe = topicFor(item->replyToTop()); return maybe ? maybe : topicFor(item->topicRootId()); diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index f52540d29..bc422d9f8 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -36,6 +36,7 @@ public: const QString &title, DocumentId iconId); void applyTopicRemoved(MsgId rootId); + void applyTopicCreated(MsgId rootId, MsgId realId); [[nodiscard]] ForumTopic *topicFor(not_null item); [[nodiscard]] ForumTopic *topicFor(MsgId rootId); @@ -46,6 +47,7 @@ public: DocumentId iconId); void discardCreatingId(MsgId rootId); [[nodiscard]] bool creating(MsgId rootId) const; + void created(MsgId rootId, MsgId realId); private: void applyReceivedTopics( diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index d5edabe41..fa866fc33 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -29,6 +29,8 @@ ForumTopic::ForumTopic(not_null history, MsgId rootId) , _rootId(rootId) { } +ForumTopic::~ForumTopic() = default; + not_null ForumTopic::channel() const { return _history->peer->asChannel(); } @@ -45,6 +47,10 @@ MsgId ForumTopic::rootId() const { return _rootId; } +void ForumTopic::setRealRootId(MsgId realId) { + _rootId = realId; +} + void ForumTopic::applyTopic(const MTPForumTopic &topic) { Expects(_rootId == topic.data().vid().v); diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 792878281..cf15a8e23 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -30,6 +30,7 @@ public: static constexpr auto kGeneralId = 1; ForumTopic(not_null history, MsgId rootId); + ~ForumTopic(); ForumTopic(const ForumTopic &) = delete; ForumTopic &operator=(const ForumTopic &) = delete; @@ -42,6 +43,8 @@ public: return (_rootId == kGeneralId); } + void setRealRootId(MsgId realId); + void applyTopic(const MTPForumTopic &topic); TimeId adjustedChatListTimeId() const override; @@ -109,7 +112,7 @@ private: const not_null _history; const not_null _list; - const MsgId _rootId = 0; + MsgId _rootId = 0; QString _title; DocumentId _iconId = 0; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index d5f2442e9..042084999 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -11,8 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_folder.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" #include "data/data_scheduled_messages.h" #include "base/unixtime.h" +#include "base/random.h" #include "main/main_session.h" #include "window/notifications_manager.h" #include "history/history.h" @@ -841,14 +844,74 @@ int Histories::sendRequest( return id; } +void Histories::sendCreateTopicRequest( + not_null history, + MsgId rootId) { + Expects(history->peer->isChannel()); + + const auto forum = history->peer->forum(); + Assert(forum != nullptr); + const auto topic = forum->topicFor(rootId); + Assert(topic != nullptr); + const auto randomId = base::RandomValue(); + session().data().registerMessageRandomId( + randomId, + { history->peer->id, rootId }); + const auto api = &session().api(); + using Flag = MTPchannels_CreateForumTopic::Flag; + api->request(MTPchannels_CreateForumTopic( + MTP_flags(topic->iconId() ? Flag::f_icon_emoji_id : Flag(0)), + history->peer->asChannel()->inputChannel, + MTP_string(topic->title()), + MTP_long(topic->iconId()), + MTP_long(randomId), + MTPInputPeer() // send_as + )).done([=](const MTPUpdates &result) { + //AssertIsDebug(); + //const auto id = result.c_updates().vupdates().v.front().c_updateMessageID().vrandom_id().v; + //session().data().registerMessageRandomId( + // id, + // { history->peer->id, rootId }); + api->applyUpdates(result, randomId); + }).fail([=](const MTP::Error &error) { + api->sendMessageFail(error, history->peer, randomId); + }).send(); +} + +bool Histories::isCreatingTopic( + not_null history, + MsgId rootId) const { + const auto forum = history->peer->forum(); + return forum && forum->creating(rootId); +} + int Histories::sendPreparedMessage( not_null history, MsgId replyTo, uint64 randomId, - PreparedMessage message, + Fn message, Fn done, Fn fail) { - return v::match(message, [&](const auto &request) { + if (isCreatingTopic(history, replyTo)) { + const auto id = ++_requestAutoincrement; + const auto creatingId = FullMsgId(history->peer->id, replyTo); + auto i = _creatingTopics.find(creatingId); + if (i == end(_creatingTopics)) { + sendCreateTopicRequest(history, replyTo); + i = _creatingTopics.emplace(creatingId).first; + } + i->second.push_back({ + .randomId = randomId, + .message = std::move(message), + .done = std::move(done), + .fail = std::move(fail), + .requestId = id, + }); + _creatingTopicRequests.emplace(id); + return id; + } + const auto realTo = convertTopicReplyTo(history, replyTo); + return v::match(message(realTo), [&](const auto &request) { const auto type = RequestType::Send; return sendRequest(history, type, [=](Fn finish) { const auto session = &_owner->session(); @@ -874,6 +937,54 @@ int Histories::sendPreparedMessage( }); } +void Histories::checkTopicCreated(FullMsgId rootId, MsgId realId) { + const auto i = _creatingTopics.find(rootId); + if (i != end(_creatingTopics)) { + auto scheduled = base::take(i->second); + _creatingTopics.erase(i); + + _createdTopicIds.emplace(rootId, realId); + + if (const auto forum = _owner->peer(rootId.peer)->forum()) { + forum->created(rootId.msg, realId); + } + + const auto history = _owner->history(rootId.peer); + for (auto &entry : scheduled) { + _creatingTopicRequests.erase(entry.requestId); + //AssertIsDebug(); + sendPreparedMessage( + history, + realId, + entry.randomId, + std::move(entry.message), + std::move(entry.done), + std::move(entry.fail)); + } + for (const auto &item : history->clientSideMessages()) { + const auto replace = [&](MsgId nowId) { + return (nowId == rootId.msg) ? realId : nowId; + }; + if (item->replyToTop() == rootId.msg) { + item->setReplyFields( + replace(item->replyToId()), + realId, + true); + } + } + } +} + +MsgId Histories::convertTopicReplyTo( + not_null history, + MsgId replyTo) const { + if (!replyTo) { + return {}; + } + const auto i = _createdTopicIds.find({ history->peer->id, replyTo }); + return (i != end(_createdTopicIds)) ? i->second : replyTo; +} + void Histories::checkPostponed(not_null history, int id) { if (const auto state = lookup(history)) { finishSentRequest(history, state, id); @@ -883,6 +994,9 @@ void Histories::checkPostponed(not_null history, int id) { void Histories::cancelRequest(int id) { if (!id) { return; + } else if (_creatingTopicRequests.contains(id)) { + cancelDelayedByTopicRequest(id); + return; } const auto history = _historyByRequest.take(id); if (!history) { @@ -896,6 +1010,15 @@ void Histories::cancelRequest(int id) { finishSentRequest(*history, state, id); } +void Histories::cancelDelayedByTopicRequest(int id) { + for (auto &[rootId, messages] : _creatingTopics) { + messages.erase( + ranges::remove(messages, id, &DelayedByTopicMessage::requestId), + end(messages)); + } + _creatingTopicRequests.remove(id); +} + void Histories::finishSentRequest( not_null history, not_null state, diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index faccd7da8..d8c67bbf9 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -104,10 +104,25 @@ public: not_null history, MsgId replyTo, uint64 randomId, - PreparedMessage message, - Fn done, + Fn message, + Fn done, Fn fail); + struct ReplyToPlaceholder { + }; + template + static Fn PrepareMessage( + const Args &...args) { + return [=](MsgId replyTo) { + return RequestType(ReplaceReplyTo(args, replyTo)...); + }; + } + + void checkTopicCreated(FullMsgId rootId, MsgId realId); + [[nodiscard]] MsgId convertTopicReplyTo( + not_null history, + MsgId replyTo) const; + private: struct PostponedHistoryRequest { Fn finish)> generator; @@ -130,6 +145,22 @@ private: MsgId aroundId = 0; mtpRequestId requestId = 0; }; + struct DelayedByTopicMessage { + uint64 randomId = 0; + Fn message; + Fn done; + Fn fail; + int requestId = 0; + }; + + template + static auto ReplaceReplyTo(Arg arg, MsgId replyTo) { + return arg; + } + template <> + static auto ReplaceReplyTo(ReplyToPlaceholder, MsgId replyTo) { + return MTP_int(replyTo); + } void readInboxTill(not_null history, MsgId tillId, bool force); void sendReadRequests(); @@ -147,6 +178,12 @@ private: void sendDialogRequests(); + [[nodiscard]] bool isCreatingTopic( + not_null history, + MsgId rootId) const; + void sendCreateTopicRequest(not_null history, MsgId rootId); + void cancelDelayedByTopicRequest(int id); + const not_null _owner; std::unordered_map> _map; @@ -169,6 +206,12 @@ private: not_null, ChatListGroupRequest> _chatListGroupRequests; + base::flat_map< + FullMsgId, + std::vector> _creatingTopics; + base::flat_map _createdTopicIds; + base::flat_set _creatingTopicRequests; + }; } // namespace Data diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index c682a8067..cc963704f 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -137,9 +137,6 @@ void RepliesList::appendClientSideMessages(MessagesSlice &slice) { } slice.ids.reserve(messages.size()); for (const auto &item : messages) { - const auto checkId = (_rootId == ForumTopic::kGeneralId) - ? item->topicRootId() - : item->replyToTop(); if (!item->inThread(_rootId)) { continue; } diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 97913a364..0a21fadcd 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1407,10 +1407,12 @@ rpl::producer> Session::newItemAdded() const { return _newItemAdded.events(); } -void Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) { +HistoryItem *Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) { const auto list = messagesListForInsert(peerId); - auto i = list->find(wasId); - Assert(i != list->end()); + const auto i = list->find(wasId); + if (i == list->end()) { + return nullptr; + } const auto item = i->second; list->erase(i); const auto [j, ok] = list->emplace(nowId, item); @@ -1427,6 +1429,7 @@ void Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) { } Ensures(ok); + return item; } bool Session::queryItemVisibility(not_null item) const { @@ -1448,19 +1451,23 @@ void Session::itemVisibilitiesUpdated() { } void Session::notifyItemIdChange(IdChange event) { - const auto item = event.item; - changeMessageId(item->history()->peer->id, event.oldId, item->id); + const auto item = changeMessageId( + event.newId.peer, + event.oldId, + event.newId.msg); _itemIdChanges.fire_copy(event); - const auto refreshViewDataId = [](not_null view) { - view->refreshDataId(); - }; - enumerateItemViews(item, refreshViewDataId); - if (const auto group = groups().find(item)) { - const auto leader = group->items.front(); - if (leader != item) { - enumerateItemViews(leader, refreshViewDataId); + if (item) { + const auto refreshViewDataId = [](not_null view) { + view->refreshDataId(); + }; + enumerateItemViews(item, refreshViewDataId); + if (const auto group = groups().find(item)) { + const auto leader = group->items.front(); + if (leader != item) { + enumerateItemViews(leader, refreshViewDataId); + } } } } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index c06807682..8b48115da 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -243,7 +243,7 @@ public: void itemVisibilitiesUpdated(); struct IdChange { - not_null item; + FullMsgId newId; MsgId oldId = 0; }; void notifyItemIdChange(IdChange event); @@ -728,7 +728,7 @@ private: not_null messagesListForInsert(PeerId peerId); not_null registerMessage( std::unique_ptr item); - void changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId); + HistoryItem *changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId); void removeDependencyMessage(not_null item); void photoApplyFields( diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 1a19f6db6..cf734a031 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -660,9 +660,11 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { setForwardsCount(data.vforwards().value_or(-1)); if (const auto reply = data.vreply_to()) { reply->match([&](const MTPDmessageReplyHeader &data) { - setReplyToTop( + setReplyFields( + data.vreply_to_msg_id().v, data.vreply_to_top_id().value_or( - data.vreply_to_msg_id().v)); + data.vreply_to_msg_id().v), + data.is_forum_topic()); }); } setPostAuthor(data.vpost_author().value_or_empty()); @@ -715,7 +717,7 @@ void HistoryItem::setRealId(MsgId newId) { if (isRegular()) { _history->unregisterClientSideMessage(this); } - _history->owner().notifyItemIdChange({ this, oldId }); + _history->owner().notifyItemIdChange({ fullId(), oldId }); // We don't fire MessageUpdate::Flag::ReplyMarkup and update keyboard // in history widget, because it can't exist for an outgoing message. diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 84c3502a7..55b8f8a71 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -341,7 +341,10 @@ public: PeerId replier, std::optional unread) { } - virtual void setReplyToTop(MsgId replyToTop) = 0; + virtual void setReplyFields( + MsgId replyTo, + MsgId replyToTop, + bool isForumPost) = 0; virtual void setPostAuthor(const QString &author) { } virtual void setRealId(MsgId newId); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 7c54b0407..c776e7160 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "data/data_channel.h" #include "data/data_forum_topic.h" +#include "data/data_forum.h" #include "data/data_user.h" #include "data/data_web_page.h" #include "data/data_sponsored_messages.h" @@ -173,6 +174,9 @@ void RequestDependentMessageData( not_null item, PeerId peerId, MsgId msgId) { + if (!IsServerMsgId(msgId)) { + return; + } const auto fullId = item->fullId(); const auto history = item->history(); const auto session = &history->session(); @@ -332,6 +336,7 @@ HistoryMessage::HistoryMessage( : id; config.replyToTop = data.vreply_to_top_id().value_or( data.vreply_to_msg_id().v); + config.replyIsTopicPost = data.is_forum_topic(); }); } config.viaBotId = data.vvia_bot_id().value_or_empty(); @@ -654,8 +659,10 @@ void HistoryMessage::createComponentsHelper( const auto to = LookupReplyTo(history(), replyTo); const auto replyToTop = LookupReplyToTop(to); config.replyToTop = replyToTop ? replyToTop : replyTo; + const auto forum = history()->peer->forum(); config.replyIsTopicPost = LookupReplyIsTopicPost(to) - || to->Has(); + || (to && to->Has()) + || (forum && forum->creating(replyToTop)); } config.markup = std::move(markup); if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor; @@ -1811,16 +1818,30 @@ void HistoryMessage::setSponsoredFrom(const Data::SponsoredFrom &from) { : Type::Group; } -void HistoryMessage::setReplyToTop(MsgId replyToTop) { +void HistoryMessage::setReplyFields( + MsgId replyTo, + MsgId replyToTop, + bool isForumPost) { const auto reply = Get(); - if (!reply - || (reply->replyToMsgTop == replyToTop) - || (reply->replyToMsgTop != 0) - || isScheduled()) { + if (!reply || isScheduled()) { return; } - reply->replyToMsgTop = replyToTop; - changeReplyToTopCounter(reply, 1); + reply->topicPost = isForumPost; + if ((reply->replyToMsgId != replyTo) + && !IsServerMsgId(reply->replyToMsgId)) { + reply->replyToMsgId = replyTo; + if (!reply->updateData(this)) { + RequestDependentMessageData( + this, + reply->replyToPeerId, + reply->replyToMsgId); + } + } + if ((reply->replyToMsgTop != replyToTop) + && !IsServerMsgId(reply->replyToMsgTop)) { + reply->replyToMsgTop = replyToTop; + changeReplyToTopCounter(reply, 1); + } } void HistoryMessage::setRealId(MsgId newId) { diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 400e8b03b..439e7f0fe 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -146,7 +146,10 @@ public: int delta, PeerId replier, std::optional unread) override; - void setReplyToTop(MsgId replyToTop) override; + void setReplyFields( + MsgId replyTo, + MsgId replyToTop, + bool isForumPost) override; void setPostAuthor(const QString &author) override; void setRealId(MsgId newId) override; void incrementReplyToTopCounter() override; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index bf900f580..d0a0b9853 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -1318,12 +1318,12 @@ MsgId HistoryService::topicRootId() const { return Data::ForumTopic::kGeneralId; } -void HistoryService::setReplyToTop(MsgId replyToTop) { +void HistoryService::setReplyFields( + MsgId replyTo, + MsgId replyToTop, + bool isForumPost) { const auto data = GetDependentData(); - if (!data - || (data->topId == replyToTop) - || (data->topId != 0) - || isScheduled()) { + if (!data || IsServerMsgId(data->topId) || isScheduled()) { return; } data->topId = replyToTop; diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index 9d3aff08f..4a596ae6e 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -139,7 +139,10 @@ public: MsgId replyToId() const override; MsgId replyToTop() const override; MsgId topicRootId() const override; - void setReplyToTop(MsgId replyToTop) override; + void setReplyFields( + MsgId replyTo, + MsgId replyToTop, + bool isForumPost) override; std::unique_ptr createView( not_null delegate, diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 8544851c3..b2305dd05 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -154,6 +154,16 @@ object_ptr RepliesMemento::createWidget( return result; } +void RepliesMemento::setupTopicViewer() { + _history->owner().itemIdChanged( + ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { + if (_rootId == change.oldId) { + _rootId = change.newId.msg; + _replies = nullptr; + } + }, _lifetime); +} + RepliesWidget::RepliesWidget( QWidget *parent, not_null controller, @@ -199,6 +209,7 @@ RepliesWidget::RepliesWidget( setupRoot(); setupRootView(); + setupTopicViewer(); session().api().requestFullPeer(_history->peer); @@ -442,6 +453,27 @@ void RepliesWidget::setupRootView() { }, _rootView->lifetime()); } +void RepliesWidget::setupTopicViewer() { + _history->owner().itemIdChanged( + ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { + if (_rootId == change.oldId) { + _rootId = change.newId.msg; + _root = lookupRoot(); + createReplies(); + if (_topic && _topic->rootId() == change.oldId) { + setTopic(_topic->forum()->topicFor(change.newId.msg)); + } + _inner->update(); + } + }, lifetime()); +} + +void RepliesWidget::setTopic(Data::ForumTopic *topic) { + if ((_topic = topic)) { + refreshTopBarActiveChat(); + } +} + HistoryItem *RepliesWidget::lookupRoot() const { return _history->owner().message(_history->peer, _rootId); } @@ -458,9 +490,7 @@ Data::ForumTopic *RepliesWidget::lookupTopic() { ).done([=](const MTPmessages_ForumTopics &result) { if (const auto forum = _history->peer->forum()) { forum->applyReceivedTopics(result); - if ((_topic = forum->topicFor(_rootId))) { - refreshTopBarActiveChat(); - } + setTopic(forum->topicFor(_rootId)); } _resolveTopicRequestId = 0; }).fail([=] { @@ -1699,32 +1729,44 @@ void RepliesWidget::saveState(not_null memento) { _inner->saveState(memento->list()); } -void RepliesWidget::restoreState(not_null memento) { - const auto setReplies = [&](std::shared_ptr replies) { - _replies = std::move(replies); +void RepliesWidget::createReplies() { + auto old = base::take(_replies); + setReplies(std::make_shared(_history, _rootId)); + if (old) { + _inner->showAroundPosition(Data::UnreadMessagePosition, nullptr); + } +} - rpl::combine( - rpl::single(0) | rpl::then(_replies->fullCount()), - _areComments.value() - ) | rpl::map([=](int count, bool areComments) { - return count - ? (areComments - ? tr::lng_comments_header - : tr::lng_replies_header)( - lt_count_decimal, - rpl::single(count) | tr::to_count()) - : (areComments - ? tr::lng_comments_header_none - : tr::lng_replies_header_none)(); - }) | rpl::flatten_latest( - ) | rpl::start_with_next([=](const QString &text) { - _topBar->setCustomTitle(text); - }, lifetime()); - }; +void RepliesWidget::setReplies(std::shared_ptr replies) { + _replies = std::move(replies); + _repliesLifetime.destroy(); + if (_topic) { + return; + } + rpl::combine( + rpl::single(0) | rpl::then(_replies->fullCount()), + _areComments.value() + ) | rpl::map([=](int count, bool areComments) { + return count + ? (areComments + ? tr::lng_comments_header + : tr::lng_replies_header)( + lt_count_decimal, + rpl::single(count) | tr::to_count()) + : (areComments + ? tr::lng_comments_header_none + : tr::lng_replies_header_none)(); + }) | rpl::flatten_latest( + ) | rpl::start_with_next([=](const QString &text) { + _topBar->setCustomTitle(text); + }, _repliesLifetime); +} + +void RepliesWidget::restoreState(not_null memento) { if (auto replies = memento->getReplies()) { setReplies(std::move(replies)); } else if (!_replies) { - setReplies(std::make_shared(_history, _rootId)); + createReplies(); } restoreReplyReturns(memento->replyReturns()); _inner->restoreState(memento->list()); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 806133c51..7e3dcb5f0 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -163,6 +163,8 @@ private: void updateAdaptiveLayout(); void saveState(not_null memento); void restoreState(not_null memento); + void setReplies(std::shared_ptr replies); + void createReplies(); void showAtStart(); void showAtEnd(); void showAtPosition( @@ -178,6 +180,8 @@ private: void setupRoot(); void setupRootView(); + void setupTopicViewer(); + void setTopic(Data::ForumTopic *topic); void setupDragArea(); void sendReadTillRequest(); void readTill(not_null item); @@ -276,6 +280,7 @@ private: mutable bool _newTopicDiscarded = false; std::shared_ptr _replies; + rpl::lifetime _repliesLifetime; rpl::variable _areComments = false; std::shared_ptr _sendAction; QPointer _inner; @@ -363,13 +368,17 @@ public: } private: + void setupTopicViewer(); + const not_null _history; - const MsgId _rootId = 0; + MsgId _rootId = 0; const MsgId _highlightId = 0; ListMemento _list; std::shared_ptr _replies; std::vector _replyReturns; + rpl::lifetime _lifetime; + }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 10b21c08d..46a66f31a 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_session.h" #include "data/data_forum_topic.h" +#include "data/data_forum.h" #include "main/main_session.h" #include "styles/style_info.h" #include "styles/style_profile.h" @@ -340,6 +341,12 @@ ContentMemento::ContentMemento(not_null topic) : _peer(topic->channel()) , _migratedPeerId(_peer->migrateFrom() ? _peer->migrateFrom()->id : 0) , _topic(topic) { + _peer->owner().itemIdChanged( + ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { + if (_topic->rootId() == change.oldId) { + _topic = _topic->forum()->topicFor(change.newId.msg); + } + }, _lifetime); } ContentMemento::ContentMemento(Settings::Tag settings) diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 1201390ed..b1f9d636a 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -213,7 +213,7 @@ public: private: PeerData * const _peer = nullptr; const PeerId _migratedPeerId = 0; - Data::ForumTopic * const _topic = nullptr; + Data::ForumTopic *_topic = nullptr; UserData * const _settingsSelf = nullptr; PollData * const _poll = nullptr; const FullMsgId _pollContextId; @@ -223,6 +223,8 @@ private: bool _searchEnabledByContent = false; bool _searchStartsFocused = false; + rpl::lifetime _lifetime; + }; } // namespace Info diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index a374ba2ee..a72e04207 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_forum_topic.h" +#include "data/data_forum.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_download_manager.h" @@ -180,6 +181,7 @@ Controller::Controller( , _section(memento->section()) { updateSearchControllers(memento); setupMigrationViewer(); + setupTopicViewer(); } void Controller::setupMigrationViewer() { @@ -210,6 +212,17 @@ void Controller::setupMigrationViewer() { }, lifetime()); } +void Controller::setupTopicViewer() { + session().data().itemIdChanged( + ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { + if (const auto topic = _key.topic()) { + if (topic->rootId() == change.oldId) { + _key = Key(topic->forum()->topicFor(change.newId.msg)); + } + } + }, _lifetime); +} + Wrap Controller::wrap() const { return _widget->wrap(); } diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 7d39facea..2e62ce50e 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -235,6 +235,7 @@ private: void updateSearchControllers(not_null memento); SearchQuery produceSearchQuery(const QString &query) const; void setupMigrationViewer(); + void setupTopicViewer(); not_null _widget; Key _key; diff --git a/Telegram/SourceFiles/info/media/info_media_widget.h b/Telegram/SourceFiles/info/media/info_media_widget.h index 3ba261d58..1b9438aff 100644 --- a/Telegram/SourceFiles/info/media/info_media_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_widget.h @@ -28,7 +28,7 @@ class Memento final : public ContentMemento { public: explicit Memento(not_null controller); Memento(not_null peer, PeerId migratedPeerId, Type type); - Memento(not_null peer, Type type); + Memento(not_null topic, Type type); using SearchState = Api::DelayedSearchController::SavedState; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index dbe0568c6..f1f460983 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -702,8 +702,8 @@ void OverlayWidget::documentUpdated(not_null document) { } } -void OverlayWidget::changingMsgId(not_null row, MsgId oldId) { - if (row == _message) { +void OverlayWidget::changingMsgId(FullMsgId newId, MsgId oldId) { + if (_message && _message->fullId() == newId) { refreshMediaViewer(); } } @@ -4181,7 +4181,7 @@ void OverlayWidget::setSession(not_null session) { session->data().itemIdChanged( ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { - changingMsgId(change.item, change.oldId); + changingMsgId(change.newId, change.oldId); }, _sessionLifetime); session->data().itemRemoved( diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 47673712b..d1eb15b51 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -330,7 +330,7 @@ private: void updateThemePreviewGeometry(); void documentUpdated(not_null document); - void changingMsgId(not_null row, MsgId oldId); + void changingMsgId(FullMsgId newId, MsgId oldId); [[nodiscard]] int finalContentRotation() const; [[nodiscard]] QRect finalContentRect() const; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp index 49be77289..155105e6f 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp @@ -68,7 +68,7 @@ AlbumThumbnail::AlbumThumbnail( const auto availableFileWidth = st::sendMediaPreviewSize - st.thumbSize - - st.padding.right() + - st.thumbSkip // Right buttons. - st::sendBoxAlbumGroupButtonFile.width * 2 - st::sendBoxAlbumGroupEditInternalSkip * 2 @@ -392,7 +392,7 @@ void AlbumThumbnail::paintFile( int top, int outerWidth) { const auto &st = st::attachPreviewThumbLayout; - const auto textLeft = left + st.thumbSize + st.padding.right(); + const auto textLeft = left + st.thumbSize + st.thumbSkip; p.drawPixmap(left, top, _fileThumb); p.setFont(st::semiboldFont); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a69405816..3ec6ed905 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -39,6 +39,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_resolver.h" #include "data/data_changes.h" #include "data/data_group_call.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" #include "data/data_chat_filters.h" #include "data/data_peer_values.h" #include "passport/passport_form_controller.h" @@ -674,6 +676,27 @@ SessionController::SessionController( }); }, lifetime()); + session->data().itemIdChanged( + ) | rpl::start_with_next([=](Data::Session::IdChange change) { + const auto current = _activeChatEntry.current(); + if (const auto topic = current.key.topic()) { + if (topic->rootId() == change.oldId) { + setActiveChatEntry({ + Dialogs::Key(topic->forum()->topicFor(change.newId.msg)), + current.fullId, + }); + } + } + for (auto &entry : _chatEntryHistory) { + if (const auto topic = entry.key.topic()) { + if (topic->rootId() == change.oldId) { + entry.key = Dialogs::Key( + topic->forum()->topicFor(change.newId.msg)); + } + } + } + }, lifetime()); + session->api().globalPrivacy().suggestArchiveAndMute( ) | rpl::take(1) | rpl::start_with_next([=] { session->api().globalPrivacy().reload(crl::guard(this, [=] {