diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c84a7e04bc..eea4757624 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1578,12 +1578,18 @@ PRIVATE settings/settings_local_passcode.h settings/settings_main.cpp settings/settings_main.h + settings/builder/settings_advanced_builder.cpp + settings/builder/settings_advanced_builder.h settings/builder/settings_builder.cpp settings/builder/settings_builder.h + settings/builder/settings_chat_builder.cpp + settings/builder/settings_chat_builder.h settings/builder/settings_main_builder.cpp settings/builder/settings_main_builder.h settings/builder/settings_notifications_builder.cpp settings/builder/settings_notifications_builder.h + settings/builder/settings_privacy_security_builder.cpp + settings/builder/settings_privacy_security_builder.h settings/settings_notifications.cpp settings/settings_notifications.h settings/settings_notifications_type.cpp diff --git a/Telegram/SourceFiles/settings/builder/settings_advanced_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_advanced_builder.cpp new file mode 100644 index 0000000000..6005341846 --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_advanced_builder.cpp @@ -0,0 +1,1177 @@ +/* +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 "settings/builder/settings_advanced_builder.h" + +#include "base/call_delayed.h" +#include "base/platform/base_platform_info.h" +#include "boxes/about_box.h" +#include "boxes/auto_download_box.h" +#include "boxes/connection_box.h" +#include "boxes/download_path_box.h" +#include "data/data_auto_download.h" +#include "boxes/local_storage_box.h" +#include "core/application.h" +#include "core/core_settings.h" +#include "core/file_utilities.h" +#include "core/launcher.h" +#include "core/update_checker.h" +#include "export/export_manager.h" +#include "info/downloads/info_downloads_widget.h" +#include "info/info_memento.h" +#include "lang/lang_keys.h" +#include "main/main_account.h" +#include "main/main_domain.h" +#include "main/main_session.h" +#include "mtproto/mtp_instance.h" +#include "platform/platform_specific.h" +#include "settings/builder/settings_builder.h" +#include "settings/settings_advanced.h" +#include "settings/settings_chat.h" +#include "settings/settings_experimental.h" +#include "settings/settings_power_saving.h" +#include "storage/localstorage.h" +#include "storage/storage_domain.h" +#include "tray.h" +#include "ui/boxes/confirm_box.h" +#include "ui/boxes/single_choice_box.h" +#include "ui/gl/gl_detection.h" +#include "ui/layers/generic_box.h" +#include "ui/platform/ui_platform_window.h" +#include "ui/text/format_values.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/rp_widget.h" +#include "ui/ui_utility.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +#ifndef TDESKTOP_DISABLE_SPELLCHECK +#include "boxes/dictionaries_manager.h" +#include "chat_helpers/spellchecker_common.h" +#include "spellcheck/platform/platform_spellcheck.h" +#endif // !TDESKTOP_DISABLE_SPELLCHECK + +namespace Settings::Builder { +namespace { + +void BuildDataStorageSection( + SectionBuilder &builder, + Window::SessionController *controller) { + const auto container = builder.container(); + const auto account = controller + ? &controller->session().account() + : nullptr; + + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_data_storage()); + + const auto connectionType = [=] { + if (!account) { + return QString(); + } + const auto transport = account->mtp().dctransport(); + if (!Core::App().settings().proxy().isEnabled()) { + return transport.isEmpty() + ? tr::lng_connection_auto_connecting(tr::now) + : tr::lng_connection_auto(tr::now, lt_transport, transport); + } else { + return transport.isEmpty() + ? tr::lng_connection_proxy_connecting(tr::now) + : tr::lng_connection_proxy(tr::now, lt_transport, transport); + } + }; + + builder.addSettingsButton({ + .id = u"advanced/connection_type"_q, + .title = tr::lng_settings_connection_type(), + .icon = { &st::menuIconNetwork }, + .label = account + ? (rpl::merge( + Core::App().settings().proxy().connectionTypeChanges(), + tr::lng_connection_auto_connecting() | rpl::to_empty + ) | rpl::map(connectionType)) + : rpl::single(QString()), + .onClick = [=] { + if (controller && account) { + controller->window().show( + ProxiesBoxController::CreateOwningBox(account)); + } + }, + .keywords = { u"connection"_q, u"proxy"_q, u"network"_q, u"vpn"_q }, + }); + + const auto showDownloadPath = container + ? container->lifetime().make_state>( + !Core::App().settings().askDownloadPath()) + : nullptr; + + auto downloadLabel = Core::App().settings().downloadPathValue( + ) | rpl::map([](const QString &text) { + if (text.isEmpty()) { + return Core::App().canReadDefaultDownloadPath() + ? tr::lng_download_path_default(tr::now) + : tr::lng_download_path_temp(tr::now); + } else if (text == FileDialog::Tmp()) { + return tr::lng_download_path_temp(tr::now); + } + return QDir::toNativeSeparators(text); + }); + + builder.addSlideLabeledButton({ + .id = u"advanced/download_path"_q, + .title = tr::lng_download_path(), + .icon = { &st::menuIconShowInFolder }, + .label = std::move(downloadLabel), + .shown = showDownloadPath + ? showDownloadPath->value() + : rpl::single(true), + .onClick = [=] { + if (controller) { + controller->show(Box(controller)); + } + }, + .keywords = { u"download"_q, u"path"_q, u"folder"_q }, + }); + + builder.addSettingsButton({ + .id = u"advanced/storage"_q, + .title = tr::lng_settings_manage_local_storage(), + .icon = { &st::menuIconStorage }, + .onClick = [=] { + if (controller) { + LocalStorageBox::Show(controller); + } + }, + .keywords = { u"storage"_q, u"cache"_q, u"local"_q }, + }); + + builder.addSettingsButton({ + .id = u"advanced/downloads"_q, + .title = tr::lng_downloads_section(), + .icon = { &st::menuIconDownload }, + .onClick = [=] { + if (controller) { + controller->showSection( + Info::Downloads::Make(controller->session().user())); + } + }, + .keywords = { u"downloads"_q, u"files"_q }, + }); + + const auto askDownloadPath = builder.addToggle({ + .id = u"advanced/ask_download"_q, + .title = tr::lng_download_path_ask(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(Core::App().settings().askDownloadPath()), + .keywords = { u"download"_q, u"path"_q, u"ask"_q }, + }); + + if (askDownloadPath) { + askDownloadPath->toggledValue( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().askDownloadPath()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setAskDownloadPath(checked); + Core::App().saveSettingsDelayed(); + if (showDownloadPath) { + *showDownloadPath = !checked; + } + }, askDownloadPath->lifetime()); + } + + builder.addSkip(st::settingsCheckboxesSkip); +} + +void BuildAutoDownloadSection( + SectionBuilder &builder, + Window::SessionController *controller) { + builder.addDivider(); + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_media_auto_settings()); + + using Source = Data::AutoDownload::Source; + + builder.addSettingsButton({ + .id = u"advanced/auto_download_private"_q, + .title = tr::lng_media_auto_in_private(), + .icon = { &st::menuIconProfile }, + .onClick = [=] { + if (controller) { + controller->show( + Box(&controller->session(), Source::User)); + } + }, + .keywords = { u"auto"_q, u"download"_q, u"private"_q, u"media"_q }, + }); + + builder.addSettingsButton({ + .id = u"advanced/auto_download_groups"_q, + .title = tr::lng_media_auto_in_groups(), + .icon = { &st::menuIconGroups }, + .onClick = [=] { + if (controller) { + controller->show( + Box(&controller->session(), Source::Group)); + } + }, + .keywords = { u"auto"_q, u"download"_q, u"groups"_q, u"media"_q }, + }); + + builder.addSettingsButton({ + .id = u"advanced/auto_download_channels"_q, + .title = tr::lng_media_auto_in_channels(), + .icon = { &st::menuIconChannel }, + .onClick = [=] { + if (controller) { + controller->show( + Box(&controller->session(), Source::Channel)); + } + }, + .keywords = { u"auto"_q, u"download"_q, u"channels"_q, u"media"_q }, + }); + + builder.addSkip(st::settingsCheckboxesSkip); +} + +void BuildWindowTitleSection( + SectionBuilder &builder, + [[maybe_unused]] Window::SessionController *controller) { + const auto settings = &Core::App().settings(); + + builder.addDivider(); + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_window_system()); + + const auto content = [=] { + return settings->windowTitleContent(); + }; + + const auto showChatName = builder.addCheckbox({ + .id = u"advanced/title_chat_name"_q, + .title = tr::lng_settings_title_chat_name(), + .checked = !content().hideChatName, + .keywords = { u"title"_q, u"chat"_q, u"name"_q }, + }); + if (showChatName) { + showChatName->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked == content().hideChatName); + }) | rpl::on_next([=](bool checked) { + auto updated = content(); + updated.hideChatName = !checked; + settings->setWindowTitleContent(updated); + Core::App().saveSettingsDelayed(); + }, showChatName->lifetime()); + } + + const auto showAccountName = (Core::App().domain().accountsAuthedCount() > 1) + ? builder.addCheckbox({ + .id = u"advanced/title_account_name"_q, + .title = tr::lng_settings_title_account_name(), + .checked = !content().hideAccountName, + .keywords = { u"title"_q, u"account"_q, u"name"_q }, + }) + : nullptr; + if (showAccountName) { + showAccountName->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked == content().hideAccountName); + }) | rpl::on_next([=](bool checked) { + auto updated = content(); + updated.hideAccountName = !checked; + settings->setWindowTitleContent(updated); + Core::App().saveSettingsDelayed(); + }, showAccountName->lifetime()); + } + + const auto showTotalUnread = builder.addCheckbox({ + .id = u"advanced/title_total_unread"_q, + .title = tr::lng_settings_title_total_count(), + .checked = !content().hideTotalUnread, + .keywords = { u"title"_q, u"unread"_q, u"count"_q, u"badge"_q }, + }); + if (showTotalUnread) { + showTotalUnread->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked == content().hideTotalUnread); + }) | rpl::on_next([=](bool checked) { + auto updated = content(); + updated.hideTotalUnread = !checked; + settings->setWindowTitleContent(updated); + Core::App().saveSettingsDelayed(); + }, showTotalUnread->lifetime()); + } + + if (Ui::Platform::NativeWindowFrameSupported()) { + const auto nativeFrame = builder.addCheckbox({ + .id = u"advanced/native_frame"_q, + .title = Platform::IsWayland() + ? tr::lng_settings_qt_frame() + : tr::lng_settings_native_frame(), + .checked = settings->nativeWindowFrame(), + .keywords = { u"frame"_q, u"native"_q, u"window"_q, u"border"_q }, + }); + if (nativeFrame) { + nativeFrame->checkedChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().nativeWindowFrame()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setNativeWindowFrame(checked); + Core::App().saveSettingsDelayed(); + }, nativeFrame->lifetime()); + } + } + + builder.addSkip(); +} + +void BuildSystemIntegrationSection( + SectionBuilder &builder, + Window::SessionController *controller) { + const auto container = builder.container(); + const auto settings = &Core::App().settings(); + + builder.addDivider(); + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_system_integration()); + + using WorkMode = Core::Settings::WorkMode; + + if (Platform::TrayIconSupported()) { + const auto trayEnabled = [=] { + const auto workMode = settings->workMode(); + return (workMode == WorkMode::TrayOnly) + || (workMode == WorkMode::WindowAndTray); + }; + const auto tray = builder.addCheckbox({ + .id = u"advanced/tray"_q, + .title = tr::lng_settings_workmode_tray(), + .checked = trayEnabled(), + .keywords = { u"tray"_q, u"icon"_q, u"system"_q }, + }); + + const auto taskbarEnabled = [=] { + const auto workMode = settings->workMode(); + return (workMode == WorkMode::WindowOnly) + || (workMode == WorkMode::WindowAndTray); + }; + const auto taskbar = Platform::SkipTaskbarSupported() + ? builder.addCheckbox({ + .id = u"advanced/taskbar"_q, + .title = tr::lng_settings_workmode_window(), + .checked = taskbarEnabled(), + .keywords = { u"taskbar"_q, u"window"_q }, + }) + : nullptr; + + const auto monochrome = Platform::HasMonochromeSetting() + ? builder.addSlideCheckbox({ + .id = u"advanced/monochrome_icon"_q, + .title = tr::lng_settings_monochrome_icon(), + .checked = settings->trayIconMonochrome(), + .shown = tray + ? tray->checkedValue() + : rpl::single(trayEnabled()), + .keywords = { u"monochrome"_q, u"icon"_q, u"tray"_q }, + }) + : nullptr; + + if (monochrome && monochrome->entity()) { + monochrome->entity()->checkedChanges( + ) | rpl::filter([=](bool value) { + return (value != settings->trayIconMonochrome()); + }) | rpl::on_next([=](bool value) { + settings->setTrayIconMonochrome(value); + Core::App().saveSettingsDelayed(); + }, monochrome->lifetime()); + } + + const auto updateWorkmode = [=] { + const auto newMode = (tray && tray->checked()) + ? ((!taskbar || taskbar->checked()) + ? WorkMode::WindowAndTray + : WorkMode::TrayOnly) + : WorkMode::WindowOnly; + if ((newMode == WorkMode::WindowAndTray + || newMode == WorkMode::TrayOnly) + && settings->workMode() != newMode) { + cSetSeenTrayTooltip(false); + } + settings->setWorkMode(newMode); + Core::App().saveSettingsDelayed(); + }; + + if (tray) { + tray->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked != trayEnabled()); + }) | rpl::on_next([=](bool checked) { + if (!checked && taskbar && !taskbar->checked()) { + taskbar->setChecked(true); + } else { + updateWorkmode(); + } + }, tray->lifetime()); + } + + if (taskbar) { + taskbar->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked != taskbarEnabled()); + }) | rpl::on_next([=](bool checked) { + if (!checked && tray && !tray->checked()) { + tray->setChecked(true); + } else { + updateWorkmode(); + } + }, taskbar->lifetime()); + } + } + +#ifdef Q_OS_MAC + const auto warnBeforeQuit = builder.addCheckbox({ + .id = u"advanced/warn_before_quit"_q, + .title = tr::lng_settings_mac_warn_before_quit( + lt_text, + rpl::single(Platform::ConfirmQuit::QuitKeysString())), + .checked = settings->macWarnBeforeQuit(), + .keywords = { u"quit"_q, u"warn"_q, u"close"_q }, + }); + if (warnBeforeQuit) { + warnBeforeQuit->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked != settings->macWarnBeforeQuit()); + }) | rpl::on_next([=](bool checked) { + settings->setMacWarnBeforeQuit(checked); + Core::App().saveSettingsDelayed(); + }, warnBeforeQuit->lifetime()); + } + +#ifndef OS_MAC_STORE + const auto roundIconEnabled = [=] { + const auto digest = base::Platform::CurrentCustomAppIconDigest(); + return digest && (settings->macRoundIconDigest() == digest); + }; + const auto roundIcon = builder.addCheckbox({ + .id = u"advanced/round_icon"_q, + .title = tr::lng_settings_mac_round_icon(), + .checked = roundIconEnabled(), + .keywords = { u"icon"_q, u"round"_q, u"dock"_q }, + }); + if (roundIcon) { + roundIcon->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked != roundIconEnabled()); + }) | rpl::on_next([=](bool checked) { + const auto digest = checked + ? base::Platform::SetCustomAppIcon(IconMacRound()) + : std::optional(); + if (!checked) { + base::Platform::ClearCustomAppIcon(); + } + Window::OverrideApplicationIcon(checked ? IconMacRound() : QImage()); + Core::App().refreshApplicationIcon(); + settings->setMacRoundIconDigest(digest); + Core::App().saveSettings(); + }, roundIcon->lifetime()); + } +#endif // OS_MAC_STORE +#elif defined Q_OS_WIN // Q_OS_MAC + using Behavior = Core::Settings::CloseBehavior; + + const auto closeToTaskbarShown = container + ? container->lifetime().make_state>( + !Core::App().tray().has()) + : nullptr; + + if (closeToTaskbarShown) { + settings->workModeValue( + ) | rpl::on_next([=](WorkMode) { + *closeToTaskbarShown = !Core::App().tray().has(); + }, container->lifetime()); + } + + const auto closeToTaskbar = builder.addSlideCheckbox({ + .id = u"advanced/close_to_taskbar"_q, + .title = tr::lng_settings_close_to_taskbar(), + .checked = settings->closeBehavior() == Behavior::CloseToTaskbar, + .shown = closeToTaskbarShown + ? closeToTaskbarShown->value() + : rpl::single(false), + .keywords = { u"close"_q, u"taskbar"_q, u"minimize"_q }, + }); + if (closeToTaskbar && closeToTaskbar->entity()) { + closeToTaskbar->entity()->checkedChanges( + ) | rpl::map([=](bool checked) { + return checked ? Behavior::CloseToTaskbar : Behavior::Quit; + }) | rpl::filter([=](Behavior value) { + return (settings->closeBehavior() != value); + }) | rpl::on_next([=](Behavior value) { + settings->setCloseBehavior(value); + Local::writeSettings(); + }, closeToTaskbar->lifetime()); + } +#endif // Q_OS_MAC || Q_OS_WIN + + if (Platform::AutostartSupported()) { + const auto minimizedToggled = [=] { + return cStartMinimized() + && controller + && !controller->session().domain().local().hasLocalPasscode(); + }; + + const auto autostart = builder.addCheckbox({ + .id = u"advanced/autostart"_q, + .title = tr::lng_settings_auto_start(), + .checked = cAutoStart(), + .keywords = { u"autostart"_q, u"startup"_q, u"boot"_q }, + }); + + const auto minimized = builder.addSlideCheckbox({ + .id = u"advanced/start_minimized"_q, + .title = tr::lng_settings_start_min(), + .checked = minimizedToggled(), + .shown = autostart + ? autostart->checkedValue() + : rpl::single(cAutoStart()), + .keywords = { u"minimized"_q, u"startup"_q, u"hidden"_q }, + }); + + if (autostart) { + autostart->checkedChanges( + ) | rpl::filter([](bool checked) { + return (checked != cAutoStart()); + }) | rpl::on_next([=](bool checked) { + const auto weak = base::make_weak(controller); + cSetAutoStart(checked); + Platform::AutostartToggle(checked, crl::guard(autostart, [=]( + bool enabled) { + if (checked && !enabled && weak) { + weak->window().showToast( + Lang::Hard::AutostartEnableError()); + } + Ui::PostponeCall(autostart, [=] { + autostart->setChecked(enabled); + }); + if (enabled || !minimized || !minimized->entity()->checked()) { + Local::writeSettings(); + } else if (minimized) { + minimized->entity()->setChecked(false); + } + })); + }, autostart->lifetime()); + + if (controller) { + Platform::AutostartRequestStateFromSystem(crl::guard( + controller, + [=](bool enabled) { autostart->setChecked(enabled); })); + } + } + + if (minimized && minimized->entity() && controller) { + minimized->entity()->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked != minimizedToggled()); + }) | rpl::on_next([=](bool checked) { + if (controller->session().domain().local().hasLocalPasscode()) { + minimized->entity()->setChecked(false); + controller->show(Ui::MakeInformBox( + tr::lng_error_start_minimized_passcoded())); + } else { + cSetStartMinimized(checked); + Local::writeSettings(); + } + }, minimized->lifetime()); + + controller->session().domain().local().localPasscodeChanged( + ) | rpl::on_next([=] { + minimized->entity()->setChecked(minimizedToggled()); + }, minimized->lifetime()); + } + } + + if (Platform::IsWindows() && !Platform::IsWindowsStoreBuild()) { + const auto sendto = builder.addCheckbox({ + .id = u"advanced/sendto"_q, + .title = tr::lng_settings_add_sendto(), + .checked = cSendToMenu(), + .keywords = { u"sendto"_q, u"send"_q, u"menu"_q, u"context"_q }, + }); + if (sendto) { + sendto->checkedChanges( + ) | rpl::filter([](bool checked) { + return (checked != cSendToMenu()); + }) | rpl::on_next([](bool checked) { + cSetSendToMenu(checked); + psSendToMenu(checked); + Local::writeSettings(); + }, sendto->lifetime()); + } + } + + builder.addSkip(); +} + +#ifdef DESKTOP_APP_USE_ANGLE +void BuildANGLEOption( + SectionBuilder &builder, + Window::SessionController *controller) { + using ANGLE = Ui::GL::ANGLE; + + const auto options = std::vector{ + tr::lng_settings_angle_backend_auto(tr::now), + tr::lng_settings_angle_backend_d3d11(tr::now), + tr::lng_settings_angle_backend_d3d9(tr::now), + tr::lng_settings_angle_backend_d3d11on12(tr::now), + tr::lng_settings_angle_backend_disabled(tr::now), + }; + const auto disabled = int(options.size()) - 1; + const auto backendIndex = [=] { + if (Core::App().settings().disableOpenGL()) { + return disabled; + } else switch (Ui::GL::CurrentANGLE()) { + case ANGLE::Auto: return 0; + case ANGLE::D3D11: return 1; + case ANGLE::D3D9: return 2; + case ANGLE::D3D11on12: return 3; + } + Unexpected("Ui::GL::CurrentANGLE value in BuildANGLEOption."); + }(); + + builder.addSettingsButton({ + .id = u"advanced/angle_backend"_q, + .title = tr::lng_settings_angle_backend(), + .st = &st::settingsButtonNoIcon, + .label = rpl::single(options[backendIndex]), + .onClick = [=] { + if (!controller) { + return; + } + controller->show(Box([=](not_null box) { + const auto save = [=](int index) { + if (index == backendIndex) { + return; + } + const auto confirmed = crl::guard(box, [=] { + const auto nowDisabled = (index == disabled); + if (!nowDisabled) { + Ui::GL::ChangeANGLE([&] { + switch (index) { + case 0: return ANGLE::Auto; + case 1: return ANGLE::D3D11; + case 2: return ANGLE::D3D9; + case 3: return ANGLE::D3D11on12; + } + Unexpected("Index in BuildANGLEOption."); + }()); + } + const auto wasDisabled = (backendIndex == disabled); + if (nowDisabled != wasDisabled) { + Core::App().settings().setDisableOpenGL(nowDisabled); + Local::writeSettings(); + } + Core::Restart(); + }); + controller->show(Ui::MakeConfirmBox({ + .text = tr::lng_settings_need_restart(), + .confirmed = confirmed, + .confirmText = tr::lng_settings_restart_now(), + })); + }; + SingleChoiceBox(box, { + .title = tr::lng_settings_angle_backend(), + .options = options, + .initialSelection = backendIndex, + .callback = save, + }); + })); + }, + .keywords = { u"angle"_q, u"opengl"_q, u"d3d"_q, u"graphics"_q }, + }); +} +#else +void BuildOpenGLOption( + SectionBuilder &builder, + Window::SessionController *controller) { + const auto opengl = builder.addToggle({ + .id = u"advanced/opengl"_q, + .title = tr::lng_settings_enable_opengl(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(!Core::App().settings().disableOpenGL()), + .keywords = { u"opengl"_q, u"graphics"_q, u"gpu"_q }, + }); + + if (opengl && controller) { + opengl->toggledValue( + ) | rpl::filter([](bool enabled) { + return (enabled == Core::App().settings().disableOpenGL()); + }) | rpl::on_next([=](bool enabled) { + const auto confirmed = crl::guard(opengl, [=] { + Core::App().settings().setDisableOpenGL(!enabled); + Local::writeSettings(); + Core::Restart(); + }); + controller->show(Ui::MakeConfirmBox({ + .text = tr::lng_settings_need_restart(), + .confirmed = confirmed, + .confirmText = tr::lng_settings_restart_now(), + })); + }, opengl->lifetime()); + } +} +#endif + +void BuildPerformanceSection( + SectionBuilder &builder, + Window::SessionController *controller) { + builder.addDivider(); + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_performance()); + + builder.addSettingsButton({ + .id = u"advanced/power_saving"_q, + .title = tr::lng_settings_power_menu(), + .st = &st::settingsButtonNoIcon, + .onClick = [=] { + if (controller) { + controller->window().show(Box(PowerSavingBox)); + } + }, + .keywords = { u"power"_q, u"saving"_q, u"battery"_q, u"animation"_q }, + }); + + const auto hwAccel = builder.addToggle({ + .id = u"advanced/hw_accel"_q, + .title = tr::lng_settings_enable_hwaccel(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single( + Core::App().settings().hardwareAcceleratedVideo()), + .keywords = { u"hardware"_q, u"acceleration"_q, u"video"_q }, + }); + + if (hwAccel) { + hwAccel->toggledValue( + ) | rpl::filter([](bool enabled) { + return (enabled != + Core::App().settings().hardwareAcceleratedVideo()); + }) | rpl::on_next([=](bool enabled) { + Core::App().settings().setHardwareAcceleratedVideo(enabled); + Core::App().saveSettingsDelayed(); + }, hwAccel->lifetime()); + } + +#ifdef DESKTOP_APP_USE_ANGLE + BuildANGLEOption(builder, controller); +#else + if constexpr (!Platform::IsMac()) { + BuildOpenGLOption(builder, controller); + } +#endif + + builder.addSkip(); +} + +void BuildSpellcheckerSection( + SectionBuilder &builder, + Window::SessionController *controller) { +#ifndef TDESKTOP_DISABLE_SPELLCHECK + const auto session = controller ? &controller->session() : nullptr; + const auto settings = &Core::App().settings(); + const auto isSystem = Platform::Spellchecker::IsSystemSpellchecker(); + const auto container = builder.container(); + + builder.addDivider(); + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_spellchecker()); + + const auto spellchecker = builder.addToggle({ + .id = u"advanced/spellchecker"_q, + .title = isSystem + ? tr::lng_settings_system_spellchecker() + : tr::lng_settings_custom_spellchecker(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(settings->spellcheckerEnabled()), + .keywords = { u"spellcheck"_q, u"spelling"_q, u"dictionary"_q }, + }); + + if (spellchecker) { + spellchecker->toggledValue( + ) | rpl::filter([=](bool enabled) { + return (enabled != settings->spellcheckerEnabled()); + }) | rpl::on_next([=](bool enabled) { + settings->setSpellcheckerEnabled(enabled); + Core::App().saveSettingsDelayed(); + }, spellchecker->lifetime()); + } + + const auto sliding = (!isSystem && container) + ? container->add( + object_ptr>( + container, + object_ptr(container))) + : nullptr; + const auto inner = sliding ? sliding->entity() : nullptr; + + if (!isSystem) { + const auto autoDownload = builder.addToggle({ + .id = u"advanced/auto_download_dictionaries"_q, + .title = tr::lng_settings_auto_download_dictionaries(), + .st = &st::settingsButtonNoIcon, + .container = inner, + .toggled = rpl::single(settings->autoDownloadDictionaries()), + .keywords = { u"dictionary"_q, u"download"_q, u"spellcheck"_q }, + }); + + if (autoDownload) { + autoDownload->toggledValue( + ) | rpl::filter([=](bool enabled) { + return (enabled != settings->autoDownloadDictionaries()); + }) | rpl::on_next([=](bool enabled) { + settings->setAutoDownloadDictionaries(enabled); + Core::App().saveSettingsDelayed(); + }, autoDownload->lifetime()); + } + + builder.addLabeledButton({ + .id = u"advanced/manage_dictionaries"_q, + .title = tr::lng_settings_manage_dictionaries(), + .st = &st::settingsButtonNoIcon, + .container = inner, + .label = session + ? Spellchecker::ButtonManageDictsState(session) + : rpl::single(QString()), + .onClick = [=] { + if (controller) { + controller->show( + Box(&controller->session())); + } + }, + .keywords = { u"dictionary"_q, u"manage"_q, u"spellcheck"_q }, + }); + + if (spellchecker && sliding) { + spellchecker->toggledValue( + ) | rpl::on_next([=](bool enabled) { + sliding->toggle(enabled, anim::type::normal); + }, container->lifetime()); + } + } + + builder.addSkip(); +#endif // !TDESKTOP_DISABLE_SPELLCHECK +} + +void BuildUpdateSection( + SectionBuilder &builder, + Window::SessionController *controller, + bool atTop) { + if (!HasUpdate()) { + return; + } + const auto container = builder.container(); + + if (!atTop) { + builder.addDivider(); + } + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_version_info()); + + const auto version = tr::lng_settings_current_version( + tr::now, + lt_version, + currentVersionText()); + + const auto texts = container + ? Ui::CreateChild>(container) + : nullptr; + const auto downloading = container + ? Ui::CreateChild>(container) + : nullptr; + + const auto toggle = builder.addToggle({ + .id = u"advanced/auto_update"_q, + .title = tr::lng_settings_update_automatically(), + .st = &st::settingsUpdateToggle, + .toggled = rpl::single(cAutoUpdate()), + .keywords = { u"update"_q, u"automatic"_q, u"version"_q }, + }); + + if (toggle) { + const auto label = Ui::CreateChild( + toggle, + texts->events(), + st::settingsUpdateState); + + rpl::combine( + toggle->widthValue(), + label->widthValue() + ) | rpl::on_next([=] { + label->moveToLeft( + st::settingsUpdateStatePosition.x(), + st::settingsUpdateStatePosition.y()); + }, label->lifetime()); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + } + + const auto options = container + ? container->add( + object_ptr>( + container, + object_ptr(container))) + : nullptr; + const auto inner = options ? options->entity() : nullptr; + + const auto install = cAlphaVersion() + ? nullptr + : builder.addToggle({ + .id = u"advanced/install_beta"_q, + .title = tr::lng_settings_install_beta(), + .st = &st::settingsButtonNoIcon, + .container = inner, + .toggled = rpl::single(cInstallBetaVersion()), + .keywords = { u"beta"_q, u"update"_q, u"version"_q }, + }); + + const auto check = builder.addSettingsButton({ + .id = u"advanced/check_update"_q, + .title = tr::lng_settings_check_now(), + .st = &st::settingsButtonNoIcon, + .container = inner, + .onClick = [] { + Core::UpdateChecker checker; + cSetLastUpdateCheck(0); + checker.start(); + }, + .keywords = { u"check"_q, u"update"_q, u"version"_q }, + }); + + if (check && container) { + const auto update = Ui::CreateChild( + check, + tr::lng_update_telegram(), + st::settingsUpdate); + update->hide(); + check->widthValue() | rpl::on_next([=](int width) { + update->resizeToWidth(width); + update->moveToLeft(0, 0); + }, update->lifetime()); + + const auto showDownloadProgress = [=](int64 ready, int64 total) { + texts->fire(tr::lng_settings_downloading_update( + tr::now, + lt_progress, + Ui::FormatDownloadText(ready, total))); + downloading->fire(true); + }; + const auto setDefaultStatus = [=]( + const Core::UpdateChecker &checker) { + using State = Core::UpdateChecker::State; + const auto state = checker.state(); + switch (state) { + case State::Download: + showDownloadProgress(checker.already(), checker.size()); + break; + case State::Ready: + texts->fire(tr::lng_settings_update_ready(tr::now)); + update->show(); + break; + default: + texts->fire_copy(version); + break; + } + }; + + toggle->toggledValue( + ) | rpl::filter([](bool toggled) { + return (toggled != cAutoUpdate()); + }) | rpl::on_next([=](bool toggled) { + cSetAutoUpdate(toggled); + Local::writeSettings(); + Core::UpdateChecker checker; + if (cAutoUpdate()) { + checker.start(); + } else { + checker.stop(); + setDefaultStatus(checker); + } + }, toggle->lifetime()); + + if (install) { + install->toggledValue( + ) | rpl::filter([](bool toggled) { + return (toggled != cInstallBetaVersion()); + }) | rpl::on_next([=](bool toggled) { + cSetInstallBetaVersion(toggled); + Core::Launcher::Instance().writeInstallBetaVersionsSetting(); + Core::UpdateChecker checker; + checker.stop(); + if (toggled) { + cSetLastUpdateCheck(0); + } + checker.start(); + }, toggle->lifetime()); + } + + Core::UpdateChecker checker; + options->toggleOn(rpl::combine( + toggle->toggledValue(), + downloading->events_starting_with( + checker.state() == Core::UpdateChecker::State::Download) + ) | rpl::map([](bool check, bool downloading) { + return check && !downloading; + })); + + checker.checking() | rpl::on_next([=] { + options->setAttribute(Qt::WA_TransparentForMouseEvents); + texts->fire(tr::lng_settings_update_checking(tr::now)); + downloading->fire(false); + }, options->lifetime()); + checker.isLatest() | rpl::on_next([=] { + options->setAttribute(Qt::WA_TransparentForMouseEvents, false); + texts->fire(tr::lng_settings_latest_installed(tr::now)); + downloading->fire(false); + }, options->lifetime()); + checker.progress( + ) | rpl::on_next([=](Core::UpdateChecker::Progress progress) { + showDownloadProgress(progress.already, progress.size); + }, options->lifetime()); + checker.failed() | rpl::on_next([=] { + options->setAttribute(Qt::WA_TransparentForMouseEvents, false); + texts->fire(tr::lng_settings_update_fail(tr::now)); + downloading->fire(false); + }, options->lifetime()); + checker.ready() | rpl::on_next([=] { + options->setAttribute(Qt::WA_TransparentForMouseEvents, false); + texts->fire(tr::lng_settings_update_ready(tr::now)); + update->show(); + downloading->fire(false); + }, options->lifetime()); + + setDefaultStatus(checker); + + update->setClickedCallback([] { + if (!Core::UpdaterDisabled()) { + Core::checkReadyUpdate(); + } + Core::Restart(); + }); + } + + builder.addSkip(); + if (atTop) { + builder.addDivider(); + } +} + +void BuildExportSection( + SectionBuilder &builder, + Window::SessionController *controller, + Fn showOther) { + builder.addSkip(); + builder.addDivider(); + builder.addSkip(); + + builder.addSettingsButton({ + .id = u"advanced/export"_q, + .title = tr::lng_settings_export_data(), + .icon = { &st::menuIconExport }, + .onClick = [=] { + if (!controller) { + return; + } + const auto session = &controller->session(); + controller->window().hideSettingsAndLayer(); + base::call_delayed( + st::boxDuration, + session, + [=] { Core::App().exportManager().start(session); }); + }, + .keywords = { u"export"_q, u"data"_q, u"backup"_q }, + }); + + builder.addSettingsButton({ + .id = u"advanced/experimental"_q, + .title = tr::lng_settings_experimental(), + .icon = { &st::menuIconExperimental }, + .onClick = [showOther] { + if (showOther) { + showOther(Experimental::Id()); + } + }, + .keywords = { u"experimental"_q, u"beta"_q, u"features"_q }, + }); +} + +void BuildAdvancedSectionContent( + SectionBuilder &builder, + Window::SessionController *controller, + Fn showOther) { + const auto autoUpdate = cAutoUpdate(); + + if (!autoUpdate) { + BuildUpdateSection(builder, controller, true); + } + BuildDataStorageSection(builder, controller); + BuildAutoDownloadSection(builder, controller); + BuildWindowTitleSection(builder, controller); + BuildSystemIntegrationSection(builder, controller); + BuildPerformanceSection(builder, controller); + BuildSpellcheckerSection(builder, controller); + if (autoUpdate) { + BuildUpdateSection(builder, controller, false); + } + BuildExportSection(builder, controller, showOther); +} + +} // namespace + +void AdvancedSection( + not_null container, + not_null controller, + Fn showOther, + rpl::producer<> showFinished) { + auto &lifetime = container->lifetime(); + const auto highlights = lifetime.make_state(); + + SectionBuilder builder(WidgetContext{ + .container = container, + .controller = controller, + .showOther = showOther, + .isPaused = [] { return false; }, + .highlights = highlights, + }); + + BuildAdvancedSectionContent(builder, controller, showOther); + + std::move(showFinished) | rpl::on_next([=] { + for (const auto &[id, entry] : *highlights) { + if (entry.widget) { + controller->checkHighlightControl( + id, + entry.widget, + base::duplicate(entry.args)); + } + } + }, lifetime); +} + +std::vector AdvancedSectionForSearch() { + std::vector entries; + SectionBuilder builder(SearchContext{ .entries = &entries }); + BuildAdvancedSectionContent(builder, nullptr, nullptr); + return entries; +} + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/builder/settings_advanced_builder.h b/Telegram/SourceFiles/settings/builder/settings_advanced_builder.h new file mode 100644 index 0000000000..58e35b0315 --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_advanced_builder.h @@ -0,0 +1,31 @@ +/* +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 "settings/settings_type.h" +#include "settings/builder/settings_builder.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace Settings::Builder { + +void AdvancedSection( + not_null container, + not_null controller, + Fn showOther, + rpl::producer<> showFinished); + +[[nodiscard]] std::vector AdvancedSectionForSearch(); + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/builder/settings_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_builder.cpp index eacd0ee03c..9177665a87 100644 --- a/Telegram/SourceFiles/settings/builder/settings_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_builder.cpp @@ -7,9 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/builder/settings_builder.h" +#include "boxes/edit_privacy_box.h" +#include "main/main_session.h" #include "settings/settings_common.h" +#include "settings/settings_privacy_security.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" @@ -61,16 +65,17 @@ Ui::SettingsButton *SectionBuilder::addSettingsButton(ButtonArgs &&args) { auto highlight = std::move(args.highlight); const auto id = args.id; const auto button = v::match(_context, [&](const WidgetContext &ctx) -> Ui::SettingsButton* { + const auto target = args.container ? args.container : ctx.container.get(); if (args.label) { return AddButtonWithLabel( - ctx.container, + target, rpl::duplicate(args.title), std::move(args.label), st, std::move(args.icon)); } else { - return ctx.container->add(CreateButtonWithIcon( - ctx.container, + return target->add(CreateButtonWithIcon( + target, rpl::duplicate(args.title), st, std::move(args.icon))); @@ -256,14 +261,51 @@ Ui::SettingsButton *SectionBuilder::addPremiumButton(PremiumButtonArgs &&args) { }); } +Ui::SettingsButton *SectionBuilder::addPrivacyButton(PrivacyButtonArgs &&args) { + const auto id = args.id; + const auto premium = args.premium; + auto title = std::move(args.title); + return v::match(_context, [&](const WidgetContext &ctx) + -> Ui::SettingsButton* { + const auto button = AddPrivacyButton( + ctx.controller, + ctx.container, + rpl::duplicate(title), + {}, + args.key, + std::move(args.controllerFactory)); + if (premium) { + AddPrivacyPremiumStar( + button, + &ctx.controller->session(), + rpl::duplicate(title), + st::settingsButtonNoIcon.padding); + } + if (!id.isEmpty()) { + registerHighlight(id, button, {}); + } + return button; + }, [&](const SearchContext &ctx) -> Ui::SettingsButton* { + if (!args.id.isEmpty()) { + ctx.entries->push_back({ + .id = std::move(args.id), + .title = ResolveTitle(std::move(title)), + .keywords = std::move(args.keywords), + }); + } + return nullptr; + }); +} + Ui::SettingsButton *SectionBuilder::addToggle(ToggleArgs &&args) { auto highlight = std::move(args.highlight); const auto id = args.id; const auto button = v::match(_context, [&](const WidgetContext &ctx) -> Ui::SettingsButton* { const auto &st = args.st ? *args.st : st::settingsButton; - const auto button = ctx.container->add(CreateButtonWithIcon( - ctx.container, + const auto target = args.container ? args.container : ctx.container.get(); + const auto button = target->add(CreateButtonWithIcon( + target, rpl::duplicate(args.title), st, std::move(args.icon))); @@ -317,6 +359,77 @@ Ui::SlideWrap *SectionBuilder::addSlideToggle( }); } +Ui::Checkbox *SectionBuilder::addCheckbox(CheckboxArgs &&args) { + return v::match(_context, [&](const WidgetContext &ctx) -> Ui::Checkbox* { + const auto checkbox = ctx.container->add( + object_ptr( + ctx.container, + ResolveTitle(rpl::duplicate(args.title)), + args.checked, + st::settingsCheckbox), + st::settingsCheckboxPadding); + return checkbox; + }, [&](const SearchContext &ctx) -> Ui::Checkbox* { + if (!args.id.isEmpty()) { + ctx.entries->push_back({ + .id = std::move(args.id), + .title = ResolveTitle(std::move(args.title)), + .keywords = std::move(args.keywords), + }); + } + return nullptr; + }); +} + +Ui::SlideWrap *SectionBuilder::addSlideCheckbox( + SlideCheckboxArgs &&args) { + return v::match(_context, [&](const WidgetContext &ctx) + -> Ui::SlideWrap* { + const auto wrap = ctx.container->add( + object_ptr>( + ctx.container, + object_ptr( + ctx.container, + ResolveTitle(rpl::duplicate(args.title)), + args.checked, + st::settingsCheckbox), + st::settingsCheckboxPadding)); + if (args.shown) { + wrap->toggleOn(std::move(args.shown)); + } + return wrap; + }, [&](const SearchContext &ctx) -> Ui::SlideWrap* { + if (!args.id.isEmpty()) { + ctx.entries->push_back({ + .id = std::move(args.id), + .title = ResolveTitle(std::move(args.title)), + .keywords = std::move(args.keywords), + }); + } + return nullptr; + }); +} + +Ui::SlideWrap *SectionBuilder::addSlideSection( + SlideSectionArgs &&args) { + return v::match(_context, [&](const WidgetContext &ctx) + -> Ui::SlideWrap* { + const auto wrap = ctx.container->add( + object_ptr>( + ctx.container, + object_ptr(ctx.container))); + if (args.shown) { + wrap->toggleOn(std::move(args.shown)); + } + if (args.fill) { + args.fill(wrap->entity()); + } + return wrap; + }, [](const SearchContext &) -> Ui::SlideWrap* { + return nullptr; + }); +} + void SectionBuilder::addSubsectionTitle(rpl::producer text) { v::match(_context, [&](const WidgetContext &ctx) { AddSubsectionTitle(ctx.container, std::move(text)); diff --git a/Telegram/SourceFiles/settings/builder/settings_builder.h b/Telegram/SourceFiles/settings/builder/settings_builder.h index 8651cd2187..dea686411a 100644 --- a/Telegram/SourceFiles/settings/builder/settings_builder.h +++ b/Telegram/SourceFiles/settings/builder/settings_builder.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "api/api_user_privacy.h" #include "base/object_ptr.h" #include "settings/settings_common.h" #include "settings/settings_type.h" @@ -14,10 +15,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +class EditPrivacyController; + namespace Ui { class RpWidget; class VerticalLayout; class SettingsButton; +class Checkbox; } // namespace Ui namespace Window { @@ -80,6 +84,7 @@ public: rpl::producer title; const style::SettingsButton *st = nullptr; IconDescriptor icon; + Ui::VerticalLayout *container = nullptr; rpl::producer label; Fn onClick; QStringList keywords; @@ -131,11 +136,22 @@ public: }; Ui::SettingsButton *addPremiumButton(PremiumButtonArgs &&args); + struct PrivacyButtonArgs { + QString id; + rpl::producer title; + Api::UserPrivacy::Key key; + Fn()> controllerFactory; + bool premium = false; + QStringList keywords; + }; + Ui::SettingsButton *addPrivacyButton(PrivacyButtonArgs &&args); + struct ToggleArgs { QString id; rpl::producer title; const style::SettingsButton *st = nullptr; IconDescriptor icon; + Ui::VerticalLayout *container = nullptr; rpl::producer toggled; QStringList keywords; HighlightDescriptor highlight; @@ -153,6 +169,29 @@ public: }; Ui::SlideWrap *addSlideToggle(SlideToggleArgs &&args); + struct CheckboxArgs { + QString id; + rpl::producer title; + bool checked = false; + QStringList keywords; + }; + Ui::Checkbox *addCheckbox(CheckboxArgs &&args); + + struct SlideCheckboxArgs { + QString id; + rpl::producer title; + bool checked = false; + rpl::producer shown; + QStringList keywords; + }; + Ui::SlideWrap *addSlideCheckbox(SlideCheckboxArgs &&args); + + struct SlideSectionArgs { + rpl::producer shown; + Fn)> fill; + }; + Ui::SlideWrap *addSlideSection(SlideSectionArgs &&args); + void addSubsectionTitle(rpl::producer text); void addDivider(); void addDividerText(rpl::producer text); diff --git a/Telegram/SourceFiles/settings/builder/settings_chat_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_chat_builder.cpp new file mode 100644 index 0000000000..a667c83684 --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_chat_builder.cpp @@ -0,0 +1,170 @@ +/* +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 "settings/builder/settings_chat_builder.h" + +#include "base/timer_rpl.h" +#include "core/application.h" +#include "core/core_settings.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/builder/settings_builder.h" +#include "settings/settings_chat.h" +#include "settings/settings_experimental.h" +#include "settings/settings_privacy_security.h" +#include "settings/settings_shortcuts.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +namespace Settings::Builder { +namespace { + +void BuildChatSectionContent( + SectionBuilder &builder, + Window::SessionController *controller, + Fn showOther) { + if (!controller) { + builder.addSettingsButton({ + .id = u"appearance/theme"_q, + .title = tr::lng_settings_themes(), + .keywords = { u"theme"_q, u"color"_q, u"dark"_q, u"light"_q }, + }); + builder.addSettingsButton({ + .id = u"appearance/background"_q, + .title = tr::lng_settings_bg_from_gallery(), + .keywords = { u"background"_q, u"wallpaper"_q, u"image"_q }, + }); + builder.addSettingsButton({ + .id = u"appearance/quick_action"_q, + .title = tr::lng_settings_quick_dialog_action_title(), + .keywords = { u"swipe"_q, u"quick"_q, u"action"_q }, + }); + builder.addSettingsButton({ + .id = u"appearance/stickers"_q, + .title = tr::lng_stickers_you_have(), + .keywords = { u"stickers"_q, u"packs"_q }, + }); + builder.addSettingsButton({ + .id = u"appearance/emoji_sets"_q, + .title = tr::lng_emoji_manage_sets(), + .keywords = { u"emoji"_q, u"sets"_q, u"packs"_q }, + }); + builder.addCheckbox({ + .id = u"appearance/large_emoji"_q, + .title = tr::lng_settings_large_emoji(), + .checked = true, + .keywords = { u"emoji"_q, u"large"_q, u"big"_q }, + }); + builder.addCheckbox({ + .id = u"appearance/replace_emoji"_q, + .title = tr::lng_settings_replace_emojis(), + .checked = true, + .keywords = { u"emoji"_q, u"replace"_q, u"convert"_q }, + }); + builder.addCheckbox({ + .id = u"appearance/suggest_emoji"_q, + .title = tr::lng_settings_suggest_emoji(), + .checked = true, + .keywords = { u"emoji"_q, u"suggest"_q, u"autocomplete"_q }, + }); + builder.addCheckbox({ + .id = u"appearance/suggest_stickers"_q, + .title = tr::lng_settings_suggest_by_emoji(), + .checked = true, + .keywords = { u"stickers"_q, u"emoji"_q, u"suggest"_q }, + }); + builder.addCheckbox({ + .id = u"appearance/loop_stickers"_q, + .title = tr::lng_settings_loop_stickers(), + .checked = true, + .keywords = { u"stickers"_q, u"loop"_q, u"animate"_q }, + }); + builder.addSettingsButton({ + .id = u"appearance/send_by_enter"_q, + .title = tr::lng_settings_send_enter(), + .keywords = { u"send"_q, u"enter"_q, u"message"_q }, + }); + builder.addToggle({ + .id = u"appearance/sensitive"_q, + .title = tr::lng_settings_sensitive_disable_filtering(), + .toggled = rpl::single(false), + .keywords = { u"sensitive"_q, u"nsfw"_q, u"adult"_q }, + }); + builder.addSettingsButton({ + .id = u"data/shortcuts"_q, + .title = tr::lng_settings_shortcuts(), + .keywords = { u"shortcuts"_q, u"keyboard"_q, u"hotkeys"_q }, + }); + builder.addSettingsButton({ + .id = u"data/archive"_q, + .title = tr::lng_context_archive_settings(), + .keywords = { u"archive"_q, u"settings"_q }, + }); + return; + } + + const auto container = builder.container(); + + auto updateOnTick = rpl::single( + ) | rpl::then(base::timer_each(60 * crl::time(1000))); + + SetupThemeOptions(controller, container); + SetupThemeSettings(controller, container); + SetupCloudThemes(controller, container); + SetupChatBackground(controller, container); + SetupChatListQuickAction(controller, container); + SetupStickersEmoji(controller, container); + SetupMessages(controller, container); + Ui::AddDivider(container); + SetupSensitiveContent(controller, container, std::move(updateOnTick)); + SetupArchive(controller, container, showOther); +} + +} // namespace + +void ChatSection( + not_null container, + not_null controller, + Fn showOther, + rpl::producer<> showFinished) { + auto &lifetime = container->lifetime(); + const auto highlights = lifetime.make_state(); + + SectionBuilder builder(WidgetContext{ + .container = container, + .controller = controller, + .showOther = showOther, + .isPaused = [] { return false; }, + .highlights = highlights, + }); + + BuildChatSectionContent(builder, controller, showOther); + + std::move(showFinished) | rpl::on_next([=] { + for (const auto &[id, entry] : *highlights) { + if (entry.widget) { + controller->checkHighlightControl( + id, + entry.widget, + base::duplicate(entry.args)); + } + } + }, lifetime); +} + +std::vector ChatSectionForSearch() { + std::vector entries; + SectionBuilder builder(SearchContext{ .entries = &entries }); + BuildChatSectionContent(builder, nullptr, nullptr); + return entries; +} + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/builder/settings_chat_builder.h b/Telegram/SourceFiles/settings/builder/settings_chat_builder.h new file mode 100644 index 0000000000..ee85b51b8c --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_chat_builder.h @@ -0,0 +1,31 @@ +/* +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 "settings/settings_type.h" +#include "settings/builder/settings_builder.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace Settings::Builder { + +void ChatSection( + not_null container, + not_null controller, + Fn showOther, + rpl::producer<> showFinished); + +[[nodiscard]] std::vector ChatSectionForSearch(); + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.cpp new file mode 100644 index 0000000000..77fa3d29f3 --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.cpp @@ -0,0 +1,726 @@ +/* +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 "settings/builder/settings_privacy_security_builder.h" + +#include "api/api_authorizations.h" +#include "api/api_blocked_peers.h" +#include "api/api_cloud_password.h" +#include "api/api_global_privacy.h" +#include "api/api_self_destruct.h" +#include "api/api_sensitive_content.h" +#include "api/api_user_privacy.h" +#include "api/api_websites.h" +#include "apiwrap.h" +#include "main/main_app_config.h" +#include "ui/chat/chat_style.h" +#include "base/timer_rpl.h" +#include "boxes/edit_privacy_box.h" +#include "boxes/self_destruction_box.h" +#include "core/application.h" +#include "core/core_cloud_password.h" +#include "core/core_settings.h" +#include "data/components/passkeys.h" +#include "data/components/top_peers.h" +#include "data/data_peer_values.h" +#include "lang/lang_keys.h" +#include "main/main_domain.h" +#include "main/main_session.h" +#include "platform/platform_webauthn.h" +#include "settings/builder/settings_builder.h" +#include "settings/cloud_password/settings_cloud_password_email_confirm.h" +#include "settings/cloud_password/settings_cloud_password_input.h" +#include "settings/cloud_password/settings_cloud_password_start.h" +#include "settings/settings_active_sessions.h" +#include "settings/settings_blocked_peers.h" +#include "settings/settings_global_ttl.h" +#include "settings/settings_local_passcode.h" +#include "settings/settings_passkeys.h" +#include "settings/settings_privacy_controllers.h" +#include "settings/settings_privacy_security.h" +#include "settings/settings_websites.h" +#include "storage/storage_domain.h" +#include "ui/layers/generic_box.h" +#include "ui/text/format_values.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +namespace Settings::Builder { +namespace { + +constexpr auto kUpdateTimeout = 60 * crl::time(1000); + +using Privacy = Api::UserPrivacy; + +void BuildSecuritySection( + SectionBuilder &builder, + not_null controller, + rpl::producer<> updateTrigger, + Fn showOther) { + const auto session = &controller->session(); + + builder.addSkip(st::settingsPrivacySkip); + builder.addSubsectionTitle(tr::lng_settings_security()); + + using State = Core::CloudPasswordState; + enum class PasswordState { + Loading, + On, + Off, + Unconfirmed, + }; + auto passwordState = rpl::single( + PasswordState::Loading + ) | rpl::then(session->api().cloudPassword().state( + ) | rpl::map([](const State &state) { + return (!state.unconfirmedPattern.isEmpty()) + ? PasswordState::Unconfirmed + : state.hasPassword + ? PasswordState::On + : PasswordState::Off; + })) | rpl::distinct_until_changed(); + + auto cloudPasswordLabel = rpl::duplicate( + passwordState + ) | rpl::map([=](PasswordState state) { + return (state == PasswordState::Loading) + ? tr::lng_profile_loading(tr::now) + : (state == PasswordState::On) + ? tr::lng_settings_cloud_password_on(tr::now) + : tr::lng_settings_cloud_password_off(tr::now); + }); + + builder.addSettingsButton({ + .id = u"security/cloud_password"_q, + .title = tr::lng_settings_cloud_password_start_title(), + .icon = { &st::menuIcon2SV }, + .label = std::move(cloudPasswordLabel), + .onClick = [=, passwordState = base::duplicate(passwordState)] { + const auto state = rpl::variable( + base::duplicate(passwordState)).current(); + if (state == PasswordState::Loading) { + return; + } else if (state == PasswordState::On) { + showOther(CloudPasswordInputId()); + } else if (state == PasswordState::Off) { + showOther(CloudPasswordStartId()); + } else if (state == PasswordState::Unconfirmed) { + showOther(CloudPasswordEmailConfirmId()); + } + }, + .keywords = { u"password"_q, u"2fa"_q, u"two-factor"_q }, + }); + + session->api().cloudPassword().reload(); + + auto ttlLabel = rpl::combine( + session->api().selfDestruct().periodDefaultHistoryTTL(), + tr::lng_settings_ttl_after_off() + ) | rpl::map([](int ttl, const QString &none) { + return ttl ? Ui::FormatTTL(ttl) : none; + }); + + builder.addSettingsButton({ + .id = u"security/ttl"_q, + .title = tr::lng_settings_ttl_title(), + .icon = { &st::menuIconTTL }, + .label = std::move(ttlLabel), + .onClick = [showOther] { + showOther(GlobalTTLId()); + }, + .keywords = { u"ttl"_q, u"auto-delete"_q, u"timer"_q }, + }); + + rpl::duplicate( + updateTrigger + ) | rpl::on_next([=] { + session->api().selfDestruct().reload(); + }, builder.container()->lifetime()); + + auto passcodeHas = rpl::single(rpl::empty) | rpl::then( + session->domain().local().localPasscodeChanged() + ) | rpl::map([=] { + return session->domain().local().hasLocalPasscode(); + }); + auto passcodeLabel = rpl::combine( + tr::lng_settings_cloud_password_on(), + tr::lng_settings_cloud_password_off(), + rpl::duplicate(passcodeHas) + ) | rpl::map([](const QString &on, const QString &off, bool has) { + return has ? on : off; + }); + + builder.addSettingsButton({ + .id = u"security/passcode"_q, + .title = tr::lng_settings_passcode_title(), + .icon = { &st::menuIconLock }, + .label = std::move(passcodeLabel), + .onClick = [=, passcodeHas = std::move(passcodeHas)]() mutable { + if (rpl::variable(std::move(passcodeHas)).current()) { + showOther(LocalPasscodeCheckId()); + } else { + showOther(LocalPasscodeCreateId()); + } + }, + .keywords = { u"passcode"_q, u"lock"_q, u"pin"_q }, + }); + + if (session->passkeys().possible()) { + auto passkeysLabel = rpl::combine( + tr::lng_profile_loading(), + (rpl::single(rpl::empty_value()) + | rpl::then(session->passkeys().requestList())) | rpl::map([=] { + return session->passkeys().list().size(); + }) + ) | rpl::map([=](const QString &loading, int count) { + return !session->passkeys().listKnown() + ? loading + : count == 1 + ? session->passkeys().list().front().name + : count + ? QString::number(count) + : tr::lng_settings_cloud_password_off(tr::now); + }); + + auto passkeysShown = (rpl::single(rpl::empty_value()) + | rpl::then(session->passkeys().requestList())) | rpl::map([=] { + return Platform::WebAuthn::IsSupported() + || !session->passkeys().list().empty(); + }); + + builder.addSlideLabeledButton({ + .id = u"security/passkeys"_q, + .title = tr::lng_settings_passkeys_title(), + .icon = { &st::menuIconPermissions }, + .label = std::move(passkeysLabel), + .shown = std::move(passkeysShown), + .onClick = [=] { + if (!session->passkeys().listKnown()) { + return; + } + const auto count = session->passkeys().list().size(); + if (count == 0) { + controller->show(Box([=](not_null box) { + PasskeysNoneBox(box, session); + box->boxClosing() | rpl::on_next([=] { + if (session->passkeys().list().size()) { + controller->showSettings(PasskeysId()); + } + }, box->lifetime()); + })); + } else { + controller->showSettings(PasskeysId()); + } + }, + .keywords = { u"passkeys"_q, u"biometric"_q }, + }); + } + + auto blockedCount = rpl::combine( + session->api().blockedPeers().slice( + ) | rpl::map([](const Api::BlockedPeers::Slice &data) { + return data.total; + }), + tr::lng_settings_no_blocked_users() + ) | rpl::map([](int count, const QString &none) { + return count ? QString::number(count) : none; + }); + + builder.addSettingsButton({ + .id = u"security/blocked"_q, + .title = tr::lng_settings_blocked_users(), + .icon = { &st::menuIconBlock }, + .label = std::move(blockedCount), + .onClick = [=] { + showOther(Blocked::Id()); + }, + .keywords = { u"blocked"_q, u"ban"_q }, + }); + + rpl::duplicate( + updateTrigger + ) | rpl::on_next([=] { + session->api().blockedPeers().reload(); + }, builder.container()->lifetime()); + + auto websitesCount = session->api().websites().totalValue(); + auto websitesShown = rpl::duplicate(websitesCount) | rpl::map( + rpl::mappers::_1 > 0); + auto websitesLabel = rpl::duplicate( + websitesCount + ) | rpl::filter(rpl::mappers::_1 > 0) | rpl::map([](int count) { + return QString::number(count); + }); + + builder.addSlideLabeledButton({ + .id = u"security/websites"_q, + .title = tr::lng_settings_logged_in(), + .icon = { &st::menuIconIpAddress }, + .label = std::move(websitesLabel), + .shown = std::move(websitesShown), + .onClick = [=] { + showOther(Websites::Id()); + }, + .keywords = { u"websites"_q, u"bots"_q, u"logged"_q }, + }); + + rpl::duplicate( + updateTrigger + ) | rpl::on_next([=] { + session->api().websites().reload(); + }, builder.container()->lifetime()); + + auto sessionsCount = session->api().authorizations().totalValue( + ) | rpl::map([](int count) { + return count ? QString::number(count) : QString(); + }); + + builder.addSettingsButton({ + .id = u"security/sessions"_q, + .title = tr::lng_settings_show_sessions(), + .icon = { &st::menuIconDevices }, + .label = std::move(sessionsCount), + .onClick = [=] { + showOther(Sessions::Id()); + }, + .keywords = { u"sessions"_q, u"devices"_q, u"active"_q }, + }); + + std::move( + updateTrigger + ) | rpl::on_next([=] { + session->api().authorizations().reload(); + }, builder.container()->lifetime()); + + builder.addSkip(); + builder.addDividerText(tr::lng_settings_sessions_about()); +} + +void BuildPrivacySection( + SectionBuilder &builder, + not_null controller) { + const auto session = &controller->session(); + + builder.addSkip(st::settingsPrivacySkip); + builder.addSubsectionTitle(tr::lng_settings_privacy_title()); + + using Key = Privacy::Key; + + builder.addPrivacyButton({ + .id = u"privacy/phone_number"_q, + .title = tr::lng_settings_phone_number_privacy(), + .key = Key::PhoneNumber, + .controllerFactory = [=] { + return std::make_unique(controller); + }, + .keywords = { u"phone"_q, u"number"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/last_seen"_q, + .title = tr::lng_settings_last_seen(), + .key = Key::LastSeen, + .controllerFactory = [=] { + return std::make_unique(session); + }, + .keywords = { u"last seen"_q, u"online"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/profile_photo"_q, + .title = tr::lng_settings_profile_photo_privacy(), + .key = Key::ProfilePhoto, + .controllerFactory = [] { + return std::make_unique(); + }, + .keywords = { u"photo"_q, u"avatar"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/forwards"_q, + .title = tr::lng_settings_forwards_privacy(), + .key = Key::Forwards, + .controllerFactory = [=] { + return std::make_unique(controller); + }, + .keywords = { u"forwards"_q, u"link"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/calls"_q, + .title = tr::lng_settings_calls(), + .key = Key::Calls, + .controllerFactory = [] { + return std::make_unique(); + }, + .keywords = { u"calls"_q, u"voice"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/voices"_q, + .title = tr::lng_settings_voices_privacy(), + .key = Key::Voices, + .controllerFactory = [=] { + return std::make_unique(session); + }, + .premium = true, + .keywords = { u"voice"_q, u"messages"_q }, + }); + + const auto privacy = &session->api().globalPrivacy(); + auto messagesLabel = rpl::combine( + privacy->newRequirePremium(), + privacy->newChargeStars() + ) | rpl::map([=](bool requirePremium, int chargeStars) { + return chargeStars + ? tr::lng_edit_privacy_paid() + : requirePremium + ? tr::lng_edit_privacy_contacts_and_premium() + : tr::lng_edit_privacy_everyone(); + }) | rpl::flatten_latest(); + + const auto messagesPremium = !session->appConfig().newRequirePremiumFree(); + const auto messagesButton = builder.addSettingsButton({ + .id = u"privacy/messages"_q, + .title = tr::lng_settings_messages_privacy(), + .st = &st::settingsButtonNoIcon, + .label = rpl::duplicate(messagesLabel), + .onClick = [=] { + controller->show(Box(EditMessagesPrivacyBox, controller)); + }, + .keywords = { u"messages"_q, u"new"_q, u"unknown"_q }, + }); + if (messagesPremium && messagesButton) { + AddPrivacyPremiumStar( + messagesButton, + session, + std::move(messagesLabel), + st::settingsButtonNoIcon.padding); + } + + builder.addPrivacyButton({ + .id = u"privacy/birthday"_q, + .title = tr::lng_settings_birthday_privacy(), + .key = Key::Birthday, + .controllerFactory = [] { + return std::make_unique(); + }, + .keywords = { u"birthday"_q, u"age"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/gifts"_q, + .title = tr::lng_settings_gifts_privacy(), + .key = Key::GiftsAutoSave, + .controllerFactory = [] { + return std::make_unique(); + }, + .keywords = { u"gifts"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/bio"_q, + .title = tr::lng_settings_bio_privacy(), + .key = Key::About, + .controllerFactory = [] { + return std::make_unique(); + }, + .keywords = { u"bio"_q, u"about"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/saved_music"_q, + .title = tr::lng_settings_saved_music_privacy(), + .key = Key::SavedMusic, + .controllerFactory = [] { + return std::make_unique(); + }, + .keywords = { u"music"_q, u"saved"_q }, + }); + + builder.addPrivacyButton({ + .id = u"privacy/groups"_q, + .title = tr::lng_settings_groups_invite(), + .key = Key::Invites, + .controllerFactory = [] { + return std::make_unique(); + }, + .keywords = { u"groups"_q, u"invite"_q }, + }); + + session->api().userPrivacy().reload(Privacy::Key::AddedByPhone); + + builder.addSkip(st::settingsPrivacySecurityPadding); + builder.addDivider(); +} + +void BuildArchiveAndMuteSection( + SectionBuilder &builder, + not_null controller) { + SetupArchiveAndMute(controller, builder.container()); +} + +void BuildBotsAndWebsitesSection( + SectionBuilder &builder, + not_null controller) { + SetupBotsAndWebsites(controller, builder.container()); +} + +void BuildTopPeersSection( + SectionBuilder &builder, + not_null controller) { + const auto session = &controller->session(); + + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_top_peers_title()); + + const auto toggle = builder.addToggle({ + .id = u"privacy/top_peers"_q, + .title = tr::lng_settings_top_peers_suggest(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single( + rpl::empty + ) | rpl::then( + session->topPeers().updates() + ) | rpl::map([=] { + return !session->topPeers().disabled(); + }), + .keywords = { u"suggest"_q, u"contacts"_q }, + }); + + if (toggle) { + toggle->toggledChanges( + ) | rpl::filter([=](bool enabled) { + return enabled == session->topPeers().disabled(); + }) | rpl::on_next([=](bool enabled) { + session->topPeers().toggleDisabled(!enabled); + }, builder.container()->lifetime()); + } + + builder.addSkip(); + builder.addDividerText(tr::lng_settings_top_peers_about()); +} + +void BuildSelfDestructionSection( + SectionBuilder &builder, + not_null controller, + rpl::producer<> updateTrigger) { + const auto session = &controller->session(); + + builder.addSkip(); + builder.addSubsectionTitle(tr::lng_settings_destroy_title()); + + std::move( + updateTrigger + ) | rpl::on_next([=] { + session->api().selfDestruct().reload(); + }, builder.container()->lifetime()); + + auto label = session->api().selfDestruct().daysAccountTTL( + ) | rpl::map(SelfDestructionBox::DaysLabel); + + builder.addSettingsButton({ + .id = u"privacy/self_destruct"_q, + .title = tr::lng_settings_destroy_if(), + .st = &st::settingsButtonNoIcon, + .label = std::move(label), + .onClick = [=] { + controller->show(Box( + session, + SelfDestructionBox::Type::Account, + session->api().selfDestruct().daysAccountTTL())); + }, + .keywords = { u"delete"_q, u"destroy"_q, u"inactive"_q }, + }); + + builder.addSkip(); +} + +void BuildPrivacySecuritySectionContent( + SectionBuilder &builder, + Window::SessionController *controller, + Fn showOther) { + if (!controller) { + builder.addSettingsButton({ + .id = u"security/cloud_password"_q, + .title = tr::lng_settings_cloud_password_start_title(), + .keywords = { u"password"_q, u"2fa"_q, u"two-factor"_q }, + }); + builder.addSettingsButton({ + .id = u"security/ttl"_q, + .title = tr::lng_settings_ttl_title(), + .keywords = { u"ttl"_q, u"auto-delete"_q, u"timer"_q }, + }); + builder.addSettingsButton({ + .id = u"security/passcode"_q, + .title = tr::lng_settings_passcode_title(), + .keywords = { u"passcode"_q, u"lock"_q, u"pin"_q }, + }); + builder.addSettingsButton({ + .id = u"security/passkeys"_q, + .title = tr::lng_settings_passkeys_title(), + .keywords = { u"passkeys"_q, u"biometric"_q }, + }); + builder.addSettingsButton({ + .id = u"security/blocked"_q, + .title = tr::lng_settings_blocked_users(), + .keywords = { u"blocked"_q, u"ban"_q }, + }); + builder.addSettingsButton({ + .id = u"security/websites"_q, + .title = tr::lng_settings_logged_in(), + .keywords = { u"websites"_q, u"bots"_q, u"logged"_q }, + }); + builder.addSettingsButton({ + .id = u"security/sessions"_q, + .title = tr::lng_settings_show_sessions(), + .keywords = { u"sessions"_q, u"devices"_q, u"active"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/phone_number"_q, + .title = tr::lng_settings_phone_number_privacy(), + .keywords = { u"phone"_q, u"number"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/last_seen"_q, + .title = tr::lng_settings_last_seen(), + .keywords = { u"last seen"_q, u"online"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/profile_photo"_q, + .title = tr::lng_settings_profile_photo_privacy(), + .keywords = { u"photo"_q, u"avatar"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/forwards"_q, + .title = tr::lng_settings_forwards_privacy(), + .keywords = { u"forwards"_q, u"link"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/calls"_q, + .title = tr::lng_settings_calls(), + .keywords = { u"calls"_q, u"voice"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/voices"_q, + .title = tr::lng_settings_voices_privacy(), + .keywords = { u"voice"_q, u"messages"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/messages"_q, + .title = tr::lng_settings_messages_privacy(), + .keywords = { u"messages"_q, u"new"_q, u"unknown"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/birthday"_q, + .title = tr::lng_settings_birthday_privacy(), + .keywords = { u"birthday"_q, u"age"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/gifts"_q, + .title = tr::lng_settings_gifts_privacy(), + .keywords = { u"gifts"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/bio"_q, + .title = tr::lng_settings_bio_privacy(), + .keywords = { u"bio"_q, u"about"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/saved_music"_q, + .title = tr::lng_settings_saved_music_privacy(), + .keywords = { u"music"_q, u"saved"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/groups"_q, + .title = tr::lng_settings_groups_invite(), + .keywords = { u"groups"_q, u"invite"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/archive_and_mute"_q, + .title = tr::lng_settings_auto_archive(), + .keywords = { u"archive"_q, u"mute"_q, u"new"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/bots_payment"_q, + .title = tr::lng_settings_clear_payment_info(), + .keywords = { u"bots"_q, u"payment"_q, u"clear"_q }, + }); + builder.addToggle({ + .id = u"privacy/top_peers"_q, + .title = tr::lng_settings_top_peers_suggest(), + .toggled = rpl::single(true), + .keywords = { u"suggest"_q, u"contacts"_q }, + }); + builder.addSettingsButton({ + .id = u"privacy/self_destruct"_q, + .title = tr::lng_settings_destroy_if(), + .keywords = { u"delete"_q, u"destroy"_q, u"inactive"_q }, + }); + return; + } + + auto updateOnTick = rpl::single( + ) | rpl::then(base::timer_each(kUpdateTimeout)); + const auto trigger = [&] { + return rpl::duplicate(updateOnTick); + }; + + BuildSecuritySection(builder, controller, trigger(), showOther); + BuildPrivacySection(builder, controller); + BuildArchiveAndMuteSection(builder, controller); + BuildBotsAndWebsitesSection(builder, controller); + SetupConfirmationExtensions(controller, builder.container()); + BuildTopPeersSection(builder, controller); + BuildSelfDestructionSection(builder, controller, trigger()); +} + +} // namespace + +void PrivacySecuritySection( + not_null container, + not_null controller, + Fn showOther, + rpl::producer<> showFinished) { + auto &lifetime = container->lifetime(); + const auto highlights = lifetime.make_state(); + + SectionBuilder builder(WidgetContext{ + .container = container, + .controller = controller, + .showOther = showOther, + .isPaused = [] { return false; }, + .highlights = highlights, + }); + + BuildPrivacySecuritySectionContent(builder, controller, showOther); + + std::move(showFinished) | rpl::on_next([=] { + for (const auto &[id, entry] : *highlights) { + if (entry.widget) { + controller->checkHighlightControl( + id, + entry.widget, + base::duplicate(entry.args)); + } + } + }, lifetime); +} + +std::vector PrivacySecuritySectionForSearch() { + std::vector entries; + SectionBuilder builder(SearchContext{ .entries = &entries }); + BuildPrivacySecuritySectionContent(builder, nullptr, nullptr); + return entries; +} + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.h b/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.h new file mode 100644 index 0000000000..bfee69f5dd --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.h @@ -0,0 +1,31 @@ +/* +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 "settings/settings_type.h" +#include "settings/builder/settings_builder.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace Settings::Builder { + +void PrivacySecuritySection( + not_null container, + not_null controller, + Fn showOther, + rpl::producer<> showFinished); + +[[nodiscard]] std::vector PrivacySecuritySectionForSearch(); + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index 2570f9cbb8..4a2f97b599 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_advanced.h" +#include "settings/builder/settings_advanced_builder.h" #include "api/api_global_privacy.h" #include "apiwrap.h" #include "settings/settings_chat.h" @@ -1044,56 +1045,8 @@ rpl::producer Advanced::title() { void Advanced::setupContent(not_null controller) { const auto content = Ui::CreateChild(this); - auto empty = true; - const auto addDivider = [&] { - if (empty) { - empty = false; - } else { - AddDivider(content); - } - }; - const auto addUpdate = [&] { - if (HasUpdate()) { - addDivider(); - AddSkip(content); - AddSubsectionTitle(content, tr::lng_settings_version_info()); - SetupUpdate(content); - AddSkip(content); - } - }; - if (!cAutoUpdate()) { - addUpdate(); - } - addDivider(); - SetupDataStorage(controller, content); - SetupAutoDownload(controller, content); - SetupWindowTitle(controller, content); - SetupWindowCloseBehavior(controller, content); - SetupSystemIntegration(controller, content); - empty = false; - - AddDivider(content); - AddSkip(content); - AddSubsectionTitle(content, tr::lng_settings_performance()); - SetupPerformance(controller, content); - AddSkip(content); - - if (HasSystemSpellchecker()) { - AddDivider(content); - AddSkip(content); - AddSubsectionTitle(content, tr::lng_settings_spellchecker()); - SetupSpellchecker(controller, content); - AddSkip(content); - } - - if (cAutoUpdate()) { - addUpdate(); - } - - AddSkip(content); - AddDivider(content); - AddSkip(content); - SetupExport(controller, content, showOtherMethod()); + setController(controller); + build(content, Builder::AdvancedSection); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 3604478f63..078d87f669 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_chat.h" #include "base/timer_rpl.h" +#include "settings/builder/settings_chat_builder.h" #include "settings/settings_advanced.h" #include "settings/settings_privacy_security.h" #include "settings/settings_experimental.h" @@ -2039,19 +2040,8 @@ void Chat::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) { void Chat::setupContent(not_null controller) { const auto content = Ui::CreateChild(this); - auto updateOnTick = rpl::single( - ) | rpl::then(base::timer_each(60 * crl::time(1000))); - - SetupThemeOptions(controller, content); - SetupThemeSettings(controller, content); - SetupCloudThemes(controller, content); - SetupChatBackground(controller, content); - SetupChatListQuickAction(controller, content); - SetupStickersEmoji(controller, content); - SetupMessages(controller, content); - Ui::AddDivider(content); - SetupSensitiveContent(controller, content, std::move(updateOnTick)); - SetupArchive(controller, content, showOtherMethod()); + setController(controller); + build(content, Builder::ChatSection); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_chat.h b/Telegram/SourceFiles/settings/settings_chat.h index 70724ea2ba..381f14d55a 100644 --- a/Telegram/SourceFiles/settings/settings_chat.h +++ b/Telegram/SourceFiles/settings/settings_chat.h @@ -38,6 +38,44 @@ void PaintRoundColorButton( QBrush brush, float64 selected); +void SetupThemeOptions( + not_null controller, + not_null container); + +void SetupThemeSettings( + not_null controller, + not_null container); + +void SetupCloudThemes( + not_null controller, + not_null container); + +void SetupChatBackground( + not_null controller, + not_null container); + +void SetupChatListQuickAction( + not_null controller, + not_null container); + +void SetupStickersEmoji( + not_null controller, + not_null container); + +void SetupMessages( + not_null controller, + not_null container); + +void SetupArchive( + not_null controller, + not_null container, + Fn showOther); + +void SetupSensitiveContent( + not_null controller, + not_null container, + rpl::producer<> updateTrigger); + class Chat : public Section { public: Chat(QWidget *parent, not_null controller); diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index 01ce6c5c06..ee6ae04e5f 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -285,6 +285,19 @@ void ScrollToWidget(not_null target) { } } +void AbstractSection::build( + not_null container, + SectionBuilder builder) { + builder( + static_cast(container->add( + object_ptr( + container, + object_ptr(container)))->entity()), + _controller, + showOtherMethod(), + _showFinished.events()); +} + Icon::Icon(IconDescriptor descriptor) : _icon(descriptor.icon) { const auto background = [&]() -> const style::color* { if (descriptor.type == IconType::Simple) { diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index cb80ddcc2a..3fe055a26a 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -88,13 +88,19 @@ struct HighlightDescriptor { void HighlightWidget(QWidget *target, HighlightArgs &&args = {}); void ScrollToWidget(not_null target); +using SectionBuilder = void(*)( + not_null container, + not_null controller, + Fn showOther, + rpl::producer<> showFinished); + class AbstractSection : public Ui::RpWidget { public: using RpWidget::RpWidget; [[nodiscard]] virtual Type id() const = 0; [[nodiscard]] virtual rpl::producer sectionShowOther() { - return nullptr; + return _showOtherRequests.events(); } [[nodiscard]] virtual rpl::producer<> sectionShowBack() { return nullptr; @@ -113,6 +119,7 @@ public: done(); } virtual void showFinished() { + _showFinished.fire({}); } virtual void setInnerFocus() { setFocus(); @@ -150,6 +157,33 @@ public: QRect clip) { return false; } + + void showOther(Type type) { + _showOtherRequests.fire_copy(type); + } + [[nodiscard]] Fn showOtherMethod() { + return crl::guard(this, [=](Type type) { + showOther(type); + }); + } + +protected: + void setController(not_null controller) { + _controller = controller; + } + [[nodiscard]] Window::SessionController *controller() const { + return _controller; + } + + void build( + not_null container, + SectionBuilder builder); + +private: + rpl::event_stream _showOtherRequests; + rpl::event_stream<> _showFinished; + Window::SessionController *_controller = nullptr; + }; enum class IconType { diff --git a/Telegram/SourceFiles/settings/settings_common_session.h b/Telegram/SourceFiles/settings/settings_common_session.h index 48abfff2cf..22546d1240 100644 --- a/Telegram/SourceFiles/settings/settings_common_session.h +++ b/Telegram/SourceFiles/settings/settings_common_session.h @@ -65,12 +65,6 @@ struct SectionFactory : AbstractSectionFactory { }; -using SectionBuilder = void(*)( - not_null container, - not_null controller, - Fn showOther, - rpl::producer<> showFinished); - template class Section : public AbstractSection { public: @@ -83,45 +77,6 @@ public: return Id(); } - [[nodiscard]] rpl::producer sectionShowOther() final override { - return _showOtherRequests.events(); - } - void showOther(Type type) { - _showOtherRequests.fire_copy(type); - } - [[nodiscard]] Fn showOtherMethod() { - return crl::guard(this, [=](Type type) { - showOther(type); - }); - } - - void showFinished() override { - _showFinished.fire({}); - } - -protected: - void setController(not_null controller) { - _controller = controller; - } - [[nodiscard]] Window::SessionController *controller() const { - return _controller; - } - - void build( - not_null container, - SectionBuilder builder) { - builder( - container, - _controller, - showOtherMethod(), - _showFinished.events()); - } - -private: - rpl::event_stream _showOtherRequests; - rpl::event_stream<> _showFinished; - Window::SessionController *_controller = nullptr; - }; bool HasMenu(Type type); diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 06c893fabf..962db4e762 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_privacy_security.h" +#include "settings/builder/settings_privacy_security_builder.h" #include "api/api_authorizations.h" #include "api/api_cloud_password.h" #include "api/api_self_destruct.h" @@ -274,6 +275,52 @@ rpl::producer BlockedPeersCount(not_null<::Main::Session*> session) { }); } +} // namespace + +void AddPrivacyPremiumStar( + not_null button, + not_null session, + rpl::producer label, + const QMargins &padding) { + const auto badge = Ui::CreateChild(button.get()); + badge->showOn(Data::AmPremiumValue(session)); + const auto sampleLeft = st::settingsColorSamplePadding.left(); + const auto badgeLeft = padding.left() + sampleLeft; + + const auto factor = style::DevicePixelRatio(); + const auto size = Size(st::settingsButtonNoIcon.style.font->ascent); + auto starImage = QImage( + size * factor, + QImage::Format_ARGB32_Premultiplied); + starImage.setDevicePixelRatio(factor); + starImage.fill(Qt::transparent); + { + auto p = QPainter(&starImage); + auto star = QSvgRenderer( + Ui::Premium::ColorizedSvg(Ui::Premium::ButtonGradientStops())); + star.render(&p, Rect(size)); + } + + badge->resize(starImage.size() / style::DevicePixelRatio()); + badge->paintRequest( + ) | rpl::on_next([=, star = std::move(starImage)] { + auto p = QPainter(badge); + p.drawImage(0, 0, star); + }, badge->lifetime()); + + rpl::combine( + button->sizeValue(), + std::move(label) + ) | rpl::on_next([=](const QSize &s, const QString &) { + if (s.isNull()) { + return; + } + badge->moveToLeft( + button->fullTextWidth() + badgeLeft, + (s.height() - badge->height()) / 2); + }, badge->lifetime()); +} + void SetupPrivacy( not_null controller, not_null container, @@ -894,8 +941,6 @@ void SetupSecurity( showOther); } -} // namespace - void SetupSensitiveContent( not_null controller, not_null container, @@ -1128,19 +1173,8 @@ void PrivacySecurity::setupContent( not_null controller) { const auto content = Ui::CreateChild(this); - auto updateOnTick = rpl::single( - ) | rpl::then(base::timer_each(kUpdateTimeout)); - const auto trigger = [=] { - return rpl::duplicate(updateOnTick); - }; - - SetupSecurity(controller, content, trigger(), showOtherMethod()); - SetupPrivacy(controller, content, trigger()); - SetupArchiveAndMute(controller, content); - SetupBotsAndWebsites(controller, content); - SetupConfirmationExtensions(controller, content); - SetupTopPeers(controller, content); - SetupSelfDestruction(controller, content, trigger()); + setController(controller); + build(content, Builder::PrivacySecuritySection); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.h b/Telegram/SourceFiles/settings/settings_privacy_security.h index 21933c0513..08f276b7fa 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.h +++ b/Telegram/SourceFiles/settings/settings_privacy_security.h @@ -40,10 +40,44 @@ not_null AddPrivacyButton( Fn()> controllerFactory, const style::SettingsButton *stOverride = nullptr); +void AddPrivacyPremiumStar( + not_null button, + not_null<::Main::Session*> session, + rpl::producer label, + const QMargins &padding); + void SetupArchiveAndMute( not_null controller, not_null container); +void SetupSecurity( + not_null controller, + not_null container, + rpl::producer<> updateTrigger, + Fn showOther); + +void SetupPrivacy( + not_null controller, + not_null container, + rpl::producer<> updateTrigger); + +void SetupBotsAndWebsites( + not_null controller, + not_null container); + +void SetupConfirmationExtensions( + not_null controller, + not_null container); + +void SetupTopPeers( + not_null controller, + not_null container); + +void SetupSelfDestruction( + not_null controller, + not_null container, + rpl::producer<> updateTrigger); + class PrivacySecurity : public Section { public: PrivacySecurity(