mirror of
https://github.com/kotatogram/kotatogram-desktop
synced 2025-08-31 06:35:14 +00:00
Support topic on-the-fly creation.
This commit is contained in:
@@ -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());
|
||||
|
@@ -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(
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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(
|
||||
|
Reference in New Issue
Block a user