tdesktop/Telegram/SourceFiles/boxes/delete_messages_box.cpp

543 lines
15 KiB
C++

/*
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/delete_messages_box.h"
#include "apiwrap.h"
#include "api/api_chat_participants.h"
#include "api/api_messages_search.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_histories.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "menu/menu_ttl_validator.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<HistoryItem*> item,
bool suggestModerateActions)
: _session(&item->history()->session())
, _ids(1, item->fullId()) {
if (suggestModerateActions) {
_moderateBan = item->suggestBanReport();
_moderateDeleteAll = item->suggestDeleteAllReport();
if (_moderateBan || _moderateDeleteAll) {
_moderateFrom = item->from();
_moderateInChannel = item->history()->peer->asChannel();
}
}
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<Main::Session*> session,
MessageIdsList &&selected)
: _session(session)
, _ids(std::move(selected)) {
Expects(!_ids.empty());
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<PeerData*> peer,
QDate firstDayToDelete,
QDate lastDayToDelete)
: _session(&peer->session())
, _wipeHistoryPeer(peer)
, _wipeHistoryJustClear(true)
, _wipeHistoryFirstToDelete(firstDayToDelete)
, _wipeHistoryLastToDelete(lastDayToDelete) {
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<PeerData*> peer,
bool justClear)
: _session(&peer->session())
, _wipeHistoryPeer(peer)
, _wipeHistoryJustClear(justClear) {
}
void DeleteMessagesBox::prepare() {
auto details = TextWithEntities();
const auto appendDetails = [&](TextWithEntities &&text) {
details.append(u"\n\n"_q).append(std::move(text));
};
auto deleteText = lifetime().make_state<rpl::variable<QString>>();
*deleteText = tr::lng_box_delete();
auto deleteStyle = &st::defaultBoxButton;
auto canDelete = true;
if (const auto peer = _wipeHistoryPeer) {
if (!_wipeHistoryFirstToDelete.isNull()) {
details = (_wipeHistoryFirstToDelete
== _wipeHistoryLastToDelete)
? tr::lng_sure_delete_by_date_one(
tr::now,
lt_date,
TextWithEntities{
langDayOfMonthFull(_wipeHistoryFirstToDelete) },
Ui::Text::RichLangValue)
: tr::lng_sure_delete_by_date_many(
tr::now,
lt_days,
tr::lng_sure_delete_selected_days(
tr::now,
lt_count,
_wipeHistoryFirstToDelete.daysTo(
_wipeHistoryLastToDelete) + 1,
Ui::Text::WithEntities),
Ui::Text::RichLangValue);
deleteStyle = &st::attentionBoxButton;
} else if (_wipeHistoryJustClear) {
const auto isChannel = peer->isBroadcast();
const auto isPublicGroup = peer->isMegagroup()
&& peer->asChannel()->isPublic();
if (isChannel || isPublicGroup) {
canDelete = false;
}
details.text = isChannel
? tr::lng_no_clear_history_channel(tr::now)
: isPublicGroup
? tr::lng_no_clear_history_group(tr::now)
: peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
? tr::lng_sure_delete_history(
tr::now,
lt_contact,
peer->name())
: tr::lng_sure_delete_group_history(
tr::now,
lt_group,
peer->name());
details = Ui::Text::RichLangValue(details.text);
deleteStyle = &st::attentionBoxButton;
} else {
details.text = peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
? tr::lng_sure_delete_history(
tr::now,
lt_contact,
peer->name())
: peer->isChat()
? tr::lng_sure_delete_and_exit(
tr::now,
lt_group,
peer->name())
: peer->isMegagroup()
? tr::lng_sure_leave_group(tr::now)
: tr::lng_sure_leave_channel(tr::now);
details = Ui::Text::RichLangValue(details.text);
if (!peer->isUser()) {
*deleteText = tr::lng_box_leave();
}
deleteStyle = &st::attentionBoxButton;
}
if (auto revoke = revokeText(peer)) {
_revoke.create(
this,
revoke->checkbox,
false,
st::defaultBoxCheckbox);
appendDetails(std::move(revoke->description));
if (!peer->isUser() && !_wipeHistoryJustClear) {
_revoke->checkedValue(
) | rpl::start_with_next([=](bool revokeForAll) {
*deleteText = revokeForAll
? tr::lng_box_delete()
: tr::lng_box_leave();
}, _revoke->lifetime());
}
} else if (canDelete
&& _wipeHistoryJustClear
&& (peer->isMegagroup() || peer->isChat())) {
appendDetails({
tr::lng_delete_clear_for_me(tr::now)
});
}
} else if (_moderateFrom) {
Assert(_moderateInChannel != nullptr);
details.text = tr::lng_selected_delete_sure_this(tr::now);
if (_moderateBan) {
_banUser.create(
this,
tr::lng_ban_user(tr::now),
false,
st::defaultBoxCheckbox);
}
_reportSpam.create(
this,
tr::lng_report_spam(tr::now),
false,
st::defaultBoxCheckbox);
if (_moderateDeleteAll) {
const auto search = lifetime().make_state<Api::MessagesSearch>(
_session->data().message(_ids.front())->history());
_deleteAll.create(
this,
tr::lng_delete_all_from_user(
tr::now,
lt_user,
Ui::Text::Bold(_moderateFrom->name()),
Ui::Text::WithEntities),
false,
st::defaultBoxCheckbox);
*deleteText = rpl::combine(
rpl::single(
0
) | rpl::then(
search->messagesFounds(
) | rpl::map([](const Api::FoundMessages &found) {
return found.total;
})
),
_deleteAll->checkedValue()
) | rpl::map([](int total, bool checked) {
return tr::lng_box_delete(tr::now)
+ ((total <= 0 || !checked)
? QString()
: QString(" (%1)").arg(total));
});
search->searchMessages(QString(), _moderateFrom);
}
} else {
details.text = (_ids.size() == 1)
? tr::lng_selected_delete_sure_this(tr::now)
: tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
if (const auto peer = checkFromSinglePeer()) {
auto count = int(_ids.size());
if (hasScheduledMessages()) {
} else if (auto revoke = revokeText(peer)) {
_revoke.create(
this,
revoke->checkbox,
true,
st::defaultBoxCheckbox);
appendDetails(std::move(revoke->description));
} else if (peer->isChannel()) {
if (peer->isMegagroup()) {
appendDetails({
tr::lng_delete_for_everyone_hint(
tr::now,
lt_count,
count)
});
}
} else if (peer->isChat()) {
appendDetails({
tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
});
} else if (!peer->isSelf()) {
if (const auto user = peer->asUser(); user && user->isBot()) {
_revokeForBot = true;
}
appendDetails({
tr::lng_delete_for_me_hint(tr::now, lt_count, count)
});
}
}
}
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
if (_wipeHistoryJustClear && _wipeHistoryPeer) {
const auto validator = TTLMenu::TTLValidator(
std::make_shared<Ui::BoxShow>(this),
_wipeHistoryPeer);
if (validator.can()) {
_wipeHistoryPeer->updateFull();
_autoDeleteSettings.create(
this,
(_wipeHistoryPeer->messagesTTL()
? tr::lng_edit_auto_delete_settings(tr::now)
: tr::lng_enable_auto_delete(tr::now)),
st::boxLinkButton);
_autoDeleteSettings->setClickedCallback([=] {
validator.showBox();
});
}
}
if (canDelete) {
addButton(
deleteText->value(),
[=] { deleteAndClear(); },
*deleteStyle);
addButton(tr::lng_cancel(), [=] { closeBox(); });
} else {
addButton(tr::lng_about_done(), [=] { closeBox(); });
}
auto fullHeight = st::boxPadding.top()
+ _text->height()
+ st::boxPadding.bottom();
if (_moderateFrom) {
fullHeight += st::boxMediumSkip;
if (_banUser) {
fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
}
fullHeight += _reportSpam->heightNoMargins();
if (_deleteAll) {
fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
}
} else if (_revoke) {
fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
}
if (_autoDeleteSettings) {
fullHeight += st::boxMediumSkip
+ _autoDeleteSettings->height()
+ st::boxLittleSkip;
}
setDimensions(st::boxWidth, fullHeight);
}
bool DeleteMessagesBox::hasScheduledMessages() const {
for (const auto &fullId : _ids) {
if (const auto item = _session->data().message(fullId)) {
if (item->isScheduled()) {
return true;
}
}
}
return false;
}
PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
auto result = (PeerData*)nullptr;
for (const auto &fullId : _ids) {
if (const auto item = _session->data().message(fullId)) {
const auto peer = item->history()->peer;
if (!result) {
result = peer;
} else if (result != peer) {
return nullptr;
}
}
}
return result;
}
auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
-> std::optional<RevokeConfig> {
auto result = RevokeConfig();
if (peer == _wipeHistoryPeer) {
if (!peer->canRevokeFullHistory()) {
return std::nullopt;
} else if (const auto user = peer->asUser()) {
result.checkbox = tr::lng_delete_for_other_check(
tr::now,
lt_user,
{ user->firstName },
Ui::Text::RichLangValue);
} else {
result.checkbox.text = tr::lng_delete_for_everyone_check(tr::now);
}
return result;
}
const auto items = ranges::views::all(
_ids
) | ranges::views::transform([&](FullMsgId id) {
return peer->owner().message(id);
}) | ranges::views::filter([](HistoryItem *item) {
return (item != nullptr);
}) | ranges::to_vector;
if (items.size() != _ids.size()) {
// We don't have information about all messages.
return std::nullopt;
}
const auto now = base::unixtime::now();
const auto canRevoke = [&](HistoryItem * item) {
return item->canDeleteForEveryone(now);
};
const auto cannotRevoke = [&](HistoryItem *item) {
return !item->canDeleteForEveryone(now);
};
const auto canRevokeAll = ranges::none_of(items, cannotRevoke);
auto outgoing = items | ranges::views::filter(&HistoryItem::out);
const auto canRevokeOutgoingCount = canRevokeAll
? -1
: ranges::count_if(outgoing, canRevoke);
if (canRevokeAll) {
if (const auto user = peer->asUser()) {
result.checkbox = tr::lng_delete_for_other_check(
tr::now,
lt_user,
{ user->firstName },
Ui::Text::RichLangValue);
} else {
result.checkbox.text = tr::lng_delete_for_everyone_check(tr::now);
}
return result;
} else if (canRevokeOutgoingCount > 0) {
result.checkbox.text = tr::lng_delete_for_other_my(tr::now);
if (const auto user = peer->asUser()) {
if (canRevokeOutgoingCount == 1) {
result.description = tr::lng_selected_unsend_about_user_one(
tr::now,
lt_user,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities);
} else {
result.description = tr::lng_selected_unsend_about_user(
tr::now,
lt_count,
canRevokeOutgoingCount,
lt_user,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities);
}
} else if (canRevokeOutgoingCount == 1) {
result.description = tr::lng_selected_unsend_about_group_one(
tr::now,
Ui::Text::WithEntities);
} else {
result.description = tr::lng_selected_unsend_about_group(
tr::now,
lt_count,
canRevokeOutgoingCount,
Ui::Text::WithEntities);
}
return result;
}
return std::nullopt;
}
void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
const auto &padding = st::boxPadding;
_text->moveToLeft(padding.left(), padding.top());
auto top = _text->bottomNoMargins() + st::boxMediumSkip;
if (_moderateFrom) {
if (_banUser) {
_banUser->moveToLeft(padding.left(), top);
top += _banUser->heightNoMargins() + st::boxLittleSkip;
}
_reportSpam->moveToLeft(padding.left(), top);
top += _reportSpam->heightNoMargins() + st::boxLittleSkip;
if (_deleteAll) {
const auto availableWidth = width() - 2 * padding.left();
_deleteAll->resizeToNaturalWidth(availableWidth);
_deleteAll->moveToLeft(padding.left(), top);
top += _deleteAll->heightNoMargins() + st::boxLittleSkip;
}
} else if (_revoke) {
const auto availableWidth = width() - 2 * padding.left();
_revoke->resizeToNaturalWidth(availableWidth);
_revoke->moveToLeft(padding.left(), top);
top += _revoke->heightNoMargins() + st::boxLittleSkip;
}
if (_autoDeleteSettings) {
top += st::boxMediumSkip - st::boxLittleSkip;
_autoDeleteSettings->moveToLeft(padding.left(), top);
}
}
void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
// Don't make the clearing history so easy.
if (!_wipeHistoryPeer) {
deleteAndClear();
}
} else {
BoxContent::keyPressEvent(e);
}
}
void DeleteMessagesBox::deleteAndClear() {
const auto revoke = _revoke ? _revoke->checked() : _revokeForBot;
const auto session = _session;
const auto invokeCallbackAndClose = [&] {
// deleteMessages can initiate closing of the current section,
// which will cause this box to be destroyed.
const auto weak = Ui::MakeWeak(this);
if (const auto callback = _deleteConfirmedCallback) {
callback();
}
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
if (!_wipeHistoryFirstToDelete.isNull()) {
const auto peer = _wipeHistoryPeer;
const auto firstDayToDelete = _wipeHistoryFirstToDelete;
const auto lastDayToDelete = _wipeHistoryLastToDelete;
invokeCallbackAndClose();
session->data().histories().deleteMessagesByDates(
session->data().history(peer),
firstDayToDelete,
lastDayToDelete,
revoke);
session->data().sendHistoryChangeNotifications();
return;
} else if (const auto peer = _wipeHistoryPeer) {
const auto justClear = _wipeHistoryJustClear;
invokeCallbackAndClose();
if (justClear) {
session->api().clearHistory(peer, revoke);
} else {
Core::App().closeChatFromWindows(peer);
// Don't delete old history by default,
// because Android app doesn't.
//
//if (const auto from = peer->migrateFrom()) {
// peer->session().api().deleteConversation(from, false);
//}
session->api().deleteConversation(peer, revoke);
}
return;
}
if (_moderateFrom) {
if (_banUser && _banUser->checked()) {
_moderateInChannel->session().api().chatParticipants().kick(
_moderateInChannel,
_moderateFrom,
ChatRestrictionsInfo());
}
if (_reportSpam->checked()) {
_moderateInChannel->session().api().request(
MTPchannels_ReportSpam(
_moderateInChannel->inputChannel,
_moderateFrom->input,
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))
).send();
}
if (_deleteAll && _deleteAll->checked()) {
_moderateInChannel->session().api().deleteAllFromParticipant(
_moderateInChannel,
_moderateFrom);
}
}
const auto ids = _ids;
invokeCallbackAndClose();
session->data().histories().deleteMessages(ids, revoke);
session->data().sendHistoryChangeNotifications();
}