Allow reporting / banning from reactions in groups.

This commit is contained in:
John Preston 2024-02-01 17:29:53 +04:00
parent c06699e8e7
commit 5401d00548
12 changed files with 241 additions and 44 deletions

View file

@ -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";

View file

@ -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),

View file

@ -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<Window::SessionController*> 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<Info::Memento>(
std::vector<std::shared_ptr<Info::ContentMemento>>{
std::make_shared<Info::Profile::Memento>(
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)) {

View file

@ -478,6 +478,10 @@ infoMainButton: SettingsButton(infoProfileButton) {
textFgOver: lightButtonFgOver;
style: semiboldTextStyle;
}
infoMainButtonAttention: SettingsButton(infoMainButton) {
textFg: attentionButtonFg;
textFgOver: attentionButtonFgOver;
}
infoSharedMediaButton: infoProfileButton;
infoSharedMediaBottomSkip: 12px;

View file

@ -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 <typename Text, typename ToggleOn, typename Callback>
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*> controller,
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer);
not_null<PeerData*> peer,
Origin origin);
DetailsFiller(
not_null<Controller*> controller,
not_null<Ui::RpWidget*> parent,
@ -223,6 +230,12 @@ private:
Ui::MultiSlideTracker fillChannelButtons(
not_null<ChannelData*> 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<Ui::RpWidget*> _parent;
not_null<PeerData*> _peer;
Data::ForumTopic *_topic = nullptr;
Origin _origin;
object_ptr<Ui::VerticalLayout> _wrap;
};
@ -272,13 +286,65 @@ private:
};
void ReportReactionBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<PeerData*> participant,
GroupReactionOrigin data,
bool ban,
Fn<void()> sent) {
box->setTitle(tr::lng_report_reaction_title());
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_report_reaction_about(),
st::boxLabel));
const auto check = ban
? box->addRow(
object_ptr<Ui::Checkbox>(
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*> controller,
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer)
not_null<PeerData*> 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<rpl::variable<bool>>(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<Ui::RpWidget> SetupDetails(
not_null<Controller*> controller,
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer) {
DetailsFiller filler(controller, parent, peer);
not_null<PeerData*> peer,
Origin origin) {
DetailsFiller filler(controller, parent, peer, origin);
return filler.fill();
}

View file

@ -25,10 +25,13 @@ namespace Info::Profile {
extern const char kOptionShowPeerIdBelowAbout[];
struct Origin;
object_ptr<Ui::RpWidget> SetupDetails(
not_null<Controller*> controller,
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer);
not_null<PeerData*> peer,
Origin origin);
object_ptr<Ui::RpWidget> SetupDetails(
not_null<Controller*> controller,

View file

@ -49,13 +49,14 @@ namespace Profile {
InnerWidget::InnerWidget(
QWidget *parent,
not_null<Controller*> controller)
not_null<Controller*> 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<Ui::RpWidget> InnerWidget::setupContent(
not_null<RpWidget*> parent) {
not_null<RpWidget*> parent,
Origin origin) {
auto result = object_ptr<Ui::VerticalLayout>(parent);
if (const auto user = _peer->asUser()) {
user->session().changes().peerFlagsValue(
@ -104,7 +106,7 @@ object_ptr<Ui::RpWidget> 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) {

View file

@ -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*> controller);
not_null<Controller*> controller,
Origin origin);
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
@ -57,7 +59,9 @@ protected:
int visibleBottom) override;
private:
object_ptr<RpWidget> setupContent(not_null<RpWidget*> parent);
object_ptr<RpWidget> setupContent(
not_null<RpWidget*> parent,
Origin origin);
object_ptr<RpWidget> setupSharedMedia(not_null<RpWidget*> parent);
void setupMembers(not_null<Ui::VerticalLayout*> container);
@ -71,6 +75,8 @@ private:
PeerData * const _migrated = nullptr;
Data::ForumTopic * const _topic = nullptr;
PeerData *_reactionGroup = nullptr;
std::shared_ptr<Data::PhotoMedia> _nonPersonalView;
Members *_members = nullptr;

View file

@ -25,18 +25,24 @@ Memento::Memento(not_null<Controller*> controller)
: Memento(
controller->peer(),
controller->topic(),
controller->migratedPeerId()) {
controller->migratedPeerId(),
{ v::null }) {
}
Memento::Memento(not_null<PeerData*> peer, PeerId migratedPeerId)
: Memento(peer, nullptr, migratedPeerId) {
Memento::Memento(
not_null<PeerData*> peer,
PeerId migratedPeerId,
Origin origin)
: Memento(peer, nullptr, migratedPeerId, origin) {
}
Memento::Memento(
not_null<PeerData*> peer,
Data::ForumTopic *topic,
PeerId migratedPeerId)
: ContentMemento(peer, topic, migratedPeerId) {
PeerId migratedPeerId,
Origin origin)
: ContentMemento(peer, topic, migratedPeerId)
, _origin(origin) {
}
Memento::Memento(not_null<Data::ForumTopic*> topic)
@ -51,7 +57,7 @@ object_ptr<ContentWidget> Memento::createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) {
auto result = object_ptr<Widget>(parent, controller);
auto result = object_ptr<Widget>(parent, controller, _origin);
result->setInternalState(geometry, this);
return result;
}
@ -66,13 +72,17 @@ std::unique_ptr<MembersState> Memento::membersState() {
Memento::~Memento() = default;
Widget::Widget(QWidget *parent, not_null<Controller*> controller)
Widget::Widget(
QWidget *parent,
not_null<Controller*> controller,
Origin origin)
: ContentWidget(parent, controller) {
controller->setSearchEnabledByContent(false);
_inner = setInnerWidget(object_ptr<InnerWidget>(
this,
controller));
controller,
origin));
_inner->move(0, 0);
_inner->scrollToRequests(
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {

View file

@ -18,10 +18,22 @@ namespace Info::Profile {
class InnerWidget;
struct MembersState;
struct GroupReactionOrigin {
not_null<PeerData*> group;
MsgId messageId = 0;
};
struct Origin {
std::variant<v::null_t, GroupReactionOrigin> data;
};
class Memento final : public ContentMemento {
public:
explicit Memento(not_null<Controller*> controller);
Memento(not_null<PeerData*> peer, PeerId migratedPeerId);
Memento(
not_null<PeerData*> peer,
PeerId migratedPeerId,
Origin origin = { v::null });
explicit Memento(not_null<Data::ForumTopic*> topic);
object_ptr<ContentWidget> createWidget(
@ -31,6 +43,10 @@ public:
Section section() const override;
[[nodiscard]] Origin origin() const {
return _origin;
}
void setMembersState(std::unique_ptr<MembersState> state);
std::unique_ptr<MembersState> membersState();
@ -40,15 +56,17 @@ private:
Memento(
not_null<PeerData*> peer,
Data::ForumTopic *topic,
PeerId migratedPeerId);
PeerId migratedPeerId,
Origin origin);
std::unique_ptr<MembersState> _membersState;
Origin _origin;
};
class Widget final : public ContentWidget {
public:
Widget(QWidget *parent, not_null<Controller*> controller);
Widget(QWidget *parent, not_null<Controller*> controller, Origin origin);
bool showInternal(
not_null<ContentMemento*> memento) override;

View file

@ -80,7 +80,7 @@ public:
not_null<PopupMenu*> parentMenu,
rpl::producer<WhoReadContent> content,
CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void(WhoReadParticipant)> participantChosen,
Fn<void()> showAllChosen);
bool isEnabled() const override;
@ -105,7 +105,7 @@ private:
const not_null<PopupMenu*> _parentMenu;
const not_null<QAction*> _dummyAction;
const Fn<void(uint64)> _participantChosen;
const Fn<void(WhoReadParticipant)> _participantChosen;
const Fn<void()> _showAllChosen;
const std::unique_ptr<GroupCallUserpics> _userpics;
const style::Menu &_st;
@ -186,7 +186,7 @@ Action::Action(
not_null<PopupMenu*> parentMenu,
rpl::producer<WhoReadContent> content,
Text::CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void(WhoReadParticipant)> participantChosen,
Fn<void()> 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<Menu::ItemBase> WhoReactedContextAction(
not_null<PopupMenu*> menu,
rpl::producer<WhoReadContent> content,
CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void(WhoReadParticipant)> participantChosen,
Fn<void()> showAllChosen) {
return base::make_unique_q<Action>(
menu,
@ -931,7 +931,7 @@ base::unique_qptr<Menu::ItemBase> WhenReadContextAction(
WhoReactedListMenu::WhoReactedListMenu(
CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void(WhoReadParticipant)> participantChosen,
Fn<void()> 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,

View file

@ -59,7 +59,7 @@ struct WhoReadContent {
not_null<PopupMenu*> menu,
rpl::producer<WhoReadContent> content,
Text::CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void(WhoReadParticipant)> participantChosen,
Fn<void()> showAllChosen);
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhenReadContextAction(
@ -123,7 +123,7 @@ class WhoReactedListMenu final {
public:
WhoReactedListMenu(
Text::CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void(WhoReadParticipant)> participantChosen,
Fn<void()> showAllChosen);
void clear();
@ -136,7 +136,7 @@ public:
private:
const Text::CustomEmojiFactory _customEmojiFactory;
const Fn<void(uint64)> _participantChosen;
const Fn<void(WhoReadParticipant)> _participantChosen;
const Fn<void()> _showAllChosen;
std::vector<not_null<WhoReactedEntryAction*>> _actions;