From 5401d0054829aa15eb983f28e667cd8aad11cad6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 1 Feb 2024 17:29:53 +0400 Subject: [PATCH] Allow reporting / banning from reactions in groups. --- Telegram/Resources/langs/lang.strings | 5 + .../chat_helpers/field_autocomplete.cpp | 11 +- .../view/history_view_context_menu.cpp | 34 ++++- Telegram/SourceFiles/info/info.style | 4 + .../info/profile/info_profile_actions.cpp | 134 +++++++++++++++++- .../info/profile/info_profile_actions.h | 5 +- .../profile/info_profile_inner_widget.cpp | 10 +- .../info/profile/info_profile_inner_widget.h | 10 +- .../info/profile/info_profile_widget.cpp | 26 ++-- .../info/profile/info_profile_widget.h | 24 +++- .../controls/who_reacted_context_action.cpp | 16 +-- .../ui/controls/who_reacted_context_action.h | 6 +- 12 files changed, 241 insertions(+), 44 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d2d4242c0..da7c33cd4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1505,6 +1505,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_report_messages_none" = "Select Messages"; "lng_report_messages_count#one" = "Report {count} Message"; "lng_report_messages_count#other" = "Report {count} Messages"; +"lng_report_reaction" = "Report reaction"; +"lng_report_and_ban" = "Ban and report"; +"lng_report_reaction_title" = "Report reaction"; +"lng_report_reaction_about" = "Are you sure you want to report reactions from this user?"; +"lng_report_and_ban_button" = "Ban user"; "lng_report_details_about" = "Please enter any additional details relevant to your report."; "lng_report_details" = "Additional Details"; "lng_report_reason_spam" = "Spam"; diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 2c7bf803c..382d850e3 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -331,7 +331,7 @@ void FieldAutocomplete::showFiltered( plainQuery = base::StringViewMid(query, 1); break; } - bool resetScroll = (_type != type || _filter != plainQuery); + const auto resetScroll = (_type != type || _filter != plainQuery); if (resetScroll) { _type = type; _filter = TextUtilities::RemoveAccents(plainQuery.toString()); @@ -342,10 +342,11 @@ void FieldAutocomplete::showFiltered( } void FieldAutocomplete::showStickers(EmojiPtr emoji) { - bool resetScroll = (_emoji != emoji); - _emoji = emoji; - _type = Type::Stickers; - if (!emoji) { + const auto resetScroll = (_emoji != emoji); + if (resetScroll || emoji) { + _emoji = emoji; + _type = Type::Stickers; + } else if (!emoji) { rowsUpdated( base::take(_mrows), base::take(_hrows), diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 898a645a8..46f043664 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -26,6 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_web_page.h" #include "history/view/reactions/history_view_reactions_list.h" +#include "info/info_memento.h" +#include "info/profile/info_profile_widget.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/menu/menu_common.h" @@ -1089,6 +1091,28 @@ void EditTagBox( }); } +void ShowWhoReadInfo( + not_null controller, + FullMsgId itemId, + Ui::WhoReadParticipant who) { + const auto peer = controller->session().data().peer(itemId.peer); + const auto participant = peer->owner().peer(PeerId(who.id)); + const auto migrated = participant->migrateFrom(); + const auto origin = who.dateReacted + ? Info::Profile::Origin{ + Info::Profile::GroupReactionOrigin{ peer, itemId.msg }, + } + : Info::Profile::Origin(); + auto memento = std::make_shared( + std::vector>{ + std::make_shared( + participant, + migrated ? migrated->id : PeerId(), + origin), + }); + controller->showSection(std::move(memento)); +} + } // namespace ContextMenuRequest::ContextMenuRequest( @@ -1392,11 +1416,12 @@ void AddWhoReactedAction( }); controller->show(std::move(box)); }; - const auto participantChosen = [=](uint64 id) { + const auto itemId = item->fullId(); + const auto participantChosen = [=](Ui::WhoReadParticipant who) { if (const auto strong = weak.data()) { strong->hideMenu(); } - controller->showPeerInfo(PeerId(id)); + ShowWhoReadInfo(controller, itemId, who); }; const auto showAllChosen = [=, itemId = item->fullId()]{ // Pressing on an item that has a submenu doesn't hide it :( @@ -1508,8 +1533,9 @@ void ShowWhoReactedMenu( struct State { int addedToBottom = 0; }; - const auto participantChosen = [=](uint64 id) { - controller->showPeerInfo(PeerId(id)); + const auto itemId = item->fullId(); + const auto participantChosen = [=](Ui::WhoReadParticipant who) { + ShowWhoReadInfo(controller, itemId, who); }; const auto showAllChosen = [=, itemId = item->fullId()]{ if (const auto item = controller->session().data().message(itemId)) { diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index de5e2f9ac..d53d12f94 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -478,6 +478,10 @@ infoMainButton: SettingsButton(infoProfileButton) { textFgOver: lightButtonFgOver; style: semiboldTextStyle; } +infoMainButtonAttention: SettingsButton(infoMainButton) { + textFg: attentionButtonFg; + textFgOver: attentionButtonFgOver; +} infoSharedMediaButton: infoProfileButton; infoSharedMediaBottomSkip: 12px; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 83a729670..eb775b0c9 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_actions.h" +#include "api/api_chat_participants.h" #include "base/options.h" #include "data/data_peer_values.h" #include "data/data_session.h" @@ -14,12 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/data_channel.h" #include "data/data_changes.h" +#include "data/data_chat.h" #include "data/data_user.h" #include "data/notify/data_notify_settings.h" #include "ui/vertical_list.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" +#include "ui/widgets/checkbox.h" #include "ui/widgets/shadow.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" @@ -44,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_phone_menu.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_text.h" +#include "info/profile/info_profile_widget.h" #include "support/support_helper.h" #include "window/window_session_controller.h" #include "window/window_controller.h" // Window::Controller::show. @@ -56,6 +60,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_blocked_peers.h" #include "styles/style_info.h" +#include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" @@ -190,14 +195,15 @@ template Text &&text, ToggleOn &&toggleOn, Callback &&callback, - Ui::MultiSlideTracker &tracker) { + Ui::MultiSlideTracker &tracker, + const style::SettingsButton &st = st::infoMainButton) { tracker.track(AddActionButton( parent, std::move(text) | Ui::Text::ToUpper(), std::move(toggleOn), std::move(callback), nullptr, - st::infoMainButton)); + st)); } class DetailsFiller { @@ -205,7 +211,8 @@ public: DetailsFiller( not_null controller, not_null parent, - not_null peer); + not_null peer, + Origin origin); DetailsFiller( not_null controller, not_null parent, @@ -223,6 +230,12 @@ private: Ui::MultiSlideTracker fillChannelButtons( not_null channel); + void addReportReaction(Ui::MultiSlideTracker &tracker); + void addReportReaction( + GroupReactionOrigin data, + bool ban, + Ui::MultiSlideTracker &tracker); + template < typename Widget, typename = std::enable_if_t< @@ -239,6 +252,7 @@ private: not_null _parent; not_null _peer; Data::ForumTopic *_topic = nullptr; + Origin _origin; object_ptr _wrap; }; @@ -272,13 +286,65 @@ private: }; +void ReportReactionBox( + not_null box, + not_null controller, + not_null participant, + GroupReactionOrigin data, + bool ban, + Fn sent) { + box->setTitle(tr::lng_report_reaction_title()); + box->addRow(object_ptr( + box, + tr::lng_report_reaction_about(), + st::boxLabel)); + const auto check = ban + ? box->addRow( + object_ptr( + box, + tr::lng_report_and_ban_button(tr::now), + true), + st::boxRowPadding + QMargins{ 0, st::boxLittleSkip, 0, 0 }) + : nullptr; + box->addButton(tr::lng_report_button(), [=] { + const auto chat = data.group->asChat(); + const auto channel = data.group->asMegagroup(); + if (check && check->checked()) { + if (chat) { + chat->session().api().chatParticipants().kick( + chat, + participant); + } else if (channel) { + channel->session().api().chatParticipants().kick( + channel, + participant, + ChatRestrictionsInfo()); + } + } + data.group->session().api().request(MTPmessages_ReportReaction( + data.group->input, + MTP_int(data.messageId.bare), + participant->input + )).done(crl::guard(controller, [=] { + controller->showToast(tr::lng_report_thanks(tr::now)); + })).send(); + sent(); + box->closeBox(); + }, st::attentionBoxButton); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); +} + DetailsFiller::DetailsFiller( not_null controller, not_null parent, - not_null peer) + not_null peer, + Origin origin) : _controller(controller) , _parent(parent) , _peer(peer) +, _origin(origin) , _wrap(_parent) { } @@ -653,6 +719,58 @@ void DetailsFiller::setupMainButtons() { } } +void DetailsFiller::addReportReaction(Ui::MultiSlideTracker &tracker) { + v::match(_origin.data, [&](GroupReactionOrigin data) { + const auto user = _peer->asUser(); + if (_peer->isSelf()) { + return; + } else if (const auto chat = data.group->asChat()) { + const auto ban = chat->canBanMembers() + && (!user || !chat->admins.contains(_peer)) + && (!user || chat->creator != user->id); + addReportReaction(data, ban, tracker); + } else if (const auto channel = data.group->asMegagroup()) { + const auto ban = channel->canBanMembers() + && (!user || !channel->mgInfo->admins.contains(user->id)) + && (!user || channel->mgInfo->creator != user); + addReportReaction(data, ban, tracker); + } + }, [](const auto &) {}); +} + +void DetailsFiller::addReportReaction( + GroupReactionOrigin data, + bool ban, + Ui::MultiSlideTracker &tracker) { + const auto peer = _peer; + if (!peer) { + return; + } + const auto controller = _controller->parentController(); + const auto forceHidden = std::make_shared>(false); + const auto user = peer->asUser(); + auto shown = user + ? rpl::combine( + Info::Profile::IsContactValue(user), + forceHidden->value(), + !rpl::mappers::_1 && !rpl::mappers::_2 + ) | rpl::type_erased() + : (forceHidden->value() | rpl::map(!rpl::mappers::_1)); + const auto sent = [=] { + *forceHidden = true; + }; + AddMainButton( + _wrap, + (ban + ? tr::lng_report_and_ban() + : tr::lng_report_reaction()), + std::move(shown), + [=] { controller->show( + Box(ReportReactionBox, controller, peer, data, ban, sent)); }, + tracker, + st::infoMainButtonAttention); +} + Ui::MultiSlideTracker DetailsFiller::fillTopicButtons() { using namespace rpl::mappers; @@ -719,6 +837,9 @@ Ui::MultiSlideTracker DetailsFiller::fillUserButtons( } else { addSendMessageButton(); } + + addReportReaction(tracker); + return tracker; } @@ -1055,8 +1176,9 @@ const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about"; object_ptr SetupDetails( not_null controller, not_null parent, - not_null peer) { - DetailsFiller filler(controller, parent, peer); + not_null peer, + Origin origin) { + DetailsFiller filler(controller, parent, peer, origin); return filler.fill(); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.h b/Telegram/SourceFiles/info/profile/info_profile_actions.h index fcbc351f5..d31714a82 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.h +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.h @@ -25,10 +25,13 @@ namespace Info::Profile { extern const char kOptionShowPeerIdBelowAbout[]; +struct Origin; + object_ptr SetupDetails( not_null controller, not_null parent, - not_null peer); + not_null peer, + Origin origin); object_ptr SetupDetails( not_null controller, diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 4cca8c165..40b626765 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -49,13 +49,14 @@ namespace Profile { InnerWidget::InnerWidget( QWidget *parent, - not_null controller) + not_null controller, + Origin origin) : RpWidget(parent) , _controller(controller) , _peer(_controller->key().peer()) , _migrated(_controller->migrated()) , _topic(_controller->key().topic()) -, _content(setupContent(this)) { +, _content(setupContent(this, origin)) { _content->heightValue( ) | rpl::start_with_next([this](int height) { if (!_inResize) { @@ -66,7 +67,8 @@ InnerWidget::InnerWidget( } object_ptr InnerWidget::setupContent( - not_null parent) { + not_null parent, + Origin origin) { auto result = object_ptr(parent); if (const auto user = _peer->asUser()) { user->session().changes().peerFlagsValue( @@ -104,7 +106,7 @@ object_ptr InnerWidget::setupContent( } result->add(SetupDetails(_controller, parent, _topic)); } else { - result->add(SetupDetails(_controller, parent, _peer)); + result->add(SetupDetails(_controller, parent, _peer, origin)); } result->add(setupSharedMedia(result.data())); if (_topic) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h index 4a6495965..a1b257801 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h @@ -37,12 +37,14 @@ namespace Profile { class Memento; class Members; class Cover; +struct Origin; class InnerWidget final : public Ui::RpWidget { public: InnerWidget( QWidget *parent, - not_null controller); + not_null controller, + Origin origin); void saveState(not_null memento); void restoreState(not_null memento); @@ -57,7 +59,9 @@ protected: int visibleBottom) override; private: - object_ptr setupContent(not_null parent); + object_ptr setupContent( + not_null parent, + Origin origin); object_ptr setupSharedMedia(not_null parent); void setupMembers(not_null container); @@ -71,6 +75,8 @@ private: PeerData * const _migrated = nullptr; Data::ForumTopic * const _topic = nullptr; + PeerData *_reactionGroup = nullptr; + std::shared_ptr _nonPersonalView; Members *_members = nullptr; diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp index d1209a246..48b0107ed 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp @@ -25,18 +25,24 @@ Memento::Memento(not_null controller) : Memento( controller->peer(), controller->topic(), - controller->migratedPeerId()) { + controller->migratedPeerId(), + { v::null }) { } -Memento::Memento(not_null peer, PeerId migratedPeerId) -: Memento(peer, nullptr, migratedPeerId) { +Memento::Memento( + not_null peer, + PeerId migratedPeerId, + Origin origin) +: Memento(peer, nullptr, migratedPeerId, origin) { } Memento::Memento( not_null peer, Data::ForumTopic *topic, - PeerId migratedPeerId) -: ContentMemento(peer, topic, migratedPeerId) { + PeerId migratedPeerId, + Origin origin) +: ContentMemento(peer, topic, migratedPeerId) +, _origin(origin) { } Memento::Memento(not_null topic) @@ -51,7 +57,7 @@ object_ptr Memento::createWidget( QWidget *parent, not_null controller, const QRect &geometry) { - auto result = object_ptr(parent, controller); + auto result = object_ptr(parent, controller, _origin); result->setInternalState(geometry, this); return result; } @@ -66,13 +72,17 @@ std::unique_ptr Memento::membersState() { Memento::~Memento() = default; -Widget::Widget(QWidget *parent, not_null controller) +Widget::Widget( + QWidget *parent, + not_null controller, + Origin origin) : ContentWidget(parent, controller) { controller->setSearchEnabledByContent(false); _inner = setInnerWidget(object_ptr( this, - controller)); + controller, + origin)); _inner->move(0, 0); _inner->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.h b/Telegram/SourceFiles/info/profile/info_profile_widget.h index 38758fc00..b6e4da66a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.h @@ -18,10 +18,22 @@ namespace Info::Profile { class InnerWidget; struct MembersState; +struct GroupReactionOrigin { + not_null group; + MsgId messageId = 0; +}; + +struct Origin { + std::variant data; +}; + class Memento final : public ContentMemento { public: explicit Memento(not_null controller); - Memento(not_null peer, PeerId migratedPeerId); + Memento( + not_null peer, + PeerId migratedPeerId, + Origin origin = { v::null }); explicit Memento(not_null topic); object_ptr createWidget( @@ -31,6 +43,10 @@ public: Section section() const override; + [[nodiscard]] Origin origin() const { + return _origin; + } + void setMembersState(std::unique_ptr state); std::unique_ptr membersState(); @@ -40,15 +56,17 @@ private: Memento( not_null peer, Data::ForumTopic *topic, - PeerId migratedPeerId); + PeerId migratedPeerId, + Origin origin); std::unique_ptr _membersState; + Origin _origin; }; class Widget final : public ContentWidget { public: - Widget(QWidget *parent, not_null controller); + Widget(QWidget *parent, not_null controller, Origin origin); bool showInternal( not_null memento) override; diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index 942427c9c..2cbba53e9 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -80,7 +80,7 @@ public: not_null parentMenu, rpl::producer content, CustomEmojiFactory factory, - Fn participantChosen, + Fn participantChosen, Fn showAllChosen); bool isEnabled() const override; @@ -105,7 +105,7 @@ private: const not_null _parentMenu; const not_null _dummyAction; - const Fn _participantChosen; + const Fn _participantChosen; const Fn _showAllChosen; const std::unique_ptr _userpics; const style::Menu &_st; @@ -186,7 +186,7 @@ Action::Action( not_null parentMenu, rpl::producer content, Text::CustomEmojiFactory factory, - Fn participantChosen, + Fn participantChosen, Fn showAllChosen) : ItemBase(parentMenu->menu(), parentMenu->menu()->st()) , _parentMenu(parentMenu) @@ -252,7 +252,7 @@ Action::Action( ) | rpl::start_with_next([=] { if (_content.participants.size() == 1) { if (const auto onstack = _participantChosen) { - onstack(_content.participants.front().id); + onstack(_content.participants.front()); } } else if (_content.fullReactionsCount > 0) { if (const auto onstack = _showAllChosen) { @@ -909,7 +909,7 @@ base::unique_qptr WhoReactedContextAction( not_null menu, rpl::producer content, CustomEmojiFactory factory, - Fn participantChosen, + Fn participantChosen, Fn showAllChosen) { return base::make_unique_q( menu, @@ -931,7 +931,7 @@ base::unique_qptr WhenReadContextAction( WhoReactedListMenu::WhoReactedListMenu( CustomEmojiFactory factory, - Fn participantChosen, + Fn participantChosen, Fn showAllChosen) : _customEmojiFactory(std::move(factory)) , _participantChosen(std::move(participantChosen)) @@ -983,8 +983,8 @@ void WhoReactedListMenu::populate( ++index; }; for (const auto &participant : content.participants) { - const auto chosen = [call = _participantChosen, id = participant.id]{ - call(id); + const auto chosen = [call = _participantChosen, participant] { + call(participant); }; append({ .text = participant.name, diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h index 1f35a3216..74e3f0d54 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h @@ -59,7 +59,7 @@ struct WhoReadContent { not_null menu, rpl::producer content, Text::CustomEmojiFactory factory, - Fn participantChosen, + Fn participantChosen, Fn showAllChosen); [[nodiscard]] base::unique_qptr WhenReadContextAction( @@ -123,7 +123,7 @@ class WhoReactedListMenu final { public: WhoReactedListMenu( Text::CustomEmojiFactory factory, - Fn participantChosen, + Fn participantChosen, Fn showAllChosen); void clear(); @@ -136,7 +136,7 @@ public: private: const Text::CustomEmojiFactory _customEmojiFactory; - const Fn _participantChosen; + const Fn _participantChosen; const Fn _showAllChosen; std::vector> _actions;