mirror of
https://github.com/kotatogram/kotatogram-desktop
synced 2025-08-30 14:17:45 +00:00
[Core] Settings system
This commit is contained in:
parent
f84850d890
commit
e951c8064b
@ -1023,8 +1023,14 @@ PRIVATE
|
||||
iv/iv_delegate_impl.h
|
||||
iv/iv_instance.cpp
|
||||
iv/iv_instance.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
|
||||
|
20
Telegram/Resources/default_kotato-settings-custom.json
Normal file
20
Telegram/Resources/default_kotato-settings-custom.json
Normal file
@ -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
|
||||
}
|
BIN
Telegram/Resources/icons/settings/kotato.png
Normal file
BIN
Telegram/Resources/icons/settings/kotato.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 721 B |
BIN
Telegram/Resources/icons/settings/kotato@2x.png
Normal file
BIN
Telegram/Resources/icons/settings/kotato@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/kotato@3x.png
Normal file
BIN
Telegram/Resources/icons/settings/kotato@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -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": ""
|
||||
}
|
||||
|
@ -58,7 +58,8 @@
|
||||
</qresource>
|
||||
<qresource prefix="/misc">
|
||||
<file alias="default_shortcuts-custom.json">../../default_shortcuts-custom.json</file>
|
||||
<file alias="io.github.kotatogram.desktop">../../../../lib/xdg/io.github.kotatogram.desktop</file>
|
||||
<file alias="default_kotato-settings-custom.json">../../default_kotato-settings-custom.json</file>
|
||||
<file alias="io.github.kotatogram.desktop">../../../../lib/xdg/io.github.kotatogram.desktop</file>
|
||||
</qresource>
|
||||
<qresource prefix="/ktg_lang">
|
||||
<file alias="en.json">../../langs/rewrites/en.json</file>
|
||||
|
@ -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"
|
||||
@ -356,6 +357,9 @@ void Launcher::initHighDpi() {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -367,6 +371,7 @@ int Launcher::exec() {
|
||||
// Must be started before Platform is started.
|
||||
Logs::start();
|
||||
base::options::init(cWorkingDir() + "tdata/experimental_options.json");
|
||||
Kotato::JsonSettings::Load();
|
||||
|
||||
// Must be called after options are inited.
|
||||
initHighDpi();
|
||||
@ -408,6 +413,7 @@ int Launcher::exec() {
|
||||
CrashReports::Finish();
|
||||
ThirdParty::finish();
|
||||
Platform::finish();
|
||||
Kotato::JsonSettings::Finish();
|
||||
Logs::finish();
|
||||
|
||||
return result;
|
||||
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "core/local_url_handlers.h"
|
||||
|
||||
#include "kotato/kotato_settings_menu.h"
|
||||
#include "api/api_authorizations.h"
|
||||
#include "api/api_confirm_phone.h"
|
||||
#include "api/api_chat_filters.h"
|
||||
@ -669,6 +670,8 @@ bool ResolveSettings(
|
||||
return ::Settings::GlobalTTLId();
|
||||
} else if (section == u"information"_q) {
|
||||
return ::Settings::Information::Id();
|
||||
} else if (section == u"kotato"_q) {
|
||||
return ::Settings::Kotato::Id();
|
||||
}
|
||||
return ::Settings::Main::Id();
|
||||
}();
|
||||
@ -1255,7 +1258,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
ResolvePrivatePost
|
||||
},
|
||||
{
|
||||
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile)?$"_q,
|
||||
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile|/kotato)?$"_q,
|
||||
ResolveSettings
|
||||
},
|
||||
{
|
||||
|
177
Telegram/SourceFiles/kotato/boxes/kotato_radio_box.cpp
Normal file
177
Telegram/SourceFiles/kotato/boxes/kotato_radio_box.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
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<QString(int)> labelGetter,
|
||||
Fn<void(int)> 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<QString(int)> labelGetter,
|
||||
Fn<void(int)> 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<QString(int)> labelGetter,
|
||||
Fn<QString(int)> descriptionGetter,
|
||||
Fn<void(int)> 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<QString(int)> labelGetter,
|
||||
Fn<QString(int)> descriptionGetter,
|
||||
Fn<void(int)> 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<Ui::FlatLabel>(_content, _description, st::boxDividerLabel),
|
||||
style::margins(
|
||||
st::boxPadding.left(),
|
||||
0,
|
||||
st::boxPadding.right(),
|
||||
st::boxPadding.bottom()));
|
||||
}
|
||||
|
||||
_group = std::make_shared<Ui::RadiobuttonGroup>(_startValue);
|
||||
|
||||
for (auto i = 0; i != _valueCount; ++i) {
|
||||
const auto description = _descriptionGetter
|
||||
? _descriptionGetter(i)
|
||||
: QString();
|
||||
|
||||
_content->add(
|
||||
object_ptr<Ui::Radiobutton>(
|
||||
_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<Ui::FlatLabel>(_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<Ui::OverrideMargins>(this, std::move(_owned));
|
||||
setDimensionsToContent(st::boxWidth, wrap.data());
|
||||
setInnerWidget(std::move(wrap));
|
||||
}
|
||||
|
||||
void RadioBox::save() {
|
||||
_saveCallback(_group->current());
|
||||
if (_warnRestart) {
|
||||
const auto box = std::make_shared<QPointer<BoxContent>>();
|
||||
|
||||
*box = getDelegate()->show(
|
||||
Ui::MakeConfirmBox({
|
||||
.text = tr::lng_settings_need_restart(),
|
||||
.confirmed = [] { Core::Restart(); },
|
||||
.cancelled = crl::guard(this, [=] { closeBox(); box->data()->closeBox(); }),
|
||||
.confirmText = tr::lng_settings_restart_now(),
|
||||
.cancelText = tr::lng_settings_restart_later(),
|
||||
}));
|
||||
} else {
|
||||
closeBox();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Kotato
|
80
Telegram/SourceFiles/kotato/boxes/kotato_radio_box.h
Normal file
80
Telegram/SourceFiles/kotato/boxes/kotato_radio_box.h
Normal file
@ -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<QString(int)> labelGetter,
|
||||
Fn<void(int)> saveCallback,
|
||||
bool warnRestart = false);
|
||||
RadioBox(
|
||||
QWidget* parent,
|
||||
const QString &title,
|
||||
const QString &description,
|
||||
int currentValue,
|
||||
int valueCount,
|
||||
Fn<QString(int)> labelGetter,
|
||||
Fn<void(int)> saveCallback,
|
||||
bool warnRestart = false);
|
||||
RadioBox(
|
||||
QWidget* parent,
|
||||
const QString &title,
|
||||
int currentValue,
|
||||
int valueCount,
|
||||
Fn<QString(int)> labelGetter,
|
||||
Fn<QString(int)> descriptionGetter,
|
||||
Fn<void(int)> saveCallback,
|
||||
bool warnRestart = false);
|
||||
RadioBox(
|
||||
QWidget* parent,
|
||||
const QString &title,
|
||||
const QString &description,
|
||||
int currentValue,
|
||||
int valueCount,
|
||||
Fn<QString(int)> labelGetter,
|
||||
Fn<QString(int)> descriptionGetter,
|
||||
Fn<void(int)> saveCallback,
|
||||
bool warnRestart = false);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
private:
|
||||
void save();
|
||||
|
||||
QString _title;
|
||||
QString _description;
|
||||
int _startValue;
|
||||
int _valueCount;
|
||||
Fn<QString(int)> _labelGetter;
|
||||
Fn<QString(int)> _descriptionGetter;
|
||||
Fn<void(int)> _saveCallback;
|
||||
bool _warnRestart = false;
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> _group;
|
||||
|
||||
object_ptr<Ui::VerticalLayout> _owned;
|
||||
not_null<Ui::VerticalLayout*> _content;
|
||||
};
|
||||
|
||||
} // namespace Kotato
|
770
Telegram/SourceFiles/kotato/kotato_settings.cpp
Normal file
770
Telegram/SourceFiles/kotato/kotato_settings.cpp
Normal file
@ -0,0 +1,770 @@
|
||||
/*
|
||||
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 "ui/widgets/input_fields.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
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<QString> events(
|
||||
const QString &key,
|
||||
uint64 accountId = 0,
|
||||
bool isTestAccount = false);
|
||||
[[nodiscard]] rpl::producer<QString> 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<QString> _eventStream;
|
||||
rpl::event_stream<QString> _pendingEventStream;
|
||||
QHash<QString, QVariant> _settingsHashMap;
|
||||
QHash<QString, QVariant> _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<QVariant(QVariant)>;
|
||||
|
||||
CheckHandler IntLimit(int min, int max, int defaultValue) {
|
||||
return [=] (QVariant value) -> QVariant {
|
||||
if (value.canConvert<int>()) {
|
||||
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<int>()) {
|
||||
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<QString, Definition, std::greater<QString>> DefinitionMap {
|
||||
|
||||
// Non-stored settings
|
||||
// Stored settings
|
||||
};
|
||||
|
||||
using OldOptionKey = QString;
|
||||
using NewOptionKey = QString;
|
||||
|
||||
const std::map<OldOptionKey, NewOptionKey, std::greater<OldOptionKey>> 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<Manager> 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<QString> 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<QString> 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<void(const QString &,
|
||||
const Definition &,
|
||||
const QJsonValue &,
|
||||
uint64,
|
||||
bool)> 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<Manager>();
|
||||
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<QString> Events(const QString &key, uint64 accountId, bool isTestAccount) {
|
||||
return (Data) ? Data->events(key, accountId, isTestAccount) : rpl::single(QString());
|
||||
}
|
||||
|
||||
rpl::producer<QString> 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
|
115
Telegram/SourceFiles/kotato/kotato_settings.h
Normal file
115
Telegram/SourceFiles/kotato/kotato_settings.h
Normal file
@ -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 <rpl/producer.h>
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
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<QString> Events(
|
||||
const QString &key,
|
||||
uint64 accountId = 0,
|
||||
bool isTestAccount = false);
|
||||
[[nodiscard]] rpl::producer<QString> 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
|
167
Telegram/SourceFiles/kotato/kotato_settings_menu.cpp
Normal file
167
Telegram/SourceFiles/kotato/kotato_settings_menu.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
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_controller.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 "styles/style_boxes.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/vertical_list.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
#define SettingsMenuJsonSwitch(LangKey, Option) container->add(object_ptr<Button>( \
|
||||
container, \
|
||||
rktr(#LangKey), \
|
||||
st::settingsButtonNoIcon \
|
||||
))->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<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSubsectionTitle(container, rktr("ktg_settings_chats"));
|
||||
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
|
||||
void SetupKotatoMessages(not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddSubsectionTitle(container, rktr("ktg_settings_messages"));
|
||||
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
|
||||
void SetupKotatoForward(not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSubsectionTitle(container, rktr("ktg_settings_forward"));
|
||||
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDividerText(container, rktr("ktg_settings_forward_chat_on_click_description"));
|
||||
}
|
||||
|
||||
void SetupKotatoNetwork(not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSubsectionTitle(container, rktr("ktg_settings_network"));
|
||||
|
||||
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
|
||||
void SetupKotatoFolders(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSubsectionTitle(container, rktr("ktg_settings_filters"));
|
||||
|
||||
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
|
||||
void SetupKotatoSystem(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSubsectionTitle(container, rktr("ktg_settings_system"));
|
||||
|
||||
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
|
||||
void SetupKotatoOther(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSubsectionTitle(container, rktr("ktg_settings_other"));
|
||||
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
|
||||
Kotato::Kotato(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: Section(parent)
|
||||
, _controller(controller) {
|
||||
setupContent(controller);
|
||||
}
|
||||
|
||||
rpl::producer<QString> Kotato::title() {
|
||||
return rktr("ktg_settings_kotato");
|
||||
}
|
||||
|
||||
void Kotato::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
|
||||
const auto window = &_controller->window();
|
||||
}
|
||||
|
||||
void Kotato::setupContent(not_null<Window::SessionController*> controller) {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(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
|
||||
|
44
Telegram/SourceFiles/kotato/kotato_settings_menu.h
Normal file
44
Telegram/SourceFiles/kotato/kotato_settings_menu.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
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_session.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
void SetupKotatoChats(not_null<Ui::VerticalLayout*> container);
|
||||
void SetupKotatoMessages(not_null<Ui::VerticalLayout*> container);
|
||||
void SetupKotatoForward(not_null<Ui::VerticalLayout*> container);
|
||||
void SetupKotatoNetwork(not_null<Ui::VerticalLayout*> container);
|
||||
void SetupKotatoFolders(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container);
|
||||
void SetupKotatoSystem(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container);
|
||||
void SetupKotatoOther(not_null<Ui::VerticalLayout*> container);
|
||||
|
||||
class Kotato : public Section<Kotato> {
|
||||
public:
|
||||
Kotato(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
void fillTopBarMenu(
|
||||
const Ui::Menu::MenuCallback &addAction) override;
|
||||
|
||||
private:
|
||||
void setupContent(not_null<Window::SessionController*> controller);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Settings
|
@ -78,6 +78,7 @@ settingsIconChat: icon {{ "settings/chat", settingsIconFg }};
|
||||
settingsIconInterfaceScale: icon {{ "settings/interface_scale", settingsIconFg }};
|
||||
settingsIconStickers: icon {{ "settings/stickers", settingsIconFg }};
|
||||
settingsIconEmoji: icon {{ "settings/emoji", settingsIconFg }};
|
||||
settingsIconKotato: icon {{ "settings/kotato", settingsIconFg }};
|
||||
|
||||
settingsPremiumIconWallpapers: icon {{ "settings/photo", settingsIconFg }};
|
||||
settingsPremiumIconStories: icon {{ "settings/stories", settingsIconFg }};
|
||||
@ -679,3 +680,8 @@ settingsChatLinkField: InputField(defaultInputField) {
|
||||
|
||||
font: normalFont;
|
||||
}
|
||||
|
||||
ktgSettingsSliderLabel: LabelSimple(defaultLabelSimple) {
|
||||
textFg: windowFg;
|
||||
font: boxTextFont;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "settings/settings_common_session.h"
|
||||
|
||||
#include "kotato/kotato_settings_menu.h"
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/application.h"
|
||||
@ -37,7 +38,8 @@ namespace Settings {
|
||||
bool HasMenu(Type type) {
|
||||
return (type == ::Settings::CloudPasswordEmailConfirmId())
|
||||
|| (type == Main::Id())
|
||||
|| (type == Chat::Id());
|
||||
|| (type == Chat::Id())
|
||||
|| (type == Kotato::Id());
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "settings/settings_main.h"
|
||||
|
||||
#include "kotato/kotato_lang.h"
|
||||
#include "kotato/kotato_settings.h"
|
||||
#include "kotato/kotato_settings_menu.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "settings/settings_business.h"
|
||||
@ -388,6 +391,10 @@ void SetupSections(
|
||||
tr::lng_settings_section_devices(),
|
||||
Calls::Id(),
|
||||
{ &st::menuIconUnmute });
|
||||
addSection(
|
||||
rktr("ktg_settings_kotato"),
|
||||
Kotato::Id(),
|
||||
{ &st::settingsIconKotato });
|
||||
|
||||
SetupPowerSavingButton(&controller->window(), container);
|
||||
SetupLanguageButton(&controller->window(), container);
|
||||
|
Loading…
x
Reference in New Issue
Block a user