Support topic on-the-fly creation.

This commit is contained in:
John Preston 2022-10-04 19:34:45 +04:00
parent 065d2e2ac9
commit 3722e55b67
32 changed files with 447 additions and 130 deletions

View file

@ -71,10 +71,10 @@ void Polls::create(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
Data::Histories::ReplyToPlaceholder(),
PollDataToInputMedia(&data),
MTP_string(),
MTP_long(randomId),

View file

@ -148,10 +148,10 @@ void SendExistingMedia(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
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<MTPmessages_SendMedia>(
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;

View file

@ -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);
}

View file

@ -3494,10 +3494,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
history,
replyTo,
randomId,
MTPmessages_SendMessage(
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
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<MTPmessages_SendInlineBotResult>(
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<MTPmessages_SendMedia>(
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<SendingAlbum*> album) {
history,
replyTo,
uint64(0), // randomId
MTPmessages_SendMultiMedia(
Data::Histories::PrepareMessage<MTPmessages_SendMultiMedia>(
MTP_flags(flags),
peer->input,
MTP_int(replyTo),
Data::Histories::ReplyToPlaceholder(),
MTP_vector<MTPInputSingleMedia>(medias),
MTP_int(album->options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())

View file

@ -69,10 +69,10 @@ void ShareBotGame(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(0),
chat->input,
MTP_int(0),
Data::Histories::ReplyToPlaceholder(),
MTP_inputMediaGame(
MTP_inputGameShortName(
bot->inputUser,

View file

@ -139,7 +139,8 @@ void EditForumTopicBox(
const auto requestId = std::make_shared<mtpRequestId>();
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<HistoryView::RepliesMemento>(
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<uint64>();
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<MTPMessageEntity>(),
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 = [=] {

View file

@ -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<ForumTopic>(_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<HistoryItem*> item) {
const auto maybe = topicFor(item->replyToTop());
return maybe ? maybe : topicFor(item->topicRootId());

View file

@ -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<HistoryItem*> 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(

View file

@ -29,6 +29,8 @@ ForumTopic::ForumTopic(not_null<History*> history, MsgId rootId)
, _rootId(rootId) {
}
ForumTopic::~ForumTopic() = default;
not_null<ChannelData*> 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);

View file

@ -30,6 +30,7 @@ public:
static constexpr auto kGeneralId = 1;
ForumTopic(not_null<History*> 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*> _history;
const not_null<Dialogs::MainList*> _list;
const MsgId _rootId = 0;
MsgId _rootId = 0;
QString _title;
DocumentId _iconId = 0;

View file

@ -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*> 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<uint64>();
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*> history,
MsgId rootId) const {
const auto forum = history->peer->forum();
return forum && forum->creating(rootId);
}
int Histories::sendPreparedMessage(
not_null<History*> history,
MsgId replyTo,
uint64 randomId,
PreparedMessage message,
Fn<PreparedMessage(MsgId replyTo)> message,
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
Fn<void(const MTP::Error&, const MTP::Response&)> 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<void()> 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*> 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*> history, int id) {
if (const auto state = lookup(history)) {
finishSentRequest(history, state, id);
@ -883,6 +994,9 @@ void Histories::checkPostponed(not_null<History*> 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*> history,
not_null<State*> state,

View file

@ -104,10 +104,25 @@ public:
not_null<History*> history,
MsgId replyTo,
uint64 randomId,
PreparedMessage message,
Fn<void(const MTPUpdates&, const MTP::Response &)> done,
Fn<PreparedMessage(MsgId replyTo)> message,
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
Fn<void(const MTP::Error&, const MTP::Response&)> fail);
struct ReplyToPlaceholder {
};
template <typename RequestType, typename ...Args>
static Fn<Histories::PreparedMessage(MsgId)> PrepareMessage(
const Args &...args) {
return [=](MsgId replyTo) {
return RequestType(ReplaceReplyTo(args, replyTo)...);
};
}
void checkTopicCreated(FullMsgId rootId, MsgId realId);
[[nodiscard]] MsgId convertTopicReplyTo(
not_null<History*> history,
MsgId replyTo) const;
private:
struct PostponedHistoryRequest {
Fn<mtpRequestId(Fn<void()> finish)> generator;
@ -130,6 +145,22 @@ private:
MsgId aroundId = 0;
mtpRequestId requestId = 0;
};
struct DelayedByTopicMessage {
uint64 randomId = 0;
Fn<PreparedMessage(MsgId replyTo)> message;
Fn<void(const MTPUpdates&, const MTP::Response&)> done;
Fn<void(const MTP::Error&, const MTP::Response&)> fail;
int requestId = 0;
};
template <typename Arg>
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*> history, MsgId tillId, bool force);
void sendReadRequests();
@ -147,6 +178,12 @@ private:
void sendDialogRequests();
[[nodiscard]] bool isCreatingTopic(
not_null<History*> history,
MsgId rootId) const;
void sendCreateTopicRequest(not_null<History*> history, MsgId rootId);
void cancelDelayedByTopicRequest(int id);
const not_null<Session*> _owner;
std::unordered_map<PeerId, std::unique_ptr<History>> _map;
@ -169,6 +206,12 @@ private:
not_null<History*>,
ChatListGroupRequest> _chatListGroupRequests;
base::flat_map<
FullMsgId,
std::vector<DelayedByTopicMessage>> _creatingTopics;
base::flat_map<FullMsgId, MsgId> _createdTopicIds;
base::flat_set<mtpRequestId> _creatingTopicRequests;
};
} // namespace Data

View file

@ -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;
}

View file

@ -1407,10 +1407,12 @@ rpl::producer<not_null<HistoryItem*>> 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<HistoryItem*> 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<ViewElement*> 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<ViewElement*> 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);
}
}
}
}

View file

@ -243,7 +243,7 @@ public:
void itemVisibilitiesUpdated();
struct IdChange {
not_null<HistoryItem*> item;
FullMsgId newId;
MsgId oldId = 0;
};
void notifyItemIdChange(IdChange event);
@ -728,7 +728,7 @@ private:
not_null<Messages*> messagesListForInsert(PeerId peerId);
not_null<HistoryItem*> registerMessage(
std::unique_ptr<HistoryItem> item);
void changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId);
HistoryItem *changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId);
void removeDependencyMessage(not_null<HistoryItem*> item);
void photoApplyFields(

View file

@ -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.

View file

@ -341,7 +341,10 @@ public:
PeerId replier,
std::optional<bool> 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);

View file

@ -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<HistoryItem*> 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<HistoryServiceTopicInfo>();
|| (to && to->Has<HistoryServiceTopicInfo>())
|| (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<HistoryMessageReply>();
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) {

View file

@ -146,7 +146,10 @@ public:
int delta,
PeerId replier,
std::optional<bool> 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;

View file

@ -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;

View file

@ -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<HistoryView::Element> createView(
not_null<HistoryView::ElementDelegate*> delegate,

View file

@ -154,6 +154,16 @@ object_ptr<Window::SectionWidget> 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<Window::SessionController*> 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<RepliesMemento*> memento) {
_inner->saveState(memento->list());
}
void RepliesWidget::restoreState(not_null<RepliesMemento*> memento) {
const auto setReplies = [&](std::shared_ptr<Data::RepliesList> replies) {
_replies = std::move(replies);
void RepliesWidget::createReplies() {
auto old = base::take(_replies);
setReplies(std::make_shared<Data::RepliesList>(_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<Data::RepliesList> 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<RepliesMemento*> memento) {
if (auto replies = memento->getReplies()) {
setReplies(std::move(replies));
} else if (!_replies) {
setReplies(std::make_shared<Data::RepliesList>(_history, _rootId));
createReplies();
}
restoreReplyReturns(memento->replyReturns());
_inner->restoreState(memento->list());

View file

@ -163,6 +163,8 @@ private:
void updateAdaptiveLayout();
void saveState(not_null<RepliesMemento*> memento);
void restoreState(not_null<RepliesMemento*> memento);
void setReplies(std::shared_ptr<Data::RepliesList> 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<HistoryItem*> item);
@ -276,6 +280,7 @@ private:
mutable bool _newTopicDiscarded = false;
std::shared_ptr<Data::RepliesList> _replies;
rpl::lifetime _repliesLifetime;
rpl::variable<bool> _areComments = false;
std::shared_ptr<SendActionPainter> _sendAction;
QPointer<ListWidget> _inner;
@ -363,13 +368,17 @@ public:
}
private:
void setupTopicViewer();
const not_null<History*> _history;
const MsgId _rootId = 0;
MsgId _rootId = 0;
const MsgId _highlightId = 0;
ListMemento _list;
std::shared_ptr<Data::RepliesList> _replies;
std::vector<MsgId> _replyReturns;
rpl::lifetime _lifetime;
};
} // namespace HistoryView

View file

@ -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<Data::ForumTopic*> 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)

View file

@ -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

View file

@ -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();
}

View file

@ -235,6 +235,7 @@ private:
void updateSearchControllers(not_null<ContentMemento*> memento);
SearchQuery produceSearchQuery(const QString &query) const;
void setupMigrationViewer();
void setupTopicViewer();
not_null<WrapWidget*> _widget;
Key _key;

View file

@ -28,7 +28,7 @@ class Memento final : public ContentMemento {
public:
explicit Memento(not_null<Controller*> controller);
Memento(not_null<PeerData*> peer, PeerId migratedPeerId, Type type);
Memento(not_null<Data::ForumTopic*> peer, Type type);
Memento(not_null<Data::ForumTopic*> topic, Type type);
using SearchState = Api::DelayedSearchController::SavedState;

View file

@ -702,8 +702,8 @@ void OverlayWidget::documentUpdated(not_null<DocumentData*> document) {
}
}
void OverlayWidget::changingMsgId(not_null<HistoryItem*> 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<Main::Session*> 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(

View file

@ -330,7 +330,7 @@ private:
void updateThemePreviewGeometry();
void documentUpdated(not_null<DocumentData*> document);
void changingMsgId(not_null<HistoryItem*> row, MsgId oldId);
void changingMsgId(FullMsgId newId, MsgId oldId);
[[nodiscard]] int finalContentRotation() const;
[[nodiscard]] QRect finalContentRect() const;

View file

@ -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);

View file

@ -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, [=] {