Update API scheme on layer 148: Drafts in topics.

This commit is contained in:
John Preston 2022-10-17 20:29:48 +04:00
parent 791addd0ee
commit 89d0a71591
60 changed files with 861 additions and 541 deletions

View file

@ -336,7 +336,7 @@ updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg
updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update;
updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update;
updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update;
updateReadFeaturedStickers#571d2742 = Update;
updateRecentStickers#9a422c20 = Update;
updateConfig#a229dd06 = Update;
@ -389,7 +389,7 @@ updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJ
updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = Update;
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
updateMessageReactions#154798c3 peer:Peer msg_id:int reactions:MessageReactions = Update;
updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update;
updateAttachMenuBots#17b7a20b = Update;
updateWebViewResultSent#1592b79d query_id:long = Update;
updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
@ -593,6 +593,7 @@ inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet;
inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet;
inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet;
inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet;
stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet;
@ -958,12 +959,17 @@ channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdmi
channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeUsernames#f04fb3a9 prev_value:Vector<string> new_value:Vector<string> = ChannelAdminLogEventAction;
channelAdminLogEventActionToggleForum#2cc6383 new_value:Bool = ChannelAdminLogEventAction;
channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction;
channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic = ChannelAdminLogEventAction;
channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction;
channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction;
channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
channels.adminLogResults#ed8af74d events:Vector<ChannelAdminLogEvent> chats:Vector<Chat> users:Vector<User> = channels.AdminLogResults;
channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true = ChannelAdminLogEventsFilter;
channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true forums:flags.17?true = ChannelAdminLogEventsFilter;
popularContact#5ce14175 client_id:long importers:int = PopularContact;
@ -1322,7 +1328,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR
account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
sponsoredMessage#3a836df8 flags:# recommended:flags.5?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
sponsoredMessage#3a836df8 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
messages.sponsoredMessages#65a4c7d5 messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
@ -1459,7 +1465,7 @@ stickerKeyword#fcfeb29c document_id:long keyword:Vector<string> = StickerKeyword
username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username;
forumTopicDeleted#23f109b id:int = ForumTopic;
forumTopic#5920d6dc flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings = ForumTopic;
forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector<ForumTopic> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> pts:int = messages.ForumTopics;
@ -1611,8 +1617,8 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t
messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.sendMessage#6460114f flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.8?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.sendMedia#a2e8e1de flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.8?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.reportSpam#cf1592db peer:InputPeer = Bool;
messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings;
@ -1656,14 +1662,14 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs;
messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
messages.sendInlineBotResult#7aa11297 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.sendInlineBotResult#15b11c73 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.8?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.15?int = Updates;
messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs;
messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
messages.saveDraft#b4331e3f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int top_msg_id:flags.2?int peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
messages.getAllDrafts#6a3f8d65 = Updates;
messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers;
messages.readFeaturedStickers#5b118126 id:Vector<long> = Bool;
@ -1692,7 +1698,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages;
messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.sendMultiMedia#68463a19 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.8?int multi_media:Vector<InputSingleMedia> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
@ -1865,6 +1871,7 @@ channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool;
channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool;
channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool;
channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int 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;

View file

