From 17549ad5eafe1aae33921507415d2fb53fb20a17 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Sep 2020 10:07:37 +0400 Subject: [PATCH] Allow replying inside replies section. --- .../view/history_view_compose_controls.cpp | 192 +++++++++++++----- .../view/history_view_compose_controls.h | 8 +- .../view/history_view_context_menu.cpp | 39 +++- .../history/view/history_view_list_widget.cpp | 8 + .../history/view/history_view_list_widget.h | 3 + .../view/history_view_replies_section.cpp | 34 +++- .../view/history_view_replies_section.h | 1 + 7 files changed, 224 insertions(+), 61 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp index ef09c0957f..bee9faf545 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp @@ -67,35 +67,37 @@ WebPageText ProcessWebPageData(WebPageData *page) { } // namespace -class FieldHeader : public Ui::RpWidget { +class FieldHeader final : public Ui::RpWidget { public: FieldHeader(QWidget *parent, not_null data); void init(); - void editMessage(FullMsgId edit); + void editMessage(FullMsgId id); + void replyToMessage(FullMsgId id); void previewRequested( rpl::producer title, rpl::producer description, rpl::producer page); - bool isDisplayed() const; - bool isEditingMessage() const; - rpl::producer editMsgId() const; - rpl::producer scrollToItemRequests() const; - MessageToEdit queryToEdit(); - WebPageId webPageId() const; + [[nodiscard]] bool isDisplayed() const; + [[nodiscard]] bool isEditingMessage() const; + [[nodiscard]] FullMsgId replyingToMessage() const; + [[nodiscard]] rpl::producer editMsgId() const; + [[nodiscard]] rpl::producer scrollToItemRequests() const; + [[nodiscard]] MessageToEdit queryToEdit(); + [[nodiscard]] WebPageId webPageId() const; - rpl::producer visibleChanged(); - -protected: + [[nodiscard]] rpl::producer visibleChanged(); private: void updateControlsGeometry(QSize size); void updateVisible(); + void setShownMessage(HistoryItem *message); + void updateShownMessageText(); void paintWebPage(Painter &p); - void paintEditMessage(Painter &p); + void paintEditOrReplyToMessage(Painter &p); struct Preview { WebPageData *data = nullptr; @@ -110,8 +112,13 @@ private: bool hasPreview() const; - Ui::Text::String _editMsgText; rpl::variable _editMsgId; + rpl::variable _replyToId; + + HistoryItem *_shownMessage = nullptr; + Ui::Text::String _shownMessageName; + Ui::Text::String _shownMessageText; + int _shownMessageNameVersion = -1; const not_null _data; const not_null _cancel; @@ -141,37 +148,46 @@ void FieldHeader::init() { Painter p(this); p.fillRect(rect(), st::historyComposeAreaBg); + const auto position = st::historyReplyIconPosition; if (isEditingMessage()) { - const auto position = st::historyReplyIconPosition; st::historyEditIcon.paint(p, position, width()); + } else if (replyingToMessage()) { + st::historyReplyIcon.paint(p, position, width()); } (!ShowWebPagePreview(_preview.data) || *leftIconPressed) - ? paintEditMessage(p) + ? paintEditOrReplyToMessage(p) : paintWebPage(p); }, lifetime()); - const auto checkPreview = [=](not_null item) { - _preview = {}; - if (const auto media = item->media()) { - if (const auto page = media->webpage()) { - const auto preview = ProcessWebPageData(page); - _title = preview.title; - _description = preview.description; - _preview.data = page; - } - } - }; - _editMsgId.value( - ) | rpl::start_with_next([=] { - updateVisible(); - if (const auto item = _data->message(_editMsgId.current())) { - _editMsgText.setText( - st::messageTextStyle, - item->inReplyText(), - Ui::DialogTextOptions()); - checkPreview(item); + ) | rpl::start_with_next([=](FullMsgId value) { + const auto shown = value ? value : _replyToId.current(); + setShownMessage(_data->message(shown)); + }, lifetime()); + + _replyToId.value( + ) | rpl::start_with_next([=](FullMsgId value) { + if (!_editMsgId.current()) { + setShownMessage(_data->message(value)); + } + }, lifetime()); + + _data->session().changes().messageUpdates( + Data::MessageUpdate::Flag::Edited + | Data::MessageUpdate::Flag::Destroyed + ) | rpl::filter([=](const Data::MessageUpdate &update) { + return (update.item == _shownMessage); + }) | rpl::start_with_next([=](const Data::MessageUpdate &update) { + if (update.flags & Data::MessageUpdate::Flag::Destroyed) { + if (_editMsgId.current() == update.item->fullId()) { + editMessage({}); + } + if (_replyToId.current() == update.item->fullId()) { + replyToMessage({}); + } + } else { + updateShownMessageText(); } }, lifetime()); @@ -179,8 +195,10 @@ void FieldHeader::init() { if (hasPreview()) { _preview = {}; update(); - } else { - _editMsgId = {}; + } else if (_editMsgId.current()) { + editMessage({}); + } else if (_replyToId.current()) { + replyToMessage({}); } updateVisible(); }); @@ -246,6 +264,46 @@ void FieldHeader::init() { }, lifetime()); } +void FieldHeader::updateShownMessageText() { + Expects(_shownMessage != nullptr); + + _shownMessageText.setText( + st::messageTextStyle, + _shownMessage->inReplyText(), + Ui::DialogTextOptions()); +} + +void FieldHeader::setShownMessage(HistoryItem *item) { + _shownMessage = item; + if (item) { + updateShownMessageText(); + if (item->fullId() == _editMsgId.current()) { + _preview = {}; + if (const auto media = item->media()) { + if (const auto page = media->webpage()) { + const auto preview = ProcessWebPageData(page); + _title = preview.title; + _description = preview.description; + _preview.data = page; + } + } + } + } else { + _shownMessageText.clear(); + } + if (isEditingMessage()) { + _shownMessageName.setText( + st::msgNameStyle, + tr::lng_edit_message(tr::now), + Ui::NameTextOptions()); + } else { + _shownMessageName.clear(); + _shownMessageNameVersion = -1; + } + updateVisible(); + update(); +} + void FieldHeader::previewRequested( rpl::producer title, rpl::producer description, @@ -315,23 +373,43 @@ void FieldHeader::paintWebPage(Painter &p) { elidedWidth); } -void FieldHeader::paintEditMessage(Painter &p) { +void FieldHeader::paintEditOrReplyToMessage(Painter &p) { + Expects(_shownMessage != nullptr); + const auto replySkip = st::historyReplySkip; + const auto availableWidth = width() + - replySkip + - _cancel->width() + - st::msgReplyPadding.right(); + + if (!isEditingMessage()) { + const auto user = _shownMessage->displayFrom() + ? _shownMessage->displayFrom() + : _shownMessage->author().get(); + if (user->nameVersion > _shownMessageNameVersion) { + _shownMessageName.setText( + st::msgNameStyle, + user->name, + Ui::NameTextOptions()); + _shownMessageNameVersion = user->nameVersion; + } + } + p.setPen(st::historyReplyNameFg); p.setFont(st::msgServiceNameFont); - p.drawTextLeft( + _shownMessageName.drawElided( + p, replySkip, st::msgReplyPadding.top(), - width(), - tr::lng_edit_message(tr::now)); + availableWidth); p.setPen(st::historyComposeAreaFg); p.setTextPalette(st::historyComposeAreaPalette); - _editMsgText.drawElided( + _shownMessageText.drawElided( p, replySkip, st::msgReplyPadding.top() + st::msgServiceNameFont->height, - width() - replySkip - _cancel->width() - st::msgReplyPadding.right()); + availableWidth); p.restoreTextPalette(); } @@ -345,13 +423,17 @@ rpl::producer FieldHeader::visibleChanged() { } bool FieldHeader::isDisplayed() const { - return isEditingMessage() || hasPreview(); + return isEditingMessage() || replyingToMessage() || hasPreview(); } bool FieldHeader::isEditingMessage() const { return !!_editMsgId.current(); } +FullMsgId FieldHeader::replyingToMessage() const { + return _replyToId.current(); +} + bool FieldHeader::hasPreview() const { return ShowWebPagePreview(_preview.data); } @@ -368,6 +450,10 @@ void FieldHeader::editMessage(FullMsgId id) { _editMsgId = id; } +void FieldHeader::replyToMessage(FullMsgId id) { + _replyToId = id; +} + rpl::producer FieldHeader::editMsgId() const { return _editMsgId.value(); } @@ -550,6 +636,7 @@ TextWithTags ComposeControls::getTextWithAppliedMarkdown() const { void ComposeControls::clear() { setText(TextWithTags()); + cancelReplyMessage(); } void ComposeControls::setText(const TextWithTags &textWithTags) { @@ -863,15 +950,24 @@ void ComposeControls::updateHeight() { } } -void ComposeControls::editMessage(FullMsgId edit) { +void ComposeControls::editMessage(FullMsgId id) { cancelEditMessage(); - _header->editMessage(std::move(edit)); + _header->editMessage(id); } void ComposeControls::cancelEditMessage() { _header->editMessage({}); } +void ComposeControls::replyToMessage(FullMsgId id) { + cancelReplyMessage(); + _header->replyToMessage(id); +} + +void ComposeControls::cancelReplyMessage() { + _header->replyToMessage({}); +} + void ComposeControls::initWebpageProcess() { Expects(_history); const auto peer = _history->peer; @@ -1060,4 +1156,8 @@ bool ComposeControls::isEditingMessage() const { return _header->isEditingMessage(); } +FullMsgId ComposeControls::replyingToMessage() const { + return _header->replyingToMessage(); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/history_view_compose_controls.h index 1d75e0cf2c..7744882c0e 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.h @@ -104,15 +104,19 @@ public: const Window::SectionShow ¶ms); bool returnTabbedSelector(); - bool isEditingMessage() const; + [[nodiscard]] bool isEditingMessage() const; + [[nodiscard]] FullMsgId replyingToMessage() const; void showForGrab(); void showStarted(); void showFinished(); - void editMessage(FullMsgId edit); + void editMessage(FullMsgId id); void cancelEditMessage(); + void replyToMessage(FullMsgId id); + void cancelReplyMessage(); + [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const; [[nodiscard]] WebPageId webPageId() const; void clear(); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index ecb0ee88db..097371b98d 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -75,7 +75,7 @@ MsgId ItemIdAcrossData(not_null item) { return session->data().scheduledMessages().lookupId(item); } -bool HasEditScheduledMessageAction(const ContextMenuRequest &request) { +bool HasEditMessageAction(const ContextMenuRequest &request) { const auto item = request.item; if (!item || item->isSending() @@ -415,7 +415,8 @@ bool AddSendNowMessageAction( bool AddRescheduleMessageAction( not_null menu, const ContextMenuRequest &request) { - if (!HasEditScheduledMessageAction(request)) { + if (!HasEditMessageAction(request) + || !request.item->isScheduled()) { return false; } const auto item = request.item; @@ -458,11 +459,33 @@ bool AddRescheduleMessageAction( return true; } +bool AddReplyToMessageAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + const auto item = request.item; + if (!item + || !IsServerMsgId(item->id) + || !item->history()->peer->canWrite()) { + return false; + } + const auto owner = &item->history()->owner(); + const auto itemId = item->fullId(); + menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { + const auto item = owner->message(itemId); + if (!item) { + return; + } + list->replyToMessageRequestNotify(item->fullId()); + }); + return true; +} + bool AddEditMessageAction( not_null menu, const ContextMenuRequest &request, not_null list) { - if (!HasEditScheduledMessageAction(request)) { + if (!HasEditMessageAction(request)) { return false; } const auto item = request.item; @@ -630,11 +653,18 @@ void AddSelectionAction( } } +void AddTopMessageActions( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + AddReplyToMessageAction(menu, request, list); + AddEditMessageAction(menu, request, list); +} + void AddMessageActions( not_null menu, const ContextMenuRequest &request, not_null list) { - AddEditMessageAction(menu, request, list); AddPostLinkAction(menu, request); AddForwardAction(menu, request, list); AddSendNowAction(menu, request, list); @@ -695,6 +725,7 @@ base::unique_qptr FillContextMenu( }); } + AddTopMessageActions(result, request, list); if (linkPhoto) { AddPhotoActions(result, photo); } else if (linkDocument) { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index e67dec98fc..d625ee3013 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2585,6 +2585,14 @@ void ListWidget::editMessageRequestNotify(FullMsgId item) { _requestedToEditMessage.fire(std::move(item)); } +rpl::producer ListWidget::replyToMessageRequested() const { + return _requestedToReplyToMessage.events(); +} + +void ListWidget::replyToMessageRequestNotify(FullMsgId item) { + _requestedToReplyToMessage.fire(std::move(item)); +} + ListWidget::~ListWidget() = default; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 073cc7ff11..0388e0eb87 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -188,6 +188,8 @@ public: rpl::producer editMessageRequested() const; void editMessageRequestNotify(FullMsgId item); + rpl::producer replyToMessageRequested() const; + void replyToMessageRequestNotify(FullMsgId item); // ElementDelegate interface. Context elementContext() override; @@ -523,6 +525,7 @@ private: base::Timer _highlightTimer; rpl::event_stream _requestedToEditMessage; + rpl::event_stream _requestedToReplyToMessage; rpl::lifetime _viewerLifetime; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index e9784ecd88..7d907cdf7b 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -158,6 +158,11 @@ RepliesWidget::RepliesWidget( } }, _inner->lifetime()); + _inner->replyToMessageRequested( + ) | rpl::start_with_next([=](auto fullId) { + _composeControls->replyToMessage(fullId); + }, _inner->lifetime()); + _history->session().changes().messageUpdates( Data::MessageUpdate::Flag::Destroyed ) | rpl::filter([=](const Data::MessageUpdate &update) { @@ -390,6 +395,7 @@ bool RepliesWidget::confirmSendingFiles( SendMenu::Type::Disabled); // #TODO replies schedule //_field->setTextWithTags({}); + const auto replyTo = replyToId(); box->setConfirmedCallback(crl::guard(this, [=]( Storage::PreparedList &&list, SendFilesWay way, @@ -409,7 +415,7 @@ bool RepliesWidget::confirmSendingFiles( std::move(list), type, std::move(caption), - MsgId(_rootId),//replyToId(), // #TODO replies reply + replyTo, options, album); })); @@ -470,7 +476,7 @@ void RepliesWidget::uploadFilesAfterConfirmation( return; } auto action = Api::SendAction(_history); - action.replyTo = _rootId;// replyTo;// #TODO replies reply + action.replyTo = replyTo ? replyTo : _rootId; action.options = options; session().api().sendFiles( std::move(list), @@ -536,7 +542,7 @@ void RepliesWidget::uploadFile( SendMediaType type) { // #TODO replies schedule auto action = Api::SendAction(_history); - action.replyTo = _rootId;// #TODO replies reply + action.replyTo = replyToId(); session().api().sendFile(fileContent, type, action); } @@ -595,7 +601,7 @@ void RepliesWidget::send(Api::SendOptions options) { auto message = ApiWrap::MessageToSend(_history); message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); message.action.options = options; - message.action.replyTo = _rootId;// replyToId();// #TODO replies reply + message.action.replyTo = replyToId(); message.webPageId = webPageId; //const auto error = GetErrorTextForSending( @@ -717,7 +723,7 @@ bool RepliesWidget::sendExistingDocument( } auto message = Api::MessageToSend(_history); - message.action.replyTo = _rootId;// replyToId();// #TODO replies reply + message.action.replyTo = replyToId(); message.action.options = options; Api::SendExistingDocument(std::move(message), document); @@ -729,6 +735,7 @@ bool RepliesWidget::sendExistingDocument( // onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft //} + _composeControls->cancelReplyMessage(); _composeControls->hidePanelsAnimated(); _composeControls->focus(); return true; @@ -757,10 +764,11 @@ bool RepliesWidget::sendExistingPhoto( } auto message = Api::MessageToSend(_history); - message.action.replyTo = _rootId;// replyToId();// #TODO replies reply + message.action.replyTo = replyToId(); message.action.options = options; Api::SendExistingPhoto(std::move(message), photo); + _composeControls->cancelReplyMessage(); _composeControls->hidePanelsAnimated(); _composeControls->focus(); return true; @@ -788,7 +796,7 @@ void RepliesWidget::sendInlineResult( not_null bot, Api::SendOptions options) { auto action = Api::SendAction(_history); - action.replyTo = _rootId;// replyToId();// #TODO replies reply + action.replyTo = replyToId(); action.options = options; action.generateLocal = true; session().api().sendInlineResult(bot, result, action); @@ -823,6 +831,11 @@ SendMenu::Type RepliesWidget::sendMenuType() const { : SendMenu::Type::Scheduled; } +MsgId RepliesWidget::replyToId() const { + const auto custom = _composeControls->replyingToMessage().msg; + return custom ? custom : _rootId; +} + void RepliesWidget::setupScrollDownButton() { _scrollDown->setClickedCallback([=] { scrollDownClicked(); @@ -1188,6 +1201,9 @@ void RepliesWidget::listCancelRequest() { if (_composeControls->isEditingMessage()) { _composeControls->cancelEditMessage(); return; + } else if (_composeControls->replyingToMessage()) { + _composeControls->cancelReplyMessage(); + return; } controller()->showBackFromStack(); } @@ -1225,8 +1241,8 @@ void RepliesWidget::listSelectionChanged(SelectedItems &&items) { if (item.canDelete) { ++state.canDeleteCount; } - if (item.canSendNow) { - ++state.canSendNowCount; + if (item.canForward) { + ++state.canForwardCount; } } _topBar->showSelected(state); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 9ee365d16e..76ba40c289 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -166,6 +166,7 @@ private: mtpRequestId *const saveEditMsgRequestId); void chooseAttach(); [[nodiscard]] SendMenu::Type sendMenuType() const; + [[nodiscard]] MsgId replyToId() const; void pushReplyReturn(not_null item); void computeCurrentReplyReturn();