/* 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/moderate_messages_box.h" #include "api/api_chat_participants.h" #include "api/api_messages_search.h" #include "apiwrap.h" #include "base/event_filter.h" #include "base/timer.h" #include "ui/widgets/participants_check_view.h" #include "boxes/delete_messages_box.h" #include "boxes/peers/edit_peer_permissions_box.h" #include "core/application.h" #include "core/ui_integration.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_chat_participant_status.h" #include "data/data_histories.h" #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/toggle_arrow.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/rect_part.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/checkbox.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" #include "styles/style_window.h" namespace { using Participants = std::vector>; struct Controller final { rpl::event_stream toggleRequestsFromTop; rpl::event_stream toggleRequestsFromInner; rpl::event_stream checkAllRequests; Fn collectRequests; }; struct ModerateOptions final { bool allCanBan = false; bool allCanDelete = false; Participants participants; }; ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) { Expects(!items.empty()); auto result = ModerateOptions{ .allCanBan = true, .allCanDelete = true, }; const auto peer = items.front()->history()->peer; for (const auto &item : items) { if (!result.allCanBan && !result.allCanDelete) { return {}; } if (peer != item->history()->peer) { return {}; } if (!item->suggestBanReport()) { result.allCanBan = false; } if (!item->suggestDeleteAllReport()) { result.allCanDelete = false; } if (const auto p = item->from()) { if (!ranges::contains(result.participants, not_null{ p })) { result.participants.push_back(p); } } } return result; } [[nodiscard]] rpl::producer MessagesCountValue( not_null history, not_null from) { return [=](auto consumer) { auto lifetime = rpl::lifetime(); auto search = lifetime.make_state(history); consumer.put_next(0); search->messagesFounds( ) | rpl::start_with_next([=](const Api::FoundMessages &found) { consumer.put_next_copy(found.total); }, lifetime); search->searchMessages({ .from = from }); return lifetime; }; } class Button final : public Ui::RippleButton { public: Button(not_null parent, int count); [[nodiscard]] not_null checkView() const; private: void paintEvent(QPaintEvent *event) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; std::unique_ptr _view; }; Button::Button(not_null parent, int count) : Ui::RippleButton(parent, st::defaultRippleAnimation) , _view(std::make_unique( count, st::slideWrapDuration, false, [=] { update(); })) { } not_null Button::checkView() const { return _view.get(); } QImage Button::prepareRippleMask() const { return _view->prepareRippleMask(); } QPoint Button::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()); } void Button::paintEvent(QPaintEvent *event) { auto p = QPainter(this); Ui::RippleButton::paintRipple(p, QPoint()); _view->paint(p, 0, 0, width()); } void CreateParticipantsList( not_null controller, not_null inner, const Participants &participants) { const auto wrap = inner->add( object_ptr>( inner, object_ptr(inner))); wrap->toggle(false, anim::type::instant); controller->toggleRequestsFromTop.events( ) | rpl::start_with_next([=](bool toggled) { wrap->toggle(toggled, anim::type::normal); }, wrap->lifetime()); const auto container = wrap->entity(); Ui::AddSkip(container); auto &lifetime = wrap->lifetime(); const auto clicks = lifetime.make_state>(); const auto checkboxes = ranges::views::all( participants ) | ranges::views::transform([&](not_null peer) { const auto line = container->add( object_ptr(container)); const auto &st = st::moderateBoxUserpic; line->resize(line->width(), st.size.height()); const auto userpic = Ui::CreateChild( line, peer, st); const auto checkbox = Ui::CreateChild( line, peer->name(), false, st::defaultBoxCheckbox); line->widthValue( ) | rpl::start_with_next([=](int width) { userpic->moveToLeft( st::boxRowPadding.left() + checkbox->checkRect().width() + st::defaultBoxCheckbox.textPosition.x(), 0); const auto skip = st::defaultBoxCheckbox.textPosition.x(); checkbox->resizeToWidth(width - rect::right(userpic) - skip - st::boxRowPadding.right()); checkbox->moveToLeft( rect::right(userpic) + skip, ((userpic->height() - checkbox->height()) / 2) + st::defaultBoxCheckbox.margin.top()); }, checkbox->lifetime()); userpic->setAttribute(Qt::WA_TransparentForMouseEvents); checkbox->setAttribute(Qt::WA_TransparentForMouseEvents); line->setClickedCallback([=] { checkbox->setChecked(!checkbox->checked()); clicks->fire({}); }); return checkbox; }) | ranges::to_vector; clicks->events( ) | rpl::start_with_next([=] { controller->toggleRequestsFromInner.fire_copy( ranges::any_of(checkboxes, &Ui::Checkbox::checked)); }, container->lifetime()); controller->checkAllRequests.events( ) | rpl::start_with_next([=](bool checked) { for (const auto &c : checkboxes) { c->setChecked(checked); } }, container->lifetime()); controller->collectRequests = [=] { auto result = Participants(); for (auto i = 0; i < checkboxes.size(); i++) { if (checkboxes[i]->checked()) { result.push_back(participants[i]); } } return result; }; } void AppendList( not_null checkbox, not_null controller, not_null inner, const Participants &participants, bool handleSingle) { const auto isSingle = handleSingle ? (participants.size() == 1) : false; if (isSingle) { const auto p = participants.front(); controller->collectRequests = [=] { return Participants{ p }; }; return; } const auto count = int(participants.size()); const auto button = Ui::CreateChild