@ -351,6 +351,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::RequestPhone: {
HideSingleUseKeyboard(controller, item);
const auto itemId = item->id;
const auto topicRootId = item->topicRootId();
const auto history = item->history();
controller->show(Ui::MakeConfirmBox({
.text = tr::lng_bot_share_phone(),
@ -362,6 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
auto action = Api::SendAction(history);
action.clearDraft = false;
action.replyTo = itemId;
action.topicRootId = topicRootId;
history->session().api().shareContact(
history->session().user(),
action);
@ -381,10 +383,12 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
}
}
const auto replyToId = MsgId(0);
const auto topicRootId = MsgId(0);
Window::PeerMenuCreatePoll(
controller,
item->history()->peer,
replyToId,
topicRootId,
chosen,
disabled);
} break;

View file

@ -37,6 +37,7 @@ struct SendAction {
not_null<History*> history;
SendOptions options;
MsgId replyTo = 0;
MsgId topicRootId = 0;
bool clearDraft = true;
bool generateLocal = true;
MsgId replaceMediaOf = 0;

View file

@ -42,16 +42,20 @@ void Polls::create(
const auto history = action.history;
const auto peer = history->peer;
const auto topicRootId = action.replyTo ? action.topicRootId : 0;
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
if (topicRootId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_top_msg_id;
}
}
const auto clearCloudDraft = action.clearDraft;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearLocalDraft();
history->clearCloudDraft();
history->startSavingCloudDraft();
history->clearLocalDraft(topicRootId);
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
}
const auto silentPost = ShouldSendSilent(peer, action.options);
if (silentPost) {
@ -65,16 +69,17 @@ void Polls::create(
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
auto &histories = history->owner().histories();
const auto replyTo = action.replyTo;
const auto randomId = base::RandomValue<uint64>();
histories.sendPreparedMessage(
history,
replyTo,
action.replyTo,
topicRootId,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
PollDataToInputMedia(&data),
MTP_string(),
MTP_long(randomId),
@ -85,6 +90,7 @@ void Polls::create(
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
_session->changes().historyUpdated(
@ -96,6 +102,7 @@ void Polls::create(
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
fail();

View file

@ -86,6 +86,9 @@ void SendExistingMedia(
if (message.action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
if (message.action.topicRootId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_top_msg_id;
}
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, message.action.options);
@ -118,7 +121,6 @@ void SendExistingMedia(
if (!sentEntities.v.isEmpty()) {
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
}
const auto replyTo = message.action.replyTo;
const auto captionText = caption.text;
if (message.action.options.scheduled) {
@ -133,7 +135,7 @@ void SendExistingMedia(
newId.msg,
flags,
viaBotId,
replyTo,
message.action.replyTo,
HistoryItem::NewMessageDate(message.action.options.scheduled),
messageFromId,
messagePostAuthor,
@ -146,12 +148,14 @@ void SendExistingMedia(
const auto usedFileReference = media->fileReference();
histories.sendPreparedMessage(
history,
replyTo,
message.action.replyTo,
message.action.topicRootId,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
inputMedia(),
MTP_string(captionText),
MTP_long(randomId),
@ -269,6 +273,9 @@ bool SendDice(MessageToSend &message) {
if (message.action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
if (message.action.topicRootId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_top_msg_id;
}
}
const auto replyHeader = NewMessageReplyHeader(message.action);
const auto anonymousPost = peer->amAnonymous();
@ -289,7 +296,6 @@ bool SendDice(MessageToSend &message) {
const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name()
: QString();
const auto replyTo = message.action.replyTo;
if (message.action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
@ -312,12 +318,14 @@ bool SendDice(MessageToSend &message) {
HistoryMessageMarkupData());
histories.sendPreparedMessage(
history,
replyTo,
message.action.replyTo,
message.action.topicRootId,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
MTP_inputMediaDice(MTP_string(emoji)),
MTP_string(),
MTP_long(randomId),
@ -368,9 +376,13 @@ void SendConfirmedFile(
const auto peer = history->peer;
if (!isEditing) {
file->to.replyTo = session->data().histories().convertTopicReplyTo(
const auto histories = &session->data().histories();
file->to.replyTo = histories->convertTopicReplyTo(
history,
file->to.replyTo);
file->to.topicRootId = histories->convertTopicReplyTo(
history,
file->to.topicRootId);
}
session->uploader().upload(newId, file);
@ -378,6 +390,7 @@ void SendConfirmedFile(
auto action = SendAction(history, file->to.options);
action.clearDraft = false;
action.replyTo = file->to.replyTo;
action.topicRootId = file->to.topicRootId;
action.generateLocal = true;
session->api().sendAction(action);

View file

@ -2443,12 +2443,14 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updateDraftMessage: {
const auto &data = update.c_updateDraftMessage();
const auto peerId = peerFromMTP(data.vpeer());
const auto topicRootId = data.vtop_msg_id().value_or_empty();
data.vdraft().match([&](const MTPDdraftMessage &data) {
Data::ApplyPeerCloudDraft(&session(), peerId, data);
Data::ApplyPeerCloudDraft(&session(), peerId, topicRootId, data);
}, [&](const MTPDdraftMessageEmpty &data) {
Data::ClearPeerCloudDraft(
&session(),
peerId,
topicRootId,
data.vdate().value_or_empty());
});
} break;

View file

@ -562,7 +562,7 @@ bool WhoReadExists(not_null<HistoryItem*> item) {
}
const auto type = DetectSeenType(item);
const auto unseen = (type == Ui::WhoReadType::Seen)
? item->unread()
? item->unread(item->history())
: item->isUnreadMedia();
if (unseen) {
return false;

View file

@ -1812,8 +1812,8 @@ void ApiWrap::sendNotifySettingsUpdates() {
session().mtp().sendAnything();
}
void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
_draftsSaveRequestIds.emplace(history, 0);
void ApiWrap::saveDraftToCloudDelayed(not_null<Data::Thread*> thread) {
_draftsSaveRequestIds.emplace(base::make_weak(thread.get()), 0);
if (!_draftsSaveTimer.isActive()) {
_draftsSaveTimer.callOnce(kSaveCloudDraftTimeout);
}
@ -2004,33 +2004,46 @@ void ApiWrap::saveCurrentDraftToCloud() {
Core::App().saveCurrentDraftsToHistories();
for (const auto &controller : _session->windows()) {
if (const auto history = controller->activeChatCurrent().history()) {
if (const auto thread = controller->activeChatCurrent().thread()) {
const auto history = thread->owningHistory();
_session->local().writeDrafts(history);
const auto localDraft = history->localDraft();
const auto cloudDraft = history->cloudDraft();
if (!Data::draftsAreEqual(localDraft, cloudDraft)
const auto topicRootId = thread->topicRootId();
const auto localDraft = history->localDraft(topicRootId);
const auto cloudDraft = history->cloudDraft(topicRootId);
if (!Data::DraftsAreEqual(localDraft, cloudDraft)
&& !_session->supportMode()) {
saveDraftToCloudDelayed(history);
saveDraftToCloudDelayed(thread);
}
}
}
}
void ApiWrap::saveDraftsToCloud() {
for (auto i = _draftsSaveRequestIds.begin(), e = _draftsSaveRequestIds.end(); i != e; ++i) {
if (i->second) continue; // sent already
for (auto i = begin(_draftsSaveRequestIds); i != end(_draftsSaveRequestIds);) {
const auto weak = i->first;
const auto thread = weak.get();
if (!thread) {
i = _draftsSaveRequestIds.erase(i);
continue;
}
auto &requestId = i->second;
++i;
if (requestId) {
continue; // sent already
}
auto history = i->first;
auto cloudDraft = history->cloudDraft();
auto localDraft = history->localDraft();
const auto history = thread->owningHistory();
const auto topicRootId = thread->topicRootId();
auto cloudDraft = history->cloudDraft(topicRootId);
auto localDraft = history->localDraft(topicRootId);
if (cloudDraft && cloudDraft->saveRequestId) {
request(base::take(cloudDraft->saveRequestId)).cancel();
}
if (!_session->supportMode()) {
cloudDraft = history->createCloudDraft(localDraft);
cloudDraft = history->createCloudDraft(topicRootId, localDraft);
} else if (!cloudDraft) {
cloudDraft = history->createCloudDraft(nullptr);
cloudDraft = history->createCloudDraft(topicRootId, nullptr);
}
auto flags = MTPmessages_SaveDraft::Flags(0);
@ -2040,6 +2053,9 @@ void ApiWrap::saveDraftsToCloud() {
}
if (cloudDraft->msgId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id;
if (cloudDraft->topicRootId) {
flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id;
}
}
if (!textWithTags.tags.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_entities;
@ -2049,44 +2065,45 @@ void ApiWrap::saveDraftsToCloud() {
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags),
Api::ConvertOption::SkipLocal);
history->startSavingCloudDraft();
history->startSavingCloudDraft(topicRootId);
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
MTP_flags(flags),
MTP_int(cloudDraft->msgId),
MTP_int(cloudDraft->topicRootId),
history->peer->input,
MTP_string(textWithTags.text),
entities
)).done([=](const MTPBool &result, const MTP::Response &response) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
const auto requestId = response.requestId;
if (const auto cloudDraft = history->cloudDraft()) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
if (cloudDraft->saveRequestId == requestId) {
cloudDraft->saveRequestId = 0;
history->draftSavedToCloud();
history->draftSavedToCloud(topicRootId);
}
}
auto i = _draftsSaveRequestIds.find(history);
const auto i = _draftsSaveRequestIds.find(weak);
if (i != _draftsSaveRequestIds.cend()
&& i->second == requestId) {
_draftsSaveRequestIds.erase(history);
_draftsSaveRequestIds.erase(i);
checkQuitPreventFinished();
}
}).fail([=](const MTP::Error &error, const MTP::Response &response) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
const auto requestId = response.requestId;
if (const auto cloudDraft = history->cloudDraft()) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
if (cloudDraft->saveRequestId == requestId) {
history->clearCloudDraft();
history->clearCloudDraft(topicRootId);
}
}
auto i = _draftsSaveRequestIds.find(history);
const auto i = _draftsSaveRequestIds.find(weak);
if (i != _draftsSaveRequestIds.cend()
&& i->second == requestId) {
_draftsSaveRequestIds.erase(history);
_draftsSaveRequestIds.erase(i);
checkQuitPreventFinished();
}
}).send();
@ -3420,6 +3437,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id;
if (action.topicRootId) {
sendFlags |= MTPmessages_SendMessage::Flag::f_top_msg_id;
}
}
const auto replyHeader = NewMessageReplyHeader(action);
MTPMessageMedia media = MTP_messageMediaEmpty();
@ -3446,10 +3466,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
}
const auto clearCloudDraft = action.clearDraft;
const auto topicRootId = action.topicRootId;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
history->clearCloudDraft();
history->startSavingCloudDraft();
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
}
const auto sendAs = action.options.sendAs;
const auto messageFromId = sendAs
@ -3468,12 +3489,11 @@ 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,
replyTo,
action.replyTo,
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
@ -3482,12 +3502,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
HistoryMessageMarkupData());
histories.sendPreparedMessage(
history,
replyTo,
action.replyTo,
topicRootId,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
@ -3497,6 +3519,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
}, [=](const MTP::Error &error, const MTP::Response &response) {
@ -3507,6 +3530,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
}
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
});
@ -3573,12 +3597,16 @@ void ApiWrap::sendInlineResult(
? (*localMessageId)
: _session->data().nextLocalMessageId());
const auto randomId = base::RandomValue<uint64>();
const auto topicRootId = action.replyTo ? action.topicRootId : 0;
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft | 0;
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id;
if (topicRootId) {
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_top_msg_id;
}
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
@ -3618,19 +3646,20 @@ void ApiWrap::sendInlineResult(
action.replyTo,
messagePostAuthor);
history->clearCloudDraft();
history->startSavingCloudDraft();
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
auto &histories = history->owner().histories();
const auto replyTo = action.replyTo;
histories.sendPreparedMessage(
history,
replyTo,
action.replyTo,
topicRootId,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendInlineBotResult>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
MTP_long(randomId),
MTP_long(data->getQueryId()),
MTP_string(data->getId()),
@ -3638,10 +3667,12 @@ void ApiWrap::sendInlineResult(
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), [=](const MTPUpdates &result, const MTP::Response &response) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}, [=](const MTP::Error &error, const MTP::Response &response) {
sendMessageFail(error, peer, randomId, newId);
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
});
finishForwarding(action);
@ -3740,6 +3771,7 @@ void ApiWrap::sendMediaWithRandomId(
uint64 randomId) {
const auto history = item->history();
const auto replyTo = item->replyToId();
const auto topicRootId = item->topicRootId();
auto caption = item->originalText();
TextUtilities::Trim(caption);
@ -3750,22 +3782,16 @@ void ApiWrap::sendMediaWithRandomId(
const auto updateRecentStickers = Api::HasAttachedStickers(media);
const auto flags = MTPmessages_SendMedia::Flags(0)
| (replyTo
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMedia::Flag(0))
using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0)
| (replyTo ? Flag::f_reply_to_msg_id : Flag(0))
| (topicRootId ? Flag::f_top_msg_id : Flag(0))
| (ShouldSendSilent(history->peer, options)
? MTPmessages_SendMedia::Flag::f_silent
: MTPmessages_SendMedia::Flag(0))
| (!sentEntities.v.isEmpty()
? MTPmessages_SendMedia::Flag::f_entities
: MTPmessages_SendMedia::Flag(0))
| (options.scheduled
? MTPmessages_SendMedia::Flag::f_schedule_date
: MTPmessages_SendMedia::Flag(0))
| (options.sendAs
? MTPmessages_SendMedia::Flag::f_send_as
: MTPmessages_SendMedia::Flag(0));
? Flag::f_silent
: Flag(0))
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
| (options.sendAs ? Flag::f_send_as : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
@ -3773,11 +3799,13 @@ void ApiWrap::sendMediaWithRandomId(
histories.sendPreparedMessage(
history,
replyTo,
topicRootId,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(flags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
media,
MTP_string(caption.text),
MTP_long(randomId),
@ -3859,30 +3887,29 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
}
const auto history = sample->history();
const auto replyTo = sample->replyToId();
const auto topicRootId = sample->topicRootId();
const auto sendAs = album->options.sendAs;
const auto flags = MTPmessages_SendMultiMedia::Flags(0)
| (replyTo
? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMultiMedia::Flag(0))
using Flag = MTPmessages_SendMultiMedia::Flag;
const auto flags = Flag(0)
| (replyTo ? Flag::f_reply_to_msg_id : Flag(0))
| (topicRootId ? Flag::f_top_msg_id : Flag(0))
| (ShouldSendSilent(history->peer, album->options)
? MTPmessages_SendMultiMedia::Flag::f_silent
: MTPmessages_SendMultiMedia::Flag(0))
| (album->options.scheduled
? MTPmessages_SendMultiMedia::Flag::f_schedule_date
: MTPmessages_SendMultiMedia::Flag(0))
| (sendAs
? MTPmessages_SendMultiMedia::Flag::f_send_as
: MTPmessages_SendMultiMedia::Flag(0));
? Flag::f_silent
: Flag(0))
| (album->options.scheduled ? Flag::f_schedule_date : Flag(0))
| (sendAs ? Flag::f_send_as : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
histories.sendPreparedMessage(
history,
replyTo,
topicRootId,
uint64(0), // randomId
Data::Histories::PrepareMessage<MTPmessages_SendMultiMedia>(
MTP_flags(flags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
MTP_vector<MTPInputSingleMedia>(medias),
MTP_int(album->options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
@ -3905,6 +3932,7 @@ FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
peer->id,
action.options,
action.replyTo,
action.topicRootId,
action.replaceMediaOf);
}

View file

@ -245,7 +245,7 @@ public:
void updateNotifySettingsDelayed(not_null<const Data::Thread*> thread);
void updateNotifySettingsDelayed(not_null<const PeerData*> peer);
void updateNotifySettingsDelayed(Data::DefaultNotify type);
void saveDraftToCloudDelayed(not_null<History*> history);
void saveDraftToCloudDelayed(not_null<Data::Thread*> thread);
static int OnlineTillFromStatus(
const MTPUserStatus &status,
@ -563,7 +563,9 @@ private:
};
base::flat_map<NotifySettingsKey, mtpRequestId> _notifySettingRequests;
base::flat_map<not_null<History*>, mtpRequestId> _draftsSaveRequestIds;
base::flat_map<
base::weak_ptr<Data::Thread>,
mtpRequestId> _draftsSaveRequestIds;
base::Timer _draftsSaveTimer;
base::flat_set<mtpRequestId> _stickerSetDisenableRequests;

View file

@ -65,14 +65,17 @@ void ShareBotGame(
auto &histories = history->owner().histories();
const auto randomId = base::RandomValue<uint64>();
const auto replyTo = 0;
const auto topicRootId = 0;
histories.sendPreparedMessage(
history,
replyTo,
topicRootId,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(0),
chat->input,
Data::Histories::ReplyToPlaceholder(),
Data::Histories::TopicRootPlaceholder(),
MTP_inputMediaGame(
MTP_inputGameShortName(
bot->inputUser,

View file

@ -127,7 +127,7 @@ void DicePack::generateLocal(int index, const QString &name) {
QByteArray(),
nullptr,
SendMediaType::File,
FileLoadTo(0, {}, 0, 0),
FileLoadTo(0, {}, 0, 0, 0),
{});
task.process({ .generateGoodThumbnail = false });
const auto result = task.peekResult();

View file

@ -151,8 +151,9 @@ struct TopicUpdate {
Notifications = (1U << 4),
Title = (1U << 5),
Icon = (1U << 6),
CloudDraft = (1U << 7),
LastUsedBit = (1U << 4),
LastUsedBit = (1U << 7),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -19,29 +19,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
DraftKey DraftKey::FromSerializedOld(int32 value) {
return !value
? DraftKey::None()
: (value == kLocalDraftIndex + kEditDraftShiftOld)
? DraftKey::LocalEdit()
: (value == kScheduledDraftIndex + kEditDraftShiftOld)
? DraftKey::ScheduledEdit()
: (value > 0 && value < 0x4000'0000)
? DraftKey::Replies(int64(value))
: (value > kEditDraftShiftOld
&& value < kEditDraftShiftOld + 0x4000'000)
? DraftKey::RepliesEdit(int64(value - kEditDraftShiftOld))
: DraftKey::None();
}
Draft::Draft(
const TextWithTags &textWithTags,
MsgId msgId,
MsgId topicRootId,
const MessageCursor &cursor,
PreviewState previewState,
mtpRequestId saveRequestId)
: textWithTags(textWithTags)
, msgId(msgId)
, topicRootId(topicRootId)
, cursor(cursor)
, previewState(previewState)
, saveRequestId(saveRequestId) {
@ -50,10 +37,12 @@ Draft::Draft(
Draft::Draft(
not_null<const Ui::InputField*> field,
MsgId msgId,
MsgId topicRootId,
PreviewState previewState,
mtpRequestId saveRequestId)
: textWithTags(field->getTextWithTags())
, msgId(msgId)
, topicRootId(topicRootId)
, cursor(field)
, previewState(previewState) {
}
@ -61,10 +50,11 @@ Draft::Draft(
void ApplyPeerCloudDraft(
not_null<Main::Session*> session,
PeerId peerId,
MsgId topicRootId,
const MTPDdraftMessage &draft) {
const auto history = session->data().history(peerId);
const auto date = draft.vdate().v;
if (history->skipCloudDraftUpdate(date)) {
if (history->skipCloudDraftUpdate(topicRootId, date)) {
return;
}
const auto textWithTags = TextWithTags{
@ -78,6 +68,7 @@ void ApplyPeerCloudDraft(
auto cloudDraft = std::make_unique<Draft>(
textWithTags,
replyTo,
topicRootId,
MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX),
(draft.is_no_webpage()
? Data::PreviewState::Cancelled
@ -85,20 +76,21 @@ void ApplyPeerCloudDraft(
cloudDraft->date = date;
history->setCloudDraft(std::move(cloudDraft));
history->applyCloudDraft();
history->applyCloudDraft(topicRootId);
}
void ClearPeerCloudDraft(
not_null<Main::Session*> session,
PeerId peerId,
MsgId topicRootId,
TimeId date) {
const auto history = session->data().history(peerId);
if (history->skipCloudDraftUpdate(date)) {
if (history->skipCloudDraftUpdate(topicRootId, date)) {
return;
}
history->clearCloudDraft();
history->applyCloudDraft();
history->clearCloudDraft(topicRootId);
history->applyCloudDraft(topicRootId);
}
} // namespace Data

View file

@ -20,10 +20,12 @@ namespace Data {
void ApplyPeerCloudDraft(
not_null<Main::Session*> session,
PeerId peerId,
MsgId topicRootId,
const MTPDdraftMessage &draft);
void ClearPeerCloudDraft(
not_null<Main::Session*> session,
PeerId peerId,
MsgId topicRootId,
TimeId date);
enum class PreviewState : char {
@ -37,18 +39,21 @@ struct Draft {
Draft(
const TextWithTags &textWithTags,
MsgId msgId,
MsgId topicRootId,
const MessageCursor &cursor,
PreviewState previewState,
mtpRequestId saveRequestId = 0);
Draft(
not_null<const Ui::InputField*> field,
MsgId msgId,
MsgId topicRootId,
PreviewState previewState,
mtpRequestId saveRequestId = 0);
TimeId date = 0;
TextWithTags textWithTags;
MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft
MsgId topicRootId = 0;
MessageCursor cursor;
PreviewState previewState = PreviewState::Allowed;
mtpRequestId saveRequestId = 0;
@ -56,70 +61,93 @@ struct Draft {
class DraftKey {
public:
[[nodiscard]] static DraftKey None() {
[[nodiscard]] static constexpr DraftKey None() {
return 0;
}
[[nodiscard]] static DraftKey Local() {
return kLocalDraftIndex;
[[nodiscard]] static constexpr DraftKey Local(MsgId topicRootId) {
return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
? None()
: (topicRootId ? topicRootId.bare : kLocalDraftIndex);
}
[[nodiscard]] static DraftKey LocalEdit() {
return kLocalDraftIndex + kEditDraftShift;
[[nodiscard]] static constexpr DraftKey LocalEdit(MsgId topicRootId) {
return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
? None()
: ((topicRootId ? topicRootId.bare : kLocalDraftIndex)
+ kEditDraftShift);
}
[[nodiscard]] static DraftKey Cloud() {
return kCloudDraftIndex;
[[nodiscard]] static constexpr DraftKey Cloud(MsgId topicRootId) {
return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
? None()
: topicRootId
? (kCloudDraftShift + topicRootId.bare)
: kCloudDraftIndex;
}
[[nodiscard]] static DraftKey Scheduled() {
[[nodiscard]] static constexpr DraftKey Scheduled() {
return kScheduledDraftIndex;
}
[[nodiscard]] static DraftKey ScheduledEdit() {
[[nodiscard]] static constexpr DraftKey ScheduledEdit() {
return kScheduledDraftIndex + kEditDraftShift;
}
[[nodiscard]] static DraftKey Replies(MsgId rootId) {
return rootId.bare;
}
[[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) {
return rootId.bare + kEditDraftShift;
}
[[nodiscard]] static DraftKey FromSerialized(qint64 value) {
[[nodiscard]] static constexpr DraftKey FromSerialized(qint64 value) {
return value;
}
[[nodiscard]] qint64 serialize() const {
[[nodiscard]] constexpr qint64 serialize() const {
return _value;
}
[[nodiscard]] static DraftKey FromSerializedOld(int32 value);
[[nodiscard]] static constexpr DraftKey FromSerializedOld(int32 value) {
return !value
? None()
: (value == kLocalDraftIndex + kEditDraftShiftOld)
? LocalEdit(0)
: (value == kScheduledDraftIndex + kEditDraftShiftOld)
? ScheduledEdit()
: (value > 0 && value < 0x4000'0000)
? Local(MsgId(value))
: (value > kEditDraftShiftOld
&& value < kEditDraftShiftOld + 0x4000'000)
? LocalEdit(int64(value - kEditDraftShiftOld))
: None();
}
[[nodiscard]] constexpr bool isLocal() const {
return (_value == kLocalDraftIndex)
|| (_value > 0 && _value < ServerMaxMsgId.bare);
}
[[nodiscard]] constexpr bool isCloud() const {
return (_value == kCloudDraftIndex)
|| (_value > kCloudDraftShift
&& _value < kCloudDraftShift + ServerMaxMsgId.bare);
}
inline bool operator<(const DraftKey &other) const {
return _value < other._value;
}
inline bool operator==(const DraftKey &other) const {
return _value == other._value;
}
inline bool operator>(const DraftKey &other) const {
return (other < *this);
}
inline bool operator<=(const DraftKey &other) const {
return !(other < *this);
}
inline bool operator>=(const DraftKey &other) const {
return !(*this < other);
}
inline bool operator!=(const DraftKey &other) const {
return !(*this == other);
[[nodiscard]] constexpr MsgId topicRootId() const {
const auto max = ServerMaxMsgId.bare;
if (_value > kCloudDraftShift && _value < kCloudDraftShift + max) {
return (_value - kCloudDraftShift);
} else if (_value > kEditDraftShift && _value < kEditDraftShift + max) {
return (_value - kEditDraftShift);
} else if (_value > 0 && _value < max) {
return _value;
}
return 0;
}
friend inline constexpr auto operator<=>(DraftKey, DraftKey) = default;
inline explicit operator bool() const {
return _value != 0;
}
private:
DraftKey(int64 value) : _value(value) {
constexpr DraftKey(int64 value) : _value(value) {
}
static constexpr auto kLocalDraftIndex = -1;
static constexpr auto kCloudDraftIndex = -2;
static constexpr auto kScheduledDraftIndex = -3;
static constexpr auto kEditDraftShift = ServerMaxMsgId.bare;
static constexpr auto kCloudDraftShift = 2 * ServerMaxMsgId.bare;
static constexpr auto kEditDraftShiftOld = 0x3FFF'FFFF;
int64 _value = 0;
@ -128,7 +156,7 @@ private:
using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
inline bool draftStringIsEmpty(const QString &text) {
[[nodiscard]] inline bool DraftStringIsEmpty(const QString &text) {
for (const auto &ch : text) {
if (!ch.isSpace()) {
return false;
@ -137,20 +165,19 @@ inline bool draftStringIsEmpty(const QString &text) {
return true;
}
inline bool draftIsNull(const Draft *draft) {
[[nodiscard]] inline bool DraftIsNull(const Draft *draft) {
return !draft
|| (draftStringIsEmpty(draft->textWithTags.text) && !draft->msgId);
|| (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text));
}
inline bool draftsAreEqual(const Draft *a, const Draft *b) {
bool aIsNull = draftIsNull(a);
bool bIsNull = draftIsNull(b);
[[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) {
const auto aIsNull = DraftIsNull(a);
const auto bIsNull = DraftIsNull(b);
if (aIsNull) {
return bIsNull;
} else if (bIsNull) {
return false;
}
return (a->textWithTags == b->textWithTags)
&& (a->msgId == b->msgId)
&& (a->previewState == b->previewState);

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum.h"
#include "data/data_histories.h"
#include "data/data_replies_list.h"
#include "data/data_send_action.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
@ -142,10 +143,15 @@ ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
, _forum(forum)
, _list(_forum->topicsList())
, _replies(std::make_shared<RepliesList>(history(), rootId))
, _sendActionPainter(owner().sendActionManager().repliesPainter(
history(),
rootId))
, _rootId(rootId)
, _lastKnownServerMessageId(rootId) {
Thread::setMuted(owner().notifySettings().isMuted(this));
_sendActionPainter->setTopic(this);
_replies->unreadCountValue(
) | rpl::combine_previous(
) | rpl::filter([=] {
@ -161,6 +167,7 @@ ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
}
ForumTopic::~ForumTopic() {
_sendActionPainter->setTopic(nullptr);
session().api().unreadThings().cancelRequests(this);
}
@ -191,11 +198,24 @@ MsgId ForumTopic::rootId() const {
return _rootId;
}
bool ForumTopic::creating() const {
return _forum->creating(_rootId);
}
void ForumTopic::discard() {
Expects(creating());
_forum->discardCreatingId(_rootId);
}
void ForumTopic::setRealRootId(MsgId realId) {
if (_rootId != realId) {
_rootId = realId;
_lastKnownServerMessageId = realId;
_replies = std::make_shared<RepliesList>(history(), _rootId);
_sendActionPainter = owner().sendActionManager().repliesPainter(
history(),
_rootId);
}
}
@ -221,6 +241,15 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) {
owner().notifySettings().apply(this, data.vnotify_settings());
const auto draft = data.vdraft();
if (draft && draft->type() == mtpc_draftMessage) {
Data::ApplyPeerCloudDraft(
&session(),
channel()->id,
_rootId,
draft->c_draftMessage());
}
_replies->setInboxReadTill(
data.vread_inbox_max_id().v,
data.vunread_count().v);
@ -390,13 +419,11 @@ void ForumTopic::requestChatListMessage() {
TimeId ForumTopic::adjustedChatListTimeId() const {
const auto result = chatListTimeId();
#if 0 // #TODO forum draft
if (const auto draft = cloudDraft()) {
if (!Data::draftIsNull(draft) && !session().supportMode()) {
if (const auto draft = history()->cloudDraft(_rootId)) {
if (!Data::DraftIsNull(draft) && !session().supportMode()) {
return std::max(result, draft->date);
}
}
#endif
return result;
}
@ -480,6 +507,17 @@ void ForumTopic::applyItemAdded(not_null<HistoryItem*> item) {
setLastMessage(item);
}
void ForumTopic::maybeSetLastMessage(not_null<HistoryItem*> item) {
Expects(item->topicRootId() == _rootId);
if (!_lastMessage
|| ((*_lastMessage)->date() < item->date())
|| ((*_lastMessage)->date() == item->date()
&& (*_lastMessage)->id < item->id)) {
setLastMessage(item);
}
}
void ForumTopic::applyItemRemoved(MsgId id) {
if (const auto lastItem = lastMessage()) {
if (lastItem->id == id) {
@ -499,6 +537,11 @@ void ForumTopic::applyItemRemoved(MsgId id) {
}
}
bool ForumTopic::isServerSideUnread(
not_null<const HistoryItem*> item) const {
return _replies->isServerSideUnread(item);
}
int ForumTopic::unreadCount() const {
return _replies->unreadCountCurrent();
}
@ -544,6 +587,10 @@ void ForumTopic::setUnreadMark(bool unread) {
Thread::setUnreadMark(unread);
}
not_null<HistoryView::SendActionPainter*> ForumTopic::sendActionPainter() {
return _sendActionPainter.get();
}
int ForumTopic::chatListUnreadCount() const {
const auto state = chatListUnreadState();
return state.marks

View file

@ -25,6 +25,10 @@ namespace Main {
class Session;
} // namespace Main
namespace HistoryView {
class SendActionPainter;
} // namespace HistoryView
namespace Data {
class RepliesList;
@ -57,6 +61,9 @@ public:
[[nodiscard]] rpl::producer<> destroyed() const;
[[nodiscard]] MsgId rootId() const;
[[nodiscard]] bool creating() const;
void discard();
void setRealRootId(MsgId realId);
void applyTopic(const MTPDforumTopic &data);
@ -91,6 +98,7 @@ public:
void applyColorId(int32 colorId);
void applyItemAdded(not_null<HistoryItem*> item);
void applyItemRemoved(MsgId id);
void maybeSetLastMessage(not_null<HistoryItem*> item);
[[nodiscard]] PeerNotifySettings &notify() {
return _notify;
@ -105,6 +113,9 @@ public:
std::shared_ptr<CloudImageView> &view,
const Dialogs::Ui::PaintContext &context) const override;
[[nodiscard]] bool isServerSideUnread(
not_null<const HistoryItem*> item) const override;
[[nodiscard]] int unreadCount() const;
[[nodiscard]] bool unreadCountKnown() const;
@ -113,6 +124,9 @@ public:
void setMuted(bool muted) override;
void setUnreadMark(bool unread) override;
[[nodiscard]] auto sendActionPainter()
->not_null<HistoryView::SendActionPainter*> override;
private:
void indexTitleParts();
void validateDefaultIcon() const;
@ -132,6 +146,7 @@ private:
const not_null<Forum*> _forum;
const not_null<Dialogs::MainList*> _list;
std::shared_ptr<RepliesList> _replies;
std::shared_ptr<HistoryView::SendActionPainter> _sendActionPainter;
MsgId _rootId = 0;
MsgId _lastKnownServerMessageId = 0;

View file

@ -247,7 +247,7 @@ void Histories::readInboxOnNewMessage(not_null<HistoryItem*> item) {
}
void Histories::readClientSideMessage(not_null<HistoryItem*> item) {
if (item->out() || !item->unread()) {
if (item->out() || !item->unread(item->history())) {
return;
}
const auto history = item->history();
@ -886,20 +886,22 @@ bool Histories::isCreatingTopic(
int Histories::sendPreparedMessage(
not_null<History*> history,
MsgId replyTo,
MsgId topicRootId,
uint64 randomId,
Fn<PreparedMessage(MsgId replyTo)> message,
Fn<PreparedMessage(MsgId replyTo, MsgId topicRootId)> message,
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
Fn<void(const MTP::Error&, const MTP::Response&)> fail) {
if (isCreatingTopic(history, replyTo)) {
if (isCreatingTopic(history, topicRootId)) {
const auto id = ++_requestAutoincrement;
const auto creatingId = FullMsgId(history->peer->id, replyTo);
const auto creatingId = FullMsgId(history->peer->id, topicRootId);
auto i = _creatingTopics.find(creatingId);
if (i == end(_creatingTopics)) {
sendCreateTopicRequest(history, replyTo);
sendCreateTopicRequest(history, topicRootId);
i = _creatingTopics.emplace(creatingId).first;
}
i->second.push_back({
.randomId = randomId,
.replyTo = replyTo,
.message = std::move(message),
.done = std::move(done),
.fail = std::move(fail),
@ -908,8 +910,9 @@ int Histories::sendPreparedMessage(
_creatingTopicRequests.emplace(id);
return id;
}
const auto realTo = convertTopicReplyTo(history, replyTo);
return v::match(message(realTo), [&](const auto &request) {
const auto realReply = convertTopicReplyTo(history, replyTo);
const auto realRoot = convertTopicReplyTo(history, topicRootId);
return v::match(message(realReply, realRoot), [&](const auto &request) {
const auto type = RequestType::Send;
return sendRequest(history, type, [=](Fn<void()> finish) {
const auto session = &_owner->session();
@ -935,38 +938,38 @@ int Histories::sendPreparedMessage(
});
}
void Histories::checkTopicCreated(FullMsgId rootId, MsgId realId) {
void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
const auto i = _creatingTopics.find(rootId);
if (i != end(_creatingTopics)) {
auto scheduled = base::take(i->second);
_creatingTopics.erase(i);
_createdTopicIds.emplace(rootId, realId);
_createdTopicIds.emplace(rootId, realRoot);
if (const auto forum = _owner->peer(rootId.peer)->forum()) {
forum->created(rootId.msg, realId);
forum->created(rootId.msg, realRoot);
}
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));
sendPreparedMessage(
history,
entry.replyTo,
realRoot,
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;
return (nowId == rootId.msg) ? realRoot : nowId;
};
if (item->replyToTop() == rootId.msg) {
if (item->topicRootId() == rootId.msg) {
item->setReplyFields(
replace(item->replyToId()),
realId,
realRoot,
true);
}
}

View file

@ -103,22 +103,25 @@ public:
int sendPreparedMessage(
not_null<History*> history,
MsgId replyTo,
MsgId topicRootId,
uint64 randomId,
Fn<PreparedMessage(MsgId replyTo)> message,
Fn<PreparedMessage(MsgId replyTo, MsgId topicRootId)> message,
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
Fn<void(const MTP::Error&, const MTP::Response&)> fail);
struct ReplyToPlaceholder {
};
struct TopicRootPlaceholder {
};
template <typename RequestType, typename ...Args>
static Fn<Histories::PreparedMessage(MsgId)> PrepareMessage(
static Fn<Histories::PreparedMessage(MsgId, MsgId)> PrepareMessage(
const Args &...args) {
return [=](MsgId replyTo) {
return RequestType(ReplaceReplyTo(args, replyTo)...);
return [=](MsgId replyTo, MsgId topicRootId) -> RequestType {
return { ReplaceReplyIds(args, replyTo, topicRootId)... };
};
}
void checkTopicCreated(FullMsgId rootId, MsgId realId);
void checkTopicCreated(FullMsgId rootId, MsgId realRoot);
[[nodiscard]] MsgId convertTopicReplyTo(
not_null<History*> history,
MsgId replyTo) const;
@ -147,7 +150,8 @@ private:
};
struct DelayedByTopicMessage {
uint64 randomId = 0;
Fn<PreparedMessage(MsgId replyTo)> message;
MsgId replyTo = 0;
Fn<PreparedMessage(MsgId replyTo, MsgId topicRootId)> message;
Fn<void(const MTPUpdates&, const MTP::Response&)> done;
Fn<void(const MTP::Error&, const MTP::Response&)> fail;
int requestId = 0;
@ -162,9 +166,11 @@ private:
};
template <typename Arg>
static auto ReplaceReplyTo(Arg arg, MsgId replyTo) {
static auto ReplaceReplyIds(Arg arg, MsgId replyTo, MsgId topicRootId) {
if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) {
return MTP_int(replyTo);
} else if constexpr (std::is_same_v<Arg, TopicRootPlaceholder>) {
return MTP_int(topicRootId);
} else {
return arg;
}

View file

@ -765,6 +765,14 @@ void RepliesList::setUnreadCount(std::optional<int> count) {
}
}
bool RepliesList::isServerSideUnread(
not_null<const HistoryItem*> item) const {
const auto till = item->out()
? computeOutboxReadTillFull()
: computeInboxReadTillFull();
return (item->id > till);
}
void RepliesList::checkReadTillEnd() {
if (_unreadCount.current() != 0
&& _skippedAfter == 0

View file

@ -43,6 +43,9 @@ public:
void setOutboxReadTill(MsgId readTillId);
[[nodiscard]] MsgId computeOutboxReadTillFull() const;
[[nodiscard]] bool isServerSideUnread(
not_null<const HistoryItem*> item) const;
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
MsgId afterId) const;
void requestUnreadCount();

View file

@ -75,7 +75,7 @@ auto SendActionManager::repliesPainter(
if (auto strong = weak.lock()) {
return strong;
}
auto result = std::make_shared<SendActionPainter>(history);
auto result = std::make_shared<SendActionPainter>(history, rootId);
weak = result;
return result;
}

View file

@ -17,10 +17,12 @@ class SendActionPainter;
namespace Data {
class Thread;
class SendActionManager final {
public:
struct AnimationUpdate {
not_null<History*> history;
not_null<Thread*> thread;
int left = 0;
int width = 0;
int height = 0;

View file

@ -3820,7 +3820,7 @@ void Session::refreshChatListEntry(Dialogs::Key key) {
const auto mainList = chatsListFor(entry);
auto event = ChatListEntryRefresh{ .key = key };
const auto creating = event.existenceChanged = !entry->inChatList();
if (creating && topic && topic->forum()->creating(topic->rootId())) {
if (creating && topic && topic->creating()) {
return;
} else if (event.existenceChanged) {
const auto mainRow = entry->addToChatList(0, mainList);

View file

@ -17,6 +17,13 @@ namespace Data {
Thread::~Thread() = default;
MsgId Thread::topicRootId() const {
if (const auto topic = asTopic()) {
return topic->rootId();
}
return MsgId();
}
not_null<PeerData*> Thread::peer() const {
return owningHistory()->peer;
}

View file

@ -24,6 +24,10 @@ class Proxy;
class ConstProxy;
} // namespace HistoryUnreadThings
namespace HistoryView {
class SendActionPainter;
} // namespace HistoryView
namespace st {
extern const int &dialogsTextWidthMin;
} // namespace st
@ -57,6 +61,7 @@ public:
[[nodiscard]] not_null<const History*> owningHistory() const {
return const_cast<Thread*>(this)->owningHistory();
}
[[nodiscard]] MsgId topicRootId() const;
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] PeerNotifySettings &notify();
[[nodiscard]] const PeerNotifySettings &notify() const;
@ -87,6 +92,9 @@ public:
}
virtual void setUnreadMark(bool unread);
[[nodiscard]] virtual bool isServerSideUnread(
not_null<const HistoryItem*> item) const = 0;
[[nodiscard]] const base::flat_set<MsgId> &unreadMentionsIds() const;
[[nodiscard]] const base::flat_set<MsgId> &unreadReactionsIds() const;
@ -97,6 +105,9 @@ public:
return _lastItemDialogsView;
}
[[nodiscard]] virtual auto sendActionPainter()
-> not_null<HistoryView::SendActionPainter*> = 0;
private:
enum class Flag : uchar {
UnreadMark = (1 << 0),

View file

@ -185,7 +185,7 @@ InnerWidget::InnerWidget(
width(),
update.textUpdated);
updateDialogRow(
RowDescriptor(update.history, FullMsgId()),
RowDescriptor(update.thread, FullMsgId()),
updateRect,
UpdateRowSection::Default | UpdateRowSection::Filtered);
}, lifetime());
@ -2394,8 +2394,10 @@ void InnerWidget::refreshEmptyLabel() {
const auto data = &session().data();
const auto state = !shownDialogs()->empty()
? EmptyState::None
: (_openedForum && _openedForum->topicsList()->loaded())
? EmptyState::EmptyForum
: _openedForum
? (_openedForum->topicsList()->loaded()
? EmptyState::EmptyForum
: EmptyState::Loading)
: (!_filterId && data->contactsLoaded().current())
? EmptyState::NoContacts
: (_filterId > 0) && data->chatsList()->loaded()

View file

@ -19,12 +19,18 @@ Key::Key(History *history) : _value(history) {
Key::Key(Data::Folder *folder) : _value(folder) {
}
Key::Key(Data::Thread *thread) : _value(thread) {
}
Key::Key(Data::ForumTopic *topic) : _value(topic) {
}
Key::Key(not_null<History*> history) : _value(history) {
}
Key::Key(not_null<Data::Thread*> thread) : _value(thread) {
}
Key::Key(not_null<Data::Folder*> folder) : _value(folder) {
}

View file

@ -27,10 +27,12 @@ public:
}
Key(History *history);
Key(Data::Folder *folder);
Key(Data::Thread *thread);
Key(Data::ForumTopic *topic);
Key(not_null<Entry*> entry) : _value(entry) {
}
Key(not_null<History*> history);
Key(not_null<Data::Thread*> thread);
Key(not_null<Data::Folder*> folder);
Key(not_null<Data::ForumTopic*> topic);

View file

@ -48,7 +48,8 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_";
return user->isBot() && !user->isSupport() && !user->isRepliesChat();
}
[[nodiscard]] bool ShowSendActionInDialogs(History *history) {
[[nodiscard]] bool ShowSendActionInDialogs(Data::Thread *thread) {
const auto history = thread ? thread->owningHistory().get() : nullptr;
return history
&& (!history->peer->isUser()
|| history->peer->asUser()->onlineTill > 0);
@ -465,8 +466,8 @@ void PaintRow(
: context.selected
? st::dialogsTextFgServiceOver
: st::dialogsTextFgService;
if (!ShowSendActionInDialogs(history)
|| !history->sendActionPainter()->paint(
if (!ShowSendActionInDialogs(thread)
|| !thread->sendActionPainter()->paint(
p,
nameleft,
texttop,
@ -474,7 +475,8 @@ void PaintRow(
context.width,
color,
context.paused)) {
if (history->cloudDraftTextCache().isEmpty()) {
auto &cache = thread->cloudDraftTextCache();
if (cache.isEmpty()) {
using namespace TextUtilities;
auto draftWrapped = Text::PlainLink(
tr::lng_dialogs_text_from_wrapped(
@ -496,10 +498,10 @@ void PaintRow(
}),
Text::WithEntities);
const auto context = Core::MarkedTextContext{
.session = &history->session(),
.session = &thread->session(),
.customEmojiRepaint = customEmojiRepaint,
};
history->cloudDraftTextCache().setMarkedText(
cache.setMarkedText(
st::dialogsTextStyle,
draftText,
DialogTextOptions(),
@ -510,7 +512,7 @@ void PaintRow(
: context.selected
? st::dialogsTextFgOver
: st::dialogsTextFg);
history->cloudDraftTextCache().draw(p, {
cache.draw(p, {
.position = { nameleft, texttop },
.availableWidth = availableWidth,
.palette = &(supportMode
@ -549,8 +551,8 @@ void PaintRow(
? st::dialogsTextFgServiceOver
: st::dialogsTextFgService;
p.setFont(st::dialogsTextFont);
if (!ShowSendActionInDialogs(history)
|| !history->sendActionPainter()->paint(
if (!ShowSendActionInDialogs(thread)
|| !thread->sendActionPainter()->paint(
p,
nameleft,
texttop,
@ -575,8 +577,10 @@ void PaintRow(
: st::dialogsPinnedIcon;
icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width);
}
auto sendStateIcon = [&]() -> const style::icon* {
if (draft) {
const auto sendStateIcon = [&]() -> const style::icon* {
if (!thread) {
return nullptr;
} else if (draft) {
if (draft->saveRequestId) {
return &(context.active
? st::dialogsSendingIconActive
@ -586,7 +590,7 @@ void PaintRow(
}
} else if (item && !item->isEmpty() && item->needCheck()) {
if (!item->isSending() && !item->hasFailed()) {
if (item->unread()) {
if (item->unread(thread)) {
return &(context.active
? st::dialogsSentIconActive
: context.selected
@ -607,7 +611,7 @@ void PaintRow(
}
return nullptr;
}();
if (sendStateIcon && history) {
if (sendStateIcon) {
rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip);
sendStateIcon->paint(p, rectForName.topLeft() + QPoint(rectForName.width(), 0), context.width);
}
@ -892,12 +896,12 @@ void RowPainter::Paint(
const auto unreadMuted = entry->chatListMutedBadge();
const auto item = entry->chatListMessage();
const auto cloudDraft = [&]() -> const Data::Draft*{
if (history && (!item || (!unreadCount && !unreadMark))) {
if (thread && (!item || (!unreadCount && !unreadMark))) {
// Draw item, if there are unread messages.
if (const auto draft = history->cloudDraft()) {
if (!Data::draftIsNull(draft)) {
return draft;
}
const auto draft = thread->owningHistory()->cloudDraft(
thread->topicRootId());
if (!Data::DraftIsNull(draft)) {
return draft;
}
}
return nullptr;
@ -977,8 +981,8 @@ void RowPainter::Paint(
texttop,
availableWidth,
st::dialogsTextFont->height);
const auto actionWasPainted = ShowSendActionInDialogs(history)
? history->sendActionPainter()->paint(
const auto actionWasPainted = ShowSendActionInDialogs(thread)
? thread->sendActionPainter()->paint(
p,
rect.x(),
rect.y(),

View file

@ -654,7 +654,7 @@ bool InnerWidget::elementHideReply(not_null<const Element*> view) {
}
bool InnerWidget::elementShownUnread(not_null<const Element*> view) {
return view->data()->unread();
return false;
}
void InnerWidget::elementSendBotCommand(

View file

@ -695,9 +695,14 @@ void GenerateItems(
using LogJoinByRequest =
MTPDchannelAdminLogEventActionParticipantJoinByRequest;
using LogNoForwards = MTPDchannelAdminLogEventActionToggleNoForwards;
using LogActionSendMessage = MTPDchannelAdminLogEventActionSendMessage;
using LogEventActionChangeAvailableReactions = MTPDchannelAdminLogEventActionChangeAvailableReactions;
using LogEventActionChangeUsernames = MTPDchannelAdminLogEventActionChangeUsernames;
using LogSendMessage = MTPDchannelAdminLogEventActionSendMessage;
using LogChangeAvailableReactions = MTPDchannelAdminLogEventActionChangeAvailableReactions;
using LogChangeUsernames = MTPDchannelAdminLogEventActionChangeUsernames;
using LogToggleForum = MTPDchannelAdminLogEventActionToggleForum;
using LogCreateTopic = MTPDchannelAdminLogEventActionCreateTopic;
using LogEditTopic = MTPDchannelAdminLogEventActionEditTopic;
using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic;
using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic;
const auto session = &history->session();
const auto id = event.vid().v;
@ -972,7 +977,7 @@ void GenerateItems(
ExtractSentDate(action.vmessage()));
};
const auto createParticipantJoin = [&]() {
const auto createParticipantJoin = [&](const LogJoin&) {
const auto text = (channel->isMegagroup()
? tr::lng_admin_log_participant_joined
: tr::lng_admin_log_participant_joined_channel)(
@ -983,7 +988,7 @@ void GenerateItems(
addSimpleServiceMessage(text);
};
const auto createParticipantLeave = [&]() {
const auto createParticipantLeave = [&](const LogLeave&) {
const auto text = (channel->isMegagroup()
? tr::lng_admin_log_participant_left
: tr::lng_admin_log_participant_left_channel)(
@ -1457,7 +1462,7 @@ void GenerateItems(
addSimpleServiceMessage(text);
};
const auto createSendMessage = [&](const LogActionSendMessage &data) {
const auto createSendMessage = [&](const LogSendMessage &data) {
const auto text = tr::lng_admin_log_sent_message(
tr::now,
lt_from,
@ -1476,7 +1481,7 @@ void GenerateItems(
};
const auto createChangeAvailableReactions = [&](
const LogEventActionChangeAvailableReactions &data) {
const LogChangeAvailableReactions &data) {
const auto text = data.vnew_value().match([&](
const MTPDchatReactionsNone&) {
return tr::lng_admin_log_reactions_disabled(
@ -1514,87 +1519,74 @@ void GenerateItems(
addSimpleServiceMessage(text);
};
const auto createChangeUsernames = [&](
const LogEventActionChangeUsernames &data) {
const auto createChangeUsernames = [&](const LogChangeUsernames &data) {
// #TODO usernames
addSimpleServiceMessage({ "changed usernames" });
};
action.match([&](const LogTitle &data) {
createChangeTitle(data);
}, [&](const LogAbout &data) {
createChangeAbout(data);
}, [&](const LogUsername &data) {
createChangeUsername(data);
}, [&](const LogPhoto &data) {
createChangePhoto(data);
}, [&](const LogInvites &data) {
createToggleInvites(data);
}, [&](const LogSign &data) {
createToggleSignatures(data);
}, [&](const LogPin &data) {
createUpdatePinned(data);
}, [&](const LogEdit &data) {
createEditMessage(data);
}, [&](const LogDelete &data) {
createDeleteMessage(data);
}, [&](const LogJoin &) {
createParticipantJoin();
}, [&](const LogLeave &) {
createParticipantLeave();
}, [&](const LogInvite &data) {
createParticipantInvite(data);
}, [&](const LogBan &data) {
createParticipantToggleBan(data);
}, [&](const LogPromote &data) {
createParticipantToggleAdmin(data);
}, [&](const LogSticker &data) {
createChangeStickerSet(data);
}, [&](const LogPreHistory &data) {
createTogglePreHistoryHidden(data);
}, [&](const LogPermissions &data) {
createDefaultBannedRights(data);
}, [&](const LogPoll &data) {
createStopPoll(data);
}, [&](const LogDiscussion &data) {
createChangeLinkedChat(data);
}, [&](const LogLocation &data) {
createChangeLocation(data);
}, [&](const LogSlowMode &data) {
createToggleSlowMode(data);
}, [&](const LogStartCall &data) {
createStartGroupCall(data);
}, [&](const LogDiscardCall &data) {
createDiscardGroupCall(data);
}, [&](const LogMute &data) {
createParticipantMute(data);
}, [&](const LogUnmute &data) {
createParticipantUnmute(data);
}, [&](const LogCallSetting &data) {
createToggleGroupCallSetting(data);
}, [&](const LogJoinByInvite &data) {
createParticipantJoinByInvite(data);
}, [&](const LogInviteDelete &data) {
createExportedInviteDelete(data);
}, [&](const LogInviteRevoke &data) {
createExportedInviteRevoke(data);
}, [&](const LogInviteEdit &data) {
createExportedInviteEdit(data);
}, [&](const LogVolume &data) {
createParticipantVolume(data);
}, [&](const LogTTL &data) {
createChangeHistoryTTL(data);
}, [&](const LogJoinByRequest &data) {
createParticipantJoinByRequest(data);
}, [&](const LogNoForwards &data) {
createToggleNoForwards(data);
}, [&](const LogActionSendMessage &data) {
createSendMessage(data);
}, [&](const LogEventActionChangeAvailableReactions &data) {
createChangeAvailableReactions(data);
}, [&](const LogEventActionChangeUsernames &data) {
createChangeUsernames(data);
});
const auto createToggleForum = [&](const LogToggleForum &data) {
};
const auto createCreateTopic = [&](const LogCreateTopic &data) {
};
const auto createEditTopic = [&](const LogEditTopic &data) {
};
const auto createDeleteTopic = [&](const LogDeleteTopic &data) {
};
const auto createPinTopic = [&](const LogPinTopic &data) {
};
action.match(
createChangeTitle,
createChangeAbout,
createChangeUsername,
createChangePhoto,
createToggleInvites,
createToggleSignatures,
createUpdatePinned,
createEditMessage,
createDeleteMessage,
createParticipantJoin,
createParticipantLeave,
createParticipantInvite,
createParticipantToggleBan,
createParticipantToggleAdmin,
createChangeStickerSet,
createTogglePreHistoryHidden,
createDefaultBannedRights,
createStopPoll,
createChangeLinkedChat,
createChangeLocation,
createToggleSlowMode,
createStartGroupCall,
createDiscardGroupCall,
createParticipantMute,
createParticipantUnmute,
createToggleGroupCallSetting,
createParticipantJoinByInvite,
createExportedInviteDelete,
createExportedInviteRevoke,
createExportedInviteEdit,
createParticipantVolume,
createChangeHistoryTTL,
createParticipantJoinByRequest,
createToggleNoForwards,
createSendMessage,
createChangeAvailableReactions,
createChangeUsernames,
createToggleForum,
createCreateTopic,
createEditTopic,
createDeleteTopic,
createPinTopic);
}
} // namespace AdminLog

View file

@ -157,53 +157,56 @@ void History::itemVanished(not_null<HistoryItem*> item) {
clearLastKeyboard();
}
if ((!item->out() || item->isPost())
&& item->unread()
&& item->unread(this)
&& unreadCount() > 0) {
setUnreadCount(unreadCount() - 1);
}
}
void History::takeLocalDraft(not_null<History*> from) {
const auto i = from->_drafts.find(Data::DraftKey::Local());
const auto topicRootId = MsgId(0);
const auto i = from->_drafts.find(Data::DraftKey::Local(topicRootId));
if (i == end(from->_drafts)) {
return;
}
auto &draft = i->second;
if (!draft->textWithTags.text.isEmpty()
&& !_drafts.contains(Data::DraftKey::Local())) {
&& !_drafts.contains(Data::DraftKey::Local(topicRootId))) {
// Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically.
draft->msgId = 0;
setLocalDraft(std::move(draft));
}
from->clearLocalDraft();
from->clearLocalDraft(topicRootId);
session().api().saveDraftToCloudDelayed(from);
}
void History::createLocalDraftFromCloud() {
const auto draft = cloudDraft();
void History::createLocalDraftFromCloud(MsgId topicRootId) {
const auto draft = cloudDraft(topicRootId);
if (!draft) {
clearLocalDraft();
clearLocalDraft(topicRootId);
return;
} else if (Data::draftIsNull(draft) || !draft->date) {
} else if (Data::DraftIsNull(draft) || !draft->date) {
return;
}
auto existing = localDraft();
if (Data::draftIsNull(existing)
auto existing = localDraft(topicRootId);
if (Data::DraftIsNull(existing)
|| !existing->date
|| draft->date >= existing->date) {
if (!existing) {
setLocalDraft(std::make_unique<Data::Draft>(
draft->textWithTags,
draft->msgId,
topicRootId,
draft->cursor,
draft->previewState));
existing = localDraft();
existing = localDraft(topicRootId);
} else if (existing != draft) {
existing->textWithTags = draft->textWithTags;
existing->msgId = draft->msgId;
existing->topicRootId = draft->topicRootId;
existing->cursor = draft->cursor;
existing->previewState = draft->previewState;
}
@ -219,18 +222,22 @@ Data::Draft *History::draft(Data::DraftKey key) const {
return (i != _drafts.end()) ? i->second.get() : nullptr;
}
void History::setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft) {
void History::setDraft(
Data::DraftKey key,
std::unique_ptr<Data::Draft> &&draft) {
if (!key) {
return;
}
const auto changingCloudDraft = (key == Data::DraftKey::Cloud());
if (changingCloudDraft) {
cloudDraftTextCache().clear();
const auto cloudThread = key.isCloud()
? threadFor(key.topicRootId())
: nullptr;
if (cloudThread) {
cloudThread->cloudDraftTextCache().clear();
}
if (draft) {
_drafts[key] = std::move(draft);
} else if (_drafts.remove(key) && changingCloudDraft) {
updateChatListSortPosition();
} else if (_drafts.remove(key) && cloudThread) {
cloudThread->updateChatListSortPosition();
}
}
@ -250,31 +257,38 @@ void History::clearDraft(Data::DraftKey key) {
}
void History::clearDrafts() {
const auto changingCloudDraft = _drafts.contains(Data::DraftKey::Cloud());
_drafts.clear();
if (changingCloudDraft) {
cloudDraftTextCache().clear();
updateChatListSortPosition();
for (auto &[key, draft] : base::take(_drafts)) {
const auto cloudThread = key.isCloud()
? threadFor(key.topicRootId())
: nullptr;
if (cloudThread) {
cloudThread->cloudDraftTextCache().clear();
cloudThread->updateChatListSortPosition();
}
}
}
Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) {
if (Data::draftIsNull(fromDraft)) {
Data::Draft *History::createCloudDraft(
MsgId topicRootId,
const Data::Draft *fromDraft) {
if (Data::DraftIsNull(fromDraft)) {
setCloudDraft(std::make_unique<Data::Draft>(
TextWithTags(),
0,
topicRootId,
MessageCursor(),
Data::PreviewState::Allowed));
cloudDraft()->date = TimeId(0);
cloudDraft(topicRootId)->date = TimeId(0);
} else {
auto existing = cloudDraft();
auto existing = cloudDraft(topicRootId);
if (!existing) {
setCloudDraft(std::make_unique<Data::Draft>(
fromDraft->textWithTags,
fromDraft->msgId,
topicRootId,
fromDraft->cursor,
fromDraft->previewState));
existing = cloudDraft();
existing = cloudDraft(topicRootId);
} else if (existing != fromDraft) {
existing->textWithTags = fromDraft->textWithTags;
existing->msgId = fromDraft->msgId;
@ -284,42 +298,60 @@ Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) {
existing->date = base::unixtime::now();
}
cloudDraftTextCache().clear();
updateChatListSortPosition();
return cloudDraft();
}
bool History::skipCloudDraftUpdate(TimeId date) const {
return (_savingCloudDraftRequests > 0)
|| (date < _acceptCloudDraftsAfter);
}
void History::startSavingCloudDraft() {
++_savingCloudDraftRequests;
}
void History::finishSavingCloudDraft(TimeId savedAt) {
if (_savingCloudDraftRequests > 0) {
--_savingCloudDraftRequests;
if (const auto thread = threadFor(topicRootId)) {
thread->cloudDraftTextCache().clear();
thread->updateChatListSortPosition();
}
const auto acceptAfter = savedAt + kSkipCloudDraftsFor;
_acceptCloudDraftsAfter = std::max(_acceptCloudDraftsAfter, acceptAfter);
return cloudDraft(topicRootId);
}
void History::applyCloudDraft() {
if (session().supportMode()) {
bool History::skipCloudDraftUpdate(MsgId topicRootId, TimeId date) const {
const auto i = _acceptCloudDraftsAfter.find(topicRootId);
return _savingCloudDraftRequests.contains(topicRootId)
|| (i != _acceptCloudDraftsAfter.end() && date < i->second);
}
void History::startSavingCloudDraft(MsgId topicRootId) {
++_savingCloudDraftRequests[topicRootId];
}
void History::finishSavingCloudDraft(MsgId topicRootId, TimeId savedAt) {
const auto i = _savingCloudDraftRequests.find(topicRootId);
if (i != _savingCloudDraftRequests.end()) {
if (--i->second <= 0) {
_savingCloudDraftRequests.erase(i);
}
}
auto &after = _acceptCloudDraftsAfter[topicRootId];
after = std::max(after, savedAt + kSkipCloudDraftsFor);
}
void History::applyCloudDraft(MsgId topicRootId) {
if (!topicRootId && session().supportMode()) {
updateChatListEntry();
session().supportHelper().cloudDraftChanged(this);
} else {
createLocalDraftFromCloud();
updateChatListSortPosition();
session().changes().historyUpdated(this, UpdateFlag::CloudDraft);
createLocalDraftFromCloud(topicRootId);
if (const auto thread = threadFor(topicRootId)) {
thread->updateChatListSortPosition();
if (!topicRootId) {
session().changes().historyUpdated(
this,
UpdateFlag::CloudDraft);
} else {
session().changes().topicUpdated(
thread->asTopic(),
Data::TopicUpdate::Flag::CloudDraft);
}
}
}
}
void History::draftSavedToCloud() {
updateChatListEntry();
void History::draftSavedToCloud(MsgId topicRootId) {
if (const auto thread = threadFor(topicRootId)) {
thread->updateChatListEntry();
}
session().local().writeDrafts(this);
}
@ -1143,20 +1175,22 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
const auto stillShow = item->showNotification(); // Could be read already.
if (stillShow) {
Core::App().notifications().schedule(notification);
if (!item->out() && item->unread()) {
}
if (item->out()) {
destroyUnreadBar();
if (!item->unread(this)) {
outboxRead(item);
}
} else {
if (item->unread(this)) {
if (unreadCountKnown()) {
setUnreadCount(unreadCount() + 1);
} else {
owner().histories().requestDialogEntry(this);
}
} else {
inboxRead(item);
}
} else if (item->out()) {
destroyUnreadBar();
} else if (!item->unread()) {
inboxRead(item);
}
if (item->out() && !item->unread()) {
outboxRead(item);
}
item->incrementReplyToTopCounter();
if (!folderKnown()) {
@ -1879,8 +1913,8 @@ void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
TimeId History::adjustedChatListTimeId() const {
const auto result = chatListTimeId();
if (const auto draft = cloudDraft()) {
if (!Data::draftIsNull(draft) && !session().supportMode()) {
if (const auto draft = cloudDraft(MsgId(0))) {
if (!Data::DraftIsNull(draft) && !session().supportMode()) {
return std::max(result, draft->date);
}
}
@ -2601,6 +2635,7 @@ void History::applyDialog(
Data::ApplyPeerCloudDraft(
&session(),
peer->id,
MsgId(0), // topicRootId
draft->c_draftMessage());
}
owner().histories().dialogEntryApplied(this);
@ -2823,6 +2858,16 @@ void History::forceFullResize() {
_flags |= Flag::HasPendingResizedItems;
}
Data::Thread *History::threadFor(MsgId topicRootId) {
return topicRootId
? peer->forumTopicFor(topicRootId)
: static_cast<Data::Thread*>(this);
}
const Data::Thread *History::threadFor(MsgId topicRootId) const {
return const_cast<History*>(this)->threadFor(topicRootId);
}
not_null<History*> History::migrateToOrMe() const {
if (const auto to = peer->migrateTo()) {
return owner().history(to);

View file

@ -80,6 +80,8 @@ public:
not_null<History*> owningHistory() override {
return this;
}
[[nodiscard]] Data::Thread *threadFor(MsgId topicRootId);
[[nodiscard]] const Data::Thread *threadFor(MsgId topicRootId) const;
[[nodiscard]] auto delegateMixin() const
-> not_null<HistoryMainElementDelegateMixin*> {
@ -229,12 +231,13 @@ public:
void inboxRead(not_null<const HistoryItem*> wasRead);
void outboxRead(MsgId upTo);
void outboxRead(not_null<const HistoryItem*> wasRead);
[[nodiscard]] bool isServerSideUnread(
not_null<const HistoryItem*> item) const;
[[nodiscard]] MsgId loadAroundId() const;
[[nodiscard]] MsgId inboxReadTillId() const;
[[nodiscard]] MsgId outboxReadTillId() const;
[[nodiscard]] bool isServerSideUnread(
not_null<const HistoryItem*> item) const override;
[[nodiscard]] bool trackUnreadMessages() const;
[[nodiscard]] int unreadCount() const;
[[nodiscard]] bool unreadCountKnown() const;
@ -301,7 +304,7 @@ public:
void setHasPendingResizedItems();
[[nodiscard]] auto sendActionPainter()
-> not_null<HistoryView::SendActionPainter*> {
-> not_null<HistoryView::SendActionPainter*> override {
return &_sendActionPainter;
}
@ -316,41 +319,51 @@ public:
[[nodiscard]] const Data::HistoryDrafts &draftsMap() const;
void setDraftsMap(Data::HistoryDrafts &&map);
Data::Draft *localDraft() const {
return draft(Data::DraftKey::Local());
Data::Draft *localDraft(MsgId topicRootId) const {
return draft(Data::DraftKey::Local(topicRootId));
}
Data::Draft *localEditDraft() const {
return draft(Data::DraftKey::LocalEdit());
Data::Draft *localEditDraft(MsgId topicRootId) const {
return draft(Data::DraftKey::LocalEdit(topicRootId));
}
Data::Draft *cloudDraft() const {
return draft(Data::DraftKey::Cloud());
Data::Draft *cloudDraft(MsgId topicRootId) const {
return draft(Data::DraftKey::Cloud(topicRootId));
}
void setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
setDraft(Data::DraftKey::Local(), std::move(draft));
setDraft(
Data::DraftKey::Local(draft->topicRootId),
std::move(draft));
}
void setLocalEditDraft(std::unique_ptr<Data::Draft> &&draft) {
setDraft(Data::DraftKey::LocalEdit(), std::move(draft));
setDraft(
Data::DraftKey::LocalEdit(draft->topicRootId),
std::move(draft));
}
void setCloudDraft(std::unique_ptr<Data::Draft> &&draft) {
setDraft(Data::DraftKey::Cloud(), std::move(draft));
setDraft(
Data::DraftKey::Cloud(draft->topicRootId),
std::move(draft));
}
void clearLocalDraft() {
clearDraft(Data::DraftKey::Local());
void clearLocalDraft(MsgId topicRootId) {
clearDraft(Data::DraftKey::Local(topicRootId));
}
void clearCloudDraft() {
clearDraft(Data::DraftKey::Cloud());
void clearCloudDraft(MsgId topicRootId) {
clearDraft(Data::DraftKey::Cloud(topicRootId));
}
void clearLocalEditDraft() {
clearDraft(Data::DraftKey::LocalEdit());
void clearLocalEditDraft(MsgId topicRootId) {
clearDraft(Data::DraftKey::LocalEdit(topicRootId));
}
void clearDrafts();
Data::Draft *createCloudDraft(const Data::Draft *fromDraft);
bool skipCloudDraftUpdate(TimeId date) const;
void startSavingCloudDraft();
void finishSavingCloudDraft(TimeId savedAt);
Data::Draft *createCloudDraft(
MsgId topicRootId,
const Data::Draft *fromDraft);
[[nodiscard]] bool skipCloudDraftUpdate(
MsgId topicRootId,
TimeId date) const;
void startSavingCloudDraft(MsgId topicRootId);
void finishSavingCloudDraft(MsgId topicRootId, TimeId savedAt);
void takeLocalDraft(not_null<History*> from);
void applyCloudDraft();
void draftSavedToCloud();
void applyCloudDraft(MsgId topicRootId);
void draftSavedToCloud(MsgId topicRootId);
[[nodiscard]] const Data::ForwardDraft &forwardDraft() const {
return _forwardDraft;
@ -553,7 +566,7 @@ private:
void viewReplaced(not_null<const Element*> was, Element *now);
void createLocalDraftFromCloud();
void createLocalDraftFromCloud(MsgId topicRootId);
HistoryService *insertJoinedMessage();
void insertMessageToBlocks(not_null<HistoryItem*> item);
@ -603,8 +616,8 @@ private:
std::unique_ptr<BuildingBlock> _buildingFrontBlock;
Data::HistoryDrafts _drafts;
TimeId _acceptCloudDraftsAfter = 0;
int _savingCloudDraftRequests = 0;
base::flat_map<MsgId, TimeId> _acceptCloudDraftsAfter;
base::flat_map<MsgId, int> _savingCloudDraftRequests;
Data::ForwardDraft _forwardDraft;
QString _topPromotedMessage;

View file

@ -227,7 +227,7 @@ public:
return false;
}
bool elementShownUnread(not_null<const Element*> view) override {
return view->data()->unread();
return view->data()->unread(view->data()->history());
}
void elementSendBotCommand(
const QString &command,
@ -983,7 +983,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto item = view->data();
const auto isSponsored = item->isSponsored();
const auto isUnread = !item->out()
&& item->unread()
&& item->unread(_history)
&& (item->history() == _history);
const auto withReaction = item->hasUnreadReaction();
const auto yShown = [&](int y) {

View file

@ -1217,22 +1217,22 @@ bool HistoryItem::needCheck() const {
return (out() && !isEmpty()) || (!isRegular() && history()->peer->isSelf());
}
bool HistoryItem::unread() const {
bool HistoryItem::unread(not_null<Data::Thread*> thread) const {
// Messages from myself are always read, unless scheduled.
if (history()->peer->isSelf() && !isFromScheduled()) {
return false;
}
if (out()) {
// Outgoing messages in converted chats are always read.
if (history()->peer->migrateTo()) {
// All messages in converted chats are always read.
if (history()->peer->migrateTo()) {
return false;
}
if (isRegular()) {
if (!thread->isServerSideUnread(this)) {
return false;
}
if (isRegular()) {
if (!history()->isServerSideUnread(this)) {
return false;
}
if (out()) {
if (const auto user = history()->peer->asUser()) {
if (user->isBot() && !user->isSupport()) {
return false;
@ -1246,13 +1246,7 @@ bool HistoryItem::unread() const {
return true;
}
if (isRegular()) {
if (!history()->isServerSideUnread(this)) {
return false;
}
return true;
}
return (_flags & MessageFlag::ClientSideUnread);
return out() || (_flags & MessageFlag::ClientSideUnread);
}
bool HistoryItem::showNotification() const {
@ -1262,7 +1256,7 @@ bool HistoryItem::showNotification() const {
}
return (out() || _history->peer->isSelf())
? isFromScheduled()
: unread();
: unread(notificationThread());
}
void HistoryItem::markClientSideAsRead() {

View file

@ -150,7 +150,7 @@ public:
[[nodiscard]] bool isPinned() const {
return _flags & MessageFlag::Pinned;
}
[[nodiscard]] bool unread() const;
[[nodiscard]] bool unread(not_null<Data::Thread*> thread) const;
[[nodiscard]] bool showNotification() const;
void markClientSideAsRead();
[[nodiscard]] bool mentionsMe() const;

View file

@ -1773,6 +1773,9 @@ void HistoryMessage::setReplyFields(
&& !IsServerMsgId(reply->replyToMsgTop)) {
reply->replyToMsgTop = replyToTop;
changeReplyToTopCounter(reply, 1);
if (const auto topic = this->topic()) {
topic->maybeSetLastMessage(this);
}
}
}

View file

@ -1342,10 +1342,19 @@ void HistoryService::setReplyFields(
MsgId replyToTop,
bool isForumPost) {
const auto data = GetDependentData();
if (!data || IsServerMsgId(data->topId) || isScheduled()) {
if (!data
|| (data->topId == replyToTop)
|| IsServerMsgId(data->topId)
|| isScheduled()) {
return;
}
data->topId = replyToTop;
if (isForumPost) {
data->topicPost = true;
}
if (const auto topic = this->topic()) {
topic->maybeSetLastMessage(this);
}
}
std::unique_ptr<HistoryView::Element> HistoryService::createView(

View file

@ -948,7 +948,7 @@ void HistoryWidget::initVoiceRecordBar() {
});
const auto applyLocalDraft = [=] {
if (_history && _history->localDraft()) {
if (_history && _history->localDraft({})) {
applyDraft();
}
};
@ -1657,10 +1657,12 @@ void HistoryWidget::saveDraft(bool delayed) {
void HistoryWidget::saveFieldToHistoryLocalDraft() {
if (!_history) return;
const auto topicRootId = MsgId();
if (_editMsgId) {
_history->setLocalEditDraft(std::make_unique<Data::Draft>(
_field,
_editMsgId,
topicRootId,
_previewState,
_saveEditMsgRequestId));
} else {
@ -1668,11 +1670,12 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() {
_history->setLocalDraft(std::make_unique<Data::Draft>(
_field,
_replyToId,
topicRootId,
_previewState));
} else {
_history->clearLocalDraft();
_history->clearLocalDraft({});
}
_history->clearLocalEditDraft();
_history->clearLocalEditDraft({});
}
}
@ -1760,7 +1763,8 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U
MessageCursor cursor = { int(textWithTags.text.size()), int(textWithTags.text.size()), QFIXED_MAX };
_history->setLocalDraft(std::make_unique<Data::Draft>(
textWithTags,
0,
0, // replyTo
0, // topicRootId
cursor,
Data::PreviewState::Allowed));
applyDraft();
@ -1782,21 +1786,19 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U
auto draft = std::make_unique<Data::Draft>(
textWithTags,
to.currentReplyToId,
to.rootId,
cursor,
Data::PreviewState::Allowed);
if (to.section == Section::Replies) {
history->setDraft(
Data::DraftKey::Replies(to.rootId),
std::move(draft));
controller()->showRepliesForMessage(history, to.rootId);
} else if (to.section == Section::Scheduled) {
if (to.section == Section::Scheduled) {
history->setDraft(Data::DraftKey::Scheduled(), std::move(draft));
controller()->showSection(
std::make_shared<HistoryView::ScheduledMemento>(history));
} else {
history->setLocalDraft(std::move(draft));
if (history == _history) {
if (to.section == Section::Replies) {
controller()->showRepliesForMessage(history, to.rootId);
} else if (history == _history) {
applyDraft();
} else {
controller()->showPeerHistory(history->peer);
@ -1878,13 +1880,14 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
return;
}
auto draft = !_history
? nullptr
: _history->localEditDraft()
? _history->localEditDraft()
: _history->localDraft();
const auto editDraft = _history ? _history->localEditDraft({}) : nullptr;
const auto draft = editDraft
? editDraft
: _history
? _history->localDraft({})
: nullptr;
auto fieldAvailable = canWriteMessage();
if (!draft || (!_history->localEditDraft() && !fieldAvailable)) {
if (!draft || (!_history->localEditDraft({}) && !fieldAvailable)) {
auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0);
clearFieldText(0, fieldHistoryAction);
_field->setFocus();
@ -1913,11 +1916,11 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
_previewState = draft->previewState;
_replyEditMsg = nullptr;
if (const auto editDraft = _history->localEditDraft()) {
if (const auto editDraft = _history->localEditDraft({})) {
setEditMsgId(editDraft->msgId);
_replyToId = 0;
} else {
_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
_replyToId = readyToForward() ? 0 : _history->localDraft({})->msgId;
setEditMsgId(0);
}
updateCmdStartShown();
@ -2036,7 +2039,7 @@ void HistoryWidget::showHistory(
info->inlineReturnTo = wasDialogsEntryState;
}
sendBotStartCommand();
_history->clearLocalDraft();
_history->clearLocalDraft({});
applyDraft();
_send->finishAnimating();
}
@ -2356,10 +2359,10 @@ void HistoryWidget::unregisterDraftSources() {
}
session().local().unregisterDraftSource(
_history,
Data::DraftKey::Local());
Data::DraftKey::Local({}));
session().local().unregisterDraftSource(
_history,
Data::DraftKey::LocalEdit());
Data::DraftKey::LocalEdit({}));
}
void HistoryWidget::registerDraftSource() {
@ -2380,7 +2383,9 @@ void HistoryWidget::registerDraftSource() {
};
session().local().registerDraftSource(
_history,
editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(),
(editMsgId
? Data::DraftKey::LocalEdit({})
: Data::DraftKey::Local({})),
std::move(draftSource));
}
@ -3564,16 +3569,16 @@ void HistoryWidget::saveEditMsg() {
cancelEdit();
}
})();
if (const auto editDraft = history->localEditDraft()) {
if (const auto editDraft = history->localEditDraft({})) {
if (editDraft->saveRequestId == requestId) {
history->clearLocalEditDraft();
history->clearLocalEditDraft({});
history->session().local().writeDrafts(history);
}
}
};
const auto fail = [=](const QString &error, mtpRequestId requestId) {
if (const auto editDraft = history->localEditDraft()) {
if (const auto editDraft = history->localEditDraft({})) {
if (editDraft->saveRequestId == requestId) {
editDraft->saveRequestId = 0;
}
@ -3648,6 +3653,7 @@ Api::SendAction HistoryWidget::prepareSendAction(
Api::SendOptions options) const {
auto result = Api::SendAction(_history, options);
result.replyTo = replyToId();
result.topicRootId = 0;
result.options.sendAs = _sendAs
? _history->session().sendAsPeers().resolveChosen(
_history->peer).get()
@ -6650,12 +6656,13 @@ void HistoryWidget::replyToMessage(not_null<HistoryItem*> item) {
}
if (_editMsgId) {
if (auto localDraft = _history->localDraft()) {
if (const auto localDraft = _history->localDraft({})) {
localDraft->msgId = item->id;
} else {
_history->setLocalDraft(std::make_unique<Data::Draft>(
TextWithTags(),
item->id,
MsgId(), // topicRootId
MessageCursor(),
Data::PreviewState::Allowed));
}
@ -6708,9 +6715,10 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
_history->setLocalDraft(std::make_unique<Data::Draft>(
_field,
_replyToId,
MsgId(), // topicRootId
_previewState));
} else {
_history->clearLocalDraft();
_history->clearLocalDraft({});
}
}
@ -6732,6 +6740,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
_history->setLocalEditDraft(std::make_unique<Data::Draft>(
editData,
item->id,
MsgId(), // topicRootId
cursor,
previewState));
applyDraft();
@ -6811,10 +6820,10 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
refreshTopBarActiveChat();
updateControlsGeometry();
update();
} else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) {
} else if (const auto localDraft = (_history ? _history->localDraft({}) : nullptr)) {
if (localDraft->msgId) {
if (localDraft->textWithTags.text.isEmpty()) {
_history->clearLocalDraft();
_history->clearLocalDraft({});
} else {
localDraft->msgId = 0;
}
@ -6856,7 +6865,7 @@ void HistoryWidget::cancelEdit() {
_replyEditMsg = nullptr;
setEditMsgId(0);
_history->clearLocalEditDraft();
_history->clearLocalEditDraft({});
applyDraft();
if (_saveEditMsgRequestId) {

View file

@ -1169,16 +1169,18 @@ void ComposeControls::setFieldText(
void ComposeControls::saveFieldToHistoryLocalDraft() {
const auto key = draftKeyCurrent();
if (!_history || key == Data::DraftKey::None()) {
if (!_history || !key) {
return;
}
const auto id = _header->getDraftMessageId();
if (_preview && (id || !_field->empty())) {
const auto key = draftKeyCurrent();
_history->setDraft(
draftKeyCurrent(),
key,
std::make_unique<Data::Draft>(
_field,
_header->getDraftMessageId(),
key.topicRootId(),
_preview->state()));
} else {
_history->clearDraft(draftKeyCurrent());
@ -1680,15 +1682,14 @@ Data::DraftKey ComposeControls::draftKey(DraftType type) const {
switch (_currentDialogsEntryState.section) {
case Section::History:
return (type == DraftType::Edit) ? Key::LocalEdit() : Key::Local();
case Section::Replies:
return (type == DraftType::Edit)
? Key::LocalEdit(_currentDialogsEntryState.rootId)
: Key::Local(_currentDialogsEntryState.rootId);
case Section::Scheduled:
return (type == DraftType::Edit)
? Key::ScheduledEdit()
: Key::Scheduled();
case Section::Replies:
return (type == DraftType::Edit)
? Key::RepliesEdit(_currentDialogsEntryState.rootId)
: Key::Replies(_currentDialogsEntryState.rootId);
}
return Key::None();
}
@ -2380,11 +2381,13 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
const auto previewState = previewPage
? Data::PreviewState::Allowed
: Data::PreviewState::EmptyOnEdit;
const auto key = draftKey(DraftType::Edit);
_history->setDraft(
draftKey(DraftType::Edit),
key,
std::make_unique<Data::Draft>(
editData,
item->id,
key.topicRootId(),
cursor,
previewState));
applyDraft();
@ -2424,6 +2427,7 @@ void ComposeControls::replyToMessage(FullMsgId id) {
std::make_unique<Data::Draft>(
TextWithTags(),
id.msg,
key.topicRootId(),
MessageCursor(),
Data::PreviewState::Allowed));
}
@ -2438,7 +2442,6 @@ void ComposeControls::replyToMessage(FullMsgId id) {
void ComposeControls::cancelReplyMessage() {
Expects(_history != nullptr);
Expects(draftKeyCurrent() != Data::DraftKey::None());
const auto wasReply = replyingToMessage();
_header->replyToMessage({});

View file

@ -645,7 +645,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
if (forwarded && forwarded->imported) {
result.flags |= Flag::Imported;
}
// We don't want to pass and update it in Date for now.
// We don't want to pass and update it in Data for now.
//if (item->unread()) {
// result.flags |= Flag::Unread;
//}

View file

@ -185,7 +185,7 @@ bool SimpleElementDelegate::elementHideReply(not_null<const Element*> view) {
bool SimpleElementDelegate::elementShownUnread(
not_null<const Element*> view) {
return view->data()->unread();
return view->data()->unread(view->data()->history());
}
void SimpleElementDelegate::elementSendBotCommand(

View file

@ -553,7 +553,7 @@ bool PinnedWidget::listElementHideReply(not_null<const Element*> view) {
}
bool PinnedWidget::listElementShownUnread(not_null<const Element*> view) {
return view->data()->unread();
return view->data()->unread(view->data()->history());
}
bool PinnedWidget::listIsGoodForAroundPosition(

View file

@ -351,8 +351,8 @@ RepliesWidget::RepliesWidget(
}
RepliesWidget::~RepliesWidget() {
if (_topic && _topic->forum()->creating(_rootId)) {
_topic->forum()->discardCreatingId(_rootId);
if (_topic && _topic->creating()) {
_topic->discard();
_topic = nullptr;
}
base::take(_sendAction);
@ -434,15 +434,20 @@ void RepliesWidget::setupRootView() {
}
void RepliesWidget::setupTopicViewer() {
_history->owner().itemIdChanged(
const auto owner = &_history->owner();
owner->itemIdChanged(
) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
if (_rootId == change.oldId) {
_rootId = change.newId.msg;
_sendAction = owner->sendActionManager().repliesPainter(
_history,
_rootId);
_root = lookupRoot();
if (_topic && _topic->rootId() == change.oldId) {
setTopic(_topic->forum()->topicFor(change.newId.msg));
} else {
refreshReplies();
refreshTopBarActiveChat();
}
_inner->update();
}
@ -1007,6 +1012,7 @@ Api::SendAction RepliesWidget::prepareSendAction(
Api::SendOptions options) const {
auto result = Api::SendAction(_history, options);
result.replyTo = replyToId();
result.topicRootId = _rootId;
result.options.sendAs = _composeControls->sendAsPeer();
return result;
}
@ -1440,7 +1446,7 @@ bool RepliesWidget::preventsClose(Fn<void()> &&continueCallback) const {
return true;
} else if (!_newTopicDiscarded
&& _topic
&& _topic->forum()->creating(_rootId)) {
&& _topic->creating()) {
const auto weak = Ui::MakeWeak(this);
auto sure = [=](Fn<void()> &&close) {
if (const auto strong = weak.data()) {
@ -1943,14 +1949,7 @@ bool RepliesWidget::listElementHideReply(not_null<const Element*> view) {
}
bool RepliesWidget::listElementShownUnread(not_null<const Element*> view) {
if (!_root) {
return false;
}
const auto item = view->data();
const auto till = item->out()
? _replies->computeOutboxReadTillFull()
: _replies->computeInboxReadTillFull();
return (item->id > till);
return _replies->isServerSideUnread(view->data());
}
bool RepliesWidget::listIsGoodForAroundPosition(

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_send_action.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "history/history.h"
@ -39,13 +40,20 @@ constexpr auto kStatusShowClientsideSpeaking = 6 * crl::time(1000);
} // namespace
SendActionPainter::SendActionPainter(not_null<History*> history)
SendActionPainter::SendActionPainter(
not_null<History*> history,
MsgId rootId)
: _history(history)
, _rootId(rootId)
, _weak(&_history->session())
, _st(st::dialogsTextStyle)
, _sendActionText(st::dialogsTextWidthMin) {
}
void SendActionPainter::setTopic(Data::ForumTopic *topic) {
_topic = topic;
}
bool SendActionPainter::updateNeedsAnimating(
not_null<UserData*> user,
const MTPSendMessageAction &action) {
@ -382,7 +390,7 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
st::normalFont->height,
st::dialogsMiniPreviewTop + st::dialogsMiniPreview);
_history->peer->owner().sendActionManager().updateAnimation({
_history,
_topic ? ((Data::Thread*)_topic) : _history,
0,
_sendActionAnimation.width() + _animationLeft,
height,

View file

@ -16,6 +16,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Data {
class ForumTopic;
} // namespace Data
namespace Api {
enum class SendProgressType;
struct SendProgress;
@ -25,7 +29,9 @@ namespace HistoryView {
class SendActionPainter final {
public:
explicit SendActionPainter(not_null<History*> history);
explicit SendActionPainter(not_null<History*> history, MsgId rootId = 0);
void setTopic(Data::ForumTopic *topic);
bool paint(
Painter &p,
@ -53,8 +59,10 @@ public:
private:
const not_null<History*> _history;
const MsgId _rootId = 0;
const base::weak_ptr<Main::Session> _weak;
const style::TextStyle &_st;
Data::ForumTopic *_topic = nullptr;
base::flat_map<not_null<UserData*>, crl::time> _typing;
base::flat_map<not_null<UserData*>, crl::time> _speaking;
base::flat_map<not_null<UserData*>, Api::SendProgress> _sendActions;

View file

@ -152,7 +152,7 @@ TopBarWidget::TopBarWidget(
using AnimationUpdate = Data::SendActionManager::AnimationUpdate;
session().data().sendActionManager().animationUpdated(
) | rpl::filter([=](const AnimationUpdate &update) {
return (update.history == _activeChat.key.history());
return (update.thread == _activeChat.key.thread());
}) | rpl::start_with_next([=] {
update();
}, lifetime());
@ -722,6 +722,7 @@ void TopBarWidget::backClicked() {
void TopBarWidget::setActiveChat(
ActiveChat activeChat,
SendActionPainter *sendAction) {
_sendAction = sendAction;
if (_activeChat.key == activeChat.key
&& _activeChat.section == activeChat.section) {
_activeChat = activeChat;
@ -733,7 +734,6 @@ void TopBarWidget::setActiveChat(
!= activeChat.key.history());
_activeChat = activeChat;
_sendAction = sendAction;
_titlePeerText.clear();
_back->clearState();
update();

View file

@ -204,29 +204,35 @@ void Controller::setupMigrationViewer() {
) | rpl::filter([=] {
return peer->migrateTo() || (peer->migrateFrom() != _migrated);
}) | rpl::start_with_next([=] {
const auto window = parentController();
const auto section = _section;
auto params = Window::SectionShow(
Window::SectionShow::Way::Backward,
anim::type::instant,
anim::activation::background);
if (wrap() == Wrap::Side) {
params.thirdColumn = true;
}
InvokeQueued(_widget, [=] {
window->showSection(
std::make_shared<Memento>(peer, section),
params);
});
replaceWith(std::make_shared<Memento>(peer, _section));
}, lifetime());
}
void Controller::replaceWith(std::shared_ptr<Memento> memento) {
const auto window = parentController();
const auto section = _section;
auto params = Window::SectionShow(
Window::SectionShow::Way::Backward,
anim::type::instant,
anim::activation::background);
if (wrap() == Wrap::Side) {
params.thirdColumn = true;
}
InvokeQueued(_widget, [=, memento = std::move(memento)]() mutable {
window->showSection(std::move(memento), params);
});
}
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));
if (topic->rootId() == change.oldId
|| (topic->peer()->id == change.newId.peer
&& topic->rootId() == change.newId.msg)) {
const auto now = topic->forum()->topicFor(change.newId.msg);
_key = Key(now);
replaceWith(std::make_shared<Memento>(now, _section));
}
}
}, _lifetime);

View file

@ -240,6 +240,8 @@ private:
void setupMigrationViewer();
void setupTopicViewer();
void replaceWith(std::shared_ptr<Memento> memento);
not_null<WrapWidget*> _widget;
Key _key;
PeerData *_migrated = nullptr;

View file

@ -577,6 +577,8 @@ Ui::MultiSlideTracker DetailsFiller::fillChannelButtons(
}
object_ptr<Ui::RpWidget> DetailsFiller::fill() {
Expects(!_topic || !_topic->creating());
add(object_ptr<Ui::BoxContentDivider>(_wrap));
add(CreateSkipWidget(_wrap));
add(setupInfo());
@ -585,6 +587,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() {
}
setupMainButtons();
add(CreateSkipWidget(_wrap));
return std::move(_wrap);
}

View file

@ -83,6 +83,9 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
}, _cover->lifetime());
_cover->setOnlineCount(rpl::single(0));
if (_topic) {
if (_topic->creating()) {
return result;
}
result->add(SetupDetails(_controller, parent, _topic));
} else {
result->add(SetupDetails(_controller, parent, _peer));

View file

@ -555,12 +555,14 @@ bool MainWidget::shareUrl(
QFIXED_MAX
};
const auto history = peer->owner().history(peer);
const auto topicRootId = 0;
history->setLocalDraft(std::make_unique<Data::Draft>(
textWithTags,
0,
0, // replyTo
topicRootId,
cursor,
Data::PreviewState::Allowed));
history->clearLocalEditDraft();
history->clearLocalEditDraft(topicRootId);
history->session().changes().historyUpdated(
history,
Data::HistoryUpdate::Flag::LocalDraftSet);
@ -587,12 +589,14 @@ bool MainWidget::inlineSwitchChosen(
int(botAndQuery.size()),
QFIXED_MAX
};
const auto topicRootId = 0;
h->setLocalDraft(std::make_unique<Data::Draft>(
textWithTags,
0,
0, // replyTo
topicRootId,
cursor,
Data::PreviewState::Allowed));
h->clearLocalEditDraft();
h->clearLocalEditDraft(topicRootId);
h->session().changes().historyUpdated(
h,
Data::HistoryUpdate::Flag::LocalDraftSet);

View file

@ -1587,7 +1587,7 @@ void FormController::uploadEncryptedFile(
auto prepared = std::make_shared<FileLoadResult>(
TaskId(),
file.uploadData->fileId,
FileLoadTo(PeerId(), Api::SendOptions(), MsgId(), MsgId()),
FileLoadTo(PeerId(), Api::SendOptions(), MsgId(), MsgId(), MsgId()),
TextWithTags(),
std::shared_ptr<SendingAlbum>(nullptr));
prepared->type = SendMediaType::Secure;

View file

@ -200,15 +200,18 @@ struct FileLoadTo {
PeerId peer,
Api::SendOptions options,
MsgId replyTo,
MsgId topicRootId,
MsgId replaceMediaOf)
: peer(peer)
, options(options)
, replyTo(replyTo)
, topicRootId(topicRootId)
, replaceMediaOf(replaceMediaOf) {
}
PeerId peer;
Api::SendOptions options;
MsgId replyTo;
MsgId topicRootId;
MsgId replaceMediaOf;
};

View file

@ -1025,17 +1025,20 @@ std::unique_ptr<MTP::Config> Account::readMtpConfig() {
template <typename Callback>
void EnumerateDrafts(
const Data::HistoryDrafts &map,
Data::Draft *cloudDraft,
bool supportMode,
const base::flat_map<Data::DraftKey, MessageDraftSource> &sources,
Callback &&callback) {
for (const auto &[key, draft] : map) {
if (key == Data::DraftKey::Cloud() || sources.contains(key)) {
continue;
} else if (key == Data::DraftKey::Local()
&& !supportMode
&& Data::draftsAreEqual(draft.get(), cloudDraft)) {
if (key.isCloud() || sources.contains(key)) {
continue;
} else if (key.isLocal()
&& (!supportMode || key.topicRootId())) {
const auto i = map.find(
Data::DraftKey::Cloud(key.topicRootId()));
const auto cloud = (i != end(map)) ? i->second.get() : nullptr;
if (Data::DraftsAreEqual(draft.get(), cloud)) {
continue;
}
}
callback(
key,
@ -1085,10 +1088,6 @@ void Account::unregisterDraftSource(
void Account::writeDrafts(not_null<History*> history) {
const auto peerId = history->peer->id;
const auto &map = history->draftsMap();
const auto cloudIt = map.find(Data::DraftKey::Cloud());
const auto cloudDraft = (cloudIt != end(map))
? cloudIt->second.get()
: nullptr;
const auto supportMode = _owner->session().supportMode();
const auto sourcesIt = _draftSources.find(history);
const auto &sources = (sourcesIt != _draftSources.end())
@ -1097,7 +1096,6 @@ void Account::writeDrafts(not_null<History*> history) {
auto count = 0;
EnumerateDrafts(
map,
cloudDraft,
supportMode,
sources,
[&](auto&&...) { ++count; });
@ -1133,7 +1131,6 @@ void Account::writeDrafts(not_null<History*> history) {
};
EnumerateDrafts(
map,
cloudDraft,
supportMode,
sources,
sizeCallback);
@ -1159,7 +1156,6 @@ void Account::writeDrafts(not_null<History*> history) {
};
EnumerateDrafts(
map,
cloudDraft,
supportMode,
sources,
writeCallback);
@ -1173,10 +1169,6 @@ void Account::writeDrafts(not_null<History*> history) {
void Account::writeDraftCursors(not_null<History*> history) {
const auto peerId = history->peer->id;
const auto &map = history->draftsMap();
const auto cloudIt = map.find(Data::DraftKey::Cloud());
const auto cloudDraft = (cloudIt != end(map))
? cloudIt->second.get()
: nullptr;
const auto supportMode = _owner->session().supportMode();
const auto sourcesIt = _draftSources.find(history);
const auto &sources = (sourcesIt != _draftSources.end())
@ -1185,7 +1177,6 @@ void Account::writeDraftCursors(not_null<History*> history) {
auto count = 0;
EnumerateDrafts(
map,
cloudDraft,
supportMode,
sources,
[&](auto&&...) { ++count; });
@ -1223,7 +1214,6 @@ void Account::writeDraftCursors(not_null<History*> history) {
};
EnumerateDrafts(
map,
cloudDraft,
supportMode,
sources,
writeCallback);
@ -1282,7 +1272,7 @@ void Account::readDraftCursors(PeerId peerId, Data::HistoryDrafts &map) {
? Data::DraftKey::FromSerialized(keyValue)
: keysOld
? Data::DraftKey::FromSerializedOld(keyValueOld)
: Data::DraftKey::Local();
: Data::DraftKey::Local(0);
qint32 position = 0, anchor = 0, scroll = QFIXED_MAX;
draft.stream >> position >> anchor >> scroll;
if (const auto i = map.find(key); i != end(map)) {
@ -1309,13 +1299,14 @@ void Account::readDraftCursorsLegacy(
return;
}
if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) {
if (const auto i = map.find(Data::DraftKey::Local({})); i != end(map)) {
i->second->cursor = MessageCursor(
localPosition,
localAnchor,
localScroll);
}
if (const auto i = map.find(Data::DraftKey::LocalEdit()); i != end(map)) {
if (const auto i = map.find(Data::DraftKey::LocalEdit({}))
; i != end(map)) {
i->second->cursor = MessageCursor(
editPosition,
editAnchor,
@ -1327,7 +1318,7 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
const auto guard = gsl::finally([&] {
if (const auto migrated = history->migrateFrom()) {
readDraftsWithCursors(migrated);
migrated->clearLocalEditDraft();
migrated->clearLocalEditDraft({});
history->takeLocalDraft(migrated);
}
});
@ -1396,10 +1387,11 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
const auto key = keysOld
? Data::DraftKey::FromSerializedOld(keyValueOld)
: Data::DraftKey::FromSerialized(keyValue);
if (key && key != Data::DraftKey::Cloud()) {
if (key && !key.isCloud()) {
map.emplace(key, std::make_unique<Data::Draft>(
data,
messageId,
key.topicRootId(),
MessageCursor(),
previewState));
}
@ -1457,24 +1449,31 @@ void Account::readDraftsWithCursorsLegacy(
editTagsSerialized,
editData.text.size());
const auto topicRootId = MsgId();
auto map = base::flat_map<Data::DraftKey, std::unique_ptr<Data::Draft>>();
if (!msgData.text.isEmpty() || msgReplyTo) {
map.emplace(Data::DraftKey::Local(), std::make_unique<Data::Draft>(
msgData,
msgReplyTo,
MessageCursor(),
(msgPreviewCancelled
? Data::PreviewState::Cancelled
: Data::PreviewState::Allowed)));
map.emplace(
Data::DraftKey::Local(topicRootId),
std::make_unique<Data::Draft>(
msgData,
msgReplyTo,
topicRootId,
MessageCursor(),
(msgPreviewCancelled
? Data::PreviewState::Cancelled
: Data::PreviewState::Allowed)));
}
if (editMsgId) {
map.emplace(Data::DraftKey::LocalEdit(), std::make_unique<Data::Draft>(
editData,
editMsgId,
MessageCursor(),
(editPreviewCancelled
? Data::PreviewState::Cancelled
: Data::PreviewState::Allowed)));
map.emplace(
Data::DraftKey::LocalEdit(topicRootId),
std::make_unique<Data::Draft>(
editData,
editMsgId,
topicRootId,
MessageCursor(),
(editPreviewCancelled
? Data::PreviewState::Cancelled
: Data::PreviewState::Allowed)));
}
readDraftCursors(peerId, map);
history->setDraftsMap(std::move(map));

View file

@ -47,6 +47,7 @@ namespace {
constexpr auto kOccupyFor = TimeId(60);
constexpr auto kReoccupyEach = 30 * crl::time(1000);
constexpr auto kMaxSupportInfoLength = MaxMessageSize * 4;
constexpr auto kTopicRootId = MsgId(0);
class EditInfoBox : public Ui::BoxContent {
public:
@ -157,7 +158,8 @@ Data::Draft OccupiedDraft(const QString &normalizedName) {
+ QString::number(OccupationTag())
+ ";n:"
+ normalizedName },
MsgId(0),
MsgId(0), // replyTo
kTopicRootId,
MessageCursor(),
Data::PreviewState::Allowed
};
@ -176,7 +178,7 @@ uint32 ParseOccupationTag(History *history) {
if (!TrackHistoryOccupation(history)) {
return 0;
}
const auto draft = history->cloudDraft();
const auto draft = history->cloudDraft(kTopicRootId);
if (!draft) {
return 0;
}
@ -202,7 +204,7 @@ QString ParseOccupationName(History *history) {
if (!TrackHistoryOccupation(history)) {
return QString();
}
const auto draft = history->cloudDraft();
const auto draft = history->cloudDraft(kTopicRootId);
if (!draft) {
return QString();
}
@ -228,7 +230,7 @@ TimeId OccupiedBySomeoneTill(History *history) {
if (!TrackHistoryOccupation(history)) {
return 0;
}
const auto draft = history->cloudDraft();
const auto draft = history->cloudDraft(kTopicRootId);
if (!draft) {
return 0;
}
@ -340,7 +342,7 @@ void Helper::updateOccupiedHistory(
not_null<Window::SessionController*> controller,
History *history) {
if (isOccupiedByMe(_occupiedHistory)) {
_occupiedHistory->clearCloudDraft();
_occupiedHistory->clearCloudDraft(kTopicRootId);
_session->api().saveDraftToCloudDelayed(_occupiedHistory);
}
_occupiedHistory = history;
@ -364,7 +366,7 @@ void Helper::occupyInDraft() {
&& !isOccupiedBySomeone(_occupiedHistory)
&& !_supportName.isEmpty()) {
const auto draft = OccupiedDraft(_supportNameNormalized);
_occupiedHistory->createCloudDraft(&draft);
_occupiedHistory->createCloudDraft(kTopicRootId, &draft);
_session->api().saveDraftToCloudDelayed(_occupiedHistory);
_reoccupyTimer.callEach(kReoccupyEach);
}
@ -373,7 +375,7 @@ void Helper::occupyInDraft() {
void Helper::reoccupy() {
if (isOccupiedByMe(_occupiedHistory)) {
const auto draft = OccupiedDraft(_supportNameNormalized);
_occupiedHistory->createCloudDraft(&draft);
_occupiedHistory->createCloudDraft(kTopicRootId, &draft);
_session->api().saveDraftToCloudDelayed(_occupiedHistory);
}
}

View file

@ -979,6 +979,10 @@ void Manager::notificationActivated(
const auto window = session->windows().front();
const auto history = session->data().history(
id.contextId.peerId);
const auto item = history->owner().message(
history->peer,
id.msgId);
const auto topic = item ? item->topic() : nullptr;
if (!reply.text.isEmpty()) {
// #TODO forum notifications
const auto replyToId = (id.msgId > 0
@ -988,6 +992,7 @@ void Manager::notificationActivated(
auto draft = std::make_unique<Data::Draft>(
reply,
replyToId,
(topic ? topic->rootId() : 0),
MessageCursor{
int(reply.text.size()),
int(reply.text.size()),
@ -1057,16 +1062,18 @@ void Manager::notificationReplied(
return;
}
const auto history = session->data().history(id.contextId.peerId);
const auto item = history->owner().message(history->peer, id.msgId);
const auto topic = item ? item->topic() : nullptr;
auto message = Api::MessageToSend(Api::SendAction(history));
message.textWithTags = reply;
message.action.replyTo = (id.msgId > 0 && !history->peer->isUser())
? id.msgId
: id.contextId.topicRootId;
message.action.topicRootId = topic ? topic->rootId() : 0;
message.action.clearDraft = false;
history->session().api().sendMessage(std::move(message));
const auto item = history->owner().message(history->peer, id.msgId);
if (item && item->isUnreadMention() && !item->isIncomingUnreadMedia()) {
history->session().api().markContentsRead(item);
}

View file

@ -844,14 +844,16 @@ void Filler::addCreatePoll() {
? SendMenu::Type::SilentOnly
: SendMenu::Type::Scheduled;
const auto flag = PollData::Flags();
const auto topicRootId = _request.rootId;
const auto replyToId = _request.currentReplyToId
? _request.currentReplyToId
: _request.rootId;
: topicRootId;
auto callback = [=] {
PeerMenuCreatePoll(
controller,
peer,
replyToId,
topicRootId,
flag,
flag,
source,
@ -1168,6 +1170,7 @@ void PeerMenuCreatePoll(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
MsgId replyToId,
MsgId topicRootId,
PollData::Flags chosen,
PollData::Flags disabled,
Api::SendType sendType,
@ -1194,8 +1197,9 @@ void PeerMenuCreatePoll(
result.options);
action.clearDraft = false;
action.replyTo = replyToId;
if (const auto localDraft = action.history->localDraft()) {
action.clearDraft = localDraft->textWithTags.text.isEmpty();
action.topicRootId = topicRootId;
if (const auto local = action.history->localDraft(topicRootId)) {
action.clearDraft = local->textWithTags.text.isEmpty();
}
const auto api = &peer->session().api();
api->polls().create(result.poll, action, crl::guard(weak, [=] {

View file

@ -69,6 +69,7 @@ void PeerMenuCreatePoll(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
MsgId replyToId = 0,
MsgId topicRootId = 0,
PollData::Flags chosen = PollData::Flags(),
PollData::Flags disabled = PollData::Flags(),
Api::SendType sendType = Api::SendType::Normal,

@ -1 +1 @@
Subproject commit 9ab11ccb36b7d03a3a24ebcd18d2f13b03fc3682
Subproject commit d8b1f46715e5fcaf781b76ecbc386cbe31492287