From 467449ac1304217a1e009d8a7bd017be03761fad Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 15 Feb 2021 21:37:22 +0400 Subject: [PATCH] When reporting peer allow to select messages first. --- Telegram/CMakeLists.txt | 2 - Telegram/Resources/langs/lang.strings | 6 + Telegram/SourceFiles/boxes/report_box.cpp | 229 ------------------ Telegram/SourceFiles/boxes/report_box.h | 74 ------ Telegram/SourceFiles/data/data_types.h | 5 +- .../history/history_inner_widget.cpp | 53 ++-- .../history/history_inner_widget.h | 6 + .../SourceFiles/history/history_widget.cpp | 148 ++++++++++- Telegram/SourceFiles/history/history_widget.h | 14 ++ .../view/history_view_context_menu.cpp | 111 ++++++++- .../history/view/history_view_context_menu.h | 14 +- .../view/history_view_top_bar_widget.cpp | 123 ++++++++-- .../view/history_view_top_bar_widget.h | 13 + Telegram/SourceFiles/info/info.style | 7 + .../info/profile/info_profile_actions.cpp | 7 +- .../profile/info_profile_inner_widget.cpp | 1 - Telegram/SourceFiles/mainwidget.cpp | 12 + Telegram/SourceFiles/mainwidget.h | 7 + Telegram/SourceFiles/ui/boxes/report_box.cpp | 93 +++++++ Telegram/SourceFiles/ui/boxes/report_box.h | 39 +++ .../SourceFiles/window/window_peer_menu.cpp | 22 +- .../SourceFiles/window/window_peer_menu.h | 5 + .../window/window_session_controller.cpp | 11 + .../window/window_session_controller.h | 7 + Telegram/cmake/td_ui.cmake | 2 + 25 files changed, 639 insertions(+), 372 deletions(-) delete mode 100644 Telegram/SourceFiles/boxes/report_box.cpp delete mode 100644 Telegram/SourceFiles/boxes/report_box.h create mode 100644 Telegram/SourceFiles/ui/boxes/report_box.cpp create mode 100644 Telegram/SourceFiles/ui/boxes/report_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 235e68261..c5ba8fdfc 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -240,8 +240,6 @@ PRIVATE boxes/photo_crop_box.h boxes/rate_call_box.cpp boxes/rate_call_box.h - boxes/report_box.cpp - boxes/report_box.h boxes/self_destruction_box.cpp boxes/self_destruction_box.h boxes/send_files_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a2b3736dd..baf02adfc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -996,6 +996,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_report_group_title" = "Report group"; "lng_report_bot_title" = "Report bot"; "lng_report_message_title" = "Report message"; +"lng_report_select_messages" = "Select messages"; +"lng_report_messages_none" = "Select Messages"; +"lng_report_messages_count#one" = "Report {count} Message"; +"lng_report_messages_count#other" = "Report {count} Messages"; +"lng_report_details_about" = "Please enter any additional details relevant to your report."; +"lng_report_details" = "Additional Details"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_fake" = "Fake Account"; "lng_report_reason_violence" = "Violence"; diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp deleted file mode 100644 index 81487ac66..000000000 --- a/Telegram/SourceFiles/boxes/report_box.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "boxes/report_box.h" - -#include "lang/lang_keys.h" -#include "data/data_peer.h" -#include "data/data_session.h" -#include "main/main_session.h" -#include "boxes/confirm_box.h" -#include "history/history_item.h" -#include "ui/widgets/checkbox.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" -#include "ui/toast/toast.h" -#include "mainwindow.h" -#include "core/core_settings.h" -#include "core/application.h" -#include "window/window_session_controller.h" -#include "window/window_peer_menu.h" -#include "styles/style_layers.h" -#include "styles/style_boxes.h" -#include "styles/style_profile.h" - -namespace { - -constexpr auto kReportReasonLengthMax = 200; - -} // namespace - -ReportBox::ReportBox(QWidget*, not_null peer) -: _peer(peer) -, _api(&_peer->session().mtp()) { -} - -ReportBox::ReportBox(QWidget*, not_null peer, MessageIdsList ids) -: _peer(peer) -, _api(&_peer->session().mtp()) -, _ids(std::move(ids)) { -} - -void ReportBox::prepare() { - setTitle([&] { - if (_ids) { - return tr::lng_report_message_title(); - } else if (_peer->isUser()) { - return tr::lng_report_bot_title(); - } else if (_peer->isMegagroup()) { - return tr::lng_report_group_title(); - } else { - return tr::lng_report_title(); - } - }()); - - addButton(tr::lng_report_button(), [=] { report(); }); - addButton(tr::lng_cancel(), [=] { closeBox(); }); - - _reasonGroup = std::make_shared>( - Reason::Spam); - const auto createButton = [&]( - object_ptr> &button, - Reason reason, - const QString &text) { - button.create( - this, - _reasonGroup, - reason, - text, - st::defaultBoxCheckbox); - }; - createButton(_reasonSpam, Reason::Spam, tr::lng_report_reason_spam(tr::now)); - createButton(_reasonFake, Reason::Fake, tr::lng_report_reason_fake(tr::now)); - createButton(_reasonViolence, Reason::Violence, tr::lng_report_reason_violence(tr::now)); - createButton(_reasonChildAbuse, Reason::ChildAbuse, tr::lng_report_reason_child_abuse(tr::now)); - createButton(_reasonPornography, Reason::Pornography, tr::lng_report_reason_pornography(tr::now)); - createButton(_reasonOther, Reason::Other, tr::lng_report_reason_other(tr::now)); - _reasonGroup->setChangedCallback([=](Reason value) { - reasonChanged(value); - }); - - updateMaxHeight(); -} - -void ReportBox::resizeEvent(QResizeEvent *e) { - BoxContent::resizeEvent(e); - - _reasonSpam->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top() + _reasonSpam->getMargins().top()); - _reasonFake->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->bottomNoMargins() + st::boxOptionListSkip); - _reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonFake->bottomNoMargins() + st::boxOptionListSkip); - _reasonChildAbuse->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->bottomNoMargins() + st::boxOptionListSkip); - _reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonChildAbuse->bottomNoMargins() + st::boxOptionListSkip); - _reasonOther->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonPornography->bottomNoMargins() + st::boxOptionListSkip); - - if (_reasonOtherText) { - _reasonOtherText->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() - st::defaultInputField.textMargins.left(), _reasonOther->bottomNoMargins() + st::newGroupDescriptionPadding.top()); - } -} - -void ReportBox::reasonChanged(Reason reason) { - if (reason == Reason::Other) { - if (!_reasonOtherText) { - _reasonOtherText.create( - this, - st::profileReportReasonOther, - Ui::InputField::Mode::MultiLine, - tr::lng_report_reason_description()); - _reasonOtherText->show(); - _reasonOtherText->setSubmitSettings(Core::App().settings().sendSubmitWay()); - _reasonOtherText->setMaxLength(kReportReasonLengthMax); - _reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height()); - - updateMaxHeight(); - connect(_reasonOtherText, &Ui::InputField::resized, [=] { reasonResized(); }); - connect(_reasonOtherText, &Ui::InputField::submitted, [=] { report(); }); - connect(_reasonOtherText, &Ui::InputField::cancelled, [=] { closeBox(); }); - } - _reasonOtherText->setFocusFast(); - } else if (_reasonOtherText) { - _reasonOtherText.destroy(); - updateMaxHeight(); - } -} - -void ReportBox::setInnerFocus() { - if (_reasonOtherText) { - _reasonOtherText->setFocusFast(); - } else { - setFocus(); - } -} - -void ReportBox::reasonResized() { - updateMaxHeight(); - update(); -} - -void ReportBox::report() { - if (_requestId) { - return; - } - - const auto text = _reasonOtherText - ? _reasonOtherText->getLastText().trimmed() - : QString(); - if (_reasonOtherText && text.isEmpty()) { - _reasonOtherText->showError(); - return; - } - - const auto reason = [&] { - switch (_reasonGroup->value()) { - case Reason::Spam: return MTP_inputReportReasonSpam(); - case Reason::Fake: return MTP_inputReportReasonFake(); - case Reason::Violence: return MTP_inputReportReasonViolence(); - case Reason::ChildAbuse: return MTP_inputReportReasonChildAbuse(); - case Reason::Pornography: return MTP_inputReportReasonPornography(); - case Reason::Other: return MTP_inputReportReasonOther(); - } - Unexpected("Bad reason group value."); - }(); - if (_ids) { - auto ids = QVector(); - for (const auto &fullId : *_ids) { - ids.push_back(MTP_int(fullId.msg)); - } - _requestId = _api.request(MTPmessages_Report( - _peer->input, - MTP_vector(ids), - reason, - MTP_string(text) - )).done([=](const MTPBool &result) { - reportDone(result); - }).fail([=](const RPCError &error) { - reportFail(error); - }).send(); - } else { - _requestId = _api.request(MTPaccount_ReportPeer( - _peer->input, - reason, - MTP_string(text) - )).done([=](const MTPBool &result) { - reportDone(result); - }).fail([=](const RPCError &error) { - reportFail(error); - }).send(); - } -} - -void ReportBox::reportDone(const MTPBool &result) { - _requestId = 0; - Ui::Toast::Show(tr::lng_report_thanks(tr::now)); - closeBox(); -} - -void ReportBox::reportFail(const RPCError &error) { - _requestId = 0; - if (_reasonOtherText) { - _reasonOtherText->showError(); - } -} - -void ReportBox::updateMaxHeight() { - const auto buttonsCount = 6; - auto newHeight = st::boxOptionListPadding.top() + _reasonSpam->getMargins().top() + buttonsCount * _reasonSpam->heightNoMargins() + (buttonsCount - 1) * st::boxOptionListSkip + _reasonSpam->getMargins().bottom() + st::boxOptionListPadding.bottom(); - - if (_reasonOtherText) { - newHeight += st::newGroupDescriptionPadding.top() + _reasonOtherText->height() + st::newGroupDescriptionPadding.bottom(); - } - setDimensions(st::boxWidth, newHeight); -} - -void BlockSenderFromRepliesBox( - not_null box, - not_null controller, - FullMsgId id) { - const auto item = controller->session().data().message(id); - Assert(item != nullptr); - - PeerMenuBlockUserBox( - box, - &controller->window(), - item->senderOriginal(), - true, - Window::ClearReply{ id }); -} diff --git a/Telegram/SourceFiles/boxes/report_box.h b/Telegram/SourceFiles/boxes/report_box.h deleted file mode 100644 index ad637aae3..000000000 --- a/Telegram/SourceFiles/boxes/report_box.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -#include "boxes/abstract_box.h" -#include "ui/layers/generic_box.h" -#include "mtproto/sender.h" - -namespace Window { -class SessionController; -} // namespace Window - -namespace Ui { -template -class RadioenumGroup; -template -class Radioenum; -class InputField; -} // namespace Ui - -class ReportBox final : public Ui::BoxContent { -public: - ReportBox(QWidget*, not_null peer); - ReportBox(QWidget*, not_null peer, MessageIdsList ids); - -protected: - void prepare() override; - void setInnerFocus() override; - - void resizeEvent(QResizeEvent *e) override; - -private: - enum class Reason { - Spam, - Fake, - Violence, - ChildAbuse, - Pornography, - Other, - }; - void reasonChanged(Reason reason); - void reasonResized(); - void updateMaxHeight(); - void report(); - - void reportDone(const MTPBool &result); - void reportFail(const RPCError &error); - - const not_null _peer; - MTP::Sender _api; - std::optional _ids; - - std::shared_ptr> _reasonGroup; - object_ptr> _reasonSpam = { nullptr }; - object_ptr> _reasonFake = { nullptr }; - object_ptr> _reasonViolence = { nullptr }; - object_ptr> _reasonChildAbuse = { nullptr }; - object_ptr> _reasonPornography = { nullptr }; - object_ptr> _reasonOther = { nullptr }; - object_ptr _reasonOtherText = { nullptr }; - - mtpRequestId _requestId = 0; - -}; - -void BlockSenderFromRepliesBox( - not_null box, - not_null controller, - FullMsgId id); diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 8089daad4..03bca3ea8 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -185,8 +185,9 @@ constexpr auto EndClientMsgId = MsgId(-0x40000000); constexpr auto ShowAtTheEndMsgId = MsgId(-0x40000000); constexpr auto SwitchAtTopMsgId = MsgId(-0x3FFFFFFF); constexpr auto ShowAtProfileMsgId = MsgId(-0x3FFFFFFE); -constexpr auto ShowAndStartBotMsgId = MsgId(-0x3FFFFFD); -constexpr auto ShowAtGameShareMsgId = MsgId(-0x3FFFFFC); +constexpr auto ShowAndStartBotMsgId = MsgId(-0x3FFFFFFD); +constexpr auto ShowAtGameShareMsgId = MsgId(-0x3FFFFFFC); +constexpr auto ShowForChooseMessagesMsgId = MsgId(-0x3FFFFFFB); constexpr auto ServerMaxMsgId = MsgId(0x3FFFFFFF); constexpr auto ShowAtUnreadMsgId = MsgId(0); constexpr inline bool IsClientMsgId(MsgId id) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index fb88bd32e..fd3661625 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -26,6 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/toast/toast.h" #include "ui/text/text_options.h" +#include "ui/boxes/report_box.h" +#include "ui/layers/generic_box.h" #include "ui/controls/delete_message_context_action.h" #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" @@ -35,7 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/notifications_manager.h" #include "boxes/confirm_box.h" -#include "boxes/report_box.h" #include "boxes/sticker_set_box.h" #include "chat_helpers/message_field.h" #include "history/history_widget.h" @@ -1058,15 +1059,13 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but if (ClickHandler::getPressed()) { _mouseAction = MouseAction::PrepareDrag; - } else if (!_selected.empty()) { - if (_selected.cbegin()->second == FullSelection) { - if (_dragStateItem - && _selected.find(_dragStateItem) != _selected.cend() - && App::hoveredItem()) { - _mouseAction = MouseAction::PrepareDrag; // start items drag - } else if (!_pressWasInactive) { - _mouseAction = MouseAction::PrepareSelect; // start items select - } + } else if (inSelectionMode()) { + if (_dragStateItem + && _selected.find(_dragStateItem) != _selected.cend() + && App::hoveredItem()) { + _mouseAction = MouseAction::PrepareDrag; // start items drag + } else if (!_pressWasInactive) { + _mouseAction = MouseAction::PrepareSelect; // start items select } } if (_mouseAction == MouseAction::None && mouseActionView) { @@ -1319,7 +1318,10 @@ void HistoryInner::mouseActionFinish( } else if (_mouseActionItem) { // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link - if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection && button != Qt::RightButton) { + if (_mouseAction == MouseAction::PrepareDrag + && !_pressWasInactive + && inSelectionMode() + && button != Qt::RightButton) { if (const auto view = _mouseActionItem->mainView()) { if (const auto media = view->media()) { if (media->toggleSelectionByHandlerClick(activated)) { @@ -1357,8 +1359,7 @@ void HistoryInner::mouseActionFinish( } if ((_mouseAction == MouseAction::PrepareSelect) && !_pressWasInactive - && !_selected.empty() - && (_selected.cbegin()->second == FullSelection)) { + && inSelectionMode()) { changeSelectionAsGroup( &_selected, _mouseActionItem, @@ -1375,8 +1376,7 @@ void HistoryInner::mouseActionFinish( } else if ((i == _selected.cend()) && !_dragStateItem->serviceMsg() && (_dragStateItem->id > 0) - && !_selected.empty() - && _selected.cbegin()->second == FullSelection) { + && inSelectionMode()) { if (_selected.size() < MaxSelectedItems) { _selected.emplace(_dragStateItem, FullSelection); repaintItem(_mouseActionItem); @@ -2519,6 +2519,8 @@ bool HistoryInner::inSelectionMode() const { && _dragSelFrom && _dragSelTo) { return true; + } else if (_chooseForReportReason.has_value()) { + return true; } return false; } @@ -2993,6 +2995,14 @@ int HistoryInner::historyDrawTop() const { return (top >= 0) ? (top + _historySkipHeight) : -1; } +void HistoryInner::setChooseReportReason(Ui::ReportReason reason) { + _chooseForReportReason = reason; +} + +void HistoryInner::clearChooseReportReason() { + _chooseForReportReason = std::nullopt; +} + // -1 if should not be visible, -2 if bad history() int HistoryInner::itemTop(const HistoryItem *item) const { if (!item) { @@ -3226,25 +3236,24 @@ void HistoryInner::deleteAsGroup(FullMsgId itemId) { } void HistoryInner::reportItem(FullMsgId itemId) { - Ui::show(Box(_peer, MessageIdsList(1, itemId))); + HistoryView::ShowReportItemsBox(_peer, { 1, itemId }); } void HistoryInner::reportAsGroup(FullMsgId itemId) { if (const auto item = session().data().message(itemId)) { const auto group = session().data().groups().find(item); - if (!group) { - return reportItem(itemId); - } - Ui::show(Box( + HistoryView::ShowReportItemsBox( _peer, - session().data().itemsToIds(group->items))); + (group + ? session().data().itemsToIds(group->items) + : MessageIdsList{ 1, itemId })); } } void HistoryInner::blockSenderItem(FullMsgId itemId) { if (const auto item = session().data().message(itemId)) { Ui::show(Box( - BlockSenderFromRepliesBox, + Window::BlockSenderFromRepliesBox, _controller, itemId)); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index ab9ba5034..82a898642 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -35,6 +35,7 @@ class SessionController; namespace Ui { class PopupMenu; +enum class ReportReason; } // namespace Ui class HistoryWidget; @@ -111,6 +112,9 @@ public: int historyTop() const; int historyDrawTop() const; + void setChooseReportReason(Ui::ReportReason reason); + void clearChooseReportReason(); + // -1 if should not be visible, -2 if bad history() int itemTop(const HistoryItem *item) const; int itemTop(const Element *view) const; @@ -312,6 +316,7 @@ private: void deleteAsGroup(FullMsgId itemId); void reportItem(FullMsgId itemId); void reportAsGroup(FullMsgId itemId); + void reportItems(MessageIdsList ids); void blockSenderItem(FullMsgId itemId); void blockSenderAsGroup(FullMsgId itemId); void copySelectedText(); @@ -350,6 +355,7 @@ private: style::cursor _cursor = style::cur_default; SelectedItems _selected; + std::optional _chooseForReportReason; base::flat_set> _animatedStickersPlayed; base::flat_map< diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 98f4a50ed..9574b6696 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -73,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_webpage_preview.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_contact_status.h" +#include "history/view/history_view_context_menu.h" #include "history/view/history_view_pinned_tracker.h" #include "history/view/history_view_pinned_section.h" #include "history/view/history_view_pinned_bar.h" @@ -102,6 +103,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "apiwrap.h" #include "base/qthelp_regex.h" +#include "ui/boxes/report_box.h" #include "ui/chat/pinned_bar.h" #include "ui/chat/group_call_bar.h" #include "ui/widgets/popup_menu.h" @@ -111,6 +113,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "window/themes/window_theme.h" #include "window/notifications_manager.h" +#include "window/window_controller.h" #include "window/window_session_controller.h" #include "window/window_slide_animation.h" #include "window/window_peer_menu.h" @@ -196,6 +199,7 @@ HistoryWidget::HistoryWidget( this, tr::lng_channel_mute(tr::now).toUpper(), st::historyComposeButton) +, _reportMessages(this, QString(), st::historyComposeButton) , _attachToggle(this, st::historyAttach) , _tabbedSelectorToggle(this, st::historyAttachEmoji) , _botKeyboardShow(this, st::historyBotKeyboardShow) @@ -242,6 +246,7 @@ HistoryWidget::HistoryWidget( _botStart->addClickHandler([=] { sendBotStartCommand(); }); _joinChannel->addClickHandler([=] { joinChannel(); }); _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); }); + _reportMessages->addClickHandler([=] { reportSelectedMessages(); }); connect( _field, &Ui::InputField::submitted, @@ -373,6 +378,7 @@ HistoryWidget::HistoryWidget( _botStart->hide(); _joinChannel->hide(); _muteUnmute->hide(); + _reportMessages->hide(); initVoiceRecordBar(); @@ -687,6 +693,10 @@ HistoryWidget::HistoryWidget( ) | rpl::start_with_next([=] { clearSelected(); }, _topBar->lifetime()); + _topBar->cancelChooseForReportRequest( + ) | rpl::start_with_next([=] { + setChooseReportMessagesDetails({}, nullptr); + }, _topBar->lifetime()); session().api().sendActions( ) | rpl::filter([=](const Api::SendAction &action) { @@ -1743,6 +1753,9 @@ void HistoryWidget::showHistory( if (startBot) { showAtMsgId = ShowAtTheEndMsgId; } + if (showAtMsgId != ShowForChooseMessagesMsgId) { + _chooseForReport = nullptr; + } clearHighlightMessages(); hideInfoTooltip(anim::type::instant); @@ -1753,7 +1766,17 @@ void HistoryWidget::showHistory( if (showAtMsgId == ShowAtUnreadMsgId && insideJumpToEndInsteadOfToUnread()) { showAtMsgId = ShowAtTheEndMsgId; + } else if (showAtMsgId == ShowForChooseMessagesMsgId) { + if (_chooseForReport) { + _chooseForReport->active = true; + _list->setChooseReportReason(_chooseForReport->reason); + updateControlsVisibility(); + updateControlsGeometry(); + updateTopBarChooseForReport(); + } + return; } + _list->clearChooseReportReason(); if (!IsServerMsgId(showAtMsgId) && !IsServerMsgId(-showAtMsgId)) { // To end or to unread. @@ -1918,6 +1941,12 @@ void HistoryWidget::showHistory( refreshTopBarActiveChat(); updateTopBarSelection(); + if (showAtMsgId == ShowForChooseMessagesMsgId) { + showAtMsgId = ShowAtUnreadMsgId; + if (_chooseForReport) { + _chooseForReport->active = true; + } + } if (_channel) { updateNotifyControls(); @@ -1944,6 +1973,11 @@ void HistoryWidget::showHistory( object_ptr(this, _scroll, controller(), _history)); _list->show(); + if (_chooseForReport && _chooseForReport->active) { + _list->setChooseReportReason(_chooseForReport->reason); + } + updateTopBarChooseForReport(); + _updateHistoryItems.cancel(); setupPinnedTracker(); @@ -2168,8 +2202,18 @@ void HistoryWidget::updateControlsVisibility() { if (_contactStatus) { _contactStatus->show(); } - if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart())) { - if (isBlocked()) { + if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart() || isReportMessages())) { + if (isReportMessages()) { + _unblock->hide(); + _joinChannel->hide(); + _muteUnmute->hide(); + _botStart->hide(); + if (_reportMessages->isHidden()) { + _reportMessages->clearState(); + _reportMessages->show(); + } + } else if (isBlocked()) { + _reportMessages->hide(); _joinChannel->hide(); _muteUnmute->hide(); _botStart->hide(); @@ -2178,6 +2222,7 @@ void HistoryWidget::updateControlsVisibility() { _unblock->show(); } } else if (isJoinChannel()) { + _reportMessages->hide(); _unblock->hide(); _muteUnmute->hide(); _botStart->hide(); @@ -2186,6 +2231,7 @@ void HistoryWidget::updateControlsVisibility() { _joinChannel->show(); } } else if (isMuteUnmute()) { + _reportMessages->hide(); _unblock->hide(); _joinChannel->hide(); _botStart->hide(); @@ -2194,6 +2240,7 @@ void HistoryWidget::updateControlsVisibility() { _muteUnmute->show(); } } else if (isBotStart()) { + _reportMessages->hide(); _unblock->hide(); _joinChannel->hide(); _muteUnmute->hide(); @@ -2244,6 +2291,7 @@ void HistoryWidget::updateControlsVisibility() { _botStart->hide(); _joinChannel->hide(); _muteUnmute->hide(); + _reportMessages->hide(); _send->show(); updateSendButtonType(); @@ -2303,6 +2351,7 @@ void HistoryWidget::updateControlsVisibility() { _botStart->hide(); _joinChannel->hide(); _muteUnmute->hide(); + _reportMessages->hide(); _attachToggle->hide(); if (_silent) { _silent->hide(); @@ -3268,6 +3317,24 @@ void HistoryWidget::toggleMuteUnmute() { session().data().updateNotifySettings(_peer, muteForSeconds); } +void HistoryWidget::reportSelectedMessages() { + if (!_list || !_chooseForReport || !_list->getSelectionState().count) { + return; + } + const auto ids = _list->getSelectedItems(); + const auto peer = _peer; + const auto reason = _chooseForReport->reason; + const auto box = std::make_shared>(); + const auto send = [=](const QString &text) { + HistoryView::SendReport(peer, reason, text, ids); + controller()->clearChooseReportMessages(); + if (*box) { + (*box)->closeBox(); + } + }; + *box = controller()->window().show(Box(Ui::ReportDetailsBox, send)); +} + History *HistoryWidget::history() const { return _history; } @@ -3706,6 +3773,10 @@ bool HistoryWidget::isBotStart() const { return false; } +bool HistoryWidget::isReportMessages() const { + return _peer && _chooseForReport && _chooseForReport->active; +} + bool HistoryWidget::isBlocked() const { return _peer && _peer->isUser() && _peer->asUser()->isBlocked(); } @@ -3993,7 +4064,7 @@ void HistoryWidget::moveFieldControls() { // _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send -// (_botStart|_unblock|_joinChannel|_muteUnmute) +// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages) auto buttonsBottom = bottom - _attachToggle->height(); auto left = st::historySendRight; @@ -4037,8 +4108,8 @@ void HistoryWidget::moveFieldControls() { _botStart->setGeometry(fullWidthButtonRect); _unblock->setGeometry(fullWidthButtonRect); _joinChannel->setGeometry(fullWidthButtonRect); - _muteUnmute->setGeometry(fullWidthButtonRect); + _reportMessages->setGeometry(fullWidthButtonRect); } void HistoryWidget::updateFieldSize() { @@ -4406,13 +4477,19 @@ void HistoryWidget::handleHistoryChange(not_null history) { const auto botStart = isBotStart(); const auto joinChannel = isJoinChannel(); const auto muteUnmute = isMuteUnmute(); + const auto reportMessages = isReportMessages(); const auto update = false - || (_unblock->isHidden() == unblock) - || (!unblock && _botStart->isHidden() == botStart) - || (!unblock + || (_reportMessages->isHidden() == reportMessages) + || (!reportMessages && _unblock->isHidden() == unblock) + || (!reportMessages + && !unblock + && _botStart->isHidden() == botStart) + || (!reportMessages + && !unblock && !botStart && _joinChannel->isHidden() == joinChannel) - || (!unblock + || (!reportMessages + && !unblock && !botStart && !joinChannel && _muteUnmute->isHidden() == muteUnmute); @@ -4667,7 +4744,7 @@ void HistoryWidget::updateHistoryGeometry( if (_contactStatus) { newScrollHeight -= _contactStatus->height(); } - if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute())) { + if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute() || isReportMessages())) { newScrollHeight -= _unblock->height(); } else { if (editingMessage() || _canSendMessages) { @@ -5427,6 +5504,28 @@ void HistoryWidget::checkMessagesTTL() { } } +void HistoryWidget::setChooseReportMessagesDetails( + Ui::ReportReason reason, + Fn callback) { + if (!callback) { + const auto refresh = _chooseForReport && _chooseForReport->active; + _chooseForReport = nullptr; + if (_list) { + _list->clearChooseReportReason(); + } + if (refresh) { + updateControlsVisibility(); + updateControlsGeometry(); + updateTopBarChooseForReport(); + } + } else { + _chooseForReport = std::make_unique( + ChooseMessagesForReport{ + .reason = reason, + .callback = std::move(callback) }); + } +} + void HistoryWidget::refreshPinnedBarButton(bool many) { const auto close = !many; auto button = object_ptr( @@ -6209,6 +6308,18 @@ MessageIdsList HistoryWidget::getSelectedItems() const { return _list ? _list->getSelectedItems() : MessageIdsList(); } +void HistoryWidget::updateTopBarChooseForReport() { + if (_chooseForReport && _chooseForReport->active) { + _topBar->showChooseMessagesForReport( + _chooseForReport->reason); + } else { + _topBar->clearChooseMessagesForReport(); + } + updateTopBarSelection(); + updateControlsVisibility(); + updateControlsGeometry(); +} + void HistoryWidget::updateTopBarSelection() { if (!_list) { _topBar->showSelected(HistoryView::TopBarWidget::SelectedState {}); @@ -6216,8 +6327,25 @@ void HistoryWidget::updateTopBarSelection() { } auto selectedState = _list->getSelectionState(); - _nonEmptySelection = (selectedState.count > 0) || selectedState.textSelected; + _nonEmptySelection = (selectedState.count > 0) + || selectedState.textSelected; _topBar->showSelected(selectedState); + + const auto transparent = Qt::WA_TransparentForMouseEvents; + if (selectedState.count == 0) { + _reportMessages->clearState(); + _reportMessages->setAttribute(transparent); + _reportMessages->setColorOverride(st::windowSubTextFg->c); + } else if (_reportMessages->testAttribute(transparent)) { + _reportMessages->setAttribute(transparent, false); + _reportMessages->setColorOverride(std::nullopt); + } + _reportMessages->setText(Ui::Text::Upper(selectedState.count + ? tr::lng_report_messages_count( + tr::now, + lt_count, + selectedState.count) + : tr::lng_report_messages_none(tr::now))); updateControlsVisibility(); updateHistoryGeometry(); if (!Ui::isLayerShown() && !Core::App().passcodeLocked()) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 13ccd8f69..49c093912 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -68,6 +68,7 @@ class PinnedBar; class GroupCallBar; struct PreparedList; class SendFilesWay; +enum class ReportReason; namespace Toast { class Instance; } // namespace Toast @@ -141,6 +142,7 @@ public: bool isItemCompletelyHidden(HistoryItem *item) const; void updateTopBarSelection(); + void updateTopBarChooseForReport(); void loadMessages(); void loadMessagesDown(); @@ -230,6 +232,9 @@ public: void applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); + void setChooseReportMessagesDetails( + Ui::ReportReason reason, + Fn callback); void clearAllLoadRequests(); void clearDelayedShowAtRequest(); void clearDelayedShowAt(); @@ -313,6 +318,11 @@ private: ScrollChangeType type; int value; }; + struct ChooseMessagesForReport { + Ui::ReportReason reason = {}; + Fn callback; + bool active = false; + }; enum class TextUpdateEvent { SaveDraft = (1 << 0), SendTyping = (1 << 1), @@ -377,6 +387,7 @@ private: [[nodiscard]] int computeMaxFieldHeight() const; void toggleMuteUnmute(); + void reportSelectedMessages(); void toggleKeyboard(bool manual = true); void startBotCommand(); void hidePinnedMessage(); @@ -572,6 +583,7 @@ private: bool isBlocked() const; bool isJoinChannel() const; bool isMuteUnmute() const; + bool isReportMessages() const; bool updateCmdStartShown(); void updateSendButtonType(); bool showRecordButton() const; @@ -687,6 +699,7 @@ private: object_ptr _botStart; object_ptr _joinChannel; object_ptr _muteUnmute; + object_ptr _reportMessages; object_ptr _attachToggle; object_ptr _tabbedSelectorToggle; object_ptr _botKeyboardShow; @@ -740,6 +753,7 @@ private: base::Timer _saveCloudDraftTimer; base::weak_ptr _topToast; + std::unique_ptr _chooseForReport; object_ptr _topShadow; bool _inGrab = false; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index bf2c7743a..433d803ef 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -24,11 +24,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/toast/toast.h" #include "ui/controls/delete_message_context_action.h" +#include "ui/boxes/report_box.h" #include "ui/ui_utility.h" #include "chat_helpers/send_context_menu.h" #include "boxes/confirm_box.h" #include "boxes/sticker_set_box.h" -#include "boxes/report_box.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_document.h" @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "base/platform/base_platform_info.h" #include "window/window_peer_menu.h" +#include "window/window_controller.h" #include "window/window_session_controller.h" #include "lang/lang_keys.h" #include "core/application.h" @@ -782,13 +783,12 @@ void AddReportAction( const auto itemId = item->fullId(); const auto callback = crl::guard(controller, [=] { if (const auto item = owner->message(itemId)) { - const auto peer = item->history()->peer; const auto group = owner->groups().find(item); - Ui::show(Box( - peer, + ShowReportItemsBox( + item->history()->peer, (group ? owner->itemsToIds(group->items) - : MessageIdsList(1, itemId)))); + : MessageIdsList{ 1, itemId })); } }); menu->addAction(tr::lng_context_report_msg(tr::now), callback); @@ -1051,4 +1051,105 @@ void AddPollActions( } } +void ShowReportItemsBox(not_null peer, MessageIdsList ids) { + const auto chosen = [=](Ui::ReportReason reason) { + Ui::show(Box(Ui::ReportDetailsBox, [=](const QString &text) { + SendReport(peer, reason, text, ids); + Ui::hideLayer(); + })); + }; + Ui::show(Box( + Ui::ReportReasonBox, + Ui::ReportSource::Message, + chosen)); +} + +void ShowReportPeerBox( + not_null window, + not_null peer) { + struct State { + QPointer reasonBox; + QPointer detailsBox; + MessageIdsList ids; + }; + const auto state = std::make_shared(); + const auto chosen = [=](Ui::ReportReason reason) { + const auto send = [=](const QString &text) { + window->clearChooseReportMessages(); + SendReport(peer, reason, text, std::move(state->ids)); + if (const auto strong = state->reasonBox.data()) { + strong->closeBox(); + } + if (const auto strong = state->detailsBox.data()) { + strong->closeBox(); + } + }; + if (reason == Ui::ReportReason::Fake + || reason == Ui::ReportReason::Other) { + state->ids = {}; + state->detailsBox = window->window().show( + Box(Ui::ReportDetailsBox, send)); + return; + } + window->showChooseReportMessages(peer, reason, [=]( + MessageIdsList ids) { + state->ids = std::move(ids); + state->detailsBox = window->window().show( + Box(Ui::ReportDetailsBox, send)); + }); + }; + state->reasonBox = window->window().show(Box( + Ui::ReportReasonBox, + (peer->isBroadcast() + ? Ui::ReportSource::Channel + : peer->isUser() + ? Ui::ReportSource::Bot + : Ui::ReportSource::Group), + chosen)); +} + +void SendReport( + not_null peer, + Ui::ReportReason reason, + const QString &comment, + MessageIdsList ids) { + const auto apiReason = [&] { + using Reason = Ui::ReportReason; + switch (reason) { + case Reason::Spam: return MTP_inputReportReasonSpam(); + case Reason::Fake: return MTP_inputReportReasonFake(); + case Reason::Violence: return MTP_inputReportReasonViolence(); + case Reason::ChildAbuse: return MTP_inputReportReasonChildAbuse(); + case Reason::Pornography: return MTP_inputReportReasonPornography(); + case Reason::Other: return MTP_inputReportReasonOther(); + } + Unexpected("Bad reason group value."); + }(); + if (ids.empty()) { + peer->session().api().request(MTPaccount_ReportPeer( + peer->input, + apiReason, + MTP_string(comment) + )).done([=](const MTPBool &result) { + Ui::Toast::Show(tr::lng_report_thanks(tr::now)); + }).fail([=](const RPCError &error) { + }).send(); + } else { + auto apiIds = QVector(); + apiIds.reserve(ids.size()); + for (const auto &fullId : ids) { + apiIds.push_back(MTP_int(fullId.msg)); + } + peer->session().api().request(MTPmessages_Report( + peer->input, + MTP_vector(apiIds), + apiReason, + MTP_string(comment) + )).done([=](const MTPBool &result) { + Ui::Toast::Show(tr::lng_report_thanks(tr::now)); + }).fail([=](const RPCError &error) { + }).send(); + } +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index be6f160dc..e9d7f2418 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -11,10 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { class PopupMenu; +enum class ReportReason; } // namespace Ui namespace Window { class SessionNavigation; +class SessionController; } // namespace Main namespace HistoryView { @@ -55,4 +57,14 @@ void AddPollActions( not_null item, Context context); -} // namespace +void ShowReportItemsBox(not_null peer, MessageIdsList ids); +void ShowReportPeerBox( + not_null window, + not_null peer); +void SendReport( + not_null peer, + Ui::ReportReason reason, + const QString &comment, + MessageIdsList ids = {}); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 1a654ee20..25f5b2c53 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/dropdown_menu.h" #include "ui/effects/radial_animation.h" #include "ui/toasts/common_toasts.h" +#include "ui/boxes/report_box.h" // Ui::ReportReason #include "ui/special_buttons.h" #include "ui/unread_badge.h" #include "ui/ui_utility.h" @@ -63,6 +64,7 @@ TopBarWidget::TopBarWidget( , _sendNow(this, tr::lng_selected_send_now(), st::defaultActiveButton) , _delete(this, tr::lng_selected_delete(), st::defaultActiveButton) , _back(this, st::historyTopBarBack) +, _cancelChoose(this, st::topBarCloseChoose) , _call(this, st::topBarCall) , _groupCall(this, st::topBarGroupCall) , _search(this, st::topBarSearch) @@ -90,6 +92,8 @@ TopBarWidget::TopBarWidget( _menuToggle->setClickedCallback([=] { showMenu(); }); _infoToggle->setClickedCallback([=] { toggleInfoSection(); }); _back->addClickHandler([=] { backClicked(); }); + _cancelChoose->setClickedCallback( + [=] { _cancelChooseForReport.fire({}); }); rpl::combine( _controller->activeChatValue(), @@ -224,6 +228,34 @@ void TopBarWidget::groupCall() { } } +void TopBarWidget::showChooseMessagesForReport(Ui::ReportReason reason) { + setChooseForReportReason(reason); +} + +void TopBarWidget::clearChooseMessagesForReport() { + setChooseForReportReason(std::nullopt); +} + +void TopBarWidget::setChooseForReportReason( + std::optional reason) { + if (_chooseForReportReason == reason) { + return; + } + const auto wasNoReason = !_chooseForReportReason; + _chooseForReportReason = reason; + const auto nowNoReason = !_chooseForReportReason; + updateControlsVisibility(); + updateControlsGeometry(); + update(); + if (wasNoReason != nowNoReason && _selectedCount > 0) { + toggleSelectedControls(false); + finishAnimating(); + } + setCursor((nowNoReason && !_selectedCount) + ? style::cur_pointer + : style::cur_default); +} + void TopBarWidget::showMenu() { if (!_activeChat.key || _menu) { return; @@ -318,8 +350,8 @@ void TopBarWidget::paintEvent(QPaintEvent *e) { } Painter p(this); - auto hasSelected = (_selectedCount > 0); - auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.value(hasSelected ? 1. : 0.)); + auto selectedButtonsTop = countSelectedButtonsTop( + _selectedShown.value(showSelectedActions() ? 1. : 0.)); p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBg); if (selectedButtonsTop < 0) { @@ -337,6 +369,34 @@ void TopBarWidget::paintTopBar(Painter &p) { auto statustop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; auto availableWidth = width() - _rightTaken - nameleft; + if (_chooseForReportReason) { + const auto text = [&] { + using Reason = Ui::ReportReason; + switch (*_chooseForReportReason) { + case Reason::Spam: return tr::lng_report_reason_spam(tr::now); + case Reason::Violence: + return tr::lng_report_reason_violence(tr::now); + case Reason::ChildAbuse: + return tr::lng_report_reason_child_abuse(tr::now); + case Reason::Pornography: + return tr::lng_report_reason_pornography(tr::now); + } + Unexpected("reason in TopBarWidget::paintTopBar."); + }(); + p.setPen(st::dialogsNameFg); + p.setFont(st::semiboldFont); + p.drawTextLeft(nameleft, nametop, width(), text); + + p.setFont(st::dialogsTextFont); + p.setPen(st::historyStatusFg); + p.drawTextLeft( + nameleft, + statustop, + width(), + tr::lng_report_select_messages(tr::now)); + return; + } + const auto history = _activeChat.key.history(); const auto folder = _activeChat.key.folder(); if (folder @@ -476,7 +536,8 @@ QRect TopBarWidget::getMembersShowAreaGeometry() const { void TopBarWidget::mousePressEvent(QMouseEvent *e) { auto handleClick = (e->button() == Qt::LeftButton) && (e->pos().y() < st::topBarHeight) - && (!_selectedCount); + && !_selectedCount + && !_chooseForReportReason; if (handleClick) { if (_animatingMode && _back->rect().contains(e->pos())) { backClicked(); @@ -610,14 +671,16 @@ void TopBarWidget::updateSearchVisibility() { const auto historyMode = (_activeChat.section == Section::History); const auto smallDialogsColumn = _activeChat.key.folder() && (width() < _back->width() + _search->width()); - _search->setVisible(historyMode && !smallDialogsColumn); + _search->setVisible(historyMode + && !smallDialogsColumn + && !_chooseForReportReason); } void TopBarWidget::updateControlsGeometry() { if (!_activeChat.key) { return; } - auto hasSelected = (_selectedCount > 0); + auto hasSelected = showSelectedActions(); auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.value(hasSelected ? 1. : 0.)); auto otherButtonsTop = selectedButtonsTop + st::topBarHeight; auto buttonsLeft = st::topBarActionSkip + (Adaptive::OneColumn() ? 0 : st::lineWidth); @@ -648,7 +711,11 @@ void TopBarWidget::updateControlsGeometry() { _delete->moveToLeft(buttonsLeft, selectedButtonsTop); _clear->moveToRight(st::topBarActionSkip, selectedButtonsTop); - if (_back->isHidden()) { + if (!_cancelChoose->isHidden()) { + _leftTaken = 0; + _cancelChoose->moveToLeft(_leftTaken, otherButtonsTop); + _leftTaken += _cancelChoose->width(); + } else if (_back->isHidden()) { _leftTaken = st::topBarArrowPadding.right(); } else { const auto smallDialogsColumn = _activeChat.key.folder() @@ -714,12 +781,13 @@ void TopBarWidget::updateControlsVisibility() { auto backVisible = Adaptive::OneColumn() || !_controller->content()->stackIsEmpty() || _activeChat.key.folder(); - _back->setVisible(backVisible); + _back->setVisible(backVisible && !_chooseForReportReason); + _cancelChoose->setVisible(_chooseForReportReason.has_value()); if (_info) { - _info->setVisible(Adaptive::OneColumn()); + _info->setVisible(Adaptive::OneColumn() && !_chooseForReportReason); } if (_unreadBadge) { - _unreadBadge->show(); + _unreadBadge->setVisible(!_chooseForReportReason); } const auto section = _activeChat.section; const auto historyMode = (section == Section::History); @@ -730,11 +798,12 @@ void TopBarWidget::updateControlsVisibility() { ? hasPollsMenu : historyMode); updateSearchVisibility(); - _menuToggle->setVisible(hasMenu); + _menuToggle->setVisible(hasMenu && !_chooseForReportReason); _infoToggle->setVisible(historyMode && !_activeChat.key.folder() && !Adaptive::OneColumn() - && _controller->canShowThirdSection()); + && _controller->canShowThirdSection() + && !_chooseForReportReason); const auto callsEnabled = [&] { if (const auto peer = _activeChat.key.peer()) { if (const auto user = peer->asUser()) { @@ -743,7 +812,9 @@ void TopBarWidget::updateControlsVisibility() { } return false; }(); - _call->setVisible(historyMode && callsEnabled); + _call->setVisible(historyMode + && callsEnabled + && !_chooseForReportReason); const auto groupCallsEnabled = [&] { if (const auto peer = _activeChat.key.peer()) { if (peer->canManageGroupCall()) { @@ -755,10 +826,12 @@ void TopBarWidget::updateControlsVisibility() { } return false; }(); - _groupCall->setVisible(historyMode && groupCallsEnabled); + _groupCall->setVisible(historyMode + && groupCallsEnabled + && !_chooseForReportReason); if (_membersShowArea) { - _membersShowArea->show(); + _membersShowArea->setVisible(!_chooseForReportReason); } updateControlsGeometry(); } @@ -824,21 +897,29 @@ void TopBarWidget::showSelected(SelectedState state) { _canSendNow = canSendNow; updateControlsVisibility(); } - if (wasSelected != hasSelected) { + if (wasSelected != hasSelected && !_chooseForReportReason) { setCursor(hasSelected ? style::cur_default : style::cur_pointer); updateMembersShowArea(); - _selectedShown.start( - [this] { selectedShowCallback(); }, - hasSelected ? 0. : 1., - hasSelected ? 1. : 0., - st::slideWrapDuration, - anim::easeOutCirc); + toggleSelectedControls(hasSelected); } else { updateControlsGeometry(); } } +void TopBarWidget::toggleSelectedControls(bool shown) { + _selectedShown.start( + [this] { selectedShowCallback(); }, + shown ? 0. : 1., + shown ? 1. : 0., + st::slideWrapDuration, + anim::easeOutCirc); +} + +bool TopBarWidget::showSelectedActions() const { + return (_selectedCount > 0) && !_chooseForReportReason; +} + void TopBarWidget::selectedShowCallback() { updateControlsGeometry(); update(); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index 0cde0340a..ae6d0453c 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -24,6 +24,7 @@ class IconButton; class DropdownMenu; class UnreadBadge; class InfiniteRadialAnimation; +enum class ReportReason; } // namespace Ui namespace Window { @@ -66,6 +67,9 @@ public: SendActionPainter *sendAction); void setCustomTitle(const QString &title); + void showChooseMessagesForReport(Ui::ReportReason reason); + void clearChooseMessagesForReport(); + rpl::producer<> forwardSelectionRequest() const { return _forwardSelection.events(); } @@ -78,6 +82,9 @@ public: rpl::producer<> clearSelectionRequest() const { return _clearSelection.events(); } + rpl::producer<> cancelChooseForReportRequest() const { + return _cancelChooseForReport.events(); + } protected: void paintEvent(QPaintEvent *e) override; @@ -126,6 +133,9 @@ private: void refreshUnreadBadge(); void updateUnreadBadge(); + void setChooseForReportReason(std::optional reason); + void toggleSelectedControls(bool shown); + [[nodiscard]] bool showSelectedActions() const; const not_null _controller; ActiveChat _activeChat; @@ -143,6 +153,7 @@ private: object_ptr _forward, _sendNow, _delete; object_ptr _back; + object_ptr _cancelChoose; object_ptr _unreadBadge = { nullptr }; object_ptr _info = { nullptr }; @@ -164,6 +175,7 @@ private: std::unique_ptr _connecting; SendActionPainter *_sendAction = nullptr; + std::optional _chooseForReportReason; base::Timer _onlineUpdater; @@ -171,6 +183,7 @@ private: rpl::event_stream<> _sendNowSelection; rpl::event_stream<> _deleteSelection; rpl::event_stream<> _clearSelection; + rpl::event_stream<> _cancelChooseForReport; }; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 6a24cf9c4..a8684372b 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -749,6 +749,13 @@ topBarSearch: IconButton { color: windowBgOver; } } +topBarCloseChoose: IconButton(topBarSearch) { + width: 56px; + icon: icon {{ "info_close", boxTitleCloseFg }}; + iconOver: icon {{ "info_close", boxTitleCloseFgOver }}; + iconPosition: point(10px, -1px); + rippleAreaPosition: point(7px, 7px); +} topBarSkip: -5px; topBarCallSkip: -1px; topBarMenuToggle: IconButton(topBarSearch) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index f9ddf2910..fc08c38a4 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/box_content_divider.h" +#include "ui/boxes/report_box.h" #include "ui/layers/generic_box.h" #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper @@ -29,7 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/add_contact_box.h" -#include "boxes/report_box.h" #include "boxes/peers/edit_contact_box.h" #include "lang/lang_keys.h" #include "info/info_controller.h" @@ -586,11 +586,14 @@ void ActionsFiller::addBotCommandActions(not_null user) { void ActionsFiller::addReportAction() { const auto peer = _peer; + const auto report = [=] { + + }; AddActionButton( _wrap, tr::lng_profile_report(), rpl::single(true), - [=] { Ui::show(Box(peer)); }, + report, st::infoBlockButton); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index cf72e9904..a0f0c42ad 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "boxes/add_contact_box.h" #include "boxes/confirm_box.h" -#include "boxes/report_box.h" #include "mainwidget.h" #include "main/main_session.h" #include "apiwrap.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 50c8614bf..0798670d5 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1414,6 +1414,18 @@ void MainWidget::ctrlEnterSubmitUpdated() { _history->updateFieldSubmitSettings(); } +void MainWidget::showChooseReportMessages( + not_null peer, + Ui::ReportReason reason, + Fn done) { + _history->setChooseReportMessagesDetails(reason, std::move(done)); + ui_showPeerHistory(peer->id, SectionShow(), ShowForChooseMessagesMsgId); +} + +void MainWidget::clearChooseReportMessages() { + _history->setChooseReportMessagesDetails({}, nullptr); +} + void MainWidget::ui_showPeerHistory( PeerId peerId, const SectionShow ¶ms, diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 79b25a047..a3dc81401 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -65,6 +65,7 @@ namespace Ui { class ResizeArea; class PlainShadow; class DropdownMenu; +enum class ReportReason; template class SlideWrap; } // namespace Ui @@ -210,6 +211,12 @@ public: void searchInChat(Dialogs::Key chat); + void showChooseReportMessages( + not_null peer, + Ui::ReportReason reason, + Fn done); + void clearChooseReportMessages(); + void ui_showPeerHistory( PeerId peer, const SectionShow ¶ms, diff --git a/Telegram/SourceFiles/ui/boxes/report_box.cpp b/Telegram/SourceFiles/ui/boxes/report_box.cpp new file mode 100644 index 000000000..539d6a97d --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/report_box.cpp @@ -0,0 +1,93 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/boxes/report_box.h" + +#include "lang/lang_keys.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/toast/toast.h" +#include "styles/style_layers.h" +#include "styles/style_boxes.h" +#include "styles/style_profile.h" + +namespace Ui { +namespace { + +constexpr auto kReportReasonLengthMax = 512; + +using Source = ReportSource; +using Reason = ReportReason; + +} // namespace + +void ReportReasonBox( + not_null box, + ReportSource source, + Fn done) { + box->setTitle([&] { + switch (source) { + case Source::Message: return tr::lng_report_message_title(); + case Source::Channel: return tr::lng_report_title(); + case Source::Group: return tr::lng_report_group_title(); + case Source::Bot: return tr::lng_report_bot_title(); + } + Unexpected("'source' in ReportReasonBox."); + }()); + const auto add = [&](Reason reason, tr::phrase<> text) { + const auto layout = box->verticalLayout(); + const auto button = layout->add( + object_ptr(layout, text())); + button->setClickedCallback([=] { + done(reason); + }); + }; + add(Reason::Spam, tr::lng_report_reason_spam); + if (source != Source::Message) { + add(Reason::Fake, tr::lng_report_reason_fake); + } + add(Reason::Violence, tr::lng_report_reason_violence); + add(Reason::ChildAbuse, tr::lng_report_reason_child_abuse); + add(Reason::Pornography, tr::lng_report_reason_pornography); + add(Reason::Other, tr::lng_report_reason_other); + + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +void ReportDetailsBox( + not_null box, + Fn done) { + box->addRow( + object_ptr( + box, // #TODO reports + tr::lng_report_details_about(), + st::boxLabel), + { + st::boxRowPadding.left(), + st::boxPadding.top(), + st::boxRowPadding.right(), + st::boxPadding.bottom() }); + const auto details = box->addRow( + object_ptr( + box, + st::newGroupDescription, + Ui::InputField::Mode::MultiLine, + tr::lng_report_details(), + QString())); + details->setMaxLength(kReportReasonLengthMax); + box->setFocusCallback([=] { + details->setFocusFast(); + }); + box->addButton(tr::lng_report_button(), [=] { + const auto text = details->getLastText(); + done(text); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/report_box.h b/Telegram/SourceFiles/ui/boxes/report_box.h new file mode 100644 index 000000000..613011654 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/report_box.h @@ -0,0 +1,39 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { + +class GenericBox; + +enum class ReportSource { + Message, + Channel, + Group, + Bot, +}; + +enum class ReportReason { + Spam, + Fake, + Violence, + ChildAbuse, + Pornography, + Other, +}; + +void ReportReasonBox( + not_null box, + ReportSource source, + Fn done); + +void ReportDetailsBox( + not_null box, + Fn done); + +} // namespace Ui diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 84e8e173e..95e891f7a 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -11,10 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "boxes/mute_settings_box.h" #include "boxes/add_contact_box.h" -#include "boxes/report_box.h" #include "boxes/create_poll_box.h" #include "boxes/peers/add_participants_box.h" #include "boxes/peers/edit_contact_box.h" +#include "ui/boxes/report_box.h" #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" #include "ui/widgets/labels.h" @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" // GetErrorTextForSending. +#include "history/view/history_view_context_menu.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "support/support_helper.h" @@ -604,8 +605,8 @@ void Filler::addChannelActions(not_null channel) { const auto needReport = !channel->amCreator() && (!isGroup || channel->isPublic()); if (needReport) { - _addAction(tr::lng_profile_report(tr::now), [channel] { - Ui::show(Box(channel)); + _addAction(tr::lng_profile_report(tr::now), [=] { + HistoryView::ShowReportPeerBox(navigation, channel); }); } } @@ -974,6 +975,21 @@ void PeerMenuUnblockUserWithBotRestart(not_null user) { }); } +void BlockSenderFromRepliesBox( + not_null box, + not_null controller, + FullMsgId id) { + const auto item = controller->session().data().message(id); + Assert(item != nullptr); + + PeerMenuBlockUserBox( + box, + &controller->window(), + item->senderOriginal(), + true, + Window::ClearReply{ id }); +} + QPointer ShowForwardMessagesBox( not_null navigation, MessageIdsList &&items, diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 939f52a39..fbec5bd76 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -86,6 +86,11 @@ void PeerMenuBlockUserBox( std::variant suggestClear); void PeerMenuUnblockUserWithBotRestart(not_null user); +void BlockSenderFromRepliesBox( + not_null box, + not_null controller, + FullMsgId id); + void ToggleHistoryArchived(not_null history, bool archived); Fn ClearHistoryHandler(not_null peer); Fn DeleteAndLeaveHandler(not_null peer); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 82da3e84f..fbcb5be6d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1073,6 +1073,17 @@ void SessionController::clearPassportForm() { _passportForm = nullptr; } +void SessionController::showChooseReportMessages( + not_null peer, + Ui::ReportReason reason, + Fn done) { + content()->showChooseReportMessages(peer, reason, std::move(done)); +} + +void SessionController::clearChooseReportMessages() { + content()->clearChooseReportMessages(); +} + void SessionController::updateColumnLayout() { content()->updateColumnLayout(); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index d973fe3da..282106b15 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -45,6 +45,7 @@ class FormController; namespace Ui { class LayerWidget; +enum class ReportReason; } // namespace Ui namespace Window { @@ -332,6 +333,12 @@ public: void showPassportForm(const Passport::FormRequest &request); void clearPassportForm(); + void showChooseReportMessages( + not_null peer, + Ui::ReportReason reason, + Fn done); + void clearChooseReportMessages(); + base::Variable &dialogsListFocused() { return _dialogsListFocused; } diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 60acbe619..5897b79c4 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -72,6 +72,8 @@ PRIVATE ui/boxes/choose_date_time.h ui/boxes/edit_invite_link.cpp ui/boxes/edit_invite_link.h + ui/boxes/report_box.cpp + ui/boxes/report_box.h ui/chat/attach/attach_album_thumbnail.cpp ui/chat/attach/attach_album_thumbnail.h ui/chat/attach/attach_album_preview.cpp