Changed behavior of mark as read actions to skip muted dialogs.

This commit is contained in:
23rd
2026-08-26 09:02:21 +03:00
parent 843fd265d3
commit 3abdc67424
13 changed files with 320 additions and 161 deletions

View File

@@ -1584,6 +1584,8 @@ PRIVATE
menu/menu_item_download_files.h
menu/menu_item_rate_transcribe_session.cpp
menu/menu_item_rate_transcribe_session.h
menu/menu_mark_as_read.cpp
menu/menu_mark_as_read.h
menu/menu_mute.cpp
menu/menu_mute.h
menu/menu_send.cpp

View File

@@ -70,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "menu/menu_mark_as_read.h"
#include "menu/menu_sponsored.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
@@ -6241,8 +6242,8 @@ void InnerWidget::setupShortcuts() {
if (!thread) {
return false;
}
if (Window::IsUnreadThread(thread)) {
Window::MarkAsReadThread(thread);
if (MarkAsReadMenu::IsUnreadThread(thread)) {
MarkAsReadMenu::MarkAsReadThread(thread);
}
return true;
});

View File

@@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "menu/menu_mark_as_read.h"
#include "menu/menu_mute.h"
#include "ui/toast/toast.h"
#include "window/window_peer_menu.h"
@@ -84,7 +85,7 @@ const style::font &SwipeActionFont(
|| action == Dialogs::Ui::QuickDialogAction::Pin) {
return nullptr;
} else if (action == Dialogs::Ui::QuickDialogAction::Read
&& Window::IsUnreadThread(history)) {
&& MarkAsReadMenu::IsUnreadThread(history)) {
return nullptr;
}
return info->channel();
@@ -134,11 +135,11 @@ void PerformQuickDialogAction(
});
}
} else if (action == Dialogs::Ui::QuickDialogAction::Read) {
if (Window::IsUnreadThread(history)) {
if (MarkAsReadMenu::IsUnreadThread(history)) {
if (const auto info = QuickActionCommunity(history)) {
Window::MarkAsReadChatList(info->chatsList());
MarkAsReadMenu::MarkAsReadChatList(info->chatsList());
} else {
Window::MarkAsReadThread(history);
MarkAsReadMenu::MarkAsReadThread(history);
}
controller->showToast(
tr::lng_quick_dialog_action_toast_read_success(tr::now));
@@ -209,7 +210,7 @@ Ui::QuickDialogActionLabel ResolveQuickDialogLabel(
? Ui::QuickDialogActionLabel::Unpin
: Ui::QuickDialogActionLabel::Pin;
} else if (action == Dialogs::Ui::QuickDialogAction::Read) {
const auto unread = Window::IsUnreadThread(history);
const auto unread = MarkAsReadMenu::IsUnreadThread(history);
if (history->isForum() && !unread) {
return Ui::QuickDialogActionLabel::Disabled;
}

View File

@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "menu/menu_mark_as_read.h"
#include "storage/storage_shared_media.h"
#include "support/support_preload.h"
#include "ui/chat/chat_style.h"
@@ -122,7 +123,7 @@ constexpr auto kBounceDuration = crl::time(400);
if (!topic || topic == current) {
continue;
}
if (Window::IsUnreadThread(topic)) {
if (MarkAsReadMenu::IsUnreadThread(topic)) {
return topic;
}
}
@@ -543,7 +544,7 @@ void PullToNextChannel::Indicator::paintEvent(QPaintEvent *e) {
? next->unreadCount()
: topic->chatListUnreadState().messages;
const auto badgeShown = (release > 0.)
&& (next ? (count > 0) : Window::IsUnreadThread(topic));
&& (next ? (count > 0) : MarkAsReadMenu::IsUnreadThread(topic));
const auto font = st::historyPullNextBadgeFont;
const auto badgeHeight = float64(st::historyPullNextBadge);
const auto string = (count > 0)
@@ -959,7 +960,7 @@ void PullToNextChannel::handleOverscroll(
&& (_nextTopic.get() == next)
&& (next != current)
&& (next->forum() == current->forum())
&& Window::IsUnreadThread(next)
&& MarkAsReadMenu::IsUnreadThread(next)
&& active()) {
_pulling = false;
_jumping = true;
@@ -1103,7 +1104,7 @@ void PullToNextChannel::jumpToTopic(
|| (_nextTopic.get() != next)
|| (next == current)
|| (next->forum() != current->forum())
|| !Window::IsUnreadThread(next)
|| !MarkAsReadMenu::IsUnreadThread(next)
|| !active()) {
reset(anim::type::normal);
return;

View File

@@ -0,0 +1,226 @@
/*
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 "base/options.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_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 {
const char kOptionMarkAsReadMutedChats[] = "mark-as-read-muted-chats";
namespace {
constexpr auto kMaxUnreadWithoutConfirmation = 1000;
base::options::toggle MarkAsReadMutedChats({
.id = kOptionMarkAsReadMutedChats,
.name = "Mark muted chats as read",
.description = "Let \"Mark all chats as read\" read muted chats as well.",
});
[[nodiscard]] MarkAsReadMuted MarkAsReadMutedMode() {
return MarkAsReadMutedChats.value()
? MarkAsReadMuted::Include
: MarkAsReadMuted::Skip;
}
[[nodiscard]] Dialogs::UnreadState MarkAsReadUnreadState(
not_null<Dialogs::MainList*> 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) && history->muted()) {
continue;
}
result += history->chatListUnreadState();
}
result.known = true;
return result;
}
[[nodiscard]] Dialogs::UnreadState MarkAsReadAllChatsState(
not_null<Data::Session*> 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;
}
} // namespace
bool IsUnreadThread(not_null<Data::Thread*> thread) {
return thread->chatListBadgesState().unread;
}
void MarkAsReadThread(
not_null<Data::Thread*> thread,
MarkAsReadMuted muted) {
const auto readHistory = [&](not_null<History*> history) {
history->owner().histories().readInbox(history);
};
if (!IsUnreadThread(thread)
|| ((muted == MarkAsReadMuted::Skip) && thread->muted())) {
return;
} else if (const auto forum = thread->asForum()) {
forum->enumerateTopics([=](not_null<Data::ForumTopic*> 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<Dialogs::MainList*> list,
MarkAsReadMuted muted) {
auto mark = std::vector<not_null<History*>>();
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<Main::Session*> session,
std::shared_ptr<Ui::Show> show,
const Ui::Menu::MenuCallback &addAction) {
const auto owner = &session->data();
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<void()> &&close) {
close();
markAll();
};
show->show(
Box([=](not_null<Ui::GenericBox*> box) {
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
box->verticalLayout(),
session->user(),
st::mainMenuUserpic);
Ui::IconWithTitle(
box->verticalLayout(),
userpic,
Ui::CreateChild<Ui::FlatLabel>(
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<Window::SessionController*> controller,
Fn<not_null<Dialogs::MainList*>()> &&list,
const Ui::Menu::MenuCallback &addAction,
Fn<Dialogs::UnreadState()> customUnreadState) {
// There is no async to make weak from controller.
const auto muted = MarkAsReadMutedMode();
const auto unreadState = (muted == MarkAsReadMuted::Skip)
? MarkAsReadUnreadState(list(), muted)
: customUnreadState
? customUnreadState()
: list()->unreadState();
if (!unreadState.messages && !unreadState.marks && !unreadState.chats) {
return;
}
auto callback = [=] {
if (unreadState.messages > kMaxUnreadWithoutConfirmation) {
auto boxCallback = [=](Fn<void()> &&close) {
MarkAsReadChatList(list(), muted);
close();
};
controller->show(
Ui::MakeConfirmBox({
tr::lng_context_mark_read_sure(),
std::move(boxCallback)
}),
Ui::LayerOption::CloseOther);
} else {
MarkAsReadChatList(list(), muted);
}
};
addAction(
tr::lng_context_mark_read(tr::now),
std::move(callback),
&st::menuIconMarkRead);
}
} // namespace MarkAsReadMenu

View File

@@ -0,0 +1,61 @@
/*
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
*/
#pragma once
#include "ui/widgets/menu/menu_add_action_callback.h"
namespace Data {
class Thread;
} // namespace Data
namespace Dialogs {
class MainList;
struct UnreadState;
} // namespace Dialogs
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class Show;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
namespace MarkAsReadMenu {
extern const char kOptionMarkAsReadMutedChats[];
enum class MarkAsReadMuted : uchar {
Include,
Skip,
};
[[nodiscard]] bool IsUnreadThread(not_null<Data::Thread*> thread);
void MarkAsReadThread(
not_null<Data::Thread*> thread,
MarkAsReadMuted muted = MarkAsReadMuted::Include);
void MarkAsReadChatList(
not_null<Dialogs::MainList*> list,
MarkAsReadMuted muted = MarkAsReadMuted::Include);
void AddAllChatsAction(
not_null<Main::Session*> session,
std::shared_ptr<Ui::Show> show,
const Ui::Menu::MenuCallback &addAction);
void AddChatListAction(
not_null<Window::SessionController*> controller,
Fn<not_null<Dialogs::MainList*>()> &&list,
const Ui::Menu::MenuCallback &addAction,
Fn<Dialogs::UnreadState()> customUnreadState = nullptr);
} // namespace MarkAsReadMenu

View File

@@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_badge.h"
#include "info/profile/info_profile_phone_menu.h"
#include "lang/lang_keys.h"
#include "menu/menu_mark_as_read.h"
#include "main/main_account.h"
#include "main/main_session.h"
#include "main/main_domain.h"
@@ -944,7 +945,7 @@ void SetupAccountsWrap(
callback({});
}, &st::menuIconProfile);
}
Window::MenuAddMarkAsReadAllChatsAction(
MarkAsReadMenu::AddAllChatsAction(
session,
window->uiShow(),
addAction);

View File

@@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "menu/menu_mark_as_read.h"
#include "media/player/media_player_instance.h"
#include "mtproto/session_private.h"
#include "webview/webview_embed.h"
@@ -388,6 +389,7 @@ void SetupExperimental(
Dialogs::kOptionForumHideChatsList,
Dialogs::kOptionDialogsUnreadOnTop,
Dialogs::Ui::kOptionDialogsMuteIcon,
MarkAsReadMenu::kOptionMarkAsReadMutedChats,
kOptionUseNewChatView,
kOptionAutoScrollInactiveChat,
kModerateCommonGroups,

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_unread_value.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "menu/menu_mark_as_read.h"
#include "main/main_session.h"
#include "settings/sections/settings_folders.h"
#include "ui/widgets/menu/menu_action.h"
@@ -85,7 +86,7 @@ void ShowMenu(
[=] { EditExistingFilter(controller, id); },
&st::menuIconEdit);
Window::MenuAddMarkAsReadChatListAction(
MarkAsReadMenu::AddChatListAction(
controller,
[=] { return session->data().chatsFilters().chatsList(id); },
addAction);
@@ -105,7 +106,7 @@ void ShowMenu(
session,
session->data().chatsList()->unreadState());
};
Window::MenuAddMarkAsReadChatListAction(
MarkAsReadMenu::AddChatListAction(
controller,
[=] { return session->data().chatsList(); },
addAction,

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "window/window_chat_preview.h"
#include "menu/menu_mark_as_read.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_histories.h"
@@ -76,7 +77,7 @@ bool ChatPreviewManager::show(
const auto owner = &thread->owner();
if (action.cancel) {
} else if (action.markRead) {
MarkAsReadThread(thread);
MarkAsReadMenu::MarkAsReadThread(thread);
} else if (action.markUnread) {
if (const auto history = thread->asHistory()) {
history->owner().histories().changeDialogUnreadMark(

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "window/window_filters_menu.h"
#include "menu/menu_mark_as_read.h"
#include "mainwindow.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
@@ -762,7 +763,7 @@ void FiltersMenu::showMenu(QPoint position, FilterId id) {
auto filteredChats = [=] {
return _session->session().data().chatsFilters().chatsList(id);
};
Window::MenuAddMarkAsReadChatListAction(
MarkAsReadMenu::AddChatListAction(
_session,
std::move(filteredChats),
addAction);
@@ -782,7 +783,7 @@ void FiltersMenu::showMenu(QPoint position, FilterId id) {
session,
session->data().chatsList()->unreadState());
};
Window::MenuAddMarkAsReadChatListAction(
MarkAsReadMenu::AddChatListAction(
_session,
[=] { return _session->session().data().chatsList(); },
addAction,

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/call_delayed.h"
#include "menu/menu_check_item.h"
#include "menu/menu_mark_as_read.h"
#include "boxes/about_box.h"
#include "boxes/share_box.h"
#include "boxes/star_gift_box.h"
@@ -196,7 +197,6 @@ const char kOptionViewProfileInChatsListContextMenu[]
namespace {
constexpr auto kArchivedToastDuration = crl::time(5000);
constexpr auto kMaxUnreadWithoutConfirmation = 1000;
[[nodiscard]] bool InsideCollapsedCommunity(History *history) {
// A member chat hidden inside a collapsed community lives in that
@@ -703,7 +703,7 @@ void Filler::addToggleFolder() {
void Filler::addToggleUnreadMark() {
const auto peer = _peer;
const auto unread = IsUnreadThread(_thread);
const auto unread = MarkAsReadMenu::IsUnreadThread(_thread);
const auto history = _request.key.history();
if (!_thread || !_thread->canToggleUnread(unread)) {
return;
@@ -724,9 +724,9 @@ void Filler::addToggleUnreadMark() {
: nullptr;
if (info) {
// Mark every chat inside the community as read.
MarkAsReadChatList(info->chatsList());
MarkAsReadMenu::MarkAsReadChatList(info->chatsList());
} else {
MarkAsReadThread(thread);
MarkAsReadMenu::MarkAsReadThread(thread);
}
} else if (const auto sublist = thread->asSublist()) {
peer->owner().histories().changeSublistUnreadMark(sublist, true);
@@ -2060,7 +2060,7 @@ void Filler::fillArchiveActions() {
}, inmenu ? &st::menuIconFromMainMenu : &st::menuIconToMainMenu);
}
MenuAddMarkAsReadChatListAction(
MarkAsReadMenu::AddChatListAction(
controller,
[folder = _folder] { return folder->chatsList(); },
_addAction);
@@ -4243,94 +4243,6 @@ void UnpinAllMessages(
Ui::LayerOption::CloseOther);
}
void MenuAddMarkAsReadAllChatsAction(
not_null<Main::Session*> session,
std::shared_ptr<Ui::Show> show,
const PeerMenuCallback &addAction) {
auto callback = [=, owner = &session->data()] {
auto boxCallback = [=](Fn<void()> &&close) {
close();
MarkAsReadChatList(owner->chatsList());
if (const auto folder = owner->folderLoaded(Data::Folder::kId)) {
MarkAsReadChatList(folder->chatsList());
}
};
show->show(
Box([=](not_null<Ui::GenericBox*> box) {
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
box->verticalLayout(),
session->user(),
st::mainMenuUserpic);
Ui::IconWithTitle(
box->verticalLayout(),
userpic,
Ui::CreateChild<Ui::FlatLabel>(
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 MenuAddMarkAsReadChatListAction(
not_null<Window::SessionController*> controller,
Fn<not_null<Dialogs::MainList*>()> &&list,
const PeerMenuCallback &addAction,
Fn<Dialogs::UnreadState()> customUnreadState) {
// There is no async to make weak from controller.
const auto unreadState = customUnreadState
? customUnreadState()
: list()->unreadState();
if (!unreadState.messages && !unreadState.marks && !unreadState.chats) {
return;
}
auto callback = [=] {
if (unreadState.messages > kMaxUnreadWithoutConfirmation) {
auto boxCallback = [=](Fn<void()> &&close) {
MarkAsReadChatList(list());
close();
};
controller->show(
Ui::MakeConfirmBox({
tr::lng_context_mark_read_sure(),
std::move(boxCallback)
}),
Ui::LayerOption::CloseOther);
} else {
MarkAsReadChatList(list());
}
};
addAction(
tr::lng_context_mark_read(tr::now),
std::move(callback),
&st::menuIconMarkRead);
}
void ToggleHistoryArchived(
std::shared_ptr<ChatHelpers::Show> show,
not_null<History*> history,
@@ -4625,42 +4537,6 @@ void AddSenderUserpicModerateAction(
}
}
bool IsUnreadThread(not_null<Data::Thread*> thread) {
return thread->chatListBadgesState().unread;
}
void MarkAsReadThread(not_null<Data::Thread*> thread) {
const auto readHistory = [&](not_null<History*> history) {
history->owner().histories().readInbox(history);
};
if (!IsUnreadThread(thread)) {
return;
} else if (const auto forum = thread->asForum()) {
forum->enumerateTopics([](not_null<Data::ForumTopic*> topic) {
MarkAsReadThread(topic);
});
} 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<Dialogs::MainList*> list) {
auto mark = std::vector<not_null<History*>>();
for (const auto &row : list->indexed()->all()) {
if (const auto history = row->history()) {
mark.push_back(history);
}
}
ranges::for_each(mark, MarkAsReadThread);
}
void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction) {
addAction({
.separatorSt = &st::popupMenuExpandedSeparator.menu.separator,

View File

@@ -92,17 +92,6 @@ void AddSenderUserpicModerateAction(
HistoryItem *moderateItem,
const PeerMenuCallback &addAction);
void MenuAddMarkAsReadAllChatsAction(
not_null<Main::Session*> session,
std::shared_ptr<Ui::Show> show,
const PeerMenuCallback &addAction);
void MenuAddMarkAsReadChatListAction(
not_null<Window::SessionController*> controller,
Fn<not_null<Dialogs::MainList*>()> &&list,
const PeerMenuCallback &addAction,
Fn<Dialogs::UnreadState()> customUnreadState = nullptr);
void PeerMenuExportChat(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
@@ -271,10 +260,6 @@ void UnpinAllMessages(
not_null<Window::SessionNavigation*> navigation,
not_null<Data::Thread*> thread);
[[nodiscard]] bool IsUnreadThread(not_null<Data::Thread*> thread);
void MarkAsReadThread(not_null<Data::Thread*> thread);
void MarkAsReadChatList(not_null<Dialogs::MainList*> list);
void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction);
[[nodiscard]] bool IsArchived(not_null<History*> history);