/* 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_blocked_peers.h" #include "api/api_chat_participants.h" #include "api/api_messages_search.h" #include "api/api_report.h" #include "apiwrap.h" #include "base/event_filter.h" #include "base/options.h" #include "base/timer.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_filters.h" #include "data/data_chat_participant_status.h" #include "data/data_histories.h" #include "data/data_peer.h" #include "data/data_saved_sublist.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 "lottie/lottie_icon.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/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/rect_part.h" #include "ui/text/text_lottie_custom_emoji.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/expandable_peer_list.h" #include "ui/widgets/participants_check_view.h" #include "ui/wrap/slide_wrap.h" #include "ui/ui_utility.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" #include "styles/style_window.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "boxes/choose_filter_box.h" #include "boxes/peer_list_box.h" #include "main/main_session_settings.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/menu/menu_action.h" #include "base/qt/qt_key_modifiers.h" #include "ui/effects/round_checkbox.h" #include "styles/style_chat.h" #include "styles/style_menu_icons.h" #include "styles/style_info.h" #include "styles/style_media_player.h" // mediaPlayerMenuCheck base::options::toggle ModerateCommonGroups({ .id = kModerateCommonGroups, .name = "Ban users from several groups at once.", }); const char kModerateCommonGroups[] = "moderate-common-groups"; namespace { 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 {}; } { const auto author = item->author(); if (author == peer) { return {}; } else if (const auto channel = author->asChannel()) { if (channel->discussionLink() == 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, std::vector> from) { return [=](auto consumer) { auto lifetime = rpl::lifetime(); struct State final { base::flat_map messagesCounts; int index = 0; rpl::lifetime apiLifetime; }; const auto search = lifetime.make_state(history); const auto state = lifetime.make_state(); const auto send = [=](auto repeat) -> void { if (state->index >= from.size()) { consumer.put_next_copy(state->messagesCounts); return; } const auto peer = from[state->index]; const auto peerId = peer->id; state->apiLifetime = search->messagesFounds( ) | rpl::on_next([=](const Api::FoundMessages &found) { state->messagesCounts[peerId] = found.total; state->index++; repeat(repeat); }); search->searchMessages({ .from = peer }); }; consumer.put_next({}); send(send); return lifetime; }; } using CommonGroups = std::vector>; using CollectCommon = std::shared_ptr>; void FillMenuModerateCommonGroups( not_null menu, CommonGroups common, CollectCommon collectCommon, not_null user, Fn onDestroyedCallback) { const auto resultList = menu->lifetime().make_state>(); const auto rememberCheckbox = Ui::CreateChild( menu, QString()); auto multiline = base::make_unique_q( menu->menu(), menu->st().menu, st::historyHasCustomEmoji, st::historyHasCustomEmojiPosition, tr::lng_restrict_users_kick_from_common_group(tr::now, tr::rich)); multiline->setAttribute(Qt::WA_TransparentForMouseEvents); menu->addAction(std::move(multiline)); const auto session = &common.front()->session(); const auto settingsOnStart = session->settings().moderateCommonGroups(); const auto checkboxesUpdate = std::make_shared>(); const auto save = [=] { auto result = std::vector( resultList->begin(), resultList->end()); *collectCommon = std::move(result); }; for (const auto &group : common) { struct State { std::optional checkbox; Ui::RpWidget *checkboxWidget = nullptr; }; auto item = base::make_unique_q( menu->menu(), menu->st().menu, Ui::Menu::CreateAction( menu->menu(), group->name(), [] {}), nullptr, nullptr); const auto state = item->lifetime().make_state(); const auto setChecked = [=, peerId = group->id](bool checked) { state->checkbox->setChecked(checked); if (state->checkbox->checked()) { resultList->insert(peerId); } else { resultList->erase(peerId); } save(); }; item->setActionTriggered([=] { setChecked(!state->checkbox->checked()); }); const auto raw = item.get(); checkboxesUpdate->events() | rpl::on_next([=, peerId = group->id] { setChecked(ranges::contains(*collectCommon, peerId)); }, raw->lifetime()); state->checkboxWidget = Ui::CreateChild(raw); state->checkboxWidget->setAttribute(Qt::WA_TransparentForMouseEvents); state->checkboxWidget->resize(item->width() * 2, item->height()); state->checkboxWidget->show(); state->checkbox.emplace( st::moderateCommonGroupsCheckbox, [=] { state->checkboxWidget->update(); }, PaintUserpicCallback(group, true), [=](int size) { return (group->isForum() || group->isMonoforum()) ? int(size * Ui::ForumUserpicRadiusMultiplier()) : std::optional(); }); state->checkbox->setChecked( /*ranges::contains( session->settings().moderateCommonGroups(), group->id) || */(collectCommon && ranges::contains(*collectCommon, group->id)), anim::type::instant); state->checkboxWidget->paintOn([=](QPainter &p) { auto pp = Painter(state->checkboxWidget); state->checkbox->paint( pp, st::menuWithIcons.itemIconPosition.x(), st::menuWithIcons.itemIconPosition.y(), raw->width()); }); menu->addAction(std::move(item)); } menu->addSeparator(); if (const auto window = Core::App().findWindow(menu->parentWidget())) { auto hasActions = false; Ui::Menu::CreateAddActionCallback(menu)(Ui::Menu::MenuCallback::Args{ .text = tr::lng_restrict_users_kick_from_common_group(tr::now), .handler = nullptr, .icon = &st::menuIconAddToFolder, .fillSubmenu = [&](not_null menu) { hasActions = FillChooseFilterWithAdminedGroupsMenu( window->sessionController(), menu, user, checkboxesUpdate, common, collectCommon); }, .submenuSt = &st::foldersMenu, }); if (!hasActions) { menu->removeAction(menu->actions().size() - 1); menu->removeAction(menu->actions().size() - 1); // Separator. } } menu->addSeparator(); { auto item = base::make_unique_q( menu->menu(), menu->st().menu, Ui::Menu::CreateAction( menu->menu(), tr::lng_remember(tr::now), [] {}), nullptr, nullptr); item->setPreventClose(true); item->setActionTriggered([=] { rememberCheckbox->setChecked(!rememberCheckbox->checked()); }); rememberCheckbox->setParent(item.get()); rememberCheckbox->setAttribute(Qt::WA_TransparentForMouseEvents); rememberCheckbox->move(st::lineWidth * 8, -st::lineWidth * 2); rememberCheckbox->show(); menu->addAction(std::move(item)); } menu->setDestroyedCallback([=] { onDestroyedCallback(); if (!rememberCheckbox->checked()) { session->settings().setModerateCommonGroups(settingsOnStart); session->saveSettingsDelayed(); } }); } void ProccessCommonGroups( const HistoryItemsList &items, Fn)> processHas) { const auto moderateOptions = CalculateModerateOptions(items); if (moderateOptions.participants.size() != 1 || !moderateOptions.allCanBan) { return; } const auto participant = moderateOptions.participants.front(); const auto user = participant->asUser(); if (!user) { return; } const auto currentGroupId = items.front()->history()->peer->id; user->session().api().requestBotCommonGroups(user, [=] { const auto commonGroups = user->session().api().botCommonGroups(user); if (!commonGroups || commonGroups->empty()) { return; } auto filtered = CommonGroups(); for (const auto &group : *commonGroups) { if (group->id == currentGroupId) { continue; } const auto channel = group->asChannel(); if (channel && channel->canRestrictParticipant(user)) { if (channel->isGroupAdmin(user) && !channel->amCreator()) { continue; } filtered.push_back(group); } } if (!filtered.empty()) { processHas(filtered, user); } }); } } // namespace void CreateModerateMessagesBox( not_null box, const HistoryItemsList &items, Fn confirmed, ModerateMessagesBoxOptions options) { Expects(!items.empty()); using Controller = Ui::ExpandablePeerListController; const auto [allCanBan, allCanDelete, participants] = CalculateModerateOptions(items); const auto inner = box->verticalLayout(); Assert(!participants.empty()); const auto confirms = inner->lifetime().make_state>(); const auto collectCommon = std::make_shared>(); const auto isSingle = participants.size() == 1; const auto buttonPadding = isSingle ? QMargins() : QMargins( 0, 0, Ui::ParticipantsCheckView::ComputeSize( participants.size()).width(), 0); const auto itemsCount = int(items.size()); const auto firstItem = items.front(); const auto history = firstItem->history(); const auto session = &history->session(); const auto historyPeerId = history->peer->id; const auto ids = session->data().itemsToIds(items); { const auto remainingIds = box->lifetime().make_state>( ids.begin(), ids.end()); session->data().itemRemoved( ) | rpl::on_next([=](not_null item) { remainingIds->erase(item->fullId()); if (remainingIds->empty()) { box->closeBox(); } }, box->lifetime()); } if (ModerateCommonGroups.value() || session->supportMode()) { ProccessCommonGroups( items, crl::guard(box, [=](CommonGroups groups, not_null user) { using namespace Ui; const auto top = box->addTopButton(st::infoTopBarMenu); auto &lifetime = top->lifetime(); const auto menu = lifetime.make_state>(); { const auto was = collectCommon->size(); *menu = base::make_unique_q( top, st::popupMenuExpandedSeparator); FillMenuModerateCommonGroups( *menu, groups, collectCommon, user, []{}); *menu = nullptr; if (was != collectCommon->size()) { top->setIconOverride( &st::infoTopBarMenuActive, &st::infoTopBarMenuActive); const auto minicheck = Ui::CreateChild(top); minicheck->paintRequest() | rpl::on_next([=] { auto p = Painter(minicheck); const auto rect = minicheck->rect(); const auto iconSize = QSize( st::mediaPlayerMenuCheck.width(), st::mediaPlayerMenuCheck.height()); const auto scale = std::min( rect.width() / float64(iconSize.width()), rect.height() / float64(iconSize.height())); if (scale < 1.0) { p.save(); p.translate(rect.center()); p.scale(scale, scale); p.translate(-rect.center()); } st::mediaPlayerMenuCheck.paintInCenter( p, rect, st::windowActiveTextFg->c); if (scale < 1.0) { p.restore(); } }, minicheck->lifetime()); minicheck->resize( st::mediaPlayerMenuCheck.width() / 1.5, st::mediaPlayerMenuCheck.width() / 1.5); minicheck->show(); minicheck->moveToLeft( top->width() - st::lineWidth * 26, top->height() - st::lineWidth * 29); } } top->setClickedCallback([=] { top->setForceRippled(true); *menu = base::make_unique_q( top, st::popupMenuExpandedSeparator); const auto onDestroyedCallback = [=, weak = top] { if (const auto strong = weak.data()) { strong->setForceRippled(false); } }; FillMenuModerateCommonGroups( *menu, groups, collectCommon, user, onDestroyedCallback); (*menu)->setForcedOrigin(PanelAnimation::Origin::TopRight); const auto point = QPoint(top->width(), top->height()); (*menu)->popup(top->mapToGlobal(point)); }); })); } using Request = Fn, not_null)>; const auto sequentiallyRequest = [=]( Request request, Participants participants, std::optional> channelIds = {}) { constexpr auto kSmallDelayMs = 5; const auto participantIds = ranges::views::all( participants ) | ranges::views::transform([](not_null peer) { return peer->id; }) | ranges::to_vector; const auto channelIdList = channelIds.value_or( std::vector{ historyPeerId }); const auto lifetime = std::make_shared(); const auto participantIndex = lifetime->make_state(0); const auto channelIndex = lifetime->make_state(0); const auto timer = lifetime->make_state(); timer->setCallback(crl::guard(session, [=] { if ((*participantIndex) < participantIds.size()) { if ((*channelIndex) < channelIdList.size()) { const auto from = session->data().peer( participantIds[*participantIndex]); const auto channel = session->data().peer( channelIdList[*channelIndex])->asChannel(); if (from && channel) { request(from, channel); } (*channelIndex)++; } else { (*participantIndex)++; *channelIndex = 0; } } else { lifetime->destroy(); } })); timer->callEach(kSmallDelayMs); }; const auto handleConfirmation = [=]( not_null checkbox, not_null controller, Request request) { confirms->events() | rpl::on_next([=] { if (checkbox->checked() && controller->collectRequests) { sequentiallyRequest(request, controller->collectRequests()); } }, checkbox->lifetime()); }; const auto isEnter = [=](not_null event) { if (event->type() == QEvent::KeyPress) { if (const auto k = static_cast(event.get())) { return (k->key() == Qt::Key_Enter) || (k->key() == Qt::Key_Return); } } return false; }; base::install_event_filter(box, [=](not_null event) { if (isEnter(event)) { box->triggerButton(0); return base::EventFilterResult::Cancel; } return base::EventFilterResult::Continue; }); const auto handleSubmition = [=](not_null checkbox) { base::install_event_filter(box, [=](not_null event) { if (!isEnter(event) || !checkbox->checked()) { return base::EventFilterResult::Continue; } box->uiShow()->show(Ui::MakeConfirmBox({ .text = tr::lng_gigagroup_warning_title(), .confirmed = [=](Fn close) { box->triggerButton(0); close(); }, .confirmText = tr::lng_box_yes(), .cancelText = tr::lng_box_no(), })); return base::EventFilterResult::Cancel; }); }; Ui::AddSkip(inner); const auto title = box->addRow( object_ptr( box, (itemsCount == 1) ? tr::lng_selected_delete_sure_this() : tr::lng_selected_delete_sure( lt_count, rpl::single(itemsCount) | tr::to_count()), st::boxLabel)); Ui::AddSkip(inner); Ui::AddSkip(inner); Ui::AddSkip(inner); { const auto report = box->addRow( object_ptr( box, tr::lng_report_spam(tr::now), options.reportSpam, st::defaultBoxCheckbox), st::boxRowPadding + buttonPadding); const auto controller = box->lifetime().make_state( Controller::Data{ .participants = participants }); Ui::AddExpandablePeerList(report, controller, inner); handleSubmition(report); handleConfirmation(report, controller, [=]( not_null p, not_null c) { Api::ReportSpam(p, ids); }); } if (allCanDelete) { Ui::AddSkip(inner); Ui::AddSkip(inner); const auto deleteAll = inner->add( object_ptr( inner, !(isSingle) ? tr::lng_delete_all_from_users( tr::now, tr::marked) : tr::lng_delete_all_from_user( tr::now, lt_user, tr::bold(firstItem->from()->name()), tr::marked), options.deleteAll, st::defaultBoxCheckbox), st::boxRowPadding + buttonPadding); auto messagesCounts = MessagesCountValue(history, participants); const auto controller = box->lifetime().make_state( Controller::Data{ .messagesCounts = rpl::duplicate(messagesCounts), .participants = participants, }); Ui::AddExpandablePeerList(deleteAll, controller, inner); { auto itemFromIds = items | ranges::views::transform([]( const auto &item) { return item->from()->id; }) | ranges::to_vector; rpl::combine( std::move(messagesCounts), isSingle ? deleteAll->checkedValue() : rpl::merge( controller->toggleRequestsFromInner.events(), controller->checkAllRequests.events()) ) | rpl::map([=](const auto &map, bool c) { const auto checked = (isSingle && !c) ? Participants() : controller->collectRequests ? controller->collectRequests() : Participants(); auto result = 0; for (const auto &[peerId, count] : map) { for (const auto &peer : checked) { if (peer->id == peerId) { result += count; break; } } } for (const auto &fromId : itemFromIds) { for (const auto &peer : checked) { if (peer->id == fromId) { result--; break; } } result++; } return float64(result); }) | rpl::on_next([=](int amount) { auto text = tr::lng_selected_delete_sure( tr::now, lt_count, float64(amount)); if (amount > 0) { title->setText(std::move(text)); } else { const auto zeroIndex = text.indexOf('0'); if (zeroIndex != -1) { auto descriptor = Lottie::IconDescriptor{ .name = u"transcribe_loading"_q, .color = &st::attentionButtonFg, // Any contrast. .sizeOverride = Size( st::historyTranscribeLoadingSize), .colorizeUsingAlpha = true, }; auto result = TextWithEntities() .append(text.mid(0, zeroIndex)) .append(Ui::Text::LottieEmoji(descriptor)) .append(text.mid(zeroIndex + 1)); using namespace Ui::Text; title->setMarkedText( std::move(result), LottieEmojiContext(std::move(descriptor))); } else { title->setText(std::move(text)); } } title->resizeToWidth(inner->width() - rect::m::sum::h(st::boxRowPadding)); }, title->lifetime()); } handleSubmition(deleteAll); handleConfirmation(deleteAll, controller, [=]( not_null p, not_null c) { p->session().api().deleteAllFromParticipant(c, p); }); } if (allCanBan) { const auto peer = items.front()->history()->peer; auto ownedWrap = peer->isMonoforum() ? nullptr : object_ptr>( inner, object_ptr(inner)); auto computeRestrictions = Fn(); const auto wrap = ownedWrap.data(); Ui::AddSkip(inner); Ui::AddSkip(inner); const auto ban = inner->add( object_ptr( box, rpl::conditional( (ownedWrap ? ownedWrap->toggledValue() : rpl::single(false) | rpl::type_erased), tr::lng_restrict_user( lt_count, rpl::single(participants.size()) | tr::to_count()), rpl::conditional( rpl::single(isSingle), tr::lng_ban_user(), tr::lng_ban_users())), options.banUser, st::defaultBoxCheckbox), st::boxRowPadding + buttonPadding); const auto controller = box->lifetime().make_state( Controller::Data{ .participants = participants }); Ui::AddExpandablePeerList(ban, controller, inner); handleSubmition(ban); Ui::AddSkip(inner); Ui::AddSkip(inner); if (ownedWrap) { inner->add(std::move(ownedWrap)); const auto container = wrap->entity(); wrap->toggle(false, anim::type::instant); const auto emojiUp = Ui::Text::IconEmoji( &st::moderateBoxExpandIcon); const auto emojiDown = Ui::Text::IconEmoji( &st::moderateBoxExpandIconDown); auto label = object_ptr( inner, QString(), st::moderateBoxDividerLabel); const auto raw = label.data(); auto &lifetime = wrap->lifetime(); const auto scrollLifetime = lifetime.make_state(); label->setClickHandlerFilter([=]( const ClickHandlerPtr &handler, Qt::MouseButton button) { if (button != Qt::LeftButton) { return false; } wrap->toggle(!wrap->toggled(), anim::type::normal); { inner->heightValue() | rpl::on_next([=] { if (!wrap->animating()) { scrollLifetime->destroy(); Ui::PostponeCall(crl::guard(box, [=] { box->scrollToY(std::numeric_limits::max()); })); } else { box->scrollToY(std::numeric_limits::max()); } }, *scrollLifetime); } return true; }); wrap->toggledValue( ) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) { return ((toggled && isSingle) ? tr::lng_restrict_user_part : (toggled && !isSingle) ? tr::lng_restrict_users_part : isSingle ? tr::lng_restrict_user_full : tr::lng_restrict_users_full)( lt_emoji, rpl::single(toggled ? emojiUp : emojiDown), tr::marked); }) | rpl::flatten_latest( ) | rpl::on_next([=](const TextWithEntities &text) { raw->setMarkedText(tr::link(text, u"internal:"_q)); }, label->lifetime()); Ui::AddSkip(inner); inner->add(object_ptr( inner, std::move(label), st::defaultBoxDividerLabelPadding)); using Flag = ChatRestriction; using Flags = ChatRestrictions; const auto chat = peer->asChat(); const auto channel = peer->asChannel(); const auto defaultRestrictions = chat ? chat->defaultRestrictions() : channel->defaultRestrictions(); const auto prepareFlags = FixDependentRestrictions( defaultRestrictions | ((channel && channel->isPublic()) ? (Flag::ChangeInfo | Flag::PinMessages) : Flags(0))); const auto disabledMessages = [&] { auto result = base::flat_map(); { const auto disabled = FixDependentRestrictions( defaultRestrictions | ((channel && channel->isPublic()) ? (Flag::ChangeInfo | Flag::PinMessages) : Flags(0))); result.emplace( disabled, tr::lng_rights_restriction_for_all(tr::now)); } return result; }(); auto [checkboxes, getRestrictions, changes, highlightWidget] = CreateEditRestrictions( box, prepareFlags, disabledMessages, { .isForum = peer->isForum(), .isUserSpecific = true }); computeRestrictions = getRestrictions; std::move(changes) | rpl::on_next([=] { ban->setChecked(true); }, ban->lifetime()); Ui::AddSkip(container); Ui::AddDivider(container); Ui::AddSkip(container); Ui::AddSubsectionTitle( container, rpl::conditional( rpl::single(isSingle), tr::lng_restrict_users_part_single_header(), tr::lng_restrict_users_part_header( lt_count, rpl::single(participants.size()) | tr::to_count()))); container->add(std::move(checkboxes)); } // Handle confirmation manually. confirms->events() | rpl::on_next([=] { if (ban->checked() && controller->collectRequests) { const auto kick = !wrap || !wrap->toggled(); const auto restrictions = computeRestrictions ? computeRestrictions() : ChatRestrictions(); const auto request = [=]( not_null peer, not_null channel) { if (base::IsAltPressed() || base::IsCtrlPressed()) { return; } if (!kick) { Api::ChatParticipants::Restrict( channel, peer, ChatRestrictionsInfo(), // Unused. ChatRestrictionsInfo(restrictions, 0), nullptr, nullptr); } else { const auto block = channel->isMonoforum() ? channel->monoforumBroadcast() : channel.get(); if (block) { block->session().api().chatParticipants().kick( block, peer, { block->restrictions(), 0 }); } } }; if (collectCommon && !collectCommon->empty()) { sequentiallyRequest( request, controller->collectRequests(), *collectCommon); } else { sequentiallyRequest( request, controller->collectRequests()); } } }, ban->lifetime()); } const auto close = crl::guard(box, [=] { box->closeBox(); }); { const auto data = &participants.front()->session().data(); const auto ids = data->itemsToIds(items); box->addButton(tr::lng_box_delete(), [=] { confirms->fire({}); if (confirmed) { confirmed(); } data->histories().deleteMessages(ids, true); data->sendHistoryChangeNotifications(); close(); }); } box->addButton(tr::lng_cancel(), close); } bool CanCreateModerateMessagesBox(const HistoryItemsList &items) { const auto options = CalculateModerateOptions(items); return (options.allCanBan || options.allCanDelete) && !options.participants.empty(); } void SafeSubmitOnEnter(not_null box) { base::install_event_filter(box, [=](not_null event) { if (event->type() == QEvent::KeyPress) { if (const auto k = static_cast(event.get())) { if ((k->key() == Qt::Key_Enter) || (k->key() == Qt::Key_Return)) { box->uiShow()->show(Ui::MakeConfirmBox({ .text = tr::lng_gigagroup_warning_title(), .confirmed = [=](Fn close) { box->triggerButton(0); close(); }, .confirmText = tr::lng_box_yes(), .cancelText = tr::lng_box_no(), })); } } } return base::EventFilterResult::Continue; }); } void DeleteChatBox(not_null box, not_null peer) { const auto container = box->verticalLayout(); const auto userpicPeer = peer->userpicPaintingPeer(); const auto maybeUser = peer->asUser(); const auto isBot = maybeUser && maybeUser->isBot(); Ui::AddSkip(container); Ui::AddSkip(container); SafeSubmitOnEnter(box); const auto userpic = Ui::CreateChild( container, userpicPeer, st::mainMenuUserpic, peer->userpicShape()); userpic->showSavedMessagesOnSelf(true); Ui::IconWithTitle( container, userpic, Ui::CreateChild( container, peer->isSelf() ? tr::lng_saved_messages(tr::bold) : maybeUser ? tr::lng_profile_delete_conversation(tr::bold) : rpl::single( tr::bold(userpicPeer->name()) ) | rpl::type_erased, box->getDelegate()->style().title)); Ui::AddSkip(container); Ui::AddSkip(container); box->addRow( object_ptr( container, peer->isSelf() ? tr::lng_sure_delete_saved_messages() : maybeUser ? tr::lng_sure_delete_history( lt_contact, rpl::single(peer->name())) : (peer->isChannel() && !peer->isMegagroup()) ? tr::lng_sure_leave_channel() : tr::lng_sure_leave_group(), st::boxLabel)); const auto maybeCheckbox = [&]() -> Ui::Checkbox* { if (!peer->canRevokeFullHistory()) { return nullptr; } Ui::AddSkip(container); Ui::AddSkip(container); return box->addRow( object_ptr( container, maybeUser ? tr::lng_delete_for_other_check( tr::now, lt_user, TextWithEntities{ maybeUser->firstName }, tr::rich) : tr::lng_delete_for_everyone_check( tr::now, tr::marked), false, st::defaultBoxCheckbox)); }(); const auto maybeBotCheckbox = [&]() -> Ui::Checkbox* { if (!isBot) { return nullptr; } Ui::AddSkip(container); Ui::AddSkip(container); return box->addRow( object_ptr( container, tr::lng_profile_block_bot(tr::now, tr::marked), false, st::defaultBoxCheckbox)); }(); const auto removeFromChatsFilters = [=]( not_null history) -> std::vector { auto result = std::vector(); for (const auto &filter : peer->owner().chatsFilters().list()) { if (filter.withoutAlways(history) != filter) { result.push_back(filter.id()); } } return result; }; const auto maybeChatsFiltersCheckbox = [&]() -> Ui::Checkbox* { const auto history = (isBot || !maybeUser) ? peer->owner().history(peer).get() : nullptr; if (!history || removeFromChatsFilters(history).empty()) { return nullptr; } Ui::AddSkip(container); Ui::AddSkip(container); return box->addRow( object_ptr( container, (maybeBotCheckbox ? tr::lng_filters_checkbox_remove_bot : (peer->isChannel() && !peer->isMegagroup()) ? tr::lng_filters_checkbox_remove_channel : tr::lng_filters_checkbox_remove_group)( tr::now, tr::marked), false, st::defaultBoxCheckbox)); }(); Ui::AddSkip(container); auto buttonText = maybeUser ? tr::lng_box_delete() : !maybeCheckbox ? tr::lng_box_leave() : maybeCheckbox->checkedValue() | rpl::map([](bool checked) { return checked ? tr::lng_box_delete() : tr::lng_box_leave(); }) | rpl::flatten_latest(); const auto close = crl::guard(box, [=] { box->closeBox(); }); box->addButton(std::move(buttonText), [=] { const auto revoke = maybeCheckbox && maybeCheckbox->checked(); const auto stopBot = maybeBotCheckbox && maybeBotCheckbox->checked(); const auto removeFromChats = maybeChatsFiltersCheckbox && maybeChatsFiltersCheckbox->checked(); Core::App().closeChatFromWindows(peer); if (stopBot) { peer->session().api().blockedPeers().block(peer); } if (removeFromChats) { const auto history = peer->owner().history(peer).get(); const auto removeFrom = removeFromChatsFilters(history); for (const auto &filter : peer->owner().chatsFilters().list()) { if (!ranges::contains(removeFrom, filter.id())) { continue; } const auto result = filter.withoutAlways(history); if (result == filter) { continue; } const auto tl = result.tl(); peer->owner().chatsFilters().apply(MTP_updateDialogFilter( MTP_flags(MTPDupdateDialogFilter::Flag::f_filter), MTP_int(filter.id()), tl)); peer->session().api().request(MTPmessages_UpdateDialogFilter( MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), MTP_int(filter.id()), tl )).send(); } } // Don't delete old history by default, // because Android app doesn't. // //if (const auto from = peer->migrateFrom()) { // peer->session().api().deleteConversation(from, false); //} peer->session().api().deleteConversation(peer, revoke); close(); }, st::attentionBoxButton); box->addButton(tr::lng_cancel(), close); } void DeleteSublistBox( not_null box, not_null sublist) { const auto container = box->verticalLayout(); const auto weak = base::make_weak(sublist.get()); const auto peer = sublist->sublistPeer(); Ui::AddSkip(container); Ui::AddSkip(container); SafeSubmitOnEnter(box); const auto userpic = Ui::CreateChild( container, peer, st::mainMenuUserpic); Ui::IconWithTitle( container, userpic, Ui::CreateChild( container, tr::lng_profile_delete_conversation(tr::bold), box->getDelegate()->style().title)); Ui::AddSkip(container); Ui::AddSkip(container); box->addRow( object_ptr( container, tr::lng_sure_delete_history( lt_contact, rpl::single(peer->name())), st::boxLabel)); Ui::AddSkip(container); const auto close = crl::guard(box, [=] { box->closeBox(); }); box->addButton(tr::lng_box_delete(), [=] { const auto strong = weak.get(); const auto parentChat = strong ? strong->parentChat() : nullptr; if (!parentChat) { return; } peer->session().api().deleteSublistHistory(parentChat, peer); close(); }, st::attentionBoxButton); box->addButton(tr::lng_cancel(), close); } ModerateMessagesBoxOptions DefaultModerateMessagesBoxOptions() { return base::IsCtrlPressed() ? ModerateMessagesBoxOptions{ .reportSpam = true, .deleteAll = true, .banUser = true, } : ModerateMessagesBoxOptions{}; }