/* 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 "menu/menu_mark_as_read.h" #include "core/application.h" #include "core/core_settings.h" #include "data/data_folder.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" #include "data/data_histories.h" #include "data/data_saved_sublist.h" #include "data/data_session.h" #include "data/data_unread_value.h" #include "data/data_user.h" #include "history/history.h" #include "info/profile/info_profile_values.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/vertical_list.h" #include "window/window_session_controller.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_window.h" namespace MarkAsReadMenu { namespace { constexpr auto kMaxUnreadWithoutConfirmation = 1000; [[nodiscard]] MarkAsReadMuted MarkAsReadMutedMode() { return Core::App().settings().includeMutedCounter() ? MarkAsReadMuted::Include : MarkAsReadMuted::Skip; } [[nodiscard]] bool SkipMutedThread(not_null thread) { return thread->muted() && !thread->chatListUnreadState().mentions; } [[nodiscard]] Dialogs::UnreadState MarkAsReadUnreadState( not_null list, MarkAsReadMuted muted) { auto result = Dialogs::UnreadState(); for (const auto &row : list->indexed()->all()) { const auto history = row->history(); if (!history) { continue; } else if ((muted == MarkAsReadMuted::Skip) && SkipMutedThread(history)) { continue; } result += history->chatListUnreadState(); } result.known = true; return result; } [[nodiscard]] Dialogs::UnreadState MarkAsReadAllChatsState( not_null owner, MarkAsReadMuted muted) { auto result = MarkAsReadUnreadState(owner->chatsList(), muted); if (const auto folder = owner->folderLoaded(Data::Folder::kId)) { result += MarkAsReadUnreadState(folder->chatsList(), muted); } return result; } [[nodiscard]] rpl::producer ConfirmChatListText(ChatListKind kind) { switch (kind) { case ChatListKind::Folder: return tr::lng_context_mark_read_sure(); case ChatListKind::Archive: return tr::lng_context_mark_read_archive_sure(); case ChatListKind::AllChats: return tr::lng_context_mark_read_all_sure(); } Unexpected("Kind in MarkAsReadMenu::ConfirmChatListText."); } } // namespace bool IsUnreadThread(not_null thread) { return thread->chatListBadgesState().unread; } void MarkAsReadThread( not_null thread, MarkAsReadMuted muted) { const auto readHistory = [&](not_null history) { history->owner().histories().readInbox(history); }; if (!IsUnreadThread(thread) || ((muted == MarkAsReadMuted::Skip) && SkipMutedThread(thread))) { return; } else if (const auto forum = thread->asForum()) { forum->enumerateTopics([=](not_null topic) { MarkAsReadThread(topic, muted); }); } else if (const auto history = thread->asHistory()) { readHistory(history); if (const auto migrated = history->migrateSibling()) { readHistory(migrated); } } else if (const auto topic = thread->asTopic()) { topic->readTillEnd(); } else if (const auto sublist = thread->asSublist()) { sublist->readTillEnd(); } } void MarkAsReadChatList( not_null list, MarkAsReadMuted muted) { auto mark = std::vector>(); for (const auto &row : list->indexed()->all()) { if (const auto history = row->history()) { mark.push_back(history); } } for (const auto &history : mark) { MarkAsReadThread(history, muted); } } void AddAllChatsAction( not_null session, std::shared_ptr show, const Ui::Menu::MenuCallback &addAction) { const auto owner = &session->data(); if (!owner->unreadWithMentionsBadge()) { return; } const auto muted = MarkAsReadMutedMode(); const auto unreadState = MarkAsReadAllChatsState(owner, muted); if (!unreadState.messages && !unreadState.marks && !unreadState.chats) { return; } auto callback = [=] { const auto markAll = [=] { MarkAsReadChatList(owner->chatsList(), muted); if (const auto folder = owner->folderLoaded(Data::Folder::kId)) { MarkAsReadChatList(folder->chatsList(), muted); } }; if (unreadState.messages <= kMaxUnreadWithoutConfirmation) { markAll(); return; } auto boxCallback = [=](Fn &&close) { close(); markAll(); }; show->show( Box([=](not_null box) { Ui::AddSkip(box->verticalLayout()); Ui::AddSkip(box->verticalLayout()); const auto userpic = Ui::CreateChild( box->verticalLayout(), session->user(), st::mainMenuUserpic); Ui::IconWithTitle( box->verticalLayout(), userpic, Ui::CreateChild( box->verticalLayout(), Info::Profile::NameValue(session->user()), box->getDelegate()->style().title)); auto text = rpl::combine( tr::lng_context_mark_read_all_sure(), tr::lng_context_mark_read_all_sure_2( tr::rich) ) | rpl::map([](QString t1, TextWithEntities t2) { return TextWithEntities() .append(std::move(t1)) .append('\n') .append('\n') .append(std::move(t2)); }); Ui::ConfirmBox(box, { .text = std::move(text), .confirmed = std::move(boxCallback), .confirmStyle = &st::attentionBoxButton, }); }), Ui::LayerOption::CloseOther); }; addAction( tr::lng_context_mark_read_all(tr::now), std::move(callback), &st::menuIconMarkRead); } void AddChatListAction( not_null controller, ChatListKind kind, Fn()> &&list, const Ui::Menu::MenuCallback &addAction) { // There is no async to make weak from controller. const auto unreadState = (kind == ChatListKind::AllChats) ? Data::MainListMapUnreadState( &controller->session(), list()->unreadState()) : list()->unreadState(); if (!unreadState.messages && !unreadState.marks && !unreadState.chats) { return; } auto callback = [=] { if (unreadState.messages > kMaxUnreadWithoutConfirmation) { auto boxCallback = [=](Fn &&close) { MarkAsReadChatList(list()); close(); }; controller->show( Ui::MakeConfirmBox({ ConfirmChatListText(kind), std::move(boxCallback) }), Ui::LayerOption::CloseOther); } else { MarkAsReadChatList(list()); } }; addAction( tr::lng_context_mark_read(tr::now), std::move(callback), &st::menuIconMarkRead); } } // namespace MarkAsReadMenu