Unify message sending, track forum topic icons.

This commit is contained in:
John Preston 2022-09-27 20:52:35 +04:00
parent 3b3792ef75
commit 9f652b0d3f
29 changed files with 477 additions and 259 deletions

View file

@ -192,7 +192,7 @@ messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction;
messageActionTopicCreate#4619708d title:string = MessageAction;
messageActionTopicCreate#6fa796ac flags:# title:string icon_emoji_id:flags.0?long = MessageAction;
messageActionTopicEditTitle#ce0b23cd title:string = MessageAction;
messageActionTopicEditIcon#820a6e2b emoji_document_id:long = MessageAction;
@ -1856,7 +1856,7 @@ channels.deleteParticipantHistory#367544db channel:InputChannel participant:Inpu
channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
channels.createForumTopic#21289f15 flags:# no_webpage:flags.3?true channel:InputChannel title:string icon_emoji_id:flags.3?long media:flags.0?InputMedia message:string random_id:long entities:flags.1?Vector<MessageEntity> send_as:flags.2?InputPeer = Updates;
channels.createForumTopic#60bf3bc9 flags:# channel:InputChannel title:string icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates;
channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics;
channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector<int> = messages.ForumTopics;
channels.editForumTitle#8881b9b1 channel:InputChannel topic_id:int title:string = Updates;

View file

