diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a08ae7ded..4859d3065 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -81,6 +81,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_calendar_select_days" = "Select days"; "lng_calendar_start_tip" = "Press and hold to jump to the start."; "lng_calendar_end_tip" = "Press and hold to jump to the end."; +"lng_calendar_days#one" = "{count} day"; +"lng_calendar_days#other" = "{count} days"; "lng_box_ok" = "OK"; "lng_box_done" = "Done"; @@ -1134,6 +1136,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sure_delete_saved_messages" = "Are you sure, you want to delete all your saved messages?\n\nThis action cannot be undone."; "lng_no_clear_history_channel" = "In channels you can enable auto-delete for messages."; "lng_no_clear_history_group" = "In public groups you can enable auto-delete for messages."; +"lng_sure_delete_by_date_one" = "Are you sure you want to delete all messages for **{date}**?\n\nThis action cannot be undone."; +"lng_sure_delete_by_date_many" = "Are you sure you want to delete all messages for the **{days}**?\n\nThis action cannot be undone."; +"lng_sure_delete_selected_days#one" = "{count} selected day"; +"lng_sure_delete_selected_days#other" = "{count} selected days"; "lng_message_empty" = "Empty Message"; "lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the latest version in Settings, or install it from {link}"; diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index b13edac73..f099b3bca 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -54,6 +54,18 @@ DeleteMessagesBox::DeleteMessagesBox( Expects(!_ids.empty()); } +DeleteMessagesBox::DeleteMessagesBox( + QWidget*, + not_null peer, + QDate firstDayToDelete, + QDate lastDayToDelete) +: _session(&peer->session()) +, _wipeHistoryPeer(peer) +, _wipeHistoryJustClear(true) +, _wipeHistoryFirstToDelete(firstDayToDelete) +, _wipeHistoryLastToDelete(lastDayToDelete) { +} + DeleteMessagesBox::DeleteMessagesBox( QWidget*, not_null peer, @@ -73,7 +85,27 @@ void DeleteMessagesBox::prepare() { auto deleteStyle = &st::defaultBoxButton; auto canDelete = true; if (const auto peer = _wipeHistoryPeer) { - if (_wipeHistoryJustClear) { + 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(); @@ -397,16 +429,41 @@ void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) { void DeleteMessagesBox::deleteAndClear() { const auto revoke = _revoke ? _revoke->checked() : false; - if (const auto peer = _wipeHistoryPeer) { + 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; - closeBox(); + invokeCallbackAndClose(); if (justClear) { - peer->session().api().clearHistory(peer, revoke); + session->api().clearHistory(peer, revoke); } else { - for (const auto &controller : peer->session().windows()) { + for (const auto &controller : session->windows()) { if (controller->activeChatCurrent().peer() == peer) { - Ui::showChatsList(&peer->session()); + Ui::showChatsList(session); } } // Don't delete old history by default, @@ -415,7 +472,7 @@ void DeleteMessagesBox::deleteAndClear() { //if (const auto from = peer->migrateFrom()) { // peer->session().api().deleteConversation(from, false); //} - peer->session().api().deleteConversation(peer, revoke); + session->api().deleteConversation(peer, revoke); } return; } @@ -441,19 +498,8 @@ void DeleteMessagesBox::deleteAndClear() { } } - if (_deleteConfirmedCallback) { - _deleteConfirmedCallback(); - } - - // deleteMessages can initiate closing of the current section, - // which will cause this box to be destroyed. - const auto session = _session; - const auto weak = Ui::MakeWeak(this); - - session->data().histories().deleteMessages(_ids, revoke); - - if (const auto strong = weak.data()) { - strong->closeBox(); - } + const auto ids = _ids; + invokeCallbackAndClose(); + session->data().histories().deleteMessages(ids, revoke); session->data().sendHistoryChangeNotifications(); } diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.h b/Telegram/SourceFiles/boxes/delete_messages_box.h index 627f433df..38bf7d38f 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.h +++ b/Telegram/SourceFiles/boxes/delete_messages_box.h @@ -29,6 +29,11 @@ public: QWidget*, not_null session, MessageIdsList &&selected); + DeleteMessagesBox( + QWidget*, + not_null peer, + QDate firstDayToDelete, + QDate lastDayToDelete); DeleteMessagesBox(QWidget*, not_null peer, bool justClear); void setDeleteConfirmedCallback(Fn callback) { @@ -56,6 +61,8 @@ private: PeerData * const _wipeHistoryPeer = nullptr; const bool _wipeHistoryJustClear = false; + const QDate _wipeHistoryFirstToDelete; + const QDate _wipeHistoryLastToDelete; const MessageIdsList _ids; UserData *_moderateFrom = nullptr; ChannelData *_moderateInChannel = nullptr; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 2af0634b7..21836dc17 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_folder.h" #include "data/data_scheduled_messages.h" +#include "base/unixtime.h" #include "main/main_session.h" #include "window/notifications_manager.h" #include "history/history.h" @@ -708,6 +709,58 @@ void Histories::deleteAllMessages( }); } +void Histories::deleteMessagesByDates( + not_null history, + QDate firstDayToDelete, + QDate lastDayToDelete, + bool revoke) { + const auto firstSecondToDelete = base::unixtime::serialize( + { firstDayToDelete, QTime(0, 0) } + ); + const auto lastSecondToDelete = base::unixtime::serialize( + { lastDayToDelete, QTime(23, 59, 59) } + ); + deleteMessagesByDates( + history, + firstSecondToDelete - 1, + lastSecondToDelete + 1, + revoke); +} + +void Histories::deleteMessagesByDates( + not_null history, + TimeId minDate, + TimeId maxDate, + bool revoke) { + sendRequest(history, RequestType::Delete, [=](Fn finish) { + const auto peer = history->peer; + const auto fail = [=](const MTP::Error &error) { + finish(); + }; + using Flag = MTPmessages_DeleteHistory::Flag; + const auto flags = Flag::f_just_clear + | Flag::f_min_date + | Flag::f_max_date + | (revoke ? Flag::f_revoke : Flag(0)); + return session().api().request(MTPmessages_DeleteHistory( + MTP_flags(flags), + peer->input, + MTP_int(0), + MTP_int(minDate), + MTP_int(maxDate) + )).done([=](const MTPmessages_AffectedHistory &result) { + const auto offset = session().api().applyAffectedHistory( + peer, + result); + if (offset > 0) { + deleteMessagesByDates(history, minDate, maxDate, revoke); + } + finish(); + }).fail(fail).send(); + }); + history->destroyMessagesByDates(minDate, maxDate); +} + void Histories::deleteMessages(const MessageIdsList &ids, bool revoke) { auto remove = std::vector>(); remove.reserve(ids.size()); diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index 81e9fd1fa..6d36b2f48 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -71,6 +71,17 @@ public: bool justClear, bool revoke); + void deleteMessagesByDates( + not_null history, + QDate firstDayToDelete, + QDate lastDayToDelete, + bool revoke); + void deleteMessagesByDates( + not_null history, + TimeId minDate, + TimeId maxDate, + bool revoke); + void deleteMessages(const MessageIdsList &ids, bool revoke); int sendRequest( diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 8001a6627..74ccca11e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -284,7 +284,7 @@ Widget::Widget( }, lifetime()); _cancelSearch->setClickedCallback([this] { onCancelSearch(); }); - _jumpToDate->entity()->setClickedCallback([this] { showJumpToDate(); }); + _jumpToDate->entity()->setClickedCallback([this] { showCalendar(); }); _chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); }); rpl::single( rpl::empty_value() @@ -1453,9 +1453,9 @@ void Widget::clearSearchCache() { cancelSearchRequest(); } -void Widget::showJumpToDate() { +void Widget::showCalendar() { if (_searchInChat) { - controller()->showJumpToDate(_searchInChat, QDate()); + controller()->showCalendar(_searchInChat, QDate()); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 668fa5f8e..63a6c6650 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -150,7 +150,7 @@ private: void setupMainMenuToggle(); bool searchForPeersRequired(const QString &query) const; void setSearchInChat(Key chat, PeerData *from = nullptr); - void showJumpToDate(); + void showCalendar(); void showSearchFrom(); void showMainMenu(); void clearSearchCache(); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index b3fd77016..655415dc0 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -462,6 +462,20 @@ void History::destroyMessage(not_null item) { } } +void History::destroyMessagesByDates(TimeId minDate, TimeId maxDate) { + auto toDestroy = std::vector>(); + for (const auto &message : _messages) { + if (message->isRegular() + && message->date() > minDate + && message->date() < maxDate) { + toDestroy.push_back(message.get()); + } + } + for (const auto item : toDestroy) { + item->destroy(); + } +} + void History::unpinAllMessages() { session().storage().remove( Storage::SharedMediaRemoveAll( diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index e8c9cc962..9f2257a84 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -132,6 +132,7 @@ public: std::forward(args)...)).get()); } void destroyMessage(not_null item); + void destroyMessagesByDates(TimeId minDate, TimeId maxDate); void unpinAllMessages(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 2f9de3913..17cffd96e 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6782,13 +6782,10 @@ void HistoryWidget::confirmDeleteSelected() { if (items.empty()) { return; } - const auto weak = Ui::MakeWeak(this); auto box = Box(&session(), std::move(items)); - box->setDeleteConfirmedCallback([=] { - if (const auto strong = weak.data()) { - strong->clearSelected(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(this, [=] { + clearSelected(); + })); controller()->show(std::move(box)); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index e39bfa6d3..3b0b9bb27 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -689,16 +689,13 @@ bool AddDeleteSelectedAction( } menu->addAction(tr::lng_context_delete_selected(tr::now), [=] { - const auto weak = Ui::MakeWeak(list); auto items = ExtractIdsList(request.selectedItems); auto box = Box( &request.navigation->session(), std::move(items)); - box->setDeleteConfirmedCallback([=] { - if (const auto strong = weak.data()) { - strong->cancelSelection(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(list, [=] { + list->cancelSelection(); + })); request.navigation->parentController()->show(std::move(box)); }); return true; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 7ea987b29..e7f3c1757 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -3027,15 +3027,12 @@ void ConfirmDeleteSelectedItems(not_null widget) { return; } } - const auto weak = Ui::MakeWeak(widget); auto box = Box( &widget->controller()->session(), widget->getSelectedIds()); - box->setDeleteConfirmedCallback([=] { - if (const auto strong = weak.data()) { - strong->cancelSelection(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(widget, [=] { + widget->cancelSelection(); + })); widget->controller()->show(std::move(box)); } diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 4b39c82ef..fae562b0c 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -544,11 +544,9 @@ void TopBar::performDelete() { auto box = Box( &_navigation->session(), std::move(items)); - box->setDeleteConfirmedCallback([weak = Ui::MakeWeak(this)] { - if (weak) { - weak->_cancelSelectionClicks.fire({}); - } - }); + box->setDeleteConfirmedCallback(crl::guard(this, [=] { + _cancelSelectionClicks.fire({}); + })); _navigation->parentController()->show(std::move(box)); } } diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 5584b182f..7f3d46cee 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -1715,12 +1715,9 @@ void ListWidget::forwardItems(MessageIdsList &&items) { void ListWidget::deleteSelected() { if (const auto box = deleteItems(collectSelectedIds())) { - const auto weak = Ui::MakeWeak(this); - box->setDeleteConfirmedCallback([=]{ - if (const auto strong = weak.data()) { - strong->clearSelected(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(this, [=]{ + clearSelected(); + })); } } diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp index 10286c1b0..7cc728044 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp @@ -693,7 +693,10 @@ CalendarBox::Title::Title( } else if (!_context->selectedMin()) { setText(tr::lng_calendar_select_days(tr::now)); } else { - setText(QString::number(1 + *_context->selectedMax() - *_context->selectedMin())); // #TODO calendar + setText(tr::lng_calendar_days( + tr::now, + lt_count, + (1 + *_context->selectedMax() - *_context->selectedMin()))); } }, lifetime()); } @@ -803,6 +806,16 @@ void CalendarBox::toggleSelectionMode(bool enabled) { _context->toggleSelectionMode(enabled); } +QDate CalendarBox::selectedFirstDate() const { + const auto min = _context->selectedMin(); + return min.has_value() ? _context->dateFromIndex(*min) : QDate(); +} + +QDate CalendarBox::selectedLastDate() const { + const auto max = _context->selectedMax(); + return max.has_value() ? _context->dateFromIndex(*max) : QDate(); +} + void CalendarBox::showJumpTooltip(not_null button) { _tooltipButton = button; Ui::Tooltip::Show(kTooltipDelay, this); diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.h b/Telegram/SourceFiles/ui/boxes/calendar_box.h index 1af15ae77..43bfa06ba 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.h +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.h @@ -50,6 +50,9 @@ public: void toggleSelectionMode(bool enabled); + [[nodiscard]] QDate selectedFirstDate() const; + [[nodiscard]] QDate selectedLastDate() const; + protected: void prepare() override; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index e9cc8b59f..7c0c591ba 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/add_contact_box.h" #include "boxes/peers/edit_peer_info_box.h" #include "boxes/peer_list_controllers.h" +#include "boxes/delete_messages_box.h" #include "window/window_adaptive.h" #include "window/window_controller.h" #include "window/main_window.h" @@ -151,7 +152,7 @@ void DateClickHandler::setDate(QDate date) { void DateClickHandler::onClick(ClickContext context) const { const auto my = context.other.value(); if (const auto window = my.sessionWindow.get()) { - window->showJumpToDate(_chat, _date); + window->showCalendar(_chat, _date); } } @@ -1146,7 +1147,7 @@ void SessionController::startOrJoinGroupCall( } } -void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { +void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) { const auto history = chat.history(); if (!history) { return; @@ -1248,7 +1249,18 @@ void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { }); auto text = tr::lng_profile_clear_history(); const auto button = box->addLeftButton(std::move(text), [=] { - + const auto firstDate = box->selectedFirstDate(); + const auto lastDate = box->selectedLastDate(); + if (!firstDate.isNull()) { + auto confirm = Box( + history->peer, + firstDate, + lastDate); + confirm->setDeleteConfirmedCallback(crl::guard(box, [=] { + box->closeBox(); + })); + box->getDelegate()->show(std::move(confirm)); + } }, (*selected > 0) ? st::attentionBoxButton : buttonState->disabled); if (!*selected) { button->setPointerCursor(false); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 68a9f5791..2cca76cdd 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -363,7 +363,7 @@ public: } void removeLayerBlackout(); - void showJumpToDate( + void showCalendar( Dialogs::Key chat, QDate requestedDate);