delete_messages_box.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/delete_messages_box.h"
  8. #include "apiwrap.h"
  9. #include "api/api_chat_participants.h"
  10. #include "api/api_messages_search.h"
  11. #include "base/unixtime.h"
  12. #include "core/application.h"
  13. #include "core/core_settings.h"
  14. #include "data/data_channel.h"
  15. #include "data/data_chat.h"
  16. #include "data/data_histories.h"
  17. #include "data/data_session.h"
  18. #include "data/data_user.h"
  19. #include "history/history.h"
  20. #include "history/history_item.h"
  21. #include "lang/lang_keys.h"
  22. #include "main/main_session.h"
  23. #include "menu/menu_ttl_validator.h"
  24. #include "ui/layers/generic_box.h"
  25. #include "ui/text/text_utilities.h"
  26. #include "ui/widgets/buttons.h"
  27. #include "ui/widgets/checkbox.h"
  28. #include "ui/widgets/labels.h"
  29. #include "ui/wrap/slide_wrap.h"
  30. #include "styles/style_layers.h"
  31. #include "styles/style_boxes.h"
  32. DeleteMessagesBox::DeleteMessagesBox(
  33. QWidget*,
  34. not_null<HistoryItem*> item,
  35. bool suggestModerateActions)
  36. : _session(&item->history()->session())
  37. , _ids(1, item->fullId()) {
  38. if (suggestModerateActions) {
  39. _moderateBan = item->suggestBanReport();
  40. _moderateDeleteAll = item->suggestDeleteAllReport();
  41. if (_moderateBan || _moderateDeleteAll) {
  42. _moderateFrom = item->from();
  43. _moderateInChannel = item->history()->peer->asChannel();
  44. }
  45. }
  46. }
  47. DeleteMessagesBox::DeleteMessagesBox(
  48. QWidget*,
  49. not_null<Main::Session*> session,
  50. MessageIdsList &&selected)
  51. : _session(session)
  52. , _ids(std::move(selected)) {
  53. Expects(!_ids.empty());
  54. }
  55. DeleteMessagesBox::DeleteMessagesBox(
  56. QWidget*,
  57. not_null<PeerData*> peer,
  58. QDate firstDayToDelete,
  59. QDate lastDayToDelete)
  60. : _session(&peer->session())
  61. , _wipeHistoryPeer(peer)
  62. , _wipeHistoryJustClear(true)
  63. , _wipeHistoryFirstToDelete(firstDayToDelete)
  64. , _wipeHistoryLastToDelete(lastDayToDelete) {
  65. }
  66. DeleteMessagesBox::DeleteMessagesBox(
  67. QWidget*,
  68. not_null<PeerData*> peer,
  69. bool justClear)
  70. : _session(&peer->session())
  71. , _wipeHistoryPeer(peer)
  72. , _wipeHistoryJustClear(justClear) {
  73. }
  74. void DeleteMessagesBox::prepare() {
  75. auto details = TextWithEntities();
  76. const auto appendDetails = [&](TextWithEntities &&text) {
  77. details.append(u"\n\n"_q).append(std::move(text));
  78. };
  79. auto deleteText = lifetime().make_state<rpl::variable<QString>>();
  80. *deleteText = tr::lng_box_delete();
  81. auto deleteStyle = &st::defaultBoxButton;
  82. auto canDelete = true;
  83. if (const auto peer = _wipeHistoryPeer) {
  84. if (!_wipeHistoryFirstToDelete.isNull()) {
  85. details = (_wipeHistoryFirstToDelete
  86. == _wipeHistoryLastToDelete)
  87. ? tr::lng_sure_delete_by_date_one(
  88. tr::now,
  89. lt_date,
  90. TextWithEntities{
  91. langDayOfMonthFull(_wipeHistoryFirstToDelete) },
  92. Ui::Text::RichLangValue)
  93. : tr::lng_sure_delete_by_date_many(
  94. tr::now,
  95. lt_days,
  96. tr::lng_sure_delete_selected_days(
  97. tr::now,
  98. lt_count,
  99. _wipeHistoryFirstToDelete.daysTo(
  100. _wipeHistoryLastToDelete) + 1,
  101. Ui::Text::WithEntities),
  102. Ui::Text::RichLangValue);
  103. deleteStyle = &st::attentionBoxButton;
  104. } else if (_wipeHistoryJustClear) {
  105. const auto isChannel = peer->isBroadcast();
  106. const auto isPublicGroup = peer->isMegagroup()
  107. && peer->asChannel()->isPublic();
  108. if (isChannel || isPublicGroup) {
  109. canDelete = false;
  110. }
  111. details.text = isChannel
  112. ? tr::lng_no_clear_history_channel(tr::now)
  113. : isPublicGroup
  114. ? tr::lng_no_clear_history_group(tr::now)
  115. : peer->isSelf()
  116. ? tr::lng_sure_delete_saved_messages(tr::now)
  117. : peer->isUser()
  118. ? tr::lng_sure_delete_history(
  119. tr::now,
  120. lt_contact,
  121. peer->name())
  122. : tr::lng_sure_delete_group_history(
  123. tr::now,
  124. lt_group,
  125. peer->name());
  126. details = Ui::Text::RichLangValue(details.text);
  127. deleteStyle = &st::attentionBoxButton;
  128. } else {
  129. details.text = peer->isSelf()
  130. ? tr::lng_sure_delete_saved_messages(tr::now)
  131. : peer->isUser()
  132. ? tr::lng_sure_delete_history(
  133. tr::now,
  134. lt_contact,
  135. peer->name())
  136. : peer->isChat()
  137. ? tr::lng_sure_delete_and_exit(
  138. tr::now,
  139. lt_group,
  140. peer->name())
  141. : peer->isMegagroup()
  142. ? tr::lng_sure_leave_group(tr::now)
  143. : tr::lng_sure_leave_channel(tr::now);
  144. details = Ui::Text::RichLangValue(details.text);
  145. if (!peer->isUser()) {
  146. *deleteText = tr::lng_box_leave();
  147. }
  148. deleteStyle = &st::attentionBoxButton;
  149. }
  150. if (auto revoke = revokeText(peer)) {
  151. _revoke.create(
  152. this,
  153. revoke->checkbox,
  154. false,
  155. st::defaultBoxCheckbox);
  156. appendDetails(std::move(revoke->description));
  157. if (!peer->isUser() && !_wipeHistoryJustClear) {
  158. _revoke->checkedValue(
  159. ) | rpl::start_with_next([=](bool revokeForAll) {
  160. *deleteText = revokeForAll
  161. ? tr::lng_box_delete()
  162. : tr::lng_box_leave();
  163. }, _revoke->lifetime());
  164. }
  165. } else if (canDelete
  166. && _wipeHistoryJustClear
  167. && (peer->isMegagroup() || peer->isChat())) {
  168. appendDetails({
  169. tr::lng_delete_clear_for_me(tr::now)
  170. });
  171. }
  172. } else if (_moderateFrom) {
  173. Assert(_moderateInChannel != nullptr);
  174. details.text = tr::lng_selected_delete_sure_this(tr::now);
  175. if (_moderateBan) {
  176. _banUser.create(
  177. this,
  178. tr::lng_ban_user(tr::now),
  179. false,
  180. st::defaultBoxCheckbox);
  181. }
  182. _reportSpam.create(
  183. this,
  184. tr::lng_report_spam(tr::now),
  185. false,
  186. st::defaultBoxCheckbox);
  187. if (_moderateDeleteAll) {
  188. const auto search = lifetime().make_state<Api::MessagesSearch>(
  189. _session->data().message(_ids.front())->history());
  190. _deleteAll.create(
  191. this,
  192. tr::lng_delete_all_from_user(
  193. tr::now,
  194. lt_user,
  195. Ui::Text::Bold(_moderateFrom->name()),
  196. Ui::Text::WithEntities),
  197. false,
  198. st::defaultBoxCheckbox);
  199. *deleteText = rpl::combine(
  200. rpl::single(
  201. 0
  202. ) | rpl::then(
  203. search->messagesFounds(
  204. ) | rpl::map([](const Api::FoundMessages &found) {
  205. return found.total;
  206. })
  207. ),
  208. _deleteAll->checkedValue()
  209. ) | rpl::map([](int total, bool checked) {
  210. return tr::lng_box_delete(tr::now)
  211. + ((total <= 0 || !checked)
  212. ? QString()
  213. : QString(" (%1)").arg(total));
  214. });
  215. search->searchMessages({ .from = _moderateFrom });
  216. }
  217. } else {
  218. details.text = (_ids.size() == 1)
  219. ? tr::lng_selected_delete_sure_this(tr::now)
  220. : tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
  221. if (const auto peer = checkFromSinglePeer()) {
  222. auto count = int(_ids.size());
  223. if (hasScheduledMessages()) {
  224. } else if (auto revoke = revokeText(peer)) {
  225. const auto &settings = Core::App().settings();
  226. const auto revokeByDefault
  227. = !settings.rememberedDeleteMessageOnlyForYou();
  228. _revoke.create(
  229. this,
  230. revoke->checkbox,
  231. revokeByDefault,
  232. st::defaultBoxCheckbox);
  233. _revokeRemember.create(
  234. this,
  235. object_ptr<Ui::Checkbox>(
  236. this,
  237. tr::lng_remember(),
  238. false,
  239. st::defaultBoxCheckbox));
  240. _revokeRemember->hide(anim::type::instant);
  241. _revoke->checkedValue(
  242. ) | rpl::start_with_next([=](bool checked) {
  243. _revokeRemember->toggle(
  244. checked != revokeByDefault,
  245. anim::type::normal);
  246. }, _revokeRemember->lifetime());
  247. _revokeRemember->heightValue(
  248. ) | rpl::start_with_next([=](int h) {
  249. setDimensions(st::boxWidth, _fullHeight + h);
  250. }, lifetime());
  251. appendDetails(std::move(revoke->description));
  252. } else if (peer->isChannel()) {
  253. if (peer->isMegagroup()) {
  254. appendDetails({
  255. tr::lng_delete_for_everyone_hint(
  256. tr::now,
  257. lt_count,
  258. count)
  259. });
  260. }
  261. } else if (peer->isChat()) {
  262. appendDetails({
  263. tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
  264. });
  265. } else if (!peer->isSelf()
  266. && (!peer->isUser() || !peer->asUser()->isInaccessible())) {
  267. if (const auto user = peer->asUser(); user && user->isBot()) {
  268. _revokeForBot = true;
  269. }
  270. appendDetails({
  271. tr::lng_delete_for_me_hint(tr::now, lt_count, count)
  272. });
  273. }
  274. }
  275. }
  276. _text.create(this, rpl::single(std::move(details)), st::boxLabel);
  277. if (_wipeHistoryJustClear && _wipeHistoryPeer) {
  278. const auto validator = TTLMenu::TTLValidator(
  279. uiShow(),
  280. _wipeHistoryPeer);
  281. if (validator.can()) {
  282. _wipeHistoryPeer->updateFull();
  283. _autoDeleteSettings.create(
  284. this,
  285. (_wipeHistoryPeer->messagesTTL()
  286. ? tr::lng_edit_auto_delete_settings(tr::now)
  287. : tr::lng_enable_auto_delete(tr::now)),
  288. st::boxLinkButton);
  289. _autoDeleteSettings->setClickedCallback([=] {
  290. validator.showBox();
  291. });
  292. }
  293. }
  294. if (canDelete) {
  295. addButton(
  296. deleteText->value(),
  297. [=] { deleteAndClear(); },
  298. *deleteStyle);
  299. addButton(tr::lng_cancel(), [=] { closeBox(); });
  300. } else {
  301. addButton(tr::lng_about_done(), [=] { closeBox(); });
  302. }
  303. auto fullHeight = st::boxPadding.top()
  304. + _text->height()
  305. + st::boxPadding.bottom();
  306. if (_moderateFrom) {
  307. fullHeight += st::boxMediumSkip;
  308. if (_banUser) {
  309. fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
  310. }
  311. fullHeight += _reportSpam->heightNoMargins();
  312. if (_deleteAll) {
  313. fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
  314. }
  315. } else if (_revoke) {
  316. fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
  317. }
  318. if (_autoDeleteSettings) {
  319. fullHeight += st::boxMediumSkip
  320. + _autoDeleteSettings->height()
  321. + st::boxLittleSkip;
  322. }
  323. setDimensions(st::boxWidth, fullHeight);
  324. _fullHeight = fullHeight;
  325. }
  326. bool DeleteMessagesBox::hasScheduledMessages() const {
  327. for (const auto &fullId : _ids) {
  328. if (const auto item = _session->data().message(fullId)) {
  329. if (item->isScheduled()) {
  330. return true;
  331. }
  332. }
  333. }
  334. return false;
  335. }
  336. PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
  337. auto result = (PeerData*)nullptr;
  338. for (const auto &fullId : _ids) {
  339. if (const auto item = _session->data().message(fullId)) {
  340. const auto peer = item->history()->peer;
  341. if (!result) {
  342. result = peer;
  343. } else if (result != peer) {
  344. return nullptr;
  345. }
  346. }
  347. }
  348. return result;
  349. }
  350. auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
  351. -> std::optional<RevokeConfig> {
  352. auto result = RevokeConfig();
  353. if (peer == _wipeHistoryPeer) {
  354. if (!peer->canRevokeFullHistory()) {
  355. return std::nullopt;
  356. } else if (const auto user = peer->asUser()) {
  357. result.checkbox = tr::lng_delete_for_other_check(
  358. tr::now,
  359. lt_user,
  360. { user->firstName },
  361. Ui::Text::RichLangValue);
  362. } else {
  363. result.checkbox.text = tr::lng_delete_for_everyone_check(tr::now);
  364. }
  365. return result;
  366. }
  367. const auto items = peer->owner().idsToItems(_ids);
  368. if (items.size() != _ids.size()) {
  369. // We don't have information about all messages.
  370. return std::nullopt;
  371. }
  372. const auto now = base::unixtime::now();
  373. const auto canRevoke = [&](HistoryItem * item) {
  374. return item->canDeleteForEveryone(now);
  375. };
  376. const auto cannotRevoke = [&](HistoryItem *item) {
  377. return !item->canDeleteForEveryone(now);
  378. };
  379. const auto canRevokeAll = ranges::none_of(items, cannotRevoke);
  380. auto outgoing = items | ranges::views::filter(&HistoryItem::out);
  381. const auto canRevokeOutgoingCount = canRevokeAll
  382. ? -1
  383. : ranges::count_if(outgoing, canRevoke);
  384. if (canRevokeAll) {
  385. if (const auto user = peer->asUser()) {
  386. result.checkbox = tr::lng_delete_for_other_check(
  387. tr::now,
  388. lt_user,
  389. { user->firstName },
  390. Ui::Text::RichLangValue);
  391. } else {
  392. result.checkbox.text = tr::lng_delete_for_everyone_check(tr::now);
  393. }
  394. return result;
  395. } else if (canRevokeOutgoingCount > 0) {
  396. result.checkbox.text = tr::lng_delete_for_other_my(tr::now);
  397. if (const auto user = peer->asUser()) {
  398. if (canRevokeOutgoingCount == 1) {
  399. result.description = tr::lng_selected_unsend_about_user_one(
  400. tr::now,
  401. lt_user,
  402. Ui::Text::Bold(user->shortName()),
  403. Ui::Text::WithEntities);
  404. } else {
  405. result.description = tr::lng_selected_unsend_about_user(
  406. tr::now,
  407. lt_count,
  408. canRevokeOutgoingCount,
  409. lt_user,
  410. Ui::Text::Bold(user->shortName()),
  411. Ui::Text::WithEntities);
  412. }
  413. } else if (canRevokeOutgoingCount == 1) {
  414. result.description = tr::lng_selected_unsend_about_group_one(
  415. tr::now,
  416. Ui::Text::WithEntities);
  417. } else {
  418. result.description = tr::lng_selected_unsend_about_group(
  419. tr::now,
  420. lt_count,
  421. canRevokeOutgoingCount,
  422. Ui::Text::WithEntities);
  423. }
  424. return result;
  425. }
  426. return std::nullopt;
  427. }
  428. void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
  429. BoxContent::resizeEvent(e);
  430. const auto &padding = st::boxPadding;
  431. _text->moveToLeft(padding.left(), padding.top());
  432. auto top = _text->bottomNoMargins() + st::boxMediumSkip;
  433. if (_moderateFrom) {
  434. if (_banUser) {
  435. _banUser->moveToLeft(padding.left(), top);
  436. top += _banUser->heightNoMargins() + st::boxLittleSkip;
  437. }
  438. _reportSpam->moveToLeft(padding.left(), top);
  439. top += _reportSpam->heightNoMargins() + st::boxLittleSkip;
  440. if (_deleteAll) {
  441. const auto availableWidth = width() - 2 * padding.left();
  442. _deleteAll->resizeToNaturalWidth(availableWidth);
  443. _deleteAll->moveToLeft(padding.left(), top);
  444. top += _deleteAll->heightNoMargins() + st::boxLittleSkip;
  445. }
  446. } else if (_revoke) {
  447. const auto availableWidth = width() - 2 * padding.left();
  448. _revoke->resizeToNaturalWidth(availableWidth);
  449. _revoke->moveToLeft(padding.left(), top);
  450. top += _revoke->heightNoMargins() + st::boxLittleSkip;
  451. if (_revokeRemember) {
  452. _revokeRemember->resizeToNaturalWidth(availableWidth);
  453. _revokeRemember->moveToLeft(padding.left(),top);
  454. top += _revokeRemember->heightNoMargins();
  455. }
  456. }
  457. if (_autoDeleteSettings) {
  458. top += st::boxMediumSkip - st::boxLittleSkip;
  459. _autoDeleteSettings->moveToLeft(padding.left(), top);
  460. }
  461. }
  462. void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
  463. if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
  464. // Don't make the clearing history so easy.
  465. if (!_wipeHistoryPeer) {
  466. deleteAndClear();
  467. }
  468. } else {
  469. BoxContent::keyPressEvent(e);
  470. }
  471. }
  472. void DeleteMessagesBox::deleteAndClear() {
  473. if (_revoke
  474. && _revokeRemember
  475. && _revokeRemember->toggled()
  476. && _revokeRemember->entity()->checked()) {
  477. Core::App().settings().setRememberedDeleteMessageOnlyForYou(
  478. !_revoke->checked());
  479. Core::App().saveSettingsDelayed();
  480. }
  481. const auto revoke = _revoke ? _revoke->checked() : _revokeForBot;
  482. const auto session = _session;
  483. const auto invokeCallbackAndClose = [&] {
  484. // deleteMessages can initiate closing of the current section,
  485. // which will cause this box to be destroyed.
  486. const auto weak = Ui::MakeWeak(this);
  487. if (const auto callback = _deleteConfirmedCallback) {
  488. callback();
  489. }
  490. if (const auto strong = weak.data()) {
  491. strong->closeBox();
  492. }
  493. };
  494. if (!_wipeHistoryFirstToDelete.isNull()) {
  495. const auto peer = _wipeHistoryPeer;
  496. const auto firstDayToDelete = _wipeHistoryFirstToDelete;
  497. const auto lastDayToDelete = _wipeHistoryLastToDelete;
  498. invokeCallbackAndClose();
  499. session->data().histories().deleteMessagesByDates(
  500. session->data().history(peer),
  501. firstDayToDelete,
  502. lastDayToDelete,
  503. revoke);
  504. session->data().sendHistoryChangeNotifications();
  505. return;
  506. } else if (const auto peer = _wipeHistoryPeer) {
  507. const auto justClear = _wipeHistoryJustClear;
  508. invokeCallbackAndClose();
  509. if (justClear) {
  510. session->api().clearHistory(peer, revoke);
  511. } else {
  512. Core::App().closeChatFromWindows(peer);
  513. // Don't delete old history by default,
  514. // because Android app doesn't.
  515. //
  516. //if (const auto from = peer->migrateFrom()) {
  517. // peer->session().api().deleteConversation(from, false);
  518. //}
  519. session->api().deleteConversation(peer, revoke);
  520. }
  521. return;
  522. }
  523. if (_moderateFrom) {
  524. if (_banUser && _banUser->checked()) {
  525. _moderateInChannel->session().api().chatParticipants().kick(
  526. _moderateInChannel,
  527. _moderateFrom,
  528. ChatRestrictionsInfo());
  529. }
  530. if (_reportSpam->checked()) {
  531. _moderateInChannel->session().api().request(
  532. MTPchannels_ReportSpam(
  533. _moderateInChannel->inputChannel,
  534. _moderateFrom->input,
  535. MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))
  536. ).send();
  537. }
  538. if (_deleteAll && _deleteAll->checked()) {
  539. _moderateInChannel->session().api().deleteAllFromParticipant(
  540. _moderateInChannel,
  541. _moderateFrom);
  542. }
  543. }
  544. const auto ids = _ids;
  545. invokeCallbackAndClose();
  546. session->data().histories().deleteMessages(ids, revoke);
  547. session->data().sendHistoryChangeNotifications();
  548. }