@ -65,47 +65,40 @@ void Polls::create(
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto replyTo = action.replyTo;
history->sendRequestId = _api.request(MTPmessages_SendMedia(
const auto replyTo = action.replyTo;
const auto randomId = base::RandomValue<uint64>();
histories.sendPreparedMessage(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
PollDataToInputMedia(&data),
MTP_string(),
MTP_long(base::RandomValue<uint64>()),
MTP_long(randomId),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](
const MTPUpdates &result,
const MTP::Response &response) mutable {
_session->updates().applyUpdates(result);
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
done();
finish();
}).fail([=](
const MTP::Error &error,
const MTP::Response &response) mutable {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
fail();
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
done();
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
fail();
});
}

View file

@ -141,12 +141,14 @@ void SendExistingMedia(
caption,
HistoryMessageMarkupData());
auto performRequest = [=](const auto &repeatRequest) -> void {
const auto performRequest = [=](const auto &repeatRequest) -> void {
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto usedFileReference = media->fileReference();
history->sendRequestId = api->request(MTPmessages_SendMedia(
const auto usedFileReference = media->fileReference();
histories.sendPreparedMessage(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
@ -157,26 +159,20 @@ void SendExistingMedia(
sentEntities,
MTP_int(message.action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
finish();
}).fail([=](const MTP::Error &error) {
if (error.code() == 400
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
api->refreshFileReference(origin, [=](const auto &result) {
if (media->fileReference() != usedFileReference) {
repeatRequest(repeatRequest);
} else {
api->sendMessageFail(error, peer, randomId, newId);
}
});
} else {
api->sendMessageFail(error, peer, randomId, newId);
}
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (error.code() == 400
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
api->refreshFileReference(origin, [=](const auto &result) {
if (media->fileReference() != usedFileReference) {
repeatRequest(repeatRequest);
} else {
api->sendMessageFail(error, peer, randomId, newId);
}
});
} else {
api->sendMessageFail(error, peer, randomId, newId);
}
});
};
performRequest(performRequest);
@ -314,10 +310,11 @@ bool SendDice(MessageToSend &message) {
TextWithEntities(),
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
HistoryMessageMarkupData());
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = api->request(MTPmessages_SendMedia(
histories.sendPreparedMessage(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
@ -328,15 +325,9 @@ bool SendDice(MessageToSend &message) {
MTP_vector<MTPMessageEntity>(),
MTP_int(message.action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
finish();
}).fail([=](const MTP::Error &error) {
api->sendMessageFail(error, peer, randomId, newId);
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId, newId);
});
api->finishForwarding(message.action);
return true;

View file

@ -3479,53 +3479,47 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
}
const auto viaBotId = UserId();
const auto replyTo = action.replyTo;
lastMessage = history->addNewLocalMessage(
newId.msg,
flags,
viaBotId,
action.replyTo,
replyTo,
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
sending,
media,
HistoryMessageMarkupData());
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_SendMessage(
histories.sendPreparedMessage(
history,
replyTo,
randomId,
MTPmessages_SendMessage(
MTP_flags(sendFlags),
peer->input,
MTP_int(action.replyTo),
MTP_int(replyTo),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](
const MTPUpdates &result,
const MTP::Response &response) {
applyUpdates(result, randomId);
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
finish();
}).fail([=](
const MTP::Error &error,
const MTP::Response &response) {
if (error.type() == qstr("MESSAGE_EMPTY")) {
lastMessage->destroy();
} else {
sendMessageFail(error, peer, randomId, newId);
}
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (error.type() == qstr("MESSAGE_EMPTY")) {
lastMessage->destroy();
} else {
sendMessageFail(error, peer, randomId, newId);
}
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
});
}
@ -3639,34 +3633,27 @@ void ApiWrap::sendInlineResult(
history->startSavingCloudDraft();
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_SendInlineBotResult(
const auto replyTo = action.replyTo;
histories.sendPreparedMessage(
history,
replyTo,
randomId,
MTPmessages_SendInlineBotResult(
MTP_flags(sendFlags),
peer->input,
MTP_int(action.replyTo),
MTP_int(replyTo),
MTP_long(randomId),
MTP_long(data->getQueryId()),
MTP_string(data->getId()),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](
const MTPUpdates &result,
const MTP::Response &response) {
applyUpdates(result, randomId);
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
finish();
}).fail([=](
const MTP::Error &error,
const MTP::Response &response) {
sendMessageFail(error, peer, randomId, newId);
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
), [=](const MTPUpdates &result, const MTP::Response &response) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}, [=](const MTP::Error &error, const MTP::Response &response) {
sendMessageFail(error, peer, randomId, newId);
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
});
finishForwarding(action);
}
@ -3792,11 +3779,13 @@ void ApiWrap::sendMediaWithRandomId(
: MTPmessages_SendMedia::Flag(0));
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto peer = history->peer;
const auto itemId = item->fullId();
history->sendRequestId = request(MTPmessages_SendMedia(
const auto peer = history->peer;
const auto itemId = item->fullId();
histories.sendPreparedMessage(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
MTP_flags(flags),
peer->input,
MTP_int(replyTo),
@ -3807,20 +3796,12 @@ void ApiWrap::sendMediaWithRandomId(
sentEntities,
MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
finish();
if (updateRecentStickers) {
requestRecentStickersForce(true);
}
}).fail([=](const MTP::Error &error) {
sendMessageFail(error, peer, randomId, itemId);
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (updateRecentStickers) {
requestRecentStickersForce(true);
}
}, [=](const MTP::Error &error, const MTP::Response &response) {
sendMessageFail(error, peer, randomId, itemId);
});
}
@ -3904,33 +3885,28 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
? MTPmessages_SendMultiMedia::Flag::f_send_as
: MTPmessages_SendMultiMedia::Flag(0));
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto peer = history->peer;
history->sendRequestId = request(MTPmessages_SendMultiMedia(
const auto peer = history->peer;
histories.sendPreparedMessage(
history,
replyTo,
uint64(0), // randomId
MTPmessages_SendMultiMedia(
MTP_flags(flags),
peer->input,
MTP_int(replyTo),
MTP_vector<MTPInputSingleMedia>(medias),
MTP_int(album->options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
_sendingAlbums.remove(groupId);
applyUpdates(result);
finish();
}).fail([=](const MTP::Error &error) {
if (const auto album = _sendingAlbums.take(groupId)) {
for (const auto &item : (*album)->items) {
sendMessageFail(error, peer, item.randomId, item.msgId);
}
} else {
sendMessageFail(error, peer);
), [=](const MTPUpdates &result, const MTP::Response &response) {
_sendingAlbums.remove(groupId);
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (const auto album = _sendingAlbums.take(groupId)) {
for (const auto &item : (*album)->items) {
sendMessageFail(error, peer, item.randomId, item.msgId);
}
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
} else {
sendMessageFail(error, peer);
}
});
}

View file

@ -63,11 +63,13 @@ void ShareBotGame(
const QString &shortName) {
const auto history = chat->owner().history(chat);
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto randomId = base::RandomValue<uint64>();
const auto api = &chat->session().api();
history->sendRequestId = api->request(MTPmessages_SendMedia(
const auto randomId = base::RandomValue<uint64>();
const auto replyTo = 0;
histories.sendPreparedMessage(
history,
replyTo,
randomId,
MTPmessages_SendMedia(
MTP_flags(0),
chat->input,
MTP_int(0),
@ -81,16 +83,9 @@ void ShareBotGame(
MTPVector<MTPMessageEntity>(),
MTP_int(0), // schedule_date
MTPInputPeer() // send_as
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
finish();
}).fail([=](const MTP::Error &error) {
api->sendMessageFail(error, chat);
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
chat->session().api().sendMessageFail(error, chat);
});
}

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_custom_emoji.h"
#include "main/main_session.h"
#include "history/history.h"
#include "history/view/history_view_replies_section.h"
#include "lang/lang_keys.h"
#include "info/profile/info_profile_emoji_status_panel.h"
#include "window/window_session_controller.h"
@ -142,9 +143,16 @@ void EditForumTopicBox(
box->closeBox();
return;
} else if (title->getLastText().trimmed().isEmpty()) {
title->setFocus();
title->showError();
return;
}
controller->showSection(
std::make_shared<HistoryView::RepliesMemento>(
forum,
forum->peer->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();
@ -178,7 +186,7 @@ void EditForumTopicBox(
const auto api = &forum->session().api();
if (state->titleRequestId <= 0) {
if (title->getLastText().trimmed().isEmpty()) {
title->setFocus();
title->showError();
return;
}
state->titleRequestId = api->request(MTPchannels_EditForumTitle(

View file

@ -120,17 +120,24 @@ void Forum::applyReceivedTopics(
}
}
void Forum::applyTopicAdded(MsgId rootId, const QString &title) {
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(
rootId,
std::make_unique<ForumTopic>(_history, rootId)
).first->second.get();
raw->applyTitle(title);
raw->addToChatList(FilterId(), topicsList());
_chatsListChanges.fire({});
raw->applyIconId(iconId);
if (!creating(rootId)) {
raw->addToChatList(FilterId(), topicsList());
_chatsListChanges.fire({});
}
}
}
@ -140,6 +147,30 @@ void Forum::applyTopicRemoved(MsgId rootId) {
//}
}
MsgId Forum::reserveCreatingId(
const QString &title,
DocumentId iconId) {
const auto result = _history->owner().nextLocalMessageId();
_creatingRootIds.emplace(result);
applyTopicAdded(result, title, iconId);
return result;
}
void Forum::discardCreatingId(MsgId rootId) {
Expects(creating(rootId));
const auto i = _topics.find(rootId);
if (i != end(_topics)) {
Assert(!i->second->inChatList());
_topics.erase(i);
}
_creatingRootIds.remove(rootId);
}
bool Forum::creating(MsgId rootId) const {
return _creatingRootIds.contains(rootId);
}
ForumTopic *Forum::topicFor(not_null<HistoryItem*> item) {
return topicFor(item->topicRootId());
}
@ -151,7 +182,7 @@ ForumTopic *Forum::topicFor(MsgId rootId) {
}
} else {
// #TODO forum lang
applyTopicAdded(rootId, "General! Created.");
applyTopicAdded(rootId, "General! Created.", DocumentId(0));
return _topics.find(rootId)->second.get();
}
return nullptr;

View file

@ -31,13 +31,22 @@ public:
[[nodiscard]] rpl::producer<> chatsListChanges() const;
[[nodiscard]] rpl::producer<> chatsListLoadedEvents() const;
void applyTopicAdded(MsgId rootId, const QString &title);
void applyTopicAdded(
MsgId rootId,
const QString &title,
DocumentId iconId);
void applyTopicRemoved(MsgId rootId);
[[nodiscard]] ForumTopic *topicFor(not_null<HistoryItem*> item);
[[nodiscard]] ForumTopic *topicFor(MsgId rootId);
void applyReceivedTopics(const MTPmessages_ForumTopics &topics);
[[nodiscard]] MsgId reserveCreatingId(
const QString &title,
DocumentId iconId);
void discardCreatingId(MsgId rootId);
[[nodiscard]] bool creating(MsgId rootId) const;
private:
void applyReceivedTopics(
const MTPmessages_ForumTopics &topics,
@ -54,6 +63,8 @@ private:
MsgId _offsetTopicId = 0;
bool _allLoaded = false;
base::flat_set<MsgId> _creatingRootIds;
rpl::event_stream<> _chatsListChanges;
rpl::event_stream<> _chatsListLoadedEvents;

View file

@ -10,27 +10,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_forum.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "dialogs/dialogs_main_list.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "history/history.h"
#include "history/history_item.h"
#include "ui/painter.h"
namespace Data {
ForumTopic::ForumTopic(not_null<History*> forum, MsgId rootId)
: Entry(&forum->owner(), Type::ForumTopic)
, _forum(forum)
, _list(forum->peer->asChannel()->forum()->topicsList())
ForumTopic::ForumTopic(not_null<History*> history, MsgId rootId)
: Entry(&history->owner(), Type::ForumTopic)
, _history(history)
, _list(forum()->topicsList())
, _rootId(rootId) {
}
not_null<ChannelData*> ForumTopic::channel() const {
return _forum->peer->asChannel();
return _history->peer->asChannel();
}
not_null<History*> ForumTopic::forum() const {
return _forum;
not_null<History*> ForumTopic::history() const {
return _history;
}
not_null<Forum*> ForumTopic::forum() const {
return channel()->forum();
}
MsgId ForumTopic::rootId() const {
@ -110,7 +116,7 @@ void ForumTopic::applyTopicFields(
void ForumTopic::applyTopicTopMessage(MsgId topMessageId) {
if (topMessageId) {
const auto itemId = FullMsgId(_forum->peer->id, topMessageId);
const auto itemId = FullMsgId(_history->peer->id, topMessageId);
if (const auto item = owner().message(itemId)) {
setLastServerMessage(item);
} else {
@ -193,6 +199,9 @@ void ForumTopic::setOutboxReadTill(MsgId upTo) {
}
void ForumTopic::loadUserpic() {
if (_icon) {
[[maybe_unused]] const auto preload = _icon->ready();
}
}
void ForumTopic::paintUserpic(
@ -201,7 +210,19 @@ void ForumTopic::paintUserpic(
int x,
int y,
int size) const {
// #TODO forum
if (_icon) {
const auto emoji = Data::FrameSizeFromTag(
Data::CustomEmojiManager::SizeTag::Isolated
) / style::DevicePixelRatio();
_icon->paint(p, {
.preview = st::windowBgOver->c,
.now = crl::now(),
.position = QPoint(
x + (size - emoji) / 2,
y + (size - emoji) / 2),
.paused = false,
});
}
}
void ForumTopic::requestChatListMessage() {
@ -270,7 +291,15 @@ DocumentId ForumTopic::iconId() const {
}
void ForumTopic::applyIconId(DocumentId iconId) {
_iconId = iconId;
if (_iconId != iconId) {
_iconId = iconId;
_icon = iconId
? _history->owner().customEmojiManager().create(
_iconId,
[=] { updateChatListEntry(); },
Data::CustomEmojiManager::SizeTag::Isolated)
: nullptr;
}
updateChatListEntry();
}
@ -348,7 +377,7 @@ Dialogs::UnreadState ForumTopic::chatListUnreadState() const {
auto result = Dialogs::UnreadState();
const auto count = _unreadCount.value_or(0);
const auto mark = !count && _unreadMark;
const auto muted = _forum->mute();
const auto muted = _history->mute();
result.messages = count;
result.messagesMuted = muted ? count : 0;
result.chats = count ? 1 : 0;

View file

@ -23,18 +23,20 @@ class Session;
namespace Data {
class Session;
class Forum;
class ForumTopic final : public Dialogs::Entry {
public:
static constexpr auto kGeneralId = 1;
ForumTopic(not_null<History*> forum, MsgId rootId);
ForumTopic(not_null<History*> history, MsgId rootId);
ForumTopic(const ForumTopic &) = delete;
ForumTopic &operator=(const ForumTopic &) = delete;
[[nodiscard]] not_null<ChannelData*> channel() const;
[[nodiscard]] not_null<History*> forum() const;
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] not_null<Forum*> forum() const;
[[nodiscard]] MsgId rootId() const;
[[nodiscard]] bool isGeneral() const {
return (_rootId == kGeneralId);
@ -107,7 +109,7 @@ private:
int chatListNameVersion() const override;
const not_null<History*> _forum;
const not_null<History*> _history;
const not_null<Dialogs::MainList*> _list;
const MsgId _rootId = 0;
@ -117,6 +119,8 @@ private:
base::flat_set<QChar> _titleFirstLetters;
int _titleVersion = 0;
std::unique_ptr<Ui::Text::CustomEmoji> _icon;
std::optional<MsgId> _inboxReadBefore;
std::optional<MsgId> _outboxReadBefore;
std::optional<int> _unreadCount;

View file

@ -841,6 +841,39 @@ int Histories::sendRequest(
return id;
}
int Histories::sendPreparedMessage(
not_null<History*> history,
MsgId replyTo,
uint64 randomId,
PreparedMessage 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) {
const auto type = RequestType::Send;
return sendRequest(history, type, [=](Fn<void()> finish) {
const auto session = &_owner->session();
const auto api = &session->api();
history->sendRequestId = api->request(
base::duplicate(request)
).done([=](
const MTPUpdates &result,
const MTP::Response &response) {
api->applyUpdates(result, randomId);
done(result, response);
finish();
}).fail([=](
const MTP::Error &error,
const MTP::Response &response) {
fail(error, response);
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
});
});
}
void Histories::checkPostponed(not_null<History*> history, int id) {
if (const auto state = lookup(history)) {
finishSentRequest(history, state, id);

View file

@ -16,6 +16,11 @@ namespace Main {
class Session;
} // namespace Main
namespace MTP {
class Error;
struct Response;
} // namespace MTP
namespace Data {
class Session;
@ -90,6 +95,19 @@ public:
Fn<mtpRequestId(Fn<void()> finish)> generator);
void cancelRequest(int id);
using PreparedMessage = std::variant<
MTPmessages_SendMessage,
MTPmessages_SendMedia,
MTPmessages_SendInlineBotResult,
MTPmessages_SendMultiMedia>;
int sendPreparedMessage(
not_null<History*> history,
MsgId replyTo,
uint64 randomId,
PreparedMessage message,
Fn<void(const MTPUpdates&, const MTP::Response &)> done,
Fn<void(const MTP::Error&, const MTP::Response&)> fail);
private:
struct PostponedHistoryRequest {
Fn<mtpRequestId(Fn<void()> finish)> generator;

View file

@ -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.h"
#include "data/data_forum_topic.h"
#include "lang/lang_keys.h"
#include "apiwrap.h"
@ -36,6 +37,13 @@ constexpr auto kMessagesPerPage = 50;
HistoryService::PreparedText{ { .text = text } });
}
[[nodiscard]] bool IsCreating(not_null<History*> history, MsgId rootId) {
if (const auto forum = history->peer->forum()) {
return forum->creating(rootId);
}
return false;
}
} // namespace
struct RepliesList::Viewer {
@ -50,7 +58,8 @@ struct RepliesList::Viewer {
RepliesList::RepliesList(not_null<History*> history, MsgId rootId)
: _history(history)
, _rootId(rootId) {
, _rootId(rootId)
, _creating(IsCreating(history, rootId)) {
}
RepliesList::~RepliesList() {
@ -104,7 +113,9 @@ rpl::producer<MessagesSlice> RepliesList::source(
_history->owner().channelDifferenceTooLong(
) | rpl::filter([=](not_null<ChannelData*> channel) {
if (_history->peer != channel || !_skippedAfter.has_value()) {
if (_creating
|| _history->peer != channel
|| !_skippedAfter.has_value()) {
return false;
}
_skippedAfter = std::nullopt;
@ -297,7 +308,8 @@ void RepliesList::injectRootDivider(
}
bool RepliesList::buildFromData(not_null<Viewer*> viewer) {
if (_list.empty() && _skippedBefore == 0 && _skippedAfter == 0) {
if (_creating
|| (_list.empty() && _skippedBefore == 0 && _skippedAfter == 0)) {
viewer->slice.ids.clear();
viewer->slice.nearestToAround = FullMsgId();
viewer->slice.fullCount
@ -429,6 +441,8 @@ HistoryItem *RepliesList::lookupRoot() {
}
void RepliesList::loadAround(MsgId id) {
Expects(!_creating);
if (_loadingAround && *_loadingAround == id) {
return;
}

View file

@ -72,6 +72,7 @@ private:
std::optional<MsgId> _loadingAround;
HistoryService *_divider = nullptr;
bool _dividerWithComments = false;
bool _creating = false;
int _beforeId = 0;
int _afterId = 0;

View file

@ -1244,7 +1244,21 @@ void Session::setupUserIsContactViewer() {
}, _lifetime);
}
Session::~Session() = default;
Session::~Session() {
// We must clear all forums before clearing customEmojiManager.
// Because in Data::ForumTopic an Ui::Text::CustomEmoji is cached.
auto forums = base::flat_set<not_null<ChannelData*>>();
for (const auto &[peerId, peer] : _peers) {
if (const auto channel = peer->asChannel()) {
if (channel->isForum()) {
forums.emplace(channel);
}
}
}
for (const auto &channel : forums) {
channel->setFlags(channel->flags() & ~ChannelDataFlag::Forum);
}
}
template <typename Method>
void Session::enumerateItemViews(
@ -3789,13 +3803,16 @@ void Session::refreshChatListEntry(Dialogs::Key key) {
using namespace Dialogs;
const auto entry = key.entry();
const auto history = key.history();
const auto mainList = entry->asTopic()
? entry->asTopic()->forum()->peer->forum()->topicsList()
const auto history = entry->asHistory();
const auto topic = entry->asTopic();
const auto mainList = topic
? topic->forum()->topicsList()
: chatsList(entry->folder());
auto event = ChatListEntryRefresh{ .key = key };
const auto creating = event.existenceChanged = !entry->inChatList();
if (event.existenceChanged) {
if (creating && topic && topic->forum()->creating(topic->rootId())) {
return;
} else if (event.existenceChanged) {
const auto mainRow = entry->addToChatList(0, mainList);
_contactsNoChatsList.del(key, mainRow);
} else {
@ -3860,7 +3877,7 @@ void Session::removeChatListEntry(Dialogs::Key key) {
}
}
const auto mainList = entry->asTopic()
? entry->asTopic()->forum()->peer->forum()->topicsList()
? entry->asTopic()->forum()->topicsList()
: chatsList(entry->folder());
entry->removeFromChatList(0, mainList);
_chatListEntryRefreshes.fire(ChatListEntryRefresh{

View file

@ -59,7 +59,7 @@ History *Key::parentHistory() const {
if (const auto result = history()) {
return result;
} else if (const auto child = topic()) {
return child->forum();
return child->history();
}
return nullptr;
}

View file

@ -385,7 +385,7 @@ void Widget::chosenRow(const ChosenRow &row) {
const auto history = row.key.history();
if (const auto topic = row.key.topic()) {
controller()->showRepliesForMessage(
topic->forum(),
topic->history(),
topic->rootId(),
ShowAtUnreadMsgId,
Window::SectionShow::Way::ClearStack);

View file

@ -1045,29 +1045,7 @@ bool HistoryItem::computeDropForwardedInfo() const {
&& (!media || !media->forceForwardedInfo()));
}
MsgId HistoryItem::replyToId() const {
if (const auto reply = Get<HistoryMessageReply>()) {
return reply->replyToId();
}
return 0;
}
MsgId HistoryItem::replyToTop() const {
if (const auto reply = Get<HistoryMessageReply>()) {
return reply->replyToTop();
}
return 0;
}
MsgId HistoryItem::topicRootId() const {
if (const auto reply = Get<HistoryMessageReply>()
; reply && reply->topicPost) {
return reply->replyToTop();
}
return Data::ForumTopic::kGeneralId;
}
MsgId HistoryItem::inThread(MsgId rootId) const {
bool HistoryItem::inThread(MsgId rootId) const {
const auto checkId = (rootId == Data::ForumTopic::kGeneralId)
? topicRootId()
: replyToTop();

View file

@ -341,8 +341,7 @@ public:
PeerId replier,
std::optional<bool> unread) {
}
virtual void setReplyToTop(MsgId replyToTop) {
}
virtual void setReplyToTop(MsgId replyToTop) = 0;
virtual void setPostAuthor(const QString &author) {
}
virtual void setRealId(MsgId newId);
@ -405,10 +404,10 @@ public:
virtual void setText(const TextWithEntities &textWithEntities) {
}
[[nodiscard]] MsgId replyToId() const;
[[nodiscard]] MsgId replyToTop() const;
[[nodiscard]] MsgId topicRootId() const;
[[nodiscard]] MsgId inThread(MsgId rootId) const;
[[nodiscard]] virtual MsgId replyToId() const = 0;
[[nodiscard]] virtual MsgId replyToTop() const = 0;
[[nodiscard]] virtual MsgId topicRootId() const = 0;
[[nodiscard]] bool inThread(MsgId rootId) const;
[[nodiscard]] not_null<PeerData*> author() const;

View file

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_media_types.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_user.h"
#include "data/data_web_page.h"
#include "data/data_sponsored_messages.h"
@ -1490,6 +1491,28 @@ TextWithEntities HistoryMessage::withLocalEntities(
return textWithEntities;
}
MsgId HistoryMessage::replyToId() const {
if (const auto reply = Get<HistoryMessageReply>()) {
return reply->replyToId();
}
return 0;
}
MsgId HistoryMessage::replyToTop() const {
if (const auto reply = Get<HistoryMessageReply>()) {
return reply->replyToTop();
}
return 0;
}
MsgId HistoryMessage::topicRootId() const {
if (const auto reply = Get<HistoryMessageReply>()
; reply && reply->topicPost) {
return reply->replyToTop();
}
return Data::ForumTopic::kGeneralId;
}
void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
for (const auto &entity : textWithEntities.entities) {
auto type = entity.type();

View file

@ -180,6 +180,10 @@ public:
void destroyHistoryEntry() override;
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override;
[[nodiscard]] MsgId replyToId() const override;
[[nodiscard]] MsgId replyToTop() const override;
[[nodiscard]] MsgId topicRootId() const override;
void setText(const TextWithEntities &textWithEntities) override;
[[nodiscard]] TextWithEntities originalText() const override;
[[nodiscard]] auto originalTextWithLocalEntities() const

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_item_preview.h"
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_game.h"
@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_changes.h"
#include "data/data_group_call.h" // Data::GroupCall::id().
#include "data/stickers/data_custom_emoji.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "base/unixtime.h"
@ -652,13 +654,15 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto result = PreparedText{};
result.text = { "topic icon: " }; // #TODO forum lang
result.text.append(TextWithEntities{
" ",
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
QString::number(+action.vemoji_document_id().v)) },
Data::SerializeCustomEmojiId({
.id = action.vemoji_document_id().v })) },
});
return result;
};
@ -800,7 +804,20 @@ void HistoryService::applyAction(const MTPMessageAction &action) {
data.vmonths().v);
}, [&](const MTPDmessageActionTopicCreate &data) {
if (const auto forum = history()->peer->forum()) {
forum->applyTopicAdded(id, qs(data.vtitle()));
const auto iconId = DocumentId(0); // #TODO forum icon
forum->applyTopicAdded(id, qs(data.vtitle()), iconId);
}
}, [&](const MTPDmessageActionTopicEditTitle &data) {
if (const auto forum = history()->peer->forum()) {
if (const auto topic = forum->topicFor(replyToTop())) {
topic->applyTitle(qs(data.vtitle()));
}
}
}, [&](const MTPDmessageActionTopicEditIcon &data) {
if (const auto forum = history()->peer->forum()) {
if (const auto topic = forum->topicFor(replyToTop())) {
topic->applyIconId(data.vemoji_document_id().v);
}
}
}, [](const auto &) {
});
@ -1299,6 +1316,36 @@ TextWithEntities HistoryService::inReplyText() const {
return Ui::Text::Wrapped(result, EntityType::PlainLink);
}
MsgId HistoryService::replyToId() const {
return 0; // Don't render replies info in service, only handle threads.
}
MsgId HistoryService::replyToTop() const {
if (const auto data = GetDependentData()) {
return data->topId;
}
return 0;
}
MsgId HistoryService::topicRootId() const {
if (const auto data = GetDependentData()
; data && data->topicPost) {
return data->topId;
}
return Data::ForumTopic::kGeneralId;
}
void HistoryService::setReplyToTop(MsgId replyToTop) {
const auto data = GetDependentData();
if (!data
|| (data->topId == replyToTop)
|| (data->topId != 0)
|| isScheduled()) {
return;
}
data->topId = replyToTop;
}
std::unique_ptr<HistoryView::Element> HistoryService::createView(
not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing) {
@ -1422,7 +1469,13 @@ void HistoryService::createFromMtp(const MTPDmessage &message) {
void HistoryService::createFromMtp(const MTPDmessageService &message) {
const auto type = message.vaction().type();
if (type == mtpc_messageActionSetChatTheme) {
if (type == mtpc_messageActionPinMessage) {
UpdateComponents(HistoryServicePinned::Bit());
} else if (type == mtpc_messageActionTopicCreate
|| type == mtpc_messageActionTopicEditTitle
|| type == mtpc_messageActionTopicEditIcon) {
UpdateComponents(HistoryServiceTopicInfo::Bit());
} else if (type == mtpc_messageActionSetChatTheme) {
setupChatThemeChange();
} else if (type == mtpc_messageActionSetMessagesTTL) {
setupTTLChange();
@ -1505,14 +1558,13 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
const auto peerId = data.vreply_to_peer_id()
? peerFromMTP(*data.vreply_to_peer_id())
: history()->peer->id;
if (message.vaction().type() == mtpc_messageActionPinMessage) {
UpdateComponents(HistoryServicePinned::Bit());
}
if (const auto dependent = GetDependentData()) {
dependent->peerId = (peerId != history()->peer->id)
? peerId
: 0;
dependent->msgId = data.vreply_to_msg_id().v;
dependent->topId = data.vreply_to_top_id().value_or(
data.vreply_to_msg_id().v);
if (!updateDependent()) {
RequestDependentMessageData(
this,

View file

@ -15,9 +15,11 @@ class Service;
struct HistoryServiceDependentData {
PeerId peerId = 0;
MsgId msgId = 0;
HistoryItem *msg = nullptr;
ClickHandlerPtr lnk;
MsgId msgId = 0;
MsgId topId = 0;
bool topicPost = false;
};
struct HistoryServicePinned
@ -25,6 +27,11 @@ struct HistoryServicePinned
, public HistoryServiceDependentData {
};
struct HistoryServiceTopicInfo
: public RuntimeComponent<HistoryServiceTopicInfo, HistoryItem>
, public HistoryServiceDependentData {
};
struct HistoryServiceGameScore
: public RuntimeComponent<HistoryServiceGameScore, HistoryItem>
, public HistoryServiceDependentData {
@ -129,6 +136,11 @@ public:
ItemPreview toPreview(ToPreviewOptions options) const override;
TextWithEntities inReplyText() const override;
MsgId replyToId() const override;
MsgId replyToTop() const override;
MsgId topicRootId() const override;
void setReplyToTop(MsgId replyToTop) override;
std::unique_ptr<HistoryView::Element> createView(
not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing = nullptr) override;
@ -149,12 +161,14 @@ protected:
private:
HistoryServiceDependentData *GetDependentData() {
if (auto pinned = Get<HistoryServicePinned>()) {
if (const auto pinned = Get<HistoryServicePinned>()) {
return pinned;
} else if (auto gamescore = Get<HistoryServiceGameScore>()) {
} else if (const auto gamescore = Get<HistoryServiceGameScore>()) {
return gamescore;
} else if (auto payment = Get<HistoryServicePayment>()) {
} else if (const auto payment = Get<HistoryServicePayment>()) {
return payment;
} else if (const auto info = Get<HistoryServiceTopicInfo>()) {
return info;
}
return nullptr;
}

View file

@ -72,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_window.h"
#include "styles/style_info.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include <QtCore/QMimeData>
@ -332,6 +333,10 @@ RepliesWidget::RepliesWidget(
}
RepliesWidget::~RepliesWidget() {
if (_topic && _topic->forum()->creating(_rootId)) {
_topic->forum()->discardCreatingId(_rootId);
_topic = nullptr;
}
if (_readRequestTimer.isActive()) {
sendReadTillRequest();
}
@ -895,7 +900,7 @@ std::optional<QString> RepliesWidget::writeRestriction() const {
}
void RepliesWidget::pushReplyReturn(not_null<HistoryItem*> item) {
if (item->history() == _history && item->replyToTop() == _rootId) {
if (item->history() == _history && item->inThread(_rootId)) {
_replyReturns.push_back(item->id);
} else {
return;
@ -1542,7 +1547,30 @@ Dialogs::RowDescriptor RepliesWidget::activeChat() const {
}
bool RepliesWidget::preventsClose(Fn<void()> &&continueCallback) const {
return _composeControls->preventsClose(std::move(continueCallback));
if (_composeControls->preventsClose(base::duplicate(continueCallback))) {
return true;
} else if (!_newTopicDiscarded
&& _topic
&& _topic->forum()->creating(_rootId)) {
const auto weak = Ui::MakeWeak(this);
auto sure = [=](Fn<void()> &&close) {
if (const auto strong = weak.data()) {
strong->_newTopicDiscarded = true;
}
close();
if (continueCallback) {
continueCallback();
}
};
controller()->show(Ui::MakeConfirmBox({
.text = rpl::single(u"Sure discard?"_q), // #TODO forum lang
.confirmed = std::move(sure),
.confirmText = tr::lng_record_lock_discard(),
.confirmStyle = &st::attentionBoxButton,
}));
return true;
}
return false;
}
QPixmap RepliesWidget::grabForShowAnimation(const Window::SectionSlideParams &params) {
@ -1628,7 +1656,7 @@ bool RepliesWidget::showMessage(
&& _inner->viewByPosition(returnTo->position())
&& returnTo->replyToId() == messageId) {
return returnTo;
} else if (!general && (returnTo->replyToTop() == _rootId)) {
} else if (!general && returnTo->inThread(_rootId)) {
return returnTo;
}
}
@ -1655,7 +1683,6 @@ Window::SectionActionResult RepliesWidget::sendBotCommand(
}
void RepliesWidget::replyToMessage(FullMsgId itemId) {
// if (item->history() != _history || item->replyToTop() != _rootId) {
_composeControls->replyToMessage(itemId);
refreshTopBarActiveChat();
}

View file

@ -269,10 +269,11 @@ private:
[[nodiscard]] std::optional<QString> writeRestriction() const;
const not_null<History*> _history;
const MsgId _rootId = 0;
MsgId _rootId = 0;
std::shared_ptr<Ui::ChatTheme> _theme;
HistoryItem *_root = nullptr;
Data::ForumTopic *_topic = nullptr;
mutable bool _newTopicDiscarded = false;
std::shared_ptr<Data::RepliesList> _replies;
rpl::variable<bool> _areComments = false;

View file

@ -21,9 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/info_controller.h"
#include "boxes/peer_list_box.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_forum_topic.h"
#include "history/history.h"
#include "main/main_session.h"
#include "styles/style_info.h"
#include "styles/style_profile.h"
@ -337,7 +337,7 @@ Key ContentMemento::key() const {
}
ContentMemento::ContentMemento(not_null<Data::ForumTopic*> topic)
: _peer(topic->forum()->peer)
: _peer(topic->channel())
, _migratedPeerId(_peer->migrateFrom() ? _peer->migrateFrom()->id : 0)
, _topic(topic) {
}

View file

@ -22,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_download_manager.h"
#include "history/history_item.h"
#include "history/history.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
@ -48,7 +47,7 @@ PeerData *Key::peer() const {
if (const auto peer = std::get_if<not_null<PeerData*>>(&_value)) {
return *peer;
} else if (const auto topic = this->topic()) {
return topic->forum()->peer;
return topic->channel();
}
return nullptr;
}

View file

@ -257,7 +257,7 @@ void Uploader::sendProgressUpdate(
const auto history = item->history();
auto &manager = _api->session().sendProgressManager();
manager.update(history, type, progress);
if (const auto replyTo = item->replyToTop()) {
if (const auto replyTo = item->topicRootId()) {
if (history->peer->isMegagroup()) {
manager.update(history, replyTo, type, progress);
}

View file

@ -752,7 +752,7 @@ void Filler::addManageTopic() {
return;
}
// #TODO forum lang
const auto history = _topic->forum();
const auto history = _topic->history();
const auto rootId = _topic->rootId();
const auto navigation = _controller;
_addAction(u"Edit topic"_q, [=] {