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; 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; 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; 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; updateReadFeaturedStickers#571d2742 = Update;
updateRecentStickers#9a422c20 = Update; updateRecentStickers#9a422c20 = Update;
updateConfig#a229dd06 = 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; updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = Update;
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = 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; 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; updateAttachMenuBots#17b7a20b = Update;
updateWebViewResultSent#1592b79d query_id:long = Update; updateWebViewResultSent#1592b79d query_id:long = Update;
updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
@ -593,6 +593,7 @@ inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet; inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet;
inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet;
inputStickerSetEmojiDefaultStatuses#29d0f5ee = 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; 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; channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction; channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeUsernames#f04fb3a9 prev_value:Vector<string> new_value:Vector<string> = 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; 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; 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; 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.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
account.resetPasswordOk#e926d63e = 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; 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; username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username;
forumTopicDeleted#23f109b id:int = ForumTopic; 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; 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.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>; messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; 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.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#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.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.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.reportSpam#cf1592db peer:InputPeer = Bool;
messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; 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.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.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.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.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.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.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.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.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.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.getAllDrafts#6a3f8d65 = Updates;
messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers;
messages.readFeaturedStickers#5b118126 id:Vector<long> = Bool; 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.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.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.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.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
messages.getSplitRanges#1cff7e08 = Vector<MessageRange>; 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.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool; channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool;
channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = 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.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.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; 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: { case ButtonType::RequestPhone: {
HideSingleUseKeyboard(controller, item); HideSingleUseKeyboard(controller, item);
const auto itemId = item->id; const auto itemId = item->id;
const auto topicRootId = item->topicRootId();
const auto history = item->history(); const auto history = item->history();
controller->show(Ui::MakeConfirmBox({ controller->show(Ui::MakeConfirmBox({
.text = tr::lng_bot_share_phone(), .text = tr::lng_bot_share_phone(),
@ -362,6 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
auto action = Api::SendAction(history); auto action = Api::SendAction(history);
action.clearDraft = false; action.clearDraft = false;
action.replyTo = itemId; action.replyTo = itemId;
action.topicRootId = topicRootId;
history->session().api().shareContact( history->session().api().shareContact(
history->session().user(), history->session().user(),
action); action);
@ -381,10 +383,12 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
} }
} }
const auto replyToId = MsgId(0); const auto replyToId = MsgId(0);
const auto topicRootId = MsgId(0);
Window::PeerMenuCreatePoll( Window::PeerMenuCreatePoll(
controller, controller,
item->history()->peer, item->history()->peer,
replyToId, replyToId,
topicRootId,
chosen, chosen,
disabled); disabled);
} break; } break;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -151,8 +151,9 @@ struct TopicUpdate {
Notifications = (1U << 4), Notifications = (1U << 4),
Title = (1U << 5), Title = (1U << 5),
Icon = (1U << 6), Icon = (1U << 6),
CloudDraft = (1U << 7),
LastUsedBit = (1U << 4), LastUsedBit = (1U << 7),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } 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 { 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( Draft::Draft(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
MsgId msgId, MsgId msgId,
MsgId topicRootId,
const MessageCursor &cursor, const MessageCursor &cursor,
PreviewState previewState, PreviewState previewState,
mtpRequestId saveRequestId) mtpRequestId saveRequestId)
: textWithTags(textWithTags) : textWithTags(textWithTags)
, msgId(msgId) , msgId(msgId)
, topicRootId(topicRootId)
, cursor(cursor) , cursor(cursor)
, previewState(previewState) , previewState(previewState)
, saveRequestId(saveRequestId) { , saveRequestId(saveRequestId) {
@ -50,10 +37,12 @@ Draft::Draft(
Draft::Draft( Draft::Draft(
not_null<const Ui::InputField*> field, not_null<const Ui::InputField*> field,
MsgId msgId, MsgId msgId,
MsgId topicRootId,
PreviewState previewState, PreviewState previewState,
mtpRequestId saveRequestId) mtpRequestId saveRequestId)
: textWithTags(field->getTextWithTags()) : textWithTags(field->getTextWithTags())
, msgId(msgId) , msgId(msgId)
, topicRootId(topicRootId)
, cursor(field) , cursor(field)
, previewState(previewState) { , previewState(previewState) {
} }
@ -61,10 +50,11 @@ Draft::Draft(
void ApplyPeerCloudDraft( void ApplyPeerCloudDraft(
not_null<Main::Session*> session, not_null<Main::Session*> session,
PeerId peerId, PeerId peerId,
MsgId topicRootId,
const MTPDdraftMessage &draft) { const MTPDdraftMessage &draft) {
const auto history = session->data().history(peerId); const auto history = session->data().history(peerId);
const auto date = draft.vdate().v; const auto date = draft.vdate().v;
if (history->skipCloudDraftUpdate(date)) { if (history->skipCloudDraftUpdate(topicRootId, date)) {
return; return;
} }
const auto textWithTags = TextWithTags{ const auto textWithTags = TextWithTags{
@ -78,6 +68,7 @@ void ApplyPeerCloudDraft(
auto cloudDraft = std::make_unique<Draft>( auto cloudDraft = std::make_unique<Draft>(
textWithTags, textWithTags,
replyTo, replyTo,
topicRootId,
MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX),
(draft.is_no_webpage() (draft.is_no_webpage()
? Data::PreviewState::Cancelled ? Data::PreviewState::Cancelled
@ -85,20 +76,21 @@ void ApplyPeerCloudDraft(
cloudDraft->date = date; cloudDraft->date = date;
history->setCloudDraft(std::move(cloudDraft)); history->setCloudDraft(std::move(cloudDraft));
history->applyCloudDraft(); history->applyCloudDraft(topicRootId);
} }
void ClearPeerCloudDraft( void ClearPeerCloudDraft(
not_null<Main::Session*> session, not_null<Main::Session*> session,
PeerId peerId, PeerId peerId,
MsgId topicRootId,
TimeId date) { TimeId date) {
const auto history = session->data().history(peerId); const auto history = session->data().history(peerId);
if (history->skipCloudDraftUpdate(date)) { if (history->skipCloudDraftUpdate(topicRootId, date)) {
return; return;
} }
history->clearCloudDraft(); history->clearCloudDraft(topicRootId);
history->applyCloudDraft(); history->applyCloudDraft(topicRootId);
} }
} // namespace Data } // namespace Data

View file

@ -20,10 +20,12 @@ namespace Data {
void ApplyPeerCloudDraft( void ApplyPeerCloudDraft(
not_null<Main::Session*> session, not_null<Main::Session*> session,
PeerId peerId, PeerId peerId,
MsgId topicRootId,
const MTPDdraftMessage &draft); const MTPDdraftMessage &draft);
void ClearPeerCloudDraft( void ClearPeerCloudDraft(
not_null<Main::Session*> session, not_null<Main::Session*> session,
PeerId peerId, PeerId peerId,
MsgId topicRootId,
TimeId date); TimeId date);
enum class PreviewState : char { enum class PreviewState : char {
@ -37,18 +39,21 @@ struct Draft {
Draft( Draft(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
MsgId msgId, MsgId msgId,
MsgId topicRootId,
const MessageCursor &cursor, const MessageCursor &cursor,
PreviewState previewState, PreviewState previewState,
mtpRequestId saveRequestId = 0); mtpRequestId saveRequestId = 0);
Draft( Draft(
not_null<const Ui::InputField*> field, not_null<const Ui::InputField*> field,
MsgId msgId, MsgId msgId,
MsgId topicRootId,
PreviewState previewState, PreviewState previewState,
mtpRequestId saveRequestId = 0); mtpRequestId saveRequestId = 0);
TimeId date = 0; TimeId date = 0;
TextWithTags textWithTags; TextWithTags textWithTags;
MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft
MsgId topicRootId = 0;
MessageCursor cursor; MessageCursor cursor;
PreviewState previewState = PreviewState::Allowed; PreviewState previewState = PreviewState::Allowed;
mtpRequestId saveRequestId = 0; mtpRequestId saveRequestId = 0;
@ -56,70 +61,93 @@ struct Draft {
class DraftKey { class DraftKey {
public: public:
[[nodiscard]] static DraftKey None() { [[nodiscard]] static constexpr DraftKey None() {
return 0; return 0;
} }
[[nodiscard]] static DraftKey Local() { [[nodiscard]] static constexpr DraftKey Local(MsgId topicRootId) {
return kLocalDraftIndex; return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
? None()
: (topicRootId ? topicRootId.bare : kLocalDraftIndex);
} }
[[nodiscard]] static DraftKey LocalEdit() { [[nodiscard]] static constexpr DraftKey LocalEdit(MsgId topicRootId) {
return kLocalDraftIndex + kEditDraftShift; return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
? None()
: ((topicRootId ? topicRootId.bare : kLocalDraftIndex)
+ kEditDraftShift);
} }
[[nodiscard]] static DraftKey Cloud() { [[nodiscard]] static constexpr DraftKey Cloud(MsgId topicRootId) {
return kCloudDraftIndex; return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
? None()
: topicRootId
? (kCloudDraftShift + topicRootId.bare)
: kCloudDraftIndex;
} }
[[nodiscard]] static DraftKey Scheduled() { [[nodiscard]] static constexpr DraftKey Scheduled() {
return kScheduledDraftIndex; return kScheduledDraftIndex;
} }
[[nodiscard]] static DraftKey ScheduledEdit() { [[nodiscard]] static constexpr DraftKey ScheduledEdit() {
return kScheduledDraftIndex + kEditDraftShift; 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; return value;
} }
[[nodiscard]] qint64 serialize() const { [[nodiscard]] constexpr qint64 serialize() const {
return _value; 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 { [[nodiscard]] constexpr MsgId topicRootId() const {
return _value < other._value; const auto max = ServerMaxMsgId.bare;
} if (_value > kCloudDraftShift && _value < kCloudDraftShift + max) {
inline bool operator==(const DraftKey &other) const { return (_value - kCloudDraftShift);
return _value == other._value; } else if (_value > kEditDraftShift && _value < kEditDraftShift + max) {
} return (_value - kEditDraftShift);
inline bool operator>(const DraftKey &other) const { } else if (_value > 0 && _value < max) {
return (other < *this); return _value;
} }
inline bool operator<=(const DraftKey &other) const { return 0;
return !(other < *this);
}
inline bool operator>=(const DraftKey &other) const {
return !(*this < other);
}
inline bool operator!=(const DraftKey &other) const {
return !(*this == other);
} }
friend inline constexpr auto operator<=>(DraftKey, DraftKey) = default;
inline explicit operator bool() const { inline explicit operator bool() const {
return _value != 0; return _value != 0;
} }
private: private:
DraftKey(int64 value) : _value(value) { constexpr DraftKey(int64 value) : _value(value) {
} }
static constexpr auto kLocalDraftIndex = -1; static constexpr auto kLocalDraftIndex = -1;
static constexpr auto kCloudDraftIndex = -2; static constexpr auto kCloudDraftIndex = -2;
static constexpr auto kScheduledDraftIndex = -3; static constexpr auto kScheduledDraftIndex = -3;
static constexpr auto kEditDraftShift = ServerMaxMsgId.bare; static constexpr auto kEditDraftShift = ServerMaxMsgId.bare;
static constexpr auto kCloudDraftShift = 2 * ServerMaxMsgId.bare;
static constexpr auto kEditDraftShiftOld = 0x3FFF'FFFF; static constexpr auto kEditDraftShiftOld = 0x3FFF'FFFF;
int64 _value = 0; int64 _value = 0;
@ -128,7 +156,7 @@ private:
using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>; 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) { for (const auto &ch : text) {
if (!ch.isSpace()) { if (!ch.isSpace()) {
return false; return false;
@ -137,20 +165,19 @@ inline bool draftStringIsEmpty(const QString &text) {
return true; return true;
} }
inline bool draftIsNull(const Draft *draft) { [[nodiscard]] inline bool DraftIsNull(const Draft *draft) {
return !draft return !draft
|| (draftStringIsEmpty(draft->textWithTags.text) && !draft->msgId); || (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text));
} }
inline bool draftsAreEqual(const Draft *a, const Draft *b) { [[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) {
bool aIsNull = draftIsNull(a); const auto aIsNull = DraftIsNull(a);
bool bIsNull = draftIsNull(b); const auto bIsNull = DraftIsNull(b);
if (aIsNull) { if (aIsNull) {
return bIsNull; return bIsNull;
} else if (bIsNull) { } else if (bIsNull) {
return false; return false;
} }
return (a->textWithTags == b->textWithTags) return (a->textWithTags == b->textWithTags)
&& (a->msgId == b->msgId) && (a->msgId == b->msgId)
&& (a->previewState == b->previewState); && (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_forum.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_replies_list.h" #include "data/data_replies_list.h"
#include "data/data_send_action.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
@ -142,10 +143,15 @@ ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
, _forum(forum) , _forum(forum)
, _list(_forum->topicsList()) , _list(_forum->topicsList())
, _replies(std::make_shared<RepliesList>(history(), rootId)) , _replies(std::make_shared<RepliesList>(history(), rootId))
, _sendActionPainter(owner().sendActionManager().repliesPainter(
history(),
rootId))
, _rootId(rootId) , _rootId(rootId)
, _lastKnownServerMessageId(rootId) { , _lastKnownServerMessageId(rootId) {
Thread::setMuted(owner().notifySettings().isMuted(this)); Thread::setMuted(owner().notifySettings().isMuted(this));
_sendActionPainter->setTopic(this);
_replies->unreadCountValue( _replies->unreadCountValue(
) | rpl::combine_previous( ) | rpl::combine_previous(
) | rpl::filter([=] { ) | rpl::filter([=] {
@ -161,6 +167,7 @@ ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
} }
ForumTopic::~ForumTopic() { ForumTopic::~ForumTopic() {
_sendActionPainter->setTopic(nullptr);
session().api().unreadThings().cancelRequests(this); session().api().unreadThings().cancelRequests(this);
} }
@ -191,11 +198,24 @@ MsgId ForumTopic::rootId() const {
return _rootId; return _rootId;
} }
bool ForumTopic::creating() const {
return _forum->creating(_rootId);
}
void ForumTopic::discard() {
Expects(creating());
_forum->discardCreatingId(_rootId);
}
void ForumTopic::setRealRootId(MsgId realId) { void ForumTopic::setRealRootId(MsgId realId) {
if (_rootId != realId) { if (_rootId != realId) {
_rootId = realId; _rootId = realId;
_lastKnownServerMessageId = realId; _lastKnownServerMessageId = realId;
_replies = std::make_shared<RepliesList>(history(), _rootId); _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()); 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( _replies->setInboxReadTill(
data.vread_inbox_max_id().v, data.vread_inbox_max_id().v,
data.vunread_count().v); data.vunread_count().v);
@ -390,13 +419,11 @@ void ForumTopic::requestChatListMessage() {
TimeId ForumTopic::adjustedChatListTimeId() const { TimeId ForumTopic::adjustedChatListTimeId() const {
const auto result = chatListTimeId(); const auto result = chatListTimeId();
#if 0 // #TODO forum draft if (const auto draft = history()->cloudDraft(_rootId)) {
if (const auto draft = cloudDraft()) { if (!Data::DraftIsNull(draft) && !session().supportMode()) {
if (!Data::draftIsNull(draft) && !session().supportMode()) {
return std::max(result, draft->date); return std::max(result, draft->date);
} }
} }
#endif
return result; return result;
} }
@ -480,6 +507,17 @@ void ForumTopic::applyItemAdded(not_null<HistoryItem*> item) {
setLastMessage(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) { void ForumTopic::applyItemRemoved(MsgId id) {
if (const auto lastItem = lastMessage()) { if (const auto lastItem = lastMessage()) {
if (lastItem->id == id) { 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 { int ForumTopic::unreadCount() const {
return _replies->unreadCountCurrent(); return _replies->unreadCountCurrent();
} }
@ -544,6 +587,10 @@ void ForumTopic::setUnreadMark(bool unread) {
Thread::setUnreadMark(unread); Thread::setUnreadMark(unread);
} }
not_null<HistoryView::SendActionPainter*> ForumTopic::sendActionPainter() {
return _sendActionPainter.get();
}
int ForumTopic::chatListUnreadCount() const { int ForumTopic::chatListUnreadCount() const {
const auto state = chatListUnreadState(); const auto state = chatListUnreadState();
return state.marks return state.marks

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -185,7 +185,7 @@ InnerWidget::InnerWidget(
width(), width(),
update.textUpdated); update.textUpdated);
updateDialogRow( updateDialogRow(
RowDescriptor(update.history, FullMsgId()), RowDescriptor(update.thread, FullMsgId()),
updateRect, updateRect,
UpdateRowSection::Default | UpdateRowSection::Filtered); UpdateRowSection::Default | UpdateRowSection::Filtered);
}, lifetime()); }, lifetime());
@ -2394,8 +2394,10 @@ void InnerWidget::refreshEmptyLabel() {
const auto data = &session().data(); const auto data = &session().data();
const auto state = !shownDialogs()->empty() const auto state = !shownDialogs()->empty()
? EmptyState::None ? EmptyState::None
: (_openedForum && _openedForum->topicsList()->loaded()) : _openedForum
? EmptyState::EmptyForum ? (_openedForum->topicsList()->loaded()
? EmptyState::EmptyForum
: EmptyState::Loading)
: (!_filterId && data->contactsLoaded().current()) : (!_filterId && data->contactsLoaded().current())
? EmptyState::NoContacts ? EmptyState::NoContacts
: (_filterId > 0) && data->chatsList()->loaded() : (_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::Folder *folder) : _value(folder) {
} }
Key::Key(Data::Thread *thread) : _value(thread) {
}
Key::Key(Data::ForumTopic *topic) : _value(topic) { Key::Key(Data::ForumTopic *topic) : _value(topic) {
} }
Key::Key(not_null<History*> history) : _value(history) { 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) { Key::Key(not_null<Data::Folder*> folder) : _value(folder) {
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -645,7 +645,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
if (forwarded && forwarded->imported) { if (forwarded && forwarded->imported) {
result.flags |= Flag::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()) { //if (item->unread()) {
// result.flags |= Flag::Unread; // result.flags |= Flag::Unread;
//} //}

View file

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

View file

@ -351,8 +351,8 @@ RepliesWidget::RepliesWidget(
} }
RepliesWidget::~RepliesWidget() { RepliesWidget::~RepliesWidget() {
if (_topic && _topic->forum()->creating(_rootId)) { if (_topic && _topic->creating()) {
_topic->forum()->discardCreatingId(_rootId); _topic->discard();
_topic = nullptr; _topic = nullptr;
} }
base::take(_sendAction); base::take(_sendAction);
@ -434,15 +434,20 @@ void RepliesWidget::setupRootView() {
} }
void RepliesWidget::setupTopicViewer() { void RepliesWidget::setupTopicViewer() {
_history->owner().itemIdChanged( const auto owner = &_history->owner();
owner->itemIdChanged(
) | rpl::start_with_next([=](const Data::Session::IdChange &change) { ) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
if (_rootId == change.oldId) { if (_rootId == change.oldId) {
_rootId = change.newId.msg; _rootId = change.newId.msg;
_sendAction = owner->sendActionManager().repliesPainter(
_history,
_rootId);
_root = lookupRoot(); _root = lookupRoot();
if (_topic && _topic->rootId() == change.oldId) { if (_topic && _topic->rootId() == change.oldId) {
setTopic(_topic->forum()->topicFor(change.newId.msg)); setTopic(_topic->forum()->topicFor(change.newId.msg));
} else { } else {
refreshReplies(); refreshReplies();
refreshTopBarActiveChat();
} }
_inner->update(); _inner->update();
} }
@ -1007,6 +1012,7 @@ Api::SendAction RepliesWidget::prepareSendAction(
Api::SendOptions options) const { Api::SendOptions options) const {
auto result = Api::SendAction(_history, options); auto result = Api::SendAction(_history, options);
result.replyTo = replyToId(); result.replyTo = replyToId();
result.topicRootId = _rootId;
result.options.sendAs = _composeControls->sendAsPeer(); result.options.sendAs = _composeControls->sendAsPeer();
return result; return result;
} }
@ -1440,7 +1446,7 @@ bool RepliesWidget::preventsClose(Fn<void()> &&continueCallback) const {
return true; return true;
} else if (!_newTopicDiscarded } else if (!_newTopicDiscarded
&& _topic && _topic
&& _topic->forum()->creating(_rootId)) { && _topic->creating()) {
const auto weak = Ui::MakeWeak(this); const auto weak = Ui::MakeWeak(this);
auto sure = [=](Fn<void()> &&close) { auto sure = [=](Fn<void()> &&close) {
if (const auto strong = weak.data()) { 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) { bool RepliesWidget::listElementShownUnread(not_null<const Element*> view) {
if (!_root) { return _replies->isServerSideUnread(view->data());
return false;
}
const auto item = view->data();
const auto till = item->out()
? _replies->computeOutboxReadTillFull()
: _replies->computeInboxReadTillFull();
return (item->id > till);
} }
bool RepliesWidget::listIsGoodForAroundPosition( 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_user.h"
#include "data/data_send_action.h" #include "data/data_send_action.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "history/history.h" #include "history/history.h"
@ -39,13 +40,20 @@ constexpr auto kStatusShowClientsideSpeaking = 6 * crl::time(1000);
} // namespace } // namespace
SendActionPainter::SendActionPainter(not_null<History*> history) SendActionPainter::SendActionPainter(
not_null<History*> history,
MsgId rootId)
: _history(history) : _history(history)
, _rootId(rootId)
, _weak(&_history->session()) , _weak(&_history->session())
, _st(st::dialogsTextStyle) , _st(st::dialogsTextStyle)
, _sendActionText(st::dialogsTextWidthMin) { , _sendActionText(st::dialogsTextWidthMin) {
} }
void SendActionPainter::setTopic(Data::ForumTopic *topic) {
_topic = topic;
}
bool SendActionPainter::updateNeedsAnimating( bool SendActionPainter::updateNeedsAnimating(
not_null<UserData*> user, not_null<UserData*> user,
const MTPSendMessageAction &action) { const MTPSendMessageAction &action) {
@ -382,7 +390,7 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
st::normalFont->height, st::normalFont->height,
st::dialogsMiniPreviewTop + st::dialogsMiniPreview); st::dialogsMiniPreviewTop + st::dialogsMiniPreview);
_history->peer->owner().sendActionManager().updateAnimation({ _history->peer->owner().sendActionManager().updateAnimation({
_history, _topic ? ((Data::Thread*)_topic) : _history,
0, 0,
_sendActionAnimation.width() + _animationLeft, _sendActionAnimation.width() + _animationLeft,
height, height,

View file

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

View file

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

View file

@ -204,29 +204,35 @@ void Controller::setupMigrationViewer() {
) | rpl::filter([=] { ) | rpl::filter([=] {
return peer->migrateTo() || (peer->migrateFrom() != _migrated); return peer->migrateTo() || (peer->migrateFrom() != _migrated);
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=] {
const auto window = parentController(); replaceWith(std::make_shared<Memento>(peer, _section));
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);
});
}, lifetime()); }, 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() { void Controller::setupTopicViewer() {
session().data().itemIdChanged( session().data().itemIdChanged(
) | rpl::start_with_next([=](const Data::Session::IdChange &change) { ) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
if (const auto topic = _key.topic()) { if (const auto topic = _key.topic()) {
if (topic->rootId() == change.oldId) { if (topic->rootId() == change.oldId
_key = Key(topic->forum()->topicFor(change.newId.msg)); || (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); }, _lifetime);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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