diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 65826aa2f..b45474fb5 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -169,9 +169,7 @@ void SendBotCallbackData( void HideSingleUseKeyboard( not_null controller, not_null item) { - controller->content()->hideSingleUseKeyboard( - item->history()->peer, - item->id); + controller->content()->hideSingleUseKeyboard(item->fullId()); } } // namespace @@ -312,7 +310,9 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::Default: { // Copy string before passing it to the sending method // because the original button can be destroyed inside. - const auto replyTo = item->isRegular() ? item->id : 0; + const auto replyTo = item->isRegular() + ? item->fullId() + : FullMsgId(); controller->content()->sendBotCommand({ .peer = item->history()->peer, .command = QString(button->text), @@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::RequestPhone: { HideSingleUseKeyboard(controller, item); - const auto itemId = item->id; + const auto itemId = item->fullId(); const auto topicRootId = item->topicRootId(); const auto history = item->history(); controller->show(Ui::MakeConfirmBox({ @@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { auto action = Api::SendAction(history); action.clearDraft = false; action.replyTo = { - .msgId = itemId, + .messageId = itemId, .topicRootId = topicRootId, }; history->session().api().shareContact( @@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { chosen |= PollData::Flag::Quiz; } } - const auto replyToId = MsgId(0); - const auto topicRootId = MsgId(0); + const auto replyTo = FullReplyTo(); Window::PeerMenuCreatePoll( controller, item->history()->peer, - replyToId, - topicRootId, + replyTo, chosen, disabled); } break; diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp index 0a44a916f..cfb1e7220 100644 --- a/Telegram/SourceFiles/api/api_common.cpp +++ b/Telegram/SourceFiles/api/api_common.cpp @@ -19,8 +19,8 @@ SendAction::SendAction( SendOptions options) : history(thread->owningHistory()) , options(options) -, replyTo({ .msgId = thread->topicRootId() }) { - replyTo.topicRootId = replyTo.msgId; +, replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) { + replyTo.topicRootId = replyTo.messageId.msg; } SendOptions DefaultSendWhenOnlineOptions() { @@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() { } MTPInputReplyTo SendAction::mtpReplyTo() const { - return Data::ReplyToForMTP(&history->owner(), replyTo); + return Data::ReplyToForMTP(history, replyTo); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 227b6e9db..7699894cf 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -43,7 +43,7 @@ void Polls::create( const auto history = action.history; const auto peer = history->peer; - const auto topicRootId = action.replyTo.msgId + const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; auto sendFlags = MTPmessages_SendMedia::Flags(0); diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 291a2675a..09001ae35 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -368,9 +368,9 @@ void SendConfirmedFile( if (!isEditing) { const auto histories = &session->data().histories(); - file->to.replyTo.msgId = histories->convertTopicReplyToId( + file->to.replyTo.messageId = histories->convertTopicReplyToId( history, - file->to.replyTo.msgId); + file->to.replyTo.messageId); file->to.replyTo.topicRootId = histories->convertTopicReplyToId( history, file->to.replyTo.topicRootId); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index affdd5524..006a99d51 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2134,16 +2134,12 @@ void ApiWrap::saveDraftsToCloud() { } auto flags = MTPmessages_SaveDraft::Flags(0); - auto replyFlags = MTPDinputReplyToMessage::Flags(0); auto &textWithTags = cloudDraft->textWithTags; if (cloudDraft->previewState != Data::PreviewState::Allowed) { flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; } - if (cloudDraft->msgId || cloudDraft->topicRootId) { + if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) { flags |= MTPmessages_SaveDraft::Flag::f_reply_to; - if (cloudDraft->topicRootId) { - replyFlags |= MTPDinputReplyToMessage::Flag::f_top_msg_id; - } } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; @@ -2156,15 +2152,7 @@ void ApiWrap::saveDraftsToCloud() { history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), - MTP_inputReplyToMessage( - MTP_flags(replyFlags), - MTP_int(cloudDraft->msgId - ? cloudDraft->msgId - : cloudDraft->topicRootId), - MTP_int(cloudDraft->topicRootId), - MTPInputPeer(), // reply_to_peer_id - MTPstring(), // quote_text - MTPVector()), // quote_entities + ReplyToForMTP(history, cloudDraft->reply), history->peer->input, MTP_string(textWithTags.text), entities @@ -3586,9 +3574,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { action.generateLocal = true; sendAction(action); - const auto replyToId = action.replyTo.msgId; - const auto replyTo = replyToId - ? peer->owner().message(peer, replyToId) + const auto replyTo = action.replyTo.messageId + ? peer->owner().message(action.replyTo.messageId) : nullptr; const auto topicRootId = replyTo ? replyTo->topicRootId() @@ -3789,7 +3776,7 @@ void ApiWrap::sendInlineResult( ? (*localMessageId) : _session->data().nextLocalMessageId()); const auto randomId = base::RandomValue(); - const auto topicRootId = action.replyTo.msgId + const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index f0282306b..9b375e4c8 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -71,7 +71,7 @@ AdminLog::OwnedItem GenerateItem( not_null delegate, not_null history, PeerId from, - MsgId replyTo, + FullMsgId replyTo, const QString &text) { Expects(history->peer->isUser()); @@ -81,7 +81,7 @@ AdminLog::OwnedItem GenerateItem( | MessageFlag::HasFromId | MessageFlag::HasReplyInfo), UserId(), // via - FullReplyTo{ .msgId = replyTo }, + FullReplyTo{ .messageId = replyTo }, base::unixtime::now(), // date from, QString(), // postAuthor @@ -143,13 +143,13 @@ void AddMessage( GenerateUser( history, tr::lng_settings_chat_message_reply_from(tr::now)), - 0, + FullMsgId(), tr::lng_settings_chat_message_reply(tr::now)); auto message = GenerateItem( state->delegate.get(), history, history->peer->id, - state->reply->data()->fullId().msg, + state->reply->data()->fullId(), tr::lng_settings_chat_message(tr::now)); const auto view = message.get(); state->item = std::move(message); diff --git a/Telegram/SourceFiles/chat_helpers/bot_command.h b/Telegram/SourceFiles/chat_helpers/bot_command.h index d5e0c9511..b91ef6903 100644 --- a/Telegram/SourceFiles/chat_helpers/bot_command.h +++ b/Telegram/SourceFiles/chat_helpers/bot_command.h @@ -16,7 +16,7 @@ struct SendCommandRequest { not_null peer; QString command; FullMsgId context; - MsgId replyTo = 0; + FullReplyTo replyTo; }; [[nodiscard]] QString WrapCommandInChat( diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index e3bbba7d1..f79661971 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const { .peer = peer, .command = _cmd, .context = my.itemId, - .replyTo = 0, }); } } diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index f0b937ca8..807af9404 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/message_field.h" #include "history/history.h" #include "history/history_widget.h" +#include "history/history_item_components.h" #include "main/main_session.h" #include "data/data_session.h" #include "mainwidget.h" @@ -21,14 +22,12 @@ namespace Data { Draft::Draft( const TextWithTags &textWithTags, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, const MessageCursor &cursor, PreviewState previewState, mtpRequestId saveRequestId) : textWithTags(textWithTags) -, msgId(msgId) -, topicRootId(topicRootId) +, reply(std::move(reply)) , cursor(cursor) , previewState(previewState) , saveRequestId(saveRequestId) { @@ -36,13 +35,11 @@ Draft::Draft( Draft::Draft( not_null field, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, PreviewState previewState, mtpRequestId saveRequestId) : textWithTags(field->getTextWithTags()) -, msgId(msgId) -, topicRootId(topicRootId) +, reply(std::move(reply)) , cursor(field) , previewState(previewState) { } @@ -64,22 +61,26 @@ void ApplyPeerCloudDraft( session, draft.ventities().value_or_empty())) }; - auto replyTo = MsgId(); - if (const auto reply = draft.vreply_to()) { - reply->match([&](const MTPDmessageReplyHeader &data) { - if (!data.vreply_to_peer_id() - || (peerFromMTP(*data.vreply_to_peer_id()) == peerId)) { - replyTo = data.vreply_to_msg_id().value_or_empty(); - } else { - // #TODO replies - } - }, [&](const MTPDmessageReplyStoryHeader &data) { - }); - } + const auto reply = draft.vreply_to() + ? ReplyFieldsFromMTP(history, *draft.vreply_to()) + : ReplyFields(); + const auto replyPeerId = reply.externalPeerId + ? reply.externalPeerId + : peerId; auto cloudDraft = std::make_unique( textWithTags, - replyTo, - topicRootId, + FullReplyTo{ + .messageId = FullMsgId(replyPeerId, reply.messageId), + .quote = TextWithTags{ + reply.quote.text, + TextUtilities::ConvertEntitiesToTextTags( + reply.quote.entities), + }, + .storyId = (reply.storyId + ? FullStoryId(replyPeerId, reply.storyId) + : FullStoryId()), + .topicRootId = topicRootId, + }, MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax), (draft.is_no_webpage() ? Data::PreviewState::Cancelled diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index 5af044019..ef295807c 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_msg_id.h" + namespace Ui { class InputField; } // namespace Ui @@ -38,22 +40,19 @@ struct Draft { Draft() = default; Draft( const TextWithTags &textWithTags, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, const MessageCursor &cursor, PreviewState previewState, mtpRequestId saveRequestId = 0); Draft( not_null field, - MsgId msgId, - MsgId topicRootId, + FullReplyTo reply, PreviewState previewState, mtpRequestId saveRequestId = 0); TimeId date = 0; TextWithTags textWithTags; - MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft - MsgId topicRootId = 0; + FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft. MessageCursor cursor; PreviewState previewState = PreviewState::Allowed; mtpRequestId saveRequestId = 0; @@ -167,7 +166,8 @@ using HistoryDrafts = base::flat_map>; [[nodiscard]] inline bool DraftIsNull(const Draft *draft) { return !draft - || (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text)); + || (!draft->reply.messageId + && DraftStringIsEmpty(draft->textWithTags.text)); } [[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) { @@ -179,7 +179,7 @@ using HistoryDrafts = base::flat_map>; return false; } return (a->textWithTags == b->textWithTags) - && (a->msgId == b->msgId) + && (a->reply == b->reply) && (a->previewState == b->previewState); } diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 7443aab62..ab002d24f 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -33,8 +33,9 @@ constexpr auto kReadRequestTimeout = 3 * crl::time(1000); } // namespace MTPInputReplyTo ReplyToForMTP( - not_null owner, + not_null history, FullReplyTo replyTo) { + const auto owner = &history->owner(); if (replyTo.storyId) { if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) { if (const auto user = peer->asUser()) { @@ -43,15 +44,19 @@ MTPInputReplyTo ReplyToForMTP( MTP_int(replyTo.storyId.story)); } } - } else if (replyTo.msgId || replyTo.topicRootId) { + } else if (replyTo.messageId || replyTo.topicRootId) { + const auto external = (replyTo.messageId.peer != history->peer->id); using Flag = MTPDinputReplyToMessage::Flag; - return MTP_inputReplyToMessage( // #TODO replies - (replyTo.topicRootId - ? MTP_flags(Flag::f_top_msg_id) - : MTP_flags(0)), - MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId), + return MTP_inputReplyToMessage( + MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag()) + | (external ? Flag::f_reply_to_peer_id : Flag())), + MTP_int(replyTo.messageId + ? replyTo.messageId.msg + : replyTo.topicRootId), MTP_int(replyTo.topicRootId), - MTPInputPeer(), // reply_to_peer_id + (external + ? owner->peer(replyTo.messageId.peer)->input + : MTPInputPeer()), MTPstring(), // quote_text MTPVector()); // quote_entities } @@ -914,7 +919,7 @@ int Histories::sendPreparedMessage( not_null history, FullReplyTo replyTo, uint64 randomId, - Fn, FullReplyTo)> message, + Fn, FullReplyTo)> message, Fn done, Fn fail) { if (isCreatingTopic(history, replyTo.topicRootId)) { @@ -929,7 +934,7 @@ int Histories::sendPreparedMessage( } i->second.push_back({ .randomId = randomId, - .replyTo = replyTo.msgId, + .replyTo = replyTo.messageId, .message = std::move(message), .done = std::move(done), .fail = std::move(fail), @@ -939,11 +944,11 @@ int Histories::sendPreparedMessage( return id; } const auto realReplyTo = FullReplyTo{ - .msgId = convertTopicReplyToId(history, replyTo.msgId), - .topicRootId = convertTopicReplyToId(history, replyTo.topicRootId), + .messageId = convertTopicReplyToId(history, replyTo.messageId), .storyId = replyTo.storyId, + .topicRootId = convertTopicReplyToId(history, replyTo.topicRootId), }; - return v::match(message(_owner, realReplyTo), [&](const auto &request) { + return v::match(message(history, realReplyTo), [&](const auto &request) { const auto type = RequestType::Send; return sendRequest(history, type, [=](Fn finish) { const auto session = &_owner->session(); @@ -987,7 +992,7 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) { sendPreparedMessage( history, FullReplyTo{ - .msgId = entry.replyTo, + .messageId = entry.replyTo, .topicRootId = realRoot, }, entry.randomId, @@ -1009,6 +1014,15 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) { } } +FullMsgId Histories::convertTopicReplyToId( + not_null history, + FullMsgId replyToId) const { + const auto id = (history->peer->id == replyToId.peer) + ? convertTopicReplyToId(history, replyToId.msg) + : replyToId.msg; + return { replyToId.peer, id }; +} + MsgId Histories::convertTopicReplyToId( not_null history, MsgId replyToId) const { diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index c98dc352c..336dcffc3 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -27,7 +27,7 @@ class Session; class Folder; [[nodiscard]] MTPInputReplyTo ReplyToForMTP( - not_null owner, + not_null history, FullReplyTo replyTo); class Histories final { @@ -108,7 +108,7 @@ public: not_null history, FullReplyTo replyTo, uint64 randomId, - Fn, FullReplyTo)> message, + Fn, FullReplyTo)> message, Fn done, Fn fail); @@ -116,14 +116,17 @@ public: }; template static auto PrepareMessage(const Args &...args) - -> Fn, FullReplyTo)> { - return [=](not_null owner, FullReplyTo replyTo) + -> Fn, FullReplyTo)> { + return [=](not_null history, FullReplyTo replyTo) -> RequestType { - return { ReplaceReplyIds(owner, args, replyTo)... }; + return { ReplaceReplyIds(history, args, replyTo)... }; }; } void checkTopicCreated(FullMsgId rootId, MsgId realRoot); + [[nodiscard]] FullMsgId convertTopicReplyToId( + not_null history, + FullMsgId replyToId) const; [[nodiscard]] MsgId convertTopicReplyToId( not_null history, MsgId replyToId) const; @@ -152,8 +155,8 @@ private: }; struct DelayedByTopicMessage { uint64 randomId = 0; - MsgId replyTo = 0; - Fn, FullReplyTo)> message; + FullMsgId replyTo; + Fn, FullReplyTo)> message; Fn done; Fn fail; int requestId = 0; @@ -169,11 +172,11 @@ private: template static auto ReplaceReplyIds( - not_null owner, + not_null history, Arg arg, FullReplyTo replyTo) { if constexpr (std::is_same_v) { - return ReplyToForMTP(owner, replyTo); + return ReplyToForMTP(history, replyTo); } else { return arg; } diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 6a9bc0d1c..5d367d83e 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_peer_id.h" +#include "ui/text/text_entity.h" struct MsgId { constexpr MsgId() noexcept = default; @@ -67,21 +68,6 @@ struct FullStoryId { friend inline bool operator==(FullStoryId, FullStoryId) = default; }; -struct FullReplyTo { - MsgId msgId = 0; - MsgId topicRootId = 0; - FullStoryId storyId; - - [[nodiscard]] bool valid() const { - return msgId || (storyId && peerIsUser(storyId.peer)); - } - explicit operator bool() const { - return valid(); - } - friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default; - friend inline bool operator==(FullReplyTo, FullReplyTo) = default; -}; - constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58)); constexpr auto ClientMsgIds = (1LL << 31); constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds); @@ -169,6 +155,22 @@ struct FullMsgId { Q_DECLARE_METATYPE(FullMsgId); +struct FullReplyTo { + FullMsgId messageId; + TextWithTags quote; + FullStoryId storyId; + MsgId topicRootId = 0; + + [[nodiscard]] bool valid() const { + return messageId || (storyId && peerIsUser(storyId.peer)); + } + explicit operator bool() const { + return valid(); + } + friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default; + friend inline bool operator==(FullReplyTo, FullReplyTo) = default; +}; + struct GlobalMsgId { FullMsgId itemId; uint64 sessionUniqueId = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 4493cb5e1..d542cfca3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -108,8 +108,7 @@ struct EntryState { Key key; Section section = Section::History; FilterId filterId = 0; - MsgId rootId = 0; - MsgId currentReplyToId = 0; + FullReplyTo currentReplyTo; friend inline constexpr auto operator<=>(EntryState, EntryState) noexcept = default; 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 2b49f8c1c..deb786a28 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -659,7 +659,7 @@ not_null InnerWidget::elementPathShiftGradient() { return _pathGradient.get(); } -void InnerWidget::elementReplyTo(const FullMsgId &to) { +void InnerWidget::elementReplyTo(const FullReplyTo &to) { } void InnerWidget::elementStartInteraction(not_null view) { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index 684fba84f..36c321810 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -127,7 +127,7 @@ public: void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; - void elementReplyTo(const FullMsgId &to) override; + void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction( not_null view) override; void elementStartPremium( diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 6ff35aea5..adafbc39b 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -177,8 +177,7 @@ void History::takeLocalDraft(not_null from) { && !_drafts.contains(Data::DraftKey::Local(topicRootId))) { // Edit and reply to drafts can't migrate. // Cloud drafts do not migrate automatically. - draft->msgId = 0; - + draft->reply = FullReplyTo(); setLocalDraft(std::move(draft)); } from->clearLocalDraft(topicRootId); @@ -194,6 +193,7 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) { return; } + draft->reply.topicRootId = topicRootId; auto existing = localDraft(topicRootId); if (Data::DraftIsNull(existing) || !existing->date @@ -201,15 +201,13 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) { if (!existing) { setLocalDraft(std::make_unique( draft->textWithTags, - draft->msgId, - topicRootId, + draft->reply, draft->cursor, draft->previewState)); existing = localDraft(topicRootId); } else if (existing != draft) { existing->textWithTags = draft->textWithTags; - existing->msgId = draft->msgId; - existing->topicRootId = draft->topicRootId; + existing->reply = draft->reply; existing->cursor = draft->cursor; existing->previewState = draft->previewState; } @@ -277,8 +275,7 @@ Data::Draft *History::createCloudDraft( if (Data::DraftIsNull(fromDraft)) { setCloudDraft(std::make_unique( TextWithTags(), - 0, - topicRootId, + FullReplyTo(), MessageCursor(), Data::PreviewState::Allowed)); cloudDraft(topicRootId)->date = TimeId(0); @@ -287,18 +284,18 @@ Data::Draft *History::createCloudDraft( if (!existing) { setCloudDraft(std::make_unique( fromDraft->textWithTags, - fromDraft->msgId, - topicRootId, + fromDraft->reply, fromDraft->cursor, fromDraft->previewState)); existing = cloudDraft(topicRootId); } else if (existing != fromDraft) { existing->textWithTags = fromDraft->textWithTags; - existing->msgId = fromDraft->msgId; + existing->reply = fromDraft->reply; existing->cursor = fromDraft->cursor; existing->previewState = fromDraft->previewState; } existing->date = base::unixtime::now(); + existing->reply.topicRootId = topicRootId; } if (const auto thread = threadFor(topicRootId)) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 933832d79..2ef89cb46 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -329,17 +329,17 @@ public: } void setLocalDraft(std::unique_ptr &&draft) { setDraft( - Data::DraftKey::Local(draft->topicRootId), + Data::DraftKey::Local(draft->reply.topicRootId), std::move(draft)); } void setLocalEditDraft(std::unique_ptr &&draft) { setDraft( - Data::DraftKey::LocalEdit(draft->topicRootId), + Data::DraftKey::LocalEdit(draft->reply.topicRootId), std::move(draft)); } void setCloudDraft(std::unique_ptr &&draft) { setDraft( - Data::DraftKey::Cloud(draft->topicRootId), + Data::DraftKey::Cloud(draft->reply.topicRootId), std::move(draft)); } void clearLocalDraft(MsgId topicRootId) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 23d3f1c4e..d4b3aaf46 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -287,7 +287,7 @@ public: return _widget->elementPathShiftGradient(); } - void elementReplyTo(const FullMsgId &to) override { + void elementReplyTo(const FullReplyTo &to) override { if (_widget) { _widget->elementReplyTo(to); } @@ -2206,7 +2206,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }(); if (canReply) { _menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { - _widget->replyToMessage(itemId); + _widget->replyToMessage({ itemId }); }, &st::menuIconReply); } const auto repliesCount = item->repliesCount(); @@ -3481,7 +3481,7 @@ not_null HistoryInner::elementPathShiftGradient() { return _pathGradient.get(); } -void HistoryInner::elementReplyTo(const FullMsgId &to) { +void HistoryInner::elementReplyTo(const FullReplyTo &to) { return _widget->replyToMessage(to); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index e6855ef92..38d68cdde 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -159,7 +159,7 @@ public: void elementHandleViaClick(not_null bot); bool elementIsChatWide(); not_null elementPathShiftGradient(); - void elementReplyTo(const FullMsgId &to); + void elementReplyTo(const FullReplyTo &to); void elementStartInteraction(not_null view); void elementStartPremium( not_null view, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0c106f19f..b1f1f1a61 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -125,23 +125,23 @@ void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { } struct HistoryItem::CreateConfig { - PeerId replyToPeer = 0; - MsgId replyTo = 0; - MsgId replyToTop = 0; - StoryId replyToStory = 0; - bool replyIsTopicPost = false; + ReplyFields reply; + UserId viaBotId = 0; int viewsCount = -1; int forwardsCount = -1; - QString author; - PeerId senderOriginal = 0; - QString senderNameOriginal; - QString forwardPsaType; + QString postAuthor; + MsgId originalId = 0; + TimeId originalDate = 0; + PeerId originalSenderId = 0; + QString originalSenderName; + QString originalPostAuthor; + + QString forwardPsaType; PeerId savedFromPeer = 0; MsgId savedFromMsgId = 0; - QString authorOriginal; - TimeId originalDate = 0; + TimeId editDate = 0; HistoryMessageMarkupData markup; HistoryMessageRepliesData replies; @@ -154,14 +154,14 @@ struct HistoryItem::CreateConfig { void HistoryItem::FillForwardedInfo( CreateConfig &config, const MTPDmessageFwdHeader &data) { - if (const auto fromId = data.vfrom_id()) { - config.senderOriginal = peerFromMTP(*fromId); - } - config.originalDate = data.vdate().v; - config.senderNameOriginal = qs(data.vfrom_name().value_or_empty()); - config.forwardPsaType = qs(data.vpsa_type().value_or_empty()); config.originalId = data.vchannel_post().value_or_empty(); - config.authorOriginal = qs(data.vpost_author().value_or_empty()); + config.originalDate = data.vdate().v; + if (const auto fromId = data.vfrom_id()) { + config.originalSenderId = peerFromMTP(*fromId); + } + config.originalSenderName = qs(data.vfrom_name().value_or_empty()); + config.originalPostAuthor = qs(data.vpost_author().value_or_empty()); + config.forwardPsaType = qs(data.vpsa_type().value_or_empty()); const auto savedFromPeer = data.vsaved_from_peer(); const auto savedFromMsgId = data.vsaved_from_msg_id(); if (savedFromPeer && savedFromMsgId) { @@ -435,21 +435,21 @@ HistoryItem::HistoryItem( const auto originalMedia = original->media(); const auto dropForwardInfo = original->computeDropForwardedInfo(); - config.replyTo = config.replyToTop = topicRootId; - config.replyIsTopicPost = (topicRootId != 0); + config.reply.messageId = config.reply.topMessageId = topicRootId; + config.reply.topicPost = (topicRootId != 0); if (!dropForwardInfo) { - config.originalDate = original->dateOriginal(); + config.originalDate = original->originalDate(); if (const auto info = original->hiddenSenderInfo()) { - config.senderNameOriginal = info->name; - } else if (const auto senderOriginal = original->senderOriginal()) { - config.senderOriginal = senderOriginal->id; - if (senderOriginal->isChannel()) { - config.originalId = original->idOriginal(); + config.originalSenderName = info->name; + } else if (const auto originalSender = original->originalSender()) { + config.originalSenderId = originalSender->id; + if (originalSender->isChannel()) { + config.originalId = original->originalId(); } } else { Unexpected("Corrupt forwarded information in message."); } - config.authorOriginal = original->authorOriginal(); + config.originalPostAuthor = original->originalPostAuthor(); } if (peer->isSelf()) { // @@ -465,12 +465,12 @@ HistoryItem::HistoryItem( //} } if (flags & MessageFlag::HasPostAuthor) { - config.author = postAuthor; + config.postAuthor = postAuthor; } if (const auto fwdViaBot = original->viaBot()) { config.viaBotId = peerToUser(fwdViaBot->id); } else if (originalMedia && originalMedia->game()) { - if (const auto sender = original->senderOriginal()) { + if (const auto sender = original->originalSender()) { if (const auto user = sender->asUser()) { if (user->isBot()) { config.viaBotId = peerToUser(user->id); @@ -482,8 +482,8 @@ HistoryItem::HistoryItem( if (fwdViewsCount > 0) { config.viewsCount = fwdViewsCount; } else if ((isPost() && !isScheduled()) - || (original->senderOriginal() - && original->senderOriginal()->isChannel())) { + || (original->originalSender() + && original->originalSender()->isChannel())) { config.viewsCount = 1; } @@ -1026,7 +1026,7 @@ void HistoryItem::setCommentsItemId(FullMsgId id) { if (id.peer == _history->peer->id) { if (id.msg != this->id) { if (const auto reply = Get()) { - reply->replyToMsgTop = id.msg; + reply->setTopMessageId(id.msg); } } } else if (const auto views = Get()) { @@ -1879,7 +1879,7 @@ void HistoryItem::changeReplyToTopCounter( this, Data::MessageUpdate::Flag::ReplyToTopAdded); } - const auto topId = reply->replyToTop(); + const auto topId = reply->topMessageId(); if (!topId) { return; } @@ -1931,8 +1931,8 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().requestItemResize(this); if (const auto reply = Get()) { - if (reply->replyToLink()) { - reply->setReplyToLinkFrom(this); + if (reply->link()) { + reply->setLinkFrom(this); } changeReplyToTopCounter(reply, 1); } @@ -2381,14 +2381,14 @@ not_null HistoryItem::author() const { return (isPost() && !isSponsored()) ? _history->peer : from(); } -TimeId HistoryItem::dateOriginal() const { +TimeId HistoryItem::originalDate() const { if (const auto forwarded = Get()) { return forwarded->originalDate; } return date(); } -PeerData *HistoryItem::senderOriginal() const { +PeerData *HistoryItem::originalSender() const { if (const auto forwarded = Get()) { return forwarded->originalSender; } @@ -2416,18 +2416,18 @@ not_null HistoryItem::fromOriginal() const { return from(); } -QString HistoryItem::authorOriginal() const { +QString HistoryItem::originalPostAuthor() const { if (const auto forwarded = Get()) { - return forwarded->originalAuthor; + return forwarded->originalPostAuthor; } else if (const auto msgsigned = Get()) { if (!msgsigned->isAnonymousRank) { - return msgsigned->author; + return msgsigned->postAuthor; } } return QString(); } -MsgId HistoryItem::idOriginal() const { +MsgId HistoryItem::originalId() const { if (const auto forwarded = Get()) { return forwarded->originalId; } @@ -2491,9 +2491,9 @@ void HistoryItem::setForwardsCount(int count) { history()->owner().notifyItemDataChange(this); } -void HistoryItem::setPostAuthor(const QString &author) { +void HistoryItem::setPostAuthor(const QString &postAuthor) { auto msgsigned = Get(); - if (author.isEmpty()) { + if (postAuthor.isEmpty()) { if (!msgsigned) { return; } @@ -2504,10 +2504,10 @@ void HistoryItem::setPostAuthor(const QString &author) { if (!msgsigned) { AddComponents(HistoryMessageSigned::Bit()); msgsigned = Get(); - } else if (msgsigned->author == author) { + } else if (msgsigned->postAuthor == postAuthor) { return; } - msgsigned->author = author; + msgsigned->postAuthor = postAuthor; msgsigned->isAnonymousRank = !isDiscussionPost() && this->author()->isMegagroup(); history()->owner().requestItemResize(this); @@ -2643,20 +2643,10 @@ void HistoryItem::setReplyFields( } } } else if (const auto reply = Get()) { - reply->topicPost = isForumPost; - if ((reply->replyToMsgId != replyTo) - && !IsServerMsgId(reply->replyToMsgId)) { - reply->replyToMsgId = replyTo; - if (!reply->updateData(this)) { - RequestDependentMessageItem( - this, - reply->replyToPeerId, - reply->replyToMsgId); - } - } - if ((reply->replyToMsgTop != replyToTop) - && !IsServerMsgId(reply->replyToMsgTop)) { - reply->replyToMsgTop = replyToTop; + const auto increment = (reply->topMessageId() != replyToTop) + && !IsServerMsgId(reply->topMessageId()); + reply->updateFields(this, replyTo, replyToTop, isForumPost); + if (increment) { changeReplyToTopCounter(reply, 1); } } @@ -2819,14 +2809,22 @@ bool HistoryItem::unread(not_null thread) const { MsgId HistoryItem::replyToId() const { if (const auto reply = Get()) { - return reply->replyToId(); + return reply->messageId(); } return 0; } +FullMsgId HistoryItem::replyToFullId() const { + if (const auto reply = Get()) { + const auto peer = reply->externalPeerId(); + return { peer ? peer : history()->peer->id, reply->messageId() }; + } + return {}; +} + MsgId HistoryItem::replyToTop() const { if (const auto reply = Get()) { - return reply->replyToTop(); + return reply->topMessageId(); } else if (const auto data = GetServiceDependentData()) { return data->topId; } @@ -2835,8 +2833,8 @@ MsgId HistoryItem::replyToTop() const { MsgId HistoryItem::topicRootId() const { if (const auto reply = Get() - ; reply && reply->topicPost) { - return reply->replyToTop(); + ; reply && reply->topicPost()) { + return reply->topMessageId(); } else if (const auto data = GetServiceDependentData() ; data && data->topicPost && data->topId) { return data->topId; @@ -2850,11 +2848,11 @@ MsgId HistoryItem::topicRootId() const { FullStoryId HistoryItem::replyToStory() const { if (const auto reply = Get()) { - if (reply->replyToStoryId) { - const auto peerId = reply->replyToPeerId - ? reply->replyToPeerId + if (reply->storyId()) { + const auto peerId = reply->externalPeerId() + ? reply->externalPeerId() : _history->peer->id; - return { .peer = peerId, .story = reply->replyToStoryId }; + return { .peer = peerId, .story = reply->storyId() }; } } return {}; @@ -2862,9 +2860,9 @@ FullStoryId HistoryItem::replyToStory() const { FullReplyTo HistoryItem::replyTo() const { return { - .msgId = replyToId(), - .topicRootId = topicRootId(), + .messageId = replyToFullId(), .storyId = replyToStory(), + .topicRootId = topicRootId(), }; } @@ -3049,7 +3047,10 @@ const std::vector &HistoryItem::customTextLinks() const { void HistoryItem::createComponents(CreateConfig &&config) { uint64 mask = 0; - if (config.replyTo || config.replyToStory) { + if (config.reply.messageId + || config.reply.externalSenderId + || !config.reply.externalSenderName.isEmpty() + || config.reply.storyId) { mask |= HistoryMessageReply::Bit(); } if (config.viaBotId) { @@ -3058,18 +3059,18 @@ void HistoryItem::createComponents(CreateConfig &&config) { if (config.viewsCount >= 0 || !config.replies.isNull) { mask |= HistoryMessageViews::Bit(); } - if (!config.author.isEmpty()) { + if (!config.postAuthor.isEmpty()) { mask |= HistoryMessageSigned::Bit(); } else if (_history->peer->isMegagroup() // Discussion posts signatures. && config.savedFromPeer - && !config.authorOriginal.isEmpty()) { + && !config.originalPostAuthor.isEmpty()) { const auto savedFrom = _history->owner().peerLoaded( config.savedFromPeer); if (savedFrom && savedFrom->isChannel()) { mask |= HistoryMessageSigned::Bit(); } } else if ((_history->peer->isSelf() || _history->peer->isRepliesChat()) - && !config.authorOriginal.isEmpty()) { + && !config.originalPostAuthor.isEmpty()) { mask |= HistoryMessageSigned::Bit(); } if (config.editDate != TimeId(0)) { @@ -3087,23 +3088,18 @@ void HistoryItem::createComponents(CreateConfig &&config) { UpdateComponents(mask); if (const auto reply = Get()) { - reply->replyToPeerId = config.replyToPeer; - reply->replyToMsgId = config.replyTo; - reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop; - reply->replyToStoryId = config.replyToStory; - reply->storyReply = (config.replyToStory != 0); - reply->topicPost = config.replyIsTopicPost; + reply->set(config.reply); if (!reply->updateData(this)) { - if (reply->replyToMsgId) { + if (const auto messageId = reply->messageId()) { RequestDependentMessageItem( this, - reply->replyToPeerId, - reply->replyToMsgId); - } else if (reply->replyToStoryId) { + reply->externalPeerId(), + reply->messageId()); + } else if (reply->storyId()) { RequestDependentMessageStory( this, - reply->replyToPeerId, - reply->replyToStoryId); + reply->externalPeerId(), + reply->storyId()); } } } @@ -3129,9 +3125,9 @@ void HistoryItem::createComponents(CreateConfig &&config) { edited->date = config.editDate; } if (const auto msgsigned = Get()) { - msgsigned->author = config.author.isEmpty() - ? config.authorOriginal - : config.author; + msgsigned->postAuthor = config.postAuthor.isEmpty() + ? config.originalPostAuthor + : config.postAuthor; msgsigned->isAnonymousRank = !isDiscussionPost() && author()->isMegagroup(); } @@ -3167,9 +3163,9 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) { return; } forwarded->originalDate = config.originalDate; - const auto originalSender = config.senderOriginal - ? config.senderOriginal - : !config.senderNameOriginal.isEmpty() + const auto originalSender = config.originalSenderId + ? config.originalSenderId + : !config.originalSenderName.isEmpty() ? PeerId() : from()->id; forwarded->originalSender = originalSender @@ -3177,11 +3173,11 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) { : nullptr; if (!forwarded->originalSender) { forwarded->hiddenSenderInfo = std::make_unique( - config.senderNameOriginal, + config.originalSenderName, config.imported); } forwarded->originalId = config.originalId; - forwarded->originalAuthor = config.authorOriginal; + forwarded->originalPostAuthor = config.originalPostAuthor; forwarded->psaType = config.forwardPsaType; forwarded->savedFromPeer = _history->owner().peerLoaded( config.savedFromPeer); @@ -3233,7 +3229,7 @@ TextWithEntities HistoryItem::withLocalEntities( : nullptr; if (document) { if (const auto duration = DurationForTimestampLinks(document)) { - const auto context = reply->replyToMsg->fullId(); + const auto context = reply->resolvedMessage->fullId(); return AddTimestampLinks( textWithEntities, duration, @@ -3241,7 +3237,7 @@ TextWithEntities HistoryItem::withLocalEntities( } } else if (webpage) { if (const auto duration = DurationForTimestampLinks(webpage)) { - const auto context = reply->replyToMsg->fullId(); + const auto context = reply->resolvedMessage->fullId(); return AddTimestampLinks( textWithEntities, duration, @@ -3290,19 +3286,28 @@ void HistoryItem::createComponentsHelper( auto config = CreateConfig(); config.viaBotId = viaBotId; if (flags & MessageFlag::HasReplyInfo) { - config.replyTo = replyTo.msgId; - config.replyToStory = replyTo.storyId.story; - config.replyToPeer = replyTo.storyId ? replyTo.storyId.peer : 0; - const auto to = LookupReplyTo(_history, replyTo.msgId); - const auto replyToTop = LookupReplyToTop(to); - config.replyToTop = replyToTop ? replyToTop : replyTo.msgId; + config.reply.messageId = replyTo.messageId.msg; + config.reply.storyId = replyTo.storyId.story; + config.reply.externalPeerId = replyTo.storyId + ? replyTo.storyId.peer + : (replyTo.messageId && replyTo.messageId.peer + != history()->peer->id) + ? replyTo.messageId.peer + : PeerId(); + const auto to = LookupReplyTo(_history, replyTo.messageId); + const auto replyToTop = LookupReplyToTop(_history, to); + config.reply.topMessageId = replyToTop + ? replyToTop + : (replyTo.messageId.peer == history()->peer->id) + ? replyTo.messageId.msg + : MsgId(); const auto forum = _history->asForum(); - config.replyIsTopicPost = LookupReplyIsTopicPost(to) + config.reply.topicPost = LookupReplyIsTopicPost(to) || (to && to->Has()) - || (forum && forum->creating(config.replyToTop)); + || (forum && forum->creating(config.reply.topMessageId)); } config.markup = std::move(markup); - if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor; + if (flags & MessageFlag::HasPostAuthor) config.postAuthor = postAuthor; if (flags & MessageFlag::HasViews) config.viewsCount = 1; createComponents(std::move(config)); @@ -3392,26 +3397,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) { }); } if (const auto reply = data.vreply_to()) { - reply->match([&](const MTPDmessageReplyHeader &data) { - // #TODO replies - if (const auto id = data.vreply_to_msg_id().value_or_empty()) { - if (const auto peer = data.vreply_to_peer_id()) { - config.replyToPeer = peerFromMTP(*peer); - if (config.replyToPeer == _history->peer->id) { - config.replyToPeer = 0; - } - } - const auto owner = &_history->owner(); - config.replyTo = data.is_reply_to_scheduled() - ? owner->scheduledMessages().localMessageId(id) - : id; - config.replyToTop = data.vreply_to_top_id().value_or(id); - config.replyIsTopicPost = data.is_forum_topic(); - } - }, [&](const MTPDmessageReplyStoryHeader &data) { - config.replyToPeer = peerFromUser(data.vuser_id()); - config.replyToStory = data.vstory_id().v; - }); + config.reply = ReplyFieldsFromMTP(history(), *reply); } config.viaBotId = data.vvia_bot_id().value_or_empty(); config.viewsCount = data.vviews().value_or(-1); @@ -3421,7 +3407,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) { : HistoryMessageRepliesData(data.vreplies()); config.markup = HistoryMessageMarkupData(data.vreply_markup()); config.editDate = data.vedit_date().value_or_empty(); - config.author = qs(data.vpost_author().value_or_empty()); + config.postAuthor = qs(data.vpost_author().value_or_empty()); createComponents(std::move(config)); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index d47660393..c04e6f9de 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -465,6 +465,7 @@ public: void setText(const TextWithEntities &textWithEntities); [[nodiscard]] MsgId replyToId() const; + [[nodiscard]] FullMsgId replyToFullId() const; [[nodiscard]] MsgId replyToTop() const; [[nodiscard]] MsgId topicRootId() const; [[nodiscard]] FullStoryId replyToStory() const; @@ -473,12 +474,12 @@ public: [[nodiscard]] not_null author() const; - [[nodiscard]] TimeId dateOriginal() const; - [[nodiscard]] PeerData *senderOriginal() const; + [[nodiscard]] TimeId originalDate() const; + [[nodiscard]] PeerData *originalSender() const; [[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const; [[nodiscard]] not_null fromOriginal() const; - [[nodiscard]] QString authorOriginal() const; - [[nodiscard]] MsgId idOriginal() const; + [[nodiscard]] QString originalPostAuthor() const; + [[nodiscard]] MsgId originalId() const; [[nodiscard]] bool isEmpty() const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 30d032bce..7bcaeb649 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item_components.h" +#include "api/api_text_entities.h" #include "base/qt/qt_key_modifiers.h" #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" @@ -38,6 +39,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_web_page.h" #include "data/data_file_click_handler.h" +#include "data/data_scheduled_messages.h" +#include "data/data_session.h" #include "data/data_stories.h" #include "main/main_session.h" #include "window/window_session_controller.h" @@ -64,7 +67,7 @@ void HistoryMessageVia::create( lt_inline_bot, '@' + bot->username())); link = std::make_shared([bot = this->bot]( - ClickContext context) { + ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { if (base::IsCtrlPressed()) { @@ -183,13 +186,13 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { ? originalSender->name() : hiddenSenderInfo->name) }; - if (!originalAuthor.isEmpty()) { + if (!originalPostAuthor.isEmpty()) { phrase = tr::lng_forwarded_signed( tr::now, lt_channel, name, lt_user, - { .text = originalAuthor }, + { .text = originalPostAuthor }, Ui::Text::WithEntities); } else { phrase = name; @@ -261,6 +264,51 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { } } +ReplyFields ReplyFieldsFromMTP( + not_null history, + const MTPMessageReplyHeader &reply) { + return reply.match([&](const MTPDmessageReplyHeader &data) { + auto result = ReplyFields(); + if (const auto peer = data.vreply_to_peer_id()) { + result.externalPeerId = peerFromMTP(*peer); + if (result.externalPeerId == history->peer->id) { + result.externalPeerId = 0; + } + } + const auto owner = &history->owner(); + if (const auto id = data.vreply_to_msg_id().value_or_empty()) { + result.messageId = data.is_reply_to_scheduled() + ? owner->scheduledMessages().localMessageId(id) + : id; + result.topMessageId + = data.vreply_to_top_id().value_or(id); + result.topicPost = data.is_forum_topic(); + } + if (const auto header = data.vreply_header()) { + const auto &data = header->data(); + result.externalPostAuthor + = qs(data.vpost_author().value_or_empty()); + result.externalSenderId = data.vfrom_id() + ? peerFromMTP(*data.vfrom_id()) + : PeerId(); + result.externalSenderName + = qs(data.vfrom_name().value_or_empty()); + } + result.quote = TextWithEntities{ + qs(data.vquote_text().value_or_empty()), + Api::EntitiesFromMTP( + &owner->session(), + data.vquote_entities().value_or_empty()), + }; + return result; + }, [&](const MTPDmessageReplyStoryHeader &data) { + return ReplyFields{ + .externalPeerId = peerFromUser(data.vuser_id()), + .storyId = data.vstory_id().v, + }; + }); +} + HistoryMessageReply::HistoryMessageReply() = default; HistoryMessageReply &HistoryMessageReply::operator=( @@ -268,8 +316,8 @@ HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply::~HistoryMessageReply() { // clearData() should be called by holder. - Expects(replyToMsg.empty()); - Expects(replyToVia == nullptr); + Expects(resolvedMessage.empty()); + Expects(originalVia == nullptr); } bool HistoryMessageReply::updateData( @@ -277,166 +325,228 @@ bool HistoryMessageReply::updateData( bool force) { const auto guard = gsl::finally([&] { refreshReplyToMedia(); }); if (!force) { - if ((replyToMsg || !replyToMsgId) - && (replyToStory || !replyToStoryId)) { + if (resolvedMessage || resolvedStory || _deleted) { return true; } } - const auto peerId = replyToPeerId - ? replyToPeerId + const auto peerId = _fields.externalPeerId + ? _fields.externalPeerId : holder->history()->peer->id; - if (!replyToMsg && replyToMsgId) { - replyToMsg = holder->history()->owner().message( + if (!resolvedMessage && _fields.messageId) { + resolvedMessage = holder->history()->owner().message( peerId, - replyToMsgId); - if (replyToMsg) { - if (replyToMsg->isEmpty()) { + _fields.messageId); + if (resolvedMessage) { + if (resolvedMessage->isEmpty()) { // Really it is deleted. - replyToMsg = nullptr; + resolvedMessage = nullptr; force = true; } else { holder->history()->owner().registerDependentMessage( holder, - replyToMsg.get()); + resolvedMessage.get()); } } } - if (!replyToStory && replyToStoryId) { + if (!resolvedStory && _fields.storyId) { const auto maybe = holder->history()->owner().stories().lookup({ peerId, - replyToStoryId, + _fields.storyId, }); if (maybe) { - replyToStory = *maybe; + resolvedStory = *maybe; holder->history()->owner().stories().registerDependentMessage( holder, - replyToStory.get()); + resolvedStory.get()); } else if (maybe.error() == Data::NoStory::Deleted) { force = true; } } - if (replyToMsg || replyToStory) { + const auto external = _fields.externalSenderId + || !_fields.externalSenderName.isEmpty(); + if (resolvedMessage || resolvedStory || (external && force)) { const auto repaint = [=] { holder->customEmojiRepaint(); }; const auto context = Core::MarkedTextContext{ .session = &holder->history()->session(), .customEmojiRepaint = repaint, }; - replyToText.setMarkedText( + const auto text = !_fields.quote.empty() + ? _fields.quote + : resolvedMessage + ? resolvedMessage->inReplyText() + : resolvedStory + ? resolvedStory->inReplyText() + : TextWithEntities{ u"..."_q }; + _text.setMarkedText( st::defaultTextStyle, - (replyToMsg - ? replyToMsg->inReplyText() - : replyToStory->inReplyText()), + text, Ui::DialogTextOptions(), context); updateName(holder); - - setReplyToLinkFrom(holder); - if (replyToMsg && !replyToMsg->Has()) { - if (auto bot = replyToMsg->viaBot()) { - replyToVia = std::make_unique(); - replyToVia->create( + setLinkFrom(holder); + if (resolvedMessage + && !resolvedMessage->Has()) { + if (const auto bot = resolvedMessage->viaBot()) { + originalVia = std::make_unique(); + originalVia->create( &holder->history()->owner(), peerToUser(bot->id)); } } - if (replyToMsg) { - const auto peer = replyToMsg->history()->peer; - replyToColorKey = (!holder->out() + if (resolvedMessage) { + const auto peer = resolvedMessage->history()->peer; + _colorKey = (!holder->out() && (peer->isMegagroup() || peer->isChat()) - && replyToMsg->from()->isUser()) - ? replyToMsg->from()->id - : PeerId(0); + && resolvedMessage->from()->isUser()) + ? resolvedMessage->from()->id + : PeerId(); } else { - replyToColorKey = PeerId(0); + resolvedMessage = 0; } - const auto media = replyToMsg ? replyToMsg->media() : nullptr; + const auto media = resolvedMessage + ? resolvedMessage->media() + : nullptr; if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) { spoiler = nullptr; } else if (!spoiler) { spoiler = std::make_unique(repaint); } } else if (force) { - replyToMsgId = 0; - replyToStoryId = 0; - replyToColorKey = PeerId(0); + if (_fields.messageId || _fields.storyId) { + _deleted = true; + } + _colorKey = 0; spoiler = nullptr; } if (force) { holder->history()->owner().requestItemResize(holder); } - return (replyToMsg || !replyToMsgId) - && (replyToStory || !replyToStoryId); + return resolvedMessage + || resolvedStory + || (external && !_fields.messageId) + || _deleted; } -void HistoryMessageReply::setReplyToLinkFrom( +void HistoryMessageReply::set(ReplyFields fields) { + _fields = std::move(fields); +} + +void HistoryMessageReply::updateFields( + not_null holder, + MsgId messageId, + MsgId topMessageId, + bool topicPost) { + _fields.topicPost = topicPost; + if ((_fields.messageId != messageId) + && !IsServerMsgId(_fields.messageId)) { + _fields.messageId = messageId; + if (!updateData(holder)) { + RequestDependentMessageItem( + holder, + _fields.externalPeerId, + _fields.messageId); + } + } + if ((_fields.topMessageId != topMessageId) + && !IsServerMsgId(_fields.topMessageId)) { + _fields.topMessageId = topMessageId; + } +} + +void HistoryMessageReply::setLinkFrom( not_null holder) { - replyToLnk = replyToMsg - ? JumpToMessageClickHandler(replyToMsg.get(), holder->fullId()) - : replyToStory - ? JumpToStoryClickHandler(replyToStory.get()) + const auto externalPeerId = _fields.externalSenderId; + const auto external = externalPeerId + || !_fields.externalSenderName.isEmpty(); + const auto externalLink = [=](ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + if (externalPeerId) { + controller->showPeerInfo( + controller->session().data().peer(externalPeerId)); + } else { + controller->showToast(u"External reply"_q); + } + } + }; + _link = resolvedMessage + ? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId()) + : resolvedStory + ? JumpToStoryClickHandler(resolvedStory.get()) + : (external && !_fields.messageId) + ? std::make_shared(externalLink) : nullptr; } +void HistoryMessageReply::setTopMessageId(MsgId topMessageId) { + _fields.topMessageId = topMessageId; +} + void HistoryMessageReply::clearData(not_null holder) { - replyToVia = nullptr; - if (replyToMsg) { + originalVia = nullptr; + if (resolvedMessage) { holder->history()->owner().unregisterDependentMessage( holder, - replyToMsg.get()); - replyToMsg = nullptr; + resolvedMessage.get()); + resolvedMessage = nullptr; } - if (replyToStory) { + if (resolvedStory) { holder->history()->owner().stories().unregisterDependentMessage( holder, - replyToStory.get()); - replyToStory = nullptr; + resolvedStory.get()); + resolvedStory = nullptr; } - replyToMsgId = 0; - replyToStoryId = 0; + _deleted = true; refreshReplyToMedia(); } -PeerData *HistoryMessageReply::replyToFrom( - not_null holder) const { - if (!replyToMsg) { - return nullptr; +PeerData *HistoryMessageReply::sender(not_null holder) const { + if (resolvedStory) { + return resolvedStory->peer(); + } else if (!resolvedMessage) { + if (!_externalSender && _fields.externalSenderId) { + _externalSender = holder->history()->owner().peer( + _fields.externalSenderId); + } + return _externalSender; } else if (holder->Has()) { - if (const auto fwd = replyToMsg->Get()) { - return fwd->originalSender; + // Forward of a reply. Show reply-to original sender. + const auto forwarded + = resolvedMessage->Get(); + if (forwarded) { + return forwarded->originalSender; } } - if (const auto from = replyToMsg->displayFrom()) { + if (const auto from = resolvedMessage->displayFrom()) { return from; } - return replyToMsg->author().get(); + return resolvedMessage->author().get(); } -QString HistoryMessageReply::replyToFromName( +QString HistoryMessageReply::senderName( not_null holder) const { - if (replyToStory) { - return replyToFromName(replyToStory->peer()); - } else if (!replyToMsg) { - return QString(); + if (const auto peer = sender(holder)) { + return senderName(peer); + } else if (!resolvedMessage) { + return _fields.externalSenderName; } else if (holder->Has()) { - if (const auto fwd = replyToMsg->Get()) { - return fwd->originalSender - ? replyToFromName(fwd->originalSender) - : fwd->hiddenSenderInfo->name; + // Forward of a reply. Show reply-to original sender. + const auto forwarded + = resolvedMessage->Get(); + if (forwarded) { + Assert(forwarded->hiddenSenderInfo != nullptr); + return forwarded->hiddenSenderInfo->name; } } - if (const auto from = replyToMsg->displayFrom()) { - return replyToFromName(from); - } - return replyToFromName(replyToMsg->author()); + return QString(); } -QString HistoryMessageReply::replyToFromName( - not_null peer) const { - if (const auto user = replyToVia ? peer->asUser() : nullptr) { +QString HistoryMessageReply::senderName(not_null peer) const { + if (const auto user = originalVia ? peer->asUser() : nullptr) { return user->firstName; } return peer->name(); @@ -444,9 +554,9 @@ QString HistoryMessageReply::replyToFromName( bool HistoryMessageReply::isNameUpdated( not_null holder) const { - if (const auto from = replyToFrom(holder)) { - if (replyToVersion < from->nameVersion()) { - updateName(holder); + if (const auto from = sender(holder)) { + if (_nameVersion < from->nameVersion()) { + updateName(holder, from); return true; } } @@ -454,55 +564,73 @@ bool HistoryMessageReply::isNameUpdated( } void HistoryMessageReply::updateName( - not_null holder) const { - if (const auto name = replyToFromName(holder); !name.isEmpty()) { - replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); - if (const auto from = replyToFrom(holder)) { - replyToVersion = from->nameVersion(); - } else if (replyToMsg) { - replyToVersion = replyToMsg->author()->nameVersion(); - } else { - replyToVersion = replyToStory->peer()->nameVersion(); + not_null holder, + std::optional resolvedSender) const { + const auto peer = resolvedSender.value_or(sender(holder)); + const auto name = peer ? senderName(peer) : senderName(holder); + if (!name.isEmpty()) { + _name.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); + if (peer) { + _nameVersion = peer->nameVersion(); } - bool hasPreview = (replyToStory && replyToStory->hasReplyPreview()) - || (replyToMsg - && replyToMsg->media() - && replyToMsg->media()->hasReplyPreview()); - int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - int32 w = replyToName.maxWidth(); - if (replyToVia) { - w += st::msgServiceFont->spacew + replyToVia->maxWidth; + bool hasPreview = (resolvedStory + && resolvedStory->hasReplyPreview()) + || (resolvedMessage + && resolvedMessage->media() + && resolvedMessage->media()->hasReplyPreview()); + int32 previewSkip = hasPreview + ? (st::msgReplyBarSize.height() + + st::msgReplyBarSkip + - st::msgReplyBarSize.width() + - st::msgReplyBarPos.x()) + : 0; + int32 w = _name.maxWidth(); + if (originalVia) { + w += st::msgServiceFont->spacew + originalVia->maxWidth; } - maxReplyWidth = previewSkip + _maxWidth = previewSkip + std::max( w, - std::min(replyToText.maxWidth(), st::maxSignatureSize)) - + (storyReply + std::min(_text.maxWidth(), st::maxSignatureSize)) + + (_fields.storyId ? (st::dialogsMiniReplyStory.skipText + st::dialogsMiniReplyStory.icon.icon.width()) : 0); } else { - maxReplyWidth = st::msgDateFont->width(statePhrase()); + _maxWidth = st::msgDateFont->width(statePhrase()); } - maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right(); + _maxWidth = st::msgReplyPadding.left() + + st::msgReplyBarSkip + + _maxWidth + + st::msgReplyPadding.right(); } void HistoryMessageReply::resize(int width) const { - if (replyToVia) { - bool hasPreview = (replyToStory && replyToStory->hasReplyPreview()) - || (replyToMsg - && replyToMsg->media() - && replyToMsg->media()->hasReplyPreview()); - int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew); + if (originalVia) { + bool hasPreview = (resolvedStory + && resolvedStory->hasReplyPreview()) + || (resolvedMessage + && resolvedMessage->media() + && resolvedMessage->media()->hasReplyPreview()); + int previewSkip = hasPreview + ? (st::msgReplyBarSize.height() + + st::msgReplyBarSkip + - st::msgReplyBarSize.width() + - st::msgReplyBarPos.x()) + : 0; + originalVia->resize(width + - st::msgReplyBarSkip + - previewSkip + - _name.maxWidth() + - st::msgServiceFont->spacew); } } void HistoryMessageReply::itemRemoved( not_null holder, not_null removed) { - if (replyToMsg.get() == removed) { + if (resolvedMessage.get() == removed) { clearData(holder); holder->history()->owner().requestItemResize(holder); } @@ -511,7 +639,7 @@ void HistoryMessageReply::itemRemoved( void HistoryMessageReply::storyRemoved( not_null holder, not_null removed) { - if (replyToStory.get() == removed) { + if (resolvedStory.get() == removed) { clearData(holder); holder->history()->owner().requestItemResize(holder); } @@ -533,8 +661,8 @@ void HistoryMessageReply::paint( const auto outerWidth = w + 2 * x; const auto &bar = !inBubble ? st->msgImgReplyBarColor() - : replyToColorKey - ? HistoryView::FromNameFg(context, replyToColorKey) + : _colorKey + ? HistoryView::FromNameFg(context, _colorKey) : stm->msgReplyBarColor; const auto rbar = style::rtlrect( x + st::msgReplyBarPos.x(), @@ -565,9 +693,10 @@ void HistoryMessageReply::paint( const auto pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler); if (w > st::msgReplyBarSkip) { - if (replyToMsg || replyToStory) { - const auto media = replyToMsg ? replyToMsg->media() : nullptr; - auto hasPreview = (replyToStory && replyToStory->hasReplyPreview()) || (media && media->hasReplyPreview()); + if (resolvedMessage || resolvedStory) { + const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; + auto hasPreview = (media && media->hasReplyPreview()) + || (resolvedStory && resolvedStory->hasReplyPreview()); if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) { hasPreview = false; } @@ -576,7 +705,7 @@ void HistoryMessageReply::paint( if (hasPreview) { const auto image = media ? media->replyPreview() - : replyToStory->replyPreview(); + : resolvedStory->replyPreview(); if (image) { auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x); const auto preview = image->pixSingle( @@ -604,26 +733,26 @@ void HistoryMessageReply::paint( if (w > st::msgReplyBarSkip + previewSkip) { p.setPen(!inBubble ? st->msgImgReplyBarColor() - : replyToColorKey - ? HistoryView::FromNameFg(context, replyToColorKey) + : _colorKey + ? HistoryView::FromNameFg(context, _colorKey) : stm->msgServiceFg); - replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); - if (replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) { + _name.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); + if (originalVia && w > st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew) { p.setFont(st::msgServiceFont); - p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia->text); + p.drawText(x + st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, originalVia->text); } p.setPen(inBubble ? stm->historyTextFg : st->msgImgReplyBarColor()); - holder->prepareCustomEmojiPaint(p, context, replyToText); + holder->prepareCustomEmojiPaint(p, context, _text); auto replyToTextPosition = QPoint( x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height); const auto replyToTextPalette = &(inBubble ? stm->replyTextPalette : st->imgReplyTextPalette()); - if (storyReply) { + if (_fields.storyId) { st::dialogsMiniReplyStory.icon.icon.paint( p, replyToTextPosition, @@ -634,7 +763,7 @@ void HistoryMessageReply::paint( + st::dialogsMiniReplyStory.icon.icon.width(), 0); } - replyToText.draw(p, { + _text.draw(p, { .position = replyToTextPosition, .availableWidth = w - st::msgReplyBarSkip - previewSkip, .palette = replyToTextPalette, @@ -657,10 +786,14 @@ void HistoryMessageReply::paint( } } +void HistoryMessageReply::unloadPersistentAnimation() { + _text.unloadPersistentAnimation(); +} + QString HistoryMessageReply::statePhrase() const { - return (replyToMsgId || replyToStoryId) + return ((_fields.messageId || _fields.storyId) && !_deleted) ? tr::lng_profile_loading(tr::now) - : storyReply + : _fields.storyId ? tr::lng_deleted_story(tr::now) : tr::lng_deleted_message(tr::now); } @@ -668,7 +801,7 @@ QString HistoryMessageReply::statePhrase() const { void HistoryMessageReply::refreshReplyToMedia() { replyToDocumentId = 0; replyToWebPageId = 0; - if (const auto media = replyToMsg ? replyToMsg->media() : nullptr) { + if (const auto media = resolvedMessage ? resolvedMessage->media() : nullptr) { if (const auto document = media->document()) { replyToDocumentId = document->id; } else if (const auto webpage = media->webpage()) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 6416475ab..4cd033879 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -75,7 +75,7 @@ struct HistoryMessageViews : public RuntimeComponent { - QString author; + QString postAuthor; bool isAnonymousRank = false; }; @@ -123,7 +123,7 @@ struct HistoryMessageForwarded : public RuntimeComponent hiddenSenderInfo; - QString originalAuthor; + QString originalPostAuthor; QString psaType; MsgId originalId = 0; mutable Ui::Text::String text = { 1 }; @@ -226,6 +226,22 @@ private: }; +struct ReplyFields { + TextWithEntities quote; + PeerId externalSenderId = 0; + QString externalSenderName; + QString externalPostAuthor; + PeerId externalPeerId = 0; + MsgId messageId = 0; + MsgId topMessageId = 0; + StoryId storyId = 0; + bool topicPost = false; +}; + +[[nodiscard]] ReplyFields ReplyFieldsFromMTP( + not_null history, + const MTPMessageReplyHeader &reply); + struct HistoryMessageReply : public RuntimeComponent { HistoryMessageReply(); @@ -238,17 +254,25 @@ struct HistoryMessageReply static constexpr auto kBarAlpha = 230. / 255.; + void set(ReplyFields fields); + + void updateFields( + not_null holder, + MsgId messageId, + MsgId topMessageId, + bool topicPost); bool updateData(not_null holder, bool force = false); // Must be called before destructor. void clearData(not_null holder); - [[nodiscard]] PeerData *replyToFrom(not_null holder) const; - [[nodiscard]] QString replyToFromName( - not_null holder) const; - [[nodiscard]] QString replyToFromName(not_null peer) const; + [[nodiscard]] PeerData *sender(not_null holder) const; + [[nodiscard]] QString senderName(not_null holder) const; + [[nodiscard]] QString senderName(not_null peer) const; [[nodiscard]] bool isNameUpdated(not_null holder) const; - void updateName(not_null holder) const; + void updateName( + not_null holder, + std::optional resolvedSender = std::nullopt) const; void resize(int width) const; void itemRemoved( not_null holder, @@ -265,52 +289,63 @@ struct HistoryMessageReply int y, int w, bool inBubble) const; + void unloadPersistentAnimation(); - [[nodiscard]] PeerId replyToPeer() const { - return replyToPeerId; + [[nodiscard]] PeerId colorKey() const { + return _colorKey; } - [[nodiscard]] MsgId replyToId() const { - return replyToMsgId; + [[nodiscard]] PeerId externalPeerId() const { + return _fields.externalPeerId; } - [[nodiscard]] MsgId replyToTop() const { - return replyToMsgTop; + [[nodiscard]] MsgId messageId() const { + return _fields.messageId; } - [[nodiscard]] int replyToWidth() const { - return maxReplyWidth; + [[nodiscard]] StoryId storyId() const { + return _fields.storyId; } - [[nodiscard]] ClickHandlerPtr replyToLink() const { - return replyToLnk; + [[nodiscard]] MsgId topMessageId() const { + return _fields.topMessageId; + } + [[nodiscard]] int maxWidth() const { + return _maxWidth; + } + [[nodiscard]] ClickHandlerPtr link() const { + return _link; + } + [[nodiscard]] bool topicPost() const { + return _fields.topicPost; } [[nodiscard]] QString statePhrase() const; - void setReplyToLinkFrom(not_null holder); + + void setLinkFrom(not_null holder); + void setTopMessageId(MsgId topMessageId); void refreshReplyToMedia(); - PeerId replyToPeerId = 0; - MsgId replyToMsgId = 0; - MsgId replyToMsgTop = 0; - StoryId replyToStoryId = 0; - using ColorKey = PeerId; - ColorKey replyToColorKey = 0; DocumentId replyToDocumentId = 0; WebPageId replyToWebPageId = 0; - ReplyToMessagePointer replyToMsg; - ReplyToStoryPointer replyToStory; - std::unique_ptr replyToVia; + ReplyToMessagePointer resolvedMessage; + ReplyToStoryPointer resolvedStory; + std::unique_ptr originalVia; std::unique_ptr spoiler; - ClickHandlerPtr replyToLnk; - mutable Ui::Text::String replyToName, replyToText; - mutable int replyToVersion = 0; - mutable int maxReplyWidth = 0; int toWidth = 0; - bool topicPost = false; - bool storyReply = false; - struct final { + struct { mutable std::unique_ptr animation; QPoint lastPoint; } ripple; +private: + ReplyFields _fields; + PeerId _colorKey = 0; + ClickHandlerPtr _link; + mutable Ui::Text::String _name; + mutable Ui::Text::String _text; + mutable PeerData *_externalSender = nullptr; + mutable int _maxWidth = 0; + mutable int _nameVersion = 0; + bool _deleted = false; + }; struct HistoryMessageTranslation diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 4129df0ad..be7838083 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -192,13 +192,14 @@ bool ShouldSendSilent( && peer->session().settings().supportAllSilent()); } -HistoryItem *LookupReplyTo(not_null history, MsgId replyToId) { - const auto &owner = history->owner(); - return owner.message(history->peer, replyToId); +HistoryItem *LookupReplyTo(not_null history, FullMsgId replyTo) { + return history->owner().message(replyTo); } -MsgId LookupReplyToTop(HistoryItem *replyTo) { - return replyTo ? replyTo->replyToTop() : 0; +MsgId LookupReplyToTop(not_null history, HistoryItem *replyTo) { + return (replyTo && replyTo->history() == history) + ? replyTo->replyToTop() + : 0; } bool LookupReplyIsTopicPost(HistoryItem *replyTo) { @@ -360,27 +361,21 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { MTP_long(peerToUser(replyTo.storyId.peer).bare), MTP_int(replyTo.storyId.story)); } - const auto to = LookupReplyTo(action.history, replyTo.msgId); - if (const auto replyToTop = LookupReplyToTop(to)) { - using Flag = MTPDmessageReplyHeader::Flag; - return MTP_messageReplyHeader( - MTP_flags(Flag::f_reply_to_top_id - | (LookupReplyIsTopicPost(to) - ? Flag::f_forum_topic - : Flag(0))), - MTP_int(replyTo.msgId), - MTPPeer(), - MTPMessageFwdHeader(), // reply_header - MTP_int(replyToTop), - MTPstring(), // quote_text - MTPVector()); // quote_entities - } + using Flag = MTPDmessageReplyHeader::Flag; + const auto historyPeer = action.history->peer->id; + const auto externalPeerId = (replyTo.messageId.peer == historyPeer) + ? PeerId() + : replyTo.messageId.peer; + const auto to = LookupReplyTo(action.history, replyTo.messageId); + const auto replyToTop = LookupReplyToTop(action.history, to); return MTP_messageReplyHeader( - MTP_flags(0), - MTP_int(replyTo.msgId), - MTPPeer(), // reply_to_peer_id + MTP_flags(Flag::f_reply_to_msg_id + | (replyToTop ? Flag::f_reply_to_top_id : Flag()) + | (externalPeerId ? Flag::f_reply_to_peer_id : Flag())), + MTP_int(replyTo.messageId.msg), + peerToMTP(externalPeerId), MTPMessageFwdHeader(), // reply_header - MTPint(), // reply_to_top_id + MTP_int(replyToTop), // reply_to_top_id MTPstring(), // quote_text MTPVector()); // quote_entities } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index e7ed74290..8dd151274 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -85,8 +85,10 @@ void RequestDependentMessageStory( const Api::SendOptions &options); [[nodiscard]] HistoryItem *LookupReplyTo( not_null history, - MsgId replyToId); -[[nodiscard]] MsgId LookupReplyToTop(HistoryItem *replyTo); + FullMsgId replyToId); +[[nodiscard]] MsgId LookupReplyToTop( + not_null history, + HistoryItem *replyTo); [[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo); struct SendingErrorRequest { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9ce5a8af3..955263ef2 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -517,7 +517,7 @@ HistoryWidget::HistoryWidget( if (!_peer || isRecording()) { return false; } - const auto replyTo = (_replyToId && !_editMsgId) + const auto replyTo = (_replyTo && !_editMsgId) ? _replyEditMsg : 0; const auto topic = replyTo ? replyTo->topic() : nullptr; @@ -848,9 +848,8 @@ HistoryWidget::HistoryWidget( ) | rpl::filter([=](const Api::SendAction &action) { return (action.history == _history); }) | rpl::start_with_next([=](const Api::SendAction &action) { - const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId( - action.history->peer->id, - action.replyTo.msgId)); + const auto lastKeyboardUsed = lastForceReplyReplied( + action.replyTo.messageId); if (action.replaceMediaOf) { } else if (action.options.scheduled) { cancelReply(lastKeyboardUsed); @@ -909,7 +908,7 @@ Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const { return Dialogs::EntryState{ .key = _history, .section = Dialogs::EntryState::Section::History, - .currentReplyToId = replyToId(), + .currentReplyTo = replyTo(), }; } @@ -1354,7 +1353,7 @@ void HistoryWidget::insertHashtagOrBotCommand( // Send bot command at once, if it was not inserted by pressing Tab. if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) { - sendBotCommand({ _peer, str, FullMsgId(), replyToId() }); + sendBotCommand({ _peer, str, FullMsgId(), replyTo() }); session().api().finishForwarding(prepareSendAction({})); setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); } else { @@ -1663,21 +1662,22 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { if (_editMsgId) { _history->setLocalEditDraft(std::make_unique( _field, - _editMsgId, - topicRootId, + FullReplyTo{ + .messageId = FullMsgId(_history->peer->id, _editMsgId), + .topicRootId = topicRootId, + }, _previewState, _saveEditMsgRequestId)); } else { - if (_replyToId || !_field->empty()) { + if (_replyTo || !_field->empty()) { _history->setLocalDraft(std::make_unique( _field, - _replyToId, - topicRootId, + _replyTo, _previewState)); } else { - _history->clearLocalDraft({}); + _history->clearLocalDraft(topicRootId); } - _history->clearLocalEditDraft({}); + _history->clearLocalEditDraft(topicRootId); } } @@ -1779,8 +1779,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived( }; _history->setLocalDraft(std::make_unique( textWithTags, - 0, // replyTo - 0, // topicRootId + FullReplyTo(), cursor, Data::PreviewState::Allowed)); applyDraft(); @@ -1885,7 +1884,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { ? _history->localDraft({}) : nullptr; auto fieldAvailable = canWriteMessage(); - const auto editMsgId = editDraft ? editDraft->msgId : 0; + const auto editMsgId = editDraft ? editDraft->reply.messageId.msg : 0; if (_voiceRecordBar->isActive() || (!_canSendTexts && !editMsgId)) { if (!_canSendTexts) { clearFieldText(0, fieldHistoryAction); @@ -1898,7 +1897,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { clearFieldText(0, fieldHistoryAction); setInnerFocus(); _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); setEditMsgId(0); if (fieldWillBeHiddenAfterEdit) { updateControlsVisibility(); @@ -1916,7 +1915,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { | TextUpdateEvent::SendTyping; _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); setEditMsgId(editMsgId); updateCmdStartShown(); updateControlsVisibility(); @@ -1929,7 +1928,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { } } else if (!readyToForward()) { const auto draft = _history->localDraft({}); - _processingReplyId = draft ? draft->msgId : MsgId(); + _processingReplyTo = draft ? draft->reply : FullReplyTo(); processReply(); } @@ -2147,7 +2146,8 @@ void HistoryWidget::showHistory( _saveEditMsgRequestId = 0; _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _editMsgId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); + _editMsgId = MsgId(); _canReplaceMedia = false; _photoEditMedia = nullptr; updateReplaceMediaButton(); @@ -2461,10 +2461,13 @@ void HistoryWidget::registerDraftSource() { if (!_history) { return; } + const auto peerId = _history->peer->id; const auto editMsgId = _editMsgId; const auto draft = [=] { return Storage::MessageDraft{ - editMsgId ? editMsgId : _replyToId, + (editMsgId + ? FullReplyTo{ FullMsgId(peerId, editMsgId) } + : _replyTo), _field->getTextWithTags(), _previewState, }; @@ -2901,7 +2904,11 @@ void HistoryWidget::updateControlsVisibility() { } updateFieldPlaceholder(); - if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) { + if (_editMsgId + || _replyTo + || readyToForward() + || (_previewData && _previewData->pendingTill >= 0) + || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); updateControlsGeometry(); @@ -2938,7 +2945,7 @@ void HistoryWidget::updateControlsVisibility() { _botMenuButton->hide(); } _kbScroll->hide(); - if (_replyToId || readyToForward() || _kbReplyTo) { + if (_replyTo || readyToForward() || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); updateControlsGeometry(); @@ -3889,7 +3896,7 @@ void HistoryWidget::hideSelectorControlsAnimated() { Api::SendAction HistoryWidget::prepareSendAction( Api::SendOptions options) const { auto result = Api::SendAction(_history, options); - result.replyTo = { .msgId = replyToId() }; + result.replyTo = replyTo(); result.options.sendAs = _sendAs ? _history->session().sendAsPeers().resolveChosen( _history->peer).get() @@ -4337,7 +4344,7 @@ void HistoryWidget::updateOverStates(QPoint pos) { _field->y() - st::historySendPadding - st::historyReplyHeight, width() - skip - _fieldBarCancel->width(), st::historyReplyHeight); - auto inReplyEditForward = (_editMsgId || replyToId() || isReadyToForward) + auto inReplyEditForward = (_editMsgId || replyTo() || isReadyToForward) && replyEditForwardInfoRect.contains(pos); auto inPhotoEdit = inReplyEditForward && _photoEditMedia @@ -4387,9 +4394,9 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { return; } - const auto lastKeyboardUsed = (_keyboard->forMsgId() - == FullMsgId(_peer->id, _history->lastKeyboardId)) - && (_keyboard->forMsgId() == FullMsgId(_peer->id, request.replyTo)); + const auto forMsgId = _keyboard->forMsgId(); + const auto lastKeyboardUsed = (forMsgId == request.replyTo.messageId) + && (forMsgId == FullMsgId(_peer->id, _history->lastKeyboardId)); // 'bot' may be nullptr in case of sending from FieldAutocomplete. const auto toSend = (request.replyTo/* || !bot*/) @@ -4398,14 +4405,14 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { auto message = Api::MessageToSend(prepareSendAction({})); message.textWithTags = { toSend, TextWithTags::Tags() }; - message.action.replyTo.msgId = request.replyTo + message.action.replyTo = request.replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) ? request.replyTo - : replyToId()) - : 0; + : replyTo()) + : FullReplyTo(); session().api().sendMessage(std::move(message)); if (request.replyTo) { - if (_replyToId == request.replyTo) { + if (_replyTo == request.replyTo) { cancelReply(); saveCloudDraft(); } @@ -4418,18 +4425,25 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { setInnerFocus(); } -void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { - if (!_peer || _peer != peer) return; +void HistoryWidget::hideSingleUseKeyboard(FullMsgId replyToId) { + if (!_peer || _peer->id != replyToId.peer) { + return; + } - bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_peer->id, _history->lastKeyboardId)) - && (_keyboard->forMsgId() == FullMsgId(_peer->id, replyTo)); - if (replyTo) { - if (_replyToId == replyTo) { + bool lastKeyboardUsed = (_keyboard->forMsgId() == replyToId) + && (_keyboard->forMsgId() + == FullMsgId(_peer->id, _history->lastKeyboardId)); + if (replyToId) { + if (_replyTo.messageId == replyToId) { cancelReply(); saveCloudDraft(); } - if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) { - if (_kbShown) toggleKeyboard(false); + if (_keyboard->singleUse() + && _keyboard->hasMarkup() + && lastKeyboardUsed) { + if (_kbShown) { + toggleKeyboard(false); + } _history->lastKeyboardUsed = true; } } @@ -4772,7 +4786,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _field->setMaxHeight(computeMaxFieldHeight()); _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyToId) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -4797,7 +4811,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? session().data().message(_keyboard->forMsgId()) : nullptr; - if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) { + if (_kbReplyTo && !_editMsgId && !_replyTo && fieldEnabled) { updateReplyToName(); updateReplyEditText(_kbReplyTo); } @@ -4817,7 +4831,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? session().data().message(_keyboard->forMsgId()) : nullptr; - if (_kbReplyTo && !_editMsgId && !_replyToId) { + if (_kbReplyTo && !_editMsgId && !_replyTo) { updateReplyToName(); updateReplyEditText(_kbReplyTo); } @@ -5589,11 +5603,11 @@ void HistoryWidget::itemRemoved(not_null item) { if (item == _replyEditMsg && _editMsgId) { cancelEdit(); } - if (item == _replyEditMsg && _replyToId) { + if (item == _replyEditMsg && _replyTo) { cancelReply(); } if (item == _processingReplyItem) { - _processingReplyId = 0; + _processingReplyTo = {}; _processingReplyItem = nullptr; } if (_kbReplyTo && item == _kbReplyTo) { @@ -5617,8 +5631,12 @@ void HistoryWidget::itemEdited(not_null item) { } } -MsgId HistoryWidget::replyToId() const { - return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0); +FullReplyTo HistoryWidget::replyTo() const { + return _replyTo + ? _replyTo + : _kbReplyTo + ? FullReplyTo{ _kbReplyTo->fullId() } + : FullReplyTo(); } bool HistoryWidget::hasSavedScroll() const { @@ -5753,7 +5771,7 @@ void HistoryWidget::updateHistoryGeometry( } else if (writeRestriction().has_value()) { newScrollHeight -= _unblock->height(); } - if (_editMsgId || replyToId() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) { + if (_editMsgId || replyTo() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) { newScrollHeight -= st::historyReplyHeight; } if (_kbShown) { @@ -5991,9 +6009,9 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { const auto wasVisible = _kbShown || _kbReplyTo; const auto wasMsgId = _keyboard->forMsgId(); auto changed = false; - if ((_replyToId && !_replyEditMsg) || _editMsgId || !_history) { + if ((_replyTo && !_replyEditMsg) || _editMsgId || !_history) { changed = _keyboard->updateMarkup(nullptr, force); - } else if (_replyToId && _replyEditMsg) { + } else if (_replyTo && _replyEditMsg) { changed = _keyboard->updateMarkup(_replyEditMsg, force); } else { const auto keyboardItem = _history->lastKeyboardId @@ -6010,7 +6028,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbScroll->scrollTo({ 0, 0 }); } - bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyToId || !_replyEditMsg); + bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyTo || !_replyEditMsg); if (hasMarkup || forceReply) { if (_keyboard->singleUse() && _keyboard->hasMarkup() @@ -6019,7 +6037,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { && _history->lastKeyboardUsed) { _history->lastKeyboardHiddenId = _history->lastKeyboardId; } - if (!isSearching() && !isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) { + if (!isSearching() && !isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyTo && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) { if (!_showAnimation) { if (hasMarkup) { _kbScroll->show(); @@ -6040,7 +6058,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? session().data().message(_keyboard->forMsgId()) : nullptr; - if (_kbReplyTo && !_replyToId) { + if (_kbReplyTo && !_replyTo) { updateReplyToName(); updateReplyEditText(_kbReplyTo); } @@ -6055,7 +6073,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _field->setMaxHeight(computeMaxFieldHeight()); _kbShown = false; _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -6071,7 +6089,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _field->setMaxHeight(computeMaxFieldHeight()); _kbShown = false; _kbReplyTo = nullptr; - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId && !_editMsgId) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo && !_editMsgId) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -6093,7 +6111,7 @@ void HistoryWidget::botCallbackSent(not_null item) { session().data().requestItemRepaint(item); - if (_replyToId == item->id) { + if (_replyTo.messageId == item->fullId()) { cancelReply(); } if (_keyboard->singleUse() @@ -6114,7 +6132,7 @@ int HistoryWidget::computeMaxFieldHeight() const { - (_groupCallBar ? _groupCallBar->height() : 0) - (_requestsBar ? _requestsBar->height() : 0) - ((_editMsgId - || replyToId() + || replyTo() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) ? st::historyReplyHeight @@ -6175,7 +6193,7 @@ bool HistoryWidget::cornerButtonsHas(HistoryView::CornerButtonType type) { void HistoryWidget::mousePressEvent(QMouseEvent *e) { const auto isReadyToForward = readyToForward(); const auto hasSecondLayer = (_editMsgId - || _replyToId + || _replyTo || isReadyToForward || _kbReplyTo); _replyForwardPressed = hasSecondLayer && QRect( @@ -6201,11 +6219,13 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } else { _forwardPanel->editOptions(controller()->uiShow()); } + } else if (replyTo() && replyTo().messageId.peer != _peer->id) { + // edit options } else { controller()->showPeerHistory( _peer, Window::SectionShow::Way::Forward, - _editMsgId ? _editMsgId : replyToId()); + _editMsgId ? _editMsgId : replyTo().messageId.msg); } } } @@ -6235,7 +6255,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (item && _field->empty() && !_editMsgId - && !_replyToId) { + && !_replyTo) { editMessage(item); return; } @@ -6303,14 +6323,17 @@ void HistoryWidget::handlePeerMigration() { } bool HistoryWidget::replyToPreviousMessage() { - if (!_history || _editMsgId || _history->isForum()) { + if (!_history + || _editMsgId + || _history->isForum() + || (_replyTo && _replyTo.messageId.peer != _history->peer->id)) { return false; } const auto fullId = FullMsgId( _history->peer->id, - _field->isVisible() - ? _replyToId - : _highlighter.latestSingleHighlightedMsgId()); + (_field->isVisible() + ? _replyTo.messageId.msg + : _highlighter.latestSingleHighlightedMsgId())); if (const auto item = session().data().message(fullId)) { if (const auto view = item->mainView()) { if (const auto previousView = view->previousDisplayedInBlocks()) { @@ -6334,14 +6357,17 @@ bool HistoryWidget::replyToPreviousMessage() { } bool HistoryWidget::replyToNextMessage() { - if (!_history || _editMsgId || _history->isForum()) { + if (!_history + || _editMsgId + || _history->isForum() + || (_replyTo && _replyTo.messageId.peer != _history->peer->id)) { return false; } const auto fullId = FullMsgId( _history->peer->id, - _field->isVisible() - ? _replyToId - : _highlighter.latestSingleHighlightedMsgId()); + (_field->isVisible() + ? _replyTo.messageId.msg + : _highlighter.latestSingleHighlightedMsgId())); if (const auto item = session().data().message(fullId)) { if (const auto view = item->mainView()) { if (const auto nextView = view->nextDisplayedInBlocks()) { @@ -7024,17 +7050,19 @@ void HistoryWidget::clearFieldText( setFieldText(TextWithTags(), events, fieldHistoryAction); } -void HistoryWidget::replyToMessage(FullMsgId itemId) { - if (const auto item = session().data().message(itemId)) { - replyToMessage(item); +void HistoryWidget::replyToMessage(FullReplyTo id) { + if (const auto item = session().data().message(id.messageId)) { + replyToMessage(item, id.quote); } } -void HistoryWidget::replyToMessage(not_null item) { +void HistoryWidget::replyToMessage( + not_null item, + TextWithTags quote) { if (isJoinChannel()) { return; } - _processingReplyId = item->id; + _processingReplyTo = { .messageId = item->fullId(), .quote = quote}; _processingReplyItem = item; processReply(); } @@ -7042,14 +7070,13 @@ void HistoryWidget::replyToMessage(not_null item) { void HistoryWidget::processReply() { const auto processContinue = [=] { return crl::guard(_list, [=] { - if (!_peer || !_processingReplyId) { + if (!_peer || !_processingReplyTo) { return; } else if (!_processingReplyItem) { _processingReplyItem = _peer->owner().message( - _peer, - _processingReplyId); + _processingReplyTo.messageId); if (!_processingReplyItem) { - _processingReplyId = 0; + _processingReplyTo = {}; } else { processReply(); } @@ -7057,16 +7084,16 @@ void HistoryWidget::processReply() { }); }; const auto processCancel = [=] { - _processingReplyId = 0; + _processingReplyTo = {}; _processingReplyItem = nullptr; }; - if (!_peer || !_processingReplyId) { + if (!_peer || !_processingReplyTo) { return processCancel(); } else if (!_processingReplyItem) { session().api().requestMessageData( - _peer, - _processingReplyId, + session().data().peer(_processingReplyTo.messageId.peer), + _processingReplyTo.messageId.msg, processContinue()); return; } else if (_processingReplyItem->history() == _migrated) { @@ -7107,7 +7134,7 @@ void HistoryWidget::processReply() { } void HistoryWidget::setReplyFieldsFromProcessing() { - if (!_processingReplyId || !_processingReplyItem) { + if (!_processingReplyTo || !_processingReplyItem) { return; } @@ -7116,22 +7143,21 @@ void HistoryWidget::setReplyFieldsFromProcessing() { _composeSearch->hideAnimated(); } - const auto id = base::take(_processingReplyId); + const auto id = base::take(_processingReplyTo); const auto item = base::take(_processingReplyItem); if (_editMsgId) { if (const auto localDraft = _history->localDraft({})) { - localDraft->msgId = id; + localDraft->reply = id; } else { _history->setLocalDraft(std::make_unique( TextWithTags(), id, - MsgId(), MessageCursor(), Data::PreviewState::Allowed)); } } else { _replyEditMsg = item; - _replyToId = id; + _replyTo = id; updateReplyEditText(_replyEditMsg); updateCanSendMessage(); updateBotKeyboard(); @@ -7170,11 +7196,10 @@ void HistoryWidget::editMessage(not_null item) { _send->clearState(); } if (!_editMsgId) { - if (_replyToId || !_field->empty()) { + if (_replyTo || !_field->empty()) { _history->setLocalDraft(std::make_unique( _field, - _replyToId, - MsgId(), // topicRootId + _replyTo, _previewState)); } else { _history->clearLocalDraft({}); @@ -7198,8 +7223,7 @@ void HistoryWidget::editMessage(not_null item) { : Data::PreviewState::EmptyOnEdit; _history->setLocalEditDraft(std::make_unique( editData, - item->id, - MsgId(), // topicRootId + FullReplyTo{ item->fullId() }, cursor, previewState)); applyDraft(); @@ -7261,17 +7285,18 @@ bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const { bool HistoryWidget::lastForceReplyReplied() const { return _peer && _keyboard->forceReply() - && _keyboard->forMsgId() == FullMsgId(_peer->id, _history->lastKeyboardId) - && _keyboard->forMsgId().msg == replyToId(); + && _keyboard->forMsgId() == replyTo().messageId + && (_keyboard->forMsgId() + == FullMsgId(_peer->id, _history->lastKeyboardId)); } bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { bool wasReply = false; - if (_replyToId) { + if (_replyTo) { wasReply = true; _processingReplyItem = _replyEditMsg = nullptr; - _processingReplyId = _replyToId = 0; + _processingReplyTo = _replyTo = FullReplyTo(); mouseMoveEvent(0); if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) { _fieldBarCancel->hide(); @@ -7284,11 +7309,11 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { updateControlsGeometry(); update(); } else if (const auto localDraft = (_history ? _history->localDraft({}) : nullptr)) { - if (localDraft->msgId) { + if (localDraft->reply) { if (localDraft->textWithTags.text.isEmpty()) { _history->clearLocalDraft({}); } else { - localDraft->msgId = 0; + localDraft->reply = {}; } } } @@ -7344,7 +7369,7 @@ void HistoryWidget::cancelEdit() { saveDraft(); mouseMoveEvent(nullptr); - if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) { + if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyTo()) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -7376,7 +7401,7 @@ void HistoryWidget::cancelFieldAreaState() { cancelEdit(); } else if (readyToForward()) { _history->setForwardDraft(MsgId(), {}); - } else if (_replyToId) { + } else if (_replyTo) { cancelReply(); } else if (_kbReplyTo) { toggleKeyboard(); @@ -7512,7 +7537,7 @@ void HistoryWidget::updatePreview() { preview.description, Ui::DialogTextOptions()); } - } else if (!readyToForward() && !replyToId() && !_editMsgId) { + } else if (!readyToForward() && !replyTo() && !_editMsgId) { _fieldBarCancel->hide(); updateMouseTracking(); } @@ -7579,7 +7604,7 @@ bool HistoryWidget::updateCanSendMessage() { if (!_peer) { return false; } - const auto replyTo = (_replyToId && !_editMsgId) ? _replyEditMsg : 0; + const auto replyTo = (_replyTo && !_editMsgId) ? _replyEditMsg : 0; const auto topic = replyTo ? replyTo->topic() : nullptr; const auto allWithoutPolls = Data::AllSendRestrictions() & ~ChatRestriction::SendPolls; @@ -7657,7 +7682,7 @@ void HistoryWidget::escape() { } } else if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideAnimated(); - } else if (_replyToId && _field->getTextWithTags().text.isEmpty()) { + } else if (_replyTo && _field->getTextWithTags().text.isEmpty()) { cancelReply(); } else if (auto &voice = _voiceRecordBar; voice->isActive()) { voice->showDiscardBox(nullptr, anim::type::normal); @@ -7752,7 +7777,8 @@ void HistoryWidget::messageDataReceived( MsgId msgId) { if (!_peer || _peer != peer || !msgId) { return; - } else if (_editMsgId == msgId || _replyToId == msgId) { + } else if (_editMsgId == msgId + || (_replyTo.messageId == FullMsgId(peer->id, msgId))) { updateReplyEditTexts(true); } } @@ -7775,14 +7801,14 @@ void HistoryWidget::updateReplyEditText(not_null item) { void HistoryWidget::updateReplyEditTexts(bool force) { if (!force) { - if (_replyEditMsg || (!_editMsgId && !_replyToId)) { + if (_replyEditMsg || (!_editMsgId && !_replyTo)) { return; } } if (!_replyEditMsg && _peer) { _replyEditMsg = session().data().message( - _peer->id, - _editMsgId ? _editMsgId : _replyToId); + _editMsgId ? _peer->id : _replyTo.messageId.peer, + _editMsgId ? _editMsgId : _replyTo.messageId.msg); } if (_replyEditMsg) { const auto media = _replyEditMsg->media(); @@ -7828,7 +7854,7 @@ void HistoryWidget::updateForwarding() { void HistoryWidget::updateReplyToName() { if (_editMsgId) { return; - } else if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) { + } else if (!_replyEditMsg && (_replyTo || !_kbReplyTo)) { return; } const auto from = [&] { @@ -7862,8 +7888,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { auto backy = _field->y() - st::historySendPadding; auto backh = fieldHeight() + 2 * st::historySendPadding; auto hasForward = readyToForward(); - auto drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo; - if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { + auto drawMsgText = (_editMsgId || _replyTo) ? _replyEditMsg : _kbReplyTo; + if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) { if (!_editMsgId && drawMsgText && (_replyToNameVersion @@ -7898,7 +7924,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { }); } - if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { + if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) { const auto now = crl::now(); const auto paused = p.inactive(); const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler); @@ -8097,7 +8123,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { const auto restrictionHidden = fieldOrDisabledShown() || isRecording(); if (restrictionHidden - || replyToId() + || replyTo() || readyToForward() || _kbShown) { drawField(p, clip); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 5011c52cb..84e4b8be9 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -182,12 +182,14 @@ public: MessageIdsList getSelectedItems() const; void itemEdited(not_null item); - void replyToMessage(FullMsgId itemId); - void replyToMessage(not_null item); + void replyToMessage(FullReplyTo id); + void replyToMessage( + not_null item, + TextWithTags quote = {}); void editMessage(FullMsgId itemId); void editMessage(not_null item); - MsgId replyToId() const; + [[nodiscard]] FullReplyTo replyTo() const; bool lastForceReplyReplied(const FullMsgId &replyTo) const; bool lastForceReplyReplied() const; bool cancelReply(bool lastKeyboardUsed = false); @@ -204,7 +206,7 @@ public: void escape(); void sendBotCommand(const Bot::SendCommandRequest &request); - void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); + void hideSingleUseKeyboard(FullMsgId replyToId); bool insertBotCommand(const QString &cmd); bool eventFilter(QObject *obj, QEvent *e) override; @@ -633,11 +635,11 @@ private: void searchInChat(); MTP::Sender _api; - MsgId _replyToId = 0; + FullReplyTo _replyTo; Ui::Text::String _replyToName; int _replyToNameVersion = 0; - MsgId _processingReplyId = 0; + FullReplyTo _processingReplyTo; HistoryItem *_processingReplyItem = nullptr; MsgId _editMsgId = 0; 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 d19da9a57..56f6cf752 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -353,7 +353,7 @@ public: void init(); void editMessage(FullMsgId id, bool photoEditAllowed = false); - void replyToMessage(FullMsgId id); + void replyToMessage(FullReplyTo id); void updateForwarding( Data::Thread *thread, Data::ResolvedForwardDraft items); @@ -367,7 +367,7 @@ public: [[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool readyToForward() const; [[nodiscard]] const HistoryItemsList &forwardItems() const; - [[nodiscard]] FullMsgId replyingToMessage() const; + [[nodiscard]] FullReplyTo replyingToMessage() const; [[nodiscard]] FullMsgId editMsgId() const; [[nodiscard]] rpl::producer editMsgIdValue() const; [[nodiscard]] rpl::producer scrollToItemRequests() const; @@ -375,7 +375,7 @@ public: [[nodiscard]] MessageToEdit queryToEdit(); [[nodiscard]] WebPageId webPageId() const; - [[nodiscard]] MsgId getDraftMessageId() const; + [[nodiscard]] FullReplyTo getDraftReply() const; [[nodiscard]] rpl::producer<> editCancelled() const { return _editCancelled.events(); } @@ -425,7 +425,7 @@ private: rpl::lifetime _previewLifetime; rpl::variable _editMsgId; - rpl::variable _replyToId; + rpl::variable _replyTo; std::unique_ptr _forwardPanel; rpl::producer<> _toForwardUpdated; @@ -508,14 +508,14 @@ void FieldHeader::init() { _editMsgId.value( ) | rpl::start_with_next([=](FullMsgId value) { - const auto shown = value ? value : _replyToId.current(); + const auto shown = value ? value : _replyTo.current().messageId; setShownMessage(_data->message(shown)); }, lifetime()); - _replyToId.value( - ) | rpl::start_with_next([=](FullMsgId value) { + _replyTo.value( + ) | rpl::start_with_next([=](const FullReplyTo &value) { if (!_editMsgId.current()) { - setShownMessage(_data->message(value)); + setShownMessage(_data->message(value.messageId)); } }, lifetime()); @@ -529,7 +529,7 @@ void FieldHeader::init() { if (_editMsgId.current() == update.item->fullId()) { _editCancelled.fire({}); } - if (_replyToId.current() == update.item->fullId()) { + if (_replyTo.current().messageId == update.item->fullId()) { _replyCancelled.fire({}); } } else { @@ -545,7 +545,7 @@ void FieldHeader::init() { _editCancelled.fire({}); } else if (readyToForward()) { _forwardCancelled.fire({}); - } else if (_replyToId.current()) { + } else if (_replyTo.current()) { _replyCancelled.fire({}); } updateVisible(); @@ -624,7 +624,7 @@ void FieldHeader::init() { } else { auto id = isEditingMessage() ? _editMsgId.current() - : replyingToMessage(); + : replyingToMessage().messageId; _scrollToItemRequests.fire(std::move(id)); } } @@ -692,16 +692,18 @@ void FieldHeader::setShownMessage(HistoryItem *item) { } void FieldHeader::resolveMessageData() { - const auto id = (isEditingMessage() ? _editMsgId : _replyToId).current(); + const auto id = isEditingMessage() + ? _editMsgId.current() + : _replyTo.current().messageId; if (!id) { return; } const auto peer = _data->peer(id.peer); const auto itemId = id.msg; const auto callback = crl::guard(this, [=] { - const auto now = (isEditingMessage() - ? _editMsgId - : _replyToId).current(); + const auto now = isEditingMessage() + ? _editMsgId.current() + : _replyTo.current().messageId; if (now == id && !_shownMessage) { if (const auto message = _data->message(peer, itemId)) { setShownMessage(message); @@ -950,8 +952,8 @@ const HistoryItemsList &FieldHeader::forwardItems() const { return _forwardPanel->items(); } -FullMsgId FieldHeader::replyingToMessage() const { - return _replyToId.current(); +FullReplyTo FieldHeader::replyingToMessage() const { + return _replyTo.current(); } bool FieldHeader::hasPreview() const { @@ -962,8 +964,10 @@ WebPageId FieldHeader::webPageId() const { return hasPreview() ? _preview.data->id : CancelledWebPageId; } -MsgId FieldHeader::getDraftMessageId() const { - return (isEditingMessage() ? _editMsgId : _replyToId).current().msg; +FullReplyTo FieldHeader::getDraftReply() const { + return isEditingMessage() + ? FullReplyTo{ _editMsgId.current() } + : _replyTo.current(); } void FieldHeader::updateControlsGeometry(QSize size) { @@ -992,8 +996,8 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { update(); } -void FieldHeader::replyToMessage(FullMsgId id) { - _replyToId = id; +void FieldHeader::replyToMessage(FullReplyTo id) { + _replyTo = id; } void FieldHeader::updateForwarding( @@ -1156,6 +1160,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { } unregisterDraftSources(); _history = history; + _topicRootId = args.topicRootId; _historyLifetime.destroy(); _header->setHistory(args); registerDraftSource(); @@ -1193,8 +1198,10 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { orderControls(); } -void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { +void ComposeControls::setCurrentDialogsEntryState( + Dialogs::EntryState state) { unregisterDraftSources(); + state.currentReplyTo.topicRootId = _topicRootId; _currentDialogsEntryState = state; updateForwarding(); registerDraftSource(); @@ -1472,16 +1479,12 @@ void ComposeControls::saveFieldToHistoryLocalDraft() { if (!_history || !key) { return; } - const auto id = _header->getDraftMessageId(); + const auto id = _header->getDraftReply(); if (_preview && (id || !_field->empty())) { const auto key = draftKeyCurrent(); _history->setDraft( key, - std::make_unique( - _field, - _header->getDraftMessageId(), - key.topicRootId(), - _preview->state())); + std::make_unique(_field, id, _preview->state())); } else { _history->clearDraft(draftKeyCurrent()); } @@ -1757,7 +1760,7 @@ void ComposeControls::initKeyHandler() { } } _replyNextRequests.fire({ - .replyId = replyingToMessage(), + .replyId = replyingToMessage().messageId, .direction = (isDown ? ReplyNextRequest::Direction::Next : ReplyNextRequest::Direction::Previous) @@ -2037,8 +2040,8 @@ Data::DraftKey ComposeControls::draftKey(DraftType type) const { case Section::History: case Section::Replies: return (type == DraftType::Edit) - ? Key::LocalEdit(_currentDialogsEntryState.rootId) - : Key::Local(_currentDialogsEntryState.rootId); + ? Key::LocalEdit(_topicRootId) + : Key::Local(_topicRootId); case Section::Scheduled: return (type == DraftType::Edit) ? Key::ScheduledEdit() @@ -2102,7 +2105,7 @@ void ComposeControls::registerDraftSource() { if (key != Data::DraftKey::None()) { const auto draft = [=] { return Storage::MessageDraft{ - _header->getDraftMessageId(), + _header->getDraftReply(), _field->getTextWithTags(), _preview->state(), }; @@ -2150,8 +2153,8 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { const auto draft = editDraft ? editDraft : _history->draft(draftKey(DraftType::Normal)); - const auto editingId = (draft == editDraft) - ? FullMsgId{ _history->peer->id, draft ? draft->msgId : 0 } + const auto editingId = (draft && draft == editDraft) + ? draft->reply.messageId : FullMsgId(); InvokeQueued(_autocomplete.get(), [=] { updateStickersByEmoji(); }); @@ -2232,7 +2235,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { } else { _canReplaceMedia = false; _photoEditMedia = nullptr; - _header->replyToMessage({ _history->peer->id, draft->msgId }); + _header->replyToMessage(draft->reply); if (_header->replyingToMessage()) { cancelForward(); } @@ -2241,9 +2244,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { } void ComposeControls::cancelForward() { - _history->setForwardDraft( - _currentDialogsEntryState.rootId, - {}); + _history->setForwardDraft(_topicRootId, {}); updateForwarding(); } @@ -2840,8 +2841,7 @@ void ComposeControls::toggleTabbedSelectorMode() { && !_regularWindow->adaptive().isOneColumn()) { Core::App().settings().setTabbedSelectorSectionEnabled(true); Core::App().saveSettingsDelayed(); - const auto topic = _history->peer->forumTopicFor( - _currentDialogsEntryState.rootId); + const auto topic = _history->peer->forumTopicFor(_topicRootId); pushTabbedSelectorToThirdSection( (topic ? topic : (Data::Thread*)_history), Window::SectionShow::Way::ClearStack); @@ -2900,8 +2900,10 @@ void ComposeControls::editMessage(not_null item) { key, std::make_unique( editData, - item->id, - key.topicRootId(), + FullReplyTo{ + .messageId = item->fullId(), + .topicRootId = key.topicRootId(), + }, cursor, previewState)); applyDraft(); @@ -2968,25 +2970,26 @@ void ComposeControls::maybeCancelEditMessage() { } } -void ComposeControls::replyToMessage(FullMsgId id) { +void ComposeControls::replyToMessage(FullReplyTo id) { Expects(_history != nullptr); Expects(draftKeyCurrent() != Data::DraftKey::None()); + id.topicRootId = _topicRootId; if (!id) { cancelReplyMessage(); return; } if (isEditingMessage()) { const auto key = draftKey(DraftType::Normal); + Assert(key.topicRootId() == id.topicRootId); if (const auto localDraft = _history->draft(key)) { - localDraft->msgId = id.msg; + localDraft->reply = id; } else { _history->setDraft( key, std::make_unique( TextWithTags(), - id.msg, - key.topicRootId(), + id, MessageCursor(), Data::PreviewState::Allowed)); } @@ -3008,11 +3011,11 @@ void ComposeControls::cancelReplyMessage() { if (_history) { const auto key = draftKey(DraftType::Normal); if (const auto localDraft = _history->draft(key)) { - if (localDraft->msgId) { + if (localDraft->reply.messageId) { if (localDraft->textWithTags.text.isEmpty()) { _history->clearDraft(key); } else { - localDraft->msgId = 0; + localDraft->reply = {}; } } } @@ -3025,7 +3028,7 @@ void ComposeControls::cancelReplyMessage() { } void ComposeControls::updateForwarding() { - const auto rootId = _currentDialogsEntryState.rootId; + const auto rootId = _topicRootId; const auto thread = (_history && rootId) ? _history->peer->forumTopicFor(rootId) : (Data::Thread*)_history; @@ -3118,7 +3121,7 @@ void ComposeControls::initForwardProcess() { ) | rpl::start_with_next([=](const Data::EntryUpdate &update) { if (const auto topic = update.entry->asTopic()) { if (topic->history() == _history - && topic->rootId() == _currentDialogsEntryState.rootId) { + && topic->rootId() == _topicRootId) { updateForwarding(); } } @@ -3145,8 +3148,10 @@ bool ComposeControls::isEditingMessage() const { return _header->isEditingMessage(); } -FullMsgId ComposeControls::replyingToMessage() const { - return _header->replyingToMessage(); +FullReplyTo ComposeControls::replyingToMessage() const { + auto result = _header->replyingToMessage(); + result.topicRootId = _topicRootId; + return result; } bool ComposeControls::readyToForward() const { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 3dc5e46fa..b98cbca83 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -185,7 +185,7 @@ public: [[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool readyToForward() const; [[nodiscard]] const HistoryItemsList &forwardItems() const; - [[nodiscard]] FullMsgId replyingToMessage() const; + [[nodiscard]] FullReplyTo replyingToMessage() const; [[nodiscard]] bool preventsClose(Fn &&continueCallback) const; @@ -198,7 +198,7 @@ public: void cancelEditMessage(); void maybeCancelEditMessage(); // Confirm if changed and cancel. - void replyToMessage(FullMsgId id); + void replyToMessage(FullReplyTo id); void cancelReplyMessage(); void updateForwarding(); @@ -345,6 +345,7 @@ private: rpl::event_stream _stickerOrEmojiChosen; History *_history = nullptr; + MsgId _topicRootId = 0; Fn _showSlowmodeError; Fn _sendActionFactory; rpl::variable _slowmodeSecondsLeft; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index f74453919..d1bf5c965 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -117,7 +117,7 @@ void ForwardPanel::checkTexts() { : kNameNoCaptionsVersion; if (keepNames) { for (const auto item : _data.items) { - if (const auto from = item->senderOriginal()) { + if (const auto from = item->originalSender()) { version += from->nameVersion(); } else if (const auto info = item->hiddenSenderInfo()) { ++version; @@ -154,7 +154,7 @@ void ForwardPanel::updateTexts() { auto names = std::vector(); names.reserve(_data.items.size()); for (const auto item : _data.items) { - if (const auto from = item->senderOriginal()) { + if (const auto from = item->originalSender()) { if (!insertedPeers.contains(from)) { insertedPeers.emplace(from); names.push_back(from->shortName()); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 60c6e8191..57862872a 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -655,7 +655,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { } if (const auto msgsigned = item->Get()) { if (!msgsigned->isAnonymousRank) { - result.author = msgsigned->author; + result.author = msgsigned->postAuthor; } } if (message->displayedEditDate()) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 7bc9c5b57..3073d3495 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -601,7 +601,7 @@ bool AddReplyToMessageAction( if (!item) { return; } - list->replyToMessageRequestNotify(item->fullId()); + list->replyToMessageRequestNotify({ item->fullId() }); }, &st::menuIconReply); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index aab999319..58e1676b0 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -68,10 +68,10 @@ Element *MousedElement/* = nullptr*/; HistoryMessageForwarded *prevForwarded, not_null item, HistoryMessageForwarded *forwarded) { - const auto sender = previous->senderOriginal(); + const auto sender = previous->originalSender(); if ((prevForwarded != nullptr) != (forwarded != nullptr)) { return false; - } else if (sender != item->senderOriginal()) { + } else if (sender != item->originalSender()) { return false; } else if (!prevForwarded || sender) { return true; @@ -178,7 +178,7 @@ bool DefaultElementDelegate::elementIsChatWide() { return false; } -void DefaultElementDelegate::elementReplyTo(const FullMsgId &to) { +void DefaultElementDelegate::elementReplyTo(const FullReplyTo &to) { } void DefaultElementDelegate::elementStartInteraction( @@ -275,8 +275,10 @@ QString DateTooltipText(not_null view) { } if (view->isSignedAuthorElided()) { if (const auto msgsigned = item->Get()) { - dateText += '\n' - + tr::lng_signed_author(tr::now, lt_user, msgsigned->author); + dateText += '\n' + tr::lng_signed_author( + tr::now, + lt_user, + msgsigned->postAuthor); } } return dateText; @@ -1447,7 +1449,7 @@ void Element::unloadHeavyPart() { _heavyCustomEmoji = false; _text.unloadPersistentAnimation(); if (const auto reply = data()->Get()) { - reply->replyToText.unloadPersistentAnimation(); + reply->unloadPersistentAnimation(); } } } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 4f62303c7..02635118a 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -100,7 +100,7 @@ public: virtual void elementHandleViaClick(not_null bot) = 0; virtual bool elementIsChatWide() = 0; virtual not_null elementPathShiftGradient() = 0; - virtual void elementReplyTo(const FullMsgId &to) = 0; + virtual void elementReplyTo(const FullReplyTo &to) = 0; virtual void elementStartInteraction(not_null view) = 0; virtual void elementStartPremium( not_null view, @@ -149,7 +149,7 @@ public: const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; - void elementReplyTo(const FullMsgId &to) override; + void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( not_null view, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 90924281c..f2ed79c39 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1735,7 +1735,7 @@ not_null ListWidget::elementPathShiftGradient() { return _pathGradient.get(); } -void ListWidget::elementReplyTo(const FullMsgId &to) { +void ListWidget::elementReplyTo(const FullReplyTo &to) { replyToMessageRequestNotify(to); } @@ -2474,7 +2474,7 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { mouseActionCancel(); switch (CurrentQuickAction()) { case DoubleClickQuickAction::Reply: { - replyToMessageRequestNotify(_overElement->data()->fullId()); + replyToMessageRequestNotify({ _overElement->data()->fullId() }); } break; case DoubleClickQuickAction::React: { toggleFavoriteReaction(_overElement); @@ -3855,12 +3855,12 @@ bool ListWidget::lastMessageEditRequestNotify() const { } } -rpl::producer ListWidget::replyToMessageRequested() const { +rpl::producer ListWidget::replyToMessageRequested() const { return _requestedToReplyToMessage.events(); } -void ListWidget::replyToMessageRequestNotify(FullMsgId item) { - _requestedToReplyToMessage.fire(std::move(item)); +void ListWidget::replyToMessageRequestNotify(FullReplyTo id) { + _requestedToReplyToMessage.fire(std::move(id)); } rpl::producer ListWidget::readMessageRequested() const { @@ -3878,10 +3878,10 @@ void ListWidget::replyNextMessage(FullMsgId fullId, bool next) { if (!view->data()->isRegular()) { return replyNextMessage(newFullId, next); } - replyToMessageRequestNotify(newFullId); + replyToMessageRequestNotify({ newFullId }); _requestedToShowMessage.fire_copy(newFullId); } else { - replyToMessageRequestNotify(FullMsgId()); + replyToMessageRequestNotify({}); _highlighter.clear(); } }; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 704fb1685..138f39cca 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -277,8 +277,8 @@ public: [[nodiscard]] rpl::producer editMessageRequested() const; void editMessageRequestNotify(FullMsgId item) const; [[nodiscard]] bool lastMessageEditRequestNotify() const; - [[nodiscard]] rpl::producer replyToMessageRequested() const; - void replyToMessageRequestNotify(FullMsgId item); + [[nodiscard]] rpl::producer replyToMessageRequested() const; + void replyToMessageRequestNotify(FullReplyTo id); [[nodiscard]] rpl::producer readMessageRequested() const; [[nodiscard]] rpl::producer showMessageRequested() const; void replyNextMessage(FullMsgId fullId, bool next = true); @@ -323,7 +323,7 @@ public: void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; - void elementReplyTo(const FullMsgId &to) override; + void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( not_null view, @@ -735,7 +735,7 @@ private: base::Timer _touchScrollTimer; rpl::event_stream _requestedToEditMessage; - rpl::event_stream _requestedToReplyToMessage; + rpl::event_stream _requestedToReplyToMessage; rpl::event_stream _requestedToReadMessage; rpl::event_stream _requestedToShowMessage; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index a50b92d27..87802b4f2 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -477,7 +477,7 @@ void Message::refreshRightBadge() { } else if (data()->author()->isMegagroup()) { if (const auto msgsigned = data()->Get()) { Assert(msgsigned->isAnonymousRank); - return msgsigned->author; + return msgsigned->postAuthor; } } const auto channel = data()->history()->peer->asMegagroup(); @@ -801,9 +801,9 @@ QSize Message::performCountOptimalSize() { accumulate_max(maxWidth, namew); } if (reply) { - auto replyw = st::msgPadding.left() + reply->maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); - if (reply->replyToVia) { - replyw += st::msgServiceFont->spacew + reply->replyToVia->maxWidth; + auto replyw = st::msgPadding.left() + reply->maxWidth() - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); + if (reply->originalVia) { + replyw += st::msgServiceFont->spacew + reply->originalVia->maxWidth; } accumulate_max(maxWidth, replyw); } @@ -1736,8 +1736,8 @@ void Message::clickHandlerPressedChanged( toggleTopicButtonRipple(pressed); } else if (_viewButton) { _viewButton->checkLink(handler, pressed); - } else if (const auto reply = displayedReply(); - reply && (handler == reply->replyToLink())) { + } else if (const auto reply = displayedReply() + ; reply && (handler == reply->link())) { toggleReplyRipple(pressed); } } @@ -2469,10 +2469,11 @@ bool Message::getStateReplyInfo( trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()); - if ((reply->replyToMsg || reply->replyToStory) - && g.contains(point)) { - outResult->link = reply->replyToLink(); - reply->ripple.lastPoint = point - g.topLeft(); + if (g.contains(point)) { + if (const auto link = reply->link()) { + outResult->link = reply->link(); + reply->ripple.lastPoint = point - g.topLeft(); + } } return true; } @@ -3439,7 +3440,7 @@ ClickHandlerPtr Message::fastReplyLink() const { } const auto itemId = data()->fullId(); _fastReplyLink = std::make_shared([=] { - delegate()->elementReplyTo(itemId); + delegate()->elementReplyTo({ itemId }); }); return _fastReplyLink; } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 7b878ec52..080984847 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1002,7 +1002,7 @@ void RepliesWidget::sendingFilesConfirmed( album, action); } - if (_composeControls->replyingToMessage().msg == action.replyTo.msgId) { + if (_composeControls->replyingToMessage() == action.replyTo) { _composeControls->cancelReplyMessage(); refreshTopBarActiveChat(); } @@ -1123,9 +1123,9 @@ bool RepliesWidget::showSendingFilesError( } Api::SendAction RepliesWidget::prepareSendAction( - Api::SendOptions options) const { + Api::SendOptions options) const { auto result = Api::SendAction(_history, options); - result.replyTo = { .msgId = replyToId(), .topicRootId = _rootId }; + result.replyTo = replyTo(); result.options.sendAs = _composeControls->sendAsPeer(); return result; } @@ -1444,28 +1444,29 @@ SendMenu::Type RepliesWidget::sendMenuType() const { : SendMenu::Type::Scheduled; } +FullReplyTo RepliesWidget::replyTo() const { + if (auto custom = _composeControls->replyingToMessage()) { + custom.topicRootId = _rootId; + return custom; + } + return FullReplyTo{ + .messageId = FullMsgId(_history->peer->id, _rootId), + .topicRootId = _rootId, + }; +} + void RepliesWidget::refreshTopBarActiveChat() { using namespace Dialogs; const auto state = EntryState{ .key = (_topic ? Key{ _topic } : Key{ _history }), .section = EntryState::Section::Replies, - .rootId = _rootId, - .currentReplyToId = _composeControls->replyingToMessage().msg, + .currentReplyTo = replyTo(), }; _topBar->setActiveChat(state, _sendAction.get()); _composeControls->setCurrentDialogsEntryState(state); controller()->setCurrentDialogsEntryState(state); } -MsgId RepliesWidget::replyToId() const { - const auto custom = _composeControls->replyingToMessage().msg; - return custom - ? custom - : (_rootId == Data::ForumTopic::kGeneralId) - ? MsgId() - : _rootId; -} - void RepliesWidget::refreshUnreadCountBadge(std::optional count) { if (count.has_value()) { _cornerButtons.updateJumpDownVisibility(count); @@ -2052,8 +2053,8 @@ bool RepliesWidget::confirmSendingFiles( insertTextOnCancel); } -void RepliesWidget::replyToMessage(FullMsgId itemId) { - _composeControls->replyToMessage(itemId); +void RepliesWidget::replyToMessage(FullReplyTo id) { + _composeControls->replyToMessage(std::move(id)); refreshTopBarActiveChat(); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index b048dca36..14104e913 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -243,7 +243,7 @@ private: mtpRequestId *const saveEditMsgRequestId); void chooseAttach(std::optional overrideSendImagesAsPhotos); [[nodiscard]] SendMenu::Type sendMenuType() const; - [[nodiscard]] MsgId replyToId() const; + [[nodiscard]] FullReplyTo replyTo() const; [[nodiscard]] HistoryItem *lookupRoot() const; [[nodiscard]] Data::ForumTopic *lookupTopic(); [[nodiscard]] bool computeAreComments() const; @@ -252,7 +252,7 @@ private: void pushReplyReturn(not_null item); void checkReplyReturns(); void recountChatWidth(); - void replyToMessage(FullMsgId itemId); + void replyToMessage(FullReplyTo id); void refreshTopBarActiveChat(); void refreshUnreadCountBadge(std::optional count); diff --git a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp index 5a3ca97bc..cc7652f62 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp @@ -112,7 +112,7 @@ bool TranslateTracker::add( } if (!skipDependencies) { if (const auto reply = item->Get()) { - if (const auto to = reply->replyToMsg.get()) { + if (const auto to = reply->resolvedMessage.get()) { add(to, true); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 52008c66f..2e01c30c9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1069,7 +1069,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { if (reply) { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { - result.link = reply->replyToLink(); + result.link = reply->link(); reply->ripple.lastPoint = point - replyRect.topLeft(); if (!reply->ripple.animation) { reply->ripple.animation = std::make_unique( @@ -1737,7 +1737,7 @@ int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); } if (reply) { - accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); + accumulate_max(result, st::msgReplyPadding.left() + reply->maxWidth()); } return result; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 4ff49e604..4f5e9a5c5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -464,7 +464,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { if (reply) { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { - result.link = reply->replyToLink(); + result.link = reply->link(); reply->ripple.lastPoint = point - replyRect.topLeft(); if (!reply->ripple.animation) { reply->ripple.animation = std::make_unique( @@ -519,10 +519,9 @@ bool UnwrappedMedia::hasTextForCopy() const { return _content->hasTextForCopy(); } -bool UnwrappedMedia::dragItemByHandler( - const ClickHandlerPtr &p) const { +bool UnwrappedMedia::dragItemByHandler(const ClickHandlerPtr &p) const { const auto reply = _parent->displayedReply(); - return !(reply && (reply->replyToLink() == p)); + return !reply || (reply->link() != p); } QRect UnwrappedMedia::contentRectForReactions() const { @@ -642,7 +641,7 @@ int UnwrappedMedia::additionalWidth( accumulate_max(result, 2 * st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.right()); } if (reply) { - accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); + accumulate_max(result, st::msgReplyPadding.left() + reply->maxWidth()); } return result; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 3473738cf..d9a8d3606 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -602,8 +602,7 @@ bool MainWidget::shareUrl( const auto topicRootId = thread->topicRootId(); history->setLocalDraft(std::make_unique( textWithTags, - 0, // replyTo - topicRootId, + FullReplyTo{ .topicRootId = topicRootId }, cursor, Data::PreviewState::Allowed)); history->clearLocalEditDraft(topicRootId); @@ -746,8 +745,8 @@ void MainWidget::sendBotCommand(Bot::SendCommandRequest request) { } } -void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { - _history->hideSingleUseKeyboard(peer, replyTo); +void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) { + _history->hideSingleUseKeyboard(replyToId); } void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index fa33b5d83..386434ecc 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -188,7 +188,7 @@ public: not_null data); void sendBotCommand(Bot::SendCommandRequest request); - void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); + void hideSingleUseKeyboard(FullMsgId replyToId); void searchMessages(const QString &query, Dialogs::Key inChat); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index d8544ffc5..e51098feb 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -3037,7 +3037,7 @@ void OverlayWidget::refreshMediaViewer() { void OverlayWidget::refreshFromLabel() { if (_message) { - _from = _message->senderOriginal(); + _from = _message->originalSender(); if (const auto info = _message->hiddenSenderInfo()) { _fromName = info->name; } else { diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 348a11591..8559bf621 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -59,6 +59,7 @@ constexpr auto kMultiDraftTagOld = quint64(0xFFFF'FFFF'FFFF'FF01ULL); constexpr auto kMultiDraftCursorsTagOld = quint64(0xFFFF'FFFF'FFFF'FF02ULL); constexpr auto kMultiDraftTag = quint64(0xFFFF'FFFF'FFFF'FF03ULL); constexpr auto kMultiDraftCursorsTag = quint64(0xFFFF'FFFF'FFFF'FF04ULL); +constexpr auto kRichDraftsTag = quint64(0xFFFF'FFFF'FFFF'FF05ULL); enum { // Local Storage Keys lskUserMap = 0x00, @@ -1041,7 +1042,7 @@ void EnumerateDrafts( } callback( key, - draft->msgId, + draft->reply, draft->textWithTags, draft->previewState, draft->cursor); @@ -1049,12 +1050,12 @@ void EnumerateDrafts( for (const auto &[key, source] : sources) { const auto draft = source.draft(); const auto cursor = source.cursor(); - if (draft.msgId + if (draft.reply.messageId || !draft.textWithTags.text.isEmpty() || cursor != MessageCursor()) { callback( key, - draft.msgId, + draft.reply, draft.textWithTags, draft.previewState, cursor); @@ -1119,14 +1120,18 @@ void Account::writeDrafts(not_null history) { auto size = int(sizeof(quint64) * 2 + sizeof(quint32)); const auto sizeCallback = [&]( auto&&, // key - MsgId, // msgId + const FullReplyTo &reply, const TextWithTags &text, Data::PreviewState, auto&&) { // cursor size += sizeof(qint64) // key + Serialize::stringSize(text.text) + sizeof(qint64) + TextUtilities::SerializeTagsSize(text.tags) - + sizeof(qint64) + sizeof(qint32); // msgId, previewState + + sizeof(qint64) + sizeof(qint64) // messageId + + Serialize::stringSize(reply.quote.text) + + sizeof(qint64) + + TextUtilities::SerializeTagsSize(reply.quote.tags) + + sizeof(qint32); // previewState }; EnumerateDrafts( map, @@ -1136,13 +1141,13 @@ void Account::writeDrafts(not_null history) { EncryptedDescriptor data(size); data.stream - << quint64(kMultiDraftTag) + << quint64(kRichDraftsTag) << SerializePeerId(peerId) << quint32(count); const auto writeCallback = [&]( const Data::DraftKey &key, - MsgId msgId, + const FullReplyTo &reply, const TextWithTags &text, Data::PreviewState previewState, auto&&) { // cursor @@ -1150,7 +1155,10 @@ void Account::writeDrafts(not_null history) { << key.serialize() << text.text << TextUtilities::SerializeTags(text.tags) - << qint64(msgId.bare) + << qint64(reply.messageId.peer.value) + << qint64(reply.messageId.msg.bare) + << reply.quote.text + << TextUtilities::SerializeTags(reply.quote.tags) << qint32(previewState); }; EnumerateDrafts( @@ -1201,7 +1209,7 @@ void Account::writeDraftCursors(not_null history) { const auto writeCallback = [&]( const Data::DraftKey &key, - MsgId, // msgId + auto&&, // reply auto&&, // text Data::PreviewState, const MessageCursor &cursor) { // cursor @@ -1343,7 +1351,9 @@ void Account::readDraftsWithCursors(not_null history) { quint64 tag = 0; draft.stream >> tag; - if (tag != kMultiDraftTag && tag != kMultiDraftTagOld) { + if (tag != kRichDraftsTag + && tag != kMultiDraftTag + && tag != kMultiDraftTagOld) { readDraftsWithCursorsLegacy(history, draft, tag); return; } @@ -1359,24 +1369,43 @@ void Account::readDraftsWithCursors(not_null history) { } auto map = Data::HistoryDrafts(); const auto keysOld = (tag == kMultiDraftTagOld); + const auto rich = (tag == kRichDraftsTag); for (auto i = 0; i != count; ++i) { - TextWithTags data; - QByteArray tagsSerialized; - qint64 keyValue = 0, messageId = 0; + TextWithTags quote; + TextWithTags text; + QByteArray textTagsSerialized; + QByteArray quoteTagsSerialized; + qint64 keyValue = 0; + qint64 messageIdPeer = 0, messageIdMsg = 0; qint32 keyValueOld = 0, uncheckedPreviewState = 0; if (keysOld) { draft.stream >> keyValueOld; } else { draft.stream >> keyValue; } - draft.stream - >> data.text - >> tagsSerialized - >> messageId - >> uncheckedPreviewState; - data.tags = TextUtilities::DeserializeTags( - tagsSerialized, - data.text.size()); + if (!rich) { + draft.stream + >> text.text + >> textTagsSerialized + >> messageIdMsg + >> uncheckedPreviewState; + messageIdPeer = peerId.value; + } else { + draft.stream + >> text.text + >> textTagsSerialized + >> messageIdPeer + >> messageIdMsg + >> quote.text + >> quoteTagsSerialized + >> uncheckedPreviewState; + quote.tags = TextUtilities::DeserializeTags( + quoteTagsSerialized, + quote.text.size()); + } + text.tags = TextUtilities::DeserializeTags( + textTagsSerialized, + text.text.size()); auto previewState = Data::PreviewState::Allowed; switch (static_cast(uncheckedPreviewState)) { case Data::PreviewState::Cancelled: @@ -1388,9 +1417,14 @@ void Account::readDraftsWithCursors(not_null history) { : Data::DraftKey::FromSerialized(keyValue); if (key && !key.isCloud()) { map.emplace(key, std::make_unique( - data, - messageId, - key.topicRootId(), + text, + FullReplyTo{ + .messageId = FullMsgId( + PeerId(messageIdPeer), + MsgId(messageIdMsg)), + .quote = quote, + .topicRootId = key.topicRootId(), + }, MessageCursor(), previewState)); } @@ -1455,8 +1489,7 @@ void Account::readDraftsWithCursorsLegacy( Data::DraftKey::Local(topicRootId), std::make_unique( msgData, - msgReplyTo, - topicRootId, + FullReplyTo{ FullMsgId(peerId, MsgId(msgReplyTo)) }, MessageCursor(), (msgPreviewCancelled ? Data::PreviewState::Cancelled @@ -1467,8 +1500,7 @@ void Account::readDraftsWithCursorsLegacy( Data::DraftKey::LocalEdit(topicRootId), std::make_unique( editData, - editMsgId, - topicRootId, + FullReplyTo{ FullMsgId(peerId, editMsgId) }, MessageCursor(), (editPreviewCancelled ? Data::PreviewState::Cancelled diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index 3c1525ed7..ebc5ace38 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -51,7 +51,7 @@ using FileKey = quint64; enum class StartResult : uchar; struct MessageDraft { - MsgId msgId = 0; + FullReplyTo reply; TextWithTags textWithTags; Data::PreviewState previewState = Data::PreviewState::Allowed; }; diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index fd273c09f..c7cbb8e25 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -159,8 +159,7 @@ Data::Draft OccupiedDraft(const QString &normalizedName) { + QString::number(OccupationTag()) + ";n:" + normalizedName }, - MsgId(0), // replyTo - kTopicRootId, + FullReplyTo(), MessageCursor(), Data::PreviewState::Allowed }; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index d00a49e71..b48e630e9 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -1060,12 +1060,14 @@ void Manager::notificationActivated( const auto replyToId = (id.msgId > 0 && !history->peer->isUser() && id.msgId != topicRootId) - ? id.msgId - : 0; + ? FullMsgId(history->peer->id, id.msgId) + : FullMsgId(); auto draft = std::make_unique( reply, - replyToId, - topicRootId, + FullReplyTo{ + .messageId = replyToId, + .topicRootId = topicRootId, + }, MessageCursor{ int(reply.text.size()), int(reply.text.size()), @@ -1150,7 +1152,7 @@ void Manager::notificationReplied( ? topicRootId : MsgId(0); message.action.replyTo = { - .msgId = replyToId, + .messageId = { replyToId ? history->peer->id : 0, replyToId }, .topicRootId = topic ? topic->rootId() : 0, }; message.action.clearDraft = false; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 13955e8db..e8ce10a78 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -112,7 +112,10 @@ void ShareBotGame( } histories.sendPreparedMessage( history, - FullReplyTo{ .msgId = replyTo, .topicRootId = topicRootId }, + FullReplyTo{ + .messageId = { replyTo ? history->peer->id : 0, replyTo }, + .topicRootId = topicRootId, + }, randomId, Data::Histories::PrepareMessage( MTP_flags(flags), @@ -1036,16 +1039,12 @@ 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 - : topicRootId; + const auto replyTo = _request.currentReplyTo; auto callback = [=] { PeerMenuCreatePoll( controller, peer, - replyToId, - topicRootId, + replyTo, flag, flag, source, @@ -1464,8 +1463,7 @@ void PeerMenuShareContactBox( void PeerMenuCreatePoll( not_null controller, not_null peer, - MsgId replyToId, - MsgId topicRootId, + FullReplyTo replyTo, PollData::Flags chosen, PollData::Flags disabled, Api::SendType sendType, @@ -1490,10 +1488,12 @@ void PeerMenuCreatePoll( auto action = Api::SendAction( peer->owner().history(peer), result.options); - action.clearDraft = false; - action.replyTo = { .msgId = replyToId, .topicRootId = topicRootId }; + action.replyTo = replyTo; + const auto topicRootId = replyTo.topicRootId; if (const auto local = action.history->localDraft(topicRootId)) { action.clearDraft = local->textWithTags.text.isEmpty(); + } else { + action.clearDraft = false; } const auto api = &peer->session().api(); api->polls().create(result.poll, action, crl::guard(weak, [=] { @@ -1637,7 +1637,7 @@ void BlockSenderFromRepliesBox( PeerMenuBlockUserBox( box, &controller->window(), - item->senderOriginal(), + item->originalSender(), true, Window::ClearReply{ id }); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index d5808fbf5..f5fff7f1f 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -86,8 +86,7 @@ void PeerMenuAddChannelMembers( void PeerMenuCreatePoll( not_null controller, not_null peer, - MsgId replyToId = 0, - MsgId topicRootId = 0, + FullReplyTo replyTo = FullReplyTo(), PollData::Flags chosen = PollData::Flags(), PollData::Flags disabled = PollData::Flags(), Api::SendType sendType = Api::SendType::Normal, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 7d223ee01..e17c9b28f 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1526,8 +1526,7 @@ bool SessionController::switchInlineQuery( }; auto draft = std::make_unique( textWithTags, - to.currentReplyToId, - to.rootId, + to.currentReplyTo, cursor, Data::PreviewState::Allowed); @@ -1539,11 +1538,12 @@ bool SessionController::switchInlineQuery( std::make_shared(history), params); } else { + const auto topicRootId = to.currentReplyTo.topicRootId; history->setLocalDraft(std::move(draft)); - history->clearLocalEditDraft(to.rootId); + history->clearLocalEditDraft(topicRootId); if (to.section == Section::Replies) { const auto commentId = MsgId(); - showRepliesForMessage(history, to.rootId, commentId, params); + showRepliesForMessage(history, topicRootId, commentId, params); } else { showPeerHistory(history->peer, params); } @@ -1560,7 +1560,7 @@ bool SessionController::switchInlineQuery( .section = (thread->asTopic() ? Dialogs::EntryState::Section::Replies : Dialogs::EntryState::Section::History), - .rootId = thread->topicRootId(), + .currentReplyTo = { .topicRootId = thread->topicRootId() }, }; return switchInlineQuery(entryState, bot, query); }