All checks were successful
Desktop source guards / guards (push) Successful in 5s
Живое зеркало раз в 15 минут синхронно обходило все чаты и сообщения каждого аккаунта в главном потоке и проверяло файлы на диске. При нескольких аккаунтах окно зависало на десятки секунд каждые 20-30 минут. Теперь данные собираются в простые структуры кусками по 6 мс с паузами между ними, файлы пишутся в фоновом потоке, а аккаунты обрабатываются по одному. Живая копия хранит последние 300 сообщений чата и не проверяет файлы на диске, интервал стал 30 минут. Сохранение при выходе убрано. Синхронно сохраняется только путь принудительного разлогина, где данные иначе пропадут. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2105 lines
63 KiB
C++
2105 lines
63 KiB
C++
/*
|
||
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/sections/settings_advanced.h"
|
||
|
||
#include "settings/settings_common_session.h"
|
||
|
||
#include "api/api_global_privacy.h"
|
||
#include "apiwrap.h"
|
||
#include "base/call_delayed.h"
|
||
#include "base/platform/base_platform_custom_app_icon.h"
|
||
#include "base/platform/base_platform_info.h"
|
||
#include "base/screen_reader_state.h"
|
||
#include "boxes/about_box.h"
|
||
#include "boxes/auto_download_box.h"
|
||
#include "boxes/connection_box.h"
|
||
#include "boxes/download_path_box.h"
|
||
#include "core/application.h"
|
||
#include "core/core_settings.h"
|
||
#include "core/file_utilities.h"
|
||
#include "core/launcher.h"
|
||
#include "core/update_channel.h"
|
||
#include "core/update_checker.h"
|
||
#include "data/components/promo_suggestions.h"
|
||
#include "data/data_auto_download.h"
|
||
#include "data/data_session.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_account_archive.h"
|
||
#include "main/main_domain.h"
|
||
#include "main/main_session.h"
|
||
#include "main/main_session_settings.h"
|
||
#include "mtproto/instance/mtp_instance.h"
|
||
#include "mtproto/runtime/connection_status.h"
|
||
#include "platform/platform_specific.h"
|
||
#include "settings/settings_builder.h"
|
||
#include "settings/sections/settings_local_storage.h"
|
||
#include "settings/sections/settings_main.h"
|
||
#include "settings/sections/settings_chat.h"
|
||
#include "settings/settings_experimental.h"
|
||
#include "settings/settings_power_saving.h"
|
||
#include "settings/sections/settings_privacy_security.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/painter.h"
|
||
#include "ui/platform/ui_platform_window.h"
|
||
#include "ui/power_saving.h"
|
||
#include "ui/rp_widget.h"
|
||
#include "ui/screen_reader_mode.h"
|
||
#include "ui/text/format_values.h"
|
||
#include "ui/ui_utility.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 "window/window_controller.h"
|
||
#include "window/window_saved_windows.h"
|
||
#include "window/window_session_controller.h"
|
||
#include "styles/style_layers.h"
|
||
#include "styles/style_menu_icons.h"
|
||
#include "styles/style_settings.h"
|
||
|
||
#ifdef Q_OS_MAC
|
||
#include "base/platform/mac/base_confirm_quit.h"
|
||
#endif // Q_OS_MAC
|
||
|
||
#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 {
|
||
namespace {
|
||
|
||
using namespace Builder;
|
||
|
||
#if defined Q_OS_MAC && !defined OS_MAC_STORE
|
||
[[nodiscard]] const QImage &IconMacRound() {
|
||
static const auto result = QImage(u":/gui/art/icon_round512@2x.png"_q);
|
||
return result;
|
||
}
|
||
#endif // Q_OS_MAC && !OS_MAC_STORE
|
||
|
||
void BuildDataStorageSection(SectionBuilder &builder) {
|
||
const auto controller = builder.controller();
|
||
const auto container = builder.container();
|
||
const auto session = builder.session();
|
||
const auto account = &session->account();
|
||
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/data_storage"_q,
|
||
.title = tr::lng_settings_data_storage(),
|
||
.keywords = { u"storage"_q, u"data"_q, u"download"_q, u"connection"_q },
|
||
});
|
||
|
||
const auto connectionType = [=] {
|
||
const auto transport = account->mtp().dctransport();
|
||
const auto ping = account->mtp().connectionStatus().pingTime();
|
||
if (!Core::App().settings().proxy().isEnabled()) {
|
||
return transport.isEmpty()
|
||
? tr::lng_connection_auto_connecting(tr::now)
|
||
: !ping
|
||
? tr::lng_connection_auto(tr::now, lt_transport, transport)
|
||
: tr::lng_connection_auto_ping(
|
||
tr::now,
|
||
lt_transport,
|
||
transport,
|
||
lt_ping,
|
||
QString::number(ping));
|
||
} else {
|
||
return transport.isEmpty()
|
||
? tr::lng_connection_proxy_connecting(tr::now)
|
||
: !ping
|
||
? tr::lng_connection_proxy(tr::now, lt_transport, transport)
|
||
: tr::lng_connection_proxy_ping(
|
||
tr::now,
|
||
lt_transport,
|
||
transport,
|
||
lt_ping,
|
||
QString::number(ping));
|
||
}
|
||
};
|
||
|
||
builder.addButton({
|
||
.id = u"advanced/connection_type"_q,
|
||
.title = tr::lng_settings_connection_type(),
|
||
.icon = { &st::menuIconNetwork },
|
||
.label = rpl::merge(
|
||
Core::App().settings().proxy().connectionTypeChanges(),
|
||
account->mtp().connectionStatus().pingTimeValue() | rpl::to_empty,
|
||
tr::lng_connection_auto_connecting() | rpl::to_empty
|
||
) | rpl::map(connectionType),
|
||
.onClick = [=] {
|
||
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<rpl::variable<bool>>(
|
||
!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.addButton({
|
||
.id = u"advanced/download_path"_q,
|
||
.title = tr::lng_download_path(),
|
||
.icon = { &st::menuIconShowInFolder },
|
||
.label = std::move(downloadLabel),
|
||
.onClick = [=] {
|
||
controller->show(Box<DownloadPathBox>(controller));
|
||
},
|
||
.keywords = { u"download"_q, u"path"_q, u"folder"_q },
|
||
.shown = showDownloadPath
|
||
? showDownloadPath->value()
|
||
: rpl::single(true) | rpl::type_erased,
|
||
});
|
||
|
||
builder.addButton({
|
||
.id = u"advanced/storage"_q,
|
||
.title = tr::lng_settings_manage_local_storage(),
|
||
.icon = { &st::menuIconStorage },
|
||
.onClick = [=] {
|
||
controller->showSettings(LocalStorageId());
|
||
},
|
||
.keywords = { u"storage"_q, u"cache"_q, u"local"_q },
|
||
});
|
||
|
||
builder.addButton({
|
||
.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.addButton({
|
||
.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) {
|
||
const auto controller = builder.controller();
|
||
const auto container = builder.container();
|
||
const auto session = builder.session();
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/auto_download"_q,
|
||
.title = tr::lng_media_auto_settings(),
|
||
.keywords = { u"auto"_q, u"download"_q, u"media"_q },
|
||
});
|
||
|
||
using Source = Data::AutoDownload::Source;
|
||
|
||
struct State {
|
||
rpl::event_stream<> changes;
|
||
};
|
||
const auto state = container
|
||
? container->lifetime().make_state<State>()
|
||
: nullptr;
|
||
const auto shouldBeChecked = [=](Source source) {
|
||
return HasEnabledTypes(session->settings().autoDownload(), source);
|
||
};
|
||
const auto add = [&](
|
||
QString id,
|
||
rpl::producer<QString> title,
|
||
const style::icon *icon,
|
||
Source source,
|
||
QStringList keywords) {
|
||
const auto row = builder.addButton({
|
||
.id = std::move(id),
|
||
.title = std::move(title),
|
||
.icon = { icon },
|
||
.onClick = [=] {
|
||
auto box = Box<AutoDownloadBox>(session, source);
|
||
box->boxClosing() | rpl::on_next(crl::guard(container, [=] {
|
||
state->changes.fire({});
|
||
}), box->lifetime());
|
||
controller->show(std::move(box));
|
||
},
|
||
.keywords = std::move(keywords),
|
||
});
|
||
if (!row) {
|
||
return;
|
||
}
|
||
const auto [toggle, checkView] = AddSeparatedToggle(
|
||
row,
|
||
st::settingsButton,
|
||
shouldBeChecked(source));
|
||
state->changes.events() | rpl::on_next([=] {
|
||
checkView->setChecked(shouldBeChecked(source), anim::type::normal);
|
||
}, row->lifetime());
|
||
toggle->clicks() | rpl::on_next([=] {
|
||
auto &data = session->settings().autoDownload();
|
||
const auto enable = !checkView->checked();
|
||
if (enable) {
|
||
SetDefaultsForSource(data, source);
|
||
session->data().photoLoadSettingsChanged();
|
||
session->data().documentLoadSettingsChanged();
|
||
} else {
|
||
SetDisabledForSource(data, source);
|
||
session->data().checkPlayingAnimations();
|
||
}
|
||
session->saveSettingsDelayed();
|
||
state->changes.fire({});
|
||
}, toggle->lifetime());
|
||
};
|
||
|
||
add(
|
||
u"advanced/auto_download_private"_q,
|
||
tr::lng_media_auto_in_private(),
|
||
&st::menuIconProfile,
|
||
Source::User,
|
||
{ u"auto"_q, u"download"_q, u"private"_q, u"media"_q });
|
||
add(
|
||
u"advanced/auto_download_groups"_q,
|
||
tr::lng_media_auto_in_groups(),
|
||
&st::menuIconGroups,
|
||
Source::Group,
|
||
{ u"auto"_q, u"download"_q, u"groups"_q, u"media"_q });
|
||
add(
|
||
u"advanced/auto_download_channels"_q,
|
||
tr::lng_media_auto_in_channels(),
|
||
&st::menuIconChannel,
|
||
Source::Channel,
|
||
{ u"auto"_q, u"download"_q, u"channels"_q, u"media"_q });
|
||
|
||
builder.addSkip(st::settingsCheckboxesSkip);
|
||
}
|
||
|
||
void BuildWindowTitleSection(SectionBuilder &builder) {
|
||
const auto settings = &Core::App().settings();
|
||
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/window_title"_q,
|
||
.title = tr::lng_settings_window_system(),
|
||
.keywords = { u"window"_q, u"title"_q, u"frame"_q },
|
||
});
|
||
|
||
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();
|
||
}
|
||
|
||
#if !defined Q_OS_WIN && !defined Q_OS_MAC
|
||
void BuildWindowCloseBehaviorSection(SectionBuilder &builder) {
|
||
using Behavior = Core::Settings::CloseBehavior;
|
||
|
||
const auto settings = &Core::App().settings();
|
||
auto shown = Platform::TrayIconSupported()
|
||
? (Core::App().settings().workModeValue(
|
||
) | rpl::map([](Core::Settings::WorkMode mode) {
|
||
return (mode == Core::Settings::WorkMode::WindowOnly);
|
||
}) | rpl::distinct_until_changed() | rpl::type_erased)
|
||
: rpl::producer<bool>(nullptr);
|
||
|
||
builder.scope([&] {
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/window_close"_q,
|
||
.title = tr::lng_settings_window_close(),
|
||
.keywords = { u"close"_q, u"window"_q, u"background"_q, u"quit"_q, u"taskbar"_q, u"minimize"_q },
|
||
});
|
||
|
||
builder.add([settings](const WidgetContext &ctx) {
|
||
const auto container = ctx.container.get();
|
||
auto wrap = object_ptr<Ui::VerticalLayout>(container);
|
||
const auto inner = wrap.data();
|
||
|
||
const auto group = std::make_shared<Ui::RadioenumGroup<Behavior>>(
|
||
settings->closeBehavior());
|
||
const auto addRadio = [&](Behavior value, const QString &label) {
|
||
inner->add(
|
||
object_ptr<Ui::Radioenum<Behavior>>(
|
||
inner,
|
||
group,
|
||
value,
|
||
label,
|
||
st::settingsSendType),
|
||
st::settingsSendTypePadding);
|
||
};
|
||
|
||
addRadio(
|
||
Behavior::RunInBackground,
|
||
tr::lng_settings_run_in_background(tr::now));
|
||
addRadio(
|
||
Behavior::CloseToTaskbar,
|
||
tr::lng_settings_close_to_taskbar(tr::now));
|
||
addRadio(
|
||
Behavior::Quit,
|
||
tr::lng_settings_quit_on_close(tr::now));
|
||
|
||
group->value() | rpl::filter([=](Behavior value) {
|
||
return (value != settings->closeBehavior());
|
||
}) | rpl::on_next([=](Behavior value) {
|
||
settings->setCloseBehavior(value);
|
||
Local::writeSettings();
|
||
}, inner->lifetime());
|
||
|
||
return SectionBuilder::WidgetToAdd{ .widget = std::move(wrap) };
|
||
});
|
||
|
||
builder.addSkip();
|
||
}, std::move(shown));
|
||
}
|
||
#endif // !Q_OS_WIN && !Q_OS_MAC
|
||
|
||
void BuildSystemIntegrationSection(SectionBuilder &builder) {
|
||
const auto controller = builder.controller();
|
||
const auto settings = &Core::App().settings();
|
||
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/system_integration"_q,
|
||
.title = tr::lng_settings_system_integration(),
|
||
.keywords = { u"system"_q, u"tray"_q, u"startup"_q, u"autostart"_q },
|
||
});
|
||
|
||
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.addCheckbox({
|
||
.id = u"advanced/monochrome_icon"_q,
|
||
.title = tr::lng_settings_monochrome_icon(),
|
||
.checked = settings->trayIconMonochrome(),
|
||
.keywords = { u"monochrome"_q, u"icon"_q, u"tray"_q },
|
||
.shown = tray
|
||
? tray->checkedValue()
|
||
: rpl::single(trayEnabled()),
|
||
})
|
||
: nullptr;
|
||
|
||
if (monochrome) {
|
||
monochrome->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());
|
||
}
|
||
|
||
const auto systemReplace = builder.addCheckbox({
|
||
.id = u"advanced/system_text_replace"_q,
|
||
.title = tr::lng_settings_system_text_replace(),
|
||
.checked = settings->systemTextReplace(),
|
||
.keywords = { u"text"_q, u"replace"_q, u"system"_q },
|
||
});
|
||
if (systemReplace) {
|
||
systemReplace->checkedChanges(
|
||
) | rpl::filter([=](bool checked) {
|
||
return (checked != settings->systemTextReplace());
|
||
}) | rpl::on_next([=](bool checked) {
|
||
settings->setSystemTextReplace(checked);
|
||
Core::App().saveSettingsDelayed();
|
||
}, systemReplace->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<uint64>();
|
||
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 container = builder.container();
|
||
const auto closeToTaskbarShown = container
|
||
? container->lifetime().make_state<rpl::variable<bool>>(
|
||
!Core::App().tray().has())
|
||
: nullptr;
|
||
|
||
if (closeToTaskbarShown) {
|
||
settings->workModeValue(
|
||
) | rpl::on_next([=](WorkMode) {
|
||
*closeToTaskbarShown = !Core::App().tray().has();
|
||
}, container->lifetime());
|
||
}
|
||
|
||
const auto closeToTaskbar = builder.addCheckbox({
|
||
.id = u"advanced/close_to_taskbar"_q,
|
||
.title = tr::lng_settings_close_to_taskbar(),
|
||
.checked = settings->closeBehavior() == Behavior::CloseToTaskbar,
|
||
.keywords = { u"close"_q, u"taskbar"_q, u"minimize"_q },
|
||
.shown = closeToTaskbarShown
|
||
? closeToTaskbarShown->value()
|
||
: rpl::single(false),
|
||
});
|
||
if (closeToTaskbar) {
|
||
closeToTaskbar->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 = [=] {
|
||
// Starting hidden must not conceal a verified launch lock.
|
||
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.addCheckbox({
|
||
.id = u"advanced/start_minimized"_q,
|
||
.title = tr::lng_settings_start_min(),
|
||
.checked = minimizedToggled(),
|
||
.keywords = { u"minimized"_q, u"startup"_q, u"hidden"_q },
|
||
.shown = autostart
|
||
? autostart->checkedValue()
|
||
: rpl::single(cAutoStart()),
|
||
});
|
||
|
||
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->checked()) {
|
||
Local::writeSettings();
|
||
} else if (minimized) {
|
||
minimized->setChecked(false);
|
||
}
|
||
}));
|
||
}, autostart->lifetime());
|
||
|
||
if (controller) {
|
||
Platform::AutostartRequestStateFromSystem(crl::guard(
|
||
controller,
|
||
[=](bool enabled) { autostart->setChecked(enabled); }));
|
||
}
|
||
}
|
||
|
||
if (minimized && controller) {
|
||
minimized->checkedChanges(
|
||
) | rpl::filter([=](bool checked) {
|
||
return (checked != minimizedToggled());
|
||
}) | rpl::on_next([=](bool checked) {
|
||
// Refuse a verified launch lock, as the checkbox does.
|
||
if (controller->session().domain().local().hasLocalPasscode()) {
|
||
minimized->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->setChecked(minimizedToggled());
|
||
}, minimized->lifetime());
|
||
}
|
||
}
|
||
|
||
const auto restoreWindows = builder.addCheckbox({
|
||
.id = u"advanced/restore_windows"_q,
|
||
.title = tr::lng_settings_restore_windows(),
|
||
.checked = Core::App().savedWindows()->restoreOnLaunch(),
|
||
.keywords = {
|
||
u"restore"_q,
|
||
u"windows"_q,
|
||
u"launch"_q,
|
||
u"startup"_q,
|
||
u"reopen"_q,
|
||
u"session"_q,
|
||
},
|
||
});
|
||
if (restoreWindows) {
|
||
restoreWindows->checkedChanges(
|
||
) | rpl::filter([=](bool checked) {
|
||
return (checked != Core::App().savedWindows()->restoreOnLaunch());
|
||
}) | rpl::on_next([=](bool checked) {
|
||
Core::App().savedWindows()->setRestoreOnLaunch(checked);
|
||
}, restoreWindows->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) {
|
||
const auto controller = builder.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.addButton({
|
||
.id = u"advanced/angle_backend"_q,
|
||
.title = tr::lng_settings_angle_backend(),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.label = rpl::single(options[backendIndex]),
|
||
.onClick = [=] {
|
||
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||
const auto save = [=](int index) {
|
||
if (index == backendIndex) {
|
||
return;
|
||
}
|
||
const auto confirmed = [=] {
|
||
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) {
|
||
const auto controller = builder.controller();
|
||
const auto opengl = builder.addButton({
|
||
.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) {
|
||
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) {
|
||
const auto controller = builder.controller();
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/performance"_q,
|
||
.title = tr::lng_settings_performance(),
|
||
.keywords = { u"performance"_q, u"power"_q, u"graphics"_q, u"hardware"_q },
|
||
});
|
||
|
||
builder.addButton({
|
||
.id = u"advanced/power_saving"_q,
|
||
.title = tr::lng_settings_power_menu(),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.onClick = [=] {
|
||
controller->window().show(Box(PowerSavingBox, PowerSaving::Flags()));
|
||
},
|
||
.keywords = { u"power"_q, u"saving"_q, u"battery"_q, u"animation"_q },
|
||
});
|
||
|
||
const auto hwAccel = builder.addButton({
|
||
.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);
|
||
#else
|
||
if constexpr (!Platform::IsMac()) {
|
||
BuildOpenGLOption(builder);
|
||
}
|
||
#endif
|
||
|
||
builder.addSkip();
|
||
}
|
||
|
||
void BuildSpellcheckerSection(SectionBuilder &builder) {
|
||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||
const auto controller = builder.controller();
|
||
const auto session = builder.session();
|
||
const auto settings = &Core::App().settings();
|
||
const auto isSystem = Platform::Spellchecker::IsSystemSpellchecker();
|
||
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/spellchecker"_q,
|
||
.title = tr::lng_settings_spellchecker(),
|
||
.keywords = { u"spellcheck"_q, u"spelling"_q, u"dictionary"_q },
|
||
});
|
||
|
||
const auto spellchecker = builder.addButton({
|
||
.id = u"advanced/spellchecker_toggle"_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());
|
||
}
|
||
|
||
if (!isSystem) {
|
||
builder.scope([&] {
|
||
const auto autoDownload = builder.addButton({
|
||
.id = u"advanced/auto_download_dictionaries"_q,
|
||
.title = tr::lng_settings_auto_download_dictionaries(),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.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.addButton({
|
||
.id = u"advanced/manage_dictionaries"_q,
|
||
.title = tr::lng_settings_manage_dictionaries(),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.label = Spellchecker::ButtonManageDictsState(session),
|
||
.onClick = [=] {
|
||
controller->show(Box<Ui::ManageDictionariesBox>(session));
|
||
},
|
||
.keywords = { u"dictionary"_q, u"manage"_q, u"spellcheck"_q },
|
||
});
|
||
}, spellchecker ? spellchecker->toggledValue() : nullptr);
|
||
}
|
||
|
||
builder.addSkip();
|
||
#endif // !TDESKTOP_DISABLE_SPELLCHECK
|
||
}
|
||
|
||
void BuildZaStoGramSection(SectionBuilder &builder) {
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/zastogram"_q,
|
||
.title = tr::lng_settings_zastogram_section(),
|
||
.keywords = { u"zastogram"_q, u"channel"_q, u"pin"_q, u"archive"_q },
|
||
});
|
||
|
||
const auto pin = builder.addButton({
|
||
.id = u"advanced/zastogram_pin_channel"_q,
|
||
.title = tr::lng_settings_zastogram_pin_channel(),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.toggled = rpl::single(
|
||
Core::App().settings().pinZaStoGramChannel()),
|
||
.keywords = { u"zastogram"_q, u"channel"_q, u"pin"_q, u"archive"_q },
|
||
});
|
||
if (pin) {
|
||
pin->toggledValue(
|
||
) | rpl::filter([](bool enabled) {
|
||
return (enabled != Core::App().settings().pinZaStoGramChannel());
|
||
}) | rpl::on_next([=](bool enabled) {
|
||
Core::App().settings().setPinZaStoGramChannel(enabled);
|
||
Core::App().saveSettingsDelayed();
|
||
for (const auto &[_, account] : Core::App().domain().accounts()) {
|
||
if (account->sessionExists()) {
|
||
account->session().promoSuggestions().refreshTopPromotion();
|
||
}
|
||
}
|
||
}, pin->lifetime());
|
||
}
|
||
builder.addSkip();
|
||
builder.addDividerText(tr::lng_settings_zastogram_pin_channel_about());
|
||
|
||
const auto controller = builder.controller();
|
||
const auto session = builder.session();
|
||
builder.addSkip();
|
||
builder.addButton({
|
||
.id = u"advanced/zastogram_archive_save"_q,
|
||
.title = rpl::single(u"Сохранить копию аккаунта сейчас"_q),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.onClick = [=] {
|
||
const auto weak = base::make_weak(controller);
|
||
if (controller) {
|
||
controller->showToast(u"Сохраняю копию, окно можно не закрывать."_q);
|
||
}
|
||
Main::AccountArchive::SaveAsync(
|
||
session,
|
||
Main::AccountArchive::Reason::Manual,
|
||
[=](QString folder) {
|
||
if (!folder.isEmpty()) {
|
||
File::ShowInFolder(folder + u"/index.html"_q);
|
||
} else if (const auto strong = weak.get()) {
|
||
strong->showToast(
|
||
u"Не сохранено: чаты ещё не загружены или копия уже делается."_q);
|
||
}
|
||
});
|
||
},
|
||
.keywords = { u"archive"_q, u"backup"_q, u"deleted"_q, u"frozen"_q },
|
||
});
|
||
builder.addButton({
|
||
.id = u"advanced/zastogram_archive_open"_q,
|
||
.title = rpl::single(u"Открыть папку архива аккаунтов"_q),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.onClick = [=] {
|
||
const auto root = Main::AccountArchive::RootPath();
|
||
QDir().mkpath(root);
|
||
File::Launch(root);
|
||
Main::AccountArchive::MarkSeen();
|
||
},
|
||
.keywords = { u"archive"_q, u"backup"_q, u"deleted"_q, u"frozen"_q },
|
||
});
|
||
builder.addSkip();
|
||
builder.addDividerText(rpl::single(
|
||
u"Если Telegram удалит, заблокирует или заморозит аккаунт, перед стиранием данных приложение сохраняет в эту папку список чатов, каналов и групп со ссылками, загруженные сообщения и скачанные файлы. ПК-клиент не хранит историю на диске, поэтому копия содержит только то, что уже было загружено в память; она обновляется в фоне."_q));
|
||
}
|
||
|
||
void BuildUpdateSection(SectionBuilder &builder, bool atTop) {
|
||
if (!HasUpdate()) {
|
||
return;
|
||
}
|
||
const auto container = builder.container();
|
||
|
||
if (!atTop) {
|
||
builder.addDivider();
|
||
}
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/version"_q,
|
||
.title = tr::lng_settings_version_info(),
|
||
.keywords = { u"version"_q, u"update"_q, u"check"_q },
|
||
});
|
||
|
||
const auto version = tr::lng_settings_current_version(
|
||
tr::now,
|
||
lt_version,
|
||
currentVersionText());
|
||
|
||
const auto texts = container
|
||
? Ui::CreateChild<rpl::event_stream<QString>>(container)
|
||
: nullptr;
|
||
const auto downloading = container
|
||
? Ui::CreateChild<rpl::event_stream<bool>>(container)
|
||
: nullptr;
|
||
|
||
const auto toggle = builder.addButton({
|
||
.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<Ui::FlatLabel>(
|
||
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);
|
||
}
|
||
|
||
auto optionsShown = rpl::producer<bool>(nullptr);
|
||
if (toggle) {
|
||
Core::UpdateChecker checker;
|
||
optionsShown = rpl::combine(
|
||
toggle->toggledValue(),
|
||
downloading->events_starting_with(
|
||
checker.state() == Core::UpdateChecker::State::Download)
|
||
) | rpl::map([](bool, bool downloading) {
|
||
return !downloading;
|
||
});
|
||
}
|
||
auto options = (Ui::SlideWrap<Ui::VerticalLayout>*)nullptr;
|
||
auto check = (Ui::SettingsButton*)nullptr;
|
||
builder.scope([&] {
|
||
check = builder.addButton({
|
||
.id = u"advanced/check_update"_q,
|
||
.title = tr::lng_settings_check_now(),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.onClick = [] {
|
||
Core::UpdateChecker checker;
|
||
cSetLastUpdateCheck(0);
|
||
checker.start();
|
||
},
|
||
.keywords = { u"check"_q, u"update"_q, u"version"_q },
|
||
});
|
||
}, std::move(optionsShown), [&](auto wrap) {
|
||
options = wrap;
|
||
});
|
||
|
||
if (check && container) {
|
||
const auto update = Ui::CreateChild<Ui::SettingsButton>(
|
||
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,
|
||
bool preferPercent) {
|
||
const auto formatted = [&] {
|
||
if (!preferPercent) {
|
||
return Ui::FormatDownloadText(ready, total);
|
||
}
|
||
const auto percent = (total > 0)
|
||
? std::clamp((ready * 100) / float64(total), 0., 100.)
|
||
: 0.;
|
||
auto result = QString::number(percent, 'f', 2);
|
||
if (result.contains('.')) {
|
||
while (result.endsWith('0')) {
|
||
result.chop(1);
|
||
}
|
||
if (result.endsWith('.')) {
|
||
result.chop(1);
|
||
}
|
||
}
|
||
return result + '%';
|
||
}();
|
||
texts->fire(tr::lng_settings_downloading_update(
|
||
tr::now,
|
||
lt_progress,
|
||
formatted));
|
||
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(),
|
||
checker.percent());
|
||
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());
|
||
|
||
Core::UpdateChecker checker;
|
||
|
||
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,
|
||
progress.percent);
|
||
}, 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) {
|
||
const auto controller = builder.controller();
|
||
const auto session = builder.session();
|
||
const auto showOther = builder.showOther();
|
||
builder.addSkip();
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
|
||
builder.addButton({
|
||
.id = u"advanced/export"_q,
|
||
.title = tr::lng_settings_export_data(),
|
||
.icon = { &st::menuIconExport },
|
||
.onClick = [=] {
|
||
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.addButton({
|
||
.id = u"advanced/experimental"_q,
|
||
.title = tr::lng_settings_experimental(),
|
||
.icon = { &st::menuIconExperimental },
|
||
.onClick = [showOther] { showOther(Experimental::Id()); },
|
||
.keywords = { u"experimental"_q, u"beta"_q, u"features"_q },
|
||
});
|
||
}
|
||
|
||
void BuildScreenReaderSection(SectionBuilder &builder) {
|
||
const auto detected = base::ScreenReaderState::Instance()->active();
|
||
const auto disabled = Ui::ScreenReaderModeDisabled();
|
||
if (!detected || !disabled) {
|
||
return;
|
||
}
|
||
|
||
builder.addDivider();
|
||
builder.addSkip();
|
||
builder.addSubsectionTitle({
|
||
.id = u"advanced/screen_reader"_q,
|
||
.title = tr::lng_screen_reader_settings_title(),
|
||
.keywords = { u"screen reader"_q, u"accessibility"_q, u"voiceover"_q },
|
||
});
|
||
|
||
const auto toggle = builder.addButton({
|
||
.id = u"advanced/screen_reader_disable"_q,
|
||
.title = tr::lng_screen_reader_settings_disable(),
|
||
.st = &st::settingsButtonNoIcon,
|
||
.toggled = rpl::single(disabled),
|
||
.keywords = { u"screen reader"_q, u"accessibility"_q },
|
||
});
|
||
|
||
if (toggle) {
|
||
toggle->toggledValue(
|
||
) | rpl::filter([=](bool value) {
|
||
return (value != Ui::ScreenReaderModeDisabled());
|
||
}) | rpl::on_next([=](bool value) {
|
||
Core::App().settings().writePref<bool>(
|
||
Core::kScreenReaderModeDisabledKey,
|
||
value);
|
||
Core::App().saveSettingsDelayed();
|
||
Ui::SetScreenReaderModeDisabled(value);
|
||
}, toggle->lifetime());
|
||
}
|
||
|
||
builder.addSkip();
|
||
}
|
||
|
||
class Advanced : public Section<Advanced> {
|
||
public:
|
||
Advanced(
|
||
QWidget *parent,
|
||
not_null<Window::SessionController*> controller);
|
||
|
||
[[nodiscard]] rpl::producer<QString> title() override;
|
||
|
||
private:
|
||
void setupContent();
|
||
|
||
};
|
||
|
||
const auto kMeta = BuildHelper({
|
||
.id = Advanced::Id(),
|
||
.parentId = MainId(),
|
||
.title = &tr::lng_settings_advanced,
|
||
.icon = &st::menuIconManage,
|
||
}, [](SectionBuilder &builder) {
|
||
BuildUpdateSection(builder, true);
|
||
BuildZaStoGramSection(builder);
|
||
BuildDataStorageSection(builder);
|
||
BuildAutoDownloadSection(builder);
|
||
BuildWindowTitleSection(builder);
|
||
#if !defined Q_OS_WIN && !defined Q_OS_MAC
|
||
BuildWindowCloseBehaviorSection(builder);
|
||
#endif
|
||
BuildSystemIntegrationSection(builder);
|
||
BuildPerformanceSection(builder);
|
||
BuildSpellcheckerSection(builder);
|
||
BuildScreenReaderSection(builder);
|
||
BuildExportSection(builder);
|
||
});
|
||
|
||
const SectionBuildMethod kAdvancedSection = kMeta.build;
|
||
|
||
Advanced::Advanced(
|
||
QWidget *parent,
|
||
not_null<Window::SessionController*> controller)
|
||
: Section(parent, controller) {
|
||
setupContent();
|
||
}
|
||
|
||
rpl::producer<QString> Advanced::title() {
|
||
return tr::lng_settings_advanced();
|
||
}
|
||
|
||
void Advanced::setupContent() {
|
||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||
|
||
build(content, kAdvancedSection);
|
||
|
||
Ui::ResizeFitChild(this, content);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
void SetupConnectionType(
|
||
not_null<Window::Controller*> controller,
|
||
not_null<::Main::Account*> account,
|
||
not_null<Ui::VerticalLayout*> container) {
|
||
const auto connectionType = [=] {
|
||
const auto transport = account->mtp().dctransport();
|
||
const auto ping = account->mtp().connectionStatus().pingTime();
|
||
if (!Core::App().settings().proxy().isEnabled()) {
|
||
return transport.isEmpty()
|
||
? tr::lng_connection_auto_connecting(tr::now)
|
||
: !ping
|
||
? tr::lng_connection_auto(tr::now, lt_transport, transport)
|
||
: tr::lng_connection_auto_ping(
|
||
tr::now,
|
||
lt_transport,
|
||
transport,
|
||
lt_ping,
|
||
QString::number(ping));
|
||
} else {
|
||
return transport.isEmpty()
|
||
? tr::lng_connection_proxy_connecting(tr::now)
|
||
: !ping
|
||
? tr::lng_connection_proxy(tr::now, lt_transport, transport)
|
||
: tr::lng_connection_proxy_ping(
|
||
tr::now,
|
||
lt_transport,
|
||
transport,
|
||
lt_ping,
|
||
QString::number(ping));
|
||
}
|
||
};
|
||
const auto button = AddButtonWithLabel(
|
||
container,
|
||
tr::lng_settings_connection_type(),
|
||
rpl::merge(
|
||
Core::App().settings().proxy().connectionTypeChanges(),
|
||
account->mtp().connectionStatus().pingTimeValue() | rpl::to_empty,
|
||
tr::lng_connection_auto_connecting() | rpl::to_empty
|
||
) | rpl::map([=] { return connectionType(); }),
|
||
st::settingsButton,
|
||
{ &st::menuIconNetwork });
|
||
button->addClickHandler([=] {
|
||
controller->show(ProxiesBoxController::CreateOwningBox(account));
|
||
});
|
||
}
|
||
|
||
bool HasUpdate() {
|
||
return !Core::UpdaterDisabled();
|
||
}
|
||
|
||
void SetupUpdate(not_null<Ui::VerticalLayout*> container) {
|
||
if (!HasUpdate()) {
|
||
return;
|
||
}
|
||
|
||
const auto texts = Ui::CreateChild<rpl::event_stream<QString>>(
|
||
container.get());
|
||
const auto downloading = Ui::CreateChild<rpl::event_stream<bool>>(
|
||
container.get());
|
||
const auto version = tr::lng_settings_current_version(
|
||
tr::now,
|
||
lt_version,
|
||
currentVersionText());
|
||
const auto toggle = container->add(object_ptr<Button>(
|
||
container,
|
||
tr::lng_settings_update_automatically(),
|
||
st::settingsUpdateToggle));
|
||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||
toggle,
|
||
texts->events(),
|
||
st::settingsUpdateState);
|
||
|
||
const auto options = container->add(
|
||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||
container,
|
||
object_ptr<Ui::VerticalLayout>(container)));
|
||
const auto inner = options->entity();
|
||
const auto check = inner->add(object_ptr<Button>(
|
||
inner,
|
||
tr::lng_settings_check_now(),
|
||
st::settingsButtonNoIcon));
|
||
const auto update = Ui::CreateChild<Button>(
|
||
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());
|
||
|
||
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 showDownloadProgress = [=](
|
||
int64 ready,
|
||
int64 total,
|
||
bool preferPercent) {
|
||
const auto formatted = [&] {
|
||
if (!preferPercent) {
|
||
return Ui::FormatDownloadText(ready, total);
|
||
}
|
||
const auto percent = (total > 0)
|
||
? std::clamp((ready * 100) / float64(total), 0., 100.)
|
||
: 0.;
|
||
auto result = QString::number(percent, 'f', 2);
|
||
if (result.contains('.')) {
|
||
while (result.endsWith('0')) {
|
||
result.chop(1);
|
||
}
|
||
if (result.endsWith('.')) {
|
||
result.chop(1);
|
||
}
|
||
}
|
||
return result + '%';
|
||
}();
|
||
texts->fire(tr::lng_settings_downloading_update(
|
||
tr::now,
|
||
lt_progress,
|
||
formatted));
|
||
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(),
|
||
checker.percent());
|
||
break;
|
||
case State::Ready:
|
||
texts->fire(tr::lng_settings_update_ready(tr::now));
|
||
update->show();
|
||
break;
|
||
default:
|
||
texts->fire_copy(version);
|
||
break;
|
||
}
|
||
};
|
||
|
||
toggle->toggleOn(rpl::single(cAutoUpdate()));
|
||
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());
|
||
|
||
Core::UpdateChecker checker;
|
||
options->toggleOn(rpl::combine(
|
||
toggle->toggledValue(),
|
||
downloading->events_starting_with(
|
||
checker.state() == Core::UpdateChecker::State::Download)
|
||
) | rpl::map([](bool, bool downloading) {
|
||
return !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,
|
||
progress.percent);
|
||
}, 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);
|
||
|
||
check->addClickHandler([] {
|
||
Core::UpdateChecker checker;
|
||
|
||
cSetLastUpdateCheck(0);
|
||
checker.start();
|
||
});
|
||
update->addClickHandler([] {
|
||
if (!Core::UpdaterDisabled()) {
|
||
Core::checkReadyUpdate();
|
||
}
|
||
Core::Restart();
|
||
});
|
||
}
|
||
|
||
void SetupWindowTitleContent(
|
||
Window::SessionController *controller,
|
||
not_null<Ui::VerticalLayout*> container) {
|
||
const auto checkbox = [&](rpl::producer<QString> &&label, bool checked) {
|
||
return object_ptr<Ui::Checkbox>(
|
||
container,
|
||
std::move(label),
|
||
checked,
|
||
st::settingsCheckbox);
|
||
};
|
||
const auto addCheckbox = [&](
|
||
rpl::producer<QString> &&label,
|
||
bool checked) {
|
||
return container->add(
|
||
checkbox(std::move(label), checked),
|
||
st::settingsCheckboxPadding);
|
||
};
|
||
const auto settings = &Core::App().settings();
|
||
if (controller) {
|
||
const auto content = [=] {
|
||
return settings->windowTitleContent();
|
||
};
|
||
const auto showChatName = addCheckbox(
|
||
tr::lng_settings_title_chat_name(),
|
||
!content().hideChatName);
|
||
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());
|
||
|
||
if (Core::App().domain().accountsAuthedCount() > 1) {
|
||
const auto showAccountName = addCheckbox(
|
||
tr::lng_settings_title_account_name(),
|
||
!content().hideAccountName);
|
||
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 = addCheckbox(
|
||
tr::lng_settings_title_total_count(),
|
||
!content().hideTotalUnread);
|
||
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 = addCheckbox(
|
||
Platform::IsWayland()
|
||
? tr::lng_settings_qt_frame()
|
||
: tr::lng_settings_native_frame(),
|
||
Core::App().settings().nativeWindowFrame());
|
||
|
||
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());
|
||
}
|
||
}
|
||
|
||
void SetupSystemIntegrationContent(
|
||
Window::SessionController *controller,
|
||
not_null<Ui::VerticalLayout*> container) {
|
||
using WorkMode = Core::Settings::WorkMode;
|
||
|
||
const auto checkbox = [&](rpl::producer<QString> &&label, bool checked) {
|
||
return object_ptr<Ui::Checkbox>(
|
||
container,
|
||
std::move(label),
|
||
checked,
|
||
st::settingsCheckbox);
|
||
};
|
||
const auto addCheckbox = [&](
|
||
rpl::producer<QString> &&label,
|
||
bool checked) {
|
||
return container->add(
|
||
checkbox(std::move(label), checked),
|
||
st::settingsCheckboxPadding);
|
||
};
|
||
const auto addSlidingCheckbox = [&](
|
||
rpl::producer<QString> &&label,
|
||
bool checked) {
|
||
return container->add(
|
||
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
|
||
container,
|
||
checkbox(std::move(label), checked),
|
||
st::settingsCheckboxPadding));
|
||
};
|
||
|
||
const auto settings = &Core::App().settings();
|
||
if (Platform::TrayIconSupported()) {
|
||
const auto trayEnabled = [=] {
|
||
const auto workMode = settings->workMode();
|
||
return (workMode == WorkMode::TrayOnly)
|
||
|| (workMode == WorkMode::WindowAndTray);
|
||
};
|
||
const auto tray = addCheckbox(
|
||
tr::lng_settings_workmode_tray(),
|
||
trayEnabled());
|
||
const auto monochrome = Platform::HasMonochromeSetting()
|
||
? addSlidingCheckbox(
|
||
tr::lng_settings_monochrome_icon(),
|
||
settings->trayIconMonochrome())
|
||
: nullptr;
|
||
if (monochrome) {
|
||
monochrome->toggle(tray->checked(), anim::type::instant);
|
||
|
||
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 taskbarEnabled = [=] {
|
||
const auto workMode = settings->workMode();
|
||
return (workMode == WorkMode::WindowOnly)
|
||
|| (workMode == WorkMode::WindowAndTray);
|
||
};
|
||
const auto taskbar = Platform::SkipTaskbarSupported()
|
||
? addCheckbox(
|
||
tr::lng_settings_workmode_window(),
|
||
taskbarEnabled())
|
||
: nullptr;
|
||
|
||
const auto updateWorkmode = [=] {
|
||
const auto newMode = 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();
|
||
};
|
||
|
||
tray->checkedChanges(
|
||
) | rpl::filter([=](bool checked) {
|
||
return (checked != trayEnabled());
|
||
}) | rpl::on_next([=](bool checked) {
|
||
if (!checked && taskbar && !taskbar->checked()) {
|
||
taskbar->setChecked(true);
|
||
} else {
|
||
updateWorkmode();
|
||
}
|
||
if (monochrome) {
|
||
monochrome->toggle(checked, anim::type::normal);
|
||
}
|
||
}, tray->lifetime());
|
||
|
||
if (taskbar) {
|
||
taskbar->checkedChanges(
|
||
) | rpl::filter([=](bool checked) {
|
||
return (checked != taskbarEnabled());
|
||
}) | rpl::on_next([=](bool checked) {
|
||
if (!checked && !tray->checked()) {
|
||
tray->setChecked(true);
|
||
} else {
|
||
updateWorkmode();
|
||
}
|
||
}, taskbar->lifetime());
|
||
}
|
||
}
|
||
|
||
#ifdef Q_OS_MAC
|
||
const auto warnBeforeQuit = addCheckbox(
|
||
tr::lng_settings_mac_warn_before_quit(
|
||
lt_text,
|
||
rpl::single(Platform::ConfirmQuit::QuitKeysString())),
|
||
settings->macWarnBeforeQuit());
|
||
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 enabled = [=] {
|
||
const auto digest = base::Platform::CurrentCustomAppIconDigest();
|
||
return digest && (settings->macRoundIconDigest() == digest);
|
||
};
|
||
const auto roundIcon = addCheckbox(
|
||
tr::lng_settings_mac_round_icon(),
|
||
enabled());
|
||
roundIcon->checkedChanges(
|
||
) | rpl::filter([=](bool checked) {
|
||
return (checked != enabled());
|
||
}) | rpl::on_next([=](bool checked) {
|
||
const auto digest = checked
|
||
? base::Platform::SetCustomAppIcon(IconMacRound())
|
||
: std::optional<uint64>();
|
||
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 closeToTaskbar = addSlidingCheckbox(
|
||
tr::lng_settings_close_to_taskbar(),
|
||
settings->closeBehavior() == Behavior::CloseToTaskbar);
|
||
|
||
const auto closeToTaskbarShown = std::make_shared<
|
||
rpl::variable<bool>
|
||
>(false);
|
||
settings->workModeValue(
|
||
) | rpl::on_next([=](WorkMode workMode) {
|
||
*closeToTaskbarShown = !Core::App().tray().has();
|
||
}, closeToTaskbar->lifetime());
|
||
|
||
closeToTaskbar->toggleOn(closeToTaskbarShown->value());
|
||
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() && controller) {
|
||
const auto minimizedToggled = [=] {
|
||
// This path also avoids hiding a verified launch prompt.
|
||
return cStartMinimized()
|
||
&& !controller->session().domain().local().hasLocalPasscode();
|
||
};
|
||
|
||
const auto autostart = addCheckbox(
|
||
tr::lng_settings_auto_start(),
|
||
cAutoStart());
|
||
const auto minimized = addSlidingCheckbox(
|
||
tr::lng_settings_start_min(),
|
||
minimizedToggled());
|
||
|
||
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->entity()->checked()) {
|
||
Local::writeSettings();
|
||
} else {
|
||
minimized->entity()->setChecked(false);
|
||
}
|
||
}));
|
||
}, autostart->lifetime());
|
||
|
||
Platform::AutostartRequestStateFromSystem(crl::guard(
|
||
controller,
|
||
[=](bool enabled) { autostart->setChecked(enabled); }));
|
||
|
||
minimized->toggleOn(autostart->checkedValue());
|
||
minimized->entity()->checkedChanges(
|
||
) | rpl::filter([=](bool checked) {
|
||
return (checked != minimizedToggled());
|
||
}) | rpl::on_next([=](bool checked) {
|
||
// Keep the verified prompt refusal in this path too.
|
||
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 = addCheckbox(
|
||
tr::lng_settings_add_sendto(),
|
||
cSendToMenu());
|
||
|
||
sendto->checkedChanges(
|
||
) | rpl::filter([](bool checked) {
|
||
return (checked != cSendToMenu());
|
||
}) | rpl::on_next([](bool checked) {
|
||
cSetSendToMenu(checked);
|
||
psSendToMenu(checked);
|
||
Local::writeSettings();
|
||
}, sendto->lifetime());
|
||
}
|
||
}
|
||
|
||
void SetupAnimations(
|
||
not_null<Window::Controller*> window,
|
||
not_null<Ui::VerticalLayout*> container) {
|
||
container->add(object_ptr<Button>(
|
||
container,
|
||
tr::lng_settings_power_menu(),
|
||
st::settingsButtonNoIcon
|
||
))->setClickedCallback([=] {
|
||
window->show(Box(PowerSavingBox, PowerSaving::Flags()));
|
||
});
|
||
}
|
||
|
||
void ArchiveSettingsBox(
|
||
not_null<Ui::GenericBox*> box,
|
||
not_null<Window::SessionController*> controller) {
|
||
box->setTitle(tr::lng_settings_archive_title());
|
||
box->setWidth(st::boxWideWidth);
|
||
|
||
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
||
|
||
PreloadArchiveSettings(&controller->session());
|
||
|
||
struct State {
|
||
Ui::SlideWrap<Ui::VerticalLayout> *foldersWrap = nullptr;
|
||
Ui::SettingsButton *folders = nullptr;
|
||
};
|
||
const auto state = box->lifetime().make_state<State>();
|
||
const auto privacy = &controller->session().api().globalPrivacy();
|
||
|
||
const auto container = box->verticalLayout();
|
||
AddSkip(container);
|
||
AddSubsectionTitle(container, tr::lng_settings_unmuted_chats());
|
||
|
||
using Unarchive = Api::UnarchiveOnNewMessage;
|
||
container->add(object_ptr<Button>(
|
||
container,
|
||
tr::lng_settings_always_in_archive(),
|
||
st::settingsButtonNoIcon
|
||
))->toggleOn(privacy->unarchiveOnNewMessage(
|
||
) | rpl::map(
|
||
rpl::mappers::_1 == Unarchive::None
|
||
))->toggledChanges(
|
||
) | rpl::filter([=](bool toggled) {
|
||
const auto current = privacy->unarchiveOnNewMessageCurrent();
|
||
state->foldersWrap->toggle(!toggled, anim::type::normal);
|
||
return toggled != (current == Unarchive::None);
|
||
}) | rpl::on_next([=](bool toggled) {
|
||
privacy->updateUnarchiveOnNewMessage(toggled
|
||
? Unarchive::None
|
||
: state->folders->toggled()
|
||
? Unarchive::NotInFoldersUnmuted
|
||
: Unarchive::AnyUnmuted);
|
||
}, container->lifetime());
|
||
|
||
AddSkip(container);
|
||
AddDividerText(container, tr::lng_settings_unmuted_chats_about());
|
||
|
||
state->foldersWrap = container->add(
|
||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||
container,
|
||
object_ptr<Ui::VerticalLayout>(container)));
|
||
const auto inner = state->foldersWrap->entity();
|
||
AddSkip(inner);
|
||
AddSubsectionTitle(inner, tr::lng_settings_chats_from_folders());
|
||
|
||
state->folders = inner->add(object_ptr<Button>(
|
||
inner,
|
||
tr::lng_settings_always_in_archive(),
|
||
st::settingsButtonNoIcon
|
||
))->toggleOn(privacy->unarchiveOnNewMessage(
|
||
) | rpl::map(
|
||
rpl::mappers::_1 != Unarchive::AnyUnmuted
|
||
));
|
||
state->folders->toggledChanges(
|
||
) | rpl::filter([=](bool toggled) {
|
||
const auto current = privacy->unarchiveOnNewMessageCurrent();
|
||
return toggled != (current != Unarchive::AnyUnmuted);
|
||
}) | rpl::on_next([=](bool toggled) {
|
||
const auto current = privacy->unarchiveOnNewMessageCurrent();
|
||
privacy->updateUnarchiveOnNewMessage(!toggled
|
||
? Unarchive::AnyUnmuted
|
||
: (current == Unarchive::AnyUnmuted)
|
||
? Unarchive::NotInFoldersUnmuted
|
||
: current);
|
||
}, inner->lifetime());
|
||
|
||
AddSkip(inner);
|
||
AddDividerText(inner, tr::lng_settings_chats_from_folders_about());
|
||
|
||
state->foldersWrap->toggle(
|
||
privacy->unarchiveOnNewMessageCurrent() != Unarchive::None,
|
||
anim::type::instant);
|
||
|
||
SetupArchiveAndMute(controller, box->verticalLayout());
|
||
}
|
||
|
||
void PreloadArchiveSettings(not_null<::Main::Session*> session) {
|
||
session->api().globalPrivacy().reload();
|
||
}
|
||
|
||
Type AdvancedId() {
|
||
return Advanced::Id();
|
||
}
|
||
|
||
} // namespace Settings
|