diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 0f78e6703..168770431 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -773,8 +773,14 @@ PRIVATE
intro/intro_step.h
intro/intro_widget.cpp
intro/intro_widget.h
+ kotato/boxes/kotato_radio_box.cpp
+ kotato/boxes/kotato_radio_box.h
kotato/kotato_lang.cpp
kotato/kotato_lang.h
+ kotato/kotato_settings.cpp
+ kotato/kotato_settings.h
+ kotato/kotato_settings_menu.cpp
+ kotato/kotato_settings_menu.h
kotato/kotato_version.h
lang/lang_cloud_manager.cpp
lang/lang_cloud_manager.h
diff --git a/Telegram/Resources/default_kotato-settings-custom.json b/Telegram/Resources/default_kotato-settings-custom.json
new file mode 100644
index 000000000..cbbb06b3c
--- /dev/null
+++ b/Telegram/Resources/default_kotato-settings-custom.json
@@ -0,0 +1,20 @@
+// This is a list of your own settings for Kotatogram Desktop
+// You can see full list of settings in the 'kotato-settings-default.json' file
+
+{
+ // "fonts": {
+ // "main": "Open Sans",
+ // "semibold": "Open Sans Semibold",
+ // "semibold_is_bold": false,
+ // "monospaced": "Consolas"
+ // },
+ // "sticker_height": 170,
+ // "big_emoji_outline": true,
+ // "always_show_scheduled": false,
+ // "show_chat_id": false,
+ // "net_speed_boost": null,
+ // "show_phone_in_drawer": true,
+ // "scales": [],
+ // "confirm_before_calls": false,
+ // "recent_stickers_limit": 20
+}
diff --git a/Telegram/Resources/icons/settings/settings_kotato.png b/Telegram/Resources/icons/settings/settings_kotato.png
new file mode 100644
index 000000000..5cbf718c2
Binary files /dev/null and b/Telegram/Resources/icons/settings/settings_kotato.png differ
diff --git a/Telegram/Resources/icons/settings/settings_kotato@2x.png b/Telegram/Resources/icons/settings/settings_kotato@2x.png
new file mode 100644
index 000000000..2e871fcf6
Binary files /dev/null and b/Telegram/Resources/icons/settings/settings_kotato@2x.png differ
diff --git a/Telegram/Resources/icons/settings/settings_kotato@3x.png b/Telegram/Resources/icons/settings/settings_kotato@3x.png
new file mode 100644
index 000000000..b187ccb2e
Binary files /dev/null and b/Telegram/Resources/icons/settings/settings_kotato@3x.png differ
diff --git a/Telegram/Resources/langs/rewrites/en.json b/Telegram/Resources/langs/rewrites/en.json
index e77a886b4..b2fa173d9 100644
--- a/Telegram/Resources/langs/rewrites/en.json
+++ b/Telegram/Resources/langs/rewrites/en.json
@@ -26,6 +26,14 @@
"ktg_outdated_soon": "Otherwise, Kotatogram Desktop will stop updating on {date}.",
"ktg_outdated_now": "So that Kotatogram Desktop can update to newer versions.",
"ktg_mac_menu_show": "Show Kotatogram",
+ "ktg_settings_kotato": "Kotatogram Settings",
+ "ktg_settings_chats": "Chats",
+ "ktg_settings_network": "Network",
+ "ktg_settings_system": "System",
+ "ktg_settings_other": "Other",
+ "ktg_settings_filters": "Folders",
+ "ktg_settings_messages": "Messages",
+ "ktg_settings_forward": "Forward",
"ktg_in_app_update_disabled": "In-app updater is disabled.",
"dummy_last_string": ""
}
diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc
index 0df2274bf..e83d242c0 100644
--- a/Telegram/Resources/qrc/telegram/telegram.qrc
+++ b/Telegram/Resources/qrc/telegram/telegram.qrc
@@ -85,6 +85,7 @@
../../default_shortcuts-custom.json
+ ../../default_kotato-settings-custom.json
../../../../lib/xdg/kotatogramdesktop.desktop
diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp
index 1cd9d1d1c..59a31dece 100644
--- a/Telegram/SourceFiles/core/launcher.cpp
+++ b/Telegram/SourceFiles/core/launcher.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/launcher.h"
+#include "kotato/kotato_settings.h"
#include "kotato/kotato_version.h"
#include "platform/platform_launcher.h"
#include "platform/platform_specific.h"
@@ -316,6 +317,9 @@ void Launcher::init() {
}
int Launcher::exec() {
+ // This should be called before init to load default
+ // values and set some options that are not stored in JSON.
+ Kotato::JsonSettings::Start();
init();
if (cLaunchMode() == LaunchModeFixPrevious) {
@@ -327,6 +331,7 @@ int Launcher::exec() {
// Must be started before Platform is started.
Logs::start(this);
base::options::init(cWorkingDir() + "tdata/experimental_options.json");
+ Kotato::JsonSettings::Load();
if (Logs::DebugEnabled()) {
const auto openalLogPath = QDir::toNativeSeparators(
@@ -363,6 +368,7 @@ int Launcher::exec() {
CrashReports::Finish();
Platform::finish();
+ Kotato::JsonSettings::Finish();
Logs::finish();
return result;
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index 485455ed8..332dc4f10 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -386,6 +386,8 @@ bool ResolveSettings(
? ::Settings::Type::Folders
: (section == qstr("devices"))
? ::Settings::Type::Sessions
+ : (section == qstr("kotato"))
+ ? ::Settings::Type::Kotato
: ::Settings::Type::Main;
controller->showSettings(type);
return true;
@@ -681,7 +683,7 @@ const std::vector &LocalUrlHandlers() {
ResolvePrivatePost
},
{
- qsl("^settings(/folders|/devices|/language)?$"),
+ qsl("^settings(/folders|/devices|/language|/kotato)?$"),
ResolveSettings
},
{
diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp
index b57ffd69a..f028a03a1 100644
--- a/Telegram/SourceFiles/info/info_top_bar.cpp
+++ b/Telegram/SourceFiles/info/info_top_bar.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include
#include
+#include "kotato/kotato_lang.h"
#include "lang/lang_keys.h"
#include "lang/lang_numbers_animation.h"
#include "info/info_wrap_widget.h"
@@ -643,6 +644,8 @@ rpl::producer TitleValue(
return tr::lng_settings_section_call_settings();
case Section::SettingsType::Experimental:
return tr::lng_settings_experimental();
+ case Section::SettingsType::Kotato:
+ return rktr("ktg_settings_kotato");
}
Unexpected("Bad settings type in Info::TitleValue()");
diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp
index bcb40f632..ac4f39024 100644
--- a/Telegram/SourceFiles/info/info_wrap_widget.cpp
+++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp
@@ -393,7 +393,8 @@ void WrapWidget::createTopBar() {
// addProfileNotificationsButton();
} else if (section.type() == Section::Type::Settings
&& (section.settingsType() == Section::SettingsType::Main
- || section.settingsType() == Section::SettingsType::Chat)) {
+ || section.settingsType() == Section::SettingsType::Chat
+ || section.settingsType() == Section::SettingsType::Kotato)) {
addTopBarMenuButton();
} else if (section.type() == Section::Type::Settings
&& section.settingsType() == Section::SettingsType::Information) {
diff --git a/Telegram/SourceFiles/kotato/boxes/kotato_radio_box.cpp b/Telegram/SourceFiles/kotato/boxes/kotato_radio_box.cpp
new file mode 100644
index 000000000..0f1c77f7b
--- /dev/null
+++ b/Telegram/SourceFiles/kotato/boxes/kotato_radio_box.cpp
@@ -0,0 +1,176 @@
+/*
+This file is part of Kotatogram Desktop,
+the unofficial app based on Telegram Desktop.
+
+For license and copyright information please follow this link:
+https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL
+*/
+#include "kotato/boxes/kotato_radio_box.h"
+
+#include "kotato/kotato_settings.h"
+#include "lang/lang_keys.h"
+#include "ui/wrap/vertical_layout.h"
+#include "ui/wrap/padding_wrap.h"
+#include "ui/wrap/wrap.h"
+#include "ui/widgets/checkbox.h"
+#include "ui/widgets/labels.h"
+#include "ui/boxes/confirm_box.h"
+#include "styles/style_layers.h"
+#include "styles/style_boxes.h"
+#include "core/application.h"
+
+namespace Kotato {
+
+RadioBox::RadioBox(
+ QWidget*,
+ const QString &title,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn saveCallback,
+ bool warnRestart)
+: _title(title)
+, _startValue(currentValue)
+, _valueCount(valueCount)
+, _labelGetter(labelGetter)
+, _saveCallback(std::move(saveCallback))
+, _warnRestart(warnRestart)
+, _owned(this)
+, _content(_owned.data()) {
+}
+
+RadioBox::RadioBox(
+ QWidget*,
+ const QString &title,
+ const QString &description,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn saveCallback,
+ bool warnRestart)
+: _title(title)
+, _description(description)
+, _startValue(currentValue)
+, _valueCount(valueCount)
+, _labelGetter(labelGetter)
+, _saveCallback(std::move(saveCallback))
+, _warnRestart(warnRestart)
+, _owned(this)
+, _content(_owned.data()) {
+}
+
+RadioBox::RadioBox(
+ QWidget*,
+ const QString &title,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn descriptionGetter,
+ Fn saveCallback,
+ bool warnRestart)
+: _title(title)
+, _startValue(currentValue)
+, _valueCount(valueCount)
+, _labelGetter(labelGetter)
+, _descriptionGetter(descriptionGetter)
+, _saveCallback(std::move(saveCallback))
+, _warnRestart(warnRestart)
+, _owned(this)
+, _content(_owned.data()) {
+}
+
+RadioBox::RadioBox(
+ QWidget*,
+ const QString &title,
+ const QString &description,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn descriptionGetter,
+ Fn saveCallback,
+ bool warnRestart)
+: _title(title)
+, _description(description)
+, _startValue(currentValue)
+, _valueCount(valueCount)
+, _labelGetter(labelGetter)
+, _descriptionGetter(descriptionGetter)
+, _saveCallback(std::move(saveCallback))
+, _warnRestart(warnRestart)
+, _owned(this)
+, _content(_owned.data()) {
+}
+
+void RadioBox::prepare() {
+ setTitle(rpl::single(_title));
+
+ addButton(tr::lng_settings_save(), [=] { save(); });
+ addButton(tr::lng_cancel(), [=] { closeBox(); });
+
+ if (!_description.isEmpty()) {
+ _content->add(
+ object_ptr(_content, _description, st::boxDividerLabel),
+ style::margins(
+ st::boxPadding.left(),
+ 0,
+ st::boxPadding.right(),
+ st::boxPadding.bottom()));
+ }
+
+ _group = std::make_shared(_startValue);
+
+ for (auto i = 0; i != _valueCount; ++i) {
+ const auto description = _descriptionGetter
+ ? _descriptionGetter(i)
+ : QString();
+
+ _content->add(
+ object_ptr(
+ _content,
+ _group,
+ i,
+ _labelGetter(i),
+ st::autolockButton),
+ style::margins(
+ st::boxPadding.left(),
+ st::boxPadding.bottom(),
+ st::boxPadding.right(),
+ description.isEmpty() ? st::boxPadding.bottom() : 0));
+ if (!description.isEmpty()) {
+ _content->add(
+ object_ptr(_content, description, st::boxDividerLabel),
+ style::margins(
+ st::boxPadding.left()
+ + st::autolockButton.margin.left()
+ + st::autolockButton.margin.right()
+ + st::defaultToggle.width
+ + st::defaultToggle.border * 2,
+ 0,
+ st::boxPadding.right(),
+ st::boxPadding.bottom()));
+ }
+ }
+
+ auto wrap = object_ptr(this, std::move(_owned));
+ setDimensionsToContent(st::boxWidth, wrap.data());
+ setInnerWidget(std::move(wrap));
+}
+
+void RadioBox::save() {
+ _saveCallback(_group->value());
+ if (_warnRestart) {
+ const auto box = std::make_shared>();
+
+ *box = getDelegate()->show(
+ Box(
+ tr::lng_settings_need_restart(tr::now),
+ tr::lng_settings_restart_now(tr::now),
+ tr::lng_settings_restart_later(tr::now),
+ [] { Core::Restart(); },
+ [=] { closeBox(); }));
+ } else {
+ closeBox();
+ }
+}
+
+} // namespace Kotato
\ No newline at end of file
diff --git a/Telegram/SourceFiles/kotato/boxes/kotato_radio_box.h b/Telegram/SourceFiles/kotato/boxes/kotato_radio_box.h
new file mode 100644
index 000000000..93ded92b7
--- /dev/null
+++ b/Telegram/SourceFiles/kotato/boxes/kotato_radio_box.h
@@ -0,0 +1,80 @@
+/*
+This file is part of Kotatogram Desktop,
+the unofficial app based on Telegram Desktop.
+
+For license and copyright information please follow this link:
+https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL
+*/
+#pragma once
+
+#include "boxes/abstract_box.h"
+
+namespace Ui {
+class RadiobuttonGroup;
+class Radiobutton;
+class FlatLabel;
+class VerticalLayout;
+} // namespace Ui
+
+namespace Kotato {
+
+class RadioBox : public Ui::BoxContent {
+public:
+ RadioBox(
+ QWidget* parent,
+ const QString &title,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn saveCallback,
+ bool warnRestart = false);
+ RadioBox(
+ QWidget* parent,
+ const QString &title,
+ const QString &description,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn saveCallback,
+ bool warnRestart = false);
+ RadioBox(
+ QWidget* parent,
+ const QString &title,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn descriptionGetter,
+ Fn saveCallback,
+ bool warnRestart = false);
+ RadioBox(
+ QWidget* parent,
+ const QString &title,
+ const QString &description,
+ int currentValue,
+ int valueCount,
+ Fn labelGetter,
+ Fn descriptionGetter,
+ Fn saveCallback,
+ bool warnRestart = false);
+
+protected:
+ void prepare() override;
+
+private:
+ void save();
+
+ QString _title;
+ QString _description;
+ int _startValue;
+ int _valueCount;
+ Fn _labelGetter;
+ Fn _descriptionGetter;
+ Fn _saveCallback;
+ bool _warnRestart = false;
+ std::shared_ptr _group;
+
+ object_ptr _owned;
+ not_null _content;
+};
+
+} // namespace Kotato
diff --git a/Telegram/SourceFiles/kotato/kotato_settings.cpp b/Telegram/SourceFiles/kotato/kotato_settings.cpp
new file mode 100644
index 000000000..6c3ec157e
--- /dev/null
+++ b/Telegram/SourceFiles/kotato/kotato_settings.cpp
@@ -0,0 +1,771 @@
+/*
+This file is part of Kotatogram Desktop,
+the unofficial app based on Telegram Desktop.
+
+For license and copyright information please follow this link:
+https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL
+*/
+#include "kotato/kotato_settings.h"
+
+#include "kotato/kotato_version.h"
+#include "mainwindow.h"
+#include "mainwidget.h"
+#include "window/window_controller.h"
+#include "core/application.h"
+#include "data/data_peer_id.h"
+#include "base/parse_helper.h"
+#include "base/timer.h"
+#include "facades.h"
+#include "ui/widgets/input_fields.h"
+#include "data/data_chat_filters.h"
+#include "platform/platform_file_utilities.h"
+
+#include
+#include
+#include
+#include
+
+namespace Kotato {
+namespace JsonSettings {
+namespace {
+
+constexpr auto kWriteJsonTimeout = crl::time(5000);
+
+class Manager : public QObject {
+public:
+ Manager();
+ void load();
+ void fill();
+ void write(bool force = false);
+
+ [[nodiscard]] QVariant get(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ [[nodiscard]] QVariant getWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ [[nodiscard]] QVariantMap getAllWithPending(const QString &key);
+ [[nodiscard]] rpl::producer events(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ [[nodiscard]] rpl::producer eventsWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ void set(
+ const QString &key,
+ QVariant value,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ void setAfterRestart(
+ const QString &key,
+ QVariant value,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ void reset(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ void resetAfterRestart(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+ void writeTimeout();
+
+private:
+ [[nodiscard]] QVariant getDefault(const QString &key);
+
+ void writeDefaultFile();
+ void writeCurrentSettings();
+ bool readCustomFile();
+ void writing();
+
+ base::Timer _jsonWriteTimer;
+
+ rpl::event_stream _eventStream;
+ rpl::event_stream _pendingEventStream;
+ QHash _settingsHashMap;
+ QHash _defaultSettingsHashMap;
+
+};
+
+inline QString MakeMapKey(const QString &key, uint64 accountId, bool isTestAccount) {
+ return (accountId == 0) ? key : key
+ + (isTestAccount ? qsl(":test_") : qsl(":"))
+ + QString::number(accountId);
+}
+
+QVariantMap GetAllWithPending(const QString &key);
+
+enum SettingScope {
+ Global,
+ Account,
+};
+
+enum SettingStorage {
+ None,
+ MainJson,
+};
+
+enum SettingType {
+ BoolSetting,
+ IntSetting,
+ QStringSetting,
+ QJsonArraySetting,
+};
+
+using CheckHandler = Fn;
+
+CheckHandler IntLimit(int min, int max, int defaultValue) {
+ return [=] (QVariant value) -> QVariant {
+ if (value.canConvert()) {
+ auto intValue = value.toInt();
+ if (intValue < min) {
+ return min;
+ } else if (intValue > max) {
+ return max;
+ } else {
+ return value;
+ }
+ } else {
+ return defaultValue;
+ }
+ };
+}
+
+inline CheckHandler IntLimit(int min, int max) {
+ return IntLimit(min, max, min);
+}
+
+CheckHandler IntLimitMin(int min) {
+ return [=] (QVariant value) -> QVariant {
+ if (value.canConvert()) {
+ auto intValue = value.toInt();
+ if (intValue < min) {
+ return min;
+ } else {
+ return value;
+ }
+ } else {
+ return min;
+ }
+ };
+}
+
+
+struct Definition {
+ SettingScope scope = SettingScope::Global;
+ SettingStorage storage = SettingStorage::MainJson;
+ SettingType type = SettingType::BoolSetting;
+ QVariant defaultValue;
+ QVariant fillerValue;
+ CheckHandler limitHandler = nullptr;
+};
+
+const std::map> DefinitionMap {
+
+ // Non-stored settings
+ // Stored settings
+};
+
+using OldOptionKey = QString;
+using NewOptionKey = QString;
+
+const std::map> ReplacedOptionsMap {
+};
+
+QString DefaultFilePath() {
+ return cWorkingDir() + qsl("tdata/kotato-settings-default.json");
+}
+
+QString CustomFilePath() {
+ return cWorkingDir() + qsl("tdata/kotato-settings-custom.json");
+}
+
+bool DefaultFileIsValid() {
+ QFile file(DefaultFilePath());
+ if (!file.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+ auto error = QJsonParseError{ 0, QJsonParseError::NoError };
+ const auto document = QJsonDocument::fromJson(
+ base::parse::stripComments(file.readAll()),
+ &error);
+ file.close();
+
+ if (error.error != QJsonParseError::NoError || !document.isObject()) {
+ return false;
+ }
+ const auto settings = document.object();
+
+ const auto version = settings.constFind(qsl("version"));
+ if (version == settings.constEnd() || (*version).toInt() != AppKotatoVersion) {
+ return false;
+ }
+
+ return true;
+}
+
+void WriteDefaultCustomFile() {
+ const auto path = CustomFilePath();
+ auto input = QFile(":/misc/default_kotato-settings-custom.json");
+ auto output = QFile(path);
+ if (input.open(QIODevice::ReadOnly) && output.open(QIODevice::WriteOnly)) {
+ output.write(input.readAll());
+ }
+}
+
+QByteArray GenerateSettingsJson(bool areDefault = false) {
+ auto settings = QJsonObject();
+
+ auto settingsFoldersLocal = QJsonObject();
+
+ const auto getRef = [&settings] (
+ QStringList &keyParts,
+ const Definition &def) -> QJsonValueRef {
+ const auto firstKey = keyParts.takeFirst();
+ if (!settings.contains(firstKey)) {
+ settings.insert(firstKey, QJsonObject());
+ }
+ auto resultRef = settings[firstKey];
+ for (const auto &key : keyParts) {
+ auto referenced = resultRef.toObject();
+ if (!referenced.contains(key)) {
+ referenced.insert(key, QJsonObject());
+ resultRef = referenced;
+ }
+ resultRef = referenced[key];
+ }
+ return resultRef;
+ };
+
+ const auto getValue = [=] (
+ const QString &key,
+ const Definition &def) -> QJsonValue {
+ auto value = (!areDefault)
+ ? GetWithPending(key)
+ : def.fillerValue.isValid()
+ ? def.fillerValue
+ : def.defaultValue.isValid()
+ ? def.defaultValue
+ : QVariant();
+ switch (def.type) {
+ case SettingType::BoolSetting:
+ return value.isValid() ? value.toBool() : false;
+ case SettingType::IntSetting:
+ return value.isValid() ? value.toInt() : 0;
+ case SettingType::QStringSetting:
+ return value.isValid() ? value.toString() : QString();
+ case SettingType::QJsonArraySetting:
+ return value.isValid() ? value.toJsonArray() : QJsonArray();
+ }
+
+ return QJsonValue();
+ };
+
+ const auto getAccountValue = [=] (const QString &key) -> QJsonValue {
+ if (areDefault) {
+ return QJsonObject();
+ }
+
+ auto values = GetAllWithPending(key);
+ auto resultObject = QJsonObject();
+
+ for (auto i = values.constBegin(); i != values.constEnd(); ++i) {
+ const auto value = i.value();
+ const auto jsonValue = (value.userType() == QMetaType::Bool)
+ ? QJsonValue(value.toBool())
+ : (value.userType() == QMetaType::Int)
+ ? QJsonValue(value.toInt())
+ : (value.userType() == QMetaType::QString)
+ ? QJsonValue(value.toString())
+ : (value.userType() == QMetaType::QJsonArray)
+ ? QJsonValue(value.toJsonArray())
+ : QJsonValue(QJsonValue::Null);
+ resultObject.insert(i.key(), jsonValue);
+ }
+
+ return resultObject;
+ };
+
+ for (const auto &[key, def] : DefinitionMap) {
+ if (def.storage == SettingStorage::None) {
+ continue;
+ }
+
+ auto parts = key.split(QChar('/'));
+ auto value = (def.scope == SettingScope::Account)
+ ? getAccountValue(key)
+ : getValue(key, def);
+ if (parts.size() > 1) {
+ const auto lastKey = parts.takeLast();
+ auto ref = getRef(parts, def);
+ auto referenced = ref.toObject();
+ referenced.insert(lastKey, value);
+ ref = referenced;
+ } else {
+ settings.insert(key, value);
+ }
+ }
+
+ if (areDefault) {
+ settings.insert(qsl("version"), QString::number(AppKotatoVersion));
+ }
+
+ auto document = QJsonDocument();
+ document.setObject(settings);
+ return document.toJson(QJsonDocument::Indented);
+}
+
+std::unique_ptr Data;
+
+QVariantMap GetAllWithPending(const QString &key) {
+ return (Data) ? Data->getAllWithPending(key) : QVariantMap();
+}
+
+} // namespace
+
+Manager::Manager()
+: _jsonWriteTimer([=] { writeTimeout(); }) {
+}
+
+void Manager::load() {
+ if (!DefaultFileIsValid()) {
+ writeDefaultFile();
+ }
+ if (!readCustomFile()) {
+ WriteDefaultCustomFile();
+ }
+}
+
+void Manager::fill() {
+ _settingsHashMap.reserve(DefinitionMap.size());
+ _defaultSettingsHashMap.reserve(DefinitionMap.size());
+
+ const auto addDefaultValue = [&] (const QString &option, QVariant value) {
+ _settingsHashMap.insert(option, value);
+ };
+
+ for (const auto &[key, def] : DefinitionMap) {
+ if (def.scope != SettingScope::Global) {
+ continue;
+ }
+
+ auto defaultValue = def.defaultValue;
+ if (!defaultValue.isValid()) {
+ if (def.type == SettingType::BoolSetting) {
+ defaultValue = false;
+ } else if (def.type == SettingType::IntSetting) {
+ defaultValue = 0;
+ } else if (def.type == SettingType::QStringSetting) {
+ defaultValue = QString();
+ } else if (def.type == SettingType::QJsonArraySetting) {
+ defaultValue = QJsonArray();
+ } else {
+ continue;
+ }
+ }
+
+ addDefaultValue(key, defaultValue);
+ }
+}
+
+void Manager::write(bool force) {
+ if (force && _jsonWriteTimer.isActive()) {
+ _jsonWriteTimer.cancel();
+ writeTimeout();
+ } else if (!force && !_jsonWriteTimer.isActive()) {
+ _jsonWriteTimer.callOnce(kWriteJsonTimeout);
+ }
+}
+
+QVariant Manager::get(const QString &key, uint64 accountId, bool isTestAccount) {
+ const auto mapKey = MakeMapKey(key, accountId, isTestAccount);
+ auto result = _settingsHashMap.contains(mapKey)
+ ? _settingsHashMap.value(mapKey)
+ : QVariant();
+ if (!result.isValid()) {
+ result = _settingsHashMap.contains(key)
+ ? _settingsHashMap.value(key)
+ : getDefault(key);
+ _settingsHashMap.insert(mapKey, result);
+ }
+ return result;
+}
+
+QVariant Manager::getWithPending(const QString &key, uint64 accountId, bool isTestAccount) {
+ const auto mapKey = MakeMapKey(key, accountId, isTestAccount);
+ auto result = _defaultSettingsHashMap.contains(mapKey)
+ ? _defaultSettingsHashMap.value(mapKey)
+ : _settingsHashMap.contains(mapKey)
+ ? _settingsHashMap.value(mapKey)
+ : QVariant();
+ if (!result.isValid()) {
+ result = _settingsHashMap.contains(key)
+ ? _settingsHashMap.value(key)
+ : getDefault(key);
+ _settingsHashMap.insert(mapKey, result);
+ }
+ return result;
+}
+
+QVariantMap Manager::getAllWithPending(const QString &key) {
+ auto resultMap = QVariantMap();
+
+ if (_defaultSettingsHashMap.contains(key) || _settingsHashMap.contains(key)) {
+ resultMap.insert(
+ qsl("0"),
+ _defaultSettingsHashMap.contains(key)
+ ? _defaultSettingsHashMap.value(key)
+ : _settingsHashMap.value(key));
+ return resultMap;
+ }
+
+ const auto prefix = key + qsl(":");
+
+ for (auto i = _settingsHashMap.constBegin(); i != _settingsHashMap.constEnd(); ++i) {
+ const auto mapKey = i.key();
+ if (!mapKey.startsWith(prefix)) {
+ continue;
+ }
+
+ const auto accountKey = mapKey.mid(prefix.size());
+ resultMap.insert(accountKey, i.value());
+ }
+
+ for (auto i = _defaultSettingsHashMap.constBegin(); i != _defaultSettingsHashMap.constEnd(); ++i) {
+ const auto mapKey = i.key();
+ if (!mapKey.startsWith(prefix)) {
+ continue;
+ }
+
+ const auto accountKey = mapKey.mid(prefix.size());
+ resultMap.insert(accountKey, i.value());
+ }
+
+ return resultMap;
+}
+
+QVariant Manager::getDefault(const QString &key) {
+ const auto &defIterator = DefinitionMap.find(key);
+ if (defIterator == DefinitionMap.end()) {
+ return QVariant();
+ }
+ const auto defaultValue = &defIterator->second.defaultValue;
+ const auto settingType = defIterator->second.type;
+ switch (settingType) {
+ case SettingType::QStringSetting:
+ return QVariant(defaultValue->isValid()
+ ? defaultValue->toString()
+ : QString());
+ case SettingType::IntSetting:
+ return QVariant(defaultValue->isValid()
+ ? defaultValue->toInt()
+ : 0);
+ case SettingType::BoolSetting:
+ return QVariant(defaultValue->isValid()
+ ? defaultValue->toBool()
+ : false);
+ case SettingType::QJsonArraySetting:
+ return QVariant(defaultValue->isValid()
+ ? defaultValue->toJsonArray()
+ : QJsonArray());
+ }
+
+ return QVariant();
+}
+
+rpl::producer Manager::events(const QString &key, uint64 accountId, bool isTestAccount) {
+ const auto mapKey = MakeMapKey(key, accountId, isTestAccount);
+ return _eventStream.events() | rpl::filter(rpl::mappers::_1 == mapKey);
+}
+
+rpl::producer Manager::eventsWithPending(const QString &key, uint64 accountId, bool isTestAccount) {
+ const auto mapKey = MakeMapKey(key, accountId, isTestAccount);
+ return _pendingEventStream.events() | rpl::filter(rpl::mappers::_1 == mapKey);
+}
+
+void Manager::set(const QString &key, QVariant value, uint64 accountId, bool isTestAccount) {
+ const auto mapKey = MakeMapKey(key, accountId, isTestAccount);
+ _settingsHashMap.insert(mapKey, value);
+ _eventStream.fire_copy(mapKey);
+}
+
+void Manager::setAfterRestart(const QString &key, QVariant value, uint64 accountId, bool isTestAccount) {
+ const auto mapKey = MakeMapKey(key, accountId, isTestAccount);
+ if (!_settingsHashMap.contains(mapKey)
+ || _settingsHashMap.value(mapKey) != value) {
+ _defaultSettingsHashMap.insert(mapKey, value);
+ } else if (_settingsHashMap.contains(mapKey)
+ && _settingsHashMap.value(mapKey) == value) {
+ _defaultSettingsHashMap.remove(mapKey);
+ }
+ _pendingEventStream.fire_copy(mapKey);
+}
+
+void Manager::reset(const QString &key, uint64 accountId, bool isTestAccount) {
+ set(key, getDefault(key), accountId, isTestAccount);
+}
+
+void Manager::resetAfterRestart(const QString &key, uint64 accountId, bool isTestAccount) {
+ setAfterRestart(key, getDefault(key), accountId, isTestAccount);
+}
+
+bool Manager::readCustomFile() {
+ QFile file(CustomFilePath());
+ if (!file.exists()) {
+ return false;
+ }
+ if (!file.open(QIODevice::ReadOnly)) {
+ return true;
+ }
+ auto error = QJsonParseError{ 0, QJsonParseError::NoError };
+ const auto document = QJsonDocument::fromJson(
+ base::parse::stripComments(file.readAll()),
+ &error);
+ file.close();
+
+ if (error.error != QJsonParseError::NoError) {
+ return true;
+ } else if (!document.isObject()) {
+ return true;
+ }
+ const auto settings = document.object();
+
+ if (settings.isEmpty()) {
+ return true;
+ }
+
+ const auto getObjectValue = [&settings] (
+ QStringList &keyParts,
+ const Definition &def) -> QJsonValue {
+ const auto firstKey = keyParts.takeFirst();
+ if (!settings.contains(firstKey)) {
+ return QJsonValue();
+ }
+ auto resultRef = settings.value(firstKey);
+ for (const auto &key : keyParts) {
+ auto referenced = resultRef.toObject();
+ if (!referenced.contains(key)) {
+ return QJsonValue();
+ }
+ resultRef = referenced.value(key);
+ }
+ return resultRef;
+ };
+
+ const auto prepareAccountOptions = [] (
+ const QString &key,
+ const Definition &def,
+ const QJsonValue &val,
+ Fn callback) {
+
+ if (val.isUndefined()) {
+ return;
+ } else if (def.scope == SettingScope::Account && val.isObject()) {
+ const auto accounts = val.toObject();
+ if (accounts.isEmpty()) {
+ return;
+ }
+
+ for (auto i = accounts.constBegin(); i != accounts.constEnd(); ++i) {
+ auto optionKey = i.key();
+ auto isTestAccount = false;
+ if (optionKey.startsWith("test_")) {
+ isTestAccount = true;
+ optionKey = optionKey.mid(5);
+ }
+ auto accountId = optionKey.toULongLong();
+ callback(key, def, i.value(), accountId, (accountId == 0) ? false : isTestAccount);
+ }
+ } else {
+ callback(key, def, val, 0, false);
+ }
+ };
+
+ const auto setValue = [this] (
+ const QString &key,
+ const Definition &def,
+ const QJsonValue &val,
+ uint64 accountId,
+ bool isTestAccount) {
+
+ const auto defType = def.type;
+ if (defType == SettingType::BoolSetting) {
+ if (val.isBool()) {
+ set(key, val.toBool(), accountId, isTestAccount);
+ } else if (val.isDouble()) {
+ set(key, val.toDouble() != 0.0, accountId, isTestAccount);
+ }
+ } else if (defType == SettingType::IntSetting) {
+ if (val.isDouble()) {
+ auto intValue = qFloor(val.toDouble());
+ set(key,
+ (def.limitHandler)
+ ? def.limitHandler(intValue)
+ : intValue,
+ accountId,
+ isTestAccount);
+ }
+ } else if (defType == SettingType::QStringSetting) {
+ if (val.isString()) {
+ set(key, val.toString(), accountId, isTestAccount);
+ }
+ } else if (defType == SettingType::QJsonArraySetting) {
+ if (val.isArray()) {
+ auto arrayValue = val.toArray();
+ set(key, (def.limitHandler)
+ ? def.limitHandler(arrayValue)
+ : arrayValue,
+ accountId,
+ isTestAccount);
+ }
+ }
+ };
+
+ for (const auto &[oldkey, newkey] : ReplacedOptionsMap) {
+ const auto &defIterator = DefinitionMap.find(newkey);
+ if (defIterator == DefinitionMap.end()) {
+ continue;
+ }
+ auto parts = oldkey.split(QChar('/'));
+ const auto val = (parts.size() > 1)
+ ? getObjectValue(parts, defIterator->second)
+ : settings.value(oldkey);
+
+ if (!val.isUndefined()) {
+ prepareAccountOptions(newkey, defIterator->second, val, setValue);
+ }
+ }
+
+ for (const auto &[key, def] : DefinitionMap) {
+ if (def.storage == SettingStorage::None) {
+ continue;
+ }
+ auto parts = key.split(QChar('/'));
+ const auto val = (parts.size() > 1)
+ ? getObjectValue(parts, def)
+ : settings.value(key);
+
+ if (!val.isUndefined()) {
+ prepareAccountOptions(key, def, val, setValue);
+ }
+ }
+ return true;
+}
+
+void Manager::writeDefaultFile() {
+ auto file = QFile(DefaultFilePath());
+ if (!file.open(QIODevice::WriteOnly)) {
+ return;
+ }
+ const char *defaultHeader = R"HEADER(
+// This is a list of default options for Kotatogram Desktop
+// Please don't modify it, its content is not used in any way
+// You can place your own options in the 'kotato-settings-custom.json' file
+
+)HEADER";
+ file.write(defaultHeader);
+ file.write(GenerateSettingsJson(true));
+}
+
+void Manager::writeCurrentSettings() {
+ auto file = QFile(CustomFilePath());
+ if (!file.open(QIODevice::WriteOnly)) {
+ return;
+ }
+ if (_jsonWriteTimer.isActive()) {
+ writing();
+ }
+ const char *customHeader = R"HEADER(
+// This file was automatically generated from current settings
+// It's better to edit it with app closed, so there will be no rewrites
+// You should restart app to see changes
+
+)HEADER";
+ file.write(customHeader);
+ file.write(GenerateSettingsJson());
+}
+
+void Manager::writeTimeout() {
+ writeCurrentSettings();
+}
+
+void Manager::writing() {
+ _jsonWriteTimer.cancel();
+}
+
+void Start() {
+ if (Data) return;
+
+ Data = std::make_unique();
+ Data->fill();
+}
+
+void Load() {
+ if (!Data) return;
+
+ Data->load();
+}
+
+void Write() {
+ if (!Data) return;
+
+ Data->write();
+}
+
+void Finish() {
+ if (!Data) return;
+
+ Data->write(true);
+}
+
+QVariant Get(const QString &key, uint64 accountId, bool isTestAccount) {
+ return (Data) ? Data->get(key, accountId, isTestAccount) : QVariant();
+}
+
+QVariant GetWithPending(const QString &key, uint64 accountId, bool isTestAccount) {
+ return (Data) ? Data->getWithPending(key, accountId, isTestAccount) : QVariant();
+}
+
+rpl::producer Events(const QString &key, uint64 accountId, bool isTestAccount) {
+ return (Data) ? Data->events(key, accountId, isTestAccount) : rpl::single(QString());
+}
+
+rpl::producer EventsWithPending(const QString &key, uint64 accountId, bool isTestAccount) {
+ return (Data) ? Data->eventsWithPending(key, accountId, isTestAccount) : rpl::single(QString());
+}
+
+void Set(const QString &key, QVariant value, uint64 accountId, bool isTestAccount) {
+ if (!Data) return;
+
+ Data->set(key, value, accountId, isTestAccount);
+}
+
+void SetAfterRestart(const QString &key, QVariant value, uint64 accountId, bool isTestAccount) {
+ if (!Data) return;
+
+ Data->setAfterRestart(key, value, accountId, isTestAccount);
+}
+
+void Reset(const QString &key, uint64 accountId, bool isTestAccount) {
+ if (!Data) return;
+
+ Data->reset(key, accountId, isTestAccount);
+}
+
+void ResetAfterRestart(const QString &key, uint64 accountId, bool isTestAccount) {
+ if (!Data) return;
+
+ Data->resetAfterRestart(key, accountId, isTestAccount);
+}
+
+} // namespace JsonSettings
+} // namespace Kotato
diff --git a/Telegram/SourceFiles/kotato/kotato_settings.h b/Telegram/SourceFiles/kotato/kotato_settings.h
new file mode 100644
index 000000000..c5dacb3ea
--- /dev/null
+++ b/Telegram/SourceFiles/kotato/kotato_settings.h
@@ -0,0 +1,115 @@
+/*
+This file is part of Kotatogram Desktop,
+the unofficial app based on Telegram Desktop.
+
+For license and copyright information please follow this link:
+https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL
+*/
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace Kotato {
+namespace JsonSettings {
+
+void Start();
+void Load();
+void Write();
+void Finish();
+
+[[nodiscard]] QVariant Get(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+[[nodiscard]] QVariant GetWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+[[nodiscard]] rpl::producer Events(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+[[nodiscard]] rpl::producer EventsWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+void Set(
+ const QString &key,
+ QVariant value,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+void SetAfterRestart(
+ const QString &key,
+ QVariant value,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+void Reset(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+void ResetAfterRestart(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false);
+
+inline bool GetBool(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return Get(key, accountId, isTestAccount).toBool();
+}
+
+inline int GetInt(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return Get(key, accountId, isTestAccount).toInt();
+}
+
+inline QString GetString(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return Get(key, accountId, isTestAccount).toString();
+}
+
+inline QJsonArray GetJsonArray(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return Get(key, accountId, isTestAccount).toJsonArray();
+}
+
+inline bool GetBoolWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return GetWithPending(key, accountId, isTestAccount).toBool();
+}
+
+inline int GetIntWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return GetWithPending(key, accountId, isTestAccount).toInt();
+}
+
+inline QString GetStringWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return GetWithPending(key, accountId, isTestAccount).toString();
+}
+
+inline QJsonArray GetJsonArrayWithPending(
+ const QString &key,
+ uint64 accountId = 0,
+ bool isTestAccount = false) {
+ return GetWithPending(key, accountId, isTestAccount).toJsonArray();
+}
+
+} // namespace JsonSettings
+} // namespace Kotato
diff --git a/Telegram/SourceFiles/kotato/kotato_settings_menu.cpp b/Telegram/SourceFiles/kotato/kotato_settings_menu.cpp
new file mode 100644
index 000000000..1b474eb24
--- /dev/null
+++ b/Telegram/SourceFiles/kotato/kotato_settings_menu.cpp
@@ -0,0 +1,154 @@
+/*
+This file is part of Kotatogram Desktop,
+the unofficial app based on Telegram Desktop.
+
+For license and copyright information please follow this link:
+https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL
+*/
+#include "kotato/kotato_settings_menu.h"
+
+#include "kotato/kotato_lang.h"
+#include "kotato/kotato_settings.h"
+#include "base/options.h"
+#include "base/platform/base_platform_info.h"
+#include "settings/settings_common.h"
+#include "settings/settings_chat.h"
+#include "ui/wrap/vertical_layout.h"
+#include "ui/wrap/slide_wrap.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/labels.h"
+#include "ui/widgets/checkbox.h"
+#include "ui/widgets/continuous_sliders.h"
+#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
+#include "boxes/connection_box.h"
+#include "kotato/boxes/kotato_fonts_box.h"
+#include "kotato/boxes/kotato_radio_box.h"
+#include "boxes/about_box.h"
+#include "ui/boxes/confirm_box.h"
+#include "platform/platform_specific.h"
+#include "platform/platform_file_utilities.h"
+#include "window/window_peer_menu.h"
+#include "window/window_session_controller.h"
+#include "lang/lang_keys.h"
+#include "core/update_checker.h"
+#include "core/application.h"
+#include "storage/localstorage.h"
+#include "data/data_session.h"
+#include "data/data_cloud_themes.h"
+#include "main/main_session.h"
+#include "mainwindow.h"
+#include "facades.h"
+#include "styles/style_settings.h"
+#include "ui/platform/ui_platform_utility.h"
+
+namespace Settings {
+
+namespace {
+
+
+} // namespace
+
+#define SettingsMenuJsonSwitch(LangKey, Option) AddButton( \
+ container, \
+ rktr(#LangKey), \
+ st::settingsButton \
+)->toggleOn( \
+ rpl::single(::Kotato::JsonSettings::GetBool(#Option)) \
+)->toggledValue( \
+) | rpl::filter([](bool enabled) { \
+ return (enabled != ::Kotato::JsonSettings::GetBool(#Option)); \
+}) | rpl::start_with_next([](bool enabled) { \
+ ::Kotato::JsonSettings::Set(#Option, enabled); \
+ ::Kotato::JsonSettings::Write(); \
+}, container->lifetime());
+
+void SetupKotatoChats(
+ not_null controller,
+ not_null container) {
+ AddSkip(container);
+ AddSubsectionTitle(container, rktr("ktg_settings_chats"));
+
+
+ AddSkip(container);
+ AddDivider(container);
+ AddSkip(container);
+}
+
+void SetupKotatoMessages(not_null container) {
+ AddSubsectionTitle(container, rktr("ktg_settings_messages"));
+
+ AddSkip(container);
+}
+
+void SetupKotatoForward(not_null container) {
+ AddDivider(container);
+ AddSkip(container);
+ AddSubsectionTitle(container, rktr("ktg_settings_forward"));
+
+
+ AddSkip(container);
+ AddDividerText(container, rktr("ktg_settings_forward_chat_on_click_description"));
+}
+
+void SetupKotatoNetwork(not_null container) {
+ AddSkip(container);
+ AddSubsectionTitle(container, rktr("ktg_settings_network"));
+
+
+ AddSkip(container);
+}
+
+void SetupKotatoFolders(
+ not_null controller,
+ not_null container) {
+ AddDivider(container);
+ AddSkip(container);
+ AddSubsectionTitle(container, rktr("ktg_settings_filters"));
+
+
+ AddSkip(container);
+}
+
+void SetupKotatoSystem(
+ not_null controller,
+ not_null container) {
+ AddDivider(container);
+ AddSkip(container);
+ AddSubsectionTitle(container, rktr("ktg_settings_system"));
+
+
+ AddSkip(container);
+}
+
+void SetupKotatoOther(
+ not_null controller,
+ not_null container) {
+ AddDivider(container);
+ AddSkip(container);
+ AddSubsectionTitle(container, rktr("ktg_settings_other"));
+
+}
+
+Kotato::Kotato(
+ QWidget *parent,
+ not_null controller)
+: Section(parent) {
+ setupContent(controller);
+}
+
+void Kotato::setupContent(not_null controller) {
+ const auto content = Ui::CreateChild(this);
+
+ SetupKotatoChats(controller, content);
+ SetupKotatoMessages(content);
+ SetupKotatoForward(content);
+ SetupKotatoNetwork(content);
+ SetupKotatoFolders(controller, content);
+ SetupKotatoSystem(controller, content);
+ SetupKotatoOther(controller, content);
+
+ Ui::ResizeFitChild(this, content);
+}
+
+} // namespace Settings
+
diff --git a/Telegram/SourceFiles/kotato/kotato_settings_menu.h b/Telegram/SourceFiles/kotato/kotato_settings_menu.h
new file mode 100644
index 000000000..66194d31d
--- /dev/null
+++ b/Telegram/SourceFiles/kotato/kotato_settings_menu.h
@@ -0,0 +1,39 @@
+/*
+This file is part of Kotatogram Desktop,
+the unofficial app based on Telegram Desktop.
+
+For license and copyright information please follow this link:
+https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL
+*/
+#pragma once
+
+#include "settings/settings_common.h"
+
+class BoxContent;
+
+namespace Settings {
+
+void SetupKotatoChats(not_null container);
+void SetupKotatoMessages(not_null container);
+void SetupKotatoForward(not_null container);
+void SetupKotatoNetwork(not_null container);
+void SetupKotatoFolders(
+ not_null controller,
+ not_null container);
+void SetupKotatoSystem(
+ not_null controller,
+ not_null container);
+void SetupKotatoOther(not_null container);
+
+class Kotato : public Section {
+public:
+ Kotato(
+ QWidget *parent,
+ not_null controller);
+
+private:
+ void setupContent(not_null controller);
+
+};
+
+} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style
index 69ecc186c..20ea09da3 100644
--- a/Telegram/SourceFiles/settings/settings.style
+++ b/Telegram/SourceFiles/settings/settings.style
@@ -63,6 +63,7 @@ settingsIconFaq: icon {{ "settings/settings_faq", menuIconFg }};
settingsIconStickers: icon {{ "settings/settings_stickers", menuIconFg }};
settingsIconEmoji: icon {{ "settings/settings_emoji", menuIconFg }};
settingsIconThemes: icon {{ "settings/settings_themes", menuIconFg }};
+settingsIconKotato: icon {{ "settings/settings_kotato", menuIconFg }};
settingsSetPhotoSkip: 7px;
diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp
index 0780809ad..41514655e 100644
--- a/Telegram/SourceFiles/settings/settings_common.cpp
+++ b/Telegram/SourceFiles/settings/settings_common.cpp
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_folders.h"
#include "settings/settings_calls.h"
#include "settings/settings_experimental.h"
+#include "kotato/kotato_settings_menu.h"
#include "core/application.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/vertical_layout.h"
@@ -62,6 +63,8 @@ object_ptr CreateSection(
return object_ptr(parent, controller);
case Type::Experimental:
return object_ptr(parent, controller);
+ case Type::Kotato:
+ return object_ptr(parent, controller);
}
Unexpected("Settings section type in Widget::createInnerWidget.");
}
@@ -223,16 +226,18 @@ void FillMenu(
Core::App().domain().addActivated(MTP::Environment{});
}, &st::menuIconAddAccount);
}
- if (!controller->session().supportMode()) {
+ if (type != Type::Kotato && !controller->session().supportMode()) {
addAction(
tr::lng_settings_information(tr::now),
[=] { showOther(Type::Information); },
&st::menuIconInfo);
}
- addAction(
- tr::lng_settings_logout(tr::now),
- [=] { window->showLogoutConfirmation(); },
- &st::menuIconLeave);
+ if (type != Type::Kotato) {
+ addAction(
+ tr::lng_settings_logout(tr::now),
+ [=] { window->showLogoutConfirmation(); },
+ &st::menuIconLeave);
+ }
}
}
diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h
index c4520ad1b..11a009d78 100644
--- a/Telegram/SourceFiles/settings/settings_common.h
+++ b/Telegram/SourceFiles/settings/settings_common.h
@@ -42,6 +42,7 @@ enum class Type {
Folders,
Calls,
Experimental,
+ Kotato,
};
using Button = Ui::SettingsButton;
diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp
index 9d8b2d6ba..371b65f10 100644
--- a/Telegram/SourceFiles/settings/settings_main.cpp
+++ b/Telegram/SourceFiles/settings/settings_main.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/settings_main.h"
+#include "kotato/kotato_lang.h"
#include "settings/settings_common.h"
#include "settings/settings_codes.h"
#include "settings/settings_chat.h"
@@ -159,6 +160,10 @@ void SetupSections(
tr::lng_settings_advanced(),
Type::Advanced,
&st::settingsIconGeneral);
+ addSection(
+ rktr("ktg_settings_kotato"),
+ Type::Kotato,
+ &st::settingsIconKotato);
SetupLanguageButton(container);