diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index a5d651135..1fd39b08e 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -336,7 +336,7 @@ updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update; -updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; +updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; updateRecentStickers#9a422c20 = Update; updateConfig#a229dd06 = Update; @@ -389,7 +389,7 @@ updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJ updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = Update; updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update; updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update; -updateMessageReactions#154798c3 peer:Peer msg_id:int reactions:MessageReactions = Update; +updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update; updateAttachMenuBots#17b7a20b = Update; updateWebViewResultSent#1592b79d query_id:long = Update; updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; @@ -593,6 +593,7 @@ inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet; inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet; inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; +inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet; stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; @@ -958,12 +959,17 @@ channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdmi channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction; channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction; channelAdminLogEventActionChangeUsernames#f04fb3a9 prev_value:Vector new_value:Vector = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleForum#2cc6383 new_value:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction; +channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic = ChannelAdminLogEventAction; +channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction; +channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; -channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true = ChannelAdminLogEventsFilter; +channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true forums:flags.17?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; @@ -1322,7 +1328,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#3a836df8 flags:# recommended:flags.5?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = 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 = SponsoredMessage; messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; @@ -1459,7 +1465,7 @@ stickerKeyword#fcfeb29c document_id:long keyword:Vector = StickerKeyword username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username; forumTopicDeleted#23f109b id:int = ForumTopic; -forumTopic#5920d6dc flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings = ForumTopic; +forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic; messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector messages:Vector chats:Vector users:Vector 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 = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; 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 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 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 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 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 random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; @@ -1656,14 +1662,14 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; -messages.sendInlineBotResult#7aa11297 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendInlineBotResult#15b11c73 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.8?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector 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 = Bool; messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = 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 = 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 = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -1692,7 +1698,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector 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 schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -1865,6 +1871,7 @@ channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates; channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates; channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector = Bool; channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool; +channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool; channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates; channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates; channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics; diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 03883c946..19ef1c06a 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -351,6 +351,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::RequestPhone: { HideSingleUseKeyboard(controller, item); const auto itemId = item->id; + const auto topicRootId = item->topicRootId(); const auto history = item->history(); controller->show(Ui::MakeConfirmBox({ .text = tr::lng_bot_share_phone(), @@ -362,6 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { auto action = Api::SendAction(history); action.clearDraft = false; action.replyTo = itemId; + action.topicRootId = topicRootId; history->session().api().shareContact( history->session().user(), action); @@ -381,10 +383,12 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { } } const auto replyToId = MsgId(0); + const auto topicRootId = MsgId(0); Window::PeerMenuCreatePoll( controller, item->history()->peer, replyToId, + topicRootId, chosen, disabled); } break; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 6e2ab6e80..65b3e7bce 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -37,6 +37,7 @@ struct SendAction { not_null history; SendOptions options; MsgId replyTo = 0; + MsgId topicRootId = 0; bool clearDraft = true; bool generateLocal = true; MsgId replaceMediaOf = 0; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 4d4727470..85f34e2c2 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -42,16 +42,20 @@ void Polls::create( const auto history = action.history; const auto peer = history->peer; + const auto topicRootId = action.replyTo ? action.topicRootId : 0; auto sendFlags = MTPmessages_SendMedia::Flags(0); if (action.replyTo) { sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; + if (topicRootId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_top_msg_id; + } } const auto clearCloudDraft = action.clearDraft; if (clearCloudDraft) { sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft; - history->clearLocalDraft(); - history->clearCloudDraft(); - history->startSavingCloudDraft(); + history->clearLocalDraft(topicRootId); + history->clearCloudDraft(topicRootId); + history->startSavingCloudDraft(topicRootId); } const auto silentPost = ShouldSendSilent(peer, action.options); if (silentPost) { @@ -65,16 +69,17 @@ void Polls::create( sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; } auto &histories = history->owner().histories(); - const auto replyTo = action.replyTo; const auto randomId = base::RandomValue(); histories.sendPreparedMessage( history, - replyTo, + action.replyTo, + topicRootId, randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), PollDataToInputMedia(&data), MTP_string(), MTP_long(randomId), @@ -85,6 +90,7 @@ void Polls::create( ), [=](const MTPUpdates &result, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( + topicRootId, UnixtimeFromMsgId(response.outerMsgId)); } _session->changes().historyUpdated( @@ -96,6 +102,7 @@ void Polls::create( }, [=](const MTP::Error &error, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( + topicRootId, UnixtimeFromMsgId(response.outerMsgId)); } fail(); diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 791bd2166..64d28c770 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -86,6 +86,9 @@ void SendExistingMedia( if (message.action.replyTo) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; + if (message.action.topicRootId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_top_msg_id; + } } const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, message.action.options); @@ -118,7 +121,6 @@ void SendExistingMedia( if (!sentEntities.v.isEmpty()) { sendFlags |= MTPmessages_SendMedia::Flag::f_entities; } - const auto replyTo = message.action.replyTo; const auto captionText = caption.text; if (message.action.options.scheduled) { @@ -133,7 +135,7 @@ void SendExistingMedia( newId.msg, flags, viaBotId, - replyTo, + message.action.replyTo, HistoryItem::NewMessageDate(message.action.options.scheduled), messageFromId, messagePostAuthor, @@ -146,12 +148,14 @@ void SendExistingMedia( const auto usedFileReference = media->fileReference(); histories.sendPreparedMessage( history, - replyTo, + message.action.replyTo, + message.action.topicRootId, randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), inputMedia(), MTP_string(captionText), MTP_long(randomId), @@ -269,6 +273,9 @@ bool SendDice(MessageToSend &message) { if (message.action.replyTo) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; + if (message.action.topicRootId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_top_msg_id; + } } const auto replyHeader = NewMessageReplyHeader(message.action); const auto anonymousPost = peer->amAnonymous(); @@ -289,7 +296,6 @@ bool SendDice(MessageToSend &message) { const auto messagePostAuthor = peer->isBroadcast() ? session->user()->name() : QString(); - const auto replyTo = message.action.replyTo; if (message.action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -312,12 +318,14 @@ bool SendDice(MessageToSend &message) { HistoryMessageMarkupData()); histories.sendPreparedMessage( history, - replyTo, + message.action.replyTo, + message.action.topicRootId, randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), MTP_inputMediaDice(MTP_string(emoji)), MTP_string(), MTP_long(randomId), @@ -368,9 +376,13 @@ void SendConfirmedFile( const auto peer = history->peer; if (!isEditing) { - file->to.replyTo = session->data().histories().convertTopicReplyTo( + const auto histories = &session->data().histories(); + file->to.replyTo = histories->convertTopicReplyTo( history, file->to.replyTo); + file->to.topicRootId = histories->convertTopicReplyTo( + history, + file->to.topicRootId); } session->uploader().upload(newId, file); @@ -378,6 +390,7 @@ void SendConfirmedFile( auto action = SendAction(history, file->to.options); action.clearDraft = false; action.replyTo = file->to.replyTo; + action.topicRootId = file->to.topicRootId; action.generateLocal = true; session->api().sendAction(action); diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index b759af4ba..07e3ce501 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2443,12 +2443,14 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateDraftMessage: { const auto &data = update.c_updateDraftMessage(); const auto peerId = peerFromMTP(data.vpeer()); + const auto topicRootId = data.vtop_msg_id().value_or_empty(); data.vdraft().match([&](const MTPDdraftMessage &data) { - Data::ApplyPeerCloudDraft(&session(), peerId, data); + Data::ApplyPeerCloudDraft(&session(), peerId, topicRootId, data); }, [&](const MTPDdraftMessageEmpty &data) { Data::ClearPeerCloudDraft( &session(), peerId, + topicRootId, data.vdate().value_or_empty()); }); } break; diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index a802787ff..26a99ce1f 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -562,7 +562,7 @@ bool WhoReadExists(not_null item) { } const auto type = DetectSeenType(item); const auto unseen = (type == Ui::WhoReadType::Seen) - ? item->unread() + ? item->unread(item->history()) : item->isUnreadMedia(); if (unseen) { return false; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 214443d67..248dd230f 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1812,8 +1812,8 @@ void ApiWrap::sendNotifySettingsUpdates() { session().mtp().sendAnything(); } -void ApiWrap::saveDraftToCloudDelayed(not_null history) { - _draftsSaveRequestIds.emplace(history, 0); +void ApiWrap::saveDraftToCloudDelayed(not_null thread) { + _draftsSaveRequestIds.emplace(base::make_weak(thread.get()), 0); if (!_draftsSaveTimer.isActive()) { _draftsSaveTimer.callOnce(kSaveCloudDraftTimeout); } @@ -2004,33 +2004,46 @@ void ApiWrap::saveCurrentDraftToCloud() { Core::App().saveCurrentDraftsToHistories(); for (const auto &controller : _session->windows()) { - if (const auto history = controller->activeChatCurrent().history()) { + if (const auto thread = controller->activeChatCurrent().thread()) { + const auto history = thread->owningHistory(); _session->local().writeDrafts(history); - const auto localDraft = history->localDraft(); - const auto cloudDraft = history->cloudDraft(); - if (!Data::draftsAreEqual(localDraft, cloudDraft) + const auto topicRootId = thread->topicRootId(); + const auto localDraft = history->localDraft(topicRootId); + const auto cloudDraft = history->cloudDraft(topicRootId); + if (!Data::DraftsAreEqual(localDraft, cloudDraft) && !_session->supportMode()) { - saveDraftToCloudDelayed(history); + saveDraftToCloudDelayed(thread); } } } } void ApiWrap::saveDraftsToCloud() { - for (auto i = _draftsSaveRequestIds.begin(), e = _draftsSaveRequestIds.end(); i != e; ++i) { - if (i->second) continue; // sent already + for (auto i = begin(_draftsSaveRequestIds); i != end(_draftsSaveRequestIds);) { + const auto weak = i->first; + const auto thread = weak.get(); + if (!thread) { + i = _draftsSaveRequestIds.erase(i); + continue; + } + auto &requestId = i->second; + ++i; + if (requestId) { + continue; // sent already + } - auto history = i->first; - auto cloudDraft = history->cloudDraft(); - auto localDraft = history->localDraft(); + const auto history = thread->owningHistory(); + const auto topicRootId = thread->topicRootId(); + auto cloudDraft = history->cloudDraft(topicRootId); + auto localDraft = history->localDraft(topicRootId); if (cloudDraft && cloudDraft->saveRequestId) { request(base::take(cloudDraft->saveRequestId)).cancel(); } if (!_session->supportMode()) { - cloudDraft = history->createCloudDraft(localDraft); + cloudDraft = history->createCloudDraft(topicRootId, localDraft); } else if (!cloudDraft) { - cloudDraft = history->createCloudDraft(nullptr); + cloudDraft = history->createCloudDraft(topicRootId, nullptr); } auto flags = MTPmessages_SaveDraft::Flags(0); @@ -2040,6 +2053,9 @@ void ApiWrap::saveDraftsToCloud() { } if (cloudDraft->msgId) { flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id; + if (cloudDraft->topicRootId) { + flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id; + } } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; @@ -2049,44 +2065,45 @@ void ApiWrap::saveDraftsToCloud() { TextUtilities::ConvertTextTagsToEntities(textWithTags.tags), Api::ConvertOption::SkipLocal); - history->startSavingCloudDraft(); + history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), MTP_int(cloudDraft->msgId), + MTP_int(cloudDraft->topicRootId), history->peer->input, MTP_string(textWithTags.text), entities )).done([=](const MTPBool &result, const MTP::Response &response) { - history->finishSavingCloudDraft( - UnixtimeFromMsgId(response.outerMsgId)); - const auto requestId = response.requestId; - if (const auto cloudDraft = history->cloudDraft()) { + history->finishSavingCloudDraft( + topicRootId, + UnixtimeFromMsgId(response.outerMsgId)); + if (const auto cloudDraft = history->cloudDraft(topicRootId)) { if (cloudDraft->saveRequestId == requestId) { cloudDraft->saveRequestId = 0; - history->draftSavedToCloud(); + history->draftSavedToCloud(topicRootId); } } - auto i = _draftsSaveRequestIds.find(history); + const auto i = _draftsSaveRequestIds.find(weak); if (i != _draftsSaveRequestIds.cend() && i->second == requestId) { - _draftsSaveRequestIds.erase(history); + _draftsSaveRequestIds.erase(i); checkQuitPreventFinished(); } }).fail([=](const MTP::Error &error, const MTP::Response &response) { - history->finishSavingCloudDraft( - UnixtimeFromMsgId(response.outerMsgId)); - const auto requestId = response.requestId; - if (const auto cloudDraft = history->cloudDraft()) { + history->finishSavingCloudDraft( + topicRootId, + UnixtimeFromMsgId(response.outerMsgId)); + if (const auto cloudDraft = history->cloudDraft(topicRootId)) { if (cloudDraft->saveRequestId == requestId) { - history->clearCloudDraft(); + history->clearCloudDraft(topicRootId); } } - auto i = _draftsSaveRequestIds.find(history); + const auto i = _draftsSaveRequestIds.find(weak); if (i != _draftsSaveRequestIds.cend() && i->second == requestId) { - _draftsSaveRequestIds.erase(history); + _draftsSaveRequestIds.erase(i); checkQuitPreventFinished(); } }).send(); @@ -3420,6 +3437,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) { if (action.replyTo) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id; + if (action.topicRootId) { + sendFlags |= MTPmessages_SendMessage::Flag::f_top_msg_id; + } } const auto replyHeader = NewMessageReplyHeader(action); MTPMessageMedia media = MTP_messageMediaEmpty(); @@ -3446,10 +3466,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_entities; } const auto clearCloudDraft = action.clearDraft; + const auto topicRootId = action.topicRootId; if (clearCloudDraft) { sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft; - history->clearCloudDraft(); - history->startSavingCloudDraft(); + history->clearCloudDraft(topicRootId); + history->startSavingCloudDraft(topicRootId); } const auto sendAs = action.options.sendAs; const auto messageFromId = sendAs @@ -3468,12 +3489,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date; } const auto viaBotId = UserId(); - const auto replyTo = action.replyTo; lastMessage = history->addNewLocalMessage( newId.msg, flags, viaBotId, - replyTo, + action.replyTo, HistoryItem::NewMessageDate(action.options.scheduled), messageFromId, messagePostAuthor, @@ -3482,12 +3502,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) { HistoryMessageMarkupData()); histories.sendPreparedMessage( history, - replyTo, + action.replyTo, + topicRootId, randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), msgText, MTP_long(randomId), MTPReplyMarkup(), @@ -3497,6 +3519,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { ), [=](const MTPUpdates &result, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( + topicRootId, UnixtimeFromMsgId(response.outerMsgId)); } }, [=](const MTP::Error &error, const MTP::Response &response) { @@ -3507,6 +3530,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { } if (clearCloudDraft) { history->finishSavingCloudDraft( + topicRootId, UnixtimeFromMsgId(response.outerMsgId)); } }); @@ -3573,12 +3597,16 @@ void ApiWrap::sendInlineResult( ? (*localMessageId) : _session->data().nextLocalMessageId()); const auto randomId = base::RandomValue(); + const auto topicRootId = action.replyTo ? action.topicRootId : 0; auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft | 0; if (action.replyTo) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id; + if (topicRootId) { + sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_top_msg_id; + } } const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); @@ -3618,19 +3646,20 @@ void ApiWrap::sendInlineResult( action.replyTo, messagePostAuthor); - history->clearCloudDraft(); - history->startSavingCloudDraft(); + history->clearCloudDraft(topicRootId); + history->startSavingCloudDraft(topicRootId); auto &histories = history->owner().histories(); - const auto replyTo = action.replyTo; histories.sendPreparedMessage( history, - replyTo, + action.replyTo, + topicRootId, randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), peer->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), MTP_long(randomId), MTP_long(data->getQueryId()), MTP_string(data->getId()), @@ -3638,10 +3667,12 @@ void ApiWrap::sendInlineResult( (sendAs ? sendAs->input : MTP_inputPeerEmpty()) ), [=](const MTPUpdates &result, const MTP::Response &response) { history->finishSavingCloudDraft( + topicRootId, UnixtimeFromMsgId(response.outerMsgId)); }, [=](const MTP::Error &error, const MTP::Response &response) { sendMessageFail(error, peer, randomId, newId); history->finishSavingCloudDraft( + topicRootId, UnixtimeFromMsgId(response.outerMsgId)); }); finishForwarding(action); @@ -3740,6 +3771,7 @@ void ApiWrap::sendMediaWithRandomId( uint64 randomId) { const auto history = item->history(); const auto replyTo = item->replyToId(); + const auto topicRootId = item->topicRootId(); auto caption = item->originalText(); TextUtilities::Trim(caption); @@ -3750,22 +3782,16 @@ void ApiWrap::sendMediaWithRandomId( const auto updateRecentStickers = Api::HasAttachedStickers(media); - const auto flags = MTPmessages_SendMedia::Flags(0) - | (replyTo - ? MTPmessages_SendMedia::Flag::f_reply_to_msg_id - : MTPmessages_SendMedia::Flag(0)) + using Flag = MTPmessages_SendMedia::Flag; + const auto flags = Flag(0) + | (replyTo ? Flag::f_reply_to_msg_id : Flag(0)) + | (topicRootId ? Flag::f_top_msg_id : Flag(0)) | (ShouldSendSilent(history->peer, options) - ? MTPmessages_SendMedia::Flag::f_silent - : MTPmessages_SendMedia::Flag(0)) - | (!sentEntities.v.isEmpty() - ? MTPmessages_SendMedia::Flag::f_entities - : MTPmessages_SendMedia::Flag(0)) - | (options.scheduled - ? MTPmessages_SendMedia::Flag::f_schedule_date - : MTPmessages_SendMedia::Flag(0)) - | (options.sendAs - ? MTPmessages_SendMedia::Flag::f_send_as - : MTPmessages_SendMedia::Flag(0)); + ? Flag::f_silent + : Flag(0)) + | (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0)) + | (options.scheduled ? Flag::f_schedule_date : Flag(0)) + | (options.sendAs ? Flag::f_send_as : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; @@ -3773,11 +3799,13 @@ void ApiWrap::sendMediaWithRandomId( histories.sendPreparedMessage( history, replyTo, + topicRootId, randomId, Data::Histories::PrepareMessage( MTP_flags(flags), peer->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), media, MTP_string(caption.text), MTP_long(randomId), @@ -3859,30 +3887,29 @@ void ApiWrap::sendAlbumIfReady(not_null album) { } const auto history = sample->history(); const auto replyTo = sample->replyToId(); + const auto topicRootId = sample->topicRootId(); const auto sendAs = album->options.sendAs; - const auto flags = MTPmessages_SendMultiMedia::Flags(0) - | (replyTo - ? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id - : MTPmessages_SendMultiMedia::Flag(0)) + using Flag = MTPmessages_SendMultiMedia::Flag; + const auto flags = Flag(0) + | (replyTo ? Flag::f_reply_to_msg_id : Flag(0)) + | (topicRootId ? Flag::f_top_msg_id : Flag(0)) | (ShouldSendSilent(history->peer, album->options) - ? MTPmessages_SendMultiMedia::Flag::f_silent - : MTPmessages_SendMultiMedia::Flag(0)) - | (album->options.scheduled - ? MTPmessages_SendMultiMedia::Flag::f_schedule_date - : MTPmessages_SendMultiMedia::Flag(0)) - | (sendAs - ? MTPmessages_SendMultiMedia::Flag::f_send_as - : MTPmessages_SendMultiMedia::Flag(0)); + ? Flag::f_silent + : Flag(0)) + | (album->options.scheduled ? Flag::f_schedule_date : Flag(0)) + | (sendAs ? Flag::f_send_as : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; histories.sendPreparedMessage( history, replyTo, + topicRootId, uint64(0), // randomId Data::Histories::PrepareMessage( MTP_flags(flags), peer->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), MTP_vector(medias), MTP_int(album->options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()) @@ -3905,6 +3932,7 @@ FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const { peer->id, action.options, action.replyTo, + action.topicRootId, action.replaceMediaOf); } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 7cbfc2ab6..ddbf75568 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -245,7 +245,7 @@ public: void updateNotifySettingsDelayed(not_null thread); void updateNotifySettingsDelayed(not_null peer); void updateNotifySettingsDelayed(Data::DefaultNotify type); - void saveDraftToCloudDelayed(not_null history); + void saveDraftToCloudDelayed(not_null thread); static int OnlineTillFromStatus( const MTPUserStatus &status, @@ -563,7 +563,9 @@ private: }; base::flat_map _notifySettingRequests; - base::flat_map, mtpRequestId> _draftsSaveRequestIds; + base::flat_map< + base::weak_ptr, + mtpRequestId> _draftsSaveRequestIds; base::Timer _draftsSaveTimer; base::flat_set _stickerSetDisenableRequests; diff --git a/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp index b220fa84d..4e636da93 100644 --- a/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp @@ -65,14 +65,17 @@ void ShareBotGame( auto &histories = history->owner().histories(); const auto randomId = base::RandomValue(); const auto replyTo = 0; + const auto topicRootId = 0; histories.sendPreparedMessage( history, replyTo, + topicRootId, randomId, Data::Histories::PrepareMessage( MTP_flags(0), chat->input, Data::Histories::ReplyToPlaceholder(), + Data::Histories::TopicRootPlaceholder(), MTP_inputMediaGame( MTP_inputGameShortName( bot->inputUser, diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 23ea9d72e..2c95462ad 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -127,7 +127,7 @@ void DicePack::generateLocal(int index, const QString &name) { QByteArray(), nullptr, SendMediaType::File, - FileLoadTo(0, {}, 0, 0), + FileLoadTo(0, {}, 0, 0, 0), {}); task.process({ .generateGoodThumbnail = false }); const auto result = task.peekResult(); diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 1a7b31099..87a0588b7 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -151,8 +151,9 @@ struct TopicUpdate { Notifications = (1U << 4), Title = (1U << 5), Icon = (1U << 6), + CloudDraft = (1U << 7), - LastUsedBit = (1U << 4), + LastUsedBit = (1U << 7), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 88d614f0b..474822a48 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -19,29 +19,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { -DraftKey DraftKey::FromSerializedOld(int32 value) { - return !value - ? DraftKey::None() - : (value == kLocalDraftIndex + kEditDraftShiftOld) - ? DraftKey::LocalEdit() - : (value == kScheduledDraftIndex + kEditDraftShiftOld) - ? DraftKey::ScheduledEdit() - : (value > 0 && value < 0x4000'0000) - ? DraftKey::Replies(int64(value)) - : (value > kEditDraftShiftOld - && value < kEditDraftShiftOld + 0x4000'000) - ? DraftKey::RepliesEdit(int64(value - kEditDraftShiftOld)) - : DraftKey::None(); -} - Draft::Draft( const TextWithTags &textWithTags, MsgId msgId, + MsgId topicRootId, const MessageCursor &cursor, PreviewState previewState, mtpRequestId saveRequestId) : textWithTags(textWithTags) , msgId(msgId) +, topicRootId(topicRootId) , cursor(cursor) , previewState(previewState) , saveRequestId(saveRequestId) { @@ -50,10 +37,12 @@ Draft::Draft( Draft::Draft( not_null field, MsgId msgId, + MsgId topicRootId, PreviewState previewState, mtpRequestId saveRequestId) : textWithTags(field->getTextWithTags()) , msgId(msgId) +, topicRootId(topicRootId) , cursor(field) , previewState(previewState) { } @@ -61,10 +50,11 @@ Draft::Draft( void ApplyPeerCloudDraft( not_null session, PeerId peerId, + MsgId topicRootId, const MTPDdraftMessage &draft) { const auto history = session->data().history(peerId); const auto date = draft.vdate().v; - if (history->skipCloudDraftUpdate(date)) { + if (history->skipCloudDraftUpdate(topicRootId, date)) { return; } const auto textWithTags = TextWithTags{ @@ -78,6 +68,7 @@ void ApplyPeerCloudDraft( auto cloudDraft = std::make_unique( textWithTags, replyTo, + topicRootId, MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), (draft.is_no_webpage() ? Data::PreviewState::Cancelled @@ -85,20 +76,21 @@ void ApplyPeerCloudDraft( cloudDraft->date = date; history->setCloudDraft(std::move(cloudDraft)); - history->applyCloudDraft(); + history->applyCloudDraft(topicRootId); } void ClearPeerCloudDraft( not_null session, PeerId peerId, + MsgId topicRootId, TimeId date) { const auto history = session->data().history(peerId); - if (history->skipCloudDraftUpdate(date)) { + if (history->skipCloudDraftUpdate(topicRootId, date)) { return; } - history->clearCloudDraft(); - history->applyCloudDraft(); + history->clearCloudDraft(topicRootId); + history->applyCloudDraft(topicRootId); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index 533c74167..5af044019 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -20,10 +20,12 @@ namespace Data { void ApplyPeerCloudDraft( not_null session, PeerId peerId, + MsgId topicRootId, const MTPDdraftMessage &draft); void ClearPeerCloudDraft( not_null session, PeerId peerId, + MsgId topicRootId, TimeId date); enum class PreviewState : char { @@ -37,18 +39,21 @@ struct Draft { Draft( const TextWithTags &textWithTags, MsgId msgId, + MsgId topicRootId, const MessageCursor &cursor, PreviewState previewState, mtpRequestId saveRequestId = 0); Draft( not_null field, MsgId msgId, + MsgId topicRootId, PreviewState previewState, mtpRequestId saveRequestId = 0); TimeId date = 0; TextWithTags textWithTags; MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft + MsgId topicRootId = 0; MessageCursor cursor; PreviewState previewState = PreviewState::Allowed; mtpRequestId saveRequestId = 0; @@ -56,70 +61,93 @@ struct Draft { class DraftKey { public: - [[nodiscard]] static DraftKey None() { + [[nodiscard]] static constexpr DraftKey None() { return 0; } - [[nodiscard]] static DraftKey Local() { - return kLocalDraftIndex; + [[nodiscard]] static constexpr DraftKey Local(MsgId topicRootId) { + return (topicRootId < 0 || topicRootId >= ServerMaxMsgId) + ? None() + : (topicRootId ? topicRootId.bare : kLocalDraftIndex); } - [[nodiscard]] static DraftKey LocalEdit() { - return kLocalDraftIndex + kEditDraftShift; + [[nodiscard]] static constexpr DraftKey LocalEdit(MsgId topicRootId) { + return (topicRootId < 0 || topicRootId >= ServerMaxMsgId) + ? None() + : ((topicRootId ? topicRootId.bare : kLocalDraftIndex) + + kEditDraftShift); } - [[nodiscard]] static DraftKey Cloud() { - return kCloudDraftIndex; + [[nodiscard]] static constexpr DraftKey Cloud(MsgId topicRootId) { + return (topicRootId < 0 || topicRootId >= ServerMaxMsgId) + ? None() + : topicRootId + ? (kCloudDraftShift + topicRootId.bare) + : kCloudDraftIndex; } - [[nodiscard]] static DraftKey Scheduled() { + [[nodiscard]] static constexpr DraftKey Scheduled() { return kScheduledDraftIndex; } - [[nodiscard]] static DraftKey ScheduledEdit() { + [[nodiscard]] static constexpr DraftKey ScheduledEdit() { return kScheduledDraftIndex + kEditDraftShift; } - [[nodiscard]] static DraftKey Replies(MsgId rootId) { - return rootId.bare; - } - [[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) { - return rootId.bare + kEditDraftShift; - } - [[nodiscard]] static DraftKey FromSerialized(qint64 value) { + [[nodiscard]] static constexpr DraftKey FromSerialized(qint64 value) { return value; } - [[nodiscard]] qint64 serialize() const { + [[nodiscard]] constexpr qint64 serialize() const { return _value; } - [[nodiscard]] static DraftKey FromSerializedOld(int32 value); + [[nodiscard]] static constexpr DraftKey FromSerializedOld(int32 value) { + return !value + ? None() + : (value == kLocalDraftIndex + kEditDraftShiftOld) + ? LocalEdit(0) + : (value == kScheduledDraftIndex + kEditDraftShiftOld) + ? ScheduledEdit() + : (value > 0 && value < 0x4000'0000) + ? Local(MsgId(value)) + : (value > kEditDraftShiftOld + && value < kEditDraftShiftOld + 0x4000'000) + ? LocalEdit(int64(value - kEditDraftShiftOld)) + : None(); + } + [[nodiscard]] constexpr bool isLocal() const { + return (_value == kLocalDraftIndex) + || (_value > 0 && _value < ServerMaxMsgId.bare); + } + [[nodiscard]] constexpr bool isCloud() const { + return (_value == kCloudDraftIndex) + || (_value > kCloudDraftShift + && _value < kCloudDraftShift + ServerMaxMsgId.bare); + } - inline bool operator<(const DraftKey &other) const { - return _value < other._value; - } - inline bool operator==(const DraftKey &other) const { - return _value == other._value; - } - inline bool operator>(const DraftKey &other) const { - return (other < *this); - } - inline bool operator<=(const DraftKey &other) const { - return !(other < *this); - } - inline bool operator>=(const DraftKey &other) const { - return !(*this < other); - } - inline bool operator!=(const DraftKey &other) const { - return !(*this == other); + [[nodiscard]] constexpr MsgId topicRootId() const { + const auto max = ServerMaxMsgId.bare; + if (_value > kCloudDraftShift && _value < kCloudDraftShift + max) { + return (_value - kCloudDraftShift); + } else if (_value > kEditDraftShift && _value < kEditDraftShift + max) { + return (_value - kEditDraftShift); + } else if (_value > 0 && _value < max) { + return _value; + } + return 0; } + + + friend inline constexpr auto operator<=>(DraftKey, DraftKey) = default; + inline explicit operator bool() const { return _value != 0; } private: - DraftKey(int64 value) : _value(value) { + constexpr DraftKey(int64 value) : _value(value) { } static constexpr auto kLocalDraftIndex = -1; static constexpr auto kCloudDraftIndex = -2; static constexpr auto kScheduledDraftIndex = -3; static constexpr auto kEditDraftShift = ServerMaxMsgId.bare; + static constexpr auto kCloudDraftShift = 2 * ServerMaxMsgId.bare; static constexpr auto kEditDraftShiftOld = 0x3FFF'FFFF; int64 _value = 0; @@ -128,7 +156,7 @@ private: using HistoryDrafts = base::flat_map>; -inline bool draftStringIsEmpty(const QString &text) { +[[nodiscard]] inline bool DraftStringIsEmpty(const QString &text) { for (const auto &ch : text) { if (!ch.isSpace()) { return false; @@ -137,20 +165,19 @@ inline bool draftStringIsEmpty(const QString &text) { return true; } -inline bool draftIsNull(const Draft *draft) { +[[nodiscard]] inline bool DraftIsNull(const Draft *draft) { return !draft - || (draftStringIsEmpty(draft->textWithTags.text) && !draft->msgId); + || (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text)); } -inline bool draftsAreEqual(const Draft *a, const Draft *b) { - bool aIsNull = draftIsNull(a); - bool bIsNull = draftIsNull(b); +[[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) { + const auto aIsNull = DraftIsNull(a); + const auto bIsNull = DraftIsNull(b); if (aIsNull) { return bIsNull; } else if (bIsNull) { return false; } - return (a->textWithTags == b->textWithTags) && (a->msgId == b->msgId) && (a->previewState == b->previewState); diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 5ea18d8d2..4ef0754dc 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum.h" #include "data/data_histories.h" #include "data/data_replies_list.h" +#include "data/data_send_action.h" #include "data/notify/data_notify_settings.h" #include "data/data_session.h" #include "data/stickers/data_custom_emoji.h" @@ -142,10 +143,15 @@ ForumTopic::ForumTopic(not_null forum, MsgId rootId) , _forum(forum) , _list(_forum->topicsList()) , _replies(std::make_shared(history(), rootId)) +, _sendActionPainter(owner().sendActionManager().repliesPainter( + history(), + rootId)) , _rootId(rootId) , _lastKnownServerMessageId(rootId) { Thread::setMuted(owner().notifySettings().isMuted(this)); + _sendActionPainter->setTopic(this); + _replies->unreadCountValue( ) | rpl::combine_previous( ) | rpl::filter([=] { @@ -161,6 +167,7 @@ ForumTopic::ForumTopic(not_null forum, MsgId rootId) } ForumTopic::~ForumTopic() { + _sendActionPainter->setTopic(nullptr); session().api().unreadThings().cancelRequests(this); } @@ -191,11 +198,24 @@ MsgId ForumTopic::rootId() const { return _rootId; } +bool ForumTopic::creating() const { + return _forum->creating(_rootId); +} + +void ForumTopic::discard() { + Expects(creating()); + + _forum->discardCreatingId(_rootId); +} + void ForumTopic::setRealRootId(MsgId realId) { if (_rootId != realId) { _rootId = realId; _lastKnownServerMessageId = realId; _replies = std::make_shared(history(), _rootId); + _sendActionPainter = owner().sendActionManager().repliesPainter( + history(), + _rootId); } } @@ -221,6 +241,15 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) { owner().notifySettings().apply(this, data.vnotify_settings()); + const auto draft = data.vdraft(); + if (draft && draft->type() == mtpc_draftMessage) { + Data::ApplyPeerCloudDraft( + &session(), + channel()->id, + _rootId, + draft->c_draftMessage()); + } + _replies->setInboxReadTill( data.vread_inbox_max_id().v, data.vunread_count().v); @@ -390,13 +419,11 @@ void ForumTopic::requestChatListMessage() { TimeId ForumTopic::adjustedChatListTimeId() const { const auto result = chatListTimeId(); -#if 0 // #TODO forum draft - if (const auto draft = cloudDraft()) { - if (!Data::draftIsNull(draft) && !session().supportMode()) { + if (const auto draft = history()->cloudDraft(_rootId)) { + if (!Data::DraftIsNull(draft) && !session().supportMode()) { return std::max(result, draft->date); } } -#endif return result; } @@ -480,6 +507,17 @@ void ForumTopic::applyItemAdded(not_null item) { setLastMessage(item); } +void ForumTopic::maybeSetLastMessage(not_null item) { + Expects(item->topicRootId() == _rootId); + + if (!_lastMessage + || ((*_lastMessage)->date() < item->date()) + || ((*_lastMessage)->date() == item->date() + && (*_lastMessage)->id < item->id)) { + setLastMessage(item); + } +} + void ForumTopic::applyItemRemoved(MsgId id) { if (const auto lastItem = lastMessage()) { if (lastItem->id == id) { @@ -499,6 +537,11 @@ void ForumTopic::applyItemRemoved(MsgId id) { } } +bool ForumTopic::isServerSideUnread( + not_null item) const { + return _replies->isServerSideUnread(item); +} + int ForumTopic::unreadCount() const { return _replies->unreadCountCurrent(); } @@ -544,6 +587,10 @@ void ForumTopic::setUnreadMark(bool unread) { Thread::setUnreadMark(unread); } +not_null ForumTopic::sendActionPainter() { + return _sendActionPainter.get(); +} + int ForumTopic::chatListUnreadCount() const { const auto state = chatListUnreadState(); return state.marks diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index c687abef7..d18d1a900 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -25,6 +25,10 @@ namespace Main { class Session; } // namespace Main +namespace HistoryView { +class SendActionPainter; +} // namespace HistoryView + namespace Data { class RepliesList; @@ -57,6 +61,9 @@ public: [[nodiscard]] rpl::producer<> destroyed() const; [[nodiscard]] MsgId rootId() const; + [[nodiscard]] bool creating() const; + void discard(); + void setRealRootId(MsgId realId); void applyTopic(const MTPDforumTopic &data); @@ -91,6 +98,7 @@ public: void applyColorId(int32 colorId); void applyItemAdded(not_null item); void applyItemRemoved(MsgId id); + void maybeSetLastMessage(not_null item); [[nodiscard]] PeerNotifySettings ¬ify() { return _notify; @@ -105,6 +113,9 @@ public: std::shared_ptr &view, const Dialogs::Ui::PaintContext &context) const override; + [[nodiscard]] bool isServerSideUnread( + not_null item) const override; + [[nodiscard]] int unreadCount() const; [[nodiscard]] bool unreadCountKnown() const; @@ -113,6 +124,9 @@ public: void setMuted(bool muted) override; void setUnreadMark(bool unread) override; + [[nodiscard]] auto sendActionPainter() + ->not_null override; + private: void indexTitleParts(); void validateDefaultIcon() const; @@ -132,6 +146,7 @@ private: const not_null _forum; const not_null _list; std::shared_ptr _replies; + std::shared_ptr _sendActionPainter; MsgId _rootId = 0; MsgId _lastKnownServerMessageId = 0; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 0d49f4016..665e9030c 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -247,7 +247,7 @@ void Histories::readInboxOnNewMessage(not_null item) { } void Histories::readClientSideMessage(not_null item) { - if (item->out() || !item->unread()) { + if (item->out() || !item->unread(item->history())) { return; } const auto history = item->history(); @@ -886,20 +886,22 @@ bool Histories::isCreatingTopic( int Histories::sendPreparedMessage( not_null history, MsgId replyTo, + MsgId topicRootId, uint64 randomId, - Fn message, + Fn message, Fn done, Fn fail) { - if (isCreatingTopic(history, replyTo)) { + if (isCreatingTopic(history, topicRootId)) { const auto id = ++_requestAutoincrement; - const auto creatingId = FullMsgId(history->peer->id, replyTo); + const auto creatingId = FullMsgId(history->peer->id, topicRootId); auto i = _creatingTopics.find(creatingId); if (i == end(_creatingTopics)) { - sendCreateTopicRequest(history, replyTo); + sendCreateTopicRequest(history, topicRootId); i = _creatingTopics.emplace(creatingId).first; } i->second.push_back({ .randomId = randomId, + .replyTo = replyTo, .message = std::move(message), .done = std::move(done), .fail = std::move(fail), @@ -908,8 +910,9 @@ int Histories::sendPreparedMessage( _creatingTopicRequests.emplace(id); return id; } - const auto realTo = convertTopicReplyTo(history, replyTo); - return v::match(message(realTo), [&](const auto &request) { + const auto realReply = convertTopicReplyTo(history, replyTo); + const auto realRoot = convertTopicReplyTo(history, topicRootId); + return v::match(message(realReply, realRoot), [&](const auto &request) { const auto type = RequestType::Send; return sendRequest(history, type, [=](Fn finish) { const auto session = &_owner->session(); @@ -935,38 +938,38 @@ int Histories::sendPreparedMessage( }); } -void Histories::checkTopicCreated(FullMsgId rootId, MsgId realId) { +void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) { const auto i = _creatingTopics.find(rootId); if (i != end(_creatingTopics)) { auto scheduled = base::take(i->second); _creatingTopics.erase(i); - _createdTopicIds.emplace(rootId, realId); + _createdTopicIds.emplace(rootId, realRoot); if (const auto forum = _owner->peer(rootId.peer)->forum()) { - forum->created(rootId.msg, realId); + forum->created(rootId.msg, realRoot); } const auto history = _owner->history(rootId.peer); for (auto &entry : scheduled) { _creatingTopicRequests.erase(entry.requestId); - AssertIsDebug(); - //sendPreparedMessage( - // history, - // realId, - // entry.randomId, - // std::move(entry.message), - // std::move(entry.done), - // std::move(entry.fail)); + sendPreparedMessage( + history, + entry.replyTo, + realRoot, + entry.randomId, + std::move(entry.message), + std::move(entry.done), + std::move(entry.fail)); } for (const auto &item : history->clientSideMessages()) { const auto replace = [&](MsgId nowId) { - return (nowId == rootId.msg) ? realId : nowId; + return (nowId == rootId.msg) ? realRoot : nowId; }; - if (item->replyToTop() == rootId.msg) { + if (item->topicRootId() == rootId.msg) { item->setReplyFields( replace(item->replyToId()), - realId, + realRoot, true); } } diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index 780867f3c..05f7534f4 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -103,22 +103,25 @@ public: int sendPreparedMessage( not_null history, MsgId replyTo, + MsgId topicRootId, uint64 randomId, - Fn message, + Fn message, Fn done, Fn fail); struct ReplyToPlaceholder { }; + struct TopicRootPlaceholder { + }; template - static Fn PrepareMessage( + static Fn PrepareMessage( const Args &...args) { - return [=](MsgId replyTo) { - return RequestType(ReplaceReplyTo(args, replyTo)...); + return [=](MsgId replyTo, MsgId topicRootId) -> RequestType { + return { ReplaceReplyIds(args, replyTo, topicRootId)... }; }; } - void checkTopicCreated(FullMsgId rootId, MsgId realId); + void checkTopicCreated(FullMsgId rootId, MsgId realRoot); [[nodiscard]] MsgId convertTopicReplyTo( not_null history, MsgId replyTo) const; @@ -147,7 +150,8 @@ private: }; struct DelayedByTopicMessage { uint64 randomId = 0; - Fn message; + MsgId replyTo = 0; + Fn message; Fn done; Fn fail; int requestId = 0; @@ -162,9 +166,11 @@ private: }; template - static auto ReplaceReplyTo(Arg arg, MsgId replyTo) { + static auto ReplaceReplyIds(Arg arg, MsgId replyTo, MsgId topicRootId) { if constexpr (std::is_same_v) { return MTP_int(replyTo); + } else if constexpr (std::is_same_v) { + return MTP_int(topicRootId); } else { return arg; } diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index 292190b5e..065e25284 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -765,6 +765,14 @@ void RepliesList::setUnreadCount(std::optional count) { } } +bool RepliesList::isServerSideUnread( + not_null item) const { + const auto till = item->out() + ? computeOutboxReadTillFull() + : computeInboxReadTillFull(); + return (item->id > till); +} + void RepliesList::checkReadTillEnd() { if (_unreadCount.current() != 0 && _skippedAfter == 0 diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index 8c30c912d..2d5690c36 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -43,6 +43,9 @@ public: void setOutboxReadTill(MsgId readTillId); [[nodiscard]] MsgId computeOutboxReadTillFull() const; + [[nodiscard]] bool isServerSideUnread( + not_null item) const; + [[nodiscard]] std::optional computeUnreadCountLocally( MsgId afterId) const; void requestUnreadCount(); diff --git a/Telegram/SourceFiles/data/data_send_action.cpp b/Telegram/SourceFiles/data/data_send_action.cpp index 77333acf6..ea8f92bac 100644 --- a/Telegram/SourceFiles/data/data_send_action.cpp +++ b/Telegram/SourceFiles/data/data_send_action.cpp @@ -75,7 +75,7 @@ auto SendActionManager::repliesPainter( if (auto strong = weak.lock()) { return strong; } - auto result = std::make_shared(history); + auto result = std::make_shared(history, rootId); weak = result; return result; } diff --git a/Telegram/SourceFiles/data/data_send_action.h b/Telegram/SourceFiles/data/data_send_action.h index a209e1263..227ef6569 100644 --- a/Telegram/SourceFiles/data/data_send_action.h +++ b/Telegram/SourceFiles/data/data_send_action.h @@ -17,10 +17,12 @@ class SendActionPainter; namespace Data { +class Thread; + class SendActionManager final { public: struct AnimationUpdate { - not_null history; + not_null thread; int left = 0; int width = 0; int height = 0; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index f5dadc374..d1fd7b983 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3820,7 +3820,7 @@ void Session::refreshChatListEntry(Dialogs::Key key) { const auto mainList = chatsListFor(entry); auto event = ChatListEntryRefresh{ .key = key }; const auto creating = event.existenceChanged = !entry->inChatList(); - if (creating && topic && topic->forum()->creating(topic->rootId())) { + if (creating && topic && topic->creating()) { return; } else if (event.existenceChanged) { const auto mainRow = entry->addToChatList(0, mainList); diff --git a/Telegram/SourceFiles/data/data_thread.cpp b/Telegram/SourceFiles/data/data_thread.cpp index 602eaf062..7c42a92b9 100644 --- a/Telegram/SourceFiles/data/data_thread.cpp +++ b/Telegram/SourceFiles/data/data_thread.cpp @@ -17,6 +17,13 @@ namespace Data { Thread::~Thread() = default; +MsgId Thread::topicRootId() const { + if (const auto topic = asTopic()) { + return topic->rootId(); + } + return MsgId(); +} + not_null Thread::peer() const { return owningHistory()->peer; } diff --git a/Telegram/SourceFiles/data/data_thread.h b/Telegram/SourceFiles/data/data_thread.h index e3c8c08d9..58df8b7f4 100644 --- a/Telegram/SourceFiles/data/data_thread.h +++ b/Telegram/SourceFiles/data/data_thread.h @@ -24,6 +24,10 @@ class Proxy; class ConstProxy; } // namespace HistoryUnreadThings +namespace HistoryView { +class SendActionPainter; +} // namespace HistoryView + namespace st { extern const int &dialogsTextWidthMin; } // namespace st @@ -57,6 +61,7 @@ public: [[nodiscard]] not_null owningHistory() const { return const_cast(this)->owningHistory(); } + [[nodiscard]] MsgId topicRootId() const; [[nodiscard]] not_null peer() const; [[nodiscard]] PeerNotifySettings ¬ify(); [[nodiscard]] const PeerNotifySettings ¬ify() const; @@ -87,6 +92,9 @@ public: } virtual void setUnreadMark(bool unread); + [[nodiscard]] virtual bool isServerSideUnread( + not_null item) const = 0; + [[nodiscard]] const base::flat_set &unreadMentionsIds() const; [[nodiscard]] const base::flat_set &unreadReactionsIds() const; @@ -97,6 +105,9 @@ public: return _lastItemDialogsView; } + [[nodiscard]] virtual auto sendActionPainter() + -> not_null = 0; + private: enum class Flag : uchar { UnreadMark = (1 << 0), diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 340267d82..94eee7e94 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -185,7 +185,7 @@ InnerWidget::InnerWidget( width(), update.textUpdated); updateDialogRow( - RowDescriptor(update.history, FullMsgId()), + RowDescriptor(update.thread, FullMsgId()), updateRect, UpdateRowSection::Default | UpdateRowSection::Filtered); }, lifetime()); @@ -2394,8 +2394,10 @@ void InnerWidget::refreshEmptyLabel() { const auto data = &session().data(); const auto state = !shownDialogs()->empty() ? EmptyState::None - : (_openedForum && _openedForum->topicsList()->loaded()) - ? EmptyState::EmptyForum + : _openedForum + ? (_openedForum->topicsList()->loaded() + ? EmptyState::EmptyForum + : EmptyState::Loading) : (!_filterId && data->contactsLoaded().current()) ? EmptyState::NoContacts : (_filterId > 0) && data->chatsList()->loaded() diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp index 1156daf43..fe96a5a7d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp @@ -19,12 +19,18 @@ Key::Key(History *history) : _value(history) { Key::Key(Data::Folder *folder) : _value(folder) { } +Key::Key(Data::Thread *thread) : _value(thread) { +} + Key::Key(Data::ForumTopic *topic) : _value(topic) { } Key::Key(not_null history) : _value(history) { } +Key::Key(not_null thread) : _value(thread) { +} + Key::Key(not_null folder) : _value(folder) { } diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index b644188b5..8ac12ec64 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -27,10 +27,12 @@ public: } Key(History *history); Key(Data::Folder *folder); + Key(Data::Thread *thread); Key(Data::ForumTopic *topic); Key(not_null entry) : _value(entry) { } Key(not_null history); + Key(not_null thread); Key(not_null folder); Key(not_null topic); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 4d886f556..5b079c732 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -48,7 +48,8 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_"; return user->isBot() && !user->isSupport() && !user->isRepliesChat(); } -[[nodiscard]] bool ShowSendActionInDialogs(History *history) { +[[nodiscard]] bool ShowSendActionInDialogs(Data::Thread *thread) { + const auto history = thread ? thread->owningHistory().get() : nullptr; return history && (!history->peer->isUser() || history->peer->asUser()->onlineTill > 0); @@ -465,8 +466,8 @@ void PaintRow( : context.selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService; - if (!ShowSendActionInDialogs(history) - || !history->sendActionPainter()->paint( + if (!ShowSendActionInDialogs(thread) + || !thread->sendActionPainter()->paint( p, nameleft, texttop, @@ -474,7 +475,8 @@ void PaintRow( context.width, color, context.paused)) { - if (history->cloudDraftTextCache().isEmpty()) { + auto &cache = thread->cloudDraftTextCache(); + if (cache.isEmpty()) { using namespace TextUtilities; auto draftWrapped = Text::PlainLink( tr::lng_dialogs_text_from_wrapped( @@ -496,10 +498,10 @@ void PaintRow( }), Text::WithEntities); const auto context = Core::MarkedTextContext{ - .session = &history->session(), + .session = &thread->session(), .customEmojiRepaint = customEmojiRepaint, }; - history->cloudDraftTextCache().setMarkedText( + cache.setMarkedText( st::dialogsTextStyle, draftText, DialogTextOptions(), @@ -510,7 +512,7 @@ void PaintRow( : context.selected ? st::dialogsTextFgOver : st::dialogsTextFg); - history->cloudDraftTextCache().draw(p, { + cache.draw(p, { .position = { nameleft, texttop }, .availableWidth = availableWidth, .palette = &(supportMode @@ -549,8 +551,8 @@ void PaintRow( ? st::dialogsTextFgServiceOver : st::dialogsTextFgService; p.setFont(st::dialogsTextFont); - if (!ShowSendActionInDialogs(history) - || !history->sendActionPainter()->paint( + if (!ShowSendActionInDialogs(thread) + || !thread->sendActionPainter()->paint( p, nameleft, texttop, @@ -575,8 +577,10 @@ void PaintRow( : st::dialogsPinnedIcon; icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width); } - auto sendStateIcon = [&]() -> const style::icon* { - if (draft) { + const auto sendStateIcon = [&]() -> const style::icon* { + if (!thread) { + return nullptr; + } else if (draft) { if (draft->saveRequestId) { return &(context.active ? st::dialogsSendingIconActive @@ -586,7 +590,7 @@ void PaintRow( } } else if (item && !item->isEmpty() && item->needCheck()) { if (!item->isSending() && !item->hasFailed()) { - if (item->unread()) { + if (item->unread(thread)) { return &(context.active ? st::dialogsSentIconActive : context.selected @@ -607,7 +611,7 @@ void PaintRow( } return nullptr; }(); - if (sendStateIcon && history) { + if (sendStateIcon) { rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip); sendStateIcon->paint(p, rectForName.topLeft() + QPoint(rectForName.width(), 0), context.width); } @@ -892,12 +896,12 @@ void RowPainter::Paint( const auto unreadMuted = entry->chatListMutedBadge(); const auto item = entry->chatListMessage(); const auto cloudDraft = [&]() -> const Data::Draft*{ - if (history && (!item || (!unreadCount && !unreadMark))) { + if (thread && (!item || (!unreadCount && !unreadMark))) { // Draw item, if there are unread messages. - if (const auto draft = history->cloudDraft()) { - if (!Data::draftIsNull(draft)) { - return draft; - } + const auto draft = thread->owningHistory()->cloudDraft( + thread->topicRootId()); + if (!Data::DraftIsNull(draft)) { + return draft; } } return nullptr; @@ -977,8 +981,8 @@ void RowPainter::Paint( texttop, availableWidth, st::dialogsTextFont->height); - const auto actionWasPainted = ShowSendActionInDialogs(history) - ? history->sendActionPainter()->paint( + const auto actionWasPainted = ShowSendActionInDialogs(thread) + ? thread->sendActionPainter()->paint( p, rect.x(), rect.y(), diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 48e015844..a450d3f21 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -654,7 +654,7 @@ bool InnerWidget::elementHideReply(not_null view) { } bool InnerWidget::elementShownUnread(not_null view) { - return view->data()->unread(); + return false; } void InnerWidget::elementSendBotCommand( diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 2df368381..92de1bd43 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -695,9 +695,14 @@ void GenerateItems( using LogJoinByRequest = MTPDchannelAdminLogEventActionParticipantJoinByRequest; using LogNoForwards = MTPDchannelAdminLogEventActionToggleNoForwards; - using LogActionSendMessage = MTPDchannelAdminLogEventActionSendMessage; - using LogEventActionChangeAvailableReactions = MTPDchannelAdminLogEventActionChangeAvailableReactions; - using LogEventActionChangeUsernames = MTPDchannelAdminLogEventActionChangeUsernames; + using LogSendMessage = MTPDchannelAdminLogEventActionSendMessage; + using LogChangeAvailableReactions = MTPDchannelAdminLogEventActionChangeAvailableReactions; + using LogChangeUsernames = MTPDchannelAdminLogEventActionChangeUsernames; + using LogToggleForum = MTPDchannelAdminLogEventActionToggleForum; + using LogCreateTopic = MTPDchannelAdminLogEventActionCreateTopic; + using LogEditTopic = MTPDchannelAdminLogEventActionEditTopic; + using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic; + using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic; const auto session = &history->session(); const auto id = event.vid().v; @@ -972,7 +977,7 @@ void GenerateItems( ExtractSentDate(action.vmessage())); }; - const auto createParticipantJoin = [&]() { + const auto createParticipantJoin = [&](const LogJoin&) { const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_joined : tr::lng_admin_log_participant_joined_channel)( @@ -983,7 +988,7 @@ void GenerateItems( addSimpleServiceMessage(text); }; - const auto createParticipantLeave = [&]() { + const auto createParticipantLeave = [&](const LogLeave&) { const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_left : tr::lng_admin_log_participant_left_channel)( @@ -1457,7 +1462,7 @@ void GenerateItems( addSimpleServiceMessage(text); }; - const auto createSendMessage = [&](const LogActionSendMessage &data) { + const auto createSendMessage = [&](const LogSendMessage &data) { const auto text = tr::lng_admin_log_sent_message( tr::now, lt_from, @@ -1476,7 +1481,7 @@ void GenerateItems( }; const auto createChangeAvailableReactions = [&]( - const LogEventActionChangeAvailableReactions &data) { + const LogChangeAvailableReactions &data) { const auto text = data.vnew_value().match([&]( const MTPDchatReactionsNone&) { return tr::lng_admin_log_reactions_disabled( @@ -1514,87 +1519,74 @@ void GenerateItems( addSimpleServiceMessage(text); }; - const auto createChangeUsernames = [&]( - const LogEventActionChangeUsernames &data) { + const auto createChangeUsernames = [&](const LogChangeUsernames &data) { // #TODO usernames addSimpleServiceMessage({ "changed usernames" }); }; - action.match([&](const LogTitle &data) { - createChangeTitle(data); - }, [&](const LogAbout &data) { - createChangeAbout(data); - }, [&](const LogUsername &data) { - createChangeUsername(data); - }, [&](const LogPhoto &data) { - createChangePhoto(data); - }, [&](const LogInvites &data) { - createToggleInvites(data); - }, [&](const LogSign &data) { - createToggleSignatures(data); - }, [&](const LogPin &data) { - createUpdatePinned(data); - }, [&](const LogEdit &data) { - createEditMessage(data); - }, [&](const LogDelete &data) { - createDeleteMessage(data); - }, [&](const LogJoin &) { - createParticipantJoin(); - }, [&](const LogLeave &) { - createParticipantLeave(); - }, [&](const LogInvite &data) { - createParticipantInvite(data); - }, [&](const LogBan &data) { - createParticipantToggleBan(data); - }, [&](const LogPromote &data) { - createParticipantToggleAdmin(data); - }, [&](const LogSticker &data) { - createChangeStickerSet(data); - }, [&](const LogPreHistory &data) { - createTogglePreHistoryHidden(data); - }, [&](const LogPermissions &data) { - createDefaultBannedRights(data); - }, [&](const LogPoll &data) { - createStopPoll(data); - }, [&](const LogDiscussion &data) { - createChangeLinkedChat(data); - }, [&](const LogLocation &data) { - createChangeLocation(data); - }, [&](const LogSlowMode &data) { - createToggleSlowMode(data); - }, [&](const LogStartCall &data) { - createStartGroupCall(data); - }, [&](const LogDiscardCall &data) { - createDiscardGroupCall(data); - }, [&](const LogMute &data) { - createParticipantMute(data); - }, [&](const LogUnmute &data) { - createParticipantUnmute(data); - }, [&](const LogCallSetting &data) { - createToggleGroupCallSetting(data); - }, [&](const LogJoinByInvite &data) { - createParticipantJoinByInvite(data); - }, [&](const LogInviteDelete &data) { - createExportedInviteDelete(data); - }, [&](const LogInviteRevoke &data) { - createExportedInviteRevoke(data); - }, [&](const LogInviteEdit &data) { - createExportedInviteEdit(data); - }, [&](const LogVolume &data) { - createParticipantVolume(data); - }, [&](const LogTTL &data) { - createChangeHistoryTTL(data); - }, [&](const LogJoinByRequest &data) { - createParticipantJoinByRequest(data); - }, [&](const LogNoForwards &data) { - createToggleNoForwards(data); - }, [&](const LogActionSendMessage &data) { - createSendMessage(data); - }, [&](const LogEventActionChangeAvailableReactions &data) { - createChangeAvailableReactions(data); - }, [&](const LogEventActionChangeUsernames &data) { - createChangeUsernames(data); - }); + const auto createToggleForum = [&](const LogToggleForum &data) { + + }; + + const auto createCreateTopic = [&](const LogCreateTopic &data) { + + }; + + const auto createEditTopic = [&](const LogEditTopic &data) { + + }; + + const auto createDeleteTopic = [&](const LogDeleteTopic &data) { + + }; + + const auto createPinTopic = [&](const LogPinTopic &data) { + + }; + + action.match( + createChangeTitle, + createChangeAbout, + createChangeUsername, + createChangePhoto, + createToggleInvites, + createToggleSignatures, + createUpdatePinned, + createEditMessage, + createDeleteMessage, + createParticipantJoin, + createParticipantLeave, + createParticipantInvite, + createParticipantToggleBan, + createParticipantToggleAdmin, + createChangeStickerSet, + createTogglePreHistoryHidden, + createDefaultBannedRights, + createStopPoll, + createChangeLinkedChat, + createChangeLocation, + createToggleSlowMode, + createStartGroupCall, + createDiscardGroupCall, + createParticipantMute, + createParticipantUnmute, + createToggleGroupCallSetting, + createParticipantJoinByInvite, + createExportedInviteDelete, + createExportedInviteRevoke, + createExportedInviteEdit, + createParticipantVolume, + createChangeHistoryTTL, + createParticipantJoinByRequest, + createToggleNoForwards, + createSendMessage, + createChangeAvailableReactions, + createChangeUsernames, + createToggleForum, + createCreateTopic, + createEditTopic, + createDeleteTopic, + createPinTopic); } } // namespace AdminLog diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 25e1bb00a..2507f9989 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -157,53 +157,56 @@ void History::itemVanished(not_null item) { clearLastKeyboard(); } if ((!item->out() || item->isPost()) - && item->unread() + && item->unread(this) && unreadCount() > 0) { setUnreadCount(unreadCount() - 1); } } void History::takeLocalDraft(not_null from) { - const auto i = from->_drafts.find(Data::DraftKey::Local()); + const auto topicRootId = MsgId(0); + const auto i = from->_drafts.find(Data::DraftKey::Local(topicRootId)); if (i == end(from->_drafts)) { return; } auto &draft = i->second; if (!draft->textWithTags.text.isEmpty() - && !_drafts.contains(Data::DraftKey::Local())) { + && !_drafts.contains(Data::DraftKey::Local(topicRootId))) { // Edit and reply to drafts can't migrate. // Cloud drafts do not migrate automatically. draft->msgId = 0; setLocalDraft(std::move(draft)); } - from->clearLocalDraft(); + from->clearLocalDraft(topicRootId); session().api().saveDraftToCloudDelayed(from); } -void History::createLocalDraftFromCloud() { - const auto draft = cloudDraft(); +void History::createLocalDraftFromCloud(MsgId topicRootId) { + const auto draft = cloudDraft(topicRootId); if (!draft) { - clearLocalDraft(); + clearLocalDraft(topicRootId); return; - } else if (Data::draftIsNull(draft) || !draft->date) { + } else if (Data::DraftIsNull(draft) || !draft->date) { return; } - auto existing = localDraft(); - if (Data::draftIsNull(existing) + auto existing = localDraft(topicRootId); + if (Data::DraftIsNull(existing) || !existing->date || draft->date >= existing->date) { if (!existing) { setLocalDraft(std::make_unique( draft->textWithTags, draft->msgId, + topicRootId, draft->cursor, draft->previewState)); - existing = localDraft(); + existing = localDraft(topicRootId); } else if (existing != draft) { existing->textWithTags = draft->textWithTags; existing->msgId = draft->msgId; + existing->topicRootId = draft->topicRootId; existing->cursor = draft->cursor; existing->previewState = draft->previewState; } @@ -219,18 +222,22 @@ Data::Draft *History::draft(Data::DraftKey key) const { return (i != _drafts.end()) ? i->second.get() : nullptr; } -void History::setDraft(Data::DraftKey key, std::unique_ptr &&draft) { +void History::setDraft( + Data::DraftKey key, + std::unique_ptr &&draft) { if (!key) { return; } - const auto changingCloudDraft = (key == Data::DraftKey::Cloud()); - if (changingCloudDraft) { - cloudDraftTextCache().clear(); + const auto cloudThread = key.isCloud() + ? threadFor(key.topicRootId()) + : nullptr; + if (cloudThread) { + cloudThread->cloudDraftTextCache().clear(); } if (draft) { _drafts[key] = std::move(draft); - } else if (_drafts.remove(key) && changingCloudDraft) { - updateChatListSortPosition(); + } else if (_drafts.remove(key) && cloudThread) { + cloudThread->updateChatListSortPosition(); } } @@ -250,31 +257,38 @@ void History::clearDraft(Data::DraftKey key) { } void History::clearDrafts() { - const auto changingCloudDraft = _drafts.contains(Data::DraftKey::Cloud()); - _drafts.clear(); - if (changingCloudDraft) { - cloudDraftTextCache().clear(); - updateChatListSortPosition(); + for (auto &[key, draft] : base::take(_drafts)) { + const auto cloudThread = key.isCloud() + ? threadFor(key.topicRootId()) + : nullptr; + if (cloudThread) { + cloudThread->cloudDraftTextCache().clear(); + cloudThread->updateChatListSortPosition(); + } } } -Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) { - if (Data::draftIsNull(fromDraft)) { +Data::Draft *History::createCloudDraft( + MsgId topicRootId, + const Data::Draft *fromDraft) { + if (Data::DraftIsNull(fromDraft)) { setCloudDraft(std::make_unique( TextWithTags(), 0, + topicRootId, MessageCursor(), Data::PreviewState::Allowed)); - cloudDraft()->date = TimeId(0); + cloudDraft(topicRootId)->date = TimeId(0); } else { - auto existing = cloudDraft(); + auto existing = cloudDraft(topicRootId); if (!existing) { setCloudDraft(std::make_unique( fromDraft->textWithTags, fromDraft->msgId, + topicRootId, fromDraft->cursor, fromDraft->previewState)); - existing = cloudDraft(); + existing = cloudDraft(topicRootId); } else if (existing != fromDraft) { existing->textWithTags = fromDraft->textWithTags; existing->msgId = fromDraft->msgId; @@ -284,42 +298,60 @@ Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) { existing->date = base::unixtime::now(); } - cloudDraftTextCache().clear(); - updateChatListSortPosition(); - - return cloudDraft(); -} - -bool History::skipCloudDraftUpdate(TimeId date) const { - return (_savingCloudDraftRequests > 0) - || (date < _acceptCloudDraftsAfter); -} - -void History::startSavingCloudDraft() { - ++_savingCloudDraftRequests; -} - -void History::finishSavingCloudDraft(TimeId savedAt) { - if (_savingCloudDraftRequests > 0) { - --_savingCloudDraftRequests; + if (const auto thread = threadFor(topicRootId)) { + thread->cloudDraftTextCache().clear(); + thread->updateChatListSortPosition(); } - const auto acceptAfter = savedAt + kSkipCloudDraftsFor; - _acceptCloudDraftsAfter = std::max(_acceptCloudDraftsAfter, acceptAfter); + + return cloudDraft(topicRootId); } -void History::applyCloudDraft() { - if (session().supportMode()) { +bool History::skipCloudDraftUpdate(MsgId topicRootId, TimeId date) const { + const auto i = _acceptCloudDraftsAfter.find(topicRootId); + return _savingCloudDraftRequests.contains(topicRootId) + || (i != _acceptCloudDraftsAfter.end() && date < i->second); +} + +void History::startSavingCloudDraft(MsgId topicRootId) { + ++_savingCloudDraftRequests[topicRootId]; +} + +void History::finishSavingCloudDraft(MsgId topicRootId, TimeId savedAt) { + const auto i = _savingCloudDraftRequests.find(topicRootId); + if (i != _savingCloudDraftRequests.end()) { + if (--i->second <= 0) { + _savingCloudDraftRequests.erase(i); + } + } + auto &after = _acceptCloudDraftsAfter[topicRootId]; + after = std::max(after, savedAt + kSkipCloudDraftsFor); +} + +void History::applyCloudDraft(MsgId topicRootId) { + if (!topicRootId && session().supportMode()) { updateChatListEntry(); session().supportHelper().cloudDraftChanged(this); } else { - createLocalDraftFromCloud(); - updateChatListSortPosition(); - session().changes().historyUpdated(this, UpdateFlag::CloudDraft); + createLocalDraftFromCloud(topicRootId); + if (const auto thread = threadFor(topicRootId)) { + thread->updateChatListSortPosition(); + if (!topicRootId) { + session().changes().historyUpdated( + this, + UpdateFlag::CloudDraft); + } else { + session().changes().topicUpdated( + thread->asTopic(), + Data::TopicUpdate::Flag::CloudDraft); + } + } } } -void History::draftSavedToCloud() { - updateChatListEntry(); +void History::draftSavedToCloud(MsgId topicRootId) { + if (const auto thread = threadFor(topicRootId)) { + thread->updateChatListEntry(); + } session().local().writeDrafts(this); } @@ -1143,20 +1175,22 @@ void History::newItemAdded(not_null item) { const auto stillShow = item->showNotification(); // Could be read already. if (stillShow) { Core::App().notifications().schedule(notification); - if (!item->out() && item->unread()) { + } + if (item->out()) { + destroyUnreadBar(); + if (!item->unread(this)) { + outboxRead(item); + } + } else { + if (item->unread(this)) { if (unreadCountKnown()) { setUnreadCount(unreadCount() + 1); } else { owner().histories().requestDialogEntry(this); } + } else { + inboxRead(item); } - } else if (item->out()) { - destroyUnreadBar(); - } else if (!item->unread()) { - inboxRead(item); - } - if (item->out() && !item->unread()) { - outboxRead(item); } item->incrementReplyToTopCounter(); if (!folderKnown()) { @@ -1879,8 +1913,8 @@ void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { TimeId History::adjustedChatListTimeId() const { const auto result = chatListTimeId(); - if (const auto draft = cloudDraft()) { - if (!Data::draftIsNull(draft) && !session().supportMode()) { + if (const auto draft = cloudDraft(MsgId(0))) { + if (!Data::DraftIsNull(draft) && !session().supportMode()) { return std::max(result, draft->date); } } @@ -2601,6 +2635,7 @@ void History::applyDialog( Data::ApplyPeerCloudDraft( &session(), peer->id, + MsgId(0), // topicRootId draft->c_draftMessage()); } owner().histories().dialogEntryApplied(this); @@ -2823,6 +2858,16 @@ void History::forceFullResize() { _flags |= Flag::HasPendingResizedItems; } +Data::Thread *History::threadFor(MsgId topicRootId) { + return topicRootId + ? peer->forumTopicFor(topicRootId) + : static_cast(this); +} + +const Data::Thread *History::threadFor(MsgId topicRootId) const { + return const_cast(this)->threadFor(topicRootId); +} + not_null History::migrateToOrMe() const { if (const auto to = peer->migrateTo()) { return owner().history(to); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 9bda7c26f..121d604fc 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -80,6 +80,8 @@ public: not_null owningHistory() override { return this; } + [[nodiscard]] Data::Thread *threadFor(MsgId topicRootId); + [[nodiscard]] const Data::Thread *threadFor(MsgId topicRootId) const; [[nodiscard]] auto delegateMixin() const -> not_null { @@ -229,12 +231,13 @@ public: void inboxRead(not_null wasRead); void outboxRead(MsgId upTo); void outboxRead(not_null wasRead); - [[nodiscard]] bool isServerSideUnread( - not_null item) const; [[nodiscard]] MsgId loadAroundId() const; [[nodiscard]] MsgId inboxReadTillId() const; [[nodiscard]] MsgId outboxReadTillId() const; + [[nodiscard]] bool isServerSideUnread( + not_null item) const override; + [[nodiscard]] bool trackUnreadMessages() const; [[nodiscard]] int unreadCount() const; [[nodiscard]] bool unreadCountKnown() const; @@ -301,7 +304,7 @@ public: void setHasPendingResizedItems(); [[nodiscard]] auto sendActionPainter() - -> not_null { + -> not_null override { return &_sendActionPainter; } @@ -316,41 +319,51 @@ public: [[nodiscard]] const Data::HistoryDrafts &draftsMap() const; void setDraftsMap(Data::HistoryDrafts &&map); - Data::Draft *localDraft() const { - return draft(Data::DraftKey::Local()); + Data::Draft *localDraft(MsgId topicRootId) const { + return draft(Data::DraftKey::Local(topicRootId)); } - Data::Draft *localEditDraft() const { - return draft(Data::DraftKey::LocalEdit()); + Data::Draft *localEditDraft(MsgId topicRootId) const { + return draft(Data::DraftKey::LocalEdit(topicRootId)); } - Data::Draft *cloudDraft() const { - return draft(Data::DraftKey::Cloud()); + Data::Draft *cloudDraft(MsgId topicRootId) const { + return draft(Data::DraftKey::Cloud(topicRootId)); } void setLocalDraft(std::unique_ptr &&draft) { - setDraft(Data::DraftKey::Local(), std::move(draft)); + setDraft( + Data::DraftKey::Local(draft->topicRootId), + std::move(draft)); } void setLocalEditDraft(std::unique_ptr &&draft) { - setDraft(Data::DraftKey::LocalEdit(), std::move(draft)); + setDraft( + Data::DraftKey::LocalEdit(draft->topicRootId), + std::move(draft)); } void setCloudDraft(std::unique_ptr &&draft) { - setDraft(Data::DraftKey::Cloud(), std::move(draft)); + setDraft( + Data::DraftKey::Cloud(draft->topicRootId), + std::move(draft)); } - void clearLocalDraft() { - clearDraft(Data::DraftKey::Local()); + void clearLocalDraft(MsgId topicRootId) { + clearDraft(Data::DraftKey::Local(topicRootId)); } - void clearCloudDraft() { - clearDraft(Data::DraftKey::Cloud()); + void clearCloudDraft(MsgId topicRootId) { + clearDraft(Data::DraftKey::Cloud(topicRootId)); } - void clearLocalEditDraft() { - clearDraft(Data::DraftKey::LocalEdit()); + void clearLocalEditDraft(MsgId topicRootId) { + clearDraft(Data::DraftKey::LocalEdit(topicRootId)); } void clearDrafts(); - Data::Draft *createCloudDraft(const Data::Draft *fromDraft); - bool skipCloudDraftUpdate(TimeId date) const; - void startSavingCloudDraft(); - void finishSavingCloudDraft(TimeId savedAt); + Data::Draft *createCloudDraft( + MsgId topicRootId, + const Data::Draft *fromDraft); + [[nodiscard]] bool skipCloudDraftUpdate( + MsgId topicRootId, + TimeId date) const; + void startSavingCloudDraft(MsgId topicRootId); + void finishSavingCloudDraft(MsgId topicRootId, TimeId savedAt); void takeLocalDraft(not_null from); - void applyCloudDraft(); - void draftSavedToCloud(); + void applyCloudDraft(MsgId topicRootId); + void draftSavedToCloud(MsgId topicRootId); [[nodiscard]] const Data::ForwardDraft &forwardDraft() const { return _forwardDraft; @@ -553,7 +566,7 @@ private: void viewReplaced(not_null was, Element *now); - void createLocalDraftFromCloud(); + void createLocalDraftFromCloud(MsgId topicRootId); HistoryService *insertJoinedMessage(); void insertMessageToBlocks(not_null item); @@ -603,8 +616,8 @@ private: std::unique_ptr _buildingFrontBlock; Data::HistoryDrafts _drafts; - TimeId _acceptCloudDraftsAfter = 0; - int _savingCloudDraftRequests = 0; + base::flat_map _acceptCloudDraftsAfter; + base::flat_map _savingCloudDraftRequests; Data::ForwardDraft _forwardDraft; QString _topPromotedMessage; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 299a75c52..24ca77c40 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -227,7 +227,7 @@ public: return false; } bool elementShownUnread(not_null view) override { - return view->data()->unread(); + return view->data()->unread(view->data()->history()); } void elementSendBotCommand( const QString &command, @@ -983,7 +983,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { const auto item = view->data(); const auto isSponsored = item->isSponsored(); const auto isUnread = !item->out() - && item->unread() + && item->unread(_history) && (item->history() == _history); const auto withReaction = item->hasUnreadReaction(); const auto yShown = [&](int y) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 12b4abca8..fe62d0bb0 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1217,22 +1217,22 @@ bool HistoryItem::needCheck() const { return (out() && !isEmpty()) || (!isRegular() && history()->peer->isSelf()); } -bool HistoryItem::unread() const { +bool HistoryItem::unread(not_null thread) const { // Messages from myself are always read, unless scheduled. if (history()->peer->isSelf() && !isFromScheduled()) { return false; } - if (out()) { - // Outgoing messages in converted chats are always read. - if (history()->peer->migrateTo()) { + // All messages in converted chats are always read. + if (history()->peer->migrateTo()) { + return false; + } + + if (isRegular()) { + if (!thread->isServerSideUnread(this)) { return false; } - - if (isRegular()) { - if (!history()->isServerSideUnread(this)) { - return false; - } + if (out()) { if (const auto user = history()->peer->asUser()) { if (user->isBot() && !user->isSupport()) { return false; @@ -1246,13 +1246,7 @@ bool HistoryItem::unread() const { return true; } - if (isRegular()) { - if (!history()->isServerSideUnread(this)) { - return false; - } - return true; - } - return (_flags & MessageFlag::ClientSideUnread); + return out() || (_flags & MessageFlag::ClientSideUnread); } bool HistoryItem::showNotification() const { @@ -1262,7 +1256,7 @@ bool HistoryItem::showNotification() const { } return (out() || _history->peer->isSelf()) ? isFromScheduled() - : unread(); + : unread(notificationThread()); } void HistoryItem::markClientSideAsRead() { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 053f69629..504db9fe1 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -150,7 +150,7 @@ public: [[nodiscard]] bool isPinned() const { return _flags & MessageFlag::Pinned; } - [[nodiscard]] bool unread() const; + [[nodiscard]] bool unread(not_null thread) const; [[nodiscard]] bool showNotification() const; void markClientSideAsRead(); [[nodiscard]] bool mentionsMe() const; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index bdde8a8df..cf6d6457b 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1773,6 +1773,9 @@ void HistoryMessage::setReplyFields( && !IsServerMsgId(reply->replyToMsgTop)) { reply->replyToMsgTop = replyToTop; changeReplyToTopCounter(reply, 1); + if (const auto topic = this->topic()) { + topic->maybeSetLastMessage(this); + } } } diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index ac209140f..bbfcc1272 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -1342,10 +1342,19 @@ void HistoryService::setReplyFields( MsgId replyToTop, bool isForumPost) { const auto data = GetDependentData(); - if (!data || IsServerMsgId(data->topId) || isScheduled()) { + if (!data + || (data->topId == replyToTop) + || IsServerMsgId(data->topId) + || isScheduled()) { return; } data->topId = replyToTop; + if (isForumPost) { + data->topicPost = true; + } + if (const auto topic = this->topic()) { + topic->maybeSetLastMessage(this); + } } std::unique_ptr HistoryService::createView( diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d9ed05694..0f80dc1ab 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -948,7 +948,7 @@ void HistoryWidget::initVoiceRecordBar() { }); const auto applyLocalDraft = [=] { - if (_history && _history->localDraft()) { + if (_history && _history->localDraft({})) { applyDraft(); } }; @@ -1657,10 +1657,12 @@ void HistoryWidget::saveDraft(bool delayed) { void HistoryWidget::saveFieldToHistoryLocalDraft() { if (!_history) return; + const auto topicRootId = MsgId(); if (_editMsgId) { _history->setLocalEditDraft(std::make_unique( _field, _editMsgId, + topicRootId, _previewState, _saveEditMsgRequestId)); } else { @@ -1668,11 +1670,12 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { _history->setLocalDraft(std::make_unique( _field, _replyToId, + topicRootId, _previewState)); } else { - _history->clearLocalDraft(); + _history->clearLocalDraft({}); } - _history->clearLocalEditDraft(); + _history->clearLocalEditDraft({}); } } @@ -1760,7 +1763,8 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U MessageCursor cursor = { int(textWithTags.text.size()), int(textWithTags.text.size()), QFIXED_MAX }; _history->setLocalDraft(std::make_unique( textWithTags, - 0, + 0, // replyTo + 0, // topicRootId cursor, Data::PreviewState::Allowed)); applyDraft(); @@ -1782,21 +1786,19 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U auto draft = std::make_unique( textWithTags, to.currentReplyToId, + to.rootId, cursor, Data::PreviewState::Allowed); - if (to.section == Section::Replies) { - history->setDraft( - Data::DraftKey::Replies(to.rootId), - std::move(draft)); - controller()->showRepliesForMessage(history, to.rootId); - } else if (to.section == Section::Scheduled) { + if (to.section == Section::Scheduled) { history->setDraft(Data::DraftKey::Scheduled(), std::move(draft)); controller()->showSection( std::make_shared(history)); } else { history->setLocalDraft(std::move(draft)); - if (history == _history) { + if (to.section == Section::Replies) { + controller()->showRepliesForMessage(history, to.rootId); + } else if (history == _history) { applyDraft(); } else { controller()->showPeerHistory(history->peer); @@ -1878,13 +1880,14 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { return; } - auto draft = !_history - ? nullptr - : _history->localEditDraft() - ? _history->localEditDraft() - : _history->localDraft(); + const auto editDraft = _history ? _history->localEditDraft({}) : nullptr; + const auto draft = editDraft + ? editDraft + : _history + ? _history->localDraft({}) + : nullptr; auto fieldAvailable = canWriteMessage(); - if (!draft || (!_history->localEditDraft() && !fieldAvailable)) { + if (!draft || (!_history->localEditDraft({}) && !fieldAvailable)) { auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0); clearFieldText(0, fieldHistoryAction); _field->setFocus(); @@ -1913,11 +1916,11 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { _previewState = draft->previewState; _replyEditMsg = nullptr; - if (const auto editDraft = _history->localEditDraft()) { + if (const auto editDraft = _history->localEditDraft({})) { setEditMsgId(editDraft->msgId); _replyToId = 0; } else { - _replyToId = readyToForward() ? 0 : _history->localDraft()->msgId; + _replyToId = readyToForward() ? 0 : _history->localDraft({})->msgId; setEditMsgId(0); } updateCmdStartShown(); @@ -2036,7 +2039,7 @@ void HistoryWidget::showHistory( info->inlineReturnTo = wasDialogsEntryState; } sendBotStartCommand(); - _history->clearLocalDraft(); + _history->clearLocalDraft({}); applyDraft(); _send->finishAnimating(); } @@ -2356,10 +2359,10 @@ void HistoryWidget::unregisterDraftSources() { } session().local().unregisterDraftSource( _history, - Data::DraftKey::Local()); + Data::DraftKey::Local({})); session().local().unregisterDraftSource( _history, - Data::DraftKey::LocalEdit()); + Data::DraftKey::LocalEdit({})); } void HistoryWidget::registerDraftSource() { @@ -2380,7 +2383,9 @@ void HistoryWidget::registerDraftSource() { }; session().local().registerDraftSource( _history, - editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(), + (editMsgId + ? Data::DraftKey::LocalEdit({}) + : Data::DraftKey::Local({})), std::move(draftSource)); } @@ -3564,16 +3569,16 @@ void HistoryWidget::saveEditMsg() { cancelEdit(); } })(); - if (const auto editDraft = history->localEditDraft()) { + if (const auto editDraft = history->localEditDraft({})) { if (editDraft->saveRequestId == requestId) { - history->clearLocalEditDraft(); + history->clearLocalEditDraft({}); history->session().local().writeDrafts(history); } } }; const auto fail = [=](const QString &error, mtpRequestId requestId) { - if (const auto editDraft = history->localEditDraft()) { + if (const auto editDraft = history->localEditDraft({})) { if (editDraft->saveRequestId == requestId) { editDraft->saveRequestId = 0; } @@ -3648,6 +3653,7 @@ Api::SendAction HistoryWidget::prepareSendAction( Api::SendOptions options) const { auto result = Api::SendAction(_history, options); result.replyTo = replyToId(); + result.topicRootId = 0; result.options.sendAs = _sendAs ? _history->session().sendAsPeers().resolveChosen( _history->peer).get() @@ -6650,12 +6656,13 @@ void HistoryWidget::replyToMessage(not_null item) { } if (_editMsgId) { - if (auto localDraft = _history->localDraft()) { + if (const auto localDraft = _history->localDraft({})) { localDraft->msgId = item->id; } else { _history->setLocalDraft(std::make_unique( TextWithTags(), item->id, + MsgId(), // topicRootId MessageCursor(), Data::PreviewState::Allowed)); } @@ -6708,9 +6715,10 @@ void HistoryWidget::editMessage(not_null item) { _history->setLocalDraft(std::make_unique( _field, _replyToId, + MsgId(), // topicRootId _previewState)); } else { - _history->clearLocalDraft(); + _history->clearLocalDraft({}); } } @@ -6732,6 +6740,7 @@ void HistoryWidget::editMessage(not_null item) { _history->setLocalEditDraft(std::make_unique( editData, item->id, + MsgId(), // topicRootId cursor, previewState)); applyDraft(); @@ -6811,10 +6820,10 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { refreshTopBarActiveChat(); updateControlsGeometry(); update(); - } else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) { + } else if (const auto localDraft = (_history ? _history->localDraft({}) : nullptr)) { if (localDraft->msgId) { if (localDraft->textWithTags.text.isEmpty()) { - _history->clearLocalDraft(); + _history->clearLocalDraft({}); } else { localDraft->msgId = 0; } @@ -6856,7 +6865,7 @@ void HistoryWidget::cancelEdit() { _replyEditMsg = nullptr; setEditMsgId(0); - _history->clearLocalEditDraft(); + _history->clearLocalEditDraft({}); applyDraft(); if (_saveEditMsgRequestId) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 670fd2b25..bd1e22ded 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1169,16 +1169,18 @@ void ComposeControls::setFieldText( void ComposeControls::saveFieldToHistoryLocalDraft() { const auto key = draftKeyCurrent(); - if (!_history || key == Data::DraftKey::None()) { + if (!_history || !key) { return; } const auto id = _header->getDraftMessageId(); if (_preview && (id || !_field->empty())) { + const auto key = draftKeyCurrent(); _history->setDraft( - draftKeyCurrent(), + key, std::make_unique( _field, _header->getDraftMessageId(), + key.topicRootId(), _preview->state())); } else { _history->clearDraft(draftKeyCurrent()); @@ -1680,15 +1682,14 @@ Data::DraftKey ComposeControls::draftKey(DraftType type) const { switch (_currentDialogsEntryState.section) { case Section::History: - return (type == DraftType::Edit) ? Key::LocalEdit() : Key::Local(); + case Section::Replies: + return (type == DraftType::Edit) + ? Key::LocalEdit(_currentDialogsEntryState.rootId) + : Key::Local(_currentDialogsEntryState.rootId); case Section::Scheduled: return (type == DraftType::Edit) ? Key::ScheduledEdit() : Key::Scheduled(); - case Section::Replies: - return (type == DraftType::Edit) - ? Key::RepliesEdit(_currentDialogsEntryState.rootId) - : Key::Replies(_currentDialogsEntryState.rootId); } return Key::None(); } @@ -2380,11 +2381,13 @@ void ComposeControls::editMessage(not_null item) { const auto previewState = previewPage ? Data::PreviewState::Allowed : Data::PreviewState::EmptyOnEdit; + const auto key = draftKey(DraftType::Edit); _history->setDraft( - draftKey(DraftType::Edit), + key, std::make_unique( editData, item->id, + key.topicRootId(), cursor, previewState)); applyDraft(); @@ -2424,6 +2427,7 @@ void ComposeControls::replyToMessage(FullMsgId id) { std::make_unique( TextWithTags(), id.msg, + key.topicRootId(), MessageCursor(), Data::PreviewState::Allowed)); } @@ -2438,7 +2442,6 @@ void ComposeControls::replyToMessage(FullMsgId id) { void ComposeControls::cancelReplyMessage() { Expects(_history != nullptr); - Expects(draftKeyCurrent() != Data::DraftKey::None()); const auto wasReply = replyingToMessage(); _header->replyToMessage({}); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 104663634..1211d83d7 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -645,7 +645,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { if (forwarded && forwarded->imported) { result.flags |= Flag::Imported; } - // We don't want to pass and update it in Date for now. + // We don't want to pass and update it in Data for now. //if (item->unread()) { // result.flags |= Flag::Unread; //} diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 7ec29fb01..d6a307feb 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -185,7 +185,7 @@ bool SimpleElementDelegate::elementHideReply(not_null view) { bool SimpleElementDelegate::elementShownUnread( not_null view) { - return view->data()->unread(); + return view->data()->unread(view->data()->history()); } void SimpleElementDelegate::elementSendBotCommand( diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 8e4ff214f..2ce5e65f1 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -553,7 +553,7 @@ bool PinnedWidget::listElementHideReply(not_null view) { } bool PinnedWidget::listElementShownUnread(not_null view) { - return view->data()->unread(); + return view->data()->unread(view->data()->history()); } bool PinnedWidget::listIsGoodForAroundPosition( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 46a087875..b70c16332 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -351,8 +351,8 @@ RepliesWidget::RepliesWidget( } RepliesWidget::~RepliesWidget() { - if (_topic && _topic->forum()->creating(_rootId)) { - _topic->forum()->discardCreatingId(_rootId); + if (_topic && _topic->creating()) { + _topic->discard(); _topic = nullptr; } base::take(_sendAction); @@ -434,15 +434,20 @@ void RepliesWidget::setupRootView() { } void RepliesWidget::setupTopicViewer() { - _history->owner().itemIdChanged( + const auto owner = &_history->owner(); + owner->itemIdChanged( ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { if (_rootId == change.oldId) { _rootId = change.newId.msg; + _sendAction = owner->sendActionManager().repliesPainter( + _history, + _rootId); _root = lookupRoot(); if (_topic && _topic->rootId() == change.oldId) { setTopic(_topic->forum()->topicFor(change.newId.msg)); } else { refreshReplies(); + refreshTopBarActiveChat(); } _inner->update(); } @@ -1007,6 +1012,7 @@ Api::SendAction RepliesWidget::prepareSendAction( Api::SendOptions options) const { auto result = Api::SendAction(_history, options); result.replyTo = replyToId(); + result.topicRootId = _rootId; result.options.sendAs = _composeControls->sendAsPeer(); return result; } @@ -1440,7 +1446,7 @@ bool RepliesWidget::preventsClose(Fn &&continueCallback) const { return true; } else if (!_newTopicDiscarded && _topic - && _topic->forum()->creating(_rootId)) { + && _topic->creating()) { const auto weak = Ui::MakeWeak(this); auto sure = [=](Fn &&close) { if (const auto strong = weak.data()) { @@ -1943,14 +1949,7 @@ bool RepliesWidget::listElementHideReply(not_null view) { } bool RepliesWidget::listElementShownUnread(not_null view) { - if (!_root) { - return false; - } - const auto item = view->data(); - const auto till = item->out() - ? _replies->computeOutboxReadTillFull() - : _replies->computeInboxReadTillFull(); - return (item->id > till); + return _replies->isServerSideUnread(view->data()); } bool RepliesWidget::listIsGoodForAroundPosition( diff --git a/Telegram/SourceFiles/history/view/history_view_send_action.cpp b/Telegram/SourceFiles/history/view/history_view_send_action.cpp index dfb941166..5380ccfb5 100644 --- a/Telegram/SourceFiles/history/view/history_view_send_action.cpp +++ b/Telegram/SourceFiles/history/view/history_view_send_action.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_send_action.h" +#include "data/data_forum_topic.h" #include "data/data_session.h" #include "main/main_session.h" #include "history/history.h" @@ -39,13 +40,20 @@ constexpr auto kStatusShowClientsideSpeaking = 6 * crl::time(1000); } // namespace -SendActionPainter::SendActionPainter(not_null history) +SendActionPainter::SendActionPainter( + not_null history, + MsgId rootId) : _history(history) +, _rootId(rootId) , _weak(&_history->session()) , _st(st::dialogsTextStyle) , _sendActionText(st::dialogsTextWidthMin) { } +void SendActionPainter::setTopic(Data::ForumTopic *topic) { + _topic = topic; +} + bool SendActionPainter::updateNeedsAnimating( not_null user, const MTPSendMessageAction &action) { @@ -382,7 +390,7 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) { st::normalFont->height, st::dialogsMiniPreviewTop + st::dialogsMiniPreview); _history->peer->owner().sendActionManager().updateAnimation({ - _history, + _topic ? ((Data::Thread*)_topic) : _history, 0, _sendActionAnimation.width() + _animationLeft, height, diff --git a/Telegram/SourceFiles/history/view/history_view_send_action.h b/Telegram/SourceFiles/history/view/history_view_send_action.h index 03ae7cede..2a0e2e6b3 100644 --- a/Telegram/SourceFiles/history/view/history_view_send_action.h +++ b/Telegram/SourceFiles/history/view/history_view_send_action.h @@ -16,6 +16,10 @@ namespace Main { class Session; } // namespace Main +namespace Data { +class ForumTopic; +} // namespace Data + namespace Api { enum class SendProgressType; struct SendProgress; @@ -25,7 +29,9 @@ namespace HistoryView { class SendActionPainter final { public: - explicit SendActionPainter(not_null history); + explicit SendActionPainter(not_null history, MsgId rootId = 0); + + void setTopic(Data::ForumTopic *topic); bool paint( Painter &p, @@ -53,8 +59,10 @@ public: private: const not_null _history; + const MsgId _rootId = 0; const base::weak_ptr _weak; const style::TextStyle &_st; + Data::ForumTopic *_topic = nullptr; base::flat_map, crl::time> _typing; base::flat_map, crl::time> _speaking; base::flat_map, Api::SendProgress> _sendActions; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 001cb9ed2..a0ac7262a 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -152,7 +152,7 @@ TopBarWidget::TopBarWidget( using AnimationUpdate = Data::SendActionManager::AnimationUpdate; session().data().sendActionManager().animationUpdated( ) | rpl::filter([=](const AnimationUpdate &update) { - return (update.history == _activeChat.key.history()); + return (update.thread == _activeChat.key.thread()); }) | rpl::start_with_next([=] { update(); }, lifetime()); @@ -722,6 +722,7 @@ void TopBarWidget::backClicked() { void TopBarWidget::setActiveChat( ActiveChat activeChat, SendActionPainter *sendAction) { + _sendAction = sendAction; if (_activeChat.key == activeChat.key && _activeChat.section == activeChat.section) { _activeChat = activeChat; @@ -733,7 +734,6 @@ void TopBarWidget::setActiveChat( != activeChat.key.history()); _activeChat = activeChat; - _sendAction = sendAction; _titlePeerText.clear(); _back->clearState(); update(); diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index d33098b7d..e6cf0add8 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -204,29 +204,35 @@ void Controller::setupMigrationViewer() { ) | rpl::filter([=] { return peer->migrateTo() || (peer->migrateFrom() != _migrated); }) | rpl::start_with_next([=] { - const auto window = parentController(); - const auto section = _section; - auto params = Window::SectionShow( - Window::SectionShow::Way::Backward, - anim::type::instant, - anim::activation::background); - if (wrap() == Wrap::Side) { - params.thirdColumn = true; - } - InvokeQueued(_widget, [=] { - window->showSection( - std::make_shared(peer, section), - params); - }); + replaceWith(std::make_shared(peer, _section)); }, lifetime()); } +void Controller::replaceWith(std::shared_ptr memento) { + const auto window = parentController(); + const auto section = _section; + auto params = Window::SectionShow( + Window::SectionShow::Way::Backward, + anim::type::instant, + anim::activation::background); + if (wrap() == Wrap::Side) { + params.thirdColumn = true; + } + InvokeQueued(_widget, [=, memento = std::move(memento)]() mutable { + window->showSection(std::move(memento), params); + }); +} + void Controller::setupTopicViewer() { session().data().itemIdChanged( ) | rpl::start_with_next([=](const Data::Session::IdChange &change) { if (const auto topic = _key.topic()) { - if (topic->rootId() == change.oldId) { - _key = Key(topic->forum()->topicFor(change.newId.msg)); + if (topic->rootId() == change.oldId + || (topic->peer()->id == change.newId.peer + && topic->rootId() == change.newId.msg)) { + const auto now = topic->forum()->topicFor(change.newId.msg); + _key = Key(now); + replaceWith(std::make_shared(now, _section)); } } }, _lifetime); diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index e1dae0794..cdbf53396 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -240,6 +240,8 @@ private: void setupMigrationViewer(); void setupTopicViewer(); + void replaceWith(std::shared_ptr memento); + not_null _widget; Key _key; PeerData *_migrated = nullptr; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 71071689c..acc5ace7b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -577,6 +577,8 @@ Ui::MultiSlideTracker DetailsFiller::fillChannelButtons( } object_ptr DetailsFiller::fill() { + Expects(!_topic || !_topic->creating()); + add(object_ptr(_wrap)); add(CreateSkipWidget(_wrap)); add(setupInfo()); @@ -585,6 +587,7 @@ object_ptr DetailsFiller::fill() { } setupMainButtons(); add(CreateSkipWidget(_wrap)); + return std::move(_wrap); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 73fbbdd3d..d9f22fe6f 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -83,6 +83,9 @@ object_ptr InnerWidget::setupContent( }, _cover->lifetime()); _cover->setOnlineCount(rpl::single(0)); if (_topic) { + if (_topic->creating()) { + return result; + } result->add(SetupDetails(_controller, parent, _topic)); } else { result->add(SetupDetails(_controller, parent, _peer)); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 8488924c7..abbcf0201 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -555,12 +555,14 @@ bool MainWidget::shareUrl( QFIXED_MAX }; const auto history = peer->owner().history(peer); + const auto topicRootId = 0; history->setLocalDraft(std::make_unique( textWithTags, - 0, + 0, // replyTo + topicRootId, cursor, Data::PreviewState::Allowed)); - history->clearLocalEditDraft(); + history->clearLocalEditDraft(topicRootId); history->session().changes().historyUpdated( history, Data::HistoryUpdate::Flag::LocalDraftSet); @@ -587,12 +589,14 @@ bool MainWidget::inlineSwitchChosen( int(botAndQuery.size()), QFIXED_MAX }; + const auto topicRootId = 0; h->setLocalDraft(std::make_unique( textWithTags, - 0, + 0, // replyTo + topicRootId, cursor, Data::PreviewState::Allowed)); - h->clearLocalEditDraft(); + h->clearLocalEditDraft(topicRootId); h->session().changes().historyUpdated( h, Data::HistoryUpdate::Flag::LocalDraftSet); diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 5ec8d0aba..ff27d2159 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -1587,7 +1587,7 @@ void FormController::uploadEncryptedFile( auto prepared = std::make_shared( TaskId(), file.uploadData->fileId, - FileLoadTo(PeerId(), Api::SendOptions(), MsgId(), MsgId()), + FileLoadTo(PeerId(), Api::SendOptions(), MsgId(), MsgId(), MsgId()), TextWithTags(), std::shared_ptr(nullptr)); prepared->type = SendMediaType::Secure; diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index d45245033..c02b87c7c 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -200,15 +200,18 @@ struct FileLoadTo { PeerId peer, Api::SendOptions options, MsgId replyTo, + MsgId topicRootId, MsgId replaceMediaOf) : peer(peer) , options(options) , replyTo(replyTo) + , topicRootId(topicRootId) , replaceMediaOf(replaceMediaOf) { } PeerId peer; Api::SendOptions options; MsgId replyTo; + MsgId topicRootId; MsgId replaceMediaOf; }; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 22d66ac1e..578dcc348 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -1025,17 +1025,20 @@ std::unique_ptr Account::readMtpConfig() { template void EnumerateDrafts( const Data::HistoryDrafts &map, - Data::Draft *cloudDraft, bool supportMode, const base::flat_map &sources, Callback &&callback) { for (const auto &[key, draft] : map) { - if (key == Data::DraftKey::Cloud() || sources.contains(key)) { - continue; - } else if (key == Data::DraftKey::Local() - && !supportMode - && Data::draftsAreEqual(draft.get(), cloudDraft)) { + if (key.isCloud() || sources.contains(key)) { continue; + } else if (key.isLocal() + && (!supportMode || key.topicRootId())) { + const auto i = map.find( + Data::DraftKey::Cloud(key.topicRootId())); + const auto cloud = (i != end(map)) ? i->second.get() : nullptr; + if (Data::DraftsAreEqual(draft.get(), cloud)) { + continue; + } } callback( key, @@ -1085,10 +1088,6 @@ void Account::unregisterDraftSource( void Account::writeDrafts(not_null history) { const auto peerId = history->peer->id; const auto &map = history->draftsMap(); - const auto cloudIt = map.find(Data::DraftKey::Cloud()); - const auto cloudDraft = (cloudIt != end(map)) - ? cloudIt->second.get() - : nullptr; const auto supportMode = _owner->session().supportMode(); const auto sourcesIt = _draftSources.find(history); const auto &sources = (sourcesIt != _draftSources.end()) @@ -1097,7 +1096,6 @@ void Account::writeDrafts(not_null history) { auto count = 0; EnumerateDrafts( map, - cloudDraft, supportMode, sources, [&](auto&&...) { ++count; }); @@ -1133,7 +1131,6 @@ void Account::writeDrafts(not_null history) { }; EnumerateDrafts( map, - cloudDraft, supportMode, sources, sizeCallback); @@ -1159,7 +1156,6 @@ void Account::writeDrafts(not_null history) { }; EnumerateDrafts( map, - cloudDraft, supportMode, sources, writeCallback); @@ -1173,10 +1169,6 @@ void Account::writeDrafts(not_null history) { void Account::writeDraftCursors(not_null history) { const auto peerId = history->peer->id; const auto &map = history->draftsMap(); - const auto cloudIt = map.find(Data::DraftKey::Cloud()); - const auto cloudDraft = (cloudIt != end(map)) - ? cloudIt->second.get() - : nullptr; const auto supportMode = _owner->session().supportMode(); const auto sourcesIt = _draftSources.find(history); const auto &sources = (sourcesIt != _draftSources.end()) @@ -1185,7 +1177,6 @@ void Account::writeDraftCursors(not_null history) { auto count = 0; EnumerateDrafts( map, - cloudDraft, supportMode, sources, [&](auto&&...) { ++count; }); @@ -1223,7 +1214,6 @@ void Account::writeDraftCursors(not_null history) { }; EnumerateDrafts( map, - cloudDraft, supportMode, sources, writeCallback); @@ -1282,7 +1272,7 @@ void Account::readDraftCursors(PeerId peerId, Data::HistoryDrafts &map) { ? Data::DraftKey::FromSerialized(keyValue) : keysOld ? Data::DraftKey::FromSerializedOld(keyValueOld) - : Data::DraftKey::Local(); + : Data::DraftKey::Local(0); qint32 position = 0, anchor = 0, scroll = QFIXED_MAX; draft.stream >> position >> anchor >> scroll; if (const auto i = map.find(key); i != end(map)) { @@ -1309,13 +1299,14 @@ void Account::readDraftCursorsLegacy( return; } - if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) { + if (const auto i = map.find(Data::DraftKey::Local({})); i != end(map)) { i->second->cursor = MessageCursor( localPosition, localAnchor, localScroll); } - if (const auto i = map.find(Data::DraftKey::LocalEdit()); i != end(map)) { + if (const auto i = map.find(Data::DraftKey::LocalEdit({})) + ; i != end(map)) { i->second->cursor = MessageCursor( editPosition, editAnchor, @@ -1327,7 +1318,7 @@ void Account::readDraftsWithCursors(not_null history) { const auto guard = gsl::finally([&] { if (const auto migrated = history->migrateFrom()) { readDraftsWithCursors(migrated); - migrated->clearLocalEditDraft(); + migrated->clearLocalEditDraft({}); history->takeLocalDraft(migrated); } }); @@ -1396,10 +1387,11 @@ void Account::readDraftsWithCursors(not_null history) { const auto key = keysOld ? Data::DraftKey::FromSerializedOld(keyValueOld) : Data::DraftKey::FromSerialized(keyValue); - if (key && key != Data::DraftKey::Cloud()) { + if (key && !key.isCloud()) { map.emplace(key, std::make_unique( data, messageId, + key.topicRootId(), MessageCursor(), previewState)); } @@ -1457,24 +1449,31 @@ void Account::readDraftsWithCursorsLegacy( editTagsSerialized, editData.text.size()); + const auto topicRootId = MsgId(); auto map = base::flat_map>(); if (!msgData.text.isEmpty() || msgReplyTo) { - map.emplace(Data::DraftKey::Local(), std::make_unique( - msgData, - msgReplyTo, - MessageCursor(), - (msgPreviewCancelled - ? Data::PreviewState::Cancelled - : Data::PreviewState::Allowed))); + map.emplace( + Data::DraftKey::Local(topicRootId), + std::make_unique( + msgData, + msgReplyTo, + topicRootId, + MessageCursor(), + (msgPreviewCancelled + ? Data::PreviewState::Cancelled + : Data::PreviewState::Allowed))); } if (editMsgId) { - map.emplace(Data::DraftKey::LocalEdit(), std::make_unique( - editData, - editMsgId, - MessageCursor(), - (editPreviewCancelled - ? Data::PreviewState::Cancelled - : Data::PreviewState::Allowed))); + map.emplace( + Data::DraftKey::LocalEdit(topicRootId), + std::make_unique( + editData, + editMsgId, + topicRootId, + MessageCursor(), + (editPreviewCancelled + ? Data::PreviewState::Cancelled + : Data::PreviewState::Allowed))); } readDraftCursors(peerId, map); history->setDraftsMap(std::move(map)); diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index 3c2cead33..5c4a57160 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -47,6 +47,7 @@ namespace { constexpr auto kOccupyFor = TimeId(60); constexpr auto kReoccupyEach = 30 * crl::time(1000); constexpr auto kMaxSupportInfoLength = MaxMessageSize * 4; +constexpr auto kTopicRootId = MsgId(0); class EditInfoBox : public Ui::BoxContent { public: @@ -157,7 +158,8 @@ Data::Draft OccupiedDraft(const QString &normalizedName) { + QString::number(OccupationTag()) + ";n:" + normalizedName }, - MsgId(0), + MsgId(0), // replyTo + kTopicRootId, MessageCursor(), Data::PreviewState::Allowed }; @@ -176,7 +178,7 @@ uint32 ParseOccupationTag(History *history) { if (!TrackHistoryOccupation(history)) { return 0; } - const auto draft = history->cloudDraft(); + const auto draft = history->cloudDraft(kTopicRootId); if (!draft) { return 0; } @@ -202,7 +204,7 @@ QString ParseOccupationName(History *history) { if (!TrackHistoryOccupation(history)) { return QString(); } - const auto draft = history->cloudDraft(); + const auto draft = history->cloudDraft(kTopicRootId); if (!draft) { return QString(); } @@ -228,7 +230,7 @@ TimeId OccupiedBySomeoneTill(History *history) { if (!TrackHistoryOccupation(history)) { return 0; } - const auto draft = history->cloudDraft(); + const auto draft = history->cloudDraft(kTopicRootId); if (!draft) { return 0; } @@ -340,7 +342,7 @@ void Helper::updateOccupiedHistory( not_null controller, History *history) { if (isOccupiedByMe(_occupiedHistory)) { - _occupiedHistory->clearCloudDraft(); + _occupiedHistory->clearCloudDraft(kTopicRootId); _session->api().saveDraftToCloudDelayed(_occupiedHistory); } _occupiedHistory = history; @@ -364,7 +366,7 @@ void Helper::occupyInDraft() { && !isOccupiedBySomeone(_occupiedHistory) && !_supportName.isEmpty()) { const auto draft = OccupiedDraft(_supportNameNormalized); - _occupiedHistory->createCloudDraft(&draft); + _occupiedHistory->createCloudDraft(kTopicRootId, &draft); _session->api().saveDraftToCloudDelayed(_occupiedHistory); _reoccupyTimer.callEach(kReoccupyEach); } @@ -373,7 +375,7 @@ void Helper::occupyInDraft() { void Helper::reoccupy() { if (isOccupiedByMe(_occupiedHistory)) { const auto draft = OccupiedDraft(_supportNameNormalized); - _occupiedHistory->createCloudDraft(&draft); + _occupiedHistory->createCloudDraft(kTopicRootId, &draft); _session->api().saveDraftToCloudDelayed(_occupiedHistory); } } diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 57f7616ec..07e0b211d 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -979,6 +979,10 @@ void Manager::notificationActivated( const auto window = session->windows().front(); const auto history = session->data().history( id.contextId.peerId); + const auto item = history->owner().message( + history->peer, + id.msgId); + const auto topic = item ? item->topic() : nullptr; if (!reply.text.isEmpty()) { // #TODO forum notifications const auto replyToId = (id.msgId > 0 @@ -988,6 +992,7 @@ void Manager::notificationActivated( auto draft = std::make_unique( reply, replyToId, + (topic ? topic->rootId() : 0), MessageCursor{ int(reply.text.size()), int(reply.text.size()), @@ -1057,16 +1062,18 @@ void Manager::notificationReplied( return; } const auto history = session->data().history(id.contextId.peerId); + const auto item = history->owner().message(history->peer, id.msgId); + const auto topic = item ? item->topic() : nullptr; auto message = Api::MessageToSend(Api::SendAction(history)); message.textWithTags = reply; message.action.replyTo = (id.msgId > 0 && !history->peer->isUser()) ? id.msgId : id.contextId.topicRootId; + message.action.topicRootId = topic ? topic->rootId() : 0; message.action.clearDraft = false; history->session().api().sendMessage(std::move(message)); - const auto item = history->owner().message(history->peer, id.msgId); if (item && item->isUnreadMention() && !item->isIncomingUnreadMedia()) { history->session().api().markContentsRead(item); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 1d3181c2a..6a2ee6fe7 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -844,14 +844,16 @@ void Filler::addCreatePoll() { ? SendMenu::Type::SilentOnly : SendMenu::Type::Scheduled; const auto flag = PollData::Flags(); + const auto topicRootId = _request.rootId; const auto replyToId = _request.currentReplyToId ? _request.currentReplyToId - : _request.rootId; + : topicRootId; auto callback = [=] { PeerMenuCreatePoll( controller, peer, replyToId, + topicRootId, flag, flag, source, @@ -1168,6 +1170,7 @@ void PeerMenuCreatePoll( not_null controller, not_null peer, MsgId replyToId, + MsgId topicRootId, PollData::Flags chosen, PollData::Flags disabled, Api::SendType sendType, @@ -1194,8 +1197,9 @@ void PeerMenuCreatePoll( result.options); action.clearDraft = false; action.replyTo = replyToId; - if (const auto localDraft = action.history->localDraft()) { - action.clearDraft = localDraft->textWithTags.text.isEmpty(); + action.topicRootId = topicRootId; + if (const auto local = action.history->localDraft(topicRootId)) { + action.clearDraft = local->textWithTags.text.isEmpty(); } const auto api = &peer->session().api(); api->polls().create(result.poll, action, crl::guard(weak, [=] { diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index b314082a1..ed3ce1103 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -69,6 +69,7 @@ void PeerMenuCreatePoll( not_null controller, not_null peer, MsgId replyToId = 0, + MsgId topicRootId = 0, PollData::Flags chosen = PollData::Flags(), PollData::Flags disabled = PollData::Flags(), Api::SendType sendType = Api::SendType::Normal, diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 9ab11ccb3..d8b1f4671 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 9ab11ccb36b7d03a3a24ebcd18d2f13b03fc3682 +Subproject commit d8b1f46715e5fcaf781b76ecbc386cbe31